diff options
-rw-r--r-- | js/.jshintrc | 12 | ||||
-rw-r--r-- | js/build/app.js | 205 | ||||
-rw-r--r-- | js/controller/SettingsController.js | 41 | ||||
-rw-r--r-- | js/directive/NewsForwardClick.js | 19 | ||||
-rw-r--r-- | js/directive/NewsReadFile.js | 28 | ||||
-rw-r--r-- | js/directive/NewsScroll.js | 2 | ||||
-rw-r--r-- | js/karma.conf.js | 2 | ||||
-rw-r--r-- | js/service/Publisher.js | 2 | ||||
-rw-r--r-- | js/service/Settings.js | 13 | ||||
-rw-r--r-- | js/tests/unit/controller/SettingsControllerSpec.js | 67 | ||||
-rw-r--r-- | js/tests/unit/service/SettingsSpec.js | 22 | ||||
-rw-r--r-- | templates/part.settings.php | 182 |
12 files changed, 429 insertions, 166 deletions
diff --git a/js/.jshintrc b/js/.jshintrc index cd85aa807..1138c1c93 100644 --- a/js/.jshintrc +++ b/js/.jshintrc @@ -1,6 +1,6 @@ { "esnext": true, - "bitwise": false, + "bitwise": true, "camelcase": true, "curly": true, "eqeqeq": true, @@ -14,7 +14,7 @@ "nonew": true, "plusplus": true, "quotmark": "single", - "undef": false, + "undef": true, "unused": true, "strict": true, "maxparams": false, @@ -33,11 +33,15 @@ "protractor": true, "browser": true, "By": true, - "jasmine": true, "it": true, + "afterEach": true, + "jasmine": true, "describe": true, "beforeEach": true, "expect": true, - "exports": true + "exports": true, + "reverse": true, + "items": true, + "enumerate": true } } diff --git a/js/build/app.js b/js/build/app.js index 95e376798..743d10fba 100644 --- a/js/build/app.js +++ b/js/build/app.js @@ -196,10 +196,42 @@ var $__build_47_app__ = function () { 'use strict'; console.log('here'); }); - app.controller('SettingsController', function () { - 'use strict'; - console.log('here'); - }); + app.controller('SettingsController', [ + 'Settings', + '$route', + 'FeedResource', + function (Settings, $route, FeedResource) { + 'use strict'; + var $__0 = this; + this.importing = false; + this.opmlImportError = false; + this.articleImportError = false; + var set = function (key, value) { + Settings.set(key, value); + if ([ + 'showAll', + 'oldestFirst' + ].indexOf(key) >= 0) { + $route.reload(); + } + }; + this.toggleSetting = function (key) { + set(key, !$__0.getSetting(key)); + }; + this.getSetting = function (key) { + return Settings.get(key); + }; + this.importOpml = function (content) { + console.log(content); + }; + this.importArticles = function (content) { + console.log(content); + }; + this.feedSize = function () { + return FeedResource.size(); + }; + } + ]); app.factory('FeedResource', [ 'Resource', '$http', @@ -514,41 +546,52 @@ var $__build_47_app__ = function () { }, {}); return Resource; }); - app.service('Settings', function () { - 'use strict'; - var $__0 = this; - this.settings = {}; - this.receive = function (data) { - for (var $__3 = items(data)[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) { - try { - throw undefined; - } catch (value) { + app.service('Settings', [ + '$http', + 'BASE_URL', + function ($http, BASE_URL) { + 'use strict'; + var $__0 = this; + this.settings = {}; + this.receive = function (data) { + for (var $__3 = items(data)[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) { try { throw undefined; - } catch (key) { + } catch (value) { try { throw undefined; - } catch ($__8) { - { - $__8 = $traceurRuntime.assertObject($__4.value); - key = $__8[0]; - value = $__8[1]; - } - { - $traceurRuntime.setProperty($__0.settings, key, value); + } catch (key) { + try { + throw undefined; + } catch ($__8) { + { + $__8 = $traceurRuntime.assertObject($__4.value); + key = $__8[0]; + value = $__8[1]; + } + { + $traceurRuntime.setProperty($__0.settings, key, value); + } } } } } - } - }; - this.get = function (key) { - return $__0.settings[$traceurRuntime.toProperty(key)]; - }; - this.set = function (key, value) { - $traceurRuntime.setProperty($__0.settings, key, value); - }; - }); + }; + this.get = function (key) { + return $__0.settings[$traceurRuntime.toProperty(key)]; + }; + this.set = function (key, value) { + $traceurRuntime.setProperty($__0.settings, key, value); + var data = {}; + $traceurRuntime.setProperty(data, key, value); + return $http({ + url: BASE_URL + '/settings', + method: 'POST', + data: data + }); + }; + } + ]); (function (window, document, $) { 'use strict'; var scrollArea = $('#app-content'); @@ -943,17 +986,38 @@ var $__build_47_app__ = function () { writable: true }), $__2; }; + app.directive('newsTriggerClick', function () { + 'use strict'; + return function (scope, elm, attr) { + elm.click(function () { + $(attr.newsTriggerClick).trigger('click'); + }); + }; + }); + app.directive('newsReadFile', function () { + 'use strict'; + return function (scope, elm, attr) { + var file = elm[0].files[0]; + var reader = new FileReader(); + reader.onload = function (event) { + elm[0].value = 0; + scope.$fileContent = event.target.result; + scope.$apply(attr.newsReadFile); + }; + reader.reasAsText(file); + }; + }); app.directive('newsScroll', [ '$timeout', function ($timeout) { 'use strict'; - var autoPage = function (enabled, limit, items, callback) { + var autoPage = function (enabled, limit, callback, elem) { if (enabled) { try { throw undefined; } catch (counter) { counter = 0; - for (var $__3 = reverse(items.find('.feed_item'))[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) { + for (var $__3 = reverse(elem.find('.feed_item'))[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) { try { throw undefined; } catch (item) { @@ -974,33 +1038,28 @@ var $__build_47_app__ = function () { } } }; - var markRead = function (enabled, items, callback) { + var markRead = function (enabled, callback, elem) { if (enabled) { try { throw undefined; - } catch (unreadItems) { - try { - throw undefined; - } catch (ids) { - ids = []; - unreadItems = items.find('.feed_item:not(.read)'); - for (var $__3 = unreadItems[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) { - try { - throw undefined; - } catch (item) { - item = $__4.value; - { - item = $(item); - if (item.position().top <= -50) { - ids.push(parseInt($(item).data('id'), 10)); - } else { - break; - } + } catch (ids) { + ids = []; + for (var $__3 = elem.find('.feed_item:not(.read)')[$traceurRuntime.toProperty(Symbol.iterator)](), $__4; !($__4 = $__3.next()).done;) { + try { + throw undefined; + } catch (item) { + item = $__4.value; + { + item = $(item); + if (item.position().top <= -50) { + ids.push(parseInt(item.data('id'), 10)); + } else { + break; } } } - callback(ids); } + callback(ids); } } }; @@ -1009,38 +1068,32 @@ var $__build_47_app__ = function () { scope: { 'newsScrollAutoPage': '&', 'newsScrollMarkRead': '&', - 'newsScrollDisabledMarkRead': '=', - 'newsScrollDisabledAutoPage': '=', + 'newsScrollEnabledMarkRead': '=', + 'newsScrollEnableAutoPage': '=', 'newsScrollMarkReadTimeout': '@', 'newsScrollTimeout': '@', - 'newsScrollAutoPageWhenLeft': '@', - 'newsScrollItemsSelector': '@' + 'newsScrollAutoPageWhenLeft': '@' }, link: function (scope, elem) { - var scrolling = false; + var allowScroll = true; 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 (items) { - scrolling = true; - $timeout(function () { - scrolling = false; - }, scope.newsScrollTimeout * 1000); - items = $(scope.newsScrollItemsSelector); - autoPage(!scope.newsScrollDisabledAutoPage, scope.newsScrollAutoPageWhenLeft, items, scope.newsScrollAutoPage); - $timeout(function () { - markRead(!scope.newsScrollDisabledMarkRead, items, scope.newsScrollMarkRead); - }, scope.newsScrollMarkReadTimeout * 1000); - } + var scrollHandler = function () { + if (allowScroll) { + allowScroll = false; + $timeout(function () { + allowScroll = true; + }, scope.newsScrollTimeout * 1000); + autoPage(scope.newsScrollEnableAutoPage, scope.newsScrollAutoPageWhenLeft, scope.newsScrollAutoPage, elem); + $timeout(function () { + markRead(scope.newsScrollEnabledMarkRead, scope.newsScrollMarkRead, elem); + }, scope.newsScrollMarkReadTimeout * 1000); } - }); + }; + elem.on('scroll', scrollHandler); scope.$on('$destroy', function () { - elem.off('scroll'); + elem.off('scroll', scrollHandler); }); } }; diff --git a/js/controller/SettingsController.js b/js/controller/SettingsController.js index 321b6dff9..ae0e1ee52 100644 --- a/js/controller/SettingsController.js +++ b/js/controller/SettingsController.js @@ -7,8 +7,45 @@ * @author Bernhard Posselt <dev@bernhard-posselt.com> * @copyright Bernhard Posselt 2014 */ -app.controller('SettingsController', function () { +app.controller('SettingsController', function (Settings, $route, FeedResource) { 'use strict'; - console.log('here'); + this.importing = false; + this.opmlImportError = false; + this.articleImportError = false; + + let set = (key, value) => { + Settings.set(key, value); + + if (['showAll', 'oldestFirst'].indexOf(key) >= 0) { + $route.reload(); + } + }; + + + this.toggleSetting = (key) => { + set(key, !this.getSetting(key)); + }; + + + this.getSetting = (key) => { + return Settings.get(key); + }; + + + this.importOpml = (content) => { + console.log(content); + }; + + + this.importArticles = (content) => { + console.log(content); + }; + + + this.feedSize = () => { + return FeedResource.size(); + }; + + });
\ No newline at end of file diff --git a/js/directive/NewsForwardClick.js b/js/directive/NewsForwardClick.js new file mode 100644 index 000000000..d5c43e442 --- /dev/null +++ b/js/directive/NewsForwardClick.js @@ -0,0 +1,19 @@ +/** + * 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('newsTriggerClick', () => { + 'use strict'; + + return (scope, elm, attr) => { + elm.click(() => { + $(attr.newsTriggerClick).trigger('click'); + }); + }; + +});
\ No newline at end of file diff --git a/js/directive/NewsReadFile.js b/js/directive/NewsReadFile.js new file mode 100644 index 000000000..d3ed5b80e --- /dev/null +++ b/js/directive/NewsReadFile.js @@ -0,0 +1,28 @@ +/** + * 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('newsReadFile', () => { + 'use strict'; + + return (scope, elm, attr) => { + + let file = elm[0].files[0]; + let reader = new FileReader(); + + reader.onload = (event) => { + elm[0].value = 0; + scope.$fileContent = event.target.result; + scope.$apply(attr.newsReadFile); // FIXME: is there a more flexible + // solution where we dont have to + // bind the file to scope? + }; + + reader.reasAsText(file); + }; +});
\ No newline at end of file diff --git a/js/directive/NewsScroll.js b/js/directive/NewsScroll.js index 043235bbf..126c79b11 100644 --- a/js/directive/NewsScroll.js +++ b/js/directive/NewsScroll.js @@ -99,7 +99,7 @@ app.directive('newsScroll', ($timeout) => { }, scope.newsScrollMarkReadTimeout*1000); } - }); + }; elem.on('scroll', scrollHandler); diff --git a/js/karma.conf.js b/js/karma.conf.js index bc00fa12a..02ca523f1 100644 --- a/js/karma.conf.js +++ b/js/karma.conf.js @@ -29,7 +29,7 @@ module.exports = function (config) { options: { blockBinding: true, experimental: true, - sourceMap: false, + sourceMap: true, modules: 'inline' } }, diff --git a/js/service/Publisher.js b/js/service/Publisher.js index d4efa4a80..dd420f629 100644 --- a/js/service/Publisher.js +++ b/js/service/Publisher.js @@ -7,6 +7,8 @@ * @author Bernhard Posselt <dev@bernhard-posselt.com> * @copyright Bernhard Posselt 2014 */ + +/*jshint undef:false*/ app.service('Publisher', function () { 'use strict'; diff --git a/js/service/Settings.js b/js/service/Settings.js index 1bc716b07..e8189fcd5 100644 --- a/js/service/Settings.js +++ b/js/service/Settings.js @@ -7,7 +7,9 @@ * @author Bernhard Posselt <dev@bernhard-posselt.com> * @copyright Bernhard Posselt 2014 */ -app.service('Settings', function () { + + /*jshint unused:false*/ +app.service('Settings', function ($http, BASE_URL) { 'use strict'; this.settings = {}; @@ -24,6 +26,15 @@ app.service('Settings', function () { this.set = (key, value) => { this.settings[key] = value; + + let data = {}; + data[key] = value; + + return $http({ + url: `${BASE_URL}/settings`, + method: 'POST', + data: data + }); }; });
\ No newline at end of file diff --git a/js/tests/unit/controller/SettingsControllerSpec.js b/js/tests/unit/controller/SettingsControllerSpec.js new file mode 100644 index 000000000..c438fbcd0 --- /dev/null +++ b/js/tests/unit/controller/SettingsControllerSpec.js @@ -0,0 +1,67 @@ +/** + * 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 + */ +describe('SettingsController', () => { + 'use strict'; + + beforeEach(module('News', ($provide) => { + $provide.value('BASE_URL', 'base'); + })); + + + it('should set values', inject(($controller) => { + let Settings = { + set: jasmine.createSpy('Settings'), + get: key => key + }; + + let ctrl = $controller('SettingsController', { + Settings: Settings + }); + + ctrl.toggleSetting(3); + + expect(Settings.set).toHaveBeenCalledWith(3, false); + })); + + + it('should reload page if set needed', inject(($controller) => { + let settings = { + set: jasmine.createSpy('Settings'), + get: key => key + }; + + let route = { + reload: jasmine.createSpy('Route') + }; + + let ctrl = $controller('SettingsController', { + Settings: settings, + $route: route + }); + + ctrl.toggleSetting('showAll'); + ctrl.toggleSetting('oldestFirst'); + + expect(settings.set).toHaveBeenCalledWith('showAll', false); + expect(route.reload).toHaveBeenCalled(); + expect(route.reload.callCount).toBe(2); + })); + + + it('should return feed size', inject(($controller, FeedResource) => { + FeedResource.add({url: 'hi'}); + + let ctrl = $controller('SettingsController', { + FeedResource: FeedResource + }); + + expect(ctrl.feedSize()).toBe(1); + })); +});
\ No newline at end of file diff --git a/js/tests/unit/service/SettingsSpec.js b/js/tests/unit/service/SettingsSpec.js index b76251e3a..06bb365b6 100644 --- a/js/tests/unit/service/SettingsSpec.js +++ b/js/tests/unit/service/SettingsSpec.js @@ -10,7 +10,16 @@ describe('Settings', () => { 'use strict'; - beforeEach(module('News')); + let http; + + beforeEach(module('News', ($provide) => { + $provide.value('BASE_URL', 'base'); + })); + + beforeEach(inject(($httpBackend) => { + http = $httpBackend; + })); + it('should receive default settings', inject((Settings) => { Settings.receive({ @@ -22,9 +31,20 @@ describe('Settings', () => { it('should set values', inject((Settings) => { + http.expectPOST('base/settings', {showAll: true}).respond(200, {}); + Settings.set('showAll', true); + http.flush(); + expect(Settings.get('showAll')).toBe(true); })); + + afterEach(() => { + http.verifyNoOutstandingExpectation(); + http.verifyNoOutstandingRequest(); + }); + + });
\ No newline at end of file diff --git a/templates/part.settings.php b/templates/part.settings.php index b1d5383ea..b8f22cda2 100644 --- a/templates/part.settings.php +++ b/templates/part.settings.php @@ -1,84 +1,106 @@ <div id="app-settings-header"> -<button name="app settings" - class="settings-button" - oc-click-slide-toggle="{ - selector: '#app-settings-content', - hideOnFocusLost: true, - cssClass: 'opened' - }"></button> +<button name="app settings" + class="settings-button" + oc-click-slide-toggle="{ + selector: '#app-settings-content', + hideOnFocusLost: true, + cssClass: 'opened' + }"></button> </div> -<div id="app-settings-content"> - <fieldset class="personalblock"> - <legend><strong><?php p($l->t('Settings')); ?></strong></legend> - <p ng-click="getSetting('compact')"> - <input type="checkbox" ng-checked="getSetting('compact')"> <?php p($l->t('Use compact view')); ?> - </p> - <p ng-click="getSetting('oldestFirst')"> - <input type="checkbox" ng-checked="getSetting('oldestFirst')"> <?php p($l->t('Order by oldest first')); ?> - </p> - <p ng-click="getSetting('preventReadOnScroll')"> - <input type="checkbox" ng-checked="getSetting('preventReadOnScroll')"> <?php p($l->t('Do not as mark read when scrolling')); ?> - </p> - <legend><strong><?php p($l->t('Subscriptions (OPML)')); ?></strong></legend> - - <input type="file" id="opml-upload" name="import" accept="text/x-opml, text/xml" - oc-read-file="import($fileContent)"/> - <button title="<?php p($l->t('Import')); ?>" - class="upload-icon svg" - oc-forward-click="{selector:'#opml-upload'}"> - <?php p($l->t('Import')); ?> - </button> - - - <a title="<?php p($l->t('Export')); ?>" class="button download-icon svg" - href="<?php p(\OCP\Util::linkToRoute('news.export.opml')); ?>" - target="_blank" - ng-show="feedBusinessLayer.getNumberOfFeeds() > 0"> - <?php p($l->t('Export')); ?> - </a> - <button - class="download-icon svg" - title="<?php p($l->t('Export')); ?>" - ng-hide="feedBusinessLayer.getNumberOfFeeds() > 0" disabled> - <?php p($l->t('Export')); ?> - </button> - - <p class="error" ng-show="error"> - <?php p($l->t('Error when importing: file does not contain valid OPML')); ?> - </p> - - </fieldset> - - <fieldset class="personalblock"> - <legend><strong><?php p($l->t('Unread/Starred Articles')); ?></strong></legend> - <input type="file" id="google-upload" name="importgoogle" - accept="application/json" - oc-read-file="importArticles($fileContent)"/> - <button title="<?php p($l->t('Import')); ?>" - class="upload-icon svg" - ng-class="{loading: importing}" - ng-disabled="importing" - oc-forward-click="{selector:'#google-upload'}"> - <?php p($l->t('Import')); ?> - </button> - - <a title="<?php p($l->t('Export')); ?>" class="button download-icon svg" - href="<?php p(\OCP\Util::linkToRoute('news.export.articles')); ?>" - target="_blank" - ng-show="feedBusinessLayer.getNumberOfFeeds() > 0"> - <?php p($l->t('Export')); ?> - </a> - <button - class="download-icon svg" - title="<?php p($l->t('Export')); ?>" - ng-hide="feedBusinessLayer.getNumberOfFeeds() > 0" disabled> - <?php p($l->t('Export')); ?> - </button> - - <p class="error" ng-show="jsonError"> - <?php p($l->t('Error when importing: file does not contain valid JSON')); ?> - </p> - - </fieldset> +<div id="app-settings-content" ng-controller="SettingsController as Settings"> + <h3><?php p($l->t('Settings')); ?></h3> + + <p ng-click="Settings.toggleSetting('compact')"> + <input type="checkbox" ng-checked="Settings.getSetting('compact')"> + <?php p($l->t('Use compact view')); ?> + </p> + + <p ng-click="Settings.toggleSetting('showAll')"> + <input type="checkbox" ng-checked="Settings.getSetting('showAll')"> + <?php p($l->t('Show unread articles')); ?> + </p> + + <p ng-click="Settings.toggleSetting('oldestFirst')"> + <input type="checkbox" ng-checked="Settings.getSetting('oldestFirst')"> + <?php p($l->t('Order by oldest first')); ?> + </p> + + <p ng-click="Settings.toggleSetting('preventReadOnScroll')"> + <input type="checkbox" ng-checked="Settings.getSetting('preventReadOnScroll')"> + <?php p($l->t('Do not as mark read when scrolling')); ?> + </p> + + + <h3><?php p($l->t('Subscriptions (OPML)')); ?></h3> + + <input type="file" + id="opml-upload" + name="import" + accept="text/x-opml, text/xml" + news-read-file="Settings.importOpml($fileContent)"/> + + <button title="<?php p($l->t('Import')); ?>" + class="upload-icon svg" + news-trigger-click="#opml-upload"> + <?php p($l->t('Import')); ?> + </button> + + + <a title="<?php p($l->t('Export')); ?>" + class="button download-icon svg" + href="<?php p(\OCP\Util::linkToRoute('news.export.opml')); ?>" + target="_blank" + ng-show="feedSize() > 0"> + <?php p($l->t('Export')); ?> + </a> + + <button + class="download-icon svg" + title="<?php p($l->t('Export')); ?>" + ng-hide="feedSize() > 0" + disabled> + <?php p($l->t('Export')); ?> + </button> + + <p class="error" ng-show="Settings.opmlImportError"> + <?php p($l->t('Error when importing: file does not contain valid OPML')); ?> + </p> + + + <h3><?php p($l->t('Unread/Starred Articles')); ?></h3> + + <input + type="file" + id="article-upload" + name="importarticle" + accept="application/json" + news-read-file="Settings.importArticles($fileContent)"/> + + <button title="<?php p($l->t('Import')); ?>" + class="upload-icon svg" + ng-class="{loading: Settings.importing}" + ng-disabled="importing" + news-trigger-click="#article-upload"> + <?php p($l->t('Import')); ?> + </button> + + <a title="<?php p($l->t('Export')); ?>" class="button download-icon svg" + href="<?php p(\OCP\Util::linkToRoute('news.export.articles')); ?>" + target="_blank" |