Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ You can also check [on GitHub](https://github.com/nextcloud/news/releases), the
# Unreleased
## [28.x.x]
### Changed

- Add feature to Group starred Items per Feed

### Fixed
- Special characters may be displayed incorrectly when full text is enabled
Expand Down
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
['name' => 'page#index', 'url' => '/folder/{folderId}', 'verb' => 'GET', 'postfix' => 'view.folderid'],
['name' => 'page#index', 'url' => '/recent', 'verb' => 'GET', 'postfix' => 'view.recent'],
['name' => 'page#index', 'url' => '/starred', 'verb' => 'GET', 'postfix' => 'view.starred'],
['name' => 'page#index', 'url' => '/starred/{feedId}', 'verb' => 'GET', 'postfix' => 'view.starred.feed'],
['name' => 'page#index', 'url' => '/unread', 'verb' => 'GET', 'postfix' => 'view.unread'],
['name' => 'page#settings', 'url' => '/settings', 'verb' => 'GET'],
['name' => 'page#update_settings', 'url' => '/settings', 'verb' => 'PUT'],
Expand Down
3 changes: 2 additions & 1 deletion lib/Controller/ItemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
// in case this is called directly and not from the website use the
// internal state
if ($showAll === null) {
$showAll = $this->settings->getUserValue(

Check failure on line 80 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.3

Call to deprecated method getUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly

Check failure on line 80 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.4

Call to deprecated method getUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly
$this->getUserId(),
$this->appName,
'showAll'
Expand All @@ -85,20 +85,20 @@
}

if ($oldestFirst === null) {
$oldestFirst = $this->settings->getUserValue(

Check failure on line 88 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.3

Call to deprecated method getUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly

Check failure on line 88 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.4

Call to deprecated method getUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly
$this->getUserId(),
$this->appName,
'oldestFirst'
) === '1';
}

$this->settings->setUserValue(

Check failure on line 95 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.3

Call to deprecated method setUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly

Check failure on line 95 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.4

Call to deprecated method setUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly
$this->getUserId(),
$this->appName,
'lastViewedFeedId',
$id
);
$this->settings->setUserValue(

Check failure on line 101 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.3

Call to deprecated method setUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly

Check failure on line 101 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.4

Call to deprecated method setUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly
$this->getUserId(),
$this->appName,
'lastViewedFeedType',
Expand Down Expand Up @@ -155,7 +155,8 @@
$limit,
$offset,
$oldestFirst,
$search_items
$search_items,
$id
);
break;
}
Expand Down Expand Up @@ -186,7 +187,7 @@
#[NoAdminRequired]
public function newItems(int $type, int $id, $lastModified = 0): array
{
$showAll = $this->settings->getUserValue(

Check failure on line 190 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.3

Call to deprecated method getUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly

Check failure on line 190 in lib/Controller/ItemController.php

View workflow job for this annotation

GitHub Actions / phpstan: Nextcloud pre-release with 8.4

Call to deprecated method getUserValue() of interface OCP\IConfig: 33.0.0 - use {@see IUserConfig} directly
$this->getUserId(),
$this->appName,
'showAll'
Expand Down
3 changes: 3 additions & 0 deletions lib/Db/Feed.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class Feed extends Entity implements IAPI, \JsonSerializable
protected $folderId;
/** @var int */
protected $unreadCount;
/** @var int */
protected $starredCount;
/** @var string|null */
protected $link = null;
/** @var bool */
Expand Down Expand Up @@ -322,6 +324,7 @@ public function jsonSerialize(): array
'added',
'folderId',
'unreadCount',
'starredCount',
'link',
'preventUpdate',
'deletedAt',
Expand Down
24 changes: 20 additions & 4 deletions lib/Db/FeedMapperV2.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,36 @@ public function __construct(IDBConnection $db, Time $time)
public function findAllFromUser(string $userId, array $params = []): array
{
$builder = $this->db->getQueryBuilder();
$builder->select('feeds.*', $builder->func()->count('items.id', 'unreadCount'))
$builder
->select('feeds.*')
->selectAlias(
$builder->createFunction('COUNT(DISTINCT items_unread.id)'),
'unreadCount'
)
->selectAlias(
$builder->createFunction('COUNT(DISTINCT items_starred.id)'),
'starredCount'
)
->from(static::TABLE_NAME, 'feeds')
->leftJoin(
'feeds',
ItemMapperV2::TABLE_NAME,
'items',
'items.feed_id = feeds.id AND items.unread = :unread'
'items_unread',
'items_unread.feed_id = feeds.id AND items_unread.unread = :unread'
)
->leftJoin(
'feeds',
ItemMapperV2::TABLE_NAME,
'items_starred',
'items_starred.feed_id = feeds.id AND items_starred.starred = :starred'
)
->where('feeds.user_id = :user_id')
->andWhere('feeds.deleted_at = 0')
->groupBy('feeds.id')
->setParameter('unread', true, IQueryBuilder::PARAM_BOOL)
->setParameter('starred', true, IQueryBuilder::PARAM_BOOL)
->setParameter('user_id', $userId)
->addOrderBy('title');
->addOrderBy('feeds.title');

return $this->findEntities($builder);
}
Expand Down
5 changes: 5 additions & 0 deletions lib/Db/ItemMapperV2.php
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ public function findAllFolder(
* @throws ServiceValidationException
*/
public function findAllItems(
int $feedId,
string $userId,
int $type,
int $limit,
Expand Down Expand Up @@ -588,6 +589,10 @@ public function findAllItems(
case ListType::STARRED:
$builder->andWhere('items.starred = :starred')
->setParameter('starred', true);
if ($feedId !== 0) {
$builder->andWhere('items.feed_id = :feedId')
->setParameter('feedId', $feedId);
}
break;
case ListType::UNREAD:
$builder->andWhere('items.unread = :unread')
Expand Down
5 changes: 3 additions & 2 deletions lib/Service/ItemServiceV2.php
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,9 @@ public function findAllWithFilters(
int $limit,
int $offset,
bool $oldestFirst,
array $search = []
array $search = [],
int $id = 0
): array {
return $this->mapper->findAllItems($userId, $type, $limit, $offset, $oldestFirst, $search);
return $this->mapper->findAllItems($id, $userId, $type, $limit, $offset, $oldestFirst, $search);
}
}
38 changes: 37 additions & 1 deletion src/components/Sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,27 @@
<HistoryIcon />
</template>
</NcAppNavigationItem>
<NcAppNavigationItem :name="t('news', 'Starred')" icon="icon-starred" :to="{ name: ROUTES.STARRED }">
<NcAppNavigationItem
:name="t('news', 'Starred')"
icon="icon-starred"
:to="{ name: ROUTES.STARRED }"
:allow-collapse="true"
:force-menu="true"
:open="wasStarredVisited">
<NcAppNavigationItem
v-for="group in GroupedStars"
:key="group.id"
:ref="'starred-' + group.id"
:name="group.title"
:to="{ name: ROUTES.STARRED, params: { feedId: group.id } }">
<template #icon>
<RssIcon v-if="!group.faviconLink" />
<span v-else style="width: 16px; height: 16px; background-size: contain;" :style="{ backgroundImage: 'url(' + group.faviconLink + ')' }" />
</template>
<template #counter>
<NcCounterBubble :count="group.starredCount" />
</template>
</NcAppNavigationItem>
<template #counter>
<NcCounterBubble :count="items.starredCount" />
</template>
Expand Down Expand Up @@ -351,6 +371,7 @@ export default defineComponent({
polling: null,
uploadStatus: null,
selectedFile: null,
wasStarredVisited: false,
displayModeOptions: [
{
id: '0',
Expand Down Expand Up @@ -408,6 +429,10 @@ export default defineComponent({
return navItems
},

GroupedStars(): Array<Feed> {
return this.$store.getters.feeds.filter((item: Feed) => item.starredCount !== 0)
},

loading: {
get() {
return this.$store.getters.loading
Expand Down Expand Up @@ -512,6 +537,17 @@ export default defineComponent({
}
},
},

// Watch route changes and set `wasStarredVisited`
$route: {
handler(to) {
if (to.name === this.ROUTES.STARRED && (to.params && to.params.feedId)) {
this.wasStarredVisited = true
}
},

immediate: true,
},
},

created() {
Expand Down
48 changes: 41 additions & 7 deletions src/components/routes/Starred.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<template>
<ContentTemplate
v-if="!loading"
:list-name="starredFeed ? starredFeed.title : t('news', 'Starred')"
:list-count="starredFeed ? starredFeed.starredCount : items.starredCount"
:items="starred"
:list-name="t('news', 'Starred')"
:list-count="items.starredCount"
fetch-key="starred"
:fetch-key="fetchKey"
@load-more="fetchMore()" />
</template>

<script lang="ts">
import type { Feed } from '../../types/Feed.ts'
import type { FeedItem } from '../../types/FeedItem.ts'

import { defineComponent } from 'vue'
Expand All @@ -23,10 +24,29 @@ export default defineComponent({
ContentTemplate,
},

props: {
/**
* Unique identifier of the feed whose starred entries this component displays.
*
* @type {string}
*
* Used to fetch the relevant entries from the backend and to filter the displayed items.
*/
feedId: {
type: String,
required: false,
default: undefined,
},
},

computed: {
...mapState(['items']),
starred(): FeedItem[] {
let items = this.$store.getters.starred
const starred = this.$store.getters.starred

let items = this.feedId
? starred.filter((item: FeedItem) => item.feedId === this.id)
: starred
/*
* Sorting items is needed because the allItems array can contain
* different orderings due to possible individual feed sorting
Expand All @@ -42,21 +62,35 @@ export default defineComponent({
oldestFirst() {
return this.$store.getters.oldestFirst
},

id(): number {
return this.feedId ? Number(this.feedId) : 0
},

fetchKey(): string {
return this.feedId ? 'starred-' + this.feedId : 'starred'
},

starredFeed(): Feed {
return this.feedId
? this.$store.getters.feeds.find((feed: Feed) => feed.id === this.id)
: undefined
},
},

created() {
/*
* When sorting newest to oldest lastItemLoaded needs to be reset to get new items for this route
*/
if (this.oldestFirst === false) {
this.$store.commit(MUTATIONS.SET_LAST_ITEM_LOADED, { key: 'starred', lastItem: undefined })
this.$store.commit(MUTATIONS.SET_LAST_ITEM_LOADED, { key: this.fetchKey, lastItem: undefined })
}
},

methods: {
async fetchMore() {
if (!this.$store.state.items.fetchingItems.starred) {
this.$store.dispatch(ACTIONS.FETCH_STARRED)
if (!this.$store.state.items.fetchingItems[this.fetchKey]) {
this.$store.dispatch(ACTIONS.FETCH_STARRED, { feedId: this.id })
}
},
},
Expand Down
4 changes: 3 additions & 1 deletion src/dataservices/item.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ export class ItemService {
/**
* Makes backend call to retrieve starred items
*
* @param feedId (id of the feed to retrieve starred items for)
* @param start (id of last starred item loaded)
* @return response object containing backend request response
*/
static async fetchStarred(start: number): Promise<AxiosResponse> {
static async fetchStarred(feedId: number, start: number): Promise<AxiosResponse> {
return await axios.get(API_ROUTES.ITEMS, {
params: {
limit: 40,
Expand All @@ -51,6 +52,7 @@ export class ItemService {
showAll: store.state.showAll,
type: ITEM_TYPES.STARRED,
offset: start,
...(feedId !== 0 ? { id: feedId } : {}),
},
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const routes = [
},
{
name: ROUTES.STARRED,
path: '/starred',
path: '/starred/:feedId?',
component: StarredPanel,
props: true,
},
Expand Down
12 changes: 12 additions & 0 deletions src/store/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,18 @@ export const mutations = {
}
},

[FEED_MUTATION_TYPES.MODIFY_STARRED_COUNT](
state: FeedState,
{ feedId, add }: { feedId: number, add: boolean },
) {
const feed = state.feeds.find((feed: Feed) => {
return feed.id === feedId
})
if (feed) {
_.assign(feed, { starredCount: feed.starredCount + (add ? 1 : -1) })
}
},

[FEED_MUTATION_TYPES.FEED_DELETE](
state: FeedState,
feedId: number,
Expand Down
20 changes: 13 additions & 7 deletions src/store/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,19 @@ export const actions = {
* @param param0 ActionParams
* @param param0.commit Commit param
* @param param1 ActionArgs
* @param param1.feedId ID of the feed
* @param param1.start Start data
*/
async [FEED_ITEM_ACTION_TYPES.FETCH_STARRED](
{ commit }: ActionParams<ItemState>,
{ start }: { start: number } = { start: 0 },
{ feedId, start }: { feedId: number, start: number } = { feedId: 0, start: 0 },
) {
const requestId = Date.now()
latestFetchRequest = requestId

commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'starred', fetching: true })
const response = await ItemService.fetchStarred(start || state.lastItemLoaded.starred)
commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'starred-' + feedId, fetching: true })

const response = await ItemService.fetchStarred(feedId, start || state.lastItemLoaded['starred-' + feedId])

// skip response if outdated
if (latestFetchRequest !== requestId) {
Expand All @@ -198,15 +200,15 @@ export const actions = {
}

if (response?.data.items.length < 40) {
commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'starred', loaded: true })
commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'starred-' + feedId, loaded: true })
} else {
commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'starred', loaded: false })
commit(FEED_ITEM_MUTATION_TYPES.SET_ALL_LOADED, { key: 'starred-' + feedId, loaded: false })
}
if (response?.data.items.length > 0) {
const lastItem = response?.data.items[response?.data.items.length - 1].id
commit(FEED_ITEM_MUTATION_TYPES.SET_LAST_ITEM_LOADED, { key: 'starred', lastItem })
commit(FEED_ITEM_MUTATION_TYPES.SET_LAST_ITEM_LOADED, { key: 'starred-' + feedId, lastItem })
}
commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'starred', fetching: false })
commit(FEED_ITEM_MUTATION_TYPES.SET_FETCHING, { key: 'starred-' + feedId, fetching: false })
},

/**
Expand Down Expand Up @@ -354,8 +356,10 @@ export const actions = {
ItemService.markStarred(item, true)

item.starred = true
const feedId = item.feedId
commit(FEED_ITEM_MUTATION_TYPES.UPDATE_ITEM, { item })
commit(FEED_ITEM_MUTATION_TYPES.SET_STARRED_COUNT, state.starredCount + 1)
commit(FEED_MUTATION_TYPES.MODIFY_STARRED_COUNT, { feedId, add: true })
},

/**
Expand All @@ -373,8 +377,10 @@ export const actions = {
ItemService.markStarred(item, false)

item.starred = false
const feedId = item.feedId
commit(FEED_ITEM_MUTATION_TYPES.UPDATE_ITEM, { item })
commit(FEED_ITEM_MUTATION_TYPES.SET_STARRED_COUNT, state.starredCount - 1)
commit(FEED_MUTATION_TYPES.MODIFY_STARRED_COUNT, { feedId, add: false })
},
}

Expand Down
1 change: 1 addition & 0 deletions src/types/Feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { FEED_ORDER, FEED_UPDATE_MODE } from '../enums/index.ts'
export type Feed = {
folderId?: number
unreadCount: number
starredCount: number
url: string
title?: string
autoDiscover?: boolean
Expand Down
Loading
Loading