diff --git a/client/js/templates/EntriesPage.jsx b/client/js/templates/EntriesPage.jsx index 85cade38a7..bc05fdd1fe 100644 --- a/client/js/templates/EntriesPage.jsx +++ b/client/js/templates/EntriesPage.jsx @@ -4,6 +4,7 @@ import React, { useEffect, useMemo, useState, + useReducer, } from 'react'; import PropTypes from 'prop-types'; import { Link, useLocation, useParams } from 'react-router-dom'; @@ -25,6 +26,39 @@ import { useShouldReload } from '../helpers/hooks'; import { forceReload, makeEntriesLinkLocation } from '../helpers/uri'; import { HttpError } from '../errors'; +const LOAD_MORE = 'load-more'; +const RESET_OFFSET = 'reset-offset'; + +function entriesReducer( + state, + action, +) { + console.log(state, action) + switch (action.type) { + case LOAD_MORE: + const { entries } = action.payload; + const lastEntry = entries[entries.length - 1]; + + return { + ...state, + // Calculate offset. + fromDatetime: lastEntry ? lastEntry.datetime : undefined, + fromId: lastEntry ? lastEntry.id : undefined, + }; + + case RESET_OFFSET: + return { + ...state, + // Calculate offset. + fromDatetime: undefined, + fromId: undefined, + }; + + default: + throw new Error('Unknown action.'); + } +} + function reloadList({ fetchParams, abortController, @@ -76,13 +110,24 @@ function reloadList({ selfoss.entriesPage.setSelectedEntry(null); } + const reloader_ = reloader; + + reloader = (...args) => { + const r = reloader_(...args); + console.trace('reloader', r); + return r; + } + setLoadingState(LoadingState.LOADING); return reloader(fetchParams, abortController).then(({ entries, hasMore }) => { + setLoadingState(LoadingState.SUCCESS); + + console.trace('reloader-then', abortController.signal, abortController.signal.aborted); if (abortController.signal.aborted) { return; } + console.log('after') - setLoadingState(LoadingState.SUCCESS); selfoss.entriesPage.setHasMore(hasMore); if (append) { @@ -195,6 +240,7 @@ export function EntriesPage({ reload, setGlobalUnreadCount, unreadItemsCount, + setDispatchEntries, }) { const allowedToUpdate = useAllowedToUpdate(); const allowedToWrite = useAllowedToWrite(); @@ -212,14 +258,28 @@ export function EntriesPage({ const currentTag = params.category?.startsWith('tag-') ? params.category.replace(/^tag-/, '') : null; const currentSource = params.category?.startsWith('source-') ? parseInt(params.category.replace(/^source-/, ''), 10) : null; - // The offsets for pagination. - // Clear them when URL changes, except for when only id changes since that happens when reading. - const [fromDatetime, setFromDatetime] = useStateWithDeps( - undefined, - [params.filter, currentTag, currentSource, searchText] + const [entriesState, dispatchEntries] = useReducer( + entriesReducer, + { + // The offsets for pagination. + // Clear them when URL changes, except for when only id changes since that happens when reading. + fromDatetime: undefined, + fromId: undefined, + } ); - const [fromId, setFromId] = useStateWithDeps( - undefined, + + // Propagate the dispatcher to stateful class component. + setDispatchEntries(dispatchEntries); + + const { + fromDatetime, + fromId, + } = entriesState; + + useEffect( + () => dispatchEntries({ + type: RESET_OFFSET, + }), [params.filter, currentTag, currentSource, searchText] ); @@ -236,7 +296,11 @@ export function EntriesPage({ return navSourcesExpanded; }, [params.filter, currentTag, currentSource, searchText]); - const [moreLoadingState, setMoreLoadingState] = useState(LoadingState.INITIAL); + const [moreLoadingState, setMoreLoadingState_] = useState(LoadingState.INITIAL); + const setMoreLoadingState = (...args) => { + console.trace('moreState', ...args); + setMoreLoadingState_(...args); + }; // Perform the scheduled reload. useEffect(() => { @@ -329,13 +393,14 @@ export function EntriesPage({ const moreOnClick = useCallback( (event) => { event.preventDefault(); - const lastEntry = entries[entries.length - 1]; - - // Calculate offset. - setFromDatetime(lastEntry ? lastEntry.datetime : undefined); - setFromId(lastEntry ? lastEntry.id : undefined); + dispatchEntries({ + type: LOAD_MORE, + payload: { + entries, + }, + }); }, - [entries, setFromDatetime, setFromId] + [entries] ); // Current time for calculating relative dates in items. @@ -445,6 +510,7 @@ EntriesPage.propTypes = { reload: PropTypes.func.isRequired, setGlobalUnreadCount: PropTypes.func.isRequired, unreadItemsCount: PropTypes.number.isRequired, + setDispatchEntries: PropTypes.func.isRequired, }; const initialState = { @@ -461,6 +527,8 @@ const initialState = { }; export default class StateHolder extends React.Component { + dispatchEntries = () => {}; + constructor(props) { super(props); this.state = initialState; @@ -753,8 +821,8 @@ export default class StateHolder extends React.Component { this.setExpandedEntries({}); this.props.setNavExpanded(false); - if (ids.length !== 0 && this.props.match.params.filter === FilterType.UNREAD) { - markedEntries = markedEntries.filter(({ id }) => !ids.includes(id)); + if (this.props.match.params.filter === FilterType.UNREAD) { + markedEntries = markedEntries.filter(({ id }) => parseInt(this.props.match.params.id ?? null, 10) === id); } this.setLoadingState(LoadingState.LOADING); @@ -790,6 +858,16 @@ export default class StateHolder extends React.Component { itemsRequests.markAll(ids).then(() => { this.setLoadingState(LoadingState.SUCCESS); + + if (this.props.match.params.filter === FilterType.UNREAD) { + // We cleared out the entries page, load more. + this.dispatchEntries({ + type: LOAD_MORE, + payload: { + entries: oldEntries, + }, + }); + } }).catch((error) => { selfoss.handleAjaxError(error).then(() => { const statuses = ids.map((id) => ({ @@ -1076,6 +1154,10 @@ export default class StateHolder extends React.Component { } render() { + const setDispatchEntries = (dispatchEntries) => { + this.dispatchEntries = dispatchEntries; + }; + return ( ); }