diff options
author | Bernhard Posselt <dev@bernhard-posselt.com> | 2016-02-11 22:36:29 +0100 |
---|---|---|
committer | Bernhard Posselt <dev@bernhard-posselt.com> | 2016-02-11 22:36:29 +0100 |
commit | 13e6e80281a0c7ddc980006ae39ae3bae4cb03d1 (patch) | |
tree | 767d7556cb419ab48bbb75e2563eeb1623ebd225 /js | |
parent | c191ea52765c2515560eee01c62c61485b1ff6af (diff) |
migrate js build system to gulp
Diffstat (limited to 'js')
-rw-r--r-- | js/.jshintignore | 3 | ||||
-rw-r--r-- | js/.jshintrc | 3 | ||||
-rw-r--r-- | js/Gruntfile.js | 241 | ||||
-rw-r--r-- | js/README.md | 43 | ||||
-rw-r--r-- | js/app/App.js | 2 | ||||
-rw-r--r-- | js/app/Config.js | 3 | ||||
-rw-r--r-- | js/build/app.js | 3178 | ||||
-rw-r--r-- | js/build/app.min.js | 6 | ||||
-rw-r--r-- | js/build/app.min.js.map | 1 | ||||
-rw-r--r-- | js/controller/AppController.js | 2 | ||||
-rw-r--r-- | js/gui/ExternSubscription.js | 2 | ||||
-rw-r--r-- | js/gulpfile.js | 93 | ||||
-rw-r--r-- | js/karma.conf.js | 5 | ||||
-rw-r--r-- | js/package.json | 24 | ||||
-rw-r--r-- | js/tests/unit/controller/AppControllerSpec.js | 2 |
15 files changed, 126 insertions, 3482 deletions
diff --git a/js/.jshintignore b/js/.jshintignore new file mode 100644 index 000000000..eb5a16ccc --- /dev/null +++ b/js/.jshintignore @@ -0,0 +1,3 @@ +build/ +node_modules/ +vendor/ diff --git a/js/.jshintrc b/js/.jshintrc index 7835a8760..19894c5ea 100644 --- a/js/.jshintrc +++ b/js/.jshintrc @@ -47,6 +47,7 @@ "News": true, "t": true, "url": true, - "navigator": true + "navigator": true, + "oc_requesttoken": true } } diff --git a/js/Gruntfile.js b/js/Gruntfile.js deleted file mode 100644 index 19a2967d8..000000000 --- a/js/Gruntfile.js +++ /dev/null @@ -1,241 +0,0 @@ -/** - * 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 2012, 2014 - */ -module.exports = function (grunt) { - 'use strict'; - - // load needed modules - grunt.loadNpmTasks('grunt-php'); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-cssmin'); - grunt.loadNpmTasks('grunt-phpunit'); - grunt.loadNpmTasks('grunt-wrap'); - grunt.loadNpmTasks('grunt-karma'); - grunt.loadNpmTasks('grunt-ng-annotate'); - grunt.loadNpmTasks('grunt-protractor-runner'); - grunt.loadNpmTasks('grunt-protractor-webdriver'); - - grunt.initConfig({ - meta: { - pkg: grunt.file.readJSON('package.json'), - version: '<%= meta.pkg.version %>', - production: 'build/' - }, - concat: { - options: { - // remove license headers - stripBanners: true - }, - dist: { - src: [ - 'app/App.js', - 'app/Config.js', - 'app/Run.js', - 'controller/**/*.js', - 'filter/**/*.js', - 'service/**/*.js', - 'gui/**/*.js', - 'plugin/**/*.js', - 'utility/**/*.js', - 'directive/**/*.js' - ], - dest: '<%= meta.production %>app.js' - } - }, - ngAnnotate: { - app: { - src: ['<%= meta.production %>app.js'], - dest: '<%= meta.production %>app.js' - } - }, - uglify: { - app: { - files: { - '<%= meta.production %>app.min.js': [ - '<%= meta.production %>app.js' - ] - } - }, - options: { - sourceMap: true - } - }, - cssmin: { - options: { - sourceMap: true - }, - news: { - files: { - '../css/news.min.css': [ - '../css/app.css', - '../css/content.css', - '../css/custom.css', - '../css/shortcuts.css', - '../css/mobile.css', - '../css/navigation.css', - '../css/settings.css', - '../css/explore.css' - ] - } - } - }, - wrap: { - basic: { - src: ['<%= meta.production %>app.js'], - dest: '<%= meta.production %>app.js', - options: { - wrapper: [ - '(function(navigator, window, document, angular, $, ' + - 'OC, csrfToken, url, undefined){' + - '\n\n\'use strict\';\n\n', - - '\n})(navigator, window, document, angular, jQuery, ' + - ' OC, oc_requesttoken, url);' - ] - } - } - }, - jshint: { - app: { - src: [ - 'Gruntfile.js', - 'app/App.js', - 'app/Config.js', - 'app/Run.js', - 'filter/**/*.js', - 'service/**/*.js', - 'controller/**/*.js', - 'directive/**/*.js', - 'tests/**/*.js', - 'gui/**/*.js', - 'plugin/**/*.js', - 'admin/**/*.js' - ] - }, - options: { - jshintrc: true - } - }, - watch: { - concat: { - files: [ - '../css/*.css', - '!../css/*.min.css', - 'admin/**/*.js', - 'tests/**/*.js', - 'app/**/*.js', - 'controller/**/*.js', - 'utility/**/*.js', - 'directive/**/*.js', - 'filter/**/*.js', - 'service/**/*.js', - 'gui/**/*.js', - 'plugin/**/*.js', - '../templates/**/*.php' - ], - tasks: ['default'], - options: { - livereload: true - } - }, - phpunit: { - files: [ - '../**/*.php' - ], - tasks: ['phpunit:unit'] - }, - phpintegration: { - files: [ - '../**/*.php' - ], - tasks: ['phpunit:integration'] - } - }, - karma: { - unit: { - configFile: 'karma.conf.js', - autoWatch: true - }, - continuous: { - configFile: 'karma.conf.js', - browsers: ['Firefox'], - singleRun: true, - } - }, - phpunit: { - unit: { - options: { - colors: true, - configuration: '../phpunit.xml' - } - }, - coverage: { - options: { - colors: true, - configuration: '../phpunit.xml', - coverageClover: '../coverage.clover' - } - }, - integration: { - options: { - colors: true, - configuration: '../phpunit.integration.xml' - } - }, - }, - /* jshint camelcase: false */ - protractor_webdriver: { - app: {} - }, - protractor: { - firefox: { - options: { - configFile: 'protractor.conf.js' - } - }, - }, - connect: { - server: { - options: { - base: 'tests/static/' - } - } - }, - php: { - dist: { - options: { - port: 8080, - keepalive: true, - open: true, - base: '../../../' - } - } - } - }); - - // make tasks available under simpler commands - grunt.registerTask('default', ['jshint', 'concat', 'wrap', 'ngAnnotate', - 'uglify', 'cssmin']); - grunt.registerTask('dev', ['watch:concat']); - grunt.registerTask('dev-js-unit', ['karma:unit']); - grunt.registerTask('dev-php-unit', ['watch:phpunit']); - grunt.registerTask('dev-php-integration', ['watch:phpintegration']); - - grunt.registerTask('js-unit', ['karma:continuous']); - grunt.registerTask('php-unit', ['phpunit:coverage']); - grunt.registerTask('php-integration', ['phpunit:integration']); - - grunt.registerTask('acceptance', ['protractor_webdriver', 'connect', - 'protractor']); - -}; diff --git a/js/README.md b/js/README.md index c03621fdc..2c25ce4ed 100644 --- a/js/README.md +++ b/js/README.md @@ -1,40 +1,17 @@ -# JavaScript && CSS Development -Before starting, install nodejs and grunt-cli: +# JavaScript +Before starting, install nodejs and gulp-cli: - sudo npm -g install grunt-cli + sudo npm -g install gulp-cli then run: npm install +## Tasks +The following tasks are available: -## Building -This sets up a watcher on file change and compiles CSS and JS: - - grunt dev - -If you don't want a watcher, just run: - - grunt - -## Testing -Watch mode: - - grunt php - grunt test - -Single run mode: - - grunt phpunit - grunt ci-unit - -### Running e2e tests -Install protractor and set up selenium: - - sudo npm install -g protractor - sudo webdriver-manager update - -then the tests can be started with: - - grunt e2e - +* **Build the JavaScript**: gulp +* **Watch for changes and build JavaScript**: gulp watch +* **Run JavaScript unit tests**: gulp karma +* **Watch for changes and run JavaScript unit tests**: gulp watch-karma +* **Watch for changes and run PHP unit tests**: gulp watch-phpuni diff --git a/js/app/App.js b/js/app/App.js index 2c8e71419..37266a438 100644 --- a/js/app/App.js +++ b/js/app/App.js @@ -9,4 +9,4 @@ */ /* jshint unused: false */ -var app = angular.module('News', ['ngRoute', 'ngSanitize', 'ngAnimate']);
\ No newline at end of file +var app = angular.module('News', ['ngRoute', 'ngSanitize', 'ngAnimate']); diff --git a/js/app/Config.js b/js/app/Config.js index df7cc60e5..8c9c09290 100644 --- a/js/app/Config.js +++ b/js/app/Config.js @@ -37,7 +37,8 @@ app.config(function ($routeProvider, $provide, $httpProvider) { $window.location.href.split($window.location.pathname)[0]; if (config.url.indexOf(BASE_URL) === 0 || config.url.indexOf(domain) === 0) { - config.headers.requesttoken = csrfToken; + /*jshint camelcase: false */ + config.headers.requesttoken = oc_requesttoken; } return config || $q.when(config); diff --git a/js/build/app.js b/js/build/app.js deleted file mode 100644 index 1520622d5..000000000 --- a/js/build/app.js +++ /dev/null @@ -1,3178 +0,0 @@ -(function(navigator, window, document, angular, $, OC, csrfToken, url, undefined){ - -'use strict'; - - -/* jshint unused: false */ -var app = angular.module('News', ['ngRoute', 'ngSanitize', 'ngAnimate']); -app.config(["$routeProvider", "$provide", "$httpProvider", function ($routeProvider, $provide, $httpProvider) { - 'use strict'; - - var feedType = { - FEED: 0, - FOLDER: 1, - STARRED: 2, - SUBSCRIPTIONS: 3, - SHARED: 4, - EXPLORE: 5 - }; - - // constants - $provide.constant('REFRESH_RATE', 60); // seconds - $provide.constant('ITEM_BATCH_SIZE', 40); // how many items should be - // fetched initially - $provide.constant('ITEM_AUTO_PAGE_SIZE', 20); - $provide.constant('BASE_URL', OC.generateUrl('/apps/news')); - $provide.constant('FEED_TYPE', feedType); - $provide.constant('MARK_READ_TIMEOUT', 0.5); - $provide.constant('SCROLL_TIMEOUT', 0.1); - - // make sure that the CSRF header is only sent to the ownCloud domain - $provide.factory('CSRFInterceptor', ["$q", "BASE_URL", "$window", function ($q, BASE_URL, $window) { - return { - request: function (config) { - var domain = - $window.location.href.split($window.location.pathname)[0]; - if (config.url.indexOf(BASE_URL) === 0 || - config.url.indexOf(domain) === 0) { - config.headers.requesttoken = csrfToken; - } - - return config || $q.when(config); - } - }; - }]); - var errorMessages = { - 0: t('news', 'Request failed, network connection unavailable!'), - 401: t('news', 'Request unauthorized. Are you logged in?'), - 403: t('news', 'Request forbidden. Are you an admin?'), - 412: t('news', 'Token expired or app not enabled! Reload the page!'), - 500: t('news', 'Internal server error! Please check your ' + - 'data/owncloud.log file for additional ' + - 'information!'), - 503: t('news', 'Request failed, ownCloud is in currently ' + - 'in maintenance mode!'), - }; - $provide.factory('ConnectionErrorInterceptor', ["$q", "$timeout", function ($q, $timeout) { - var timer; - return { - responseError: function (response) { - // status 0 is a network error - if (response.status in errorMessages) { - if (timer) { - $timeout.cancel(timer); - } - OC.Notification.hide(); - OC.Notification.showHtml(errorMessages[response.status]); - timer = $timeout(function () { - OC.Notification.hide(); - }, 5000); - } - return $q.reject(response); - } - }; - }]); - $httpProvider.interceptors.push('CSRFInterceptor'); - $httpProvider.interceptors.push('ConnectionErrorInterceptor'); - - // routing - var getItemResolve = function (type) { - return { - // request to items also returns feeds - data: /* @ngInject */ ["$http", "$route", "$q", "$location", "BASE_URL", "ITEM_BATCH_SIZE", "FEED_TYPE", "SettingsResource", "FeedResource", function ( - $http, $route, $q, $location, BASE_URL, ITEM_BATCH_SIZE, FEED_TYPE, - SettingsResource, FeedResource) { - - var showAll = SettingsResource.get('showAll'); - var oldestFirst = SettingsResource.get('oldestFirst'); - var search = $location.search().search || ''; - - var deferred = $q.defer(); - - // if those two values are null it means we did not receive - // the settings request from the server so dont query the server - if (showAll === null || oldestFirst === null) { - deferred.resolve({}); - } else { - var parameters = { - type: type, - limit: ITEM_BATCH_SIZE, - showAll: showAll, - oldestFirst: oldestFirst, - search: search - }; - - if ($route.current.params.id !== undefined) { - parameters.id = $route.current.params.id; - } - - // check if a custom ordering is set - if (type === FEED_TYPE.FEED) { - var feed = FeedResource.getById(parameters.id); - - // on intial load, the feed ordering is undefined - if (feed === undefined || feed.ordering === 2) { - parameters.oldestFirst = false; - } else if (feed.ordering === 1) { - parameters.oldestFirst = true; - } - } - - $http({ - url: BASE_URL + '/items', - method: 'GET', - params: parameters - }).success(function (data) { - deferred.resolve(data); - }); - } - - return deferred.promise; - }] - }; - }; - - var getExploreResolve = function () { - return { - sites: /* @ngInject */ ["$http", "$q", "BASE_URL", "$location", "Publisher", "SettingsResource", function ( - $http, $q, BASE_URL, $location, Publisher, SettingsResource) { - var deferred = $q.defer(); - - // always use the code from the url - var language = $location.search().lang; - if (!language) { - language = SettingsResource.get('language'); - } - - $http.get(BASE_URL + '/settings').then(function (data) { - Publisher.publishAll(data); - - // get url and strip trailing slashes - var url = SettingsResource.get('exploreUrl') - .replace(/\/+$/, ''); - - var exploreUrl = url + '/feeds.' + language + '.json'; - var defaultExploreUrl = url + '/feeds.en.json'; - return $http - .get(exploreUrl) - .catch(function () { - return $http.get(defaultExploreUrl); - }); - - }).then(function (data) { - deferred.resolve(data.data); - }).catch(function () { - deferred.reject(); - }); - - return deferred.promise; - }] - }; - }; - - $routeProvider - .when('/items', { - controller: 'ContentController as Content', - templateUrl: 'content.html', - resolve: getItemResolve(feedType.SUBSCRIPTIONS), - type: feedType.SUBSCRIPTIONS - }) - .when('/items/starred', { - controller: 'ContentController as Content', - templateUrl: 'content.html', - resolve: getItemResolve(feedType.STARRED), - type: feedType.STARRED - }) - .when('/items/feeds/:id', { - controller: 'ContentController as Content', - templateUrl: 'content.html', - resolve: getItemResolve(feedType.FEED), - type: feedType.FEED - }) - .when('/items/folders/:id', { - controller: 'ContentController as Content', - templateUrl: 'content.html', - resolve: getItemResolve(feedType.FOLDER), - type: feedType.FOLDER - }).when('/explore', { - controller: 'ExploreController as Explore', - templateUrl: 'explore.html', - resolve: getExploreResolve(), - type: feedType.EXPLORE - }).when('/shortcuts', { - templateUrl: 'shortcuts.html', - type: -1 - }); - -}]); - -app.run(["$rootScope", "$location", "$http", "$q", "$interval", "$route", "Loading", "ItemResource", "FeedResource", "FolderResource", "SettingsResource", "Publisher", "BASE_URL", "FEED_TYPE", "REFRESH_RATE", function ($rootScope, $location, $http, $q, $interval, $route, Loading, - ItemResource, FeedResource, FolderResource, SettingsResource, - Publisher, BASE_URL, FEED_TYPE, REFRESH_RATE) { - 'use strict'; - - // show Loading screen - Loading.setLoading('global', true); - - // listen to keys in returned queries to automatically distribute the - // incoming values to models - Publisher.subscribe(ItemResource).toChannels(['items', 'newestItemId', - 'starred']); - Publisher.subscribe(FolderResource).toChannels(['folders']); - Publisher.subscribe(FeedResource).toChannels(['feeds']); - Publisher.subscribe(SettingsResource).toChannels(['settings']); - - // load feeds, settings and last read feed - var settingsDeferred = $q.defer(); - $http.get(BASE_URL + '/settings').success(function (data) { - Publisher.publishAll(data); - settingsDeferred.resolve(); - }); - - var activeFeedDeferred = $q.defer(); - var path = $location.path(); - $http.get(BASE_URL + '/feeds/active').success(function (data) { - var url; - - switch (data.activeFeed.type) { - - case FEED_TYPE.FEED: - url = '/items/feeds/' + data.activeFeed.id; - break; - - case FEED_TYPE.FOLDER: - url = '/items/folders/' + data.activeFeed.id; - break; - - case FEED_TYPE.STARRED: - url = '/items/starred'; - break; - - case FEED_TYPE.EXPLORE: - url = '/explore'; - break; - - default: - url = '/items'; - } - - // only redirect if url is empty or faulty - if (!/^\/items(\/(starred|explore|feeds\/\d+|folders\/\d+))?\/?$/ - .test(path)) { - $location.path(url); - } - - activeFeedDeferred.resolve(); - }); - - var feedDeferred = $q.defer(); - var feeds; - $http.get(BASE_URL + '/feeds').success(function (data) { - feeds = data; - feedDeferred.resolve(); - }); - - var folderDeferred = $q.defer(); - var folders; - $http.get(BASE_URL + '/folders').success(function (data) { - folders = data; - folderDeferred.resolve(); - }); - - $q.all([ - feedDeferred.promise, - folderDeferred.promise - ]).then(function () { - // first publish feeds to correctly update the folder resource unread - // cache - Publisher.publishAll(feeds); - Publisher.publishAll(folders); - if (feeds.feeds.length === 0 && folders.folders.length === 0) { - $location.path('/explore'); - } - }); - - // disable loading if all initial requests finished - $q.all( - [ - settingsDeferred.promise, - activeFeedDeferred.promise, - feedDeferred.promise, - folderDeferred.promise - ] - ) - .then(function () { - $route.reload(); - Loading.setLoading('global', false); - }); - - // refresh feeds and folders - $interval(function () { - $http.get(BASE_URL + '/feeds').success(function (data) { - Publisher.publishAll(data); - }); - $http.get(BASE_URL + '/folders').success(function (data) { - Publisher.publishAll(data); - }); - }, REFRESH_RATE * 1000); - - - $rootScope.$on('$routeChangeStart', function () { - Loading.setLoading('content', true); - }); - - $rootScope.$on('$routeChangeSuccess', function () { - Loading.setLoading('content', false); - }); - - // in case of wrong id etc show all items - $rootScope.$on('$routeChangeError', function () { - $location.path('/items'); - }); - -}]); -app.controller('AppController', -["Loading", "FeedResource", "FolderResource", function (Loading, FeedResource, FolderResource) { - 'use strict'; - - this.loading = Loading; - - this.isFirstRun = function () { - return FeedResource.size() === 0 && FolderResource.size() === 0; - }; - - this.play = function (item) { - this.playingItem = item; - }; -}]); -app.controller('ContentController', -["Publisher", "FeedResource", "ItemResource", "SettingsResource", "data", "$route", "$routeParams", "$location", "FEED_TYPE", "ITEM_AUTO_PAGE_SIZE", "Loading", function (Publisher, FeedResource, ItemResource, SettingsResource, data, - $route, $routeParams, $location, FEED_TYPE, ITEM_AUTO_PAGE_SIZE, Loading) { - 'use strict'; - - ItemResource.clear(); - - // distribute data to models based on key - Publisher.publishAll(data); - - - this.isAutoPagingEnabled = true; - - // the interface should show a hint if there are not enough items sent so - // it's assumed that theres nothing to autpage - if (ItemResource.size() >= ITEM_AUTO_PAGE_SIZE) { - this.isNothingMoreToAutoPage = false; - } else { - this.isNothingMoreToAutoPage = true; - } - - this.getItems = function () { - return ItemResource.getAll(); - }; - - this.toggleStar = function (itemId) { - ItemResource.toggleStar(itemId); - }; - - this.toggleItem = function (item) { - // TODO: unittest - if (this.isCompactView()) { - item.show = !item.show; - } - }; - - this.isShowAll = function () { - return SettingsResource.get('showAll'); - }; - - this.markRead = function (itemId) { - var item = ItemResource.get(itemId); - - if (!item.keepUnread && item.unread === true) { - ItemResource.markItemRead(itemId); - FeedResource.markItemOfFeedRead(item.feedId); - } - }; - - this.getFeed = function (feedId) { - return FeedResource.getById(feedId); - }; - - this.toggleKeepUnread = function (itemId) { - var item = ItemResource.get(itemId); - if (!item.unread) { - FeedResource.markItemOfFeedUnread(item.feedId); - ItemResource.markItemRead(itemId, false); - } - - 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 (getOrdering()) { - return 'id'; - } else { - return '-id'; - } - }; - - this.isCompactView = function () { - return SettingsResource.get('compact'); - }; - - this.isCompactExpand = function () { - return SettingsResource.get('compactExpand'); - }; - - this.autoPagingEnabled = function () { - return this.isAutoPagingEnabled; - }; - - this.markReadEnabled = function () { - return !SettingsResource.get('preventReadOnScroll'); - }; - - this.scrollRead = function (itemIds) { - var ids = []; - var feedIds = []; - - itemIds.forEach(function (itemId) { - var item = ItemResource.get(itemId); - if (!item.keepUnread) { - ids.push(itemId); - feedIds.push(item.feedId); - } - }); - - if (ids.length > 0) { - FeedResource.markItemsOfFeedsRead(feedIds); - ItemResource.markItemsRead(ids); - } - }; - - this.isFeed = function () { - return $route.current.$$route.type === FEED_TYPE.FEED; - }; - - this.autoPage = function () { - if (this.isNothingMoreToAutoPage) { - return; - } - - // in case a subsequent autopage request comes in wait until - // the current one finished and ex |