forked from akai/readest
fix(bookshelf): merge groups and ungrouped books then sort them together (#3169)
This commit is contained in:
@@ -26,6 +26,9 @@ import {
|
||||
createWithinGroupSorter,
|
||||
ensureLibraryGroupByType,
|
||||
ensureLibrarySortByType,
|
||||
getBookSortValue,
|
||||
getGroupSortValue,
|
||||
compareSortValues,
|
||||
} from '../utils/libraryUtils';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
|
||||
@@ -180,10 +183,6 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
const ungroupedBooks = currentBookshelfItems.filter((item): item is Book => 'format' in item);
|
||||
const groups = currentBookshelfItems.filter((item): item is BooksGroup => 'books' in item);
|
||||
|
||||
// Sort groups by aggregate value
|
||||
const groupSorter = createGroupSorter(sortBy, uiLanguage);
|
||||
groups.sort((a, b) => groupSorter(a, b) * sortOrderMultiplier);
|
||||
|
||||
// Sort books within each group
|
||||
const withinGroupSorter = createWithinGroupSorter(groupBy, sortBy, uiLanguage);
|
||||
groups.forEach((group) => {
|
||||
@@ -192,15 +191,45 @@ const Bookshelf: React.FC<BookshelfProps> = ({
|
||||
|
||||
// Sort ungrouped books - use within-group sorter if we're inside a group
|
||||
// (for series, this ensures books are sorted by series index)
|
||||
const bookSorter = createBookSorter(sortBy, uiLanguage);
|
||||
if (groupId && groupBy !== LibraryGroupByType.Manual && groupBy !== LibraryGroupByType.None) {
|
||||
ungroupedBooks.sort((a, b) => withinGroupSorter(a, b) * sortOrderMultiplier);
|
||||
} else {
|
||||
const bookSorter = createBookSorter(sortBy, uiLanguage);
|
||||
ungroupedBooks.sort((a, b) => bookSorter(a, b) * sortOrderMultiplier);
|
||||
}
|
||||
|
||||
// Return groups first, then ungrouped books
|
||||
return [...groups, ...ungroupedBooks];
|
||||
// Merge groups and ungrouped books, then sort them together
|
||||
const allItems: (Book | BooksGroup)[] = [...groups, ...ungroupedBooks];
|
||||
const groupSorter = createGroupSorter(sortBy, uiLanguage);
|
||||
|
||||
allItems.sort((a, b) => {
|
||||
const isAGroup = 'books' in a;
|
||||
const isBGroup = 'books' in b;
|
||||
|
||||
// If both are groups, use group sorter
|
||||
if (isAGroup && isBGroup) {
|
||||
return groupSorter(a, b) * sortOrderMultiplier;
|
||||
}
|
||||
|
||||
// If both are books, use book sorter
|
||||
if (!isAGroup && !isBGroup) {
|
||||
return bookSorter(a, b) * sortOrderMultiplier;
|
||||
}
|
||||
|
||||
// One is a group, one is a book - compare their sort values
|
||||
if (isAGroup && !isBGroup) {
|
||||
const groupValue = getGroupSortValue(a, sortBy);
|
||||
const bookValue = getBookSortValue(b, sortBy);
|
||||
return compareSortValues(groupValue, bookValue, uiLanguage) * sortOrderMultiplier;
|
||||
} else if (!isAGroup && isBGroup) {
|
||||
const bookValue = getBookSortValue(a, sortBy);
|
||||
const groupValue = getGroupSortValue(b, sortBy);
|
||||
return compareSortValues(bookValue, groupValue, uiLanguage) * sortOrderMultiplier;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return allItems;
|
||||
}, [sortOrder, sortBy, groupBy, groupId, uiLanguage, currentBookshelfItems]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -295,6 +295,38 @@ export const createWithinGroupSorter =
|
||||
return createBookSorter(sortBy, uiLanguage)(a, b);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the sort value from a book for comparison with groups.
|
||||
*/
|
||||
export const getBookSortValue = (book: Book, sortBy: LibrarySortByType): number | string => {
|
||||
switch (sortBy) {
|
||||
case LibrarySortByType.Title:
|
||||
return formatTitle(book.title);
|
||||
|
||||
case LibrarySortByType.Author:
|
||||
return formatAuthors(book.author, book?.primaryLanguage || 'en', true);
|
||||
|
||||
case LibrarySortByType.Updated:
|
||||
return book.updatedAt;
|
||||
|
||||
case LibrarySortByType.Created:
|
||||
return book.createdAt;
|
||||
|
||||
case LibrarySortByType.Format:
|
||||
return book.format;
|
||||
|
||||
case LibrarySortByType.Published: {
|
||||
const published = book.metadata?.published;
|
||||
if (!published) return 0;
|
||||
const publishedTime = new Date(published).getTime();
|
||||
return isNaN(publishedTime) ? 0 : publishedTime;
|
||||
}
|
||||
|
||||
default:
|
||||
return book.updatedAt;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the aggregate sort value from a group for sorting groups.
|
||||
*/
|
||||
@@ -344,6 +376,27 @@ export const getGroupSortValue = (
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare two sort values (string or number) for sorting.
|
||||
*/
|
||||
export const compareSortValues = (
|
||||
aValue: number | string,
|
||||
bValue: number | string,
|
||||
uiLanguage: string,
|
||||
): number => {
|
||||
// String comparison for text-based sorts
|
||||
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
||||
return aValue.localeCompare(bValue, uiLanguage || navigator.language);
|
||||
}
|
||||
|
||||
// Numeric comparison for date-based sorts
|
||||
if (typeof aValue === 'number' && typeof bValue === 'number') {
|
||||
return aValue - bValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a sorter for groups themselves based on sort criteria.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user