diff --git a/controller/settingscontroller.php b/controller/settingscontroller.php index 2a40c0ce..d5c12776 100644 --- a/controller/settingscontroller.php +++ b/controller/settingscontroller.php @@ -36,6 +36,6 @@ class SettingsController extends Controller * @NoAdminRequired */ public function get() { - return new JSONResponse($this->service->get()); + return new JSONResponse($this->service->getAll()); } } diff --git a/css/notes.css b/css/notes.css index 690ee571..37d1ec7a 100644 --- a/css/notes.css +++ b/css/notes.css @@ -86,6 +86,10 @@ opacity: 1 !important; } +#app-settings-content > div + div { + padding-top: 2ex; +} + .note-search span { background-position: 0 center; background-origin: content-box; diff --git a/js/app/controllers/notessettingscontroller.js b/js/app/controllers/notessettingscontroller.js index b8fd24c7..99252939 100644 --- a/js/app/controllers/notessettingscontroller.js +++ b/js/app/controllers/notessettingscontroller.js @@ -2,6 +2,8 @@ app.controller('NotesSettingsController', function($scope, Restangular, $document) { 'use strict'; + $scope.extensions = ['.txt', '.md']; + Restangular.one('settings').get().then(function(settings) { if(angular.isObject(settings)) { $scope.settings = settings; @@ -17,4 +19,8 @@ app.controller('NotesSettingsController', window.location.reload(true); }); }); + + $document.on('change', '#fileSuffix', function() { + $scope.settings.put(); + }); }); diff --git a/js/public/app.min.js b/js/public/app.min.js index 49ba77ba..721617b8 100644 --- a/js/public/app.min.js +++ b/js/public/app.min.js @@ -1,2 +1,2 @@ -!function(e,n,o,i,r){"use strict";var u=e.module("Notes",["restangular","ngRoute"]).config(["$provide","$routeProvider","RestangularProvider","$httpProvider","$windowProvider",function(e,t,n,i,r){i.defaults.headers.common.requesttoken=o,e.value("Constants",{saveInterval:5e3}),t.when("/notes/:noteId",{templateUrl:"note.html",controller:"NoteController",resolve:{note:["$route","$q","is","Restangular",function(e,t,n,o){var i=t.defer(),r=e.current.params.noteId;return n.loading=!0,o.one("notes",r).get().then(function(e){n.loading=!1,i.resolve(e)},function(){n.loading=!1,i.reject()}),i.promise}]}}).otherwise({redirectTo:"/"});var u=OC.generateUrl("/apps/notes");n.setBaseUrl(u)}]).run(["$rootScope","$location","NotesModel",function(e,t,o){n('link[rel="shortcut icon"]').attr("href",OC.filePath("notes","img","favicon.png")),e.$on("$routeChangeError",function(){var e=o.getAll();if(e.length>0){var n=e.sort(function(e,t){return e.modified>t.modified?1:e.modified0){var n=e.sort(function(e,t){return e.modified>t.modified?1:e.modified\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n/* jshint unused: false */\nvar app = angular.module('Notes', ['restangular', 'ngRoute']).\nconfig(function($provide, $routeProvider, RestangularProvider, $httpProvider,\n $windowProvider) {\n 'use strict';\n\n // Always send the CSRF token by default\n $httpProvider.defaults.headers.common.requesttoken = requestToken;\n\n // you have to use $provide inside the config method to provide a globally\n // shared and injectable object\n $provide.value('Constants', {\n saveInterval: 5*1000 // miliseconds\n });\n\n // define your routes that that load templates into the ng-view\n $routeProvider.when('/notes/:noteId', {\n templateUrl: 'note.html',\n controller: 'NoteController',\n resolve: {\n // $routeParams does not work inside resolve so use $route\n // note is the name of the argument that will be injected into the\n // controller\n /* @ngInject */\n note: function ($route, $q, is, Restangular) {\n\n var deferred = $q.defer();\n var noteId = $route.current.params.noteId;\n is.loading = true;\n\n Restangular.one('notes', noteId).get().then(function (note) {\n is.loading = false;\n deferred.resolve(note);\n }, function () {\n is.loading = false;\n deferred.reject();\n });\n\n return deferred.promise;\n }\n }\n }).otherwise({\n redirectTo: '/'\n });\n\n var baseUrl = OC.generateUrl('/apps/notes');\n RestangularProvider.setBaseUrl(baseUrl);\n\n\n\n}).run(function ($rootScope, $location, NotesModel) {\n 'use strict';\n\n $('link[rel=\"shortcut icon\"]').attr(\n\t\t 'href',\n\t\t OC.filePath('notes', 'img', 'favicon.png')\n );\n\n // handle route errors\n $rootScope.$on('$routeChangeError', function () {\n var notes = NotesModel.getAll();\n\n // route change error should redirect to the latest note if possible\n if (notes.length > 0) {\n var sorted = notes.sort(function (a, b) {\n if(a.modified > b.modified) {\n return 1;\n } else if(a.modified < b.modified) {\n return -1;\n } else {\n return 0;\n }\n });\n\n var note = notes[sorted.length-1];\n $location.path('/notes/' + note.id);\n } else {\n $location.path('/');\n }\n });\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.controller('AppController', function ($scope, $location, is) {\n 'use strict';\n\n $scope.is = is;\n\n $scope.init = function (lastViewedNote,errorMessage) {\n\n\n if(lastViewedNote !== 0) {\n $location.path('/notes/' + lastViewedNote);\n }\n if(errorMessage !==0){\n OC.Notification.showTemporary(errorMessage);\n }\n };\n\n $scope.search = '';\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.controller('NoteController', function($routeParams, $scope, NotesModel,\n SaveQueue, note, debounce,\n $document) {\n 'use strict';\n\n NotesModel.updateIfExists(note);\n\n $scope.note = NotesModel.get($routeParams.noteId);\n\n $scope.isSaving = function () {\n return SaveQueue.isSaving();\n };\n $scope.isManualSaving = function () {\n return SaveQueue.isManualSaving();\n };\n\n $scope.updateTitle = function () {\n $scope.note.title = $scope.note.content.split('\\n')[0] ||\n t('notes', 'New note');\n };\n\n $scope.onEdit = function() {\n var note = $scope.note;\n note.unsaved = true;\n $scope.autoSave(note);\n };\n\n $scope.autoSave = debounce(function(note) {\n SaveQueue.add(note);\n }, 1000);\n\n $scope.manualSave = function() {\n var note = $scope.note;\n note.error = false;\n SaveQueue.addManual(note);\n };\n\n $document.unbind('keypress.notes.save');\n $document.bind('keypress.notes.save', function(event) {\n if(event.ctrlKey || event.metaKey) {\n switch(String.fromCharCode(event.which).toLowerCase()) {\n case 's':\n event.preventDefault();\n $scope.manualSave();\n break;\n }\n }\n });\n\n $scope.toggleDistractionFree = function() {\n function launchIntoFullscreen(element) {\n if(element.requestFullscreen) {\n element.requestFullscreen();\n } else if(element.mozRequestFullScreen) {\n element.mozRequestFullScreen();\n } else if(element.webkitRequestFullscreen) {\n element.webkitRequestFullscreen();\n } else if(element.msRequestFullscreen) {\n element.msRequestFullscreen();\n }\n }\n\n function exitFullscreen() {\n if(document.exitFullscreen) {\n document.exitFullscreen();\n } else if(document.mozCancelFullScreen) {\n document.mozCancelFullScreen();\n } else if(document.webkitExitFullscreen) {\n document.webkitExitFullscreen();\n }\n }\n\n if(document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement) {\n exitFullscreen();\n } else {\n launchIntoFullscreen(document.getElementById('app-content'));\n }\n };\n\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n// This is available by using ng-controller=\"NotesController\" in your HTML\napp.controller('NotesController', function($routeParams, $scope, $location,\n Restangular, NotesModel, $window) {\n 'use strict';\n\n $scope.route = $routeParams;\n $scope.notes = NotesModel.getAll();\n\n var notesResource = Restangular.all('notes');\n\n // initial request for getting all notes\n notesResource.getList().then(function (notes) {\n NotesModel.addAll(notes);\n });\n\n $scope.create = function () {\n notesResource.post().then(function (note) {\n NotesModel.add(note);\n $location.path('/notes/' + note.id);\n });\n };\n\n $scope.delete = function (noteId) {\n var note = NotesModel.get(noteId);\n note.remove().then(function () {\n NotesModel.remove(noteId);\n $scope.$emit('$routeChangeError');\n });\n };\n\n $scope.toggleFavorite = function (noteId) {\n var note = NotesModel.get(noteId);\n note.customPUT({favorite: !note.favorite},\n 'favorite', {}, {}).then(function (favorite) {\n note.favorite = favorite ? true : false;\n });\n };\n\n\n $window.onbeforeunload = function() {\n var notes = NotesModel.getAll();\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n/* jshint unused: false */\nvar app = angular.module('Notes', ['restangular', 'ngRoute']).\nconfig([\"$provide\", \"$routeProvider\", \"RestangularProvider\", \"$httpProvider\", \"$windowProvider\", function($provide, $routeProvider, RestangularProvider, $httpProvider,\n $windowProvider) {\n 'use strict';\n\n // Always send the CSRF token by default\n $httpProvider.defaults.headers.common.requesttoken = requestToken;\n\n // you have to use $provide inside the config method to provide a globally\n // shared and injectable object\n $provide.value('Constants', {\n saveInterval: 5*1000 // miliseconds\n });\n\n // define your routes that that load templates into the ng-view\n $routeProvider.when('/notes/:noteId', {\n templateUrl: 'note.html',\n controller: 'NoteController',\n resolve: {\n // $routeParams does not work inside resolve so use $route\n // note is the name of the argument that will be injected into the\n // controller\n /* @ngInject */\n note: [\"$route\", \"$q\", \"is\", \"Restangular\", function ($route, $q, is, Restangular) {\n\n var deferred = $q.defer();\n var noteId = $route.current.params.noteId;\n is.loading = true;\n\n Restangular.one('notes', noteId).get().then(function (note) {\n is.loading = false;\n deferred.resolve(note);\n }, function () {\n is.loading = false;\n deferred.reject();\n });\n\n return deferred.promise;\n }]\n }\n }).otherwise({\n redirectTo: '/'\n });\n\n var baseUrl = OC.generateUrl('/apps/notes');\n RestangularProvider.setBaseUrl(baseUrl);\n\n\n\n}]).run([\"$rootScope\", \"$location\", \"NotesModel\", function ($rootScope, $location, NotesModel) {\n 'use strict';\n\n $('link[rel=\"shortcut icon\"]').attr(\n\t\t 'href',\n\t\t OC.filePath('notes', 'img', 'favicon.png')\n );\n\n // handle route errors\n $rootScope.$on('$routeChangeError', function () {\n var notes = NotesModel.getAll();\n\n // route change error should redirect to the latest note if possible\n if (notes.length > 0) {\n var sorted = notes.sort(function (a, b) {\n if(a.modified > b.modified) {\n return 1;\n } else if(a.modified < b.modified) {\n return -1;\n } else {\n return 0;\n }\n });\n\n var note = notes[sorted.length-1];\n $location.path('/notes/' + note.id);\n } else {\n $location.path('/');\n }\n });\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.controller('AppController', [\"$scope\", \"$location\", \"is\", function ($scope, $location, is) {\n 'use strict';\n\n $scope.is = is;\n\n $scope.init = function (lastViewedNote,errorMessage) {\n\n\n if(lastViewedNote !== 0) {\n $location.path('/notes/' + lastViewedNote);\n }\n if(errorMessage !==0){\n OC.Notification.showTemporary(errorMessage);\n }\n };\n\n $scope.search = '';\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.controller('NoteController', [\"$routeParams\", \"$scope\", \"NotesModel\", \"SaveQueue\", \"note\", \"debounce\", \"$document\", function($routeParams, $scope, NotesModel,\n SaveQueue, note, debounce,\n $document) {\n 'use strict';\n\n NotesModel.updateIfExists(note);\n\n $scope.note = NotesModel.get($routeParams.noteId);\n\n $scope.isSaving = function () {\n return SaveQueue.isSaving();\n };\n $scope.isManualSaving = function () {\n return SaveQueue.isManualSaving();\n };\n\n $scope.updateTitle = function () {\n $scope.note.title = $scope.note.content.split('\\n')[0] ||\n t('notes', 'New note');\n };\n\n $scope.onEdit = function() {\n var note = $scope.note;\n note.unsaved = true;\n $scope.autoSave(note);\n };\n\n $scope.autoSave = debounce(function(note) {\n SaveQueue.add(note);\n }, 1000);\n\n $scope.manualSave = function() {\n var note = $scope.note;\n note.error = false;\n SaveQueue.addManual(note);\n };\n\n $document.unbind('keypress.notes.save');\n $document.bind('keypress.notes.save', function(event) {\n if(event.ctrlKey || event.metaKey) {\n switch(String.fromCharCode(event.which).toLowerCase()) {\n case 's':\n event.preventDefault();\n $scope.manualSave();\n break;\n }\n }\n });\n\n $scope.toggleDistractionFree = function() {\n function launchIntoFullscreen(element) {\n if(element.requestFullscreen) {\n element.requestFullscreen();\n } else if(element.mozRequestFullScreen) {\n element.mozRequestFullScreen();\n } else if(element.webkitRequestFullscreen) {\n element.webkitRequestFullscreen();\n } else if(element.msRequestFullscreen) {\n element.msRequestFullscreen();\n }\n }\n\n function exitFullscreen() {\n if(document.exitFullscreen) {\n document.exitFullscreen();\n } else if(document.mozCancelFullScreen) {\n document.mozCancelFullScreen();\n } else if(document.webkitExitFullscreen) {\n document.webkitExitFullscreen();\n }\n }\n\n if(document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement) {\n exitFullscreen();\n } else {\n launchIntoFullscreen(document.getElementById('app-content'));\n }\n };\n\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n// This is available by using ng-controller=\"NotesController\" in your HTML\napp.controller('NotesController', [\"$routeParams\", \"$scope\", \"$location\", \"Restangular\", \"NotesModel\", \"$window\", function($routeParams, $scope, $location,\n Restangular, NotesModel, $window) {\n 'use strict';\n\n $scope.route = $routeParams;\n $scope.notes = NotesModel.getAll();\n\n var notesResource = Restangular.all('notes');\n\n // initial request for getting all notes\n notesResource.getList().then(function (notes) {\n NotesModel.addAll(notes);\n });\n\n $scope.create = function () {\n notesResource.post().then(function (note) {\n NotesModel.add(note);\n $location.path('/notes/' + note.id);\n });\n };\n\n $scope.delete = function (noteId) {\n var note = NotesModel.get(noteId);\n note.remove().then(function () {\n NotesModel.remove(noteId);\n $scope.$emit('$routeChangeError');\n });\n };\n\n $scope.toggleFavorite = function (noteId) {\n var note = NotesModel.get(noteId);\n note.customPUT({favorite: !note.favorite},\n 'favorite', {}, {}).then(function (favorite) {\n note.favorite = favorite ? true : false;\n });\n };\n\n\n $window.onbeforeunload = function() {\n var notes = NotesModel.getAll();\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.directive('notesAutofocus', function () {\n 'use strict';\n return {\n restrict: 'A',\n link: function (scope, element) {\n element.focus();\n }\n };\n});\n\n/*global SimpleMDE*/\napp.directive('editor', ['$timeout',\n 'urlFinder',\n function ($timeout, urlFinder) {\n\t'use strict';\n\treturn {\n\t\trestrict: 'A',\n\t\tlink: function(scope, element) {\n\n\t\t\tvar simplemde = new SimpleMDE({\n\t\t\t\telement: element[0],\n\t\t\t\tspellChecker: false,\n\t\t\t\tautoDownloadFontAwesome: false,\n\t\t\t\ttoolbar: false,\n\t\t\t\tstatus: false,\n\t\t\t\tforceSync: true\n\t\t\t});\n\t\t\tvar editorElement = $(simplemde.codemirror.getWrapperElement());\n\n\t\t\tsimplemde.value(scope.note.content);\n\n\t\t\tsimplemde.codemirror.on('change', function() {\n\t\t\t\t$timeout(function() {\n\t\t\t\t\tscope.$apply(function () {\n\t\t\t\t\t\tscope.note.content = simplemde.value();\n\t\t\t\t\t\tscope.onEdit();\n\t\t\t\t\t\tscope.updateTitle();\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\n\t\t\teditorElement.on('click', '.cm-link, .cm-url', function(event) {\n\t\t\t\tif(event.ctrlKey) {\n\t\t\t\t\tvar url = urlFinder(this);\n\t\t\t\t\tif(angular.isDefined(url)) {\n\t\t\t\t\t\twindow.open(url, '_blank');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.directive('notesTooltip', function () {\n 'use strict';\n\n return {\n restrict: 'A',\n link: function (scope, element) {\n element.tooltip({'container': 'body'});\n\n element.on('$destroy', function() {\n element.tooltip('hide');\n });\n }\n };\n});\n\n/**\n * filter by multiple words (AND operation)\n */\napp.filter('and', ['$filter', function ($filter) {\n\t'use strict';\n\treturn function (items, searchString) {\n\t\tvar searchValues = searchString.split(' ');\n\t\tvar filtered = items;\n\t\tfor(var i in searchValues) {\n\t\t\tfiltered = $filter('filter')(filtered, searchValues[i]);\n\t\t}\n\t\treturn filtered;\n\t};\n}]);\n\n/**\n * removes whitespaces and leading #\n */\napp.filter('noteTitle', function () {\n\t'use strict';\n\treturn function (value) {\n \tvalue = value.split('\\n')[0] || 'newNote';\n\t\treturn value.trim().replace(/^#+/g, '');\n\t};\n});\n\napp.filter('wordCount', function () {\n\t'use strict';\n\treturn function (value) {\n\t\tif (value && (typeof value === 'string')) {\n\t\t\tvar wordCount = value.split(/\\s+/).filter(\n\t\t\t\t// only count words containing\n\t\t\t\t// at least one alphanumeric character\n\t\t\t\tfunction(value) {\n\t\t\t\t\treturn value.search(/[A-Za-z0-9]/) !== -1;\n\t\t\t\t}\n\t\t\t).length;\n\t\t\treturn window.n('notes', '%n word', '%n words', wordCount);\n\t\t} else {\n\t\t\treturn 0;\n\t\t}\n\t};\n});\n\n/**\n * Copyright (c) 2016, Hendrik Leppelsack\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('debounce', ['$timeout', function($timeout) {\n\t'use strict';\n\n\treturn function debounce(func, delay) {\n\t\tvar timeout;\n\n\t\treturn function() {\n\t\t\tvar context = this, args = arguments;\n\n\t\t\tif(timeout) {\n\t\t\t\t$timeout.cancel(timeout);\n\t\t\t}\n\t\t\ttimeout = $timeout(function() {\n\t\t\t\tfunc.apply(context, args);\n\t\t\t}, delay);\n\t\t};\n\t};\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('is', function () {\n 'use strict';\n\n return {\n loading: false\n };\n});\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n// take care of fileconflicts by appending a number\napp.factory('NotesModel', function () {\n 'use strict';\n\n var NotesModel = function () {\n this.notes = [];\n this.notesIds = {};\n };\n\n NotesModel.prototype = {\n addAll: function (notes) {\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('SaveQueue', [\"$q\", function($q) {\n 'use strict';\n\n var SaveQueue = function () {\n this._queue = {};\n this._flushLock = false;\n this._manualSaveActive = false;\n };\n\n SaveQueue.prototype = {\n add: function (note) {\n this._queue[note.id] = note;\n this._flush();\n },\n addManual: function (note) {\n this._manualSaveActive = true;\n this.add(note);\n },\n _flush: function () {\n // if there are no changes dont execute the requests\n var keys = Object.keys(this._queue);\n if(keys.length === 0 || this._flushLock) {\n return;\n } else {\n this._flushLock = true;\n }\n\n var self = this;\n var requests = [];\n\n // iterate over updated objects and run an update request for\n // each one of them\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.directive('notesAutofocus', function () {\n 'use strict';\n return {\n restrict: 'A',\n link: function (scope, element) {\n element.focus();\n }\n };\n});\n","/*global SimpleMDE*/\napp.directive('editor', ['$timeout',\n 'urlFinder',\n function ($timeout, urlFinder) {\n\t'use strict';\n\treturn {\n\t\trestrict: 'A',\n\t\tlink: function(scope, element) {\n\n\t\t\tvar simplemde = new SimpleMDE({\n\t\t\t\telement: element[0],\n\t\t\t\tspellChecker: false,\n\t\t\t\tautoDownloadFontAwesome: false,\n\t\t\t\ttoolbar: false,\n\t\t\t\tstatus: false,\n\t\t\t\tforceSync: true\n\t\t\t});\n\t\t\tvar editorElement = $(simplemde.codemirror.getWrapperElement());\n\n\t\t\tsimplemde.value(scope.note.content);\n\n\t\t\tsimplemde.codemirror.on('change', function() {\n\t\t\t\t$timeout(function() {\n\t\t\t\t\tscope.$apply(function () {\n\t\t\t\t\t\tscope.note.content = simplemde.value();\n\t\t\t\t\t\tscope.onEdit();\n\t\t\t\t\t\tscope.updateTitle();\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\n\t\t\teditorElement.on('click', '.cm-link, .cm-url', function(event) {\n\t\t\t\tif(event.ctrlKey) {\n\t\t\t\t\tvar url = urlFinder(this);\n\t\t\t\t\tif(angular.isDefined(url)) {\n\t\t\t\t\t\twindow.open(url, '_blank');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}]);\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.directive('notesTooltip', function () {\n 'use strict';\n\n return {\n restrict: 'A',\n link: function (scope, element) {\n element.tooltip({'container': 'body'});\n\n element.on('$destroy', function() {\n element.tooltip('hide');\n });\n }\n };\n});\n","/**\n * filter by multiple words (AND operation)\n */\napp.filter('and', ['$filter', function ($filter) {\n\t'use strict';\n\treturn function (items, searchString) {\n\t\tvar searchValues = searchString.split(' ');\n\t\tvar filtered = items;\n\t\tfor(var i in searchValues) {\n\t\t\tfiltered = $filter('filter')(filtered, searchValues[i]);\n\t\t}\n\t\treturn filtered;\n\t};\n}]);\n","/**\n * removes whitespaces and leading #\n */\napp.filter('noteTitle', function () {\n\t'use strict';\n\treturn function (value) {\n \tvalue = value.split('\\n')[0] || 'newNote';\n\t\treturn value.trim().replace(/^#+/g, '');\n\t};\n});\n","app.filter('wordCount', function () {\n\t'use strict';\n\treturn function (value) {\n\t\tif (value && (typeof value === 'string')) {\n\t\t\tvar wordCount = value.split(/\\s+/).filter(\n\t\t\t\t// only count words containing\n\t\t\t\t// at least one alphanumeric character\n\t\t\t\tfunction(value) {\n\t\t\t\t\treturn value.search(/[A-Za-z0-9]/) !== -1;\n\t\t\t\t}\n\t\t\t).length;\n\t\t\treturn window.n('notes', '%n word', '%n words', wordCount);\n\t\t} else {\n\t\t\treturn 0;\n\t\t}\n\t};\n});\n","/**\n * Copyright (c) 2016, Hendrik Leppelsack\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('debounce', ['$timeout', function($timeout) {\n\t'use strict';\n\n\treturn function debounce(func, delay) {\n\t\tvar timeout;\n\n\t\treturn function() {\n\t\t\tvar context = this, args = arguments;\n\n\t\t\tif(timeout) {\n\t\t\t\t$timeout.cancel(timeout);\n\t\t\t}\n\t\t\ttimeout = $timeout(function() {\n\t\t\t\tfunc.apply(context, args);\n\t\t\t}, delay);\n\t\t};\n\t};\n}]);\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('is', function () {\n 'use strict';\n\n return {\n loading: false\n };\n});","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n// take care of fileconflicts by appending a number\napp.factory('NotesModel', function () {\n 'use strict';\n\n var NotesModel = function () {\n this.notes = [];\n this.notesIds = {};\n };\n\n NotesModel.prototype = {\n addAll: function (notes) {\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('SaveQueue', function($q) {\n 'use strict';\n\n var SaveQueue = function () {\n this._queue = {};\n this._flushLock = false;\n this._manualSaveActive = false;\n };\n\n SaveQueue.prototype = {\n add: function (note) {\n this._queue[note.id] = note;\n this._flush();\n },\n addManual: function (note) {\n this._manualSaveActive = true;\n this.add(note);\n },\n _flush: function () {\n // if there are no changes dont execute the requests\n var keys = Object.keys(this._queue);\n if(keys.length === 0 || this._flushLock) {\n return;\n } else {\n this._flushLock = true;\n }\n\n var self = this;\n var requests = [];\n\n // iterate over updated objects and run an update request for\n // each one of them\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n/* jshint unused: false */\nvar app = angular.module('Notes', ['restangular', 'ngRoute']).\nconfig(function($provide, $routeProvider, RestangularProvider, $httpProvider,\n $windowProvider) {\n 'use strict';\n\n // Always send the CSRF token by default\n $httpProvider.defaults.headers.common.requesttoken = requestToken;\n\n // you have to use $provide inside the config method to provide a globally\n // shared and injectable object\n $provide.value('Constants', {\n saveInterval: 5*1000 // miliseconds\n });\n\n // define your routes that that load templates into the ng-view\n $routeProvider.when('/notes/:noteId', {\n templateUrl: 'note.html',\n controller: 'NoteController',\n resolve: {\n // $routeParams does not work inside resolve so use $route\n // note is the name of the argument that will be injected into the\n // controller\n /* @ngInject */\n note: function ($route, $q, is, Restangular) {\n\n var deferred = $q.defer();\n var noteId = $route.current.params.noteId;\n is.loading = true;\n\n Restangular.one('notes', noteId).get().then(function (note) {\n is.loading = false;\n deferred.resolve(note);\n }, function () {\n is.loading = false;\n deferred.reject();\n });\n\n return deferred.promise;\n }\n }\n }).otherwise({\n redirectTo: '/'\n });\n\n var baseUrl = OC.generateUrl('/apps/notes');\n RestangularProvider.setBaseUrl(baseUrl);\n\n\n\n}).run(function ($rootScope, $location, NotesModel) {\n 'use strict';\n\n $('link[rel=\"shortcut icon\"]').attr(\n\t\t 'href',\n\t\t OC.filePath('notes', 'img', 'favicon.png')\n );\n\n // handle route errors\n $rootScope.$on('$routeChangeError', function () {\n var notes = NotesModel.getAll();\n\n // route change error should redirect to the latest note if possible\n if (notes.length > 0) {\n var sorted = notes.sort(function (a, b) {\n if(a.modified > b.modified) {\n return 1;\n } else if(a.modified < b.modified) {\n return -1;\n } else {\n return 0;\n }\n });\n\n var note = notes[sorted.length-1];\n $location.path('/notes/' + note.id);\n } else {\n $location.path('/');\n }\n });\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.controller('AppController', function ($scope, $location, is) {\n 'use strict';\n\n $scope.is = is;\n\n $scope.init = function (lastViewedNote,errorMessage) {\n\n\n if(lastViewedNote !== 0) {\n $location.path('/notes/' + lastViewedNote);\n }\n if(errorMessage !==0){\n OC.Notification.showTemporary(errorMessage);\n }\n };\n\n $scope.search = '';\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.controller('NoteController', function($routeParams, $scope, NotesModel,\n SaveQueue, note, debounce,\n $document) {\n 'use strict';\n\n NotesModel.updateIfExists(note);\n\n $scope.note = NotesModel.get($routeParams.noteId);\n\n $scope.isSaving = function () {\n return SaveQueue.isSaving();\n };\n $scope.isManualSaving = function () {\n return SaveQueue.isManualSaving();\n };\n\n $scope.updateTitle = function () {\n $scope.note.title = $scope.note.content.split('\\n')[0] ||\n t('notes', 'New note');\n };\n\n $scope.onEdit = function() {\n var note = $scope.note;\n note.unsaved = true;\n $scope.autoSave(note);\n };\n\n $scope.autoSave = debounce(function(note) {\n SaveQueue.add(note);\n }, 1000);\n\n $scope.manualSave = function() {\n var note = $scope.note;\n note.error = false;\n SaveQueue.addManual(note);\n };\n\n $document.unbind('keypress.notes.save');\n $document.bind('keypress.notes.save', function(event) {\n if(event.ctrlKey || event.metaKey) {\n switch(String.fromCharCode(event.which).toLowerCase()) {\n case 's':\n event.preventDefault();\n $scope.manualSave();\n break;\n }\n }\n });\n\n $scope.toggleDistractionFree = function() {\n function launchIntoFullscreen(element) {\n if(element.requestFullscreen) {\n element.requestFullscreen();\n } else if(element.mozRequestFullScreen) {\n element.mozRequestFullScreen();\n } else if(element.webkitRequestFullscreen) {\n element.webkitRequestFullscreen();\n } else if(element.msRequestFullscreen) {\n element.msRequestFullscreen();\n }\n }\n\n function exitFullscreen() {\n if(document.exitFullscreen) {\n document.exitFullscreen();\n } else if(document.mozCancelFullScreen) {\n document.mozCancelFullScreen();\n } else if(document.webkitExitFullscreen) {\n document.webkitExitFullscreen();\n }\n }\n\n if(document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement) {\n exitFullscreen();\n } else {\n launchIntoFullscreen(document.getElementById('app-content'));\n }\n };\n\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n// This is available by using ng-controller=\"NotesController\" in your HTML\napp.controller('NotesController', function($routeParams, $scope, $location,\n Restangular, NotesModel, $window) {\n 'use strict';\n\n $scope.route = $routeParams;\n $scope.notes = NotesModel.getAll();\n\n var notesResource = Restangular.all('notes');\n\n // initial request for getting all notes\n notesResource.getList().then(function (notes) {\n NotesModel.addAll(notes);\n });\n\n $scope.create = function () {\n notesResource.post().then(function (note) {\n NotesModel.add(note);\n $location.path('/notes/' + note.id);\n });\n };\n\n $scope.delete = function (noteId) {\n var note = NotesModel.get(noteId);\n note.remove().then(function () {\n NotesModel.remove(noteId);\n $scope.$emit('$routeChangeError');\n });\n };\n\n $scope.toggleFavorite = function (noteId) {\n var note = NotesModel.get(noteId);\n note.customPUT({favorite: !note.favorite},\n 'favorite', {}, {}).then(function (favorite) {\n note.favorite = favorite ? true : false;\n });\n };\n\n\n $window.onbeforeunload = function() {\n var notes = NotesModel.getAll();\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n/* jshint unused: false */\nvar app = angular.module('Notes', ['restangular', 'ngRoute']).\nconfig([\"$provide\", \"$routeProvider\", \"RestangularProvider\", \"$httpProvider\", \"$windowProvider\", function($provide, $routeProvider, RestangularProvider, $httpProvider,\n $windowProvider) {\n 'use strict';\n\n // Always send the CSRF token by default\n $httpProvider.defaults.headers.common.requesttoken = requestToken;\n\n // you have to use $provide inside the config method to provide a globally\n // shared and injectable object\n $provide.value('Constants', {\n saveInterval: 5*1000 // miliseconds\n });\n\n // define your routes that that load templates into the ng-view\n $routeProvider.when('/notes/:noteId', {\n templateUrl: 'note.html',\n controller: 'NoteController',\n resolve: {\n // $routeParams does not work inside resolve so use $route\n // note is the name of the argument that will be injected into the\n // controller\n /* @ngInject */\n note: [\"$route\", \"$q\", \"is\", \"Restangular\", function ($route, $q, is, Restangular) {\n\n var deferred = $q.defer();\n var noteId = $route.current.params.noteId;\n is.loading = true;\n\n Restangular.one('notes', noteId).get().then(function (note) {\n is.loading = false;\n deferred.resolve(note);\n }, function () {\n is.loading = false;\n deferred.reject();\n });\n\n return deferred.promise;\n }]\n }\n }).otherwise({\n redirectTo: '/'\n });\n\n var baseUrl = OC.generateUrl('/apps/notes');\n RestangularProvider.setBaseUrl(baseUrl);\n\n\n\n}]).run([\"$rootScope\", \"$location\", \"NotesModel\", function ($rootScope, $location, NotesModel) {\n 'use strict';\n\n $('link[rel=\"shortcut icon\"]').attr(\n\t\t 'href',\n\t\t OC.filePath('notes', 'img', 'favicon.png')\n );\n\n // handle route errors\n $rootScope.$on('$routeChangeError', function () {\n var notes = NotesModel.getAll();\n\n // route change error should redirect to the latest note if possible\n if (notes.length > 0) {\n var sorted = notes.sort(function (a, b) {\n if(a.modified > b.modified) {\n return 1;\n } else if(a.modified < b.modified) {\n return -1;\n } else {\n return 0;\n }\n });\n\n var note = notes[sorted.length-1];\n $location.path('/notes/' + note.id);\n } else {\n $location.path('/');\n }\n });\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.controller('AppController', [\"$scope\", \"$location\", \"is\", function ($scope, $location, is) {\n 'use strict';\n\n $scope.is = is;\n\n $scope.init = function (lastViewedNote,errorMessage) {\n\n\n if(lastViewedNote !== 0) {\n $location.path('/notes/' + lastViewedNote);\n }\n if(errorMessage !==0){\n OC.Notification.showTemporary(errorMessage);\n }\n };\n\n $scope.search = '';\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.controller('NoteController', [\"$routeParams\", \"$scope\", \"NotesModel\", \"SaveQueue\", \"note\", \"debounce\", \"$document\", function($routeParams, $scope, NotesModel,\n SaveQueue, note, debounce,\n $document) {\n 'use strict';\n\n NotesModel.updateIfExists(note);\n\n $scope.note = NotesModel.get($routeParams.noteId);\n\n $scope.isSaving = function () {\n return SaveQueue.isSaving();\n };\n $scope.isManualSaving = function () {\n return SaveQueue.isManualSaving();\n };\n\n $scope.updateTitle = function () {\n $scope.note.title = $scope.note.content.split('\\n')[0] ||\n t('notes', 'New note');\n };\n\n $scope.onEdit = function() {\n var note = $scope.note;\n note.unsaved = true;\n $scope.autoSave(note);\n };\n\n $scope.autoSave = debounce(function(note) {\n SaveQueue.add(note);\n }, 1000);\n\n $scope.manualSave = function() {\n var note = $scope.note;\n note.error = false;\n SaveQueue.addManual(note);\n };\n\n $document.unbind('keypress.notes.save');\n $document.bind('keypress.notes.save', function(event) {\n if(event.ctrlKey || event.metaKey) {\n switch(String.fromCharCode(event.which).toLowerCase()) {\n case 's':\n event.preventDefault();\n $scope.manualSave();\n break;\n }\n }\n });\n\n $scope.toggleDistractionFree = function() {\n function launchIntoFullscreen(element) {\n if(element.requestFullscreen) {\n element.requestFullscreen();\n } else if(element.mozRequestFullScreen) {\n element.mozRequestFullScreen();\n } else if(element.webkitRequestFullscreen) {\n element.webkitRequestFullscreen();\n } else if(element.msRequestFullscreen) {\n element.msRequestFullscreen();\n }\n }\n\n function exitFullscreen() {\n if(document.exitFullscreen) {\n document.exitFullscreen();\n } else if(document.mozCancelFullScreen) {\n document.mozCancelFullScreen();\n } else if(document.webkitExitFullscreen) {\n document.webkitExitFullscreen();\n }\n }\n\n if(document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement) {\n exitFullscreen();\n } else {\n launchIntoFullscreen(document.getElementById('app-content'));\n }\n };\n\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n// This is available by using ng-controller=\"NotesController\" in your HTML\napp.controller('NotesController', [\"$routeParams\", \"$scope\", \"$location\", \"Restangular\", \"NotesModel\", \"$window\", function($routeParams, $scope, $location,\n Restangular, NotesModel, $window) {\n 'use strict';\n\n $scope.route = $routeParams;\n $scope.notes = NotesModel.getAll();\n\n var notesResource = Restangular.all('notes');\n\n // initial request for getting all notes\n notesResource.getList().then(function (notes) {\n NotesModel.addAll(notes);\n });\n\n $scope.create = function () {\n notesResource.post().then(function (note) {\n NotesModel.add(note);\n $location.path('/notes/' + note.id);\n });\n };\n\n $scope.delete = function (noteId) {\n var note = NotesModel.get(noteId);\n note.remove().then(function () {\n NotesModel.remove(noteId);\n $scope.$emit('$routeChangeError');\n });\n };\n\n $scope.toggleFavorite = function (noteId) {\n var note = NotesModel.get(noteId);\n note.customPUT({favorite: !note.favorite},\n 'favorite', {}, {}).then(function (favorite) {\n note.favorite = favorite ? true : false;\n });\n };\n\n\n $window.onbeforeunload = function() {\n var notes = NotesModel.getAll();\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.directive('notesAutofocus', function () {\n 'use strict';\n return {\n restrict: 'A',\n link: function (scope, element) {\n element.focus();\n }\n };\n});\n\n/*global SimpleMDE*/\napp.directive('editor', ['$timeout',\n 'urlFinder',\n function ($timeout, urlFinder) {\n\t'use strict';\n\treturn {\n\t\trestrict: 'A',\n\t\tlink: function(scope, element) {\n\n\t\t\tvar simplemde = new SimpleMDE({\n\t\t\t\telement: element[0],\n\t\t\t\tspellChecker: false,\n\t\t\t\tautoDownloadFontAwesome: false,\n\t\t\t\ttoolbar: false,\n\t\t\t\tstatus: false,\n\t\t\t\tforceSync: true\n\t\t\t});\n\t\t\tvar editorElement = $(simplemde.codemirror.getWrapperElement());\n\n\t\t\tsimplemde.value(scope.note.content);\n\n\t\t\tsimplemde.codemirror.on('change', function() {\n\t\t\t\t$timeout(function() {\n\t\t\t\t\tscope.$apply(function () {\n\t\t\t\t\t\tscope.note.content = simplemde.value();\n\t\t\t\t\t\tscope.onEdit();\n\t\t\t\t\t\tscope.updateTitle();\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\n\t\t\teditorElement.on('click', '.cm-link, .cm-url', function(event) {\n\t\t\t\tif(event.ctrlKey) {\n\t\t\t\t\tvar url = urlFinder(this);\n\t\t\t\t\tif(angular.isDefined(url)) {\n\t\t\t\t\t\twindow.open(url, '_blank');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.directive('notesTooltip', function () {\n 'use strict';\n\n return {\n restrict: 'A',\n link: function (scope, element) {\n element.tooltip({'container': 'body'});\n\n element.on('$destroy', function() {\n element.tooltip('hide');\n });\n }\n };\n});\n\n/**\n * filter by multiple words (AND operation)\n */\napp.filter('and', ['$filter', function ($filter) {\n\t'use strict';\n\treturn function (items, searchString) {\n\t\tvar searchValues = searchString.split(' ');\n\t\tvar filtered = items;\n\t\tfor(var i in searchValues) {\n\t\t\tfiltered = $filter('filter')(filtered, searchValues[i]);\n\t\t}\n\t\treturn filtered;\n\t};\n}]);\n\n/**\n * removes whitespaces and leading #\n */\napp.filter('noteTitle', function () {\n\t'use strict';\n\treturn function (value) {\n \tvalue = value.split('\\n')[0] || 'newNote';\n\t\treturn value.trim().replace(/^#+/g, '');\n\t};\n});\n\napp.filter('wordCount', function () {\n\t'use strict';\n\treturn function (value) {\n\t\tif (value && (typeof value === 'string')) {\n\t\t\tvar wordCount = value.split(/\\s+/).filter(\n\t\t\t\t// only count words containing\n\t\t\t\t// at least one alphanumeric character\n\t\t\t\tfunction(value) {\n\t\t\t\t\treturn value.search(/[A-Za-z0-9]/) !== -1;\n\t\t\t\t}\n\t\t\t).length;\n\t\t\treturn window.n('notes', '%n word', '%n words', wordCount);\n\t\t} else {\n\t\t\treturn 0;\n\t\t}\n\t};\n});\n\n/**\n * Copyright (c) 2016, Hendrik Leppelsack\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('debounce', ['$timeout', function($timeout) {\n\t'use strict';\n\n\treturn function debounce(func, delay) {\n\t\tvar timeout;\n\n\t\treturn function() {\n\t\t\tvar context = this, args = arguments;\n\n\t\t\tif(timeout) {\n\t\t\t\t$timeout.cancel(timeout);\n\t\t\t}\n\t\t\ttimeout = $timeout(function() {\n\t\t\t\tfunc.apply(context, args);\n\t\t\t}, delay);\n\t\t};\n\t};\n}]);\n\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('is', function () {\n 'use strict';\n\n return {\n loading: false\n };\n});\n/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n// take care of fileconflicts by appending a number\napp.factory('NotesModel', function () {\n 'use strict';\n\n var NotesModel = function () {\n this.notes = [];\n this.notesIds = {};\n };\n\n NotesModel.prototype = {\n addAll: function (notes) {\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('SaveQueue', [\"$q\", function($q) {\n 'use strict';\n\n var SaveQueue = function () {\n this._queue = {};\n this._flushLock = false;\n this._manualSaveActive = false;\n };\n\n SaveQueue.prototype = {\n add: function (note) {\n this._queue[note.id] = note;\n this._flush();\n },\n addManual: function (note) {\n this._manualSaveActive = true;\n this.add(note);\n },\n _flush: function () {\n // if there are no changes dont execute the requests\n var keys = Object.keys(this._queue);\n if(keys.length === 0 || this._flushLock) {\n return;\n } else {\n this._flushLock = true;\n }\n\n var self = this;\n var requests = [];\n\n // iterate over updated objects and run an update request for\n // each one of them\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.directive('notesAutofocus', function () {\n 'use strict';\n return {\n restrict: 'A',\n link: function (scope, element) {\n element.focus();\n }\n };\n});\n","/*global SimpleMDE*/\napp.directive('editor', ['$timeout',\n 'urlFinder',\n function ($timeout, urlFinder) {\n\t'use strict';\n\treturn {\n\t\trestrict: 'A',\n\t\tlink: function(scope, element) {\n\n\t\t\tvar simplemde = new SimpleMDE({\n\t\t\t\telement: element[0],\n\t\t\t\tspellChecker: false,\n\t\t\t\tautoDownloadFontAwesome: false,\n\t\t\t\ttoolbar: false,\n\t\t\t\tstatus: false,\n\t\t\t\tforceSync: true\n\t\t\t});\n\t\t\tvar editorElement = $(simplemde.codemirror.getWrapperElement());\n\n\t\t\tsimplemde.value(scope.note.content);\n\n\t\t\tsimplemde.codemirror.on('change', function() {\n\t\t\t\t$timeout(function() {\n\t\t\t\t\tscope.$apply(function () {\n\t\t\t\t\t\tscope.note.content = simplemde.value();\n\t\t\t\t\t\tscope.onEdit();\n\t\t\t\t\t\tscope.updateTitle();\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\n\t\t\teditorElement.on('click', '.cm-link, .cm-url', function(event) {\n\t\t\t\tif(event.ctrlKey) {\n\t\t\t\t\tvar url = urlFinder(this);\n\t\t\t\t\tif(angular.isDefined(url)) {\n\t\t\t\t\t\twindow.open(url, '_blank');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}]);\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.directive('notesTooltip', function () {\n 'use strict';\n\n return {\n restrict: 'A',\n link: function (scope, element) {\n element.tooltip({'container': 'body'});\n\n element.on('$destroy', function() {\n element.tooltip('hide');\n });\n }\n };\n});\n","/**\n * filter by multiple words (AND operation)\n */\napp.filter('and', ['$filter', function ($filter) {\n\t'use strict';\n\treturn function (items, searchString) {\n\t\tvar searchValues = searchString.split(' ');\n\t\tvar filtered = items;\n\t\tfor(var i in searchValues) {\n\t\t\tfiltered = $filter('filter')(filtered, searchValues[i]);\n\t\t}\n\t\treturn filtered;\n\t};\n}]);\n","/**\n * removes whitespaces and leading #\n */\napp.filter('noteTitle', function () {\n\t'use strict';\n\treturn function (value) {\n \tvalue = value.split('\\n')[0] || 'newNote';\n\t\treturn value.trim().replace(/^#+/g, '');\n\t};\n});\n","app.filter('wordCount', function () {\n\t'use strict';\n\treturn function (value) {\n\t\tif (value && (typeof value === 'string')) {\n\t\t\tvar wordCount = value.split(/\\s+/).filter(\n\t\t\t\t// only count words containing\n\t\t\t\t// at least one alphanumeric character\n\t\t\t\tfunction(value) {\n\t\t\t\t\treturn value.search(/[A-Za-z0-9]/) !== -1;\n\t\t\t\t}\n\t\t\t).length;\n\t\t\treturn window.n('notes', '%n word', '%n words', wordCount);\n\t\t} else {\n\t\t\treturn 0;\n\t\t}\n\t};\n});\n","/**\n * Copyright (c) 2016, Hendrik Leppelsack\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('debounce', ['$timeout', function($timeout) {\n\t'use strict';\n\n\treturn function debounce(func, delay) {\n\t\tvar timeout;\n\n\t\treturn function() {\n\t\t\tvar context = this, args = arguments;\n\n\t\t\tif(timeout) {\n\t\t\t\t$timeout.cancel(timeout);\n\t\t\t}\n\t\t\ttimeout = $timeout(function() {\n\t\t\t\tfunc.apply(context, args);\n\t\t\t}, delay);\n\t\t};\n\t};\n}]);\n","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('is', function () {\n 'use strict';\n\n return {\n loading: false\n };\n});","/**\n * Copyright (c) 2013, Bernhard Posselt \n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n// take care of fileconflicts by appending a number\napp.factory('NotesModel', function () {\n 'use strict';\n\n var NotesModel = function () {\n this.notes = [];\n this.notesIds = {};\n };\n\n NotesModel.prototype = {\n addAll: function (notes) {\n for(var i=0; i\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\napp.factory('SaveQueue', function($q) {\n 'use strict';\n\n var SaveQueue = function () {\n this._queue = {};\n this._flushLock = false;\n this._manualSaveActive = false;\n };\n\n SaveQueue.prototype = {\n add: function (note) {\n this._queue[note.id] = note;\n this._flush();\n },\n addManual: function (note) {\n this._manualSaveActive = true;\n this.add(note);\n },\n _flush: function () {\n // if there are no changes dont execute the requests\n var keys = Object.keys(this._queue);\n if(keys.length === 0 || this._flushLock) {\n return;\n } else {\n this._flushLock = true;\n }\n\n var self = this;\n var requests = [];\n\n // iterate over updated objects and run an update request for\n // each one of them\n for(var i=0; igenerateFileName($folder, $title, "txt", -1); + $path = $this->generateFileName($folder, $title, $this->settings->get('fileSuffix'), -1); $file = $folder->newFile($path); return $this->getNote($file, $folder); @@ -160,7 +160,7 @@ class NotesService { // this can fail if access rights are not sufficient or category name is illegal try { $currentFilePath = $this->root->getFullPath($file->getPath()); - $fileExtension = pathinfo($file->getName(), PATHINFO_EXTENSION); + $fileSuffix = '.' . pathinfo($file->getName(), PATHINFO_EXTENSION); // detect (new) folder path based on category name if($category===null) { @@ -178,7 +178,7 @@ class NotesService { } // assemble new file path - $newFilePath = $basePath . '/' . $this->generateFileName($folder, $title, $fileExtension, $id); + $newFilePath = $basePath . '/' . $this->generateFileName($folder, $title, $fileSuffix, $id); // if the current path is not the new path, the file has to be renamed if($currentFilePath !== $newFilePath) { @@ -344,15 +344,15 @@ class NotesService { * * @param Folder $folder a folder to the notes directory * @param string $title the filename which should be used - * @param string $extension the extension which should be used + * @param string $suffix the suffix (incl. dot) which should be used * @param int $id the id of the note for which the title should be generated * used to see if the file itself has the title and not a different file for * checking for filename collisions * @return string the resolved filename to prevent overwriting different * files with the same title */ - private function generateFileName (Folder $folder, $title, $extension, $id) { - $path = $title . '.' . $extension; + private function generateFileName (Folder $folder, $title, $suffix, $id) { + $path = $title . $suffix; // if file does not exist, that name has not been taken. Similar we don't // need to handle file collisions if it is the filename did not change @@ -368,7 +368,7 @@ class NotesService { } else { $newTitle = $title . ' (2)'; } - return $this->generateFileName($folder, $newTitle, $extension, $id); + return $this->generateFileName($folder, $newTitle, $suffix, $id); } } diff --git a/service/settingsservice.php b/service/settingsservice.php index 19940847..4b245bc9 100644 --- a/service/settingsservice.php +++ b/service/settingsservice.php @@ -17,8 +17,9 @@ class SettingsService private $root; /* Default values */ - private $settings = [ + private $defaults = [ "notesPath" => "Notes", + "fileSuffix" => ".txt", ]; public function __construct( @@ -33,16 +34,42 @@ class SettingsService * @throws \OCP\PreConditionNotMetException */ public function set($settings) { - foreach($this->settings as $name => $value) { - $this->settings[$name] = isset($settings[$name]) ? $settings[$name] : $value; + // remove illegal, empty and default settings + foreach($settings as $name => $value) { + if(!array_key_exists($name, $this->defaults) + || empty($value) + || $value === $this->defaults[$name] + ) { + unset($settings[$name]); + } } - - $this->config->setUserValue($this->uid, 'notes', 'settings', json_encode($this->settings)); + $this->config->setUserValue($this->uid, 'notes', 'settings', json_encode($settings)); } - public function get($name = null, $default = null) { + public function getAll() { $settings = json_decode($this->config->getUserValue($this->uid, 'notes', 'settings')); - if(!$settings || !is_object($settings)) $settings = $this->settings; - return $name ? (property_exists($settings, $name) ? $settings->{$name} : $default) : $settings; + if(is_object($settings)) { + // use default for empty settings + foreach($this->defaults as $name => $defaultValue) { + if(!property_exists($settings, $name) || empty($settings->{$name})) { + $settings->{$name} = $defaultValue; + } + } + } else { + $settings = (object)$this->defaults; + } + return $settings; + } + + /** + * @throws \OCP\PreConditionNotMetException + */ + public function get($name) { + $settings = $this->getAll(); + if(property_exists($settings, $name)) { + return $settings->{$name}; + } else { + throw new \OCP\PreConditionNotMetException('Setting '.$name.' not found.'); + } } } diff --git a/templates/main.php b/templates/main.php index 06c00d80..94311a00 100644 --- a/templates/main.php +++ b/templates/main.php @@ -80,8 +80,14 @@ style('notes', [
-

- +
+

+ +
+
+

+ +