Hardcover tweaks (#720)

This commit is contained in:
Alex
2026-03-07 18:16:40 +00:00
committed by GitHub
parent 9d08bb3ef1
commit a7db7f04e9
8 changed files with 46 additions and 7 deletions
+16 -3
View File
@@ -79,6 +79,7 @@ import {
import { bookFromRequestData } from './utils/requestFulfil';
import { emitBookTargetChange, onBookTargetChange } from './utils/bookTargetEvents';
import { bookSupportsTargets } from './utils/bookTargetLoader';
import { getDynamicOptionGroup } from './components/shared/DynamicDropdown';
import { policyTrace } from './utils/policyTrace';
import { SearchModeProvider } from './contexts/SearchModeContext';
import { useSocket } from './contexts/SocketContext';
@@ -1089,16 +1090,28 @@ function App() {
[]
);
// When downloading a book while browsing a Hardcover list, automatically
// remove it from that list (fire-and-forget).
// When downloading a book while browsing a Hardcover list the user owns,
// automatically remove it from that list (fire-and-forget).
const searchFieldLabelsRef = useRef(searchFieldLabels);
searchFieldLabelsRef.current = searchFieldLabels;
const metadataConfigRef = useRef(activeMetadataConfig);
metadataConfigRef.current = activeMetadataConfig;
const removeBookFromActiveList = useCallback((book: Book) => {
if (!bookSupportsTargets(book)) return;
const activeList = searchFieldValuesRef.current.hardcover_list;
if (!activeList) return;
const target = String(activeList);
// Only auto-remove from lists the user owns (My Books / My Lists)
const listField = metadataConfigRef.current?.search_fields.find(
(f) => f.key === 'hardcover_list' && f.type === 'DynamicSelectSearchField',
);
if (listField && listField.type === 'DynamicSelectSearchField') {
const group = getDynamicOptionGroup(listField.options_endpoint, target);
if (group && group !== 'My Books' && group !== 'My Lists') return;
}
void setBookTargetState(book.provider!, book.provider_id!, target, false).then((result) => {
if (result.changed) {
emitBookTargetChange({
@@ -2135,7 +2148,7 @@ function App() {
getButtonState={getDirectActionButtonState}
getUniversalButtonState={getUniversalActionButtonState}
sortValue={visibleResultsSort}
showSortControl={!activeQueryUsesSeriesBrowse && !activeQueryUsesListBrowse}
showSortControl={!activeQueryUsesSeriesBrowse && !activeQueryUsesListBrowse && !resultsSourceUrl}
onSortChange={(value) => {
const request = buildCurrentSearchRequest(value);
const shouldPersistAppliedSort = !(
@@ -217,7 +217,7 @@ export const BookTargetDropdown = ({
<button
type="button"
onClick={(e) => { e.stopPropagation(); toggle(); }}
className={`flex items-center justify-center rounded-full transition-all duration-200 focus:outline-none ${className ?? 'p-1.5 sm:p-2 text-gray-600 dark:text-gray-200 hover-action'}`}
className={`flex items-center justify-center rounded-full transition-colors duration-200 focus:outline-none ${className ?? 'p-1.5 sm:p-2 text-gray-600 dark:text-gray-200 hover-action'}`}
aria-label="Hardcover Lists"
title={count > 0 ? `On ${count} Hardcover list${count > 1 ? 's' : ''}` : 'Hardcover Lists'}
>
@@ -25,6 +25,13 @@ const buildOptions = (
}));
};
/** Look up the group of a cached option by endpoint and value. */
export const getDynamicOptionGroup = (endpoint: string, value: string): string | undefined => {
const cached = optionsCache.get(endpoint);
if (!cached) return undefined;
return cached.find((o) => o.value === value)?.group;
};
export const DynamicDropdown = ({
endpoint,
value,
+12 -1
View File
@@ -49,8 +49,9 @@ interface UseSearchReturn {
isLoadingMore: boolean;
loadMore: (config: AppConfig | null, searchMode?: SearchMode) => Promise<void>;
totalFound: number;
// Source URL for the current result set (e.g. Hardcover list page)
// Source URL and title for the current result set (e.g. Hardcover list page)
resultsSourceUrl: string | undefined;
resultsSourceTitle: string | undefined;
}
export function useSearch(options: UseSearchOptions): UseSearchReturn {
@@ -82,6 +83,7 @@ export function useSearch(options: UseSearchOptions): UseSearchReturn {
const [isLoadingMore, setIsLoadingMore] = useState(false);
const [totalFound, setTotalFound] = useState(0);
const [resultsSourceUrl, setResultsSourceUrl] = useState<string | undefined>();
const [resultsSourceTitle, setResultsSourceTitle] = useState<string | undefined>();
// Store last search params for loadMore
const lastSearchParamsRef = useRef<{
@@ -167,6 +169,7 @@ export function useSearch(options: UseSearchOptions): UseSearchReturn {
setTotalFound(0);
setCurrentPage(1);
setResultsSourceUrl(undefined);
setResultsSourceTitle(undefined);
lastSearchParamsRef.current = null;
return;
}
@@ -193,6 +196,11 @@ export function useSearch(options: UseSearchOptions): UseSearchReturn {
setHasMore(result.hasMore);
setTotalFound(result.totalFound);
setResultsSourceUrl(result.sourceUrl);
setResultsSourceTitle(result.sourceTitle);
// Replace URL in search input with list title for display
if (result.sourceTitle && searchQuery) {
setSearchInput(result.sourceTitle);
}
// Store params for loadMore
lastSearchParamsRef.current = {
query: searchQuery,
@@ -206,6 +214,7 @@ export function useSearch(options: UseSearchOptions): UseSearchReturn {
setHasMore(false);
setTotalFound(0);
setResultsSourceUrl(undefined);
setResultsSourceTitle(undefined);
showToast('No results found', 'error');
}
} catch (error) {
@@ -276,6 +285,7 @@ export function useSearch(options: UseSearchOptions): UseSearchReturn {
setHasMore(false);
setTotalFound(0);
setResultsSourceUrl(undefined);
setResultsSourceTitle(undefined);
lastSearchParamsRef.current = null;
}, [onSearchReset]);
@@ -340,5 +350,6 @@ export function useSearch(options: UseSearchOptions): UseSearchReturn {
loadMore,
totalFound,
resultsSourceUrl,
resultsSourceTitle,
};
}
+3
View File
@@ -225,6 +225,7 @@ interface MetadataSearchResponse {
total_found?: number;
has_more?: boolean;
source_url?: string;
source_title?: string;
}
// Metadata search result with pagination info
@@ -234,6 +235,7 @@ export interface MetadataSearchResult {
totalFound: number;
hasMore: boolean;
sourceUrl?: string;
sourceTitle?: string;
}
export interface DynamicFieldOption {
@@ -300,6 +302,7 @@ export const searchMetadata = async (
totalFound: response.total_found || 0,
hasMore: response.has_more || false,
sourceUrl: response.source_url,
sourceTitle: response.source_title,
};
};