#176: navigation by tag

This commit is contained in:
Benjamin Gamard 2018-03-10 16:36:14 +01:00
parent a0e89103af
commit 77311f42cd
7 changed files with 202 additions and 71 deletions

View File

@ -247,7 +247,7 @@ public class DocumentDao {
tagCriteriaList.add(String.format("dt%d.DOT_ID_C is not null", index)); tagCriteriaList.add(String.format("dt%d.DOT_ID_C is not null", index));
index++; index++;
} }
criteriaList.add(Joiner.on(" OR ").join(tagCriteriaList)); criteriaList.add("(" + Joiner.on(" OR ").join(tagCriteriaList) + ")");
} }
if (criteria.getShared() != null && criteria.getShared()) { if (criteria.getShared() != null && criteria.getShared()) {
criteriaList.add("(select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null) > 0"); criteriaList.add("(select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null) > 0");

View File

@ -499,14 +499,14 @@ public class DocumentResource extends BaseResource {
break; break;
case "shared": case "shared":
// New shared state criteria // New shared state criteria
if (params[1].equals("yes")) { documentCriteria.setShared(params[1].equals("yes"));
documentCriteria.setShared(true);
}
break; break;
case "lang": case "lang":
// New language criteria // New language criteria
if (Constants.SUPPORTED_LANGUAGES.contains(params[1])) { if (Constants.SUPPORTED_LANGUAGES.contains(params[1])) {
documentCriteria.setLanguage(params[1]); documentCriteria.setLanguage(params[1]);
} else {
documentCriteria.setLanguage(UUID.randomUUID().toString());
} }
break; break;
case "by": case "by":
@ -522,9 +522,7 @@ public class DocumentResource extends BaseResource {
break; break;
case "workflow": case "workflow":
// New shared state criteria // New shared state criteria
if (params[1].equals("me")) { documentCriteria.setActiveRoute(params[1].equals("me"));
documentCriteria.setActiveRoute(true);
}
break; break;
case "full": case "full":
// New full content search criteria // New full content search criteria

View File

@ -14,7 +14,6 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
$scope.limit = _.isUndefined(localStorage.documentsPageSize) ? '10' : localStorage.documentsPageSize; $scope.limit = _.isUndefined(localStorage.documentsPageSize) ? '10' : localStorage.documentsPageSize;
$scope.search = $state.params.search ? $state.params.search : ''; $scope.search = $state.params.search ? $state.params.search : '';
$scope.searchOpened = false; $scope.searchOpened = false;
$scope.setSearch = function (search) { $scope.search = search };
$scope.searchDropdownAnchor = angular.element(document.querySelector('.search-dropdown-anchor')); $scope.searchDropdownAnchor = angular.element(document.querySelector('.search-dropdown-anchor'));
$scope.paginationShown = true; $scope.paginationShown = true;
$scope.advsearch = {}; $scope.advsearch = {};
@ -81,6 +80,8 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
}); });
} }
$scope.extractNavigatedTag();
// Call API later // Call API later
timeoutPromise = $timeout(function () { timeoutPromise = $timeout(function () {
$scope.loadDocuments(); $scope.loadDocuments();
@ -118,22 +119,6 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
$state.go('document.view', { id: id }); $state.go('document.view', { id: id });
}; };
// Load tags
$scope.tags = [];
Restangular.one('tag/list').get().then(function (data) {
$scope.tags = data.tags;
});
/**
* Find children tags.
* @param parent
*/
$scope.getChildrenTags = function(parent) {
return _.filter($scope.tags, function(tag) {
return tag.parent === parent;
});
};
/** /**
* Returns a promise for typeahead user. * Returns a promise for typeahead user.
*/ */
@ -210,12 +195,18 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
$scope.searchOpened = false; $scope.searchOpened = false;
}; };
/**
* Clear the search.
*/
$scope.clearSearch = function () { $scope.clearSearch = function () {
$scope.advsearch = {}; $scope.advsearch = {};
$scope.search = ''; $scope.search = '';
$scope.searchOpened = false; $scope.searchOpened = false;
}; };
/**
* Import an EML file.
*/
$scope.importEml = function (file) { $scope.importEml = function (file) {
// Open the import modal // Open the import modal
$uibModal.open({ $uibModal.open({
@ -235,4 +226,97 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
$scope.loadDocuments(); $scope.loadDocuments();
}); });
}; };
// Tag navigation
$scope.tags = [];
$scope.navigatedFilter = { parent: '' };
$scope.navigatedTag = undefined;
$scope.navigationEnabled = _.isUndefined(localStorage.navigationEnabled) ?
true : localStorage.navigationEnabled === 'true';
Restangular.one('tag/list').get().then(function (data) {
$scope.tags = data.tags;
$scope.extractNavigatedTag();
});
/**
* Comparator for the navigation tag filter.
*/
$scope.navigatedComparator = function (actual, expected) {
if (expected === '') {
return _.isUndefined(actual);
}
return angular.equals(actual, expected);
};
/**
* Navigate to a specific tag.
*/
$scope.navigateToTag = function (tag) {
if (tag) {
$scope.search = 'tag:' + tag.name;
} else {
$scope.search = '';
}
};
/**
* Navigate one tag up.
*/
$scope.navigateUp = function () {
if (!$scope.navigatedTag) {
return;
}
$scope.navigateToTag(_.findWhere($scope.tags, { id: $scope.navigatedTag.parent }));
};
/**
* Get the current navigation breadcrumb.
*/
$scope.getCurrentNavigation = function () {
if (!$scope.navigatedTag) {
return [];
}
var nav = [];
nav.push($scope.navigatedTag);
var current = $scope.navigatedTag;
while (current.parent) {
current = _.findWhere($scope.tags, { id: current.parent });
if (!current) {
break;
}
nav.push(current);
}
return nav.reverse();
};
/**
* Extract the current navigated tag from the search query.
* Called each time the search query changes.
*/
$scope.extractNavigatedTag = function () {
// Find the current tag in the search query
var tagFound = /tag:([^ ]*)/.exec($scope.search);
if (tagFound) {
tagFound = tagFound[1];
// We search only for exact match
$scope.navigatedTag = _.findWhere($scope.tags, { name: tagFound });
} else {
$scope.navigatedTag = undefined;
}
if ($scope.navigatedTag) {
$scope.navigatedFilter = {parent: $scope.navigatedTag.id};
} else {
$scope.navigatedFilter = {parent: ''};
}
};
/**
* Toggle the navigation context.
*/
$scope.navigationToggle = function () {
$scope.navigationEnabled = !$scope.navigationEnabled;
localStorage.navigationEnabled = $scope.navigationEnabled;
};
}); });

View File

@ -39,6 +39,8 @@
"global_quota_warning": "<strong>Warning!</strong> Global quota almost reached at {{ current | number: 0 }}MB ({{ percent | number: 1 }}%) used on {{ total | number: 0 }}MB" "global_quota_warning": "<strong>Warning!</strong> Global quota almost reached at {{ current | number: 0 }}MB ({{ percent | number: 1 }}%) used on {{ total | number: 0 }}MB"
}, },
"document": { "document": {
"navigation_up": "Go up one level",
"toggle_navigation": "Toggle folder navigation",
"search_simple": "Simple search", "search_simple": "Simple search",
"search_fulltext": "Fulltext search", "search_fulltext": "Fulltext search",
"search_creator": "Creator", "search_creator": "Creator",

View File

@ -1,16 +1,7 @@
<script type="text/ng-template" id="tag-tree-item">
<span class="btn" ng-style="{ 'background-color': tag.color }"></span>
<span class="btn btn-link" ng-click="setSearch('tag:' + tag.name)">
{{ tag.name }}
</span>
<ul class="list-unstyled">
<li ng-repeat="tag in getChildrenTags(tag.id)" ng-include="'tag-tree-item'"></li>
</ul>
</script>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<div class="well well-3d"> <div class="well well-3d">
<!-- Main new document button -->
<div class="text-center mb-19"> <div class="text-center mb-19">
<div class="btn-group" uib-dropdown> <div class="btn-group" uib-dropdown>
<a href="#/document/add" class="btn btn-primary"> <a href="#/document/add" class="btn btn-primary">
@ -25,18 +16,9 @@
</div> </div>
</div> </div>
<!-- Search (simple and advanced) -->
<div class="row search-dropdown-anchor"> <div class="row search-dropdown-anchor">
<div class="col-xs-2 tag-tree-dropdown" uib-dropdown> <div class="col-xs-12 input-group">
<button class="btn btn-block btn-default" uib-dropdown-toggle ng-disabled="disabled">
{{ 'document.tags' | translate }} <span class="caret"></span>
</button>
<ul uib-dropdown-menu class="tag-tree">
<li ng-if="getChildrenTags().length == 0">{{ 'document.no_tags' | translate }}</li>
<li ng-repeat="tag in getChildrenTags()" ng-include="'tag-tree-item'"></li>
</ul>
</div>
<div class="col-xs-10 input-group">
<input type="search" class="form-control" ng-attr-placeholder="{{ 'document.search' | translate }}" ng-model="search" /> <input type="search" class="form-control" ng-attr-placeholder="{{ 'document.search' | translate }}" ng-model="search" />
<span class="input-group-addon btn" ng-click="openSearch()"> <span class="input-group-addon btn" ng-click="openSearch()">
<div uib-dropdown <div uib-dropdown
@ -145,6 +127,39 @@
</div> </div>
</div> </div>
<!-- Navigation breadcrumb -->
<ol class="breadcrumb breadcrumb-navigation pull-left mt-10">
<li><a href ng-click="navigateToTag()"><span class="fas fa-home"></span></a></li>
<li ng-repeat="tag in getCurrentNavigation()" ng-class="{ active: tag.id == navigatedTag.id }">
<a class="label" ng-style="{ 'background-color': tag.color }" ng-if="tag.id != navigatedTag.id" href ng-click="navigateToTag(tag)">{{ tag.name }}</a>
<span class="label" ng-style="{ 'background-color': tag.color }" ng-if="tag.id == navigatedTag.id">{{ tag.name }}</span>
</li>
</ol>
<div class="btn-group mt-10 pull-right">
<!-- Go up in the navigation -->
<button class="btn btn-default" ng-click="navigateUp()"
uib-tooltip="{{ 'document.navigation_up' | translate }}"
tooltip-append-to-body="true"
ng-if="navigatedTag">
<span class="fas fa-chevron-up"></span>
</button>
<button class="btn btn-default" ng-click="navigationToggle()"
ng-class="{ active: navigationEnabled }"
uib-tooltip="{{ 'document.toggle_navigation' | translate }}"
tooltip-append-to-body="true">
<span class="far" ng-class="{ 'fa-folder-open': navigationEnabled, 'fa-folder': !navigationEnabled }"></span>
</button>
</div>
<!-- Current folder -->
<table class="row table table-hover table-navigation" ng-show="navigationEnabled">
<tr ng-repeat="tag in tags | filter: navigatedFilter: navigatedComparator" ng-click="navigateToTag(tag)">
<td colspan="2"><span class="fas fa-tags" ng-style="{ color: tag.color }"></span> {{ tag.name }}</td>
</tr>
</table>
<!-- Document list -->
<table class="row table table-hover table-documents"> <table class="row table table-hover table-documents">
<thead> <thead>
<tr> <tr>

View File

@ -14,32 +14,34 @@
</div> </div>
<div ng-show="document"> <div ng-show="document">
<div class="pull-right"> <div class="pull-right btn-group">
<div class="dropdown" uib-dropdown ng-class="{ 'btn-group': document.writable }"> <button class="btn btn-danger" ng-show="document.writable" ng-click="deleteDocument(document)"><span class="fas fa-trash"></span> {{ 'delete' | translate }}</button>
<button class="btn btn-default" uib-dropdown-toggle> <a href="#/document/edit/{{ document.id }}" ng-show="document.writable" class="btn btn-primary"><span class="fas fa-pencil-alt"></span> {{ 'edit' | translate }}</a>
<span class="fas fa-cloud-download-alt"></span> </div>
{{ 'export' | translate }}
<span class="caret"></span> <div class="pull-right dropdown" uib-dropdown>
</button> <button class="btn btn-default" uib-dropdown-toggle>
<ul class="dropdown-menu"> <span class="fas fa-cloud-download-alt"></span>
<li> {{ 'export' | translate }}
<a ng-href="../api/file/zip?id={{ document.id }}"> <span class="caret"></span>
<span class="fas fa-download"></span> </button>
{{ 'document.view.download_files' | translate }} <ul class="dropdown-menu">
</a> <li>
</li> <a ng-href="../api/file/zip?id={{ document.id }}">
<li> <span class="fas fa-download"></span>
<a href ng-click="exportPdf()"> {{ 'document.view.download_files' | translate }}
<span class="fas fa-file-pdf"></span> </a>
{{ 'document.view.export_pdf' | translate }} </li>
</a> <li>
</li> <a href ng-click="exportPdf()">
</ul> <span class="fas fa-file-pdf"></span>
<button class="btn btn-danger" ng-show="document.writable" ng-click="deleteDocument(document)"><span class="fas fa-trash"></span> {{ 'delete' | translate }}</button> {{ 'document.view.export_pdf' | translate }}
<a href="#/document/edit/{{ document.id }}" ng-show="document.writable" class="btn btn-primary"><span class="fas fa-pencil-alt"></span> {{ 'edit' | translate }}</a> </a>
</div> </li>
</div> </ul>
&nbsp;
</div>
<div class="page-header"> <div class="page-header">
<h1> <h1>
{{ document.title }} {{ document.title }}

View File

@ -34,6 +34,36 @@
} }
} }
// Navigation
.table-navigation {
tr {
cursor: pointer;
td {
border: none !important;
}
}
}
.breadcrumb-navigation {
background: none;
margin-bottom: 10px;
display: flex;
&> li + li:before {
content: '/';
}
li {
display: flex;
}
.label {
display: flex;
align-items: center;
}
}
// Documents list // Documents list
.table-documents { .table-documents {
margin-top: 18px !important; margin-top: 18px !important;