diff options
author | Devlin Junker <devlin.junker@gmail.com> | 2023-09-01 13:24:16 -0700 |
---|---|---|
committer | Benjamin Brahmer <info@b-brahmer.de> | 2023-09-15 06:57:42 +0200 |
commit | 5caefbadc6b9fec1010a0e092dc0f014525d21b9 (patch) | |
tree | 5896cc471d0ca2e6061018c4234524e57d7792d3 /src | |
parent | e2e8ae517afcb57bd2e0d5e6d4aacbb96603bd96 (diff) |
add routes for folder items and all items
Signed-off-by: Devlin Junker <devlin.junker@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/Sidebar.vue | 6 | ||||
-rw-r--r-- | src/components/feed-display/FeedItemDisplay.vue | 18 | ||||
-rw-r--r-- | src/components/feed-display/FeedItemDisplayList.vue | 3 | ||||
-rw-r--r-- | src/components/routes/All.vue | 63 | ||||
-rw-r--r-- | src/components/routes/Folder.vue | 94 | ||||
-rw-r--r-- | src/dataservices/item.service.ts | 52 | ||||
-rw-r--r-- | src/routes/index.ts | 16 | ||||
-rw-r--r-- | src/store/item.ts | 51 |
8 files changed, 294 insertions, 9 deletions
diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index eedead3b0..493eba31e 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -20,7 +20,7 @@ <NcCounterBubble>{{ items.unreadCount }}</NcCounterBubble> </template> </NcAppNavigationItem> - <NcAppNavigationItem :title="t('news', 'All articles')" icon="icon-rss"> + <NcAppNavigationItem :title="t('news', 'All articles')" icon="icon-rss" :to="{ name: ROUTES.ALL }"> <template #actions> <ActionButton icon="icon-checkmark" @click="alert('TODO: Edit')"> t('news','Mark read') @@ -37,13 +37,13 @@ :key="topLevelItem.name || topLevelItem.title" :title="topLevelItem.name || topLevelItem.title" :icon="isFolder(topLevelItem) ? 'icon-folder': ''" - :to="isFolder(topLevelItem) ? {} : { name: ROUTES.FEED, params: { feedId: topLevelItem.id.toString() } }" + :to="isFolder(topLevelItem) ? { name: ROUTES.FOLDER, params: { folderId: topLevelItem.id.toString() }} : { name: ROUTES.FEED, params: { feedId: topLevelItem.id.toString() } }" :allow-collapse="true"> <template #default> <NcAppNavigationItem v-for="feed in topLevelItem.feeds" :key="feed.name" :title="feed.title" - :to="{ name: ROUTES.FEED, props: { feedId: feed.id } }"> + :to="{ name: ROUTES.FEED, params: { feedId: feed.id } }"> <template #icon> <RssIcon v-if="!feed.faviconLink" /> <span v-if="feed.faviconLink" style="width: 24px; background-size: contain;" :style="{ 'backgroundImage': 'url(' + feed.faviconLink + ')' }" /> diff --git a/src/components/feed-display/FeedItemDisplay.vue b/src/components/feed-display/FeedItemDisplay.vue index 4eab900ab..8d97e856e 100644 --- a/src/components/feed-display/FeedItemDisplay.vue +++ b/src/components/feed-display/FeedItemDisplay.vue @@ -183,7 +183,7 @@ export default Vue.extend({ </script> -<style scoped> +<style> .feed-item-display { max-height: 100%; overflow-y: hidden; @@ -210,6 +210,22 @@ export default Vue.extend({ color: #3a84e4 } + .article .body ul { + margin: 7px 0; + padding-left: 14px; + list-style-type: disc; + } + + .article .body ul li { + cursor: default; + line-height: 21px; + } + + .article .body p { + line-height: 1.5; + margin: 7px 0 14px 0; + } + .article .subtitle { color: var(--color-text-lighter); font-size: 15px; diff --git a/src/components/feed-display/FeedItemDisplayList.vue b/src/components/feed-display/FeedItemDisplayList.vue index 8fce1fb25..8fc58281e 100644 --- a/src/components/feed-display/FeedItemDisplayList.vue +++ b/src/components/feed-display/FeedItemDisplayList.vue @@ -1,5 +1,5 @@ <template> - <div> + <div style="height: 100%; display: flex; flex-direction: column;"> <div style="justify-content: right; display: flex"> <NcActions class="filter-container" :force-menu="true"> <template #icon> @@ -193,6 +193,7 @@ export default Vue.extend({ .feed-item-container { max-width: 50%; overflow-y: hidden; + height: calc(100vh - 50px - 50px - 10px) } .filter-container { diff --git a/src/components/routes/All.vue b/src/components/routes/All.vue new file mode 100644 index 000000000..a1cc52fef --- /dev/null +++ b/src/components/routes/All.vue @@ -0,0 +1,63 @@ +<template> + <div class="route-container"> + <div class="header"> + {{ t('news', 'All Articles') }} + </div> + + <FeedItemDisplayList :items="allItems" + :fetch-key="'all'" + @load-more="fetchMore()" /> + </div> +</template> + +<script lang="ts"> +import Vue from 'vue' +import { mapState } from 'vuex' + +import FeedItemDisplayList from '../feed-display/FeedItemDisplayList.vue' + +import { FeedItem } from '../../types/FeedItem' +import { ACTIONS, MUTATIONS } from '../../store' + +export default Vue.extend({ + components: { + FeedItemDisplayList, + }, + computed: { + ...mapState(['items']), + + allItems(): FeedItem[] { + return this.$store.getters.allItems + }, + }, + created() { + this.$store.commit(MUTATIONS.SET_SELECTED_ITEM, { id: undefined }) + }, + methods: { + async fetchMore() { + if (!this.$store.state.items.fetchingItems.all) { + this.$store.dispatch(ACTIONS.FETCH_ITEMS) + } + }, + }, +}) +</script> + +<style scoped> + .route-container { + height: 100%; + } + + .header { + padding-left: 50px; + position: absolute; + top: 1em; + font-weight: 700; + } + + .counter-bubble { + display: inline-block; + vertical-align: sub; + margin-left: 10px; + } +</style> diff --git a/src/components/routes/Folder.vue b/src/components/routes/Folder.vue new file mode 100644 index 000000000..17fd768c3 --- /dev/null +++ b/src/components/routes/Folder.vue @@ -0,0 +1,94 @@ +<template> + <div class="route-container"> + <div class="header"> + {{ folder ? folder.name : '' }} + <NcCounterBubble v-if="folder" class="counter-bubble"> + {{ unreadCount }} + </NcCounterBubble> + </div> + + <FeedItemDisplayList :items="items" :fetch-key="'folder-'+folderId" @load-more="fetchMore()" /> + </div> +</template> + +<script lang="ts"> +import Vue from 'vue' +import { mapState } from 'vuex' + +import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js' + +import FeedItemDisplayList from '../feed-display/FeedItemDisplayList.vue' + +import { FeedItem } from '../../types/FeedItem' +import { ACTIONS, MUTATIONS } from '../../store' +import { Feed } from '../../types/Feed' +import { Folder } from '../../types/Folder' + +export default Vue.extend({ + components: { + NcCounterBubble, + FeedItemDisplayList, + }, + props: { + folderId: { + type: String, + required: true, + }, + }, + computed: { + ...mapState(['items', 'feeds', 'folders']), + folder(): Folder { + return this.$store.getters.folders.find((folder: Folder) => folder.id === this.id) + }, + items(): FeedItem[] { + const feeds: Array<number> = this.$store.getters.feeds.filter((feed: Feed) => feed.folderId === this.id).map((feed: Feed) => feed.id) + + return this.$store.state.items.allItems.filter((item: FeedItem) => { + return feeds.includes(item.feedId) + }) || [] + }, + id(): number { + return Number(this.folderId) + }, + unreadCount(): number { + const totalUnread = this.$store.getters.feeds + .filter((feed: Feed) => feed.folderId === this.id) + .reduce((acc: number, feed: Feed) => { acc += feed.unreadCount; return acc }, 0) + + return totalUnread + }, + }, + created() { + this.$store.commit(MUTATIONS.SET_SELECTED_ITEM, { id: undefined }) + this.fetchMore() + this.$watch(() => this.$route.params, this.fetchMore) + }, + methods: { + async fetchMore() { + if (!this.$store.state.items.fetchingItems['folder-' + this.folderId]) { + this.$store.dispatch(ACTIONS.FETCH_FOLDER_FEED_ITEMS, { folderId: this.id }) + } + }, + }, +}) +</script> + +<style scoped> +.route-container { + height: 100%; +} + +.header { + padding-left: 50px; + position: absolute; + top: 1em; + font-weight: 700; +} + +.counter-bubble { + display: inline-block; + vertical-align: sub; + margin-left: 10px; +} + +</style> diff --git a/src/dataservices/item.service.ts b/src/dataservices/item.service.ts index 6d4aabdb4..98b9d9eb3 100644 --- a/src/dataservices/item.service.ts +++ b/src/dataservices/item.service.ts @@ -6,16 +6,39 @@ import { API_ROUTES } from '../types/ApiRoutes' import { FeedItem } from '../types/FeedItem' export const ITEM_TYPES = { - ALL: 0, + FEED: 0, + FOLDER: 1, STARRED: 2, + ALL: 3, UNREAD: 6, } export class ItemService { + static debounceFetchAll = _.debounce(ItemService.fetchAll, 400, { leading: true }) static debounceFetchStarred = _.debounce(ItemService.fetchStarred, 400, { leading: true }) static debounceFetchUnread = _.debounce(ItemService.fetchUnread, 400, { leading: true }) static debounceFetchFeedItems = _.debounce(ItemService.fetchFeedItems, 400, { leading: true }) + static debounceFetchFolderFeedItems = _.debounce(ItemService.fetchFolderItems, 400, { leading: true }) + + /** + * Makes backend call to retrieve all items + * + * @param start (id of last starred item loaded) + * @return {AxiosResponse} response object containing backend request response + */ + static async fetchAll(start: number): Promise<AxiosResponse> { + return await axios.get(API_ROUTES.ITEMS, { + params: { + limit: 40, + oldestFirst: false, + search: '', + showAll: true, + type: ITEM_TYPES.ALL, + offset: start, + }, + }) + } /** * Makes backend call to retrieve starred items @@ -29,7 +52,7 @@ export class ItemService { limit: 40, oldestFirst: false, search: '', - showAll: false, + showAll: true, type: ITEM_TYPES.STARRED, offset: start, }, @@ -68,8 +91,8 @@ export class ItemService { limit: 40, oldestFirst: false, search: '', - showAll: false, - type: ITEM_TYPES.ALL, + showAll: true, + type: ITEM_TYPES.FEED, offset: start, id: feedId, }, @@ -77,6 +100,27 @@ export class ItemService { } /** + * Makes backend call to retrieve items from a specific folder + * + * @param folderId id number of folder to retrieve items for + * @param start (id of last unread item loaded) + * @return {AxiosResponse} response object containing backend request response + */ + static async fetchFolderItems(folderId: number, start: number): Promise<AxiosResponse> { + return await axios.get(API_ROUTES.ITEMS, { + params: { + limit: 40, + oldestFirst: false, + search: '', + showAll: true, + type: ITEM_TYPES.FOLDER, + offset: start, + id: folderId, + }, + }) + } + + /** * Makes backend call to mark item as read/unread in DB * * @param {FeedItem} item FeedItem (containing id) that wil be marked as read/unread diff --git a/src/routes/index.ts b/src/routes/index.ts index fb0f7742a..b4ed2f757 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -4,12 +4,16 @@ import ExplorePanel from '../components/routes/Explore.vue' import StarredPanel from '../components/routes/Starred.vue' import UnreadPanel from '../components/routes/Unread.vue' import FeedPanel from '../components/routes/Feed.vue' +import FolderPanel from '../components/routes/Folder.vue' +import AllPanel from '../components/routes/All.vue' export const ROUTES = { EXPLORE: 'explore', STARRED: 'starred', UNREAD: 'unread', FEED: 'feed', + FOLDER: 'folder', + ALL: 'all', } const getInitialRoute = function() { @@ -49,6 +53,18 @@ const routes = [ component: FeedPanel, props: true, }, + { + name: ROUTES.FOLDER, + path: '/folder/:folderId', + component: FolderPanel, + props: true, + }, + { + name: ROUTES.ALL, + path: '/all', + component: AllPanel, + props: true, + }, ] export default new VueRouter({ diff --git a/src/store/item.ts b/src/store/item.ts index fe8d5b6c8..4f73bff55 100644 --- a/src/store/item.ts +++ b/src/store/item.ts @@ -12,6 +12,8 @@ export const FEED_ITEM_ACTION_TYPES = { STAR_ITEM: 'STAR_ITEM', UNSTAR_ITEM: 'UNSTAR_ITEM', FETCH_FEED_ITEMS: 'FETCH_FEED_ITEMS', + FETCH_FOLDER_FEED_ITEMS: 'FETCH_FOLDER_FEED_ITEMS', + FETCH_ITEMS: 'FETCH_ITEMS', } export type ItemState = { @@ -49,6 +51,9 @@ const getters = { selected(state: ItemState) { return state.allItems.find((item: FeedItem) => item.id === state.selectedId) }, + allItems(state: ItemState) { + return state.allItems + }, } export const actions = { @@ -77,6 +82,30 @@ export const actions = { }, /** + * Fetch All Items from Backend and call commit to update state + * + * @param param0 ActionParams + * @param param0.commit + * @param param1 ActionArgs + * @param param1.start + */ + async [FEED_ITEM_ACTION_TYPES.FETCH_ITEMS]({ commit }: ActionParams, { start }: { start: number } = { start: 0 }) { + commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'all', fetching: true }) + + const response = await ItemService.debounceFetchAll(start || state.lastItemLoaded.all) + + 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 }) + } + + const lastItem = response?.data.items[response?.data.items.length - 1].id + commit(FEED_ITEM_MUTATION_TYPES.SET_LAST_ITEM_LOADED, { key: 'all', lastItem }) + commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'all', fetching: false }) + }, + + /** * Fetch Starred Items from Backend and call commit to update state * * @param param0 ActionParams @@ -124,6 +153,28 @@ export const actions = { }, /** + * Fetch Folder Items from Backend and call commit to update state + * + * @param param0 ActionParams + * @param param0.commit + * @param param1 ActionArgs + * @param param1.start + * @param param1.folderId + */ + async [FEED_ITEM_ACTION_TYPES.FETCH_FOLDER_FEED_ITEMS]({ commit }: ActionParams, { folderId, start }: { folderId: number; start: number }) { + commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'folder-' + folderId, fetching: true }) + const response = await ItemService.debounceFetchFolderFeedItems(folderId, start || state.lastItemLoaded['folder-' + folderId]) + + 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 }) + } + const lastItem = response?.data.items[response?.data.items.length - 1].id + commit(FEED_ITEM_MUTATION_TYPES.SET_LAST_ITEM_LOADED, { key: 'folder-' + folderId, lastItem }) + commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'folder-' + folderId, fetching: false }) + }, + + /** * Sends message to Backend to mark as read, and then call commit to update state * * @param param0 ActionParams |