Simple search functionality (#28)
* simple search functionality * filter by multiple words * fix firefox styling issues * add test for and filter
This commit is contained in:
parent
f8084a61ae
commit
6aa596ed6b
|
@ -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;
|
||||
|
|
|
@ -15,4 +15,6 @@ app.controller('AppController', function ($scope, $location, is) {
|
|||
$location.path('/notes/' + lastViewedNote);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.search = '';
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}]);
|
|
@ -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
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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([]);
|
||||
});
|
||||
});
|
|
@ -36,13 +36,18 @@ style('notes', [
|
|||
|
||||
<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 @@ style('notes', [
|
|||
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>
|
||||
|
|
Loading…
Reference in New Issue