diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83d22d7ca..041bfe156 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,11 +9,16 @@ You can also check [on GitHub](https://github.com/nextcloud/news/releases), the
### Changed
- Performance improvements on item list
- Rework feed and global sort ordering
+- Add 'r' shortkey to refresh feed and item list
+- Show automatically new news items on VueJS implementation (#2502)
+- Show when feeds and folder have errors
+- Add keyboard shortcuts 'd', 'f', 'c' and 'v' to switch between feeds and folders
### Fixed
- starred items in a feed can prevent further scrolling
- j shortcut doesn't load more items in infinite scroll (#2847)
- Feed ordering uses wrong values (#2846)
+- Unread Counter becomes negative (#2839)
# Releases
## [25.0.0-alpha12] - 2024-10-23
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index a1113e2a5..8462d3ba5 100644
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -81,7 +81,8 @@ public function index(): TemplateResponse
'oldestFirst',
'showAll',
'lastViewedFeedId',
- 'lastViewedFeedType'
+ 'lastViewedFeedType',
+ 'disableRefresh'
];
foreach ($usersettings as $setting) {
@@ -155,6 +156,7 @@ public function settings(): array
* @param bool $preventReadOnScroll
* @param bool $oldestFirst
* @param bool $compactExpand
+ * @param bool $disableRefresh
*/
#[NoAdminRequired]
public function updateSettings(
@@ -162,7 +164,8 @@ public function updateSettings(
bool $compact,
bool $preventReadOnScroll,
bool $oldestFirst,
- bool $compactExpand
+ bool $compactExpand,
+ bool $disableRefresh
): void {
$settings = [
'showAll' => $showAll,
@@ -170,6 +173,7 @@ public function updateSettings(
'preventReadOnScroll' => $preventReadOnScroll,
'oldestFirst' => $oldestFirst,
'compactExpand' => $compactExpand,
+ 'disableRefresh' => $disableRefresh,
];
foreach ($settings as $setting => $value) {
diff --git a/src/components/MoveFeed.vue b/src/components/MoveFeed.vue
index 4d6e90eb0..ed2b05964 100644
--- a/src/components/MoveFeed.vue
+++ b/src/components/MoveFeed.vue
@@ -73,31 +73,13 @@ export default Vue.extend({
async moveFeed() {
const data = {
feedId: this.feed.id,
- folderId: this.folder ? this.folder.id : null,
+ folderId: this.folder ? this.folder.id : 0,
}
await this.$store.dispatch(ACTIONS.MOVE_FEED, data)
- await this.reloadFeeds()
+ await this.$store.dispatch(ACTIONS.FETCH_FEEDS)
this.$emit('close')
},
- async reloadFeeds() {
- // Clear feeds and folders
- const currentState = this.$store.state
- const newState = {
- ...currentState,
- folders: {
- folders: [],
- },
- feeds: {
- feeds: [],
- },
- }
- this.$store.replaceState(newState)
-
- // Fetch feeds and folders
- await this.$store.dispatch(ACTIONS.FETCH_FOLDERS)
- await this.$store.dispatch(ACTIONS.FETCH_FEEDS)
- },
},
})
diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue
index 6e65a1b8c..acebf2f2d 100644
--- a/src/components/Sidebar.vue
+++ b/src/components/Sidebar.vue
@@ -29,12 +29,12 @@
-
+
{{ items.unreadCount }}
-
@@ -53,7 +53,9 @@
-
@@ -71,7 +75,13 @@
-
+
+ {{ feed.updateErrorCount }}
+
+
{{ feed.unreadCount }}
@@ -82,15 +92,22 @@
-
-
-
+
+
+
+
-
+
+ {{ topLevelItem.updateErrorCount }}
+
+
{{ topLevelItem.feedCount }}
-
+
{{ topLevelItem.unreadCount }}
@@ -119,6 +136,10 @@
+
+
+
+
@@ -154,6 +175,15 @@
{{ t('news', 'Reverse ordering (oldest on top)') }}
+
+
+
+
@@ -183,6 +213,7 @@ import FolderIcon from 'vue-material-design-icons/Folder.vue'
import EyeIcon from 'vue-material-design-icons/Eye.vue'
import EarthIcon from 'vue-material-design-icons/Earth.vue'
import FolderPlusIcon from 'vue-material-design-icons/FolderPlus.vue'
+import FolderAlertIcon from 'vue-material-design-icons/FolderAlert.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import { ROUTES } from '../routes'
@@ -210,6 +241,7 @@ export default Vue.extend({
FolderIcon,
EyeIcon,
EarthIcon,
+ FolderAlertIcon,
FolderPlusIcon,
PlusIcon,
SidebarFeedLinkActions,
@@ -220,37 +252,28 @@ export default Vue.extend({
showAddFeed: false,
ROUTES,
showHelp: false,
+ polling: null,
+ errorFolder: new Map(),
}
},
computed: {
...mapState(['feeds', 'folders', 'items']),
topLevelNav(): (Feed | Folder)[] {
- const showAll = this.$store.getters.showAll
-
- const feeds: { pinned: Feed[], visible: Feed[], ungrouped: Feed[] } = this.$store.getters.feeds.reduce((result, feed: Feed) => {
+ const feeds: { pinned: Feed[], ungrouped: Feed[] } = this.$store.getters.feeds.reduce((result, feed: Feed) => {
if (feed.pinned) result.pinned.push(feed)
- if (showAll || feed.unreadCount > 0) {
- result.visible.push(feed)
- if (feed.folderId === undefined || feed.folderId === null) {
- result.ungrouped.push(feed)
- }
+ if (feed.updateErrorCount > 0) this.errorFolder.set('folder-' + feed.folderId, true)
+ if (feed.folderId === undefined || feed.folderId === null) {
+ result.ungrouped.push(feed)
}
return result
- }, { pinned: [], visible: [], ungrouped: [] })
+ }, { pinned: [], ungrouped: [] })
- const visibleFolders = this.$store.getters.folders
- .filter((folder: Folder) => {
- return showAll || feeds.visible.some((feed: Feed) => feed.folderId === folder.id)
- })
- .map((folder: Folder) => {
- folder.feeds = feeds.visible.filter((feed: Feed) => feed.folderId === folder.id)
- return folder
- })
+ const folders = this.$store.getters.folders
const navItems: (Feed | Folder)[] = [
...feeds.pinned,
...feeds.ungrouped,
- ...visibleFolders,
+ ...folders,
]
return navItems
@@ -304,17 +327,54 @@ export default Vue.extend({
this.saveSetting('showAll', newValue)
},
},
+ disableRefresh: {
+ get() {
+ return this.$store.getters.disableRefresh
+
+ },
+ set(newValue) {
+ if (!newValue) {
+ // refresh feeds every minute
+ this.polling = setInterval(() => {
+ this.$store.dispatch(ACTIONS.FETCH_FEEDS)
+ }, 60000)
+ } else {
+ clearInterval(this.polling)
+ }
+ this.saveSetting('disableRefresh', newValue)
+ },
+ },
+ navFolder() {
+ return this.topLevelNav.filter(item => item.name !== undefined && this.showItem(item))
+ },
+ navFeeds() {
+ return this.navFolder
+ .filter(folder => folder.opened)
+ .reduce((result, folder) => {
+ return result.concat(folder.feeds)
+ }, [])
+ .filter(item => this.showItem(item))
+ },
},
created() {
if (this.$route.query.subscribe_to) {
this.showAddFeed = true
}
+ if (!this.disableRefresh) {
+ // refresh feeds every minute
+ this.polling = setInterval(() => {
+ this.$store.dispatch(ACTIONS.FETCH_FEEDS)
+ }, 60000)
+ }
},
mounted() {
subscribe('news:global:toggle-help-dialog', () => {
this.showHelp = !this.showHelp
})
},
+ beforeDestroy() {
+ clearInterval(this.polling)
+ },
methods: {
async saveSetting(key, value) {
this.$store.commit(key, { value })
@@ -398,6 +458,77 @@ export default Vue.extend({
this.$set(folder, 'opened', !folder.opened)
this.$store.dispatch(ACTIONS.FOLDER_OPEN_STATE, { folder })
},
+ isActiveFeed(feed) {
+ return this.$route.name === 'feed' ? feed.id === Number(this.$route.params?.feedId) : false
+ },
+ isActiveFolder(folder) {
+ return this.$route.name === 'folder' ? folder.id === Number(this.$route.params?.folderId) : false
+ },
+ hasActiveFeeds(folder) {
+ return folder.feeds.some(item => this.isActiveFeed(item))
+ },
+ hasErrorFeeds(folder) {
+ return this.errorFolder.has('folder-' + folder.id)
+ },
+ showItem(item: Feed | Folder) {
+ if (this.showAll) {
+ return true
+ }
+ if (this.isFolder(item)) {
+ return item.feedCount > 0 || this.isActiveFolder(item) || this.hasActiveFeeds(item) || this.hasErrorFeeds(item)
+ } else {
+ return item.pinned || item.unreadCount > 0 || item.updateErrorCount > 0 || this.isActiveFeed(item)
+ }
+ },
+ getFeedIndex(direction) {
+ if (this.$route.name === 'feed') {
+ const feedIndex = this.navFeeds.findIndex((it) => it.id === Number(this.$route.params.feedId))
+ return direction === 'prev' ? feedIndex - 1 : feedIndex + 1
+ } else {
+ // get current folder index
+ const folderIndex = this.getFolderIndex(direction)
+ // search for the nearest feed
+ if (direction === 'next') {
+ return this.navFeeds.findIndex((feed) => {
+ const feedFolderIndex = this.navFolder.findIndex(folder => folder.id === feed.folderId)
+ return feedFolderIndex >= folderIndex - 1
+ })
+ } else {
+ return this.navFeeds.findLastIndex((feed) => {
+ const feedFolderIndex = this.navFolder.findIndex(folder => folder.id === feed.folderId)
+ return feedFolderIndex <= folderIndex
+ })
+ }
+ }
+ },
+ getFolderIndex(direction) {
+ if (this.$route.name === 'feed') {
+ // use folder id from feed when the active item is a feed
+ const feed = this.navFeeds.find((feed: Feed) => this.isActiveFeed(feed))
+ const folderIndex = feed ? this.navFolder.findIndex((it) => it.id === feed.folderId) : -1
+ return direction === 'prev' ? folderIndex : folderIndex + 1
+ } else {
+ const folderIndex = this.navFolder.findIndex((it) => it.id === Number(this.$route.params.folderId))
+ return direction === 'prev' ? folderIndex - 1 : folderIndex + 1
+ }
+ },
+ nextFeed(direction) {
+ const newIndex = this.getFeedIndex(direction)
+ if (newIndex >= 0 && newIndex < this.navFeeds.length) {
+ const feedId = this.navFeeds[newIndex].id.toString()
+ this.$router.push({ name: 'feed', params: { feedId } })
+ this.$refs['feed-' + feedId][0].$el.scrollIntoView({ behavior: 'auto', block: 'nearest' })
+
+ }
+ },
+ nextFolder(direction) {
+ const newIndex = this.getFolderIndex(direction)
+ if (newIndex >= 0 && newIndex < this.navFolder.length) {
+ const folderId = this.navFolder[newIndex].id.toString()
+ this.$router.push({ name: 'folder', params: { folderId } })
+ this.$refs['folder-' + folderId][0].$el.scrollIntoView({ behavior: 'auto', block: 'nearest' })
+ }
+ },
},
})
diff --git a/src/components/feed-display/FeedItemDisplayList.vue b/src/components/feed-display/FeedItemDisplayList.vue
index 4761caf07..1172546b1 100644
--- a/src/components/feed-display/FeedItemDisplayList.vue
+++ b/src/components/feed-display/FeedItemDisplayList.vue
@@ -46,10 +46,12 @@
+
@@ -83,6 +85,7 @@ import FeedItemRow from './FeedItemRow.vue'
import { FeedItem } from '../../types/FeedItem'
import { FEED_ORDER } from '../../dataservices/feed.service'
+import { ACTIONS } from '../../store'
const DEFAULT_DISPLAY_LIST_CONFIG = {
starFilter: true,
@@ -147,15 +150,15 @@ export default Vue.extend({
}
},
computed: {
- reachedEnd(): boolean {
- return this.mounted && this.$store.state.items.allItemsLoaded[this.fetchKey] === true
- },
cfg() {
return _.defaults({ ...this.config }, DEFAULT_DISPLAY_LIST_CONFIG)
},
getSelectedItem() {
return this.$store.getters.selected
},
+ syncNeeded() {
+ return this.$store.state.items.syncNeeded
+ },
changedFeedOrdering() {
if (this.fetchKey.startsWith('feed-')) {
return this.$store.state.feeds.ordering[this.fetchKey]
@@ -174,14 +177,28 @@ export default Vue.extend({
changedShowAll() {
return this.$store.getters.showAll
},
+ isLoading() {
+ return this.$store.getters.loading
+ },
},
watch: {
+ async syncNeeded(needSync) {
+ if (!this.isLoading && needSync) {
+ await this.$store.dispatch(ACTIONS.FETCH_FEEDS)
+ }
+ },
getSelectedItem(newVal) {
this.selectedItem = newVal
},
- fetchKey() {
- this.listOrdering = this.getListOrdering()
- this.cache = undefined
+ // clear cache on route change
+ fetchKey: {
+ handler() {
+ if (this.listOrdering === false) {
+ this.$store.dispatch(ACTIONS.RESET_LAST_ITEM_LOADED)
+ }
+ this.cache = undefined
+ },
+ immediate: true,
},
// rebuild filtered item list only when items has changed
items: {
@@ -193,16 +210,20 @@ export default Vue.extend({
},
// ordering has changed rebuild item list
changedOrdering() {
- this.listOrdering = this.getListOrdering()
- // make sure the first items from this ordering are loaded
- this.fetchMore()
- this.cache = undefined
- // refresh the list with the new ordering
- this.refreshItemList()
- this.$refs.virtualScroll.scrollTop = 0
+ const newListOrdering = this.getListOrdering()
+ if (newListOrdering !== this.listOrdering) {
+ this.listOrdering = newListOrdering
+ this.$refs.virtualScroll.scrollTop = 0
+ // make sure the first items from this ordering are loaded
+ this.fetchMore()
+ this.cache = undefined
+ // refresh the list with the new ordering
+ this.refreshItemList()
+ }
},
// showAll has changed rebuild item list
changedShowAll() {
+ this.$refs.virtualScroll.scrollTop = 0
this.cache = undefined
this.refreshItemList()
},
@@ -215,6 +236,19 @@ export default Vue.extend({
this.setupDebouncedClick()
},
methods: {
+ async refreshFeedList() {
+ // with ordering newest>oldest complete refresh of item list needed
+ if (!this.listOrdering) {
+ this.$store.dispatch(ACTIONS.RESET_LAST_ITEM_LOADED)
+ this.$refs.virtualScroll.scrollTop = 0
+ // make sure the first items from this ordering are loaded
+ this.fetchMore()
+ this.cache = undefined
+ this.refreshItemList()
+ }
+ // sync feed counter with backend
+ await this.$store.dispatch(ACTIONS.FETCH_FEEDS)
+ },
refreshItemList() {
if (this.items.length > 0) {
this.filteredItemcache = this.filterSortedItems()
diff --git a/src/components/feed-display/VirtualScroll.vue b/src/components/feed-display/VirtualScroll.vue
index c21b68743..f90e8602d 100644
--- a/src/components/feed-display/VirtualScroll.vue
+++ b/src/components/feed-display/VirtualScroll.vue
@@ -16,10 +16,6 @@ const LIST_ITEM_HEIGHT = 110 + 1
export default Vue.extend({
name: 'VirtualScroll',
props: {
- reachedEnd: {
- type: Boolean,
- required: true,
- },
fetchKey: {
type: String,
required: true,
@@ -39,6 +35,12 @@ export default Vue.extend({
}
},
computed: {
+ reachedEnd: {
+ cache: false,
+ get() {
+ return this.$store.state.items.allItemsLoaded[this.fetchKey] === true
+ },
+ },
fetching: {
cache: false,
get() {
@@ -47,8 +49,12 @@ export default Vue.extend({
},
},
watch: {
- fetchKey() {
- this.scrollTop = 0
+ fetchKey: {
+ handler() {
+ this.scrollTop = 0
+ this.seenItems = new Map()
+ },
+ immediate: true,
},
lastRendered() {
if (!this.$store.getters.preventReadOnScroll) {
@@ -121,8 +127,8 @@ export default Vue.extend({
this.lastRendered = children
}
- if (!this.reachedEnd && lowerPaddingItems === 0) {
- if (!this.fetching) {
+ if (lowerPaddingItems === 0) {
+ if (!this.reachedEnd && !this.fetching) {
this.$emit('load-more')
}
if (upperPaddingItems + renderedItems + lowerPaddingItems === 0) {
diff --git a/src/components/routes/Unread.vue b/src/components/routes/Unread.vue
index feb070b18..2daab77f3 100644
--- a/src/components/routes/Unread.vue
+++ b/src/components/routes/Unread.vue
@@ -64,7 +64,7 @@ export default Vue.extend({
return this.unreadCache
},
async fetchMore() {
- if (this.unreadCache && !this.$store.state.items.fetchingItems.unread) {
+ if (!this.$store.state.items.fetchingItems.unread) {
this.$store.dispatch(ACTIONS.FETCH_UNREAD)
}
},
diff --git a/src/store/app.ts b/src/store/app.ts
index 4cb7c91b7..5a6ecd015 100644
--- a/src/store/app.ts
+++ b/src/store/app.ts
@@ -13,6 +13,7 @@ export type AppInfoState = {
oldestFirst: boolean;
preventReadOnScroll: boolean;
showAll: boolean;
+ disableRefresh: boolean;
lastViewedFeedId: string;
lastViewedFeedType: string;
}
@@ -25,6 +26,7 @@ const state: AppInfoState = {
oldestFirst: loadState('news', 'oldestFirst', null) === '1',
preventReadOnScroll: loadState('news', 'preventReadOnScroll', null) === '1',
showAll: loadState('news', 'showAll', null) === '1',
+ disableRefresh: loadState('news', 'disableRefresh', null) === '1',
lastViewedFeedId: loadState('news', 'lastViewedFeedId', '0'),
lastViewedFeedType: loadState('news', 'lastViewedFeedType', '6'),
}
@@ -51,6 +53,9 @@ const getters = {
showAll() {
return state.showAll
},
+ disableRefresh() {
+ return state.disableRefresh
+ },
lastViewedFeedId() {
return state.lastViewedFeedId
},
@@ -108,6 +113,12 @@ export const mutations = {
) {
state.showAll = value
},
+ disableRefresh(
+ state: AppInfoState,
+ { value }: { value: boolean },
+ ) {
+ state.disableRefresh = value
+ },
}
export default {
diff --git a/src/store/feed.ts b/src/store/feed.ts
index 1a86f6989..b12177903 100644
--- a/src/store/feed.ts
+++ b/src/store/feed.ts
@@ -27,12 +27,14 @@ export const FEED_ACTION_TYPES = {
export type FeedState = {
feeds: Feed[];
unreadCount: number;
+ newestItemId: number;
ordering: { [key: string]: number };
}
const state: FeedState = {
feeds: [],
unreadCount: 0,
+ newestItemId: 0,
ordering: {},
}
@@ -44,10 +46,14 @@ const getters = {
export const actions = {
async [FEED_ACTION_TYPES.FETCH_FEEDS]({ commit }: ActionParams) {
- const feeds = await FeedService.fetchAllFeeds()
+ const response = await FeedService.fetchAllFeeds()
+ if (response.data.newestItemId) {
+ commit(FEED_MUTATION_TYPES.SET_NEWEST_ITEM_ID, response.data.newestItemId)
+ commit(FEED_ITEM_MUTATION_TYPES.SET_NEWEST_ITEM_ID, response.data.newestItemId)
+ }
- commit(FEED_MUTATION_TYPES.SET_FEEDS, feeds.data.feeds)
- commit(FEED_ITEM_MUTATION_TYPES.SET_UNREAD_COUNT, (feeds.data.feeds.reduce((total: number, feed: Feed) => {
+ commit(FEED_MUTATION_TYPES.SET_FEEDS, response.data.feeds)
+ commit(FEED_ITEM_MUTATION_TYPES.SET_UNREAD_COUNT, (response.data.feeds.reduce((total: number, feed: Feed) => {
return total + feed.unreadCount
}, 0)))
},
@@ -117,9 +123,7 @@ export const actions = {
{ commit }: ActionParams,
{ feed }: { feed: Feed },
) {
- // want to fetch feed so that we can retrieve the "newestItemId"
- const response = await ItemService.fetchFeedItems(feed.id as number)
- await FeedService.markRead({ feedId: feed.id as number, highestItemId: response.data.newestItemId })
+ await FeedService.markRead({ feedId: feed.id as number, highestItemId: state.newestItemId })
if (feed.folderId) {
commit(FOLDER_MUTATION_TYPES.MODIFY_FOLDER_UNREAD_COUNT, { folderId: feed.folderId, delta: -feed.unreadCount })
@@ -208,11 +212,11 @@ export const mutations = {
feeds: Feed[],
) {
feeds.forEach(it => {
- state.feeds.push(it)
- if (it.ordering) {
+ if (typeof it?.ordering === 'number') {
state.ordering['feed-' + it.id] = it.ordering
}
})
+ state.feeds = [...feeds]
},
[FEED_MUTATION_TYPES.ADD_FEED](
@@ -232,6 +236,13 @@ export const mutations = {
_.assign(feed, newFeed)
},
+ [FEED_MUTATION_TYPES.SET_NEWEST_ITEM_ID](
+ state: FeedState,
+ newestItemId: number,
+ ) {
+ state.newestItemId = newestItemId
+ },
+
[FEED_MUTATION_TYPES.SET_FEED_ALL_READ](
state: FeedState,
feed: Feed,
diff --git a/src/store/folder.ts b/src/store/folder.ts
index 16941a6bb..473e4c904 100644
--- a/src/store/folder.ts
+++ b/src/store/folder.ts
@@ -69,11 +69,7 @@ export const mutations = {
state: FolderState,
folders: Folder[],
) {
- folders.forEach(it => {
- it.feedCount = 0
- it.feeds = []
- state.folders.push(it)
- })
+ state.folders = [...state.folders, ...folders]
},
[FOLDER_MUTATION_TYPES.DELETE_FOLDER](
@@ -88,13 +84,19 @@ export const mutations = {
state: FolderState,
feeds: Feed[],
) {
+ const updatedFolders = state.folders.map(folder => ({
+ ...folder,
+ feeds: [] as Feed[],
+ feedCount: 0,
+ }))
feeds.forEach(it => {
- const folder = state.folders.find((folder: Folder) => { return folder.id === it.folderId })
+ const folder = updatedFolders.find((folder: Folder) => { return folder.id === it.folderId })
if (folder) {
folder.feeds.push(it)
folder.feedCount += it.unreadCount
}
})
+ state.folders = updatedFolders
},
[FEED_MUTATION_TYPES.ADD_FEED](
diff --git a/src/store/item.ts b/src/store/item.ts
index 1748cd453..77d8e9446 100644
--- a/src/store/item.ts
+++ b/src/store/item.ts
@@ -23,6 +23,8 @@ export type ItemState = {
fetchingItems: { [key: string]: boolean };
allItemsLoaded: { [key: string]: boolean };
lastItemLoaded: { [key: string]: number };
+ newestItemId: number;
+ syncNeeded: boolean;
starredCount: number;
unreadCount: number;
@@ -37,6 +39,8 @@ const state: ItemState = {
fetchingItems: {},
allItemsLoaded: {},
lastItemLoaded: {},
+ newestItemId: 0,
+ syncNeeded: false,
starredCount: 0,
unreadCount: 0,
@@ -60,6 +64,9 @@ const getters = {
allItems(state: ItemState) {
return state.allItems
},
+ newestItemId(state: ItemState) {
+ return state.newestItemId
+ },
}
export const actions = {
@@ -78,11 +85,16 @@ export const actions = {
commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'unread', fetching: true })
const response = await ItemService.debounceFetchUnread(start || state.lastItemLoaded.unread)
+ if (response?.data.newestItemId && response?.data.newestItemId !== state.newestItemId) {
+ state.syncNeeded = true
+ }
commit(FEED_ITEM_MUTATION_TYPES.SET_ITEMS, response?.data.items)
if (response?.data.items.length < 40) {
commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'unread', loaded: true })
+ } else {
+ commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'unread', loaded: false })
}
if (response?.data.items.length > 0) {
@@ -107,11 +119,16 @@ export const actions = {
commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'all', fetching: true })
const response = await ItemService.debounceFetchAll(start || state.lastItemLoaded.all)
+ if (response?.data.newestItemId && response?.data.newestItemId !== state.newestItemId) {
+ state.syncNeeded = true
+ }
commit(FEED_ITEM_MUTATION_TYPES.SET_ITEMS, response?.data.items)
if (response?.data.items.length < 40) {
commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'all', loaded: true })
+ } else {
+ commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'all', loaded: false })
}
if (response?.data.items.length > 0) {
@@ -135,6 +152,9 @@ export const actions = {
) {
commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'starred', fetching: true })
const response = await ItemService.debounceFetchStarred(start || state.lastItemLoaded.starred)
+ if (response?.data.newestItemId && response?.data.newestItemId !== state.newestItemId) {
+ state.syncNeeded = true
+ }
commit(FEED_ITEM_MUTATION_TYPES.SET_ITEMS, response?.data.items)
if (response?.data.starred) {
@@ -143,6 +163,8 @@ export const actions = {
if (response?.data.items.length < 40) {
commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'starred', loaded: true })
+ } else {
+ commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'starred', loaded: false })
}
if (response?.data.items.length > 0) {
const lastItem = response?.data.items[response?.data.items.length - 1].id
@@ -166,9 +188,15 @@ export const actions = {
) {
commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'feed-' + feedId, fetching: true })
const response = await ItemService.debounceFetchFeedItems(feedId, start || state.lastItemLoaded['feed-' + feedId])
+ if (response?.data.newestItemId && response?.data.newestItemId !== state.newestItemId) {
+ state.syncNeeded = true
+ }
+
commit(FEED_ITEM_MUTATION_TYPES.SET_ITEMS, response?.data.items)
if (response?.data.items.length < 40) {
commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'feed-' + feedId, loaded: true })
+ } else {
+ commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'feed-' + feedId, loaded: false })
}
if (response?.data.items.length > 0) {
const lastItem = response?.data.items[response?.data.items.length - 1].id
@@ -192,10 +220,15 @@ export const actions = {
) {
commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'folder-' + folderId, fetching: true })
const response = await ItemService.debounceFetchFolderFeedItems(folderId, start || state.lastItemLoaded['folder-' + folderId])
+ if (response?.data.newestItemId && response?.data.newestItemId !== state.newestItemId) {
+ state.syncNeeded = true
+ }
commit(FEED_ITEM_MUTATION_TYPES.SET_ITEMS, response?.data.items)
if (response?.data.items.length < 40) {
commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'folder-' + folderId, loaded: true })
+ } else {
+ commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'folder-' + folderId, loaded: false })
}
if (response?.data.items.length > 0) {
const lastItem = response?.data.items[response?.data.items.length - 1].id
@@ -324,11 +357,25 @@ export const mutations = {
items: FeedItem[],
) {
if (items) {
+ let newestFetchedItemId = 0
+ const newItems: FeedItem[] = []
+
items.forEach(it => {
if (state.allItems.find((existing: FeedItem) => existing.id === it.id) === undefined) {
- state.allItems.push(it)
+ newItems.push(it)
+ if (state.newestItemId < Number(it.id)) {
+ newestFetchedItemId = Number(it.id)
+ }
}
})
+
+ if (newItems.length > 0) {
+ state.allItems = [...state.allItems, ...newItems]
+ }
+
+ if (newestFetchedItemId > state.newestItemId) {
+ state.syncNeeded = true
+ }
}
},
@@ -390,6 +437,17 @@ export const mutations = {
state.lastItemLoaded[key] = lastItem
},
+ [FEED_ITEM_MUTATION_TYPES.SET_NEWEST_ITEM_ID](
+ state: ItemState,
+ newestItemId: number,
+ ) {
+ if (state.newestItemId !== newestItemId) {
+ state.newestItemId = newestItemId
+ state.allItemsLoaded = {}
+ }
+ state.syncNeeded = false
+ },
+
[FEED_MUTATION_TYPES.SET_FEED_ALL_READ](
state: ItemState,
feed: Feed,
diff --git a/src/types/MutationTypes.ts b/src/types/MutationTypes.ts
index 323a0a7a7..96180582e 100644
--- a/src/types/MutationTypes.ts
+++ b/src/types/MutationTypes.ts
@@ -4,6 +4,7 @@ export const FEED_MUTATION_TYPES = {
UPDATE_FEED: 'UPDATE_FEED',
MOVE_FEED: 'MOVE_FEED',
+ SET_NEWEST_ITEM_ID: 'SET_NEWEST_ITEM_ID',
SET_FEED_ALL_READ: 'SET_FEED_ALL_READ',
MODIFY_FEED_UNREAD_COUNT: 'MODIFY_FEED_UNREAD_COUNT',
@@ -32,6 +33,7 @@ export const FEED_ITEM_MUTATION_TYPES = {
SET_FETCHING: 'SET_FETCHING',
SET_ALL_LOADED: 'SET_ALL_LOADED',
SET_LAST_ITEM_LOADED: 'SET_LAST_ITEM_LOADED',
+ SET_NEWEST_ITEM_ID: 'SET_NEWEST_ITEM_ID',
}
export const APPLICATION_MUTATION_TYPES = {
diff --git a/tests/Unit/Controller/PageControllerTest.php b/tests/Unit/Controller/PageControllerTest.php
index af0399fa4..f07c1040f 100644
--- a/tests/Unit/Controller/PageControllerTest.php
+++ b/tests/Unit/Controller/PageControllerTest.php
@@ -276,17 +276,18 @@ public function testSettingsExploreUrlSet()
*/
public function testUpdateSettings()
{
- $this->config->expects($this->exactly(5))
+ $this->config->expects($this->exactly(6))
->method('setUserValue')
->withConsecutive(
['becka', 'news', 'showAll', '1'],
['becka', 'news', 'compact', '1'],
['becka', 'news', 'preventReadOnScroll', '0'],
['becka', 'news', 'oldestFirst', '1'],
- ['becka', 'news', 'compactExpand', '1']
+ ['becka', 'news', 'compactExpand', '1'],
+ ['becka', 'news', 'disableRefresh', '0']
);
- $this->controller->updateSettings(true, true, false, true, true);
+ $this->controller->updateSettings(true, true, false, true, true, false);
}
public function testExplore()