зеркало из https://github.com/nextcloud/notes.git
show category selector in navigation
This commit is contained in:
Родитель
934dcc6df8
Коммит
f852e1bee4
|
@ -82,10 +82,10 @@ class NotesController extends Controller {
|
|||
*
|
||||
* @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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1"><path d="M1.457 1.997c-.25 0-.46.21-.46.46v11.08c0 .26.202.46.46.46h13.08c.258 0 .46-.2.46-.46V4.46c0-.25-.21-.463-.46-.463h-6.54l-2-2z" fill-rule="evenodd" /><path style="fill:#ffffff;fill-rule:evenodd;stroke-width:0.84413958" d="m 2.4220964,2.9363347 c -0.2142587,0 -0.4237777,0.1169727 -0.4231396,0.324831 l 0.028903,9.4140373 c 6.638e-4,0.216175 0.1731212,0.382462 0.3942362,0.382462 h 11.210022 c 0.221116,0 0.394236,-0.166286 0.394236,-0.382462 V 5.128242 c 0,-0.2078594 -0.179977,-0.3849557 -0.394236,-0.3849557 L 7.5405756,4.8009163 5.742203,2.9363343 Z" /></svg>
|
После Ширина: | Высота: | Размер: 647 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1"><path d="M1.457 1.997c-.25 0-.46.21-.46.46v11.08c0 .26.202.46.46.46h13.08c.258 0 .46-.2.46-.46V4.46c0-.25-.21-.463-.46-.463h-6.54l-2-2z" fill-rule="evenodd"/></svg>
|
После Ширина: | Высота: | Размер: 239 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1"><path color="#000" fill="none" d="M-62.897-32.993h163.31v97.986h-163.31z"/><path style="text-decoration-color:#000;isolation:auto;mix-blend-mode:normal;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none" d="M8 0C3.594 0 0 3.594 0 8c0 4.406 3.594 8 8 8 4.406 0 8-3.594 8-8 0-4.406-3.594-8-8-8zm0 2c3.326 0 6 2.674 6 6s-2.674 6-6 6-6-2.674-6-6 2.674-6 6-6zm-.44.932a.617.617 0 0 0-.605.613l-.852 4.44V8C5.92 8.755 6.44 9 7.02 9.352h.005l2.998 1.58c.653.502 1.407-.476.754-.98L9 8.01V8l-.81-4.45a.617.617 0 0 0-.63-.618z" color="#000" white-space="normal"/></svg>
|
После Ширина: | Высота: | Размер: 693 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="22" width="22"><path d="M11.017.06l2.946 7.384L22 8.077l-6.11 5.082L17.77 21l-6.72-4.242-6.876 4.213 1.957-7.703L0 8.03l7.932-.52z"/></svg>
|
После Ширина: | Высота: | Размер: 187 B |
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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();
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
app.filter('categoryTitle', function () {
|
||||
'use strict';
|
||||
return function (str) {
|
||||
if (str && (typeof str === 'string')) {
|
||||
return str.replace(/\//g, ' / ');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}]);
|
|
@ -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;
|
||||
|
@ -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;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -37,7 +37,7 @@ style('notes', [
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<ul class="with-icon">
|
||||
<?php if(!$_['useSearchAPI']) { ?>
|
||||
<li class="note-search">
|
||||
<span class="nav-entry icon-search">
|
||||
|
@ -45,14 +45,62 @@ style('notes', [
|
|||
</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)"
|
||||
>… / {{ 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"
|
||||
|
@ -85,7 +133,6 @@ style('notes', [
|
|||
</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">
|
||||
|
|
|
@ -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>
|
||||
|
|
Загрузка…
Ссылка в новой задаче