1
0
mirror of https://github.com/chylex/Nextcloud-News.git synced 2025-04-09 19:15:42 +02:00
This commit is contained in:
Bernhard Posselt 2015-01-23 14:32:53 +01:00
parent 5adeba1a56
commit df509a4b85
22 changed files with 371 additions and 85 deletions

View File

@ -1,7 +1,7 @@
owncloud-news (5.0.1)
* **Enhancement**: Show error messages when authentication or network related errors appear
* **Enhancement**: Show a pull to refresh area if you are at the very top and jump to the previous article using either page up or a jump to previous article shortcut. If this area is already visible reload the page
* **Enhancement**: Make it possible to overwrite the global ordering for certain feeds
owncloud-news (5.0.0)
* **New dependency**: Bump required ownCloud version to 8

View File

@ -160,6 +160,12 @@
<default>false</default>
<notnull>true</notnull>
</field>
<field>
<name>ordering</name>
<type>integer</type>
<default>0</default>
<notnull>true</notnull>
</field>
<index>
<name>news_feeds_user_id_index</name>

View File

@ -7,7 +7,7 @@
<author>Bernhard Posselt, Alessandro Cosentino, Jan-Christoph Borchardt</author>
<category>multimedia</category>
<licence>AGPL</licence>
<version>5.0.0</version>
<version>5.0.1</version>
<namespace>News</namespace>
<!-- resources -->

View File

@ -42,6 +42,7 @@ return ['routes' => [
['name' => 'feed#update', 'url' => '/feeds/{feedId}/update', 'verb' => 'POST'],
['name' => 'feed#active', 'url' => '/feeds/active', 'verb' => 'GET'],
['name' => 'feed#import', 'url' => '/feeds/import/articles', 'verb' => 'POST'],
['name' => 'feed#ordering', 'url' => '/feeds/{feedId}/ordering', 'verb' => 'POST'],
// items
['name' => 'item#index', 'url' => '/items', 'verb' => 'GET'],

View File

@ -294,4 +294,21 @@ class FeedController extends Controller {
}
/**
* @NoAdminRequired
*
* @param int $feedId
* @param int $ordering
*/
public function ordering ($feedId, $ordering) {
try {
$this->feedService->setOrdering($feedId, $ordering, $this->userId);
} catch(ServiceNotFoundException $ex) {
return $this->error($ex, Http::STATUS_NOT_FOUND);
}
return [];
}
}

View File

@ -174,4 +174,15 @@
#app-navigation .animate-show.ng-hide {
opacity: 0;
}
#app-navigation .feed-no-ordering {
transform: rotate(270deg);
}
#app-navigation .feed-reverse-ordering {
transform: rotate(180deg);
}
#app-navigation .feed-normal-ordering {
}

2
css/news.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,8 @@ use \OCP\AppFramework\Db\Entity;
* @method void setId(integer $value)
* @method string getUserId()
* @method void setUserId(string $value)
* @method int getOrdering()
* @method void setOrdering(int $value)
* @method string getUrlHash()
* @method void setUrlHash(string $value)
* @method string getLocation()
@ -66,6 +68,7 @@ class Feed extends Entity implements IAPI, \JsonSerializable {
protected $lastModified;
protected $etag;
protected $location;
protected $ordering;
public function __construct(){
$this->addType('parentId', 'integer');
@ -75,6 +78,7 @@ class Feed extends Entity implements IAPI, \JsonSerializable {
$this->addType('preventUpdate', 'boolean');
$this->addType('deletedAt', 'integer');
$this->addType('articlesPerUpdate', 'integer');
$this->addType('ordering', 'integer');
}
@ -96,7 +100,8 @@ class Feed extends Entity implements IAPI, \JsonSerializable {
'preventUpdate',
'deletedAt',
'articlesPerUpdate',
'location'
'location',
'ordering'
]);
$url = parse_url($this->link)['host'];

View File

@ -84,7 +84,8 @@ app.config(function ($routeProvider, $provide, $httpProvider) {
return {
// request to items also returns feeds
data: /* @ngInject */ function (
$http, $route, $q, BASE_URL, ITEM_BATCH_SIZE, SettingsResource) {
$http, $route, $q, BASE_URL, ITEM_BATCH_SIZE, FEED_TYPE,
SettingsResource, FeedResource) {
var showAll = SettingsResource.get('showAll');
var oldestFirst = SettingsResource.get('oldestFirst');
@ -107,6 +108,15 @@ app.config(function ($routeProvider, $provide, $httpProvider) {
parameters.id = $route.current.params.id;
}
// check if a custom ordering is set
if (type === FEED_TYPE.FEED) {
var feed = FeedResource.getById(parameters.id);
if (feed.ordering === 1) {
parameters.oldestFirst = true;
} else if (feed.ordering === 2) {
parameters.oldestFirst = false;
}
}
$http({
url: BASE_URL + '/items',

View File

@ -81,8 +81,9 @@ app.config(["$routeProvider", "$provide", "$httpProvider", function ($routeProvi
var getItemResolve = function (type) {
return {
// request to items also returns feeds
data: /* @ngInject */ ["$http", "$route", "$q", "BASE_URL", "ITEM_BATCH_SIZE", "SettingsResource", function (
$http, $route, $q, BASE_URL, ITEM_BATCH_SIZE, SettingsResource) {
data: /* @ngInject */ ["$http", "$route", "$q", "BASE_URL", "ITEM_BATCH_SIZE", "FEED_TYPE", "SettingsResource", "FeedResource", function (
$http, $route, $q, BASE_URL, ITEM_BATCH_SIZE, FEED_TYPE,
SettingsResource, FeedResource) {
var showAll = SettingsResource.get('showAll');
var oldestFirst = SettingsResource.get('oldestFirst');
@ -105,6 +106,15 @@ app.config(["$routeProvider", "$provide", "$httpProvider", function ($routeProvi
parameters.id = $route.current.params.id;
}
// check if a custom ordering is set
if (type === FEED_TYPE.FEED) {
var feed = FeedResource.getById(parameters.id);
if (feed.ordering === 1) {
parameters.oldestFirst = true;
} else if (feed.ordering === 2) {
parameters.oldestFirst = false;
}
}
$http({
url: BASE_URL + '/items',
@ -386,8 +396,24 @@ app.controller('ContentController',
item.keepUnread = !item.keepUnread;
};
var self = this;
var getOrdering = function () {
var ordering = SettingsResource.get('oldestFirst');
if (self.isFeed()) {
var feed = FeedResource.getById($routeParams.id);
if (feed && feed.ordering === 1) {
ordering = true;
} else if (feed && feed.ordering === 2) {
ordering = false;
}
}
return ordering;
};
this.orderBy = function () {
if (SettingsResource.get('oldestFirst')) {
if (getOrdering()) {
return 'id';
} else {
return '-id';
@ -449,7 +475,7 @@ app.controller('ContentController',
var type = $route.current.$$route.type;
var id = $routeParams.id;
var oldestFirst = SettingsResource.get('oldestFirst');
var oldestFirst = getOrdering();
var showAll = SettingsResource.get('showAll');
var self = this;
@ -785,6 +811,11 @@ app.controller('NavigationController',
FolderResource.delete(folder.name);
};
this.setOrdering = function (feed, ordering) {
FeedResource.setOrdering(feed.id, ordering);
$route.reload();
};
var self = this;
$rootScope.$on('moveFeedToFolder', function (scope, data) {
@ -1225,6 +1256,19 @@ app.factory('FeedResource', ["Resource", "$http", "BASE_URL", "$q", function (Re
};
FeedResource.prototype.setOrdering = function (feedId, ordering) {
var feed = this.getById(feedId);
if (feed) {
feed.ordering = ordering;
var url = this.BASE_URL + '/feeds/' + feedId + '/ordering';
return this.http.post(url, {
ordering: ordering
});
}
};
return new FeedResource($http, BASE_URL, $q);
}]);
app.factory('FolderResource', ["Resource", "$http", "BASE_URL", "$q", function (Resource, $http, BASE_URL, $q) {

4
js/build/app.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -70,8 +70,24 @@ function (Publisher, FeedResource, ItemResource, SettingsResource, data,
item.keepUnread = !item.keepUnread;
};
var self = this;
var getOrdering = function () {
var ordering = SettingsResource.get('oldestFirst');
if (self.isFeed()) {
var feed = FeedResource.getById($routeParams.id);
if (feed && feed.ordering === 1) {
ordering = true;
} else if (feed && feed.ordering === 2) {
ordering = false;
}
}
return ordering;
};
this.orderBy = function () {
if (SettingsResource.get('oldestFirst')) {
if (getOrdering()) {
return 'id';
} else {
return '-id';
@ -133,7 +149,7 @@ function (Publisher, FeedResource, ItemResource, SettingsResource, data,
var type = $route.current.$$route.type;
var id = $routeParams.id;
var oldestFirst = SettingsResource.get('oldestFirst');
var oldestFirst = getOrdering();
var showAll = SettingsResource.get('showAll');
var self = this;

View File

@ -281,6 +281,11 @@ function ($route, FEED_TYPE, FeedResource, FolderResource, ItemResource,
FolderResource.delete(folder.name);
};
this.setOrdering = function (feed, ordering) {
FeedResource.setOrdering(feed.id, ordering);
$route.reload();
};
var self = this;
$rootScope.$on('moveFeedToFolder', function (scope, data) {

View File

@ -317,5 +317,18 @@ app.factory('FeedResource', function (Resource, $http, BASE_URL, $q) {
};
FeedResource.prototype.setOrdering = function (feedId, ordering) {
var feed = this.getById(feedId);
if (feed) {
feed.ordering = ordering;
var url = this.BASE_URL + '/feeds/' + feedId + '/ordering';
return this.http.post(url, {
ordering: ordering
});
}
};
return new FeedResource($http, BASE_URL, $q);
});

View File

@ -58,11 +58,18 @@ describe('ContentController', function () {
it('should return order by', inject(function ($controller,
SettingsResource) {
SettingsResource, FEED_TYPE) {
var route = {
current: {
$$route: {
type: FEED_TYPE.FOLDER
}
}
};
var ctrl = $controller('ContentController', {
SettingsResource: SettingsResource,
data: {},
$route: route
});
expect(ctrl.orderBy()).toBe('-id');
@ -72,6 +79,33 @@ describe('ContentController', function () {
expect(ctrl.orderBy()).toBe('id');
}));
it('should return order if custom ordering', inject(function ($controller,
SettingsResource, FeedResource, FEED_TYPE) {
var route = {
current: {
$$route: {
type: FEED_TYPE.FEED
}
}
};
FeedResource.receive([
{id: 1, folderId: 3, url: 'ye', unreadCount: 45, ordering: 1},
]);
var ctrl = $controller('ContentController', {
data: {},
$route: route,
$routeParams: {
id: 1
}
});
expect(ctrl.orderBy()).toBe('id');
SettingsResource.set('oldestFirst', true);
expect(ctrl.orderBy()).toBe('id');
}));
it('should mark read', inject(function ($controller, ItemResource,
FeedResource, Publisher) {

View File

@ -1012,4 +1012,30 @@ describe('NavigationController', function () {
}));
it ('should set the feed ordering',
inject(function ($controller, FeedResource) {
FeedResource.add({
id: 2,
url: 'http://test.com',
folderId: 3,
ordering: 0
});
FeedResource.setOrdering = jasmine.createSpy('ordering');
var route = {
reload: jasmine.createSpy('reload')
};
var ctrl = $controller('NavigationController', {
$route: route
});
ctrl.setOrdering(FeedResource.getById(2), 2);
expect(FeedResource.setOrdering).toHaveBeenCalledWith(2, 2);
expect(route.reload).toHaveBeenCalled();
}));
});

View File

@ -29,7 +29,8 @@ describe('FeedResource', function () {
FeedResource.receive([
{id: 1, folderId: 3, url: 'ye', unreadCount: 45},
{id: 2, folderId: 4, location: 'test', url: 'sye', unreadCount: 25},
{id: 3, folderId: 3, title: 'hore', url: '1sye', unreadCount: 0}
{id: 3, folderId: 3, title: 'hore', url: '1sye', unreadCount: 0,
ordering: 0}
]);
}));
@ -287,4 +288,17 @@ describe('FeedResource', function () {
expect(FeedResource.getUnreadCount()).toBe(70);
}));
it ('should set the feed ordering', inject(function (FeedResource) {
http.expectPOST('base/feeds/3/ordering', {
ordering: 2
}).respond(200, {});
FeedResource.setOrdering(3, 2);
http.flush();
expect(FeedResource.getById(3).ordering).toBe(2);
}));
});

View File

@ -182,86 +182,80 @@ class FeedService extends Service {
* @return Feed the updated feed entity
*/
public function update($feedId, $userId){
try {
$existingFeed = $this->feedMapper->find($feedId, $userId);
$existingFeed = $this->find($feedId, $userId);
if($existingFeed->getPreventUpdate() === true) {
if($existingFeed->getPreventUpdate() === true) {
return $existingFeed;
}
// for backwards compability it can be that the location is not set
// yet, if so use the url
$location = $existingFeed->getLocation();
if (!$location) {
$location = $existingFeed->getUrl();
}
try {
list($fetchedFeed, $items) = $this->feedFetcher->fetch(
$location,
false,
$existingFeed->getLastModified(),
$existingFeed->getEtag()
);
// if there is no feed it means that no update took place
if (!$fetchedFeed) {
return $existingFeed;
}
// for backwards compability it can be that the location is not set
// yet, if so use the url
$location = $existingFeed->getLocation();
if (!$location) {
$location = $existingFeed->getUrl();
// update number of articles on every feed update
$itemCount = count($items);
// this is needed to adjust to updates that add more items
// than when the feed was created. You can't update the count
// if it's lower because it may be due to the caching headers
// that were sent as the request and it might cause unwanted
// deletion and reappearing of feeds
if ($itemCount > $existingFeed->getArticlesPerUpdate()) {
$existingFeed->setArticlesPerUpdate($itemCount);
}
try {
list($fetchedFeed, $items) = $this->feedFetcher->fetch(
$location,
false,
$existingFeed->getLastModified(),
$existingFeed->getEtag()
);
$existingFeed->setLastModified($fetchedFeed->getLastModified());
$existingFeed->setEtag($fetchedFeed->getEtag());
$existingFeed->setLocation($fetchedFeed->getLocation());
$this->feedMapper->update($existingFeed);
// if there is no feed it means that no update took place
if (!$fetchedFeed) {
return $existingFeed;
// insert items in reverse order because the first one is
// usually the newest item
for($i=$itemCount-1; $i>=0; $i--){
$item = $items[$i];
$item->setFeedId($existingFeed->getId());
try {
$this->itemMapper->findByGuidHash(
$item->getGuidHash(), $feedId, $userId
);
} catch(DoesNotExistException $ex){
$item = $this->enhancer->enhance($item,
$existingFeed->getLink());
$item->setBody(
$this->purifier->purify($item->getBody())
);
$this->itemMapper->insert($item);
}
// update number of articles on every feed update
$itemCount = count($items);
// this is needed to adjust to updates that add more items
// than when the feed was created. You can't update the count
// if it's lower because it may be due to the caching headers
// that were sent as the request and it might cause unwanted
// deletion and reappearing of feeds
if ($itemCount > $existingFeed->getArticlesPerUpdate()) {
$existingFeed->setArticlesPerUpdate($itemCount);
}
$existingFeed->setLastModified($fetchedFeed->getLastModified());
$existingFeed->setEtag($fetchedFeed->getEtag());
$existingFeed->setLocation($fetchedFeed->getLocation());
$this->feedMapper->update($existingFeed);
// insert items in reverse order because the first one is
// usually the newest item
for($i=$itemCount-1; $i>=0; $i--){
$item = $items[$i];
$item->setFeedId($existingFeed->getId());
try {
$this->itemMapper->findByGuidHash(
$item->getGuidHash(), $feedId, $userId
);
} catch(DoesNotExistException $ex){
$item = $this->enhancer->enhance($item,
$existingFeed->getLink());
$item->setBody(
$this->purifier->purify($item->getBody())
);
$this->itemMapper->insert($item);
}
}
} catch(FetcherException $ex){
// failed updating is not really a problem, so only log it
$this->logger->debug(
'Can not update feed with url ' . $existingFeed->getUrl() .
' and location ' . $existingFeed->getLocation() .
': ' . $ex->getMessage(),
$this->loggerParams
);
}
return $this->feedMapper->find($feedId, $userId);
} catch (DoesNotExistException $ex){
throw new ServiceNotFoundException('Feed does not exist');
} catch(FetcherException $ex){
// failed updating is not really a problem, so only log it
$this->logger->debug(
'Can not update feed with url ' . $existingFeed->getUrl() .
' and location ' . $existingFeed->getLocation() .
': ' . $ex->getMessage(),
$this->loggerParams
);
}
return $this->find($feedId, $userId);
}
@ -421,4 +415,17 @@ class FeedService extends Service {
}
/**
* Sets the feed ordering
* @param int $id the id of the feed
* @param int $ordering 0 for no ordering, 1 for reverse, 2 for normal
* @param string $userId the id of the user
*/
public function setOrdering ($id, $ordering, $userId) {
$feed = $this->find($id, $userId);
$feed->setOrdering($ordering);
$this->feedMapper->update($feed);
}
}

View File

@ -83,6 +83,23 @@
<div class="app-navigation-entry-menu">
<ul>
<li>
<button ng-click="Navigation.setOrdering(feed, 1)"
ng-show="feed.ordering == 0"
class="icon-caret-dark feed-no-ordering"
title="<?php p($l->t('No feed ordering')); ?>">
</button>
<button ng-click="Navigation.setOrdering(feed, 2)"
ng-show="feed.ordering == 1"
class="icon-caret-dark feed-reverse-ordering"
title="<?php p($l->t('Reversed feed ordering')); ?>">
</button>
<button ng-click="Navigation.setOrdering(feed, 0)"
ng-show="feed.ordering == 2"
class="icon-caret-dark feed-normal-ordering"
title="<?php p($l->t('Normal feed ordering')); ?>">
</button>
</li>
<li>
<button ng-click="feed.editing=true"
class="icon-rename"

View File

@ -507,4 +507,31 @@ class FeedControllerTest extends \PHPUnit_Framework_TestCase {
$this->assertEquals($response->getStatus(), Http::STATUS_NOT_FOUND);
}
public function testOrdering() {
$this->feedService->expects($this->once())
->method('setOrdering')
->with($this->equalTo(4),
$this->equalTo(2),
$this->equalTo($this->user));
$this->controller->ordering(4, 2);
}
public function testOrderingDoesNotExist(){
$msg = 'hehe';
$this->feedService->expects($this->once())
->method('setOrdering')
->will($this->throwException(new ServiceNotFoundException($msg)));
$response = $this->controller->ordering(4, 2);
$params = json_decode($response->render(), true);
$this->assertEquals($msg, $params['message']);
$this->assertEquals($response->getStatus(), Http::STATUS_NOT_FOUND);
}
}

View File

@ -30,6 +30,7 @@ class FeedTest extends \PHPUnit_Framework_TestCase {
$feed->setUnreadCount(321);
$feed->setLink('https://www.google.com/some/weird/path');
$feed->setLocation('http://google.at');
$feed->setOrdering(2);
return $feed;
}
@ -67,7 +68,8 @@ class FeedTest extends \PHPUnit_Framework_TestCase {
'deletedAt' => null,
'articlesPerUpdate' => null,
'cssClass' => 'custom-google-com',
'location' => 'http://google.at'
'location' => 'http://google.at',
'ordering' => 2
], $feed->jsonSerialize());
}

View File

@ -777,5 +777,36 @@ class FeedServiceTest extends \PHPUnit_Framework_TestCase {
}
public function testOrdering () {
$feed = Feed::fromRow(['id' => 3]);
$this->feedMapper->expects($this->once())
->method('find')
->with($this->equalTo($feed->getId()),
$this->equalTo($this->user))
->will($this->returnValue($feed));
$feed->setOrdering(2);
$this->feedMapper->expects($this->once())
->method('update')
->with($this->equalTo($feed));
$this->feedService->setOrdering(3, 2, $this->user);
}
/**
* @expectedException OCA\News\Service\ServiceNotFoundException
*/
public function testOrderingDoesNotExist () {
$feed = Feed::fromRow(['id' => 3]);
$this->feedMapper->expects($this->once())
->method('find')
->will($this->throwException(new DoesNotExistException('')));
$this->feedService->setOrdering(3, 2, $this->user);
}
}