diff --git a/CHANGELOG b/CHANGELOG index 774ea3ae..058e289b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,11 @@ +owncloud-notes (1.0.0) +* **Bugfix**: Remove flying loading icon +* **Enhancement**: Make app ready for ownCloud 8 +* **Enhancement**: Show a spinner to signal when app is saving +* **Enhancement**: Prevent closing the window when app is saving + owncloud-notes (0.9) -* Security: Remove markdown support because of [XSS in markdown-js library](https://github.com/evilstreak/markdown-js/pull/52) +* **Security**: Remove markdown support because of [XSS in markdown-js library](https://github.com/evilstreak/markdown-js/pull/52) owncloud-notes (0.7) * Port to ownCloud internal app framework. Additional installation of the appframework app is not needed anymore diff --git a/js/Gruntfile.js b/js/Gruntfile.js index f649b557..a6534e8e 100644 --- a/js/Gruntfile.js +++ b/js/Gruntfile.js @@ -31,7 +31,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-wrap'); grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-phpunit'); - + grunt.loadNpmTasks('grunt-ng-annotate'); grunt.initConfig({ @@ -123,12 +123,19 @@ module.exports = function(grunt) { browsers: ['PhantomJS'], reporters: ['progress'] } - } + }, + + ngAnnotate: { + app: { + src: ['<%= meta.production %>app.js'], + dest: '<%= meta.production %>app.js' + } + }, }); // make tasks available under simpler commands - grunt.registerTask('build', ['jshint', 'concat', 'wrap:app']); + grunt.registerTask('build', ['jshint', 'concat', 'wrap:app', 'ngAnnotate']); grunt.registerTask('default', ['build']); }; \ No newline at end of file diff --git a/js/app/controllers/appcontroller.js b/js/app/controllers/appcontroller.js index 1d910827..28f97436 100644 --- a/js/app/controllers/appcontroller.js +++ b/js/app/controllers/appcontroller.js @@ -4,8 +4,7 @@ * See the COPYING file. */ -app.controller('AppController', ['$scope', '$location', 'is', - function ($scope, $location, is) { +app.controller('AppController', function ($scope, $location, is) { $scope.is = is; $scope.init = function (lastViewedNote) { @@ -13,4 +12,4 @@ app.controller('AppController', ['$scope', '$location', 'is', $location.path('/notes/' + lastViewedNote); } }; -}]); \ No newline at end of file +}); \ No newline at end of file diff --git a/js/app/controllers/notecontroller.js b/js/app/controllers/notecontroller.js index f8f292da..4df9cb3e 100644 --- a/js/app/controllers/notecontroller.js +++ b/js/app/controllers/notecontroller.js @@ -4,9 +4,8 @@ * See the COPYING file. */ -app.controller('NoteController', ['$routeParams', '$scope', 'NotesModel', - 'SaveQueue', 'note', 'Config', - function($routeParams, $scope, NotesModel, SaveQueue, note, Config) { +app.controller('NoteController', function($routeParams, $scope, NotesModel, + SaveQueue, note, Config) { NotesModel.updateIfExists(note); @@ -33,4 +32,4 @@ app.controller('NoteController', ['$routeParams', '$scope', 'NotesModel', Config.sync(); }; -}]); \ No newline at end of file +}); \ No newline at end of file diff --git a/js/app/controllers/notescontroller.js b/js/app/controllers/notescontroller.js index 04bbf5b0..2b41b317 100644 --- a/js/app/controllers/notescontroller.js +++ b/js/app/controllers/notescontroller.js @@ -5,9 +5,8 @@ */ // This is available by using ng-controller="NotesController" in your HTML -app.controller('NotesController', ['$routeParams', '$scope', '$location', - 'Restangular', 'NotesModel', - function($routeParams, $scope, $location, Restangular, NotesModel) { +app.controller('NotesController', function($routeParams, $scope, $location, + Restangular, NotesModel) { $scope.route = $routeParams; $scope.notes = NotesModel.getAll(); @@ -31,7 +30,7 @@ app.controller('NotesController', ['$routeParams', '$scope', '$location', note.remove().then(function () { NotesModel.remove(noteId); $scope.$emit('$routeChangeError'); - }); + }); }; -}]); +}); diff --git a/js/app/directives/autofocus.js b/js/app/directives/autofocus.js index 7151baaa..119912af 100644 --- a/js/app/directives/autofocus.js +++ b/js/app/directives/autofocus.js @@ -4,15 +4,11 @@ * See the COPYING file. */ -/** - * Like ng-change only that it does not fire when you type faster than - * 300 ms - */ -app.directive('notesAutofocus', [function () { +app.directive('notesAutofocus', function () { return { restrict: 'A', link: function (scope, element, attributes) { element.focus(); } }; -}]); +}); diff --git a/js/app/directives/issaving.js b/js/app/directives/issaving.js new file mode 100644 index 00000000..4deb64b6 --- /dev/null +++ b/js/app/directives/issaving.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2013, Bernhard Posselt + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING file. + */ + +app.directive('notesIsSaving', function ($window) { + return { + restrict: 'A', + scope: { + 'notesIsSaving': '=' + }, + link: function (scope, element, attributes) { + $window.onbeforeunload = function () { + if (scope.notesIsSaving) { + return t('notes', 'Note is currently saving. Leaving ' + + 'the page will delete all changes!'); + } else { + return null; + } + }; + } + }; +}); diff --git a/js/app/directives/timeoutchange.js b/js/app/directives/timeoutchange.js index 0c451c50..dc2fa996 100644 --- a/js/app/directives/timeoutchange.js +++ b/js/app/directives/timeoutchange.js @@ -8,7 +8,7 @@ * Like ng-change only that it does not fire when you type faster than * 300 ms */ -app.directive('notesTimeoutChange', ['$timeout', function ($timeout) { +app.directive('notesTimeoutChange', function ($timeout) { return { restrict: 'A', link: function (scope, element, attributes) { @@ -24,4 +24,4 @@ app.directive('notesTimeoutChange', ['$timeout', function ($timeout) { }); } }; -}]); +}); diff --git a/js/app/directives/tooltip.js b/js/app/directives/tooltip.js index e70825ea..8d2e1a3f 100644 --- a/js/app/directives/tooltip.js +++ b/js/app/directives/tooltip.js @@ -4,11 +4,11 @@ * See the COPYING file. */ -app.directive('notesTooltip', [function () { +app.directive('notesTooltip', function () { return { restrict: 'A', link: function (scope, element, attributes) { element.tooltip(); } }; -}]); +}); diff --git a/js/app/services/config.js b/js/app/services/config.js index 85b3091e..2a8f7c32 100644 --- a/js/app/services/config.js +++ b/js/app/services/config.js @@ -1,10 +1,10 @@ /** * Copyright (c) 2013, Bernhard Posselt * This file is licensed under the Affero General Public License version 3 or later. - * See the COPYING file. + * See the COPYING file. */ -app.factory('Config', ['Restangular', function (Restangular) { +app.factory('Config', function (Restangular) { var Config = function (Restangular) { this._markdown = false; this._Restangular = Restangular; @@ -32,4 +32,4 @@ app.factory('Config', ['Restangular', function (Restangular) { }; return new Config(Restangular); -}]); \ No newline at end of file +}); \ No newline at end of file diff --git a/js/app/services/savequeue.js b/js/app/services/savequeue.js index 677b7b09..3963f845 100644 --- a/js/app/services/savequeue.js +++ b/js/app/services/savequeue.js @@ -4,7 +4,7 @@ * See the COPYING file. */ -app.factory('SaveQueue', ['$q', function($q) { +app.factory('SaveQueue', function($q) { var SaveQueue = function () { this._queue = {}; this._flushLock = false; @@ -54,4 +54,4 @@ app.factory('SaveQueue', ['$q', function($q) { }; return new SaveQueue(); -}]); \ No newline at end of file +}); \ No newline at end of file diff --git a/js/config/app.js b/js/config/app.js index 636f3c15..ebb9ba57 100644 --- a/js/config/app.js +++ b/js/config/app.js @@ -5,10 +5,8 @@ */ var app = angular.module('Notes', ['restangular', 'ngRoute']). -config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', - '$windowProvider', - function($provide, $routeProvider, RestangularProvider, $httpProvider, - $windowProvider) { +config(function($provide, $routeProvider, RestangularProvider, $httpProvider, + $windowProvider) { // Always send the CSRF token by default $httpProvider.defaults.headers.common.requesttoken = oc_requesttoken; @@ -26,8 +24,8 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', // $routeParams does not work inside resolve so use $route // note is the name of the argument that will be injected into the // controller - note: ['$route', '$q', 'is', 'Restangular', - function ($route, $q, is, Restangular) { + /* @ngInject */ + note: function ($route, $q, is, Restangular) { var deferred = $q.defer(); var noteId = $route.current.params.noteId; @@ -42,7 +40,7 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', }); return deferred.promise; - }] + } } }).otherwise({ redirectTo: '/' @@ -57,8 +55,7 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', -}]).run(['$rootScope', '$location', 'NotesModel', 'Config', - function ($rootScope, $location, NotesModel, Config) { +}).run(function ($rootScope, $location, NotesModel, Config) { // get config Config.load(); @@ -81,4 +78,4 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', $location.path('/'); } }); -}]); +}); diff --git a/js/package.json b/js/package.json index c5c4625d..5dcfb3bc 100644 --- a/js/package.json +++ b/js/package.json @@ -1,31 +1,33 @@ { - "name": "notes", - "description": "Notes app", - "version": "0.0.1", - "private": true, - "homepage": "https://github.com/owncloud/notes", - "repository": { - "type": "git", - "url": "git@github.com:owncloud/notes.git" - }, - "bugs": "https://github.com/owncloud/notes/issues", - "dependencies": {}, - "devDependencies": { - "grunt": "*", - "grunt-cli": "*", - "grunt-contrib-concat": "*", - "grunt-contrib-jshint": "*", - "grunt-contrib-watch": "*", - "karma": "*", - "grunt-karma": "*", - "grunt-wrap": "*", - "phantomjs": "*", - "grunt-phpunit": "*", - "bower": "*", - "karma-jasmine": "*", - "karma-phantomjs-launcher": "*", - "karma-chrome-launcher": "*", - "karma-firefox-launcher": "*" - }, - "engine": "node >= 0.8" -} \ No newline at end of file + "name": "notes", + "description": "Notes app", + "version": "0.0.1", + "private": true, + "homepage": "https://github.com/owncloud/notes", + "repository": { + "type": "git", + "url": "git@github.com:owncloud/notes.git" + }, + "bugs": "https://github.com/owncloud/notes/issues", + "dependencies": { + "grunt-ng-annotate": "^0.10.0" + }, + "devDependencies": { + "grunt": "*", + "grunt-cli": "*", + "grunt-contrib-concat": "*", + "grunt-contrib-jshint": "*", + "grunt-contrib-watch": "*", + "karma": "*", + "grunt-karma": "*", + "grunt-wrap": "*", + "phantomjs": "*", + "grunt-phpunit": "*", + "bower": "*", + "karma-jasmine": "*", + "karma-phantomjs-launcher": "*", + "karma-chrome-launcher": "*", + "karma-firefox-launcher": "*" + }, + "engine": "node >= 0.8" +} diff --git a/js/public/app.js b/js/public/app.js index 78f5d293..1a22a9e3 100644 --- a/js/public/app.js +++ b/js/public/app.js @@ -25,10 +25,8 @@ if (!Function.prototype.bind) { }; } var app = angular.module('Notes', ['restangular', 'ngRoute']). -config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', - '$windowProvider', - function($provide, $routeProvider, RestangularProvider, $httpProvider, - $windowProvider) { +config(["$provide", "$routeProvider", "RestangularProvider", "$httpProvider", "$windowProvider", function($provide, $routeProvider, RestangularProvider, $httpProvider, + $windowProvider) { // Always send the CSRF token by default $httpProvider.defaults.headers.common.requesttoken = oc_requesttoken; @@ -46,8 +44,8 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', // $routeParams does not work inside resolve so use $route // note is the name of the argument that will be injected into the // controller - note: ['$route', '$q', 'is', 'Restangular', - function ($route, $q, is, Restangular) { + /* @ngInject */ + note: ["$route", "$q", "is", "Restangular", function ($route, $q, is, Restangular) { var deferred = $q.defer(); var noteId = $route.current.params.noteId; @@ -77,8 +75,7 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', -}]).run(['$rootScope', '$location', 'NotesModel', 'Config', - function ($rootScope, $location, NotesModel, Config) { +}]).run(["$rootScope", "$location", "NotesModel", "Config", function ($rootScope, $location, NotesModel, Config) { // get config Config.load(); @@ -103,8 +100,7 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', }); }]); -app.controller('AppController', ['$scope', '$location', 'is', - function ($scope, $location, is) { +app.controller('AppController', ["$scope", "$location", "is", function ($scope, $location, is) { $scope.is = is; $scope.init = function (lastViewedNote) { @@ -113,9 +109,8 @@ app.controller('AppController', ['$scope', '$location', 'is', } }; }]); -app.controller('NoteController', ['$routeParams', '$scope', 'NotesModel', - 'SaveQueue', 'note', 'Config', - function($routeParams, $scope, NotesModel, SaveQueue, note, Config) { +app.controller('NoteController', ["$routeParams", "$scope", "NotesModel", "SaveQueue", "note", "Config", function($routeParams, $scope, NotesModel, + SaveQueue, note, Config) { NotesModel.updateIfExists(note); @@ -144,9 +139,8 @@ app.controller('NoteController', ['$routeParams', '$scope', 'NotesModel', }]); // This is available by using ng-controller="NotesController" in your HTML -app.controller('NotesController', ['$routeParams', '$scope', '$location', - 'Restangular', 'NotesModel', - function($routeParams, $scope, $location, Restangular, NotesModel) { +app.controller('NotesController', ["$routeParams", "$scope", "$location", "Restangular", "NotesModel", function($routeParams, $scope, $location, + Restangular, NotesModel) { $scope.route = $routeParams; $scope.notes = NotesModel.getAll(); @@ -170,22 +164,37 @@ app.controller('NotesController', ['$routeParams', '$scope', '$location', note.remove().then(function () { NotesModel.remove(noteId); $scope.$emit('$routeChangeError'); - }); + }); }; }]); -/** - * Like ng-change only that it does not fire when you type faster than - * 300 ms - */ -app.directive('notesAutofocus', [function () { +app.directive('notesAutofocus', function () { return { restrict: 'A', link: function (scope, element, attributes) { element.focus(); } }; +}); + +app.directive('notesIsSaving', ["$window", function ($window) { + return { + restrict: 'A', + scope: { + 'notesIsSaving': '=' + }, + link: function (scope, element, attributes) { + $window.onbeforeunload = function () { + if (scope.notesIsSaving) { + return t('notes', 'Note is currently saving. Leaving ' + + 'the page will delete all changes!'); + } else { + return null; + } + }; + } + }; }]); app.directive('markdown', function () { @@ -209,7 +218,7 @@ app.directive('markdown', function () { * Like ng-change only that it does not fire when you type faster than * 300 ms */ -app.directive('notesTimeoutChange', ['$timeout', function ($timeout) { +app.directive('notesTimeoutChange', ["$timeout", function ($timeout) { return { restrict: 'A', link: function (scope, element, attributes) { @@ -227,14 +236,14 @@ app.directive('notesTimeoutChange', ['$timeout', function ($timeout) { }; }]); -app.directive('notesTooltip', [function () { +app.directive('notesTooltip', function () { return { restrict: 'A', link: function (scope, element, attributes) { element.tooltip(); } }; -}]); +}); /** * Binds translated values to scope and hides the element @@ -251,7 +260,7 @@ app.directive('notesTranslate', function () { }; }); -app.factory('Config', ['Restangular', function (Restangular) { +app.factory('Config', ["Restangular", function (Restangular) { var Config = function (Restangular) { this._markdown = false; this._Restangular = Restangular; @@ -332,7 +341,7 @@ app.factory('NotesModel', function () { return new NotesModel(); }); -app.factory('SaveQueue', ['$q', function($q) { +app.factory('SaveQueue', ["$q", function($q) { var SaveQueue = function () { this._queue = {}; this._flushLock = false; diff --git a/js/public/public/app.js b/js/public/public/app.js index 45858cc2..9d207b84 100644 --- a/js/public/public/app.js +++ b/js/public/public/app.js @@ -26,10 +26,8 @@ if (!Function.prototype.bind) { }; } var app = angular.module('Notes', ['restangular', 'ngRoute']). -config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', - '$windowProvider', - function($provide, $routeProvider, RestangularProvider, $httpProvider, - $windowProvider) { +config(function($provide, $routeProvider, RestangularProvider, $httpProvider, + $windowProvider) { // Always send the CSRF token by default $httpProvider.defaults.headers.common.requesttoken = oc_requesttoken; @@ -47,8 +45,8 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', // $routeParams does not work inside resolve so use $route // note is the name of the argument that will be injected into the // controller - note: ['$route', '$q', 'is', 'Restangular', - function ($route, $q, is, Restangular) { + /* @ngInject */ + note: function ($route, $q, is, Restangular) { var deferred = $q.defer(); var noteId = $route.current.params.noteId; @@ -63,7 +61,7 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', }); return deferred.promise; - }] + } } }).otherwise({ redirectTo: '/' @@ -78,8 +76,7 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', -}]).run(['$rootScope', '$location', 'NotesModel', 'Config', - function ($rootScope, $location, NotesModel, Config) { +}).run(function ($rootScope, $location, NotesModel, Config) { // get config Config.load(); @@ -102,10 +99,9 @@ config(['$provide', '$routeProvider', 'RestangularProvider', '$httpProvider', $location.path('/'); } }); -}]); +}); -app.controller('AppController', ['$scope', '$location', 'is', - function ($scope, $location, is) { +app.controller('AppController', function ($scope, $location, is) { $scope.is = is; $scope.init = function (lastViewedNote) { @@ -113,10 +109,9 @@ app.controller('AppController', ['$scope', '$location', 'is', $location.path('/notes/' + lastViewedNote); } }; -}]); -app.controller('NoteController', ['$routeParams', '$scope', 'NotesModel', - 'SaveQueue', 'note', 'Config', - function($routeParams, $scope, NotesModel, SaveQueue, note, Config) { +}); +app.controller('NoteController', function($routeParams, $scope, NotesModel, + SaveQueue, note, Config) { NotesModel.updateIfExists(note); @@ -143,11 +138,10 @@ app.controller('NoteController', ['$routeParams', '$scope', 'NotesModel', Config.sync(); }; -}]); +}); // This is available by using ng-controller="NotesController" in your HTML -app.controller('NotesController', ['$routeParams', '$scope', '$location', - 'Restangular', 'NotesModel', - function($routeParams, $scope, $location, Restangular, NotesModel) { +app.controller('NotesController', function($routeParams, $scope, $location, + Restangular, NotesModel) { $scope.route = $routeParams; $scope.notes = NotesModel.getAll(); @@ -171,23 +165,38 @@ app.controller('NotesController', ['$routeParams', '$scope', '$location', note.remove().then(function () { NotesModel.remove(noteId); $scope.$emit('$routeChangeError'); - }); + }); }; -}]); +}); -/** - * Like ng-change only that it does not fire when you type faster than - * 300 ms - */ -app.directive('notesAutofocus', [function () { +app.directive('notesAutofocus', function () { return { restrict: 'A', link: function (scope, element, attributes) { element.focus(); } }; -}]); +}); + +app.directive('notesIsSaving', function ($window) { + return { + restrict: 'A', + scope: { + 'notesIsSaving': '=' + }, + link: function (scope, element, attributes) { + $window.onbeforeunload = function () { + if (scope.notesIsSaving) { + return t('notes', 'Note is currently saving. Leaving ' + + 'the page will delete all changes!'); + } else { + return null; + } + }; + } + }; +}); app.directive('markdown', function () { return { @@ -210,7 +219,7 @@ app.directive('markdown', function () { * Like ng-change only that it does not fire when you type faster than * 300 ms */ -app.directive('notesTimeoutChange', ['$timeout', function ($timeout) { +app.directive('notesTimeoutChange', function ($timeout) { return { restrict: 'A', link: function (scope, element, attributes) { @@ -226,16 +235,16 @@ app.directive('notesTimeoutChange', ['$timeout', function ($timeout) { }); } }; -}]); +}); -app.directive('notesTooltip', [function () { +app.directive('notesTooltip', function () { return { restrict: 'A', link: function (scope, element, attributes) { element.tooltip(); } }; -}]); +}); /** * Binds translated values to scope and hides the element @@ -252,7 +261,7 @@ app.directive('notesTranslate', function () { }; }); -app.factory('Config', ['Restangular', function (Restangular) { +app.factory('Config', function (Restangular) { var Config = function (Restangular) { this._markdown = false; this._Restangular = Restangular; @@ -280,7 +289,7 @@ app.factory('Config', ['Restangular', function (Restangular) { }; return new Config(Restangular); -}]); +}); app.factory('is', function () { return { loading: false @@ -333,7 +342,7 @@ app.factory('NotesModel', function () { return new NotesModel(); }); -app.factory('SaveQueue', ['$q', function($q) { +app.factory('SaveQueue', function($q) { var SaveQueue = function () { this._queue = {}; this._flushLock = false; @@ -383,4 +392,4 @@ app.factory('SaveQueue', ['$q', function($q) { }; return new SaveQueue(); -}]); +}); diff --git a/templates/note.php b/templates/note.php index 5999671f..08ac65cc 100644 --- a/templates/note.php +++ b/templates/note.php @@ -12,7 +12,8 @@