summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--js/.jshintrc2
-rw-r--r--js/README.md9
-rw-r--r--js/build/app.js217
-rw-r--r--js/controller/ContentController.js2
-rw-r--r--js/directive/NewsScroll.js110
-rw-r--r--js/service/ItemResource.js33
-rw-r--r--js/tests/unit/controller/ContentControllerSpec.js13
-rw-r--r--js/tests/unit/service/ItemResourceSpec.js68
-rw-r--r--js/utility/Iterators.js19
9 files changed, 458 insertions, 15 deletions
diff --git a/js/.jshintrc b/js/.jshintrc
index cd85aa807..d4d7e6279 100644
--- a/js/.jshintrc
+++ b/js/.jshintrc
@@ -18,7 +18,7 @@
"unused": true,
"strict": true,
"maxparams": false,
- "maxdepth": 3,
+ "maxdepth": 4,
"maxlen": 80,
"browser": true,
"devel": true,
diff --git a/js/README.md b/js/README.md
index b529fbf6a..e0fb435eb 100644
--- a/js/README.md
+++ b/js/README.md
@@ -29,6 +29,15 @@ The following iterators are defined and availabe:
}
```
+* **reverse**:
+
+ ```js
+ // iterate over list in reverse order
+ for (let value of reverse(list)) {
+ console.log(`value is: ${value}`)
+ }
+ ```
+
## Building
Watch mode:
diff --git a/js/build/app.js b/js/build/app.js
index 38ed97fda..dc62566aa 100644
--- a/js/build/app.js
+++ b/js/build/app.js
@@ -182,6 +182,7 @@ var $__build_47_app__ = function () {
'data',
function (Publisher, FeedResource, ItemResource, data) {
'use strict';
+ ItemResource.clear();
Publisher.publishAll(data);
this.getItems = function () {
return ItemResource.getAll();
@@ -247,9 +248,21 @@ var $__build_47_app__ = function () {
BASE_URL
]);
this.starredCount = 0;
+ this.highestId = 0;
+ this.lowestId = 0;
};
var $ItemResource = ItemResource;
$traceurRuntime.createClass(ItemResource, {
+ add: function (obj) {
+ var id = obj[$traceurRuntime.toProperty(this.id)];
+ if (this.highestId < id) {
+ this.highestId = id;
+ }
+ if (this.lowestId === 0 || this.lowestId > id) {
+ this.lowestId = id;
+ }
+ $traceurRuntime.superCall(this, $ItemResource.prototype, 'add', [obj]);
+ },
receive: function (value, channel) {
switch (channel) {
case 'newestItemId':
@@ -325,9 +338,20 @@ var $__build_47_app__ = function () {
}
return this.http.post(this.BASE_URL + '/items/read');
},
+ getHighestId: function () {
+ return this.highestId;
+ },
+ getLowestId: function () {
+ return this.lowestId;
+ },
keepUnread: function (itemId) {
this.get(itemId).keepUnread = true;
return this.read(itemId, false);
+ },
+ clear: function () {
+ this.highestId = 0;
+ this.lowestId = 0;
+ $traceurRuntime.superCall(this, $ItemResource.prototype, 'clear', []);
}
}, {}, Resource);
return new ItemResource($http, BASE_URL);
@@ -830,6 +854,199 @@ var $__build_47_app__ = function () {
writable: true
}), $__2;
};
+ window.reverse = function (list) {
+ 'use strict';
+ var $__2;
+ return $__2 = {}, Object.defineProperty($__2, Symbol.iterator, {
+ value: function () {
+ return $traceurRuntime.initGeneratorFunction(function $__9() {
+ var counter, $counter;
+ return $traceurRuntime.createGeneratorInstance(function ($ctx) {
+ while (true)
+ switch ($ctx.state) {
+ case 0:
+ $ctx.pushTry(28, null);
+ $ctx.state = 31;
+ break;
+ case 31:
+ throw undefined;
+ $ctx.state = 33;
+ break;
+ case 33:
+ $ctx.popTry();
+ $ctx.state = -2;
+ break;
+ case 28:
+ $ctx.popTry();
+ $counter = $ctx.storedException;
+ $ctx.state = 26;
+ break;
+ case 26:
+ $counter = list.length;
+ $ctx.state = 27;
+ break;
+ case 27:
+ $ctx.state = $counter >= 0 ? 17 : -2;
+ break;
+ case 22:
+ $counter -= 1;
+ $ctx.state = 27;
+ break;
+ case 17:
+ $ctx.pushTry(15, null);
+ $ctx.state = 18;
+ break;
+ case 18:
+ throw undefined;
+ $ctx.state = 20;
+ break;
+ case 20:
+ $ctx.popTry();
+ $ctx.state = 22;
+ break;
+ case 15:
+ $ctx.popTry();
+ counter = $ctx.storedException;
+ $ctx.state = 13;
+ break;
+ case 13:
+ counter = $counter;
+ $ctx.state = 14;
+ break;
+ case 14:
+ $ctx.pushTry(null, 6);
+ $ctx.state = 8;
+ break;
+ case 8:
+ $ctx.state = 2;
+ return list[$traceurRuntime.toProperty(counter)];
+ case 2:
+ $ctx.maybeThrow();
+ $ctx.state = 22;
+ break;
+ case 6:
+ $ctx.popTry();
+ $ctx.state = 12;
+ break;
+ case 12:
+ $counter = counter;
+ $ctx.state = 10;
+ break;
+ default:
+ return $ctx.end();
+ }
+ }, $__9, this);
+ })();
+ },
+ configurable: true,
+ enumerable: true,
+ writable: true
+ }), $__2;
+ };
+ app.directive('newsScroll', [
+ '$timeout',
+ function ($timeout) {
+ 'use strict';
+ return {
+ restrict: 'A',
+ scope: {
+ 'newsScrollAutoPage': '&',
+ 'newsScrollMarkRead': '&',
+ 'newsScrollDisabledMarkRead': '=',
+ 'newsScrollDisabledAutoPage': '=',
+ 'newsScrollMarkReadTimeout': '@',
+ 'newsScrollTimeout': '@',
+ 'newsScrollAutoPageWhenLeft': '@',
+ 'newsScrollItemsSelector': '@'
+ },
+ link: function (scope, elem) {
+ var scrolling = false;
+ scope.newsScrollTimeout = scope.newsScrollTimeout || 1;
+ scope.newsScrollMarkReadTimeout = scope.newsScrollMarkReadTimeout || 1;
+ scope.newsScrollAutoPageWhenLeft = scope.newsScrollAutoPageWhenLeft || 50;
+ scope.newsScrollItemsSelector = scope.newsScrollItemsSelector || '.items';
+ elem.on('scroll', function () {
+ if (!scrolling) {
+ try {
+ throw undefined;
+ } catch (markRead) {
+ try {
+ throw undefined;
+ } catch (items) {
+ scrolling = true;
+ $timeout(function () {
+ scrolling = false;
+ }, scope.newsScrollTimeout * 1000);
+ items = $(scope.newsScrollItemsSelector);
+ if (!scope.newsScrollDisabledAutoPage) {
+ try {
+ throw undefined;
+ } catch (counter) {
+ counter = 0;
+ for (var $__3 = reverse(items.find('.feed_item'))[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) {
+ try {
+ throw undefined;
+ } catch (item) {
+ item = $__4.value;
+ {
+ item = $(item);
+ if (counter >= scope.newsScrollAutoPageWhenLeft) {
+ break;
+ }
+ if (item.position().top < 0) {
+ scope.newsScrollAutoPage();
+ break;
+ }
+ counter += 1;
+ }
+ }
+ }
+ }
+ }
+ markRead = function () {
+ if (!scope.newsScrollDisabledMarkRead) {
+ try {
+ throw undefined;
+ } catch (unread) {
+ try {
+ throw undefined;
+ } catch (ids) {
+ ids = [];
+ unread = items.find('.feed_item:not(.read)');
+ for (var $__5 = unread[$traceurRuntime.toProperty(Symbol.iterator)](), $__6; !($__6 = $__5.next()).done;) {
+ try {
+ throw undefined;
+ } catch (item) {
+ item = $__6.value;
+ {
+ item = $(item);
+ if (item.position().top <= -50) {
+ ids.push(parseInt($(item).data('id'), 10));
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ scope.newsScrollMarkRead(ids);
+ }
+ }
+ }
+ };
+ $timeout(function () {
+ markRead();
+ }, scope.newsScrollMarkReadTimeout * 1000);
+ }
+ }
+ }
+ });
+ scope.$on('$destroy', function () {
+ element.off('scroll');
+ });
+ }
+ };
+ }
+ ]);
}(window, document, angular, jQuery, OC, oc_requesttoken));
return {};
}(); \ No newline at end of file
diff --git a/js/controller/ContentController.js b/js/controller/ContentController.js
index ae5116dbb..796fea71a 100644
--- a/js/controller/ContentController.js
+++ b/js/controller/ContentController.js
@@ -11,6 +11,8 @@ app.controller('ContentController',
function (Publisher, FeedResource, ItemResource, data) {
'use strict';
+ ItemResource.clear();
+
// distribute data to models based on key
Publisher.publishAll(data);
diff --git a/js/directive/NewsScroll.js b/js/directive/NewsScroll.js
new file mode 100644
index 000000000..5972cce67
--- /dev/null
+++ b/js/directive/NewsScroll.js
@@ -0,0 +1,110 @@
+/**
+ * ownCloud - News
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @copyright Bernhard Posselt 2014
+ */
+app.directive('newsScroll', ($timeout) => {
+ 'use strict';
+
+ return {
+ restrict: 'A',
+ scope: {
+ 'newsScrollAutoPage': '&',
+ 'newsScrollMarkRead': '&',
+ 'newsScrollDisabledMarkRead': '=',
+ 'newsScrollDisabledAutoPage': '=',
+ 'newsScrollMarkReadTimeout': '@', // optional, defaults to 1 second
+ 'newsScrollTimeout': '@', // optional, defaults to 1 second
+ 'newsScrollAutoPageWhenLeft': '@', // optional, defaults to 50
+ 'newsScrollItemsSelector': '@' // optional, defaults to .items
+ },
+ link: (scope, elem) => {
+ let scrolling = false;
+
+ scope.newsScrollTimeout = scope.newsScrollTimeout || 1;
+ scope.newsScrollMarkReadTimeout =
+ scope.newsScrollMarkReadTimeout || 1;
+ scope.newsScrollAutoPageWhenLeft =
+ scope.newsScrollAutoPageWhenLeft || 50;
+ scope.newsScrollItemsSelector =
+ scope.newsScrollItemsSelector || '.items';
+
+
+ elem.on('scroll', () => {
+
+ // only allow one scrolling event to trigger
+ if (!scrolling) {
+ scrolling = true;
+
+ $timeout(() => {
+ scrolling = false;
+ }, scope.newsScrollTimeout*1000);
+
+
+ let items = $(scope.newsScrollItemsSelector);
+
+ // autopaging
+ if (!scope.newsScrollDisabledAutoPage) {
+ let counter = 0;
+ for (let item of reverse(items.find('.feed_item'))) {
+ item = $(item);
+
+
+ // 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 >= scope.newsScrollAutoPageWhenLeft) {
+ 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.newsScrollAutoPage();
+ break;
+ }
+
+ counter += 1;
+ }
+ }
+
+ // mark read
+ let markRead = () => {
+ if (!scope.newsScrollDisabledMarkRead) {
+ let ids = [];
+ let unread = items.find('.feed_item:not(.read)');
+
+ for (let item of unread) {
+ item = $(item);
+
+ if (item.position().top <= -50) {
+ ids.push(parseInt($(item).data('id'), 10));
+ } else {
+ break;
+ }
+ }
+
+ scope.newsScrollMarkRead(ids);
+ }
+ };
+
+ // allow user to undo accidental scroll
+ $timeout(() => {
+ markRead();
+ }, scope.newsScrollMarkReadTimeout*1000);
+ }
+
+ });
+
+ // remove scroll handler if element is destroyed
+ scope.$on('$destroy', () => {
+ element.off('scroll');
+ });
+ }
+ };
+}); \ No newline at end of file
diff --git a/js/service/ItemResource.js b/js/service/ItemResource.js
index dc74f637a..67a6d23e6 100644
--- a/js/service/ItemResource.js
+++ b/js/service/ItemResource.js
@@ -16,6 +16,23 @@ app.factory('ItemResource', (Resource, $http, BASE_URL) => {
constructor ($http, BASE_URL) {
super($http, BASE_URL);
this.starredCount = 0;
+ this.highestId = 0;
+ this.lowestId = 0;
+ }
+
+
+ add (obj) {
+ let id = obj[this.id];
+
+ if (this.highestId < id) {
+ this.highestId = id;
+ }
+
+ if (this.lowestId === 0 || this.lowestId > id) {
+ this.lowestId = id;
+ }
+
+ super.add(obj);
}
@@ -95,12 +112,28 @@ app.factory('ItemResource', (Resource, $http, BASE_URL) => {
}
+ getHighestId () {
+ return this.highestId;
+ }
+
+
+ getLowestId () {
+ return this.lowestId;
+ }
+
+
keepUnread (itemId) {
this.get(itemId).keepUnread = true;
return this.read(itemId, false);
}
+ clear () {
+ this.highestId = 0;
+ this.lowestId = 0;
+ super.clear();
+ }
+
}
return new ItemResource($http, BASE_URL);
diff --git a/js/tests/unit/controller/ContentControllerSpec.js b/js/tests/unit/controller/ContentControllerSpec.js
index 422bd21f4..20dd3c71f 100644
--- a/js/tests/unit/controller/ContentControllerSpec.js
+++ b/js/tests/unit/controller/ContentControllerSpec.js
@@ -43,4 +43,17 @@ describe('ContentController', () => {
expect(controller.getFeeds().length).toBe(1);
}));
+
+ it('should clear data on url change', inject(($controller,
+ ItemResource) => {
+
+ ItemResource.clear = jasmine.createSpy('clear');
+
+ $controller('ContentController', {
+ data: {}
+ });
+
+ expect(ItemResource.clear).toHaveBeenCalled();
+ }));
+
});
diff --git a/js/tests/unit/service/ItemResourceSpec.js b/js/tests/unit/service/ItemResourceSpec.js
index 0316d39cc..bfdaf4b4a 100644
--- a/js/tests/unit/service/ItemResourceSpec.js
+++ b/js/tests/unit/service/ItemResourceSpec.js
@@ -35,20 +35,6 @@ describe('ItemResource', () => {
}));
- it('should receive items', inject((ItemResource) => {
- ItemResource.receive([
- {
- id: 3
- },
- {
- id: 4
- }
- ], 'items');
-
- expect(ItemResource.size()).toBe(2);
- }));
-
-
it ('should keep item unread', inject((ItemResource) => {
http.expectPOST('base/items/3/read', {isRead: false}).respond(200, {});
@@ -186,6 +172,60 @@ describe('ItemResource', () => {
}));
+ it ('should remember the highest id', inject((ItemResource) => {
+ ItemResource.receive([
+ {
+ id: 3,
+ },
+ {
+ id: 5,
+ },
+ {
+ id: 4,
+ }
+ ], 'items');
+
+ expect(ItemResource.getHighestId()).toBe(5);
+ }));
+
+
+ it ('should remember the lowest id', inject((ItemResource) => {
+ ItemResource.receive([
+ {
+ id: 3,
+ },
+ {
+ id: 5,
+ },
+ {
+ id: 4,
+ }
+ ], 'items');
+
+ expect(ItemResource.getLowestId()).toBe(3);
+ }));
+
+
+ it ('should clear the highest and lowest id', inject((ItemResource) => {
+ ItemResource.receive([
+ {
+ id: 3,
+ },
+ {
+ id: 5,
+ },
+ {
+ id: 4,
+ }
+ ], 'items');
+
+ ItemResource.clear();
+
+ expect(ItemResource.getHighestId()).toBe(0);
+ expect(ItemResource.getLowestId()).toBe(0);
+ }));
+
+
afterEach(() => {
http.verifyNoOutstandingExpectation();
http.verifyNoOutstandingRequest();
diff --git a/js/utility/Iterators.js b/js/utility/Iterators.js
index 3c5e34b18..1f96aab71 100644
--- a/js/utility/Iterators.js
+++ b/js/utility/Iterators.js
@@ -48,4 +48,23 @@ window.enumerate = function (list) {
})();
}
};
+};
+
+
+/**
+ * Iterates over a list in reverse
+ * like: for (let value of reverse(list))
+ */
+window.reverse = function (list) {
+ 'use strict';
+
+ return {
+ [Symbol.iterator]: function () {
+ return (function*() {
+ for (let counter = list.length; counter >= 0 ; counter -= 1) {
+ yield list[counter];
+ }
+ })();
+ }
+ };
}; \ No newline at end of file