1
0
mirror of https://github.com/chylex/Nextcloud-News.git synced 2024-12-30 11:42:46 +01:00

autopaging

This commit is contained in:
Bernhard Posselt 2014-09-13 16:58:38 +02:00
parent 11c4b03d70
commit 08df2433ca
11 changed files with 264 additions and 97 deletions

View File

@ -184,7 +184,7 @@ class ItemMapper extends Mapper implements IMapper {
$sql .= 'AND `items`.`id` ' . $this->getOperator($oldestFirst) . ' ? ';
$params[] = $offset;
}
$sql = $this->makeSelectQueryStatus($sql, $status);
$sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst);
return $this->findEntities($sql, $params, $limit);
}
@ -196,7 +196,7 @@ class ItemMapper extends Mapper implements IMapper {
$sql .= 'AND `items`.`id` ' . $this->getOperator($oldestFirst) . ' ? ';
$params[] = $offset;
}
$sql = $this->makeSelectQueryStatus($sql, $status);
$sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst);
return $this->findEntities($sql, $params, $limit);
}
@ -208,7 +208,7 @@ class ItemMapper extends Mapper implements IMapper {
$sql .= 'AND `items`.`id` ' . $this->getOperator($oldestFirst) . ' ? ';
$params[] = $offset;
}
$sql = $this->makeSelectQueryStatus($sql, $status);
$sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst);
return $this->findEntities($sql, $params, $limit);
}

View File

@ -20,7 +20,7 @@ app.config(function ($routeProvider, $provide, $httpProvider) {
// constants
$provide.constant('REFRESH_RATE', 60); // seconds
$provide.constant('ITEM_BATCH_SIZE', 50); // how many items to autopage by
$provide.constant('ITEM_BATCH_SIZE', 3); // how many items to autopage by
$provide.constant('BASE_URL', OC.generateUrl('/apps/news'));
$provide.constant('FEED_TYPE', feedType);

View File

@ -17,7 +17,7 @@ app.config(["$routeProvider", "$provide", "$httpProvider", function ($routeProvi
// constants
$provide.constant('REFRESH_RATE', 60); // seconds
$provide.constant('ITEM_BATCH_SIZE', 50); // how many items to autopage by
$provide.constant('ITEM_BATCH_SIZE', 3); // how many items to autopage by
$provide.constant('BASE_URL', OC.generateUrl('/apps/news'));
$provide.constant('FEED_TYPE', feedType);
@ -296,8 +296,10 @@ app.controller('ContentController',
}
});
FeedResource.markItemsOfFeedsRead(feedIds);
ItemResource.markItemsRead(ids);
if (ids.length > 0) {
FeedResource.markItemsOfFeedsRead(feedIds);
ItemResource.markItemsRead(ids);
}
};
this.isFeed = function () {
@ -305,18 +307,31 @@ app.controller('ContentController',
};
this.autoPage = function () {
// in case a subsequent autopage request comes in wait until
// the current one finished and execute a request immediately afterwards
if (!this.isAutoPagingEnabled) {
this.autoPageAgain = true;
return;
}
this.isAutoPagingEnabled = false;
this.autoPageAgain = false;
var type = $route.current.$$route.type;
var id = $routeParams.id;
var oldestFirst = SettingsResource.get('oldestFirst');
var self = this;
ItemResource.autoPage(type, id).success(function (data) {
ItemResource.autoPage(type, id, oldestFirst).success(function (data) {
Publisher.publishAll(data);
if (data.items.length > 0) {
self.isAutoPagingEnabled = true;
}
if (self.isAutoPagingEnabled && self.autoPageAgain) {
self.autoPage();
}
}).error(function () {
self.isAutoPagingEnabled = true;
});
@ -1055,12 +1070,18 @@ app.factory('ItemResource', ["Resource", "$http", "BASE_URL", "ITEM_BATCH_SIZE",
var ItemResource = function ($http, BASE_URL, ITEM_BATCH_SIZE) {
Resource.call(this, $http, BASE_URL);
this.starredCount = 0;
this.batchSize = ITEM_BATCH_SIZE;
this.clear();
};
ItemResource.prototype = Object.create(Resource.prototype);
ItemResource.prototype.clear = function () {
this.starredCount = 0;
this.lowestId = 0;
this.highestId = 0;
Resource.prototype.clear.call(this);
};
ItemResource.prototype.receive = function (value, channel) {
switch (channel) {
@ -1074,6 +1095,24 @@ app.factory('ItemResource', ["Resource", "$http", "BASE_URL", "ITEM_BATCH_SIZE",
break;
default:
var self = this;
value.forEach(function (item) {
// initialize lowest and highest id
if (self.lowestId === 0) {
self.lowestId = item.id;
}
if (self.highestId === 0) {
self.highestId = item.id;
}
if (item.id > self.highestId) {
self.highestId = item.id;
}
if (item.id < self.lowestId) {
self.lowestId = item.id;
}
});
Resource.prototype.receive.call(this, value, channel);
}
};
@ -1193,15 +1232,24 @@ app.factory('ItemResource', ["Resource", "$http", "BASE_URL", "ITEM_BATCH_SIZE",
};
ItemResource.prototype.autoPage = function (type, id) {
ItemResource.prototype.autoPage = function (type, id, oldestFirst) {
var offset;
if (oldestFirst) {
offset = this.highestId;
} else {
offset = this.lowestId;
}
return this.http({
url: this.BASE_URL + '/items',
method: 'GET',
params: {
type: type,
id: id,
offset: this.size(),
limit: this.batchSize
offset: offset,
limit: this.batchSize,
oldestFirst: oldestFirst
}
});
};
@ -1760,34 +1808,33 @@ app.directive('newsReadFile', function () {
});
app.directive('newsScroll', ["$timeout", function ($timeout) {
'use strict';
var timer;
// autopaging
var autoPage = function (enabled, limit, elem, scope) {
if (enabled) {
var counter = 0;
var articles = elem.find('.item');
var autoPage = function (limit, elem, scope) {
var counter = 0;
var articles = elem.find('.item');
for (var i = articles.length - 1; i >= 0; i -= 1) {
var item = $(articles[i]);
for (var i = articles.length - 1; i >= 0; i -= 1) {
var item = $(articles[i]);
// if the counter is higher than the size it means
// that it didnt break to auto page yet and that
// there are more items, so break
if (counter >= limit) {
break;
}
// this is only reached when the item is not is
// below the top and we didnt hit the factor yet so
// autopage and break
if (item.position().top < 0) {
scope.$apply(scope.newsScrollAutoPage);
break;
}
counter += 1;
// if the counter is higher than the size it means
// that it didnt break to auto page yet and that
// there are more items, so break
if (counter >= limit) {
break;
}
// this is only reached when the item is not is
// below the top and we didnt hit the factor yet so
// autopage and break
if (item.position().top < 0) {
scope.$apply(scope.newsScrollAutoPage);
break;
}
counter += 1;
}
};
@ -1819,7 +1866,6 @@ app.directive('newsScroll', ["$timeout", function ($timeout) {
'newsScrollAutoPage': '&',
'newsScrollMarkRead': '&',
'newsScrollEnabledMarkRead': '=',
'newsScrollEnabledAutoPage': '=',
'newsScrollMarkReadTimeout': '@', // optional, defaults to 1 second
'newsScrollTimeout': '@', // optional, defaults to 1 second
'newsScrollAutoPageWhenLeft': '@' // optional, defaults to 50
@ -1837,24 +1883,27 @@ app.directive('newsScroll', ["$timeout", function ($timeout) {
var autoPageLimit = scope.newsScrollAutoPageWhenLeft || 50;
var scrollHandler = function () {
// allow only one scroll event to trigger at once
// allow only one scroll event to trigger every 300ms
if (allowScroll) {
allowScroll = false;
$timeout(function () {
allowScroll = true;
}, scrollTimeout*1000);
}, scrollTimeout*100);
autoPage(scope.newsScrollEnabledAutoPage,
autoPageLimit,
elem,
scope);
autoPage(autoPageLimit, elem, scope);
// dont stack mark read requests
if (timer) {
$timeout.cancel(timer);
}
// allow user to undo accidental scroll
$timeout(function () {
timer = $timeout(function () {
markRead(scope.newsScrollEnabledMarkRead,
elem,
scope);
timer = undefined;
}, markReadTimeout*1000);
}
};

2
js/build/app.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -91,8 +91,10 @@ function (Publisher, FeedResource, ItemResource, SettingsResource, data,
}
});
FeedResource.markItemsOfFeedsRead(feedIds);
ItemResource.markItemsRead(ids);
if (ids.length > 0) {
FeedResource.markItemsOfFeedsRead(feedIds);
ItemResource.markItemsRead(ids);
}
};
this.isFeed = function () {
@ -100,18 +102,31 @@ function (Publisher, FeedResource, ItemResource, SettingsResource, data,
};
this.autoPage = function () {
// in case a subsequent autopage request comes in wait until
// the current one finished and execute a request immediately afterwards
if (!this.isAutoPagingEnabled) {
this.autoPageAgain = true;
return;
}
this.isAutoPagingEnabled = false;
this.autoPageAgain = false;
var type = $route.current.$$route.type;
var id = $routeParams.id;
var oldestFirst = SettingsResource.get('oldestFirst');
var self = this;
ItemResource.autoPage(type, id).success(function (data) {
ItemResource.autoPage(type, id, oldestFirst).success(function (data) {
Publisher.publishAll(data);
if (data.items.length > 0) {
self.isAutoPagingEnabled = true;
}
if (self.isAutoPagingEnabled && self.autoPageAgain) {
self.autoPage();
}
}).error(function () {
self.isAutoPagingEnabled = true;
});

View File

@ -9,34 +9,33 @@
*/
app.directive('newsScroll', function ($timeout) {
'use strict';
var timer;
// autopaging
var autoPage = function (enabled, limit, elem, scope) {
if (enabled) {
var counter = 0;
var articles = elem.find('.item');
var autoPage = function (limit, elem, scope) {
var counter = 0;
var articles = elem.find('.item');
for (var i = articles.length - 1; i >= 0; i -= 1) {
var item = $(articles[i]);
for (var i = articles.length - 1; i >= 0; i -= 1) {
var item = $(articles[i]);
// if the counter is higher than the size it means
// that it didnt break to auto page yet and that
// there are more items, so break
if (counter >= limit) {
break;
}
// this is only reached when the item is not is
// below the top and we didnt hit the factor yet so
// autopage and break
if (item.position().top < 0) {
scope.$apply(scope.newsScrollAutoPage);
break;
}
counter += 1;
// if the counter is higher than the size it means
// that it didnt break to auto page yet and that
// there are more items, so break
if (counter >= limit) {
break;
}
// this is only reached when the item is not is
// below the top and we didnt hit the factor yet so
// autopage and break
if (item.position().top < 0) {
scope.$apply(scope.newsScrollAutoPage);
break;
}
counter += 1;
}
};
@ -68,7 +67,6 @@ app.directive('newsScroll', function ($timeout) {
'newsScrollAutoPage': '&',
'newsScrollMarkRead': '&',
'newsScrollEnabledMarkRead': '=',
'newsScrollEnabledAutoPage': '=',
'newsScrollMarkReadTimeout': '@', // optional, defaults to 1 second
'newsScrollTimeout': '@', // optional, defaults to 1 second
'newsScrollAutoPageWhenLeft': '@' // optional, defaults to 50
@ -86,24 +84,27 @@ app.directive('newsScroll', function ($timeout) {
var autoPageLimit = scope.newsScrollAutoPageWhenLeft || 50;
var scrollHandler = function () {
// allow only one scroll event to trigger at once
// allow only one scroll event to trigger every 300ms
if (allowScroll) {
allowScroll = false;
$timeout(function () {
allowScroll = true;
}, scrollTimeout*1000);
}, scrollTimeout*100);
autoPage(scope.newsScrollEnabledAutoPage,
autoPageLimit,
elem,
scope);
autoPage(autoPageLimit, elem, scope);
// dont stack mark read requests
if (timer) {
$timeout.cancel(timer);
}
// allow user to undo accidental scroll
$timeout(function () {
timer = $timeout(function () {
markRead(scope.newsScrollEnabledMarkRead,
elem,
scope);
timer = undefined;
}, markReadTimeout*1000);
}
};

View File

@ -13,12 +13,18 @@ app.factory('ItemResource', function (Resource, $http, BASE_URL,
var ItemResource = function ($http, BASE_URL, ITEM_BATCH_SIZE) {
Resource.call(this, $http, BASE_URL);
this.starredCount = 0;
this.batchSize = ITEM_BATCH_SIZE;
this.clear();
};
ItemResource.prototype = Object.create(Resource.prototype);
ItemResource.prototype.clear = function () {
this.starredCount = 0;
this.lowestId = 0;
this.highestId = 0;
Resource.prototype.clear.call(this);
};
ItemResource.prototype.receive = function (value, channel) {
switch (channel) {
@ -32,6 +38,24 @@ app.factory('ItemResource', function (Resource, $http, BASE_URL,
break;
default:
var self = this;
value.forEach(function (item) {
// initialize lowest and highest id
if (self.lowestId === 0) {
self.lowestId = item.id;
}
if (self.highestId === 0) {
self.highestId = item.id;
}
if (item.id > self.highestId) {
self.highestId = item.id;
}
if (item.id < self.lowestId) {
self.lowestId = item.id;
}
});
Resource.prototype.receive.call(this, value, channel);
}
};
@ -151,15 +175,24 @@ app.factory('ItemResource', function (Resource, $http, BASE_URL,
};
ItemResource.prototype.autoPage = function (type, id) {
ItemResource.prototype.autoPage = function (type, id, oldestFirst) {
var offset;
if (oldestFirst) {
offset = this.highestId;
} else {
offset = this.lowestId;
}
return this.http({
url: this.BASE_URL + '/items',
method: 'GET',
params: {
type: type,
id: id,
offset: this.size(),
limit: this.batchSize
offset: offset,
limit: this.batchSize,
oldestFirst: oldestFirst
}
});
};

View File

@ -270,7 +270,8 @@ describe('ContentController', function () {
it('should not autopage if less than 0 elements', inject(function (
$controller, ItemResource, Publisher) {
$controller, ItemResource, Publisher, SettingsResource) {
SettingsResource.set('oldestFirst', true);
var $route = {
current: {
@ -305,6 +306,7 @@ describe('ContentController', function () {
$route: $route,
Publisher: Publisher,
ItemResource: ItemResource,
SettingsResource: SettingsResource,
data: {},
});
@ -314,7 +316,7 @@ describe('ContentController', function () {
expect(ctrl.autoPagingEnabled()).toBe(false);
expect(ItemResource.autoPage).toHaveBeenCalledWith(3, 2);
expect(ItemResource.autoPage).toHaveBeenCalledWith(3, 2, true);
}));

View File

@ -209,8 +209,9 @@ describe('ItemResource', function () {
}));
it ('should auto page', inject(function (ItemResource) {
http.expectGET('base/items?id=4&limit=5&offset=3&type=3')
it ('should auto page newest first', inject(function (ItemResource) {
http.expectGET(
'base/items?id=4&limit=5&offset=3&oldestFirst=false&type=3')
.respond(200, {});
ItemResource.receive([
@ -220,21 +221,81 @@ describe('ItemResource', function () {
unread: true
},
{
id: 4,
id: 5,
feedId: 3,
unread: true
},
{
id: 5,
id: 4,
feedId: 4,
unread: true
}
], 'items');
ItemResource.autoPage(3, 4);
ItemResource.autoPage(3, 4, false);
http.flush();
}));
it ('should auto page oldest first', inject(function (ItemResource) {
http.expectGET(
'base/items?id=4&limit=5&offset=5&oldestFirst=true&type=3')
.respond(200, {});
ItemResource.receive([
{
id: 3,
feedId: 4,
unread: true
},
{
id: 5,
feedId: 3,
unread: true
},
{
id: 4,
feedId: 4,
unread: true
}
], 'items');
ItemResource.autoPage(3, 4, true);
http.flush();
}));
it ('should clear all state', inject(function (ItemResource) {
ItemResource.receive([
{
id: 3,
feedId: 4,
unread: true
},
{
id: 5,
feedId: 3,
unread: true
},
{
id: 4,
feedId: 4,
unread: true
}
], 'items');
ItemResource.receive(5, 'newestItemId');
ItemResource.receive(4, 'starred');
ItemResource.clear();
expect(ItemResource.size()).toBe(0);
expect(ItemResource.highestId).toBe(0);
expect(ItemResource.lowestId).toBe(0);
expect(ItemResource.starredCount).toBe(0);
}));
});

View File

@ -55,7 +55,6 @@ style('news', [
ng-hide="App.loading.isLoading('global')"
ng-view
news-scroll="#app-content"
news-scroll-enabled-auto-page="Content.autoPagingEnabled()"
news-scroll-enabled-mark-read="Content.markReadEnabled()"
news-scroll-auto-page="Content.autoPage()"
news-scroll-mark-read="Content.scrollRead(itemIds)"></div>

View File

@ -64,7 +64,13 @@ class ItemMapperTest extends \Test\AppFramework\Db\MapperTestUtility {
}
private function makeSelectQuery($prependTo){
private function makeSelectQuery($prependTo, $oldestFirst=false){
if ($oldestFirst) {
$ordering = 'ASC';
} else {
$ordering = 'DESC';
}
return 'SELECT `items`.* FROM `*PREFIX*news_items` `items` '.
'JOIN `*PREFIX*news_feeds` `feeds` ' .
'ON `feeds`.`id` = `items`.`feed_id` '.
@ -75,15 +81,16 @@ class ItemMapperTest extends \Test\AppFramework\Db\MapperTestUtility {
'ON `folders`.`id` = `feeds`.`folder_id` ' .
'WHERE `feeds`.`folder_id` = 0 ' .
'OR `folders`.`deleted_at` = 0 ' .
'ORDER BY `items`.`id` DESC';
'ORDER BY `items`.`id` ' . $ordering;
}
private function makeSelectQueryStatus($prependTo, $status) {
private function makeSelectQueryStatus($prependTo, $status,
$oldestFirst=false) {
$status = (int) $status;
return $this->makeSelectQuery(
'AND ((`items`.`status` & ' . $status . ') = ' . $status . ') ' .
$prependTo
$prependTo, $oldestFirst
);
}
@ -239,7 +246,7 @@ class ItemMapperTest extends \Test\AppFramework\Db\MapperTestUtility {
public function testFindAllFeedOldestFirst(){
$sql = 'AND `items`.`feed_id` = ? ' .
'AND `items`.`id` > ? ';
$sql = $this->makeSelectQueryStatus($sql, $this->status);
$sql = $this->makeSelectQueryStatus($sql, $this->status, true);
$params = array($this->user, $this->id, $this->offset);
$this->setMapperResult($sql, $params, $this->rows);
$result = $this->mapper->findAllFeed($this->id, $this->limit,
@ -278,7 +285,7 @@ class ItemMapperTest extends \Test\AppFramework\Db\MapperTestUtility {
public function testFindAllFolderOldestFirst(){
$sql = 'AND `feeds`.`folder_id` = ? ' .
'AND `items`.`id` > ? ';
$sql = $this->makeSelectQueryStatus($sql, $this->status);
$sql = $this->makeSelectQueryStatus($sql, $this->status, true);
$params = array($this->user, $this->id,
$this->offset);
$this->setMapperResult($sql, $params, $this->rows);
@ -315,7 +322,7 @@ class ItemMapperTest extends \Test\AppFramework\Db\MapperTestUtility {
public function testFindAllOldestFirst(){
$sql = 'AND `items`.`id` > ? ';
$sql = $this->makeSelectQueryStatus($sql, $this->status);
$sql = $this->makeSelectQueryStatus($sql, $this->status, true);
$params = array($this->user, $this->offset);
$this->setMapperResult($sql, $params, $this->rows);
$result = $this->mapper->findAll($this->limit,