diff --git a/src/assets/locales/zh-CN.json b/src/assets/locales/zh-CN.json index 9183fa33..1c207703 100644 --- a/src/assets/locales/zh-CN.json +++ b/src/assets/locales/zh-CN.json @@ -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 客户端", diff --git a/src/containers/lists/navList/component.tsx b/src/containers/lists/navList/component.tsx index ee16f613..a28f254d 100644 --- a/src/containers/lists/navList/component.tsx +++ b/src/containers/lists/navList/component.tsx @@ -21,6 +21,22 @@ class NavList extends React.Component { 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 { searchKeyword: "", searchResults: [], isComposing: false, + isSortOpen: false, + sortCode, + orderCode, }; this.searchInputRef = React.createRef(); } @@ -203,9 +222,9 @@ class NavList extends React.Component { 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 { 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 { 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 (
@@ -365,12 +414,95 @@ class NavList extends React.Component {
Total: {this.state.currentData.length}
-
- - {this.state.isSearchOpen ? "Cancel" : "Search"} +
+
{ + this.setState({ isSortOpen: false }); + }} + > +
+ + Sort +
+ {this.state.isSortOpen && ( +
+
    + {["Sort by Date", "Reading progress"].map((item, index) => { + return ( +
  • { + this.handleSortSelect(index + 1); + }} + style={ + this.state.sortCode === index + 1 + ? {} + : { opacity: 0.34 } + } + key={index + 1} + > + {item} + {this.state.sortCode === index + 1 && ( + + )} +
  • + ); + })} +
+
+
    +
  • { + this.handleOrderSelect(1); + }} + style={ + this.state.orderCode === 1 ? {} : { opacity: 0.34 } + } + > + Ascend + {this.state.orderCode === 1 && ( + + )} +
  • +
  • { + this.handleOrderSelect(2); + }} + style={ + this.state.orderCode === 2 ? {} : { opacity: 0.34 } + } + > + Descend + {this.state.orderCode === 2 && ( + + )} +
  • +
+
+ )} +
+
+ + {this.state.isSearchOpen ? "Cancel" : "Search"} +
{this.state.isSearchOpen && ( diff --git a/src/containers/lists/navList/interface.tsx b/src/containers/lists/navList/interface.tsx index cecc0c3c..5876a3ec 100644 --- a/src/containers/lists/navList/interface.tsx +++ b/src/containers/lists/navList/interface.tsx @@ -20,4 +20,7 @@ export interface NavListState { searchKeyword: string; searchResults: (BookmarkModel | NoteModel)[]; isComposing: boolean; + isSortOpen: boolean; + sortCode: number; + orderCode: number; } diff --git a/src/containers/lists/navList/navList.css b/src/containers/lists/navList/navList.css index a73288a3..186d7e52 100644 --- a/src/containers/lists/navList/navList.css +++ b/src/containers/lists/navList/navList.css @@ -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; +}