diff --git a/css/notes.css b/css/notes.css index 5f4f1f4c2..c39c7bfe3 100644 --- a/css/notes.css +++ b/css/notes.css @@ -14,6 +14,25 @@ padding-left: 2px; } +#app-navigation li .nav-entry { + display: block; + width: 100%; + line-height: 44px; + min-height: 44px; + padding: 0 12px; + overflow: hidden; + box-sizing: border-box; + white-space: nowrap; + text-overflow: ellipsis; + color: #000; + opacity: .57; +} + +#app-navigation li:hover .nav-entry, +#app-navigation li:focus .nav-entry { + opacity: 1; +} + #app-navigation #note-add:hover span, #app-navigation #note-add:focus span { display: inline; @@ -52,6 +71,25 @@ opacity: 1 !important; } +.note-search span { + background-position: 0 center; + background-origin: content-box; +} + +.note-search input { + width: 100%; + padding-left: 20px; + background-color: transparent; + border: none; + border-radius: 0; +} + +.note-search input:active, +.note-search input:focus, +.note-search input:hover { + border-bottom: 1px solid grey; +} + .tooltip { text-shadow: none; text-transform: none; diff --git a/js/app/controllers/appcontroller.js b/js/app/controllers/appcontroller.js index 609e9b19d..5fa07ec1d 100644 --- a/js/app/controllers/appcontroller.js +++ b/js/app/controllers/appcontroller.js @@ -15,4 +15,6 @@ app.controller('AppController', function ($scope, $location, is) { $location.path('/notes/' + lastViewedNote); } }; + + $scope.search = ''; }); diff --git a/js/app/filters/and.js b/js/app/filters/and.js new file mode 100644 index 000000000..61a3be8ec --- /dev/null +++ b/js/app/filters/and.js @@ -0,0 +1,14 @@ +/** + * filter by multiple words (AND operation) + */ +app.filter('and', ['$filter', function ($filter) { + 'use strict'; + return function (items, searchString) { + var searchValues = searchString.split(' '); + var filtered = items; + for(var i in searchValues) { + filtered = $filter('filter')(filtered, searchValues[i]); + } + return filtered; + }; +}]); diff --git a/js/public/app.min.js b/js/public/app.min.js index c62615cdd..c1a996890 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 s=e.module("Notes",["restangular","ngRoute"]).config(["$provide","$routeProvider","RestangularProvider","$httpProvider","$windowProvider",function(t,e,n,i,r){i.defaults.headers.common.requesttoken=o,t.value("Constants",{saveInterval:5e3}),e.when("/notes/:noteId",{templateUrl:"note.html",controller:"NoteController",resolve:{note:["$route","$q","is","Restangular",function(t,e,n,o){var i=e.defer(),r=t.current.params.noteId;return n.loading=!0,o.one("notes",r).get().then(function(t){n.loading=!1,i.resolve(t)},function(){n.loading=!1,i.reject()}),i.promise}]}}).otherwise({redirectTo:"/"});var s=OC.generateUrl("/apps/notes");n.setBaseUrl(s)}]).run(["$rootScope","$location","NotesModel",function(t,e,o){n('link[rel="shortcut icon"]').attr("href",OC.filePath("notes","img","favicon.png")),t.$on("$routeChangeError",function(){var t=o.getAll();if(t.length>0){var n=t.sort(function(t,e){return t.modified>e.modified?1:t.modified<e.modified?-1:0}),i=t[n.length-1];e.path("/notes/"+i.id)}else e.path("/")})}]);s.controller("AppController",["$scope","$location","is",function(t,e,n){t.is=n,t.init=function(t){0!==t&&e.path("/notes/"+t)}}]),s.controller("NoteController",["$routeParams","$scope","NotesModel","SaveQueue","note",function(e,n,o,i,r){o.updateIfExists(r),n.note=o.get(e.noteId),n.isSaving=function(){return i.isSaving()},n.updateTitle=function(){n.note.title=n.note.content.split("\n")[0]||t("notes","New note")},n.save=function(){var t=n.note;i.add(t)}}]),s.controller("NotesController",["$routeParams","$scope","$location","Restangular","NotesModel",function(t,e,n,o,i){e.route=t,e.notes=i.getAll();var r=o.all("notes");r.getList().then(function(t){i.addAll(t)}),e.create=function(){r.post().then(function(t){i.add(t),n.path("/notes/"+t.id)})},e["delete"]=function(t){var n=i.get(t);n.remove().then(function(){i.remove(t),e.$emit("$routeChangeError")})},e.toggleFavorite=function(t){var e=i.get(t);e.customPUT({favorite:!e.favorite},"favorite",{},{}).then(function(t){e.favorite=!!t})}}]),s.filter("noteTitle",function(){return function(t){return t=t.split("\n")[0]||"newNote",t.trim().replace(/^#+/g,"")}}),s.filter("wordCount",function(){return function(t){if(t&&"string"==typeof t){var e=t.split(/\s+/).filter(function(t){return t.search(/[A-Za-z0-9]/)!==-1}).length;return window.n("notes","%n word","%n words",e)}return 0}}),s.directive("notesAutofocus",function(){return{restrict:"A",link:function(t,e){e.focus()}}}),s.directive("editor",["$timeout",function(t){return{restrict:"A",link:function(e,o){var r=i(o[0],{change:function(n){t(function(){e.$apply(function(){e.note.content=n,e.updateTitle()})})}});r.setValue(e.note.content),o.on("click",".link",function(t){if(t.ctrlKey){var e=n(this).find(".link-params-inner").text();window.open(e,"_blank")}})}}}]),s.directive("notesIsSaving",["$window",function(e){return{restrict:"A",scope:{notesIsSaving:"="},link:function(n){e.onbeforeunload=function(){return n.notesIsSaving?t("notes","Note is currently saving. Leaving the page will delete all changes!"):null}}}}]),s.directive("notesTimeoutChange",["$timeout",function(t){return{restrict:"A",link:function(e,o,i){var r,s=300;n(o).bind("input propertychange paste",function(){t.cancel(r),r=t(function(){e.$apply(i.notesTimeoutChange)},s)})}}}]),s.directive("notesTooltip",function(){return{restrict:"A",link:function(t,e){e.tooltip()}}}),s.factory("is",function(){return{loading:!1}}),s.factory("NotesModel",function(){var t=function(){this.notes=[],this.notesIds={}};return t.prototype={addAll:function(t){for(var e=0;e<t.length;e+=1)this.add(t[e])},add:function(t){this.updateIfExists(t)},getAll:function(){return this.notes},get:function(t){return this.notesIds[t]},updateIfExists:function(t){var n=this.notesIds[t.id];e.isDefined(n)?(n.title=t.title,n.modified=t.modified,n.content=t.content,n.favorite=t.favorite):(this.notes.push(t),this.notesIds[t.id]=t)},remove:function(t){for(var e=0;e<this.notes.length;e+=1){var n=this.notes[e];if(n.id===t){this.notes.splice(e,1),delete this.notesIds[t];break}}}},new t}),s.factory("SaveQueue",["$q",function(t){var e=function(){this._queue={},this._flushLock=!1};return e.prototype={add:function(t){this._queue[t.id]=t,this._flush()},_flush:function(){var e=Object.keys(this._queue);if(0!==e.length&&!this._flushLock){this._flushLock=!0;for(var n=this,o=[],i=0;i<e.length;i+=1){var r=this._queue[e[i]];o.push(r.put().then(this._noteUpdateRequest.bind(null,r)))}this._queue={},t.all(o).then(function(){n._flushLock=!1,n._flush()})}},_noteUpdateRequest:function(t,e){t.title=e.title,t.modified=e.modified},isSaving:function(){return this._flushLock}},new e}])}(angular,jQuery,oc_requesttoken,mdEdit); +!function(e,n,o,i,r){"use strict";var u=e.module("Notes",["restangular","ngRoute"]).config(["$provide","$routeProvider","RestangularProvider","$httpProvider","$windowProvider",function(t,e,n,i,r){i.defaults.headers.common.requesttoken=o,t.value("Constants",{saveInterval:5e3}),e.when("/notes/:noteId",{templateUrl:"note.html",controller:"NoteController",resolve:{note:["$route","$q","is","Restangular",function(t,e,n,o){var i=e.defer(),r=t.current.params.noteId;return n.loading=!0,o.one("notes",r).get().then(function(t){n.loading=!1,i.resolve(t)},function(){n.loading=!1,i.reject()}),i.promise}]}}).otherwise({redirectTo:"/"});var u=OC.generateUrl("/apps/notes");n.setBaseUrl(u)}]).run(["$rootScope","$location","NotesModel",function(t,e,o){n('link[rel="shortcut icon"]').attr("href",OC.filePath("notes","img","favicon.png")),t.$on("$routeChangeError",function(){var t=o.getAll();if(t.length>0){var n=t.sort(function(t,e){return t.modified>e.modified?1:t.modified<e.modified?-1:0}),i=t[n.length-1];e.path("/notes/"+i.id)}else e.path("/")})}]);u.controller("AppController",["$scope","$location","is",function(t,e,n){t.is=n,t.init=function(t){0!==t&&e.path("/notes/"+t)},t.search=""}]),u.controller("NoteController",["$routeParams","$scope","NotesModel","SaveQueue","note",function(e,n,o,i,r){o.updateIfExists(r),n.note=o.get(e.noteId),n.isSaving=function(){return i.isSaving()},n.updateTitle=function(){n.note.title=n.note.content.split("\n")[0]||t("notes","New note")},n.save=function(){var t=n.note;i.add(t)}}]),u.controller("NotesController",["$routeParams","$scope","$location","Restangular","NotesModel",function(t,e,n,o,i){e.route=t,e.notes=i.getAll();var r=o.all("notes");r.getList().then(function(t){i.addAll(t)}),e.create=function(){r.post().then(function(t){i.add(t),n.path("/notes/"+t.id)})},e["delete"]=function(t){var n=i.get(t);n.remove().then(function(){i.remove(t),e.$emit("$routeChangeError")})},e.toggleFavorite=function(t){var e=i.get(t);e.customPUT({favorite:!e.favorite},"favorite",{},{}).then(function(t){e.favorite=!!t})}}]),u.directive("notesAutofocus",function(){return{restrict:"A",link:function(t,e){e.focus()}}}),u.directive("editor",["$timeout",function(t){return{restrict:"A",link:function(e,o){var r=i(o[0],{change:function(n){t(function(){e.$apply(function(){e.note.content=n,e.updateTitle()})})}});r.setValue(e.note.content),o.on("click",".link",function(t){if(t.ctrlKey){var e=n(this).find(".link-params-inner").text();window.open(e,"_blank")}})}}}]),u.directive("notesIsSaving",["$window",function(e){return{restrict:"A",scope:{notesIsSaving:"="},link:function(n){e.onbeforeunload=function(){return n.notesIsSaving?t("notes","Note is currently saving. Leaving the page will delete all changes!"):null}}}}]),u.directive("notesTimeoutChange",["$timeout",function(t){return{restrict:"A",link:function(e,o,i){var r,u=300;n(o).bind("input propertychange paste",function(){t.cancel(r),r=t(function(){e.$apply(i.notesTimeoutChange)},u)})}}}]),u.directive("notesTooltip",function(){return{restrict:"A",link:function(t,e){e.tooltip()}}}),u.filter("and",["$filter",function(t){return function(e,n){var o=n.split(" "),i=e;for(var r in o)i=t("filter")(i,o[r]);return i}}]),u.filter("noteTitle",function(){return function(t){return t=t.split("\n")[0]||"newNote",t.trim().replace(/^#+/g,"")}}),u.filter("wordCount",function(){return function(t){if(t&&"string"==typeof t){var e=t.split(/\s+/).filter(function(t){return t.search(/[A-Za-z0-9]/)!==-1}).length;return window.n("notes","%n word","%n words",e)}return 0}}),u.factory("is",function(){return{loading:!1}}),u.factory("NotesModel",function(){var t=function(){this.notes=[],this.notesIds={}};return t.prototype={addAll:function(t){for(var e=0;e<t.length;e+=1)this.add(t[e])},add:function(t){this.updateIfExists(t)},getAll:function(){return this.notes},get:function(t){return this.notesIds[t]},updateIfExists:function(t){var n=this.notesIds[t.id];e.isDefined(n)?(n.title=t.title,n.modified=t.modified,n.content=t.content,n.favorite=t.favorite):(this.notes.push(t),this.notesIds[t.id]=t)},remove:function(t){for(var e=0;e<this.notes.length;e+=1){var n=this.notes[e];if(n.id===t){this.notes.splice(e,1),delete this.notesIds[t];break}}}},new t}),u.factory("SaveQueue",["$q",function(t){var e=function(){this._queue={},this._flushLock=!1};return e.prototype={add:function(t){this._queue[t.id]=t,this._flush()},_flush:function(){var e=Object.keys(this._queue);if(0!==e.length&&!this._flushLock){this._flushLock=!0;for(var n=this,o=[],i=0;i<e.length;i+=1){var r=this._queue[e[i]];o.push(r.put().then(this._noteUpdateRequest.bind(null,r)))}this._queue={},t.all(o).then(function(){n._flushLock=!1,n._flush()})}},_noteUpdateRequest:function(t,e){t.title=e.title,t.modified=e.modified},isSaving:function(){return this._flushLock}},new e}])}(angular,jQuery,oc_requesttoken,mdEdit); //# sourceMappingURL=app.min.js.map diff --git a/js/public/app.min.js.map b/js/public/app.min.js.map index e28f98c95..5e94a609b 100644 --- a/js/public/app.min.js.map +++ b/js/public/app.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["app.js","controllers/appcontroller.js","controllers/notecontroller.js","controllers/notescontroller.js","filters/noteTitle.js","filters/wordCount.js","directives/autofocus.js","directives/editor.js","directives/issaving.js","directives/timeoutchange.js","directives/tooltip.js","services/is.js","services/notesmodel.js","services/savequeue.js"],"names":["angular","$","requestToken","mdEdit","undefined","app","module","config","$provide","$routeProvider","RestangularProvider","$httpProvider","$windowProvider","defaults","headers","common","requesttoken","value","saveInterval","when","templateUrl","controller","resolve","note","$route","$q","is","Restangular","deferred","defer","noteId","current","params","loading","one","get","then","reject","promise","otherwise","redirectTo","baseUrl","OC","generateUrl","setBaseUrl","run","$rootScope","$location","NotesModel","attr","filePath","$on","notes","getAll","length","sorted","sort","a","b","modified","path","id","$scope","init","lastViewedNote","$routeParams","SaveQueue","updateIfExists","isSaving","updateTitle","title","content","split","t","save","add","route","notesResource","all","getList","addAll","create","post","remove","$emit","toggleFavorite","customPUT","favorite","filter","trim","replace","wordCount","search","window","n","directive","restrict","link","scope","element","focus","$timeout","editor","change","$apply","setValue","on","event","ctrlKey","url","this","find","text","open","$window","notesIsSaving","onbeforeunload","attributes","timeout","interval","bind","cancel","notesTimeoutChange","tooltip","factory","notesIds","prototype","i","updated","isDefined","push","splice","_queue","_flushLock","_flush","keys","Object","self","requests","put","_noteUpdateRequest","response","jQuery","oc_requesttoken"],"mappings":"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","file":"app.min.js","sourcesContent":["/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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 <dev@bernhard-posselt.com>\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) {\n if(lastViewedNote !== 0) {\n $location.path('/notes/' + lastViewedNote);\n }\n };\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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) {\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\n $scope.updateTitle = function () {\n $scope.note.title = $scope.note.content.split('\\n')[0] ||\n t('notes', 'New note');\n };\n\n $scope.save = function() {\n var note = $scope.note;\n SaveQueue.add(note);\n };\n\n});","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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) {\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","/**\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) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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 mdEdit*/\napp.directive('editor', ['$timeout', function ($timeout) {\n\t'use strict';\n\treturn {\n\t\trestrict: 'A',\n\t\tlink: function(scope, element) {\n\t\t\tvar editor = mdEdit(element[0], {change: function(value) {\n\t\t\t\t$timeout(function(){\n\t\t\t\t\tscope.$apply(function() {\n\t\t\t\t\t\tscope.note.content = value;\n\t\t\t\t\t\tscope.updateTitle();\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}});\n\t\t\teditor.setValue(scope.note.content);\n\t\t\telement.on('click', '.link', function(event) {\n\t\t\t\tif(event.ctrlKey) {\n\t\t\t\t\tvar url = $(this).find('.link-params-inner').text();\n\t\t\t\t\twindow.open(url, '_blank');\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}]);\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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('notesIsSaving', function ($window) {\n 'use strict';\n return {\n restrict: 'A',\n scope: {\n 'notesIsSaving': '='\n },\n link: function (scope) {\n $window.onbeforeunload = function () {\n if (scope.notesIsSaving) {\n return t('notes', 'Note is currently saving. Leaving ' +\n 'the page will delete all changes!');\n } else {\n return null;\n }\n };\n }\n };\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n/**\n * Like ng-change only that it does not fire when you type faster than\n * 300 ms\n */\napp.directive('notesTimeoutChange', function ($timeout) {\n 'use strict';\n\n return {\n restrict: 'A',\n link: function (scope, element, attributes) {\n var interval = 300; // 300 miliseconds timeout after typing\n var timeout;\n\n $(element).bind('input propertychange paste', function () {\n $timeout.cancel(timeout);\n\n timeout = $timeout(function () {\n scope.$apply(attributes.notesTimeoutChange);\n }, interval);\n });\n }\n };\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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();\n }\n };\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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 <dev@bernhard-posselt.com>\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<notes.length; i+=1) {\n this.add(notes[i]);\n }\n },\n add: function(note) {\n this.updateIfExists(note);\n },\n getAll: function () {\n return this.notes;\n },\n get: function (id) {\n return this.notesIds[id];\n },\n updateIfExists: function(updated) {\n var note = this.notesIds[updated.id];\n if(angular.isDefined(note)) {\n note.title = updated.title;\n note.modified = updated.modified;\n note.content = updated.content;\n note.favorite = updated.favorite;\n } else {\n this.notes.push(updated);\n this.notesIds[updated.id] = updated;\n }\n },\n remove: function (id) {\n for(var i=0; i<this.notes.length; i+=1) {\n var note = this.notes[i];\n if(note.id === id) {\n this.notes.splice(i, 1);\n delete this.notesIds[id];\n break;\n }\n }\n }\n };\n\n return new NotesModel();\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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 };\n\n SaveQueue.prototype = {\n add: function (note) {\n this._queue[note.id] = note;\n this._flush();\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<keys.length; i+=1) {\n var note = this._queue[keys[i]];\n // if the update finished, update the modified and title\n // attributes on the note\n requests.push(note.put().then(\n this._noteUpdateRequest.bind(null, note))\n );\n }\n this._queue = {};\n\n // if all update requests are completed, run the flush\n // again to update the next batch of queued notes\n $q.all(requests).then(function () {\n self._flushLock = false;\n self._flush();\n });\n },\n _noteUpdateRequest: function (note, response) {\n note.title = response.title;\n note.modified = response.modified;\n },\n isSaving: function () {\n return this._flushLock;\n }\n };\n\n return new SaveQueue();\n});"]} \ No newline at end of file +{"version":3,"sources":["app.js","controllers/appcontroller.js","controllers/notecontroller.js","controllers/notescontroller.js","directives/autofocus.js","directives/editor.js","directives/issaving.js","directives/timeoutchange.js","directives/tooltip.js","filters/and.js","filters/noteTitle.js","filters/wordCount.js","services/is.js","services/notesmodel.js","services/savequeue.js"],"names":["angular","$","requestToken","mdEdit","undefined","app","module","config","$provide","$routeProvider","RestangularProvider","$httpProvider","$windowProvider","defaults","headers","common","requesttoken","value","saveInterval","when","templateUrl","controller","resolve","note","$route","$q","is","Restangular","deferred","defer","noteId","current","params","loading","one","get","then","reject","promise","otherwise","redirectTo","baseUrl","OC","generateUrl","setBaseUrl","run","$rootScope","$location","NotesModel","attr","filePath","$on","notes","getAll","length","sorted","sort","a","b","modified","path","id","$scope","init","lastViewedNote","search","$routeParams","SaveQueue","updateIfExists","isSaving","updateTitle","title","content","split","t","save","add","route","notesResource","all","getList","addAll","create","post","remove","$emit","toggleFavorite","customPUT","favorite","directive","restrict","link","scope","element","focus","$timeout","editor","change","$apply","setValue","on","event","ctrlKey","url","this","find","text","window","open","$window","notesIsSaving","onbeforeunload","attributes","timeout","interval","bind","cancel","notesTimeoutChange","tooltip","filter","$filter","items","searchString","searchValues","filtered","i","trim","replace","wordCount","n","factory","notesIds","prototype","updated","isDefined","push","splice","_queue","_flushLock","_flush","keys","Object","self","requests","put","_noteUpdateRequest","response","jQuery","oc_requesttoken"],"mappings":"CAAA,SAAAA,EAAAC,EAAAC,EAAAC,EAAAC,GAAA,YAQA,IAAAC,GAAAL,EAAAM,OAAA,SAAA,cAAA,YACAC,QAAA,WAAA,iBAAA,sBAAA,gBAAA,kBAAA,SAAAC,EAAAC,EAAAC,EAAAC,EACAC,GAIAD,EAAAE,SAAAC,QAAAC,OAAAC,aAAAd,EAIAM,EAAAS,MAAA,aACAC,aAAA,MAIAT,EAAAU,KAAA,kBACAC,YAAA,YACAC,WAAA,iBACAC,SAKAC,MAAA,SAAA,KAAA,KAAA,cAAA,SAAAC,EAAAC,EAAAC,EAAAC,GAEA,GAAAC,GAAAH,EAAAI,QACAC,EAAAN,EAAAO,QAAAC,OAAAF,MAWA,OAVAJ,GAAAO,SAAA,EAEAN,EAAAO,IAAA,QAAAJ,GAAAK,MAAAC,KAAA,SAAAb,GACAG,EAAAO,SAAA,EACAL,EAAAN,QAAAC,IACA,WACAG,EAAAO,SAAA,EACAL,EAAAS,WAGAT,EAAAU,aAGAC,WACAC,WAAA,KAGA,IAAAC,GAAAC,GAAAC,YAAA,cACAjC,GAAAkC,WAAAH,MAIAI,KAAA,aAAA,YAAA,aAAA,SAAAC,EAAAC,EAAAC,GAGA/C,EAAA,6BAAAgD,KACA,OACAP,GAAAQ,SAAA,QAAA,MAAA,gBAIAJ,EAAAK,IAAA,oBAAA,WACA,GAAAC,GAAAJ,EAAAK,QAGA,IAAAD,EAAAE,OAAA,EAAA,CACA,GAAAC,GAAAH,EAAAI,KAAA,SAAAC,EAAAC,GACA,MAAAD,GAAAE,SAAAD,EAAAC,SACA,EACAF,EAAAE,SAAAD,EAAAC,YAGA,IAIApC,EAAA6B,EAAAG,EAAAD,OAAA,EACAP,GAAAa,KAAA,UAAArC,EAAAsC,QAEAd,GAAAa,KAAA,SC7EAvD,GAAAgB,WAAA,iBAAA,SAAA,YAAA,KAAA,SAAAyC,EAAAf,EAAArB,GAGAoC,EAAApC,GAAAA,EAEAoC,EAAAC,KAAA,SAAAC,GACA,IAAAA,GACAjB,EAAAa,KAAA,UAAAI,IAIAF,EAAAG,OAAA,MCXA5D,EAAAgB,WAAA,kBAAA,eAAA,SAAA,aAAA,YAAA,OAAA,SAAA6C,EAAAJ,EAAAd,EACAmB,EAAA5C,GAGAyB,EAAAoB,eAAA7C,GAEAuC,EAAAvC,KAAAyB,EAAAb,IAAA+B,EAAApC,QAEAgC,EAAAO,SAAA,WACA,MAAAF,GAAAE,YAGAP,EAAAQ,YAAA,WACAR,EAAAvC,KAAAgD,MAAAT,EAAAvC,KAAAiD,QAAAC,MAAA,MAAA,IACAC,EAAA,QAAA,aAGAZ,EAAAa,KAAA,WACA,GAAApD,GAAAuC,EAAAvC,IACA4C,GAAAS,IAAArD,OClBAlB,EAAAgB,WAAA,mBAAA,eAAA,SAAA,YAAA,cAAA,aAAA,SAAA6C,EAAAJ,EAAAf,EACApB,EAAAqB,GAGAc,EAAAe,MAAAX,EACAJ,EAAAV,MAAAJ,EAAAK,QAEA,IAAAyB,GAAAnD,EAAAoD,IAAA,QAGAD,GAAAE,UAAA5C,KAAA,SAAAgB,GACAJ,EAAAiC,OAAA7B,KAGAU,EAAAoB,OAAA,WACAJ,EAAAK,OAAA/C,KAAA,SAAAb,GACAyB,EAAA4B,IAAArD,GACAwB,EAAAa,KAAA,UAAArC,EAAAsC,OAIAC,EAAAA,UAAA,SAAAhC,GACA,GAAAP,GAAAyB,EAAAb,IAAAL,EACAP,GAAA6D,SAAAhD,KAAA,WACAY,EAAAoC,OAAAtD,GACAgC,EAAAuB,MAAA,wBAIAvB,EAAAwB,eAAA,SAAAxD,GACA,GAAAP,GAAAyB,EAAAb,IAAAL,EACAP,GAAAgE,WAAAC,UAAAjE,EAAAiE,UACA,kBAAApD,KAAA,SAAAoD,GACAjE,EAAAiE,WAAAA,QClCAnF,EAAAoF,UAAA,iBAAA,WAEA,OACAC,SAAA,IACAC,KAAA,SAAAC,EAAAC,GACAA,EAAAC,YCXAzF,EAAAoF,UAAA,UAAA,WAAA,SAAAM,GAEA,OACAL,SAAA,IACAC,KAAA,SAAAC,EAAAC,GACA,GAAAG,GAAA7F,EAAA0F,EAAA,IAAAI,OAAA,SAAAhF,GACA8E,EAAA,WACAH,EAAAM,OAAA,WACAN,EAAArE,KAAAiD,QAAAvD,EACA2E,EAAAtB,oBAIA0B,GAAAG,SAAAP,EAAArE,KAAAiD,SACAqB,EAAAO,GAAA,QAAA,QAAA,SAAAC,GACA,GAAAA,EAAAC,QAAA,CACA,GAAAC,GAAAtG,EAAAuG,MAAAC,KAAA,sBAAAC,MACAC,QAAAC,KAAAL,EAAA,kBCXAlG,EAAAoF,UAAA,iBAAA,UAAA,SAAAoB,GAEA,OACAnB,SAAA,IACAE,OACAkB,cAAA,KAEAnB,KAAA,SAAAC,GACAiB,EAAAE,eAAA,WACA,MAAAnB,GAAAkB,cACApC,EAAA,QAAA,uEAGA,WCTArE,EAAAoF,UAAA,sBAAA,WAAA,SAAAM,GAGA,OACAL,SAAA,IACAC,KAAA,SAAAC,EAAAC,EAAAmB,GACA,GACAC,GADAC,EAAA,GAGAjH,GAAA4F,GAAAsB,KAAA,6BAAA,WACApB,EAAAqB,OAAAH,GAEAA,EAAAlB,EAAA,WACAH,EAAAM,OAAAc,EAAAK,qBACAH,UClBA7G,EAAAoF,UAAA,eAAA,WAGA,OACAC,SAAA,IACAC,KAAA,SAAAC,EAAAC,GACAA,EAAAyB,cCVAjH,EAAAkH,OAAA,OAAA,UAAA,SAAAC,GAEA,MAAA,UAAAC,EAAAC,GACA,GAAAC,GAAAD,EAAAjD,MAAA,KACAmD,EAAAH,CACA,KAAA,GAAAI,KAAAF,GACAC,EAAAJ,EAAA,UAAAI,EAAAD,EAAAE,GAEA,OAAAD,OCRAvH,EAAAkH,OAAA,YAAA,WAEA,MAAA,UAAAtG,GAEA,MADAA,GAAAA,EAAAwD,MAAA,MAAA,IAAA,UACAxD,EAAA6G,OAAAC,QAAA,OAAA,OCPA1H,EAAAkH,OAAA,YAAA,WAEA,MAAA,UAAAtG,GACA,GAAAA,GAAA,gBAAAA,GAAA,CACA,GAAA+G,GAAA/G,EAAAwD,MAAA,OAAA8C,OAGA,SAAAtG,GACA,MAAAA,GAAAgD,OAAA,sBAEAX,MACA,OAAAqD,QAAAsB,EAAA,QAAA,UAAA,WAAAD,GAEA,MAAA,MCNA3H,EAAA6H,QAAA,KAAA,WAGA,OACAjG,SAAA,KCHA5B,EAAA6H,QAAA,aAAA,WAGA,GAAAlF,GAAA,WACAwD,KAAApD,SACAoD,KAAA2B,YA0CA,OAvCAnF,GAAAoF,WACAnD,OAAA,SAAA7B,GACA,IAAA,GAAAyE,GAAA,EAAAA,EAAAzE,EAAAE,OAAAuE,GAAA,EACArB,KAAA5B,IAAAxB,EAAAyE,KAGAjD,IAAA,SAAArD,GACAiF,KAAApC,eAAA7C,IAEA8B,OAAA,WACA,MAAAmD,MAAApD,OAEAjB,IAAA,SAAA0B,GACA,MAAA2C,MAAA2B,SAAAtE,IAEAO,eAAA,SAAAiE,GACA,GAAA9G,GAAAiF,KAAA2B,SAAAE,EAAAxE,GACA7D,GAAAsI,UAAA/G,IACAA,EAAAgD,MAAA8D,EAAA9D,MACAhD,EAAAoC,SAAA0E,EAAA1E,SACApC,EAAAiD,QAAA6D,EAAA7D,QACAjD,EAAAiE,SAAA6C,EAAA7C,WAEAgB,KAAApD,MAAAmF,KAAAF,GACA7B,KAAA2B,SAAAE,EAAAxE,IAAAwE,IAGAjD,OAAA,SAAAvB,GACA,IAAA,GAAAgE,GAAA,EAAAA,EAAArB,KAAApD,MAAAE,OAAAuE,GAAA,EAAA,CACA,GAAAtG,GAAAiF,KAAApD,MAAAyE,EACA,IAAAtG,EAAAsC,KAAAA,EAAA,CACA2C,KAAApD,MAAAoF,OAAAX,EAAA,SACArB,MAAA2B,SAAAtE,EACA,WAMA,GAAAb,KChDA3C,EAAA6H,QAAA,aAAA,KAAA,SAAAzG,GAGA,GAAA0C,GAAA,WACAqC,KAAAiC,UACAjC,KAAAkC,YAAA,EAgDA,OA7CAvE,GAAAiE,WACAxD,IAAA,SAAArD,GACAiF,KAAAiC,OAAAlH,EAAAsC,IAAAtC,EACAiF,KAAAmC,UAEAA,OAAA,WAEA,GAAAC,GAAAC,OAAAD,KAAApC,KAAAiC,OACA,IAAA,IAAAG,EAAAtF,SAAAkD,KAAAkC,WAAA,CAGAlC,KAAAkC,YAAA,CAQA,KAAA,GALAI,GAAAtC,KACAuC,KAIAlB,EAAA,EAAAA,EAAAe,EAAAtF,OAAAuE,GAAA,EAAA,CACA,GAAAtG,GAAAiF,KAAAiC,OAAAG,EAAAf,GAGAkB,GAAAR,KAAAhH,EAAAyH,MAAA5G,KACAoE,KAAAyC,mBAAA9B,KAAA,KAAA5F,KAGAiF,KAAAiC,UAIAhH,EAAAsD,IAAAgE,GAAA3G,KAAA,WACA0G,EAAAJ,YAAA,EACAI,EAAAH,aAGAM,mBAAA,SAAA1H,EAAA2H,GACA3H,EAAAgD,MAAA2E,EAAA3E,MACAhD,EAAAoC,SAAAuF,EAAAvF,UAEAU,SAAA,WACA,MAAAmC,MAAAkC,aAIA,GAAAvE,OACAnE,QAAAmJ,OAAAC,gBAAAjJ","file":"app.min.js","sourcesContent":["/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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 <dev@bernhard-posselt.com>\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) {\n if(lastViewedNote !== 0) {\n $location.path('/notes/' + lastViewedNote);\n }\n };\n\n $scope.search = '';\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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) {\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\n $scope.updateTitle = function () {\n $scope.note.title = $scope.note.content.split('\\n')[0] ||\n t('notes', 'New note');\n };\n\n $scope.save = function() {\n var note = $scope.note;\n SaveQueue.add(note);\n };\n\n});","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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) {\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","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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 mdEdit*/\napp.directive('editor', ['$timeout', function ($timeout) {\n\t'use strict';\n\treturn {\n\t\trestrict: 'A',\n\t\tlink: function(scope, element) {\n\t\t\tvar editor = mdEdit(element[0], {change: function(value) {\n\t\t\t\t$timeout(function(){\n\t\t\t\t\tscope.$apply(function() {\n\t\t\t\t\t\tscope.note.content = value;\n\t\t\t\t\t\tscope.updateTitle();\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}});\n\t\t\teditor.setValue(scope.note.content);\n\t\t\telement.on('click', '.link', function(event) {\n\t\t\t\tif(event.ctrlKey) {\n\t\t\t\t\tvar url = $(this).find('.link-params-inner').text();\n\t\t\t\t\twindow.open(url, '_blank');\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}]);\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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('notesIsSaving', function ($window) {\n 'use strict';\n return {\n restrict: 'A',\n scope: {\n 'notesIsSaving': '='\n },\n link: function (scope) {\n $window.onbeforeunload = function () {\n if (scope.notesIsSaving) {\n return t('notes', 'Note is currently saving. Leaving ' +\n 'the page will delete all changes!');\n } else {\n return null;\n }\n };\n }\n };\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\n * This file is licensed under the Affero General Public License version 3 or\n * later.\n * See the COPYING file.\n */\n\n/**\n * Like ng-change only that it does not fire when you type faster than\n * 300 ms\n */\napp.directive('notesTimeoutChange', function ($timeout) {\n 'use strict';\n\n return {\n restrict: 'A',\n link: function (scope, element, attributes) {\n var interval = 300; // 300 miliseconds timeout after typing\n var timeout;\n\n $(element).bind('input propertychange paste', function () {\n $timeout.cancel(timeout);\n\n timeout = $timeout(function () {\n scope.$apply(attributes.notesTimeoutChange);\n }, interval);\n });\n }\n };\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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();\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) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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 <dev@bernhard-posselt.com>\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<notes.length; i+=1) {\n this.add(notes[i]);\n }\n },\n add: function(note) {\n this.updateIfExists(note);\n },\n getAll: function () {\n return this.notes;\n },\n get: function (id) {\n return this.notesIds[id];\n },\n updateIfExists: function(updated) {\n var note = this.notesIds[updated.id];\n if(angular.isDefined(note)) {\n note.title = updated.title;\n note.modified = updated.modified;\n note.content = updated.content;\n note.favorite = updated.favorite;\n } else {\n this.notes.push(updated);\n this.notesIds[updated.id] = updated;\n }\n },\n remove: function (id) {\n for(var i=0; i<this.notes.length; i+=1) {\n var note = this.notes[i];\n if(note.id === id) {\n this.notes.splice(i, 1);\n delete this.notesIds[id];\n break;\n }\n }\n }\n };\n\n return new NotesModel();\n});\n","/**\n * Copyright (c) 2013, Bernhard Posselt <dev@bernhard-posselt.com>\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 };\n\n SaveQueue.prototype = {\n add: function (note) {\n this._queue[note.id] = note;\n this._flush();\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<keys.length; i+=1) {\n var note = this._queue[keys[i]];\n // if the update finished, update the modified and title\n // attributes on the note\n requests.push(note.put().then(\n this._noteUpdateRequest.bind(null, note))\n );\n }\n this._queue = {};\n\n // if all update requests are completed, run the flush\n // again to update the next batch of queued notes\n $q.all(requests).then(function () {\n self._flushLock = false;\n self._flush();\n });\n },\n _noteUpdateRequest: function (note, response) {\n note.title = response.title;\n note.modified = response.modified;\n },\n isSaving: function () {\n return this._flushLock;\n }\n };\n\n return new SaveQueue();\n});"]} \ No newline at end of file diff --git a/js/tests/unit/filters/andfilterSpec.js b/js/tests/unit/filters/andfilterSpec.js new file mode 100644 index 000000000..1f4495252 --- /dev/null +++ b/js/tests/unit/filters/andfilterSpec.js @@ -0,0 +1,36 @@ +describe('and filter', function() { + 'use strict'; + + var result; + var $filter; + + beforeEach(module('Notes')); + + beforeEach(inject(function(_$filter_) { + $filter = _$filter_; + })); + + + it ('should do nothing if search string is empty', function() { + result = $filter('and')([], ''); + expect(result).toEqual([]); + + result = $filter('and')(['a', 'lot', 'of', 'strings'], ''); + expect(result).toEqual(['a', 'lot', 'of', 'strings']); + }); + + it ('should match single words', function() { + result = $filter('and')(['a', 'ad', 'multiple words'], 'd'); + expect(result).toEqual(['ad', 'multiple words']); + }); + + it ('should math multiple words', function() { + result = $filter('and')(['a b c', 'a c e', 'a d'], 'a c'); + expect(result).toEqual(['a b c', 'a c e']); + }); + + it ('should return nothing if nothing matches', function() { + result = $filter('and')(['brown fox jumps over the lazy dog'], 'quick'); + expect(result).toEqual([]); + }); +}); diff --git a/templates/main.php b/templates/main.php index 7d8c0400c..509203c6c 100644 --- a/templates/main.php +++ b/templates/main.php @@ -36,13 +36,18 @@ <div id="app-navigation" ng-controller="NotesController"> <ul> + <li class="note-search"> + <span class="nav-entry icon-search"> + <input type="text" ng-model="search" /> + </span> + </li> <!-- new note button --> <li id="note-add" ng-click="create()" oc-click-focus="{ selector: '#app-content textarea' }"> <a href='#'>+ <span><?php p($l->t('New note')); ?></span></a> </li> <!-- notes list --> - <li ng-repeat="note in notes|orderBy:['-favorite','-modified']" + <li ng-repeat="note in filteredNotes = (notes| and:search | orderBy:['-favorite','-modified'])" ng-class="{ active: note.id == route.noteId }"> <a href="#/notes/{{ note.id }}"> {{ note.title | noteTitle }} @@ -61,6 +66,11 @@ ng-class="{'icon-starred': note.favorite}"></button> </span> </li> + <li ng-hide="filteredNotes.length"> + <span class="nav-entry"> + <?php p($l->t('No notes found')); ?> + </span> + </li> </ul> </div>