Implement full text search (client side) Fixes #1699

This commit is contained in:
Emanuel Schuetze 2016-01-27 00:16:30 +01:00
parent 23ad11d232
commit 2a9e0b4b81
8 changed files with 122 additions and 21 deletions

View File

@ -94,6 +94,14 @@ angular.module('OpenSlidesApp.assignments', [])
}, },
getAgendaTitle: function () { getAgendaTitle: function () {
return this.title; return this.title;
},
// link name which is shown in search result
getSearchResultName: function () {
return this.getAgendaTitle();
},
// subtitle of search result
getSearchResultSubtitle: function () {
return "Election";
} }
}, },
relations: { relations: {

View File

@ -199,25 +199,15 @@ img {
#nav .searchbar { #nav .searchbar {
float: right; float: right;
margin-top: 35px; margin-top: 35px;
display: inline-table;
} }
.searchbar .input-medium { #nav .searchbar input {
float: left; width: 150px;
height: 28px;
width: 200px;
border: 1px solid #ccc;
padding: 0 5px;
font-size: 14px;
} }
.searchbar .btn { #nav .searchbar .btn {
float: left;
height: 30px;
width: 30px;
background: #e8eaed; background: #e8eaed;
border: 1px solid #ccc;
border-left: none;
font-size: 14px;
color: #555; color: #555;
} }
@ -617,6 +607,13 @@ img {
content: ":"; content: ":";
} }
/* search results */
.searchresults li {
margin-bottom: 12px;
}
.searchresults li .subtitle {
color: #999999;
}
/* ngDialog: override ngdialog-theme-default */ /* ngDialog: override ngdialog-theme-default */
@ -796,12 +793,19 @@ tr.selected td {
/* display for resolutions smaller that 880px */ /* display for resolutions smaller that 900px */
@media only screen and (max-width: 880px) { @media only screen and (max-width: 900px) {
#nav .navbar li a { padding: 24px 5px; } #nav .navbar li a { padding: 24px 5px; }
} }
/* display for resolutions smaller that 760px */
@media only screen and (max-width: 760px) {
#nav .navbar li a { padding: 24px 2px; }
#nav .searchbar input { width: 100px; }
}
/* display for resolutions smaller that 480px */ /* display for resolutions smaller that 480px */
@media only screen and (max-width: 480px) { @media only screen and (max-width: 480px) {
@ -821,7 +825,7 @@ tr.selected td {
#nav .navbar .button { display: block;} #nav .navbar .button { display: block;}
#nav .navbar .button i { padding-top: 15px;} #nav .navbar .button i { padding-top: 15px;}
#nav .searchbar { margin-top: 15px; } #nav .searchbar { margin-top: 15px; }
.searchbar .input-medium { width: 150px; } #nav .searchbar input { width: 150px; }
#chatbox { width: 100%; top: 130px; } #chatbox { width: 100%; top: 130px; }
.optional { /* hide optional column */ .optional { /* hide optional column */

View File

@ -251,7 +251,15 @@ angular.module('OpenSlidesApp.core', [
}, },
getAgendaTitle: function () { getAgendaTitle: function () {
return this.title; return this.title;
} },
// link name which is shown in search result
getSearchResultName: function () {
return this.getAgendaTitle();
},
// subtitle of search result
getSearchResultSubtitle: function () {
return "Agenda item";
},
}, },
relations: { relations: {
belongsTo: { belongsTo: {

View File

@ -209,11 +209,13 @@ angular.module('OpenSlidesApp.core.site', [
abstract: true, abstract: true,
template: "<ui-view/>", template: "<ui-view/>",
}) })
// legal notice and version // legal notice and version
.state('legalnotice', { .state('legalnotice', {
url: '/legalnotice', url: '/legalnotice',
controller: 'LegalNoticeCtrl', controller: 'LegalNoticeCtrl',
}) })
//config //config
.state('config', { .state('config', {
url: '/config', url: '/config',
@ -224,6 +226,14 @@ angular.module('OpenSlidesApp.core.site', [
} }
} }
}) })
// search
.state('search', {
url: '/search?q',
controller: 'SearchCtrl',
templateUrl: 'static/templates/search.html',
})
// customslide // customslide
.state('core.customslide', { .state('core.customslide', {
url: '/customslide', url: '/customslide',
@ -449,6 +459,47 @@ angular.module('OpenSlidesApp.core.site', [
} }
]) ])
// Search Bar Controller
.controller('SearchBarCtrl', [
'$scope',
'$state',
function ($scope, $state) {
$scope.search = function(query) {
$scope.query = '';
$state.go('search', {q: query});
}
}
])
// Search Controller
.controller('SearchCtrl', [
'$scope',
'$http',
'$stateParams',
'DS',
function ($scope, $http, $stateParams, DS) {
// search function
$scope.search = function(query) {
$http.get('/core/search_api/?q=' + query).then(function(success) {
var elements = success.data.elements;
$scope.results = [];
angular.forEach(elements, function(element) {
DS.find(element.collection, element.id).then(function(data) {
data.urlState = element.collection.replace('/','.')+'.detail';
data.urlParam = {id: element.id};
$scope.results.push(data);
});
})
});
}
// run search with get parameter from url
if ($stateParams.q) {
$scope.search($stateParams.q);
$scope.query = $stateParams.q;
}
}
])
// Provide generic customslide form fields for create and update view // Provide generic customslide form fields for create and update view
.factory('CustomslideForm', [ .factory('CustomslideForm', [

View File

@ -135,6 +135,19 @@
</a> </a>
</ul> </ul>
</div> </div>
<div class="searchbar pull-right" ng-controller="SearchBarCtrl">
<form ng-submit="search(query)">
<div class="input-group">
<input ng-model="query" class="form-control" type="text" placeholder="{{ 'Search' | translate}}">
<span class="input-group-btn">
<button type="submit" class="btn btn-default">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</form>
</div>
</div> </div>
</div><!--end nav--> </div><!--end nav-->

View File

@ -15,6 +15,10 @@ urlpatterns = [
views.VersionView.as_view(), views.VersionView.as_view(),
name='core_version'), name='core_version'),
url(r'^core/search_api/$',
views.SearchView.as_view(),
name='core_search'),
url(r'^angular_js/(?P<openslides_app>site|projector)/$', url(r'^angular_js/(?P<openslides_app>site|projector)/$',
views.AppsJsView.as_view(), views.AppsJsView.as_view(),
name='core_apps_js'), name='core_apps_js'),
@ -22,9 +26,6 @@ urlpatterns = [
# View for the projectors are handelt by angular. # View for the projectors are handelt by angular.
url(r'^projector.*$', views.ProjectorView.as_view()), url(r'^projector.*$', views.ProjectorView.as_view()),
url(r'^search/$',
views.SearchView.as_view(),
name='core_search'),
# Main entry point for all angular pages. # Main entry point for all angular pages.
# Has to be the last entry in the urls.py # Has to be the last entry in the urls.py

View File

@ -202,6 +202,14 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
} }
return "Motion " + value + ': ' + this.getTitle(); return "Motion " + value + ': ' + this.getTitle();
}, },
// link name which is shown in search result
getSearchResultName: function () {
return this.getAgendaTitle();
},
// subtitle of search result
getSearchResultSubtitle: function () {
return "Motion";
},
isAllowed: function (action) { isAllowed: function (action) {
/* /*
* Return true if the requested user is allowed to do the specific action. * Return true if the requested user is allowed to do the specific action.

View File

@ -134,6 +134,14 @@ angular.module('OpenSlidesApp.users', [])
}); });
return _.uniq(allPerms); return _.uniq(allPerms);
}, },
// link name which is shown in search result
getSearchResultName: function () {
return this.get_full_name();
},
// subtitle of search result
getSearchResultSubtitle: function () {
return "Participant";
},
}, },
}); });
} }