Implement sorting functionality in NavList component with UI for sort options

This commit is contained in:
troyeguo committed 2026-08-08 16:41:26 +08:00
1 parent 5f3fd700fa
commit 01ef97b5fa
4 files changed
+182 -10

No files matched your search

+1
View File
@@ -990,6 +990,7 @@
"This feature is not available in the free version": "免费版不支持此功能",
"Data": "数据",
"View": "查看",
"Sort": "排序",
"Select format": "选择格式",
"Enable Discord Rich Presence": "启用 Discord Rich Presence",
"Show your reading status and the book your're reading on your Discord profile. Discord needs to be running on your computer": "启用后会在您的 Discord 个人资料上显示您的阅读状态和正在阅读的图书。需要您在电脑上运行 Discord 客户端",
+142 -10
View File
@@ -21,6 +21,22 @@ class NavList extends React.Component<NavListProps, NavListState> {
constructor(props: NavListProps) {
super(props);
this.highlightUtil = new HighlightUtil(ConfigService);
let sortCode = 1;
let orderCode = 2;
const navSortCode = ConfigService.getReaderConfig("navSortCode");
if (navSortCode) {
try {
const parsed = JSON.parse(navSortCode);
if (parsed && (parsed.sort === 1 || parsed.sort === 2)) {
sortCode = parsed.sort;
}
if (parsed && (parsed.order === 1 || parsed.order === 2)) {
orderCode = parsed.order;
}
} catch {
// ignore malformed config, fall back to defaults
}
}
this.state = {
deleteIndex: -1,
currentData: [],
@@ -28,6 +44,9 @@ class NavList extends React.Component<NavListProps, NavListState> {
searchKeyword: "",
searchResults: [],
isComposing: false,
isSortOpen: false,
sortCode,
orderCode,
};
this.searchInputRef = React.createRef<HTMLInputElement>();
}
@@ -203,9 +222,9 @@ class NavList extends React.Component<NavListProps, NavListState> {
if (currentTab === "bookmarks") {
this.setState(
{
currentData: bookmarks
.filter((item) => item.bookKey === currentBook.key)
.reverse(),
currentData: bookmarks.filter(
(item) => item.bookKey === currentBook.key
),
},
() => {
if (this.state.searchKeyword.trim()) {
@@ -290,6 +309,35 @@ class NavList extends React.Component<NavListProps, NavListState> {
return this.highlightUtil.buildHighlightPreviewStyle(styleType, color);
};
toggleSortOpen = () => {
this.setState({ isSortOpen: !this.state.isSortOpen });
};
handleSortSelect = (code: number) => {
this.setState({ sortCode: code, isSortOpen: false }, () => {
ConfigService.setReaderConfig(
"navSortCode",
JSON.stringify({ sort: code, order: this.state.orderCode })
);
});
};
handleOrderSelect = (code: number) => {
this.setState({ orderCode: code, isSortOpen: false }, () => {
ConfigService.setReaderConfig(
"navSortCode",
JSON.stringify({ sort: this.state.sortCode, order: code })
);
});
};
getSortedData = (data: (Bookmark | Note)[]) => {
const { sortCode, orderCode } = this.state;
return [...data].sort((a: any, b: any) => {
const diff =
sortCode === 1
? (Number(a.key) || 0) - (Number(b.key) || 0)
: (Number(a.percentage) || 0) - (Number(b.percentage) || 0);
return orderCode === 1 ? diff : -diff;
});
};
renderBookNavList = (displayData: (Bookmark | Note)[]) => {
return displayData.map((item: any, index: number) => {
const bookmarkProps = {
@@ -355,9 +403,10 @@ class NavList extends React.Component<NavListProps, NavListState> {
render() {
const isSearching =
this.state.searchKeyword.trim().length > 0 && !this.state.isComposing;
const displayData = isSearching
const baseList = isSearching
? this.state.searchResults
: this.state.currentData;
const displayData = this.getSortedData(baseList);
return (
<div className="book-bookmark-container">
@@ -365,12 +414,95 @@ class NavList extends React.Component<NavListProps, NavListState> {
<div>
<Trans>Total</Trans>: {this.state.currentData.length}
</div>
<div onClick={this.toggleSearch} className="book-nav-expand">
<span
className="icon-search"
style={{ paddingRight: "5px" }}
></span>
<Trans>{this.state.isSearchOpen ? "Cancel" : "Search"}</Trans>
<div className="book-nav-actions">
<div
className="nav-sort-wrap"
onMouseLeave={() => {
this.setState({ isSortOpen: false });
}}
>
<div className="book-nav-expand" onClick={this.toggleSortOpen}>
<span
className="icon-sort-desc"
style={{ paddingRight: "5px" }}
></span>
<Trans>Sort</Trans>
</div>
{this.state.isSortOpen && (
<div className="sort-dialog-container nav-sort-dialog-container">
<ul className="sort-by-category">
{["Sort by Date", "Reading progress"].map((item, index) => {
return (
<li
className="sort-by-category-list"
onClick={() => {
this.handleSortSelect(index + 1);
}}
style={
this.state.sortCode === index + 1
? {}
: { opacity: 0.34 }
}
key={index + 1}
>
<Trans>{item}</Trans>
{this.state.sortCode === index + 1 && (
<span
className="icon-check"
style={{ fontWeight: "bold" }}
></span>
)}
</li>
);
})}
</ul>
<div className="sort-dialog-seperator"></div>
<ul className="sort-by-order">
<li
className="sort-by-order-list"
onClick={() => {
this.handleOrderSelect(1);
}}
style={
this.state.orderCode === 1 ? {} : { opacity: 0.34 }
}
>
<Trans>Ascend</Trans>
{this.state.orderCode === 1 && (
<span
className="icon-check"
style={{ fontWeight: "bold" }}
></span>
)}
</li>
<li
className="sort-by-order-list"
onClick={() => {
this.handleOrderSelect(2);
}}
style={
this.state.orderCode === 2 ? {} : { opacity: 0.34 }
}
>
<Trans>Descend</Trans>
{this.state.orderCode === 2 && (
<span
className="icon-check"
style={{ fontWeight: "bold" }}
></span>
)}
</li>
</ul>
</div>
)}
</div>
<div onClick={this.toggleSearch} className="book-nav-expand">
<span
className="icon-search"
style={{ paddingRight: "5px" }}
></span>
<Trans>{this.state.isSearchOpen ? "Cancel" : "Search"}</Trans>
</div>
</div>
</div>
{this.state.isSearchOpen && (
@@ -20,4 +20,7 @@ export interface NavListState {
searchKeyword: string;
searchResults: (BookmarkModel | NoteModel)[];
isComposing: boolean;
isSortOpen: boolean;
sortCode: number;
orderCode: number;
}
+36
View File
@@ -98,3 +98,39 @@
font-size: 15px;
opacity: 0.64;
}
.book-nav-actions {
display: flex;
align-items: center;
}
.nav-sort-wrap {
position: relative;
display: flex;
align-items: center;
}
.nav-sort-dialog-container {
position: absolute;
left: auto;
right: 0;
top: 30px;
z-index: 10;
}
.nav-sort-dialog-container .sort-by-category-list,
.nav-sort-dialog-container .sort-by-order-list {
font-size: 15px;
font-weight: 500;
min-height: 30px;
line-height: 1.5;
width: calc(100% - 30px);
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
padding-left: 15px;
padding-right: 15px;
border-radius: 3px;
}
.nav-sort-dialog-container .sort-dialog-seperator {
width: 100%;
height: 0px;
margin: 6px 0px;
}