Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Categories in navigation #210

Merged
merged 1 commit into from
Nov 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions controller/notescontroller.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ public function get($id) {
*
* @param string $content
*/
public function create($content="") {
public function create($content='', $category=null) {
$note = $this->notesService->create($this->userId);
$note = $this->notesService->update(
$note->getId(), $content, $this->userId
$note->getId(), $content, $this->userId, $category
);
return new DataResponse($note);
}
Expand Down
29 changes: 29 additions & 0 deletions css/notes.css
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ form.category .icon-confirm {
}
.ui-autocomplete.category-autocomplete {
z-index: 5000 !important;
position: fixed;
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
Expand Down Expand Up @@ -348,3 +349,31 @@ form.category .icon-confirm {
padding-left: 90px;
}
}


/* icons for sidebar */
.nav-icon-emptyfolder {
background-image: url('../img/folder-empty.svg?v=1');
}
.nav-icon-files {
background-image: url('../img/folder.svg?v=1');
}
.nav-icon-recent {
background-image: url('../img/recent.svg?v=1');
}
.nav-icon-favorites {
background-image: url('../img/star.svg?v=1');
}



.separator-below {
border-bottom: 1px solid var(--color-border);
}
.separator-above {
border-top: 1px solid var(--color-border);
}
.current-category-item > a,
a.current-category-item {
font-weight: bold;
}
1 change: 1 addition & 0 deletions img/folder-empty.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions img/folder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions img/recent.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions img/star.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 38 additions & 1 deletion js/app/controllers/notescontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ app.controller('NotesController', function($routeParams, $scope, $location,
$scope.notesLoaded = false;
$scope.notes = NotesModel.getAll();

$scope.folderSelectorOpen = false;
$scope.filterCategory = null;

$scope.orderRecent = ['-favorite','-modified'];
$scope.orderAlpha = ['category','-favorite','title'];
$scope.filterOrder = $scope.orderRecent;

var notesResource = Restangular.all('notes');

// initial request for getting all notes
Expand All @@ -23,7 +30,8 @@ app.controller('NotesController', function($routeParams, $scope, $location,
});

$scope.create = function () {
notesResource.post().then(function (note) {
notesResource.post({category: $scope.filterCategory})
.then(function (note) {
NotesModel.add(note);
$location.path('/notes/' + note.id);
});
Expand All @@ -46,6 +54,35 @@ app.controller('NotesController', function($routeParams, $scope, $location,
event.target.blur();
};

$scope.getCategories = _.memoize(function (notes) {
return NotesModel.getCategories(notes, 1, true);
});

$scope.toggleFolderSelector = function () {
$scope.folderSelectorOpen = !$scope.folderSelectorOpen;
};

$scope.setFilter = function (category) {
if(category===null) {
$scope.filterOrder = $scope.orderRecent;
} else {
$scope.filterOrder = $scope.orderAlpha;
}
$scope.filterCategory = category;
$scope.folderSelectorOpen = false;
$('#app-navigation > ul').animate({scrollTop: 0}, 'fast');
};

$scope.categoryFilter = function (note) {
if($scope.filterCategory!==null) {
if(note.category===$scope.filterCategory) {
return true;
} else if(note.category!==null) {
return note.category.startsWith($scope.filterCategory+'/');
}
}
return true;
};

$window.onbeforeunload = function() {
var notes = NotesModel.getAll();
Expand Down
10 changes: 10 additions & 0 deletions js/app/filters/categoryTitle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
app.filter('categoryTitle', function () {
'use strict';
return function (str) {
if (str && (typeof str === 'string')) {
return str.replace(/\//g, ' / ');
} else {
return '';
}
};
});
26 changes: 26 additions & 0 deletions js/app/filters/groupNotes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* group notes by (sub) category
*/
app.filter('groupNotes', ['$filter', function () {
'use strict';
return _.memoize(function (notes, category) {
if(category) {
var items = [];
var prevCat = null;
for(var i=0; i<notes.length; i+=1) {
var note = notes[i];
if(prevCat !== null && prevCat !== note.category) {
items.push({
isCategory: true,
title: note.category.substring(category.length+1),
});
}
prevCat = note.category;
items.push(note);
}
return items;
} else {
return notes;
}
});
}]);
84 changes: 44 additions & 40 deletions js/app/services/notesmodel.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ app.factory('NotesModel', function () {
updateIfExists: function(updated) {
var note = this.notesIds[updated.id];
if(angular.isDefined(note)) {
// only update if it hat full data
if(updated.content !== null) {
// don't update meta-data over full data
if(updated.content !== null || note.content === null) {
note.title = updated.title;
note.modified = updated.modified;
note.content = updated.content;
Expand All @@ -64,46 +64,50 @@ app.factory('NotesModel', function () {
}
}
},
nthIndexOf: function(str, pattern, n) {
var i = -1;
while (n-- && i++ < str.length) {
i = str.indexOf(pattern, i);
if (i < 0) {
break;
}
}
return i;
},

getCategories: _.memoize(function (notes, maxLevel, details) {
var categories = {};
for(var i=0; i<notes.length; i+=1) {
var cat = notes[i].category;
if(maxLevel>0) {
var index = this.nthIndexOf(cat, '/', maxLevel);
if(index>0) {
cat = cat.substring(0, index);
nthIndexOf: function(str, pattern, n) {
var i = -1;
while (n-- && i++ < str.length) {
i = str.indexOf(pattern, i);
if (i < 0) {
break;
}
}
if(categories[cat]===undefined) {
categories[cat] = 1;
} else {
categories[cat] += 1;
}
}
var result = [];
for(var category in categories) {
if(details) {
result.push({ name: category, count: categories[category]});
} else if(category) {
result.push(category);
}
}
if(!details) {
result.sort();
}
return result;
}),
}
return i;
},

getCategories: function (notes, maxLevel, details) {
var categories = {};
for(var i=0; i<notes.length; i+=1) {
var cat = notes[i].category;
if(maxLevel>0) {
var index = this.nthIndexOf(cat, '/', maxLevel);
if(index>0) {
cat = cat.substring(0, index);
}
}
if(categories[cat]===undefined) {
categories[cat] = 1;
} else {
categories[cat] += 1;
}
}
var result = [];
for(var category in categories) {
if(details) {
result.push({
name: category,
count: categories[category],
});
} else if(category) {
result.push(category);
}
}
if(!details) {
result.sort();
}
return result;
},

};

Expand Down
2 changes: 1 addition & 1 deletion js/public/app.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/public/app.min.js.map

Large diffs are not rendered by default.

59 changes: 53 additions & 6 deletions templates/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,70 @@
</button>
</div>

<ul>
<ul class="with-icon">
<?php if(!$_['useSearchAPI']) { ?>
<li class="note-search">
<span class="nav-entry icon-search">
<input type="text" ng-model="search" />
</span>
</li>
<?php } ?>

<li class="collapsible app-navigation-noclose separator-below" ng-class="{ open: folderSelectorOpen, 'current-category-item': !folderSelectorOpen && filterCategory!=null }" ng-show="notes.length>1">
<a class="nav-icon-files svg" ng-click="toggleFolderSelector()">{{!folderSelectorOpen && filterCategory!=null ? filterCategory || '<?php p($l->t('Uncategorized')); ?>' : '<?php p($l->t('Categories')); ?>' | categoryTitle}}</a>

<ul>
<li data-id="recent" class="nav-recent" ng-class="{ active: filterCategory==null && filterFavorite==false }" ng-show="notes.length>1">
<a
ng-click="setFilter(null)"
class="nav-icon-recent svg"
><?php p($l->t('All notes')); ?></a>
<div class="app-navigation-entry-utils">
<ul>
<li class="app-navigation-entry-utils-counter">{{notes.length}}</li>
</ul>
</div>
</li>

<!-- category list -->
<li
ng-repeat="category in (getCategories(notes) | orderBy:['name'])"
class="nav-files"
ng-class="{ active: filterCategory==category.name && filterFavorite==false }"
title="{{ category.name || '<?php p($l->t('Uncategorized')); ?>' }}"
>
<a
ng-click="setFilter(category.name)"
class="svg"
ng-class="{ 'nav-icon-emptyfolder': !category.name, 'nav-icon-files': category.name }"
>{{ category.name || '<?php p($l->t('Uncategorized')); ?>' }}</a>
<div class="app-navigation-entry-utils">
<ul>
<li class="app-navigation-entry-utils-counter">{{category.count}}</li>
</ul>
</div>
</li>
</ul>
</li>

<!-- notes list -->
<li ng-repeat="note in filteredNotes = (notes| and:search | orderBy:['-favorite','-modified'])"
ng-class="{ active: note.id == route.noteId,'has-error': note.error }">
<a href="#/notes/{{ note.id }}" title="{{ note.title }}">
<li ng-repeat="note in filteredNotes = (notes | filter:categoryFilter | and:search | orderBy:filterOrder | groupNotes:filterCategory)"
ng-class="{ active: note.id == route.noteId, 'has-error': note.error, 'app-navigation-noclose': note.isCategory }"
class="note-item">

<a class="nav-icon-files svg separator-above"
ng-if="note.isCategory"
ng-click="setFilter(filterCategory + '/' + note.title)"
>&hellip; / {{ note.title | categoryTitle }}</a>

<a href="#/notes/{{ note.id }}"
title="{{ note.title }}"
ng-if="!note.isCategory"
>
{{ note.title }}
<span ng-if="note.unsaved">*</span>
</a>
<div class="app-navigation-entry-utils" ng-class="{'hidden': note.error }">
<div class="app-navigation-entry-utils" ng-class="{'hidden': note.error }" ng-if="!note.isCategory">
<ul>
<li class="app-navigation-entry-utils-menu-button button-delete">
<button class="svg action icon-delete"
Expand Down Expand Up @@ -85,7 +133,6 @@
</span>
<span class="nav-entry" ng-show="!search"><?php p($l->t('No notes found')); ?></span>
</li>

</ul>

<div id="app-settings" ng-controller="NotesSettingsController">
Expand Down
2 changes: 1 addition & 1 deletion templates/note.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<textarea editor notes-timeout-change="onEdit()" name="editor" ng-if="note!==false"></textarea>
<div class="note-meta" ng-if="note!==false">
<span class="note-category" ng-class="{ uncategorized: !note.category }" title="<?php p($l->t('Category')); ?>" ng-show="!editCategory" ng-click="showEditCategory()">{{ note.category || '<?php p($l->t('Uncategorized')) ?>'}} <input type="button" class="edit icon icon-rename" title="<?php p($l->t('Edit category')); ?>"></span>
<span class="note-category" ng-class="{ uncategorized: !note.category }" title="<?php p($l->t('Category')); ?>" ng-show="!editCategory" ng-click="showEditCategory()">{{ note.category || '<?php p($l->t('Uncategorized')) ?>' | categoryTitle}} <input type="button" class="edit icon icon-rename" title="<?php p($l->t('Edit category')); ?>"></span>
<span class="note-category" title="<?php p($l->t('Edit category')); ?>" ng-show="editCategory"><form class="category"><input type="text" id="category" name="category" ng-blur="closeCategory()" placeholder="<?php p($l->t('Uncategorized')); ?>"><input type="submit" class="icon-confirm" value=""></form></span>
<span class="note-word-count" ng-if="note.content.length > 0">{{note.content | wordCount}}</span>
<span class="note-unsaved" ng-if="note.unsaved" title="<?php p($l->t('The note has unsaved changes.')); ?>"><?php p($l->t('*')); ?></span>
Expand Down