mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-04 13:11:13 +01:00
Hardcover tweaks (#720)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user