Commit b585f4ce authored by delanoe's avatar delanoe

Merge branch 'elias2' into unstable

parents bc355062 9c0a6c7e
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
"angular-loader": "~1.2.x", "angular-loader": "~1.2.x",
"angular-resource": "~1.2.x", "angular-resource": "~1.2.x",
"bootstrap": "~3.x", "bootstrap": "~3.x",
"angular-cookies": "1.2" "angular-cookies": "1.2",
"bootstrap-select": "silviomoreto/bootstrap-select#~1.7.3"
}, },
"resolutions": { "resolutions": {
"angular": "~1.2.x" "angular": "~1.2.x"
......
(function () {
'use strict';
var annotationsAppActiveLists = angular.module('annotationsAppActiveLists', []);
annotationsAppActiveLists.controller('ActiveListsController',
['$scope', '$rootScope', '$timeout',
function ($scope, $rootScope, $timeout) {
$scope.activeListsChange = function() {
var selected = $('.selectpicker option:selected').val();
var newActive = {};
$('.selectpicker option:selected').each(function(item, value) {
var id = value.id.split("---", 2)[1];
newActive[id] = value.value;
});
$rootScope.activeLists = newActive;
};
$rootScope.$watchCollection('activeLists', function (newValue, oldValue) {
if (newValue === undefined) return;
$timeout(function() {
$('.selectpicker').selectpicker('refresh');
});
});
$rootScope.$watchCollection('lists', function (newValue, oldValue) {
if (newValue === undefined) return;
// reformat lists to allListsSelect
var allListsSelect = [];
angular.forEach($rootScope.lists, function(value, key) {
this.push({
'id': key,
'label': value
});
// initialize activeLists with the MiamList by default
if (value == 'MiamList') {
$rootScope.activeLists = {};
$rootScope.activeLists[key] = value;
}
}, allListsSelect);
$rootScope.allListsSelect = allListsSelect;
$timeout(function() {
$('.selectpicker').selectpicker();
$('.selectpicker').selectpicker('val', ['MiamList']);
});
});
}]);
})(window);
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
/* /*
* Class names corresponding to server-side list names * Class names corresponding to server-side list names
* To display another list name, please add a new class under this * To display another list name, add a new class under this
*/ */
.MiamList { .MiamList {
color: black; color: black;
background-color: rgba(60, 118, 61, 0.5); background-color: rgba(60, 118, 61, 0.5);
...@@ -44,24 +46,31 @@ ...@@ -44,24 +46,31 @@
} }
.main-panel, .text-panel, .words-panel { .main-panel, .text-panel, .words-panel {
height: 800px;
margin: 10px 0; margin: 10px 0;
} }
#annotationsApp {
min-width: 780px;
}
.words-panel {
min-width: 220px;
}
.text-panel { .text-panel {
overflow-y: auto; overflow-y: auto;
min-width: 400px;
} }
.words-list { .words-list {
margin-bottom: 5px; margin-bottom: 5px;
height: 250px;
} }
.keyword-text { .keyword-text {
word-break: break-all; word-break: break-all;
} }
.list-group-item { .keyword-group-item {
display: inline-block; display: inline-block;
float: left; float: left;
padding: 5px; padding: 5px;
...@@ -139,3 +148,7 @@ ...@@ -139,3 +148,7 @@
border-right: solid thin #CCC; border-right: solid thin #CCC;
margin-right: 5px; margin-right: 5px;
} }
.float-right {
float: right;
}
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
*/ */
var S = window.STATIC_URL; var S = window.STATIC_URL;
window.annotationsApp = angular.module('annotationsApp', ['annotationsAppHttp']); window.annotationsApp = angular.module('annotationsApp', ['annotationsAppHttp',
'annotationsAppNgramList', 'annotationsAppHighlight', 'annotationsAppDocument',
'annotationsAppActiveLists', 'annotationsAppUtils']);
/* /*
* Angular Templates must not conflict with Django's * Angular Templates must not conflict with Django's
...@@ -16,501 +18,6 @@ ...@@ -16,501 +18,6 @@
$interpolateProvider.endSymbol('}]}'); $interpolateProvider.endSymbol('}]}');
}); });
/*
* Template of the ngram element displayed in the flat lists (called "extra-text")
*/
window.annotationsApp.directive('keywordTemplate', function () {
return {
templateUrl: function ($element, $attributes) {
return S + 'annotations/keyword_tpl.html';
}
};
});
/*
* For ngram elements displayed in the flat lists (called "extra-text")
*/
window.annotationsApp.controller('ExtraAnnotationController',
['$scope', '$rootScope', '$element', 'NgramHttpService',
function ($scope, $rootScope, $element, NgramHttpService) {
/*
* Click on the 'delete' cross button
*/
$scope.onDeleteClick = function () {
NgramHttpService.delete({
'listId': $scope.keyword.list_id,
'ngramId': $scope.keyword.uuid
}, function(data) {
$.each($rootScope.annotations, function(index, element) {
if (element.list_id == $scope.keyword.list_id && element.uuid == $scope.keyword.uuid) {
$rootScope.annotations.splice(index, 1);
return false;
}
});
}, function(data) {
console.log(data);
console.error("unable to delete the Ngram " + $scope.keyword.uuid);
});
};
}]);
/*
* For mouse selection on the text
*/
window.annotationsApp.controller('AnnotationController',
['$scope', '$rootScope', '$element',
function ($scope, $rootScope, $element) {
// FIXME maybe use angular.copy of the annotation
var keyword = _.find(
$rootScope.annotations,
function(annotation) { return annotation.uuid.toString() === $element[0].getAttribute('uuid').toString(); }
);
// attach the annotation scope dynamically
if (keyword) {
$scope.keyword = keyword;
}
$scope.onClick = function(e) {
$rootScope.$emit("positionAnnotationMenu", e.pageX, e.pageY);
$rootScope.$emit("toggleAnnotationMenu", $scope.keyword);
e.stopPropagation();
};
}]);
/*
* Controller of the menu over the current mouse selection
*/
window.annotationsApp.controller('AnnotationMenuController',
['$scope', '$rootScope', '$element', '$timeout', 'NgramHttpService',
function ($scope, $rootScope, $element, $timeout, NgramHttpService) {
/*
* Universal text selection
*/
function getSelected() {
if (window.getSelection) {
return window.getSelection();
}
else if (document.getSelection) {
return document.getSelection();
}
else {
var selection = document.selection && document.selection.createRange();
if (selection.text) {
return selection.text;
}
return false;
}
return false;
}
// we only need one singleton at a time
var selection = getSelected();
/*
* When mouse selection is started, we highlight it
*/
function toggleSelectionHighlight(text) {
if (text.trim() !== "") {
$(".text-panel").addClass("selection");
} else {
$(".text-panel").removeClass("selection");
}
}
/*
* Dynamically construct the selection menu scope
*/
function toggleMenu(context, annotation) {
$timeout(function() {
$scope.$apply(function() {
var miamlist_id = _.invert($rootScope.activeLists).MiamList;
var stoplist_id = _.invert($rootScope.activeLists).StopList;
// variable used in onClick
$scope.selection_text = angular.copy(annotation);
if (angular.isObject(annotation)) {
// existing ngram
// Delete from the current list
$scope.menuItems = [
{
'action': 'delete',
'listId': annotation.list_id,
'verb': 'Delete from',
'listName': $rootScope.lists[annotation.list_id]
}
];
if ($rootScope.lists[annotation.list_id] == "MiamList") {
// Add to the alternative list
$scope.menuItems.push({
'action': 'post',
'listId': stoplist_id,
'verb': 'Add to',
'listName': $rootScope.lists[stoplist_id]
});
} else if ($rootScope.lists[annotation.list_id] == "StopList") {
// Add to the alternative list
$scope.menuItems.push({
'action': 'post',
'listId': miamlist_id,
'verb': 'Add to',
'listName': $rootScope.lists[miamlist_id]
});
}
// show the menu
$element.fadeIn(100);
} else if (annotation.trim() !== "") {
// new ngram
$scope.menuItems = [
{
'action': 'post',
'listId': miamlist_id,
'verb': 'Add to',
'listName': $rootScope.activeLists[miamlist_id]
}
];
// show the menu
$element.fadeIn(100);
} else {
$scope.menuItems = [];
// close the menu
$element.fadeOut(100);
}
});
});
}
var pos = $(".text-panel").position();
function positionElement(context, x, y) {
// todo try bootstrap popover component
$element.css('left', x + 10);
$element.css('top', y + 10);
}
function positionMenu(e) {
positionElement(null, e.pageX, e.pageY);
}
/*
* Dynamically position the menu
*/
$(".text-container").mousedown(function(){
$(".text-container").mousemove(positionMenu);
});
/*
* Finish positioning the menu then display the menu
*/
$(".text-container").mouseup(function(){
$(".text-container").unbind("mousemove", positionMenu);
toggleSelectionHighlight(selection.toString().trim());
toggleMenu(null, selection.toString().trim());
});
/*
* Toggle the menu when clicking on an existing ngram keyword
*/
$(".text-container").delegate(':not("#selection")', "click", function(e) {
if ($(e.target).hasClass("keyword-inline")) return;
positionMenu(e);
toggleSelectionHighlight(selection.toString().trim());
toggleMenu(null, selection.toString().trim());
});
$rootScope.$on("positionAnnotationMenu", positionElement);
$rootScope.$on("toggleAnnotationMenu", toggleMenu);
/*
* Menu click action
*/
$scope.onMenuClick = function($event, action, listId) {
if (angular.isObject($scope.selection_text)) {
// action on an existing Ngram
NgramHttpService[action]({
'listId': listId,
'ngramId': $scope.selection_text.uuid
}, function(data) {
$.each($rootScope.annotations, function(index, element) {
if (element.list_id == listId && element.uuid == $scope.selection_text.uuid) {
$rootScope.annotations.splice(index, 1);
return false;
}
});
}, function(data) {
console.log(data);
console.error("unable to edit the Ngram " + $scope.selection_text);
}
);
} else if ($scope.selection_text.trim() !== "") {
// new annotation from selection
NgramHttpService.post(
{
'listId': listId,
'ngramId': 'new'
},
{
'annotation' : {'text': $scope.selection_text.trim()}
}, function(data) {
$rootScope.annotations.push(data);
}, function(data) {
console.log(data);
console.error("unable to edit the Ngram " + $scope.selection_text);
}
);
}
// hide the highlighted text the the menu
$(".text-panel").removeClass("selection");
$element.fadeOut(100);
};
}
]);
/*
* Text highlighting controller
*/
window.annotationsApp.controller('IntraTextController',
['$scope', '$rootScope', '$compile', 'NgramHttpService',
function ($scope, $rootScope, $compile, NgramHttpService) {
var counter = 0;
/*
* Replace the text by an html template for ngram keywords
*/
function replaceTextByTemplate(text, ngram, template, pattern, lists) {
return text.replace(pattern, function(matched) {
var tpl = angular.element(template);
tpl.append(matched);
tpl.attr('title', ngram.tooltip_content);
tpl.attr('uuid', ngram.uuid);
/*
* Add CSS class depending on the list the ngram is into
* FIXME Lists names and css classes are fixed, can do better
*/
tpl.addClass(ngram.listName);
return tpl.get(0).outerHTML;
});
}
/*
* Sorts annotations on the number of words
* Required for overlapping ngrams
*/
function lengthSort(listitems, valuekey) {
listitems.sort(function(a, b) {
var compA = a[valuekey].split(" ").length;
var compB = b[valuekey].split(" ").length;
return (compA > compB) ? -1 : (compA <= compB) ? 1 : 0;
});
return listitems;
}
/*
* Match and replace Ngram into the text
*/
function compileNgramsHtml(annotations, textMapping, $rootScope) {
// TODO remove this debug counter
counter = 0;
var templateBegin = "<span ng-controller='AnnotationController' ng-click='onClick($event)' class='keyword-inline'>";
var templateBeginRegexp = "<span ng-controller='AnnotationController' ng-click='onClick\(\$event\)' class='keyword-inline'>";
var templateEnd = "</span>";
var template = templateBegin + templateEnd;
var startPattern = "\\b((?:"+templateBeginRegexp+")*";
var middlePattern = "(?:<\/span>)*\\s(?:"+templateBeginRegexp+")*";
var endPattern = "(?:<\/span>)*)\\b";
var sortedSizeAnnotations = lengthSort(annotations, "text"),
extraNgramList = angular.copy($rootScope.extraNgramList);
// reinitialize an empty list
extraNgramList = angular.forEach(extraNgramList, function(name, id) {
extraNgramList[id] = [];
});
angular.forEach(sortedSizeAnnotations, function (annotation) {
// exclude ngrams that are into inactive lists
if ($rootScope.activeLists[annotation.list_id] === undefined) return;
// used to setup css class
annotation.listName = $rootScope.lists[annotation.list_id];
// regexps
var words = annotation.text.split(" ");
var pattern = new RegExp(startPattern + words.join(middlePattern) + endPattern, 'gmi');
var textRegexp = new RegExp("\\b"+annotation.text+"\\b", 'igm');
var isDisplayedIntraText = false;
// highlight text as html
angular.forEach(textMapping, function(text, eltId) {
if (pattern.test(text) === true) {
textMapping[eltId] = replaceTextByTemplate(text, annotation, template, pattern, $rootScope.lists);
// TODO remove debug
counter++;
isDisplayedIntraText = true;
}
});
if (!isDisplayedIntraText) {
// add extra-text ngrams that are not already displayed
if ($.inArray(annotation.uuid, extraNgramList[annotation.list_id].map(function (item) {
return item.uuid;
})) == -1) {
// push the ngram and sort
extraNgramList[annotation.list_id] = extraNgramList[annotation.list_id].concat(annotation);
}
}
});
// update extraNgramList
$rootScope.extraNgramList = angular.forEach(extraNgramList, function(name, id) {
extraNgramList[id] = lengthSort(extraNgramList[id], 'text');
});
// return the object of element ID with the corresponding HTML
return textMapping;
}
/*
* Listen changes on the ngram data
*/
$rootScope.$watchCollection('annotations', function (newValue, oldValue) {
if ($rootScope.annotations === undefined) return;
if (angular.equals(newValue, oldValue)) return;
// initialize extraNgramList
var extraNgramList = {};
$rootScope.extraNgramList = angular.forEach($rootScope.activeLists, function(name, id) {
this[id] = [];
}, extraNgramList);
$rootScope.extraNgramList = extraNgramList;
/*
* Transform text into HTML with higlighted ngrams
*/
var result = compileNgramsHtml(
$rootScope.annotations,
{
'#full-text': angular.copy($rootScope.full_text),
'#abstract-text': angular.copy($rootScope.abstract_text),
'#title': angular.copy($rootScope.title)
},
$rootScope
);
// inject highlighted HTML
angular.forEach(result, function(html, eltId) {
angular.element(eltId).html(html);
});
// inject one Angular controller on every highlighted text element
angular.element('.text-container').find('[ng-controller=AnnotationController]').each(function(idx, elt) {
angular.element(elt).replaceWith($compile(elt)($rootScope.$new(true)));
});
});
/*
* Add a new NGram from the user input in the extra-text list
*/
$scope.onListSubmit = function ($event, listId) {
var inputEltId = "#"+ listId +"-input";
if ($event.keyCode !== undefined && $event.keyCode != 13) return;
var value = $(inputEltId).val().trim();
if (value === "") return;
NgramHttpService.post(
{
'listId': listId,
'ngramId': 'new'
},
{
'annotation' : {'text': value}
},
function(data) {
// on success
if (data) {
$rootScope.annotations.push(data);
$(inputEltId).val("");
}
}, function(data) {
// on error
$(inputEltId).parent().addClass("has-error");
console.error("error adding Ngram "+ value);
}
);
};
}
]);
/*
* Controller for one List Tab displaying extra-text ngram
*/
window.annotationsApp.controller('ExtraTextPaginationController',
['$scope', '$rootScope', function ($scope, $rootScope) {
$rootScope.$watchCollection('extraNgramList', function (newValue, oldValue) {
$scope.currentListPage = 0;
$scope.pageSize = 15;
$scope.nextListPage = function() {
$scope.currentListPage = $scope.currentListPage + 1;
};
$scope.previousListPage = function() {
$scope.currentListPage = $scope.currentListPage - 1;
};
$scope.totalListPages = function (listId) {
if ($rootScope.extraNgramList[listId] === undefined) return 0;
return Math.ceil($rootScope.extraNgramList[listId].length / $scope.pageSize);
};
});
}]);
/*
* Filter used in Ngram flat lists pagination (extra-text panel)
*/
window.annotationsApp.filter('startFrom', function () {
return function (input, start) {
if (input === undefined) return;
start = +start; //parse to int
return input.slice(start);
};
});
window.annotationsApp.controller('DocController',
['$scope', '$rootScope', 'NgramListHttpService', 'DocumentHttpService',
function ($scope, $rootScope, NgramListHttpService, DocumentHttpService) {
$rootScope.documentResource = DocumentHttpService.get(
{'docId': $rootScope.docId},
function(data, responseHeaders) {
$scope.authors = data.authors;
$scope.journal = data.journal;
$scope.publication_date = data.publication_date;
//$scope.current_page_number = data.current_page_number;
//$scope.last_page_number = data.last_page_number;
$rootScope.title = data.title;
$rootScope.docId = data.id;
$rootScope.full_text = data.full_text;
$rootScope.abstract_text = data.abstract_text;
// GET the annotationss
$rootScope.annotationsResource = NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.lists = data[$rootScope.corpusId.toString()].lists;
// TODO active list selection controller
$rootScope.activeLists = angular.copy($rootScope.lists);
$rootScope.mainListId = _.invert($rootScope.activeLists).MiamList;
}
);
});
// TODO setup article pagination
$scope.onPreviousClick = function () {
DocumentHttpService.get($scope.docId - 1);
};
$scope.onNextClick = function () {
DocumentHttpService.get($scope.docId + 1);
};
}]);
/* /*
* Main function * Main function
* GET the document node and all its ngrams * GET the document node and all its ngrams
......
(function () {
'use strict';
var annotationsAppDocument = angular.module('annotationsAppDocument', ['annotationsAppHttp']);
annotationsAppDocument.controller('DocController',
['$scope', '$rootScope', '$timeout', 'NgramListHttpService', 'DocumentHttpService',
function ($scope, $rootScope, $timeout, NgramListHttpService, DocumentHttpService) {
$rootScope.documentResource = DocumentHttpService.get(
{'docId': $rootScope.docId},
function(data, responseHeaders) {
$scope.authors = data.authors;
$scope.journal = data.journal;
$scope.publication_date = data.publication_date;
//$scope.current_page_number = data.current_page_number;
//$scope.last_page_number = data.last_page_number;
$rootScope.title = data.title;
$rootScope.docId = data.id;
$rootScope.full_text = data.full_text;
$rootScope.abstract_text = data.abstract_text;
// GET the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.lists = data[$rootScope.corpusId.toString()].lists;
},
function(data) {
console.error("unable to get the list of ngrams");
}
);
});
// TODO setup article pagination
$scope.onPreviousClick = function () {
DocumentHttpService.get($scope.docId - 1);
};
$scope.onNextClick = function () {
DocumentHttpService.get($scope.docId + 1);
};
}]);
annotationsAppDocument.controller('DocFavoriteController',
['$scope', '$rootScope', 'DocumentHttpService',
function ($scope, $rootScope, DocumentHttpService) {
$scope.onStarClick = function($event) {
console.log("TODO");
};
$scope.isFavorite = false;
}]);
})(window);
(function () {
'use strict';
var annotationsAppHighlight = angular.module('annotationsAppHighlight', ['annotationsAppHttp']);
/*
* Controls the mouse selection on the text
*/
annotationsAppHighlight.controller('TextSelectionController',
['$scope', '$rootScope', '$element',
function ($scope, $rootScope, $element) {
// FIXME maybe use angular.copy of the annotation
var keyword = _.find(
$rootScope.annotations,
function(annotation) { return annotation.uuid.toString() === $element[0].getAttribute('uuid').toString(); }
);
// attach the annotation scope dynamically
if (keyword) {
$scope.keyword = keyword;
}
$scope.onClick = function(e) {
$rootScope.$emit("positionAnnotationMenu", e.pageX, e.pageY);
$rootScope.$emit("toggleAnnotationMenu", $scope.keyword);
e.stopPropagation();
};
}]);
/*
* Controls the menu over the current mouse selection
*/
annotationsAppHighlight.controller('TextSelectionMenuController',
['$scope', '$rootScope', '$element', '$timeout', 'NgramHttpService', 'NgramListHttpService',
function ($scope, $rootScope, $element, $timeout, NgramHttpService, NgramListHttpService) {
/*
* Universal text selection
*/
function getSelected() {
if (window.getSelection) {
return window.getSelection();
}
else if (document.getSelection) {
return document.getSelection();
}
else {
var selection = document.selection && document.selection.createRange();
if (selection.text) {
return selection.text;
}
return false;
}
return false;
}
// we only need one singleton at a time
var selection = getSelected();
/*
* When mouse selection is started, we highlight it
*/
function toggleSelectionHighlight(text) {
if (text.trim() !== "" && !$element.hasClass('menu-is-opened')) {
$(".text-panel").addClass("selection");
} else {
$(".text-panel").removeClass("selection");
}
}
/*
* Dynamically construct the selection menu scope
*/
function toggleMenu(context, annotation) {
$timeout(function() {
$scope.$apply(function() {
var miamlist_id = _.invert($rootScope.lists).MiamList;
var stoplist_id = _.invert($rootScope.lists).StopList;
// variable used in onClick
$scope.selection_text = angular.copy(annotation);
if (angular.isObject(annotation) && !$element.hasClass('menu-is-opened')) {
// existing ngram
// Delete from the current list
$scope.menuItems = [
{
'action': 'delete',
'listId': annotation.list_id,
'verb': 'Delete from',
'listName': $rootScope.lists[annotation.list_id]
}
];
if ($rootScope.lists[annotation.list_id] == "MiamList") {
// Add to the alternative list
$scope.menuItems.push({
'action': 'post',
'listId': stoplist_id,
'verb': 'Move to',
'listName': $rootScope.lists[stoplist_id]
});
} else if ($rootScope.lists[annotation.list_id] == "StopList") {
// Add to the alternative list
$scope.menuItems.push({
'action': 'post',
'listId': miamlist_id,
'verb': 'Move to',
'listName': $rootScope.lists[miamlist_id]
});
}
// show the menu
$element.fadeIn(100);
$element.addClass('menu-is-opened');
} else if (annotation.trim() !== "" && !$element.hasClass('menu-is-opened')) {
// new ngram
$scope.menuItems = [
{
'action': 'post',
'listId': miamlist_id,
'verb': 'Add to',
'listName': $rootScope.lists[miamlist_id]
}
];
// show the menu
$element.fadeIn(100);
$element.addClass('menu-is-opened');
} else {
$scope.menuItems = [];
// close the menu
$element.fadeOut(100);
$element.removeClass('menu-is-opened');
}
});
});
}
var pos = $(".text-panel").position();
function positionElement(context, x, y) {
// todo try bootstrap popover component
$element.css('left', x + 10);
$element.css('top', y + 10);
}
function positionMenu(e) {
positionElement(null, e.pageX, e.pageY);
}
/*
* Dynamically position the menu
*/
$(".text-container").mousedown(function(){
$(".text-container").mousemove(positionMenu);
});
/*
* Finish positioning the menu then display the menu
*/
$(".text-container").mouseup(function(){
$(".text-container").unbind("mousemove", positionMenu);
toggleSelectionHighlight(selection.toString().trim());
toggleMenu(null, selection.toString().trim());
});
/*
* Toggle the menu when clicking on an existing ngram keyword
*/
$(".text-container").delegate(':not("#selection")', "click", function(e) {
// if ($(e.target).hasClass("keyword-inline")) return;
positionMenu(e);
toggleSelectionHighlight(selection.toString().trim());
toggleMenu(null, selection.toString().trim());
});
$rootScope.$on("positionAnnotationMenu", positionElement);
$rootScope.$on("toggleAnnotationMenu", toggleMenu);
/*
* Menu click action
*/
$scope.onMenuClick = function($event, action, listId) {
if (angular.isObject($scope.selection_text)) {
// action on an existing Ngram
NgramHttpService[action]({
'listId': listId,
'ngramId': $scope.selection_text.uuid
}, function(data) {
// Refresh the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.refreshDisplay();
},
function(data) {
console.error("unable to get the list of ngrams");
}
);
}, function(data) {
console.error("unable to edit the Ngram " + $scope.selection_text.text);
}
);
} else if ($scope.selection_text.trim() !== "") {
// new annotation from selection
NgramHttpService.post(
{
'listId': listId,
'ngramId': 'create'
},
{
'text': $scope.selection_text.trim()
}, function(data) {
// Refresh the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.refreshDisplay();
},
function(data) {
console.error("unable to get the list of ngrams");
}
);
}, function(data) {
console.error("unable to edit the Ngram " + $scope.selection_text);
}
);
}
// hide the highlighted text the the menu
$(".text-panel").removeClass("selection");
$element.fadeOut(100);
};
}
]);
/*
* Text highlighting controller
*/
annotationsAppHighlight.controller('NGramHighlightController',
['$scope', '$rootScope', '$compile',
function ($scope, $rootScope, $compile) {
/*
* Replace the text by an html template for ngram keywords
*/
function replaceTextByTemplate(text, ngram, template, pattern) {
return text.replace(pattern, function(matched) {
var tpl = angular.element(template);
tpl.append(matched);
tpl.attr('title', ngram.tooltip_content);
tpl.attr('uuid', ngram.uuid);
/*
* Add CSS class depending on the list the ngram is into
* FIXME Lists names and css classes are fixed, can do better
*/
tpl.addClass(ngram.listName);
return tpl.get(0).outerHTML;
});
}
/*
* Sorts annotations on the number of words
* Required for overlapping ngrams
*/
function lengthSort(listitems, valuekey) {
listitems.sort(function(a, b) {
var compA = a[valuekey].split(" ").length;
var compB = b[valuekey].split(" ").length;
return (compA > compB) ? -1 : (compA <= compB) ? 1 : 0;
});
return listitems;
}
/*
* Match and replace Ngram into the text
*/
function compileNgramsHtml(annotations, textMapping, $rootScope) {
if ($rootScope.activeLists === undefined) return;
if (_.keys($rootScope.activeLists).length === 0) return;
var templateBegin = "<span ng-controller='TextSelectionController' ng-click='onClick($event)' class='keyword-inline'>";
var templateBeginRegexp = "<span ng-controller='TextSelectionController' ng-click='onClick\(\$event\)' class='keyword-inline'>";
var templateEnd = "</span>";
var template = templateBegin + templateEnd;
var startPattern = "\\b((?:"+templateBeginRegexp+")*";
var middlePattern = "(?:<\/span>)*\\s(?:"+templateBeginRegexp+")*";
var endPattern = "(?:<\/span>)*)\\b";
var sortedSizeAnnotations = lengthSort(annotations, "text"),
extraNgramList = angular.copy($rootScope.extraNgramList);
// reinitialize an empty list
extraNgramList = angular.forEach(extraNgramList, function(name, id) {
extraNgramList[id] = [];
});
angular.forEach(sortedSizeAnnotations, function (annotation) {
// exclude ngrams that are into inactive lists
if ($rootScope.activeLists[annotation.list_id] === undefined) return;
// used to setup css class
annotation.listName = $rootScope.lists[annotation.list_id];
// regexps
var words = annotation.text.split(" ");
var pattern = new RegExp(startPattern + words.join(middlePattern) + endPattern, 'gmi');
var textRegexp = new RegExp("\\b"+annotation.text+"\\b", 'igm');
var isDisplayedIntraText = false;
// highlight text as html
angular.forEach(textMapping, function(text, eltId) {
if (pattern.test(text) === true) {
textMapping[eltId] = replaceTextByTemplate(text, annotation, template, pattern);
isDisplayedIntraText = true;
}
});
if (!isDisplayedIntraText) {
// add extra-text ngrams that are not already displayed
if ($.inArray(annotation.uuid, extraNgramList[annotation.list_id].map(function (item) {
return item.uuid;
})) == -1) {
// push the ngram and sort
extraNgramList[annotation.list_id] = extraNgramList[annotation.list_id].concat(annotation);
}
}
});
// update extraNgramList
$rootScope.extraNgramList = angular.forEach(extraNgramList, function(name, id) {
extraNgramList[id] = lengthSort(extraNgramList[id], 'text');
});
// return the object of element ID with the corresponding HTML
return textMapping;
}
$rootScope.refreshDisplay = function() {
if ($rootScope.annotations === undefined) return;
if ($rootScope.activeLists === undefined) return;
if (_.keys($rootScope.activeLists).length === 0) return;
// initialize extraNgramList
var extraNgramList = {};
$rootScope.extraNgramList = angular.forEach($rootScope.activeLists, function(name, id) {
this[id] = [];
}, extraNgramList);
$rootScope.extraNgramList = extraNgramList;
/*
* Transform text into HTML with higlighted ngrams
*/
var result = compileNgramsHtml(
$rootScope.annotations,
{
'#full-text': angular.copy($rootScope.full_text),
'#abstract-text': angular.copy($rootScope.abstract_text),
'#title': angular.copy($rootScope.title)
},
$rootScope
);
// inject highlighted HTML
angular.forEach(result, function(html, eltId) {
angular.element(eltId).html(html);
});
// inject one Angular controller on every highlighted text element
angular.element('.text-container').find('[ng-controller=TextSelectionController]').each(function(idx, elt) {
angular.element(elt).replaceWith($compile(elt)($rootScope.$new(true)));
});
}
/*
* Listen changes on the ngram data
*/
$rootScope.$watchCollection('activeLists', function (newValue, oldValue) {
$rootScope.refreshDisplay();
});
}
]);
})(window);
...@@ -19,6 +19,7 @@ var S = window.STATIC_URL; ...@@ -19,6 +19,7 @@ var S = window.STATIC_URL;
$script([ $script([
S + 'bower_components/angular/angular.min.js', S + 'bower_components/angular/angular.min.js',
S + 'bower_components/bootstrap/dist/js/bootstrap.min.js', S + 'bower_components/bootstrap/dist/js/bootstrap.min.js',
S + 'bower_components/bootstrap-select/dist/js/bootstrap-select.min.js',
S + 'bower_components/angular-loader/angular-loader.min.js', S + 'bower_components/angular-loader/angular-loader.min.js',
S + 'bower_components/underscore/underscore-1.5.2.js', S + 'bower_components/underscore/underscore-1.5.2.js',
//'bower_components/angular-route/angular-route.js', //'bower_components/angular-route/angular-route.js',
...@@ -26,7 +27,10 @@ $script([ ...@@ -26,7 +27,10 @@ $script([
$script([ $script([
S + 'bower_components/angular-cookies/angular-cookies.min.js', S + 'bower_components/angular-cookies/angular-cookies.min.js',
S + 'bower_components/angular-resource/angular-resource.min.js'], function() { S + 'bower_components/angular-resource/angular-resource.min.js'], function() {
$script([S + 'annotations/http.js', S + 'annotations/app.js'], function() { $script([S + 'annotations/http.js', S + 'annotations/highlight.js',
S + 'annotations/document.js', S + 'annotations/ngramlist.js',
S + 'annotations/activelists.js', S + 'annotations/ngramlist.js',
S + 'annotations/utils.js', S + 'annotations/app.js'], function() {
// when all is done, execute bootstrap angular application (replace ng-app directive) // when all is done, execute bootstrap angular application (replace ng-app directive)
angular.bootstrap(document.getElementById("annotationsApp"), ['annotationsApp']); angular.bootstrap(document.getElementById("annotationsApp"), ['annotationsApp']);
}); });
......
(function () {
'use strict';
var annotationsAppNgramList = angular.module('annotationsAppNgramList', ['annotationsAppHttp']);
/*
* Controls one Ngram displayed in the flat lists (called "extra-text")
*/
annotationsAppNgramList.controller('NgramController',
['$scope', '$rootScope', 'NgramHttpService', 'NgramListHttpService',
function ($scope, $rootScope, NgramHttpService, NgramListHttpService) {
/*
* Click on the 'delete' cross button
*/
$scope.onDeleteClick = function () {
NgramHttpService.delete({
'listId': $scope.keyword.list_id,
'ngramId': $scope.keyword.uuid
}, function(data) {
// Refresh the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.refreshDisplay();
},
function(data) {
console.error("unable to refresh the list of ngrams");
}
);
}, function(data) {
console.error("unable to remove the Ngram " + $scope.keyword.text);
});
};
}]);
/*
* Controller for the list panel displaying extra-text ngram
*/
annotationsAppNgramList.controller('NgramListPaginationController',
['$scope', '$rootScope', function ($scope, $rootScope) {
$rootScope.$watchCollection('extraNgramList', function (newValue, oldValue) {
$scope.currentListPage = 0;
$scope.pageSize = 15;
$scope.nextListPage = function() {
$scope.currentListPage = $scope.currentListPage + 1;
};
$scope.previousListPage = function() {
$scope.currentListPage = $scope.currentListPage - 1;
};
$scope.totalListPages = function(listId) {
if ($rootScope.extraNgramList[listId] === undefined) return 0;
return Math.ceil($rootScope.extraNgramList[listId].length / $scope.pageSize);
};
});
}]);
/*
* Template of the ngram element displayed in the flat lists
*/
annotationsAppNgramList.directive('keywordTemplate', function () {
return {
templateUrl: function ($element, $attributes) {
return S + 'annotations/keyword_tpl.html';
}
};
});
/*
* new NGram from the user input
*/
annotationsAppNgramList.controller('NgramInputController',
['$scope', '$rootScope', '$element', 'NgramHttpService', 'NgramListHttpService',
function ($scope, $rootScope, $element, NgramHttpService, NgramListHttpService) {
/*
* Add a new NGram from the user input in the extra-text list
*/
$scope.onListSubmit = function ($event, listId) {
var inputEltId = "#"+ listId +"-input";
if ($event.keyCode !== undefined && $event.keyCode != 13) return;
var value = angular.element(inputEltId).val().trim();
if (value === "") return;
NgramHttpService.post(
{
'listId': listId,
'ngramId': 'create'
},
{
'text': value
},
function(data) {
// on success
if (data) {
angular.element(inputEltId).val("");
// Refresh the annotationss
NgramListHttpService.get(
{
'corpusId': $rootScope.corpusId,
'docId': $rootScope.docId
},
function(data) {
$rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
$rootScope.refreshDisplay();
},
function(data) {
console.error("unable to get the list of ngrams");
}
);
}
}, function(data) {
// on error
angular.element(inputEltId).parent().addClass("has-error");
console.error("error adding Ngram "+ value);
}
);
};
}]);
})(window);
(function () {
'use strict';
var annotationsAppUtils = angular.module('annotationsAppUtils', []);
/*
* Filter used in lists pagination (extra-text panel)
*/
annotationsAppUtils.filter('startFrom', function () {
return function (input, start) {
if (input === undefined) return;
start = +start; //parse to int
return input.slice(start);
};
});
})(window);
/*!
* Bootstrap-select v1.7.3 (http://silviomoreto.github.io/bootstrap-select)
*
* Copyright 2013-2015 bootstrap-select
* Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE)
*/.bootstrap-select{width:220px \0}.bootstrap-select>.dropdown-toggle{width:100%;padding-right:25px}.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.btn-group:not(.input-group-btn),.bootstrap-select.btn-group[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.btn-group.dropdown-menu-right,.bootstrap-select.btn-group[class*=col-].dropdown-menu-right,.row .bootstrap-select.btn-group[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group{margin-bottom:0}.form-group-lg .bootstrap-select.btn-group.form-control,.form-group-sm .bootstrap-select.btn-group.form-control{padding:0}.form-inline .bootstrap-select.btn-group .form-control{width:100%}.bootstrap-select.btn-group.disabled,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group.disabled:focus,.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group .dropdown-toggle .filter-option{display:inline-block;overflow:hidden;width:100%;text-align:left}.bootstrap-select.btn-group .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;z-index:1035;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li.active small{color:#fff}.bootstrap-select.btn-group .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select.btn-group .dropdown-menu li a{cursor:pointer}.bootstrap-select.btn-group .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select.btn-group .dropdown-menu li a span.check-mark{display:none}.bootstrap-select.btn-group .dropdown-menu li a span.text{display:inline-block}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.btn-group.fit-width .dropdown-toggle .filter-option{position:static}.bootstrap-select.btn-group.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark{position:absolute;display:inline-block;right:15px;margin-top:5px}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle{z-index:1036}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%}select.bs-select-hidden,select.selectpicker{display:none!important}select.mobile-device{position:absolute!important;top:0;left:0;display:block!important;width:100%;height:100%!important;opacity:0}
\ No newline at end of file
/*!
* Bootstrap-select v1.7.3 (http://silviomoreto.github.io/bootstrap-select)
*
* Copyright 2013-2015 bootstrap-select
* Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE)
*/
!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("jquery")):b(jQuery)}(this,function(){!function(a){"use strict";function b(b){var c=[{re:/[\xC0-\xC6]/g,ch:"A"},{re:/[\xE0-\xE6]/g,ch:"a"},{re:/[\xC8-\xCB]/g,ch:"E"},{re:/[\xE8-\xEB]/g,ch:"e"},{re:/[\xCC-\xCF]/g,ch:"I"},{re:/[\xEC-\xEF]/g,ch:"i"},{re:/[\xD2-\xD6]/g,ch:"O"},{re:/[\xF2-\xF6]/g,ch:"o"},{re:/[\xD9-\xDC]/g,ch:"U"},{re:/[\xF9-\xFC]/g,ch:"u"},{re:/[\xC7-\xE7]/g,ch:"c"},{re:/[\xD1]/g,ch:"N"},{re:/[\xF1]/g,ch:"n"}];return a.each(c,function(){b=b.replace(this.re,this.ch)}),b}function c(a){var b={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},c="(?:"+Object.keys(b).join("|")+")",d=new RegExp(c),e=new RegExp(c,"g"),f=null==a?"":""+a;return d.test(f)?f.replace(e,function(a){return b[a]}):f}function d(b,c){var d=arguments,f=b,g=c;[].shift.apply(d);var h,i=this.each(function(){var b=a(this);if(b.is("select")){var c=b.data("selectpicker"),i="object"==typeof f&&f;if(c){if(i)for(var j in i)i.hasOwnProperty(j)&&(c.options[j]=i[j])}else{var k=a.extend({},e.DEFAULTS,a.fn.selectpicker.defaults||{},b.data(),i);b.data("selectpicker",c=new e(this,k,g))}"string"==typeof f&&(h=c[f]instanceof Function?c[f].apply(c,d):c.options[f])}});return"undefined"!=typeof h?h:i}String.prototype.includes||!function(){var a={}.toString,b=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(d){}return c}(),c="".indexOf,d=function(b){if(null==this)throw TypeError();var d=String(this);if(b&&"[object RegExp]"==a.call(b))throw TypeError();var e=d.length,f=String(b),g=f.length,h=arguments.length>1?arguments[1]:void 0,i=h?Number(h):0;i!=i&&(i=0);var j=Math.min(Math.max(i,0),e);return g+j>e?!1:-1!=c.call(d,f,i)};b?b(String.prototype,"includes",{value:d,configurable:!0,writable:!0}):String.prototype.includes=d}(),String.prototype.startsWith||!function(){var a=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(d){}return c}(),b={}.toString,c=function(a){if(null==this)throw TypeError();var c=String(this);if(a&&"[object RegExp]"==b.call(a))throw TypeError();var d=c.length,e=String(a),f=e.length,g=arguments.length>1?arguments[1]:void 0,h=g?Number(g):0;h!=h&&(h=0);var i=Math.min(Math.max(h,0),d);if(f+i>d)return!1;for(var j=-1;++j<f;)if(c.charCodeAt(i+j)!=e.charCodeAt(j))return!1;return!0};a?a(String.prototype,"startsWith",{value:c,configurable:!0,writable:!0}):String.prototype.startsWith=c}(),Object.keys||(Object.keys=function(a,b,c){c=[];for(b in a)c.hasOwnProperty.call(a,b)&&c.push(b);return c}),a.fn.triggerNative=function(a){var b,c=this[0];c.dispatchEvent?("function"==typeof Event?b=new Event(a,{bubbles:!0}):(b=document.createEvent("Event"),b.initEvent(a,!0,!1)),c.dispatchEvent(b)):(c.fireEvent&&(b=document.createEventObject(),b.eventType=a,c.fireEvent("on"+a,b)),this.trigger(a))},a.expr[":"].icontains=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.text()).toUpperCase();return f.includes(d[3].toUpperCase())},a.expr[":"].ibegins=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.text()).toUpperCase();return f.startsWith(d[3].toUpperCase())},a.expr[":"].aicontains=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.data("normalizedText")||e.text()).toUpperCase();return f.includes(d[3].toUpperCase())},a.expr[":"].aibegins=function(b,c,d){var e=a(b),f=(e.data("tokens")||e.data("normalizedText")||e.text()).toUpperCase();return f.startsWith(d[3].toUpperCase())};var e=function(b,c,d){d&&(d.stopPropagation(),d.preventDefault()),this.$element=a(b),this.$newElement=null,this.$button=null,this.$menu=null,this.$lis=null,this.options=c,null===this.options.title&&(this.options.title=this.$element.attr("title")),this.val=e.prototype.val,this.render=e.prototype.render,this.refresh=e.prototype.refresh,this.setStyle=e.prototype.setStyle,this.selectAll=e.prototype.selectAll,this.deselectAll=e.prototype.deselectAll,this.destroy=e.prototype.remove,this.remove=e.prototype.remove,this.show=e.prototype.show,this.hide=e.prototype.hide,this.init()};e.VERSION="1.7.2",e.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(a,b){return 1==a?"{0} item selected":"{0} items selected"},maxOptionsText:function(a,b){return[1==a?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==b?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)"]},selectAllText:"Select All",deselectAllText:"Deselect All",doneButton:!1,doneButtonText:"Close",multipleSeparator:", ",styleBase:"btn",style:"btn-default",size:"auto",title:null,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,liveSearchPlaceholder:null,liveSearchNormalize:!1,liveSearchStyle:"contains",actionsBox:!1,iconBase:"glyphicon",tickIcon:"glyphicon-ok",maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1},e.prototype={constructor:e,init:function(){var b=this,c=this.$element.attr("id");this.$element.addClass("bs-select-hidden"),this.liObj={},this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$newElement=this.createView(),this.$element.after(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(".dropdown-menu"),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.options.dropdownAlignRight&&this.$menu.addClass("dropdown-menu-right"),"undefined"!=typeof c&&(this.$button.attr("data-id",c),a('label[for="'+c+'"]').click(function(a){a.preventDefault(),b.$button.focus()})),this.checkDisabled(),this.clickListener(),this.options.liveSearch&&this.liveSearchListener(),this.render(),this.setStyle(),this.setWidth(),this.options.container&&this.selectPosition(),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on("hide.bs.dropdown",function(a){b.$element.trigger("hide.bs.select",a)}),this.$newElement.on("hidden.bs.dropdown",function(a){b.$element.trigger("hidden.bs.select",a)}),this.$newElement.on("show.bs.dropdown",function(a){b.$element.trigger("show.bs.select",a)}),this.$newElement.on("shown.bs.dropdown",function(a){b.$element.trigger("shown.bs.select",a)}),setTimeout(function(){b.$element.trigger("loaded.bs.select")})},createDropdown:function(){var b=this.multiple?" show-tick":"",d=this.$element.parent().hasClass("input-group")?" input-group-btn":"",e=this.autofocus?" autofocus":"",f=this.options.header?'<div class="popover-title"><button type="button" class="close" aria-hidden="true">&times;</button>'+this.options.header+"</div>":"",g=this.options.liveSearch?'<div class="bs-searchbox"><input type="text" class="form-control" autocomplete="off"'+(null===this.options.liveSearchPlaceholder?"":' placeholder="'+c(this.options.liveSearchPlaceholder)+'"')+"></div>":"",h=this.multiple&&this.options.actionsBox?'<div class="bs-actionsbox"><div class="btn-group btn-group-sm btn-block"><button type="button" class="actions-btn bs-select-all btn btn-default">'+this.options.selectAllText+'</button><button type="button" class="actions-btn bs-deselect-all btn btn-default">'+this.options.deselectAllText+"</button></div></div>":"",i=this.multiple&&this.options.doneButton?'<div class="bs-donebutton"><div class="btn-group btn-block"><button type="button" class="btn btn-sm btn-default">'+this.options.doneButtonText+"</button></div></div>":"",j='<div class="btn-group bootstrap-select'+b+d+'"><button type="button" class="'+this.options.styleBase+' dropdown-toggle" data-toggle="dropdown"'+e+'><span class="filter-option pull-left"></span>&nbsp;<span class="caret"></span></button><div class="dropdown-menu open">'+f+g+h+'<ul class="dropdown-menu inner" role="menu"></ul>'+i+"</div></div>";return a(j)},createView:function(){var a=this.createDropdown(),b=this.createLi();return a.find("ul")[0].innerHTML=b,a},reloadLi:function(){this.destroyLi();var a=this.createLi();this.$menuInner[0].innerHTML=a},destroyLi:function(){this.$menu.find("li").remove()},createLi:function(){var d=this,e=[],f=0,g=document.createElement("option"),h=-1,i=function(a,b,c,d){return"<li"+("undefined"!=typeof c&""!==c?' class="'+c+'"':"")+("undefined"!=typeof b&null!==b?' data-original-index="'+b+'"':"")+("undefined"!=typeof d&null!==d?'data-optgroup="'+d+'"':"")+">"+a+"</li>"},j=function(a,e,f,g){return'<a tabindex="0"'+("undefined"!=typeof e?' class="'+e+'"':"")+("undefined"!=typeof f?' style="'+f+'"':"")+(d.options.liveSearchNormalize?' data-normalized-text="'+b(c(a))+'"':"")+("undefined"!=typeof g||null!==g?' data-tokens="'+g+'"':"")+">"+a+'<span class="'+d.options.iconBase+" "+d.options.tickIcon+' check-mark"></span></a>'};if(this.options.title&&!this.multiple&&(h--,!this.$element.find(".bs-title-option").length)){var k=this.$element[0];g.className="bs-title-option",g.appendChild(document.createTextNode(this.options.title)),g.value="",k.insertBefore(g,k.firstChild),void 0===a(k.options[k.selectedIndex]).attr("selected")&&(g.selected=!0)}return this.$element.find("option").each(function(b){var c=a(this);if(h++,!c.hasClass("bs-title-option")){var g=this.className||"",k=this.style.cssText,l=c.data("content")?c.data("content"):c.html(),m=c.data("tokens")?c.data("tokens"):null,n="undefined"!=typeof c.data("subtext")?'<small class="text-muted">'+c.data("subtext")+"</small>":"",o="undefined"!=typeof c.data("icon")?'<span class="'+d.options.iconBase+" "+c.data("icon")+'"></span> ':"",p=this.disabled||"OPTGROUP"===this.parentElement.tagName&&this.parentElement.disabled;if(""!==o&&p&&(o="<span>"+o+"</span>"),d.options.hideDisabled&&p)return void h--;if(c.data("content")||(l=o+'<span class="text">'+l+n+"</span>"),"OPTGROUP"===this.parentElement.tagName&&c.data("divider")!==!0){var q=" "+this.parentElement.className||"";if(0===c.index()){f+=1;var r=this.parentElement.label,s="undefined"!=typeof c.parent().data("subtext")?'<small class="text-muted">'+c.parent().data("subtext")+"</small>":"",t=c.parent().data("icon")?'<span class="'+d.options.iconBase+" "+c.parent().data("icon")+'"></span> ':"";r=t+'<span class="text">'+r+s+"</span>",0!==b&&e.length>0&&(h++,e.push(i("",null,"divider",f+"div"))),h++,e.push(i(r,null,"dropdown-header"+q,f))}e.push(i(j(l,"opt "+g+q,k,m),b,"",f))}else c.data("divider")===!0?e.push(i("",b,"divider")):c.data("hidden")===!0?e.push(i(j(l,g,k,m),b,"hidden is-hidden")):(this.previousElementSibling&&"OPTGROUP"===this.previousElementSibling.tagName&&(h++,e.push(i("",null,"divider",f+"div"))),e.push(i(j(l,g,k,m),b)));d.liObj[b]=h}}),this.multiple||0!==this.$element.find("option:selected").length||this.options.title||this.$element.find("option").eq(0).prop("selected",!0).attr("selected","selected"),e.join("")},findLis:function(){return null==this.$lis&&(this.$lis=this.$menu.find("li")),this.$lis},render:function(b){var c,d=this;b!==!1&&this.$element.find("option").each(function(a){var b=d.findLis().eq(d.liObj[a]);d.setDisabled(a,this.disabled||"OPTGROUP"===this.parentElement.tagName&&this.parentElement.disabled,b),d.setSelected(a,this.selected,b)}),this.tabIndex();var e=this.$element.find("option").map(function(){if(this.selected){if(d.options.hideDisabled&&(this.disabled||"OPTGROUP"===this.parentElement.tagName&&this.parentElement.disabled))return!1;var b,c=a(this),e=c.data("icon")&&d.options.showIcon?'<i class="'+d.options.iconBase+" "+c.data("icon")+'"></i> ':"";return b=d.options.showSubtext&&c.data("subtext")&&!d.multiple?' <small class="text-muted">'+c.data("subtext")+"</small>":"","undefined"!=typeof c.attr("title")?c.attr("title"):c.data("content")&&d.options.showContent?c.data("content"):e+c.html()+b}}).toArray(),f=this.multiple?e.join(this.options.multipleSeparator):e[0];if(this.multiple&&this.options.selectedTextFormat.indexOf("count")>-1){var g=this.options.selectedTextFormat.split(">");if(g.length>1&&e.length>g[1]||1==g.length&&e.length>=2){c=this.options.hideDisabled?", [disabled]":"";var h=this.$element.find("option").not('[data-divider="true"], [data-hidden="true"]'+c).length,i="function"==typeof this.options.countSelectedText?this.options.countSelectedText(e.length,h):this.options.countSelectedText;f=i.replace("{0}",e.length.toString()).replace("{1}",h.toString())}}void 0==this.options.title&&(this.options.title=this.$element.attr("title")),"static"==this.options.selectedTextFormat&&(f=this.options.title),f||(f="undefined"!=typeof this.options.title?this.options.title:this.options.noneSelectedText),this.$button.attr("title",a.trim(f.replace(/<[^>]*>?/g,""))),this.$button.children(".filter-option").html(f),this.$element.trigger("rendered.bs.select")},setStyle:function(a,b){this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,""));var c=a?a:this.options.style;"add"==b?this.$button.addClass(c):"remove"==b?this.$button.removeClass(c):(this.$button.removeClass(this.options.style),this.$button.addClass(c))},liHeight:function(b){if(b||this.options.size!==!1&&!this.sizeInfo){var c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("ul"),f=document.createElement("li"),g=document.createElement("li"),h=document.createElement("a"),i=document.createElement("span"),j=this.options.header?this.$menu.find(".popover-title")[0].cloneNode(!0):null,k=this.options.liveSearch?document.createElement("div"):null,l=this.options.actionsBox&&this.multiple?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,m=this.options.doneButton&&this.multiple?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null;if(i.className="text",c.className=this.$menu[0].parentNode.className+" open",d.className="dropdown-menu open",e.className="dropdown-menu inner",f.className="divider",i.appendChild(document.createTextNode("Inner text")),h.appendChild(i),g.appendChild(h),e.appendChild(g),e.appendChild(f),j&&d.appendChild(j),k){var n=document.createElement("span");k.className="bs-searchbox",n.className="form-control",k.appendChild(n),d.appendChild(k)}l&&d.appendChild(l),d.appendChild(e),m&&d.appendChild(m),c.appendChild(d),document.body.appendChild(c);var o=h.offsetHeight,p=j?j.offsetHeight:0,q=k?k.offsetHeight:0,r=l?l.offsetHeight:0,s=m?m.offsetHeight:0,t=a(f).outerHeight(!0),u="function"==typeof getComputedStyle?getComputedStyle(d):!1,v=u?null:a(d),w=parseInt(u?u.paddingTop:v.css("paddingTop"))+parseInt(u?u.paddingBottom:v.css("paddingBottom"))+parseInt(u?u.borderTopWidth:v.css("borderTopWidth"))+parseInt(u?u.borderBottomWidth:v.css("borderBottomWidth")),x=w+parseInt(u?u.marginTop:v.css("marginTop"))+parseInt(u?u.marginBottom:v.css("marginBottom"))+2;document.body.removeChild(c),this.sizeInfo={liHeight:o,headerHeight:p,searchHeight:q,actionsHeight:r,doneButtonHeight:s,dividerHeight:t,menuPadding:w,menuExtras:x}}},setSize:function(){if(this.findLis(),this.liHeight(),this.options.header&&this.$menu.css("padding-top",0),this.options.size!==!1){var b,c,d,e,f=this,g=this.$menu,h=this.$menuInner,i=a(window),j=this.$newElement[0].offsetHeight,k=this.sizeInfo.liHeight,l=this.sizeInfo.headerHeight,m=this.sizeInfo.searchHeight,n=this.sizeInfo.actionsHeight,o=this.sizeInfo.doneButtonHeight,p=this.sizeInfo.dividerHeight,q=this.sizeInfo.menuPadding,r=this.sizeInfo.menuExtras,s=this.options.hideDisabled?".disabled":"",t=function(){d=f.$newElement.offset().top-i.scrollTop(),e=i.height()-d-j};if(t(),"auto"===this.options.size){var u=function(){var i,j=function(b,c){return function(d){return c?d.classList?d.classList.contains(b):a(d).hasClass(b):!(d.classList?d.classList.contains(b):a(d).hasClass(b))}},p=f.$menuInner[0].getElementsByTagName("li"),s=Array.prototype.filter?Array.prototype.filter.call(p,j("hidden",!1)):f.$lis.not(".hidden"),u=Array.prototype.filter?Array.prototype.filter.call(s,j("dropdown-header",!0)):s.filter(".dropdown-header");t(),b=e-r,f.options.container?(g.data("height")||g.data("height",g.height()),c=g.data("height")):c=g.height(),f.options.dropupAuto&&f.$newElement.toggleClass("dropup",d>e&&c>b-r),f.$newElement.hasClass("dropup")&&(b=d-r),i=s.length+u.length>3?3*k+r-2:0,g.css({"max-height":b+"px",overflow:"hidden","min-height":i+l+m+n+o+"px"}),h.css({"max-height":b-l-m-n-o-q+"px","overflow-y":"auto","min-height":Math.max(i-q,0)+"px"})};u(),this.$searchbox.off("input.getSize propertychange.getSize").on("input.getSize propertychange.getSize",u),i.off("resize.getSize scroll.getSize").on("resize.getSize scroll.getSize",u)}else if(this.options.size&&"auto"!=this.options.size&&this.$lis.not(s).length>this.options.size){var v=this.$lis.not(".divider").not(s).children().slice(0,this.options.size).last().parent().index(),w=this.$lis.slice(0,v+1).filter(".divider").length;b=k*this.options.size+w*p+q,f.options.container?(g.data("height")||g.data("height",g.height()),c=g.data("height")):c=g.height(),f.options.dropupAuto&&this.$newElement.toggleClass("dropup",d>e&&c>b-r),g.css({"max-height":b+l+m+n+o+"px",overflow:"hidden","min-height":""}),h.css({"max-height":b-q+"px","overflow-y":"auto","min-height":""})}}},setWidth:function(){if("auto"===this.options.width){this.$menu.css("min-width","0");var a=this.$menu.parent().clone().appendTo("body"),b=this.options.container?this.$newElement.clone().appendTo("body"):a,c=a.children(".dropdown-menu").outerWidth(),d=b.css("width","auto").children("button").outerWidth();a.remove(),b.remove(),this.$newElement.css("width",Math.max(c,d)+"px")}else"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width",""));this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement.removeClass("fit-width")},selectPosition:function(){var b,c,d=this,e="<div />",f=a(e),g=function(a){f.addClass(a.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass("dropup",a.hasClass("dropup")),b=a.offset(),c=a.hasClass("dropup")?0:a[0].offsetHeight,f.css({top:b.top+c,left:b.left,width:a[0].offsetWidth,position:"absolute"})};this.$newElement.on("click",function(){d.isDisabled()||(g(a(this)),f.appendTo(d.options.container),f.toggleClass("open",!a(this).hasClass("open")),f.append(d.$menu))}),a(window).on("resize scroll",function(){g(d.$newElement)}),this.$element.on("hide.bs.select",function(){d.$menu.data("height",d.$menu.height()),f.detach()})},setSelected:function(a,b,c){if(!c)var c=this.findLis().eq(this.liObj[a]);c.toggleClass("selected",b)},setDisabled:function(a,b,c){if(!c)var c=this.findLis().eq(this.liObj[a]);b?c.addClass("disabled").children("a").attr("href","#").attr("tabindex",-1):c.removeClass("disabled").children("a").removeAttr("href").attr("tabindex",0)},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){var a=this;this.isDisabled()?(this.$newElement.addClass("disabled"),this.$button.addClass("disabled").attr("tabindex",-1)):(this.$button.hasClass("disabled")&&(this.$newElement.removeClass("disabled"),this.$button.removeClass("disabled")),-1!=this.$button.attr("tabindex")||this.$element.data("tabindex")||this.$button.removeAttr("tabindex")),this.$button.click(function(){return!a.isDisabled()})},tabIndex:function(){this.$element.is("[tabindex]")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex")))},clickListener:function(){var b=this,c=a(document);this.$newElement.on("touchstart.dropdown",".dropdown-menu",function(a){a.stopPropagation()}),c.data("spaceSelect",!1),this.$button.on("keyup",function(a){/(32)/.test(a.keyCode.toString(10))&&c.data("spaceSelect")&&(a.preventDefault(),c.data("spaceSelect",!1))}),this.$newElement.on("click",function(){b.setSize(),b.$element.on("shown.bs.select",function(){if(b.options.liveSearch||b.multiple){if(!b.multiple){var a=b.liObj[b.$element[0].selectedIndex];if("number"!=typeof a||b.options.size===!1)return;var c=b.$lis.eq(a)[0].offsetTop-b.$menuInner[0].offsetTop;c=c-b.$menuInner[0].offsetHeight/2+b.sizeInfo.liHeight/2,b.$menuInner[0].scrollTop=c}}else b.$menu.find(".selected a").focus()})}),this.$menu.on("click","li a",function(c){var d=a(this),e=d.parent().data("originalIndex"),f=b.$element.val(),g=b.$element.prop("selectedIndex");if(b.multiple&&c.stopPropagation(),c.preventDefault(),!b.isDisabled()&&!d.parent().hasClass("disabled")){var h=b.$element.find("option"),i=h.eq(e),j=i.prop("selected"),k=i.parent("optgroup"),l=b.options.maxOptions,m=k.data("maxOptions")||!1;if(b.multiple){if(i.prop("selected",!j),b.setSelected(e,!j),d.blur(),l!==!1||m!==!1){var n=l<h.filter(":selected").length,o=m<k.find("option:selected").length;if(l&&n||m&&o)if(l&&1==l)h.prop("selected",!1),i.prop("selected",!0),b.$menu.find(".selected").removeClass("selected"),b.setSelected(e,!0);else if(m&&1==m){k.find("option:selected").prop("selected",!1),i.prop("selected",!0);var p=d.parent().data("optgroup");b.$menu.find('[data-optgroup="'+p+'"]').removeClass("selected"),b.setSelected(e,!0)}else{var q="function"==typeof b.options.maxOptionsText?b.options.maxOptionsText(l,m):b.options.maxOptionsText,r=q[0].replace("{n}",l),s=q[1].replace("{n}",m),t=a('<div class="notify"></div>');q[2]&&(r=r.replace("{var}",q[2][l>1?0:1]),s=s.replace("{var}",q[2][m>1?0:1])),i.prop("selected",!1),b.$menu.append(t),l&&n&&(t.append(a("<div>"+r+"</div>")),b.$element.trigger("maxReached.bs.select")),m&&o&&(t.append(a("<div>"+s+"</div>")),b.$element.trigger("maxReachedGrp.bs.select")),setTimeout(function(){b.setSelected(e,!1)},10),t.delay(750).fadeOut(300,function(){a(this).remove()})}}}else h.prop("selected",!1),i.prop("selected",!0),b.$menu.find(".selected").removeClass("selected"),b.setSelected(e,!0);b.multiple?b.options.liveSearch&&b.$searchbox.focus():b.$button.focus(),(f!=b.$element.val()&&b.multiple||g!=b.$element.prop("selectedIndex")&&!b.multiple)&&(b.$element.triggerNative("change"),b.$element.trigger("changed.bs.select",[e,i.prop("selected"),j]))}}),this.$menu.on("click","li.disabled a, .popover-title, .popover-title :not(.close)",function(c){c.currentTarget==this&&(c.preventDefault(),c.stopPropagation(),b.options.liveSearch&&!a(c.target).hasClass("close")?b.$searchbox.focus():b.$button.focus())}),this.$menu.on("click","li.divider, li.dropdown-header",function(a){a.preventDefault(),a.stopPropagation(),b.options.liveSearch?b.$searchbox.focus():b.$button.focus()}),this.$menu.on("click",".popover-title .close",function(){b.$button.click()}),this.$searchbox.on("click",function(a){a.stopPropagation()}),this.$menu.on("click",".actions-btn",function(c){b.options.liveSearch?b.$searchbox.focus():b.$button.focus(),c.preventDefault(),c.stopPropagation(),a(this).hasClass("bs-select-all")?b.selectAll():b.deselectAll(),b.$element.triggerNative("change")}),this.$element.change(function(){b.render(!1)})},liveSearchListener:function(){var d=this,e=a('<li class="no-results"></li>');this.$newElement.on("click.dropdown.data-api touchstart.dropdown.data-api",function(){d.$menuInner.find(".active").removeClass("active"),d.$searchbox.val()&&(d.$searchbox.val(""),d.$lis.not(".is-hidden").removeClass("hidden"),e.parent().length&&e.remove()),d.multiple||d.$menuInner.find(".selected").addClass("active"),setTimeout(function(){d.$searchbox.focus()},10)}),this.$searchbox.on("click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api",function(a){a.stopPropagation()}),this.$searchbox.on("input propertychange",function(){if(d.$searchbox.val()){var f=d.$lis.not(".is-hidden").removeClass("hidden").children("a");f=d.options.liveSearchNormalize?f.not(":a"+d._searchStyle()+'("'+b(d.$searchbox.val())+'")'):f.not(":"+d._searchStyle()+'("'+d.$searchbox.val()+'")'),f.parent().addClass("hidden"),d.$lis.filter(".dropdown-header").each(function(){var b=a(this),c=b.data("optgroup");0===d.$lis.filter("[data-optgroup="+c+"]").not(b).not(".hidden").length&&(b.addClass("hidden"),d.$lis.filter("[data-optgroup="+c+"div]").addClass("hidden"))});var g=d.$lis.not(".hidden");g.each(function(b){var c=a(this);c.hasClass("divider")&&(c.index()===g.eq(0).index()||c.index()===g.last().index()||g.eq(b+1).hasClass("divider"))&&c.addClass("hidden")}),d.$lis.not(".hidden, .no-results").length?e.parent().length&&e.remove():(e.parent().length&&e.remove(),e.html(d.options.noneResultsText.replace("{0}",'"'+c(d.$searchbox.val())+'"')).show(),d.$menuInner.append(e))}else d.$lis.not(".is-hidden").removeClass("hidden"),e.parent().length&&e.remove();d.$lis.filter(".active").removeClass("active"),d.$searchbox.val()&&d.$lis.not(".hidden, .divider, .dropdown-header").eq(0).addClass("active").children("a").focus(),a(this).focus()})},_searchStyle:function(){var a="icontains";switch(this.options.liveSearchStyle){case"begins":case"startsWith":a="ibegins";break;case"contains":}return a},val:function(a){return"undefined"!=typeof a?(this.$element.val(a),this.render(),this.$element):this.$element.val()},selectAll:function(){this.findLis(),this.$element.find("option:enabled").not("[data-divider], [data-hidden]").prop("selected",!0),this.$lis.not(".divider, .dropdown-header, .disabled, .hidden").addClass("selected"),this.render(!1)},deselectAll:function(){this.findLis(),this.$element.find("option:enabled").not("[data-divider], [data-hidden]").prop("selected",!1),this.$lis.not(".divider, .dropdown-header, .disabled, .hidden").removeClass("selected"),this.render(!1)},keydown:function(c){var d,e,f,g,h,i,j,k,l,m=a(this),n=m.is("input")?m.parent().parent():m.parent(),o=n.data("this"),p=":not(.disabled, .hidden, .dropdown-header, .divider)",q={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"};if(o.options.liveSearch&&(n=m.parent().parent()),o.options.container&&(n=o.$menu),d=a("[role=menu] li a",n),l=o.$menu.parent().hasClass("open"),!l&&(c.keyCode>=48&&c.keyCode<=57||c.keyCode>=65&&c.keyCode<=90)&&(o.options.container?o.$newElement.trigger("click"):(o.setSize(),o.$menu.parent().addClass("open"),l=!0),o.$searchbox.focus()),o.options.liveSearch&&(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&0===o.$menu.find(".active").length&&(c.preventDefault(),o.$menu.parent().removeClass("open"),o.options.container&&o.$newElement.removeClass("open"),o.$button.focus()),d=a("[role=menu] li:not(.disabled, .hidden, .dropdown-header, .divider)",n),m.val()||/(38|40)/.test(c.keyCode.toString(10))||0===d.filter(".active").length&&(d=o.$newElement.find("li"),d=o.options.liveSearchNormalize?d.filter(":a"+o._searchStyle()+"("+b(q[c.keyCode])+")"):d.filter(":"+o._searchStyle()+"("+q[c.keyCode]+")"))),d.length){if(/(38|40)/.test(c.keyCode.toString(10)))e=d.index(d.filter(":focus")),g=d.parent(p).first().data("originalIndex"),h=d.parent(p).last().data("originalIndex"),f=d.eq(e).parent().nextAll(p).eq(0).data("originalIndex"),i=d.eq(e).parent().prevAll(p).eq(0).data("originalIndex"),j=d.eq(f).parent().prevAll(p).eq(0).data("originalIndex"),o.options.liveSearch&&(d.each(function(b){a(this).hasClass("disabled")||a(this).data("index",b)}),e=d.index(d.filter(".active")),g=d.first().data("index"),h=d.last().data("index"),f=d.eq(e).nextAll().eq(0).data("index"),i=d.eq(e).prevAll().eq(0).data("index"),j=d.eq(f).prevAll().eq(0).data("index")),k=m.data("prevIndex"),38==c.keyCode?(o.options.liveSearch&&(e-=1),e!=j&&e>i&&(e=i),g>e&&(e=g),e==k&&(e=h)):40==c.keyCode&&(o.options.liveSearch&&(e+=1),-1==e&&(e=0),e!=j&&f>e&&(e=f),e>h&&(e=h),e==k&&(e=g)),m.data("prevIndex",e),o.options.liveSearch?(c.preventDefault(),m.hasClass("dropdown-toggle")||(d.removeClass("active").eq(e).addClass("active").children("a").focus(),m.focus())):d.eq(e).focus();else if(!m.is("input")){var r,s,t=[];d.each(function(){a(this).parent().hasClass("disabled")||a.trim(a(this).text().toLowerCase()).substring(0,1)==q[c.keyCode]&&t.push(a(this).parent().index())}),r=a(document).data("keycount"),r++,a(document).data("keycount",r),s=a.trim(a(":focus").text().toLowerCase()).substring(0,1),s!=q[c.keyCode]?(r=1,a(document).data("keycount",r)):r>=t.length&&(a(document).data("keycount",0),r>t.length&&(r=1)),d.eq(t[r-1]).focus()}if((/(13|32)/.test(c.keyCode.toString(10))||/(^9$)/.test(c.keyCode.toString(10))&&o.options.selectOnTab)&&l){if(/(32)/.test(c.keyCode.toString(10))||c.preventDefault(),o.options.liveSearch)/(32)/.test(c.keyCode.toString(10))||(o.$menu.find(".active a").click(),m.focus());else{var u=a(":focus");u.click(),u.focus(),c.preventDefault(),a(document).data("spaceSelect",!0)}a(document).data("keycount",0)}(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(o.multiple||o.options.liveSearch)||/(27)/.test(c.keyCode.toString(10))&&!l)&&(o.$menu.parent().removeClass("open"),o.options.container&&o.$newElement.removeClass("open"),o.$button.focus())}},mobile:function(){this.$element.addClass("mobile-device").appendTo(this.$newElement),this.options.container&&this.$menu.hide()},refresh:function(){this.$lis=null,this.liObj={},this.reloadLi(),this.render(),this.checkDisabled(),this.liHeight(!0),this.setStyle(),this.setWidth(),this.$lis&&this.$searchbox.trigger("propertychange"),this.$element.trigger("refreshed.bs.select")},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()}};var f=a.fn.selectpicker;a.fn.selectpicker=d,a.fn.selectpicker.Constructor=e,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=f,this},a(document).data("keycount",0).on("keydown",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="menu"], .bs-searchbox input',e.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="menu"], .bs-searchbox input',function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);d.call(b,b.data())})})}(jQuery)});
//# sourceMappingURL=bootstrap-select.js.map
\ No newline at end of file
...@@ -13,15 +13,17 @@ ...@@ -13,15 +13,17 @@
<meta name="description" content="Gargantext"> <meta name="description" content="Gargantext">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{% static 'bower_components/bootstrap/dist/css/bootstrap.min.css' %}"> <link rel="stylesheet" href="{% static 'bower_components/bootstrap/dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'bower_components/bootstrap-select/dist/css/bootstrap-select.min.css' %}">
<link rel="stylesheet" href="{% static 'bower_components/angular/angular-csp.css' %}"> <link rel="stylesheet" href="{% static 'bower_components/angular/angular-csp.css' %}">
<link rel="stylesheet" href="{% static 'annotations/app.css' %}"> <link rel="stylesheet" href="{% static 'annotations/app.css' %}">
<script src="{% static 'bower_components/jquery/dist/jquery.min.js' %}"></script> <script src="{% static 'bower_components/jquery/dist/jquery.min.js' %}"></script>
</head> </head>
<body> <body>
<!-- TODO integrate this later into the any other django template --> <!-- TODO integrate this later into the any other django template -->
<div id="annotationsApp"> <div id="annotationsApp" ng-cloak>
<div class="container-fluid"> <div class="container-fluid">
<div class="row-fluid main-panel" ng-controller="IntraTextController"> <div class="row-fluid main-panel" ng-controller="NGramHighlightController">
<div class="col-md-4 col-xs-4 tabbable words-panel"> <div class="col-md-4 col-xs-4 tabbable words-panel">
<ul class="nav nav-pills nav-justified"> <ul class="nav nav-pills nav-justified">
<li ng-repeat="(listId, listName) in activeLists" ng-class="{active: $first == true}"> <li ng-repeat="(listId, listName) in activeLists" ng-class="{active: $first == true}">
...@@ -29,65 +31,78 @@ ...@@ -29,65 +31,78 @@
</li> </li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div ng-controller="ExtraTextPaginationController" ng-repeat="(listId, listName) in activeLists" ng-class="{active: $first == true}" class="tab-pane" id="tab-{[{listId}]}"> <div ng-controller="NgramListPaginationController" ng-repeat="(listId, listName) in activeLists" ng-class="{active: $first == true}" class="tab-pane" id="tab-{[{listId}]}">
<div ng-if="extraNgramList[listId].length == 0" class="alert alert-info" role="alert"> <div ng-if="extraNgramList[listId].length == 0" class="alert alert-info" role="alert">
Input any keyword you want to link to this article and the list named '{[{listName}]}' Input any keyword you want to link to this article and the list named '{[{listName}]}'
</div> </div>
<ul class="list-group words-list clearfix"> <ul class="list-group words-list clearfix">
<li ng-repeat="keyword in extraNgramList[listId] | startFrom:currentListPage * pageSize | limitTo:pageSize" class="list-group-item"> <li ng-repeat="keyword in extraNgramList[listId] | startFrom:currentListPage * pageSize | limitTo:pageSize" class="keyword-group-item">
<div ng-controller="ExtraAnnotationController" keyword-template class="keyword-container"></div> <div ng-controller="NgramController" keyword-template class="keyword-container"></div>
</li> </li>
</ul> </ul>
<nav ng-class="{invisible: totalListPages - 1 == 0}" class="clearfix"> <nav ng-class="{invisible: totalListPages(listId) - 1 == 0}" class="clearfix">
<ul class="pagination pagination-s pull-right words-pagination"> <ul class="pagination pagination-s pull-right words-pagination">
<li ng-class="{disabled: currentListPage == 0}"><a ng-click="previousListPage()" class="glyphicon glyphicon-backward"></a></li> <li ng-class="{'disabled': currentListPage == 0}"><a ng-click="previousListPage()" class="glyphicon glyphicon-backward"></a></li>
<li ng-class="{disabled: currentListPage >= totalListPages - 1}"><a ng-click="nextListPage()" class="glyphicon glyphicon-forward"></a></li> <li ng-class="{'disabled': currentListPage >= totalListPages(listId) - 1}"><a ng-click="nextListPage()" class="glyphicon glyphicon-forward"></a></li>
</ul> </ul>
</nav> </nav>
<div class="form-group"> <div class="form-group" ng-controller="NgramInputController">
<input type="text" class="form-control" id="{[{ listId }]}-input" ng-keypress="onListSubmit($event, listId)"> <input autosave="search" maxlength="240" placeholder="Add any text" type="text" class="form-control" id="{[{listId}]}-input" ng-keypress="onListSubmit($event, listId)">
<button type="submit" class="form-control btn btn-default btn-primary" ng-click="onListSubmit($event, listId)">Add to {[{listName}]}</button> <button type="submit" class="form-control btn btn-default btn-primary" ng-click="onListSubmit($event, listId)">Add to {[{listName}]}</button>
</div> </div>
</div> </div>
</div> </div>
<div>
<h5>Select lists</h5>
<select class="selectpicker" multiple ng-change="activeListsChange()" ng-model="lists" ng-controller="ActiveListsController">
<option ng-repeat="item in allListsSelect" id="list---{[{item.id}]}" ng-disabled="{[{ item.label == 'MiamList' }]}">{[{item.label}]}</option>
</select>
</div>
</div> </div>
<div class="col-md-8 col-xs-8 text-panel" ng-controller="DocController" id="document"> <div class="col-md-8 col-xs-8 text-panel" ng-controller="DocController" id="document">
<div class="row-fluid clearfix"> <div class="row-fluid clearfix">
<div class="col-md-7 col-xs-7"> <div class="col-md-10 col-xs-10">
<h3 class="text-container" id="title">{[{title}]}</h3> <h3 class="text-container" id="title">{[{title}]}</h3>
</div> </div>
<div class="col-md-5 col-xs-5"> <div class="col-md-2 col-xs-2 clearfix">
<nav> <button ng-controller="DocFavoriteController" type="button" class="btn btn-default float-right" ng-click="onStarClick($event)">
<span class="glyphicon" ng-class="{'glyphicon-star-empty': isFavorite == false, 'glyphicon-star': isFavorite == true}"></span>
</button>
<!--<nav>
<ul class="pager"> <ul class="pager">
<li ng-if="current_page_number > 1"><a ng-click="onPreviousClick()" href="#">Previous</a></li> <li ng-if="current_page_number > 1"><a ng-click="onPreviousClick()" href="#">Previous</a></li>
<li ng-if="current_page_number < last_page_number"><a ng-click="onNextClick()" href="#">Next</a></li> <li ng-if="current_page_number < last_page_number"><a ng-click="onNextClick()" href="#">Next</a></li>
</ul> </ul>
</nav> </nav>-->
</div> </div>
</div> </div>
<div class="row-fluid cleafix"> <div class="row-fluid">
<ul class="breadcrumb"> <ul class="list-group clearfix">
<li>{[{authors}]}</li> <li class="list-group-item small"><span class="badge">journal</span>{[{journal}]}</li>
<li>{[{journal}]}</li> <li class="list-group-item small"><span class="badge">authors</span>{[{authors}]}</li>
<li class="active pull-right">{[{publication_date}]}</li> <li class="list-group-item small"><span class="badge">date</span>{[{publication_date}]}</li>
</ul> </ul>
</div> </div>
<h4 ng-if="abstract_text != null">Abstract</h4> <div ng-if="abstract_text != null">
<span class="badge">abstract</span>
</div>
<p id="abstract-text" class="text-container"> <p id="abstract-text" class="text-container">
<div ng-if="abstract_text == null" class="alert alert-info" role="alert">Empty abstract text</div> <div ng-if="abstract_text == null" class="alert alert-info small" role="alert">Empty abstract text</div>
</p> </p>
<h4 ng-if="full_text != null">Full Article</h4> <div ng-if="full_text != null">
<span class="badge">full article</span>
</div>
<p id="full-text" class="text-container"> <p id="full-text" class="text-container">
<div ng-if="full_text == null" class="alert alert-info" role="alert">Empty full text</div> <div ng-if="full_text == null" class="alert alert-info small" role="alert">Empty full text</div>
</p> </p>
</div> </div>
</div> <!-- end of the main row --> </div> <!-- end of the main row -->
</div> </div>
<!-- this menu is over the text on mouse selection --> <!-- this menu is over the text on mouse selection -->
<div ng-controller="AnnotationMenuController" id="selection" class="selection-menu"> <div ng-controller="TextSelectionMenuController" id="selection" class="selection-menu">
<ul class="noselection"> <ul class="noselection">
<li ng-repeat="item in menuItems" class="{[{item.listName}]}" ng-click="onMenuClick($event, item.action, item.listId)">{[{item.verb}]} {[{item.listName}]}</li> <li ng-repeat="item in menuItems" class="{[{item.listName}]}" ng-click="onMenuClick($event, item.action, item.listId)">{[{item.verb}]} {[{item.listName}]}</li>
</ul> </ul>
......
...@@ -5,6 +5,7 @@ from annotations import views ...@@ -5,6 +5,7 @@ from annotations import views
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^document/(?P<doc_id>[0-9]+)$', views.Document.as_view()), # document view url(r'^document/(?P<doc_id>[0-9]+)$', views.Document.as_view()), # document view
url(r'^corpus/(?P<corpus_id>[0-9]+)/document/(?P<doc_id>[0-9]+)$', views.NgramList.as_view()), # the list associated with an ngram url(r'^corpus/(?P<corpus_id>[0-9]+)/document/(?P<doc_id>[0-9]+)$', views.NgramList.as_view()), # the list associated with an ngram
url(r'^lists/(?P<list_id>[0-9]+)/ngrams(?:/(?P<ngram_id>[0-9]+))?$', views.NgramEdit.as_view()), # url(r'^lists/(?P<list_id>[0-9]+)/ngrams/(?P<ngram_id>[0-9]+)$', views.NgramEdit.as_view()), #
url(r'^lists/(?P<list_id>[0-9]+)/multiple?$', views.deleteMultiple ), # url(r'^lists/(?P<list_id>[0-9]+)/ngrams/create$', views.NgramCreate.as_view()), #
url(r'^lists/(?P<list_id>[0-9]+)/multiple?$', views.deleteMultiple ), # FIXME What's this ?
) )
...@@ -17,7 +17,6 @@ from node.models import Node ...@@ -17,7 +17,6 @@ from node.models import Node
from gargantext_web.db import * from gargantext_web.db import *
from ngram.lists import listIds, listNgramIds, ngramList from ngram.lists import listIds, listNgramIds, ngramList
from gargantext_web.api import JsonHttpResponse from gargantext_web.api import JsonHttpResponse
import json
@login_required @login_required
...@@ -45,12 +44,8 @@ class NgramList(APIView): ...@@ -45,12 +44,8 @@ class NgramList(APIView):
list_id = listIds(user_id=request.user.id, corpus_id=int(corpus_id), typeList=list_type) list_id = listIds(user_id=request.user.id, corpus_id=int(corpus_id), typeList=list_type)
lists["%s" % list_id[0][0]] = list_type lists["%s" % list_id[0][0]] = list_type
# ngrams of list_id of corpus_id: # ngrams for the corpus_id (ignoring doc_id for the moment):
doc_ngram_list = listNgramIds(corpus_id=corpus_id, doc_id=doc_id, user_id=request.user.id) doc_ngram_list = listNgramIds(corpus_id=corpus_id, doc_id=None, user_id=request.user.id)
doc_ngram_list = [(i, 'miam', i, 1931) for i in range(500)]
doc_ngram_list += [(i, 'stop', i, 1932) for i in range(501, 600)]
# doc_ngram_list = [(1, 'miam', 2, 1931), (2, 'stop', 2, 1932), (3, 'Potassium channels', 4, 1931)]
data = { '%s' % corpus_id : { data = { '%s' % corpus_id : {
'%s' % doc_id : [ '%s' % doc_id : [
{ {
...@@ -67,50 +62,83 @@ class NgramList(APIView): ...@@ -67,50 +62,83 @@ class NgramList(APIView):
class NgramEdit(APIView): class NgramEdit(APIView):
""" """
Actions on one Ngram in one list Actions on one existing Ngram in one list
""" """
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
authentication_classes = (SessionAuthentication, BasicAuthentication) authentication_classes = (SessionAuthentication, BasicAuthentication)
def post(self, request, list_id, ngram_id): def post(self, request, list_id, ngram_id):
""" """
Add a ngram in a list Edit an existing NGram in a given list
"""
list_id = int(list_id)
ngram_id = int(ngram_id)
# TODO remove the node_ngram from another conflicting list
# FIXME session.query(Node_Ngram).filter(Node_Ngram.ngram_id==ngram_id).delete()
# add the ngram to the list
node_ngram = Node_Ngram(node_id=list_id, ngram_id=ngram_id, weight=1.0)
session.add(node_ngram)
session.commit()
# return the response
return Response({
'uuid': ngram_id,
'list_id': list_id,
})
def delete(self, request, list_id, ngram_id):
"""
Delete a ngram from a list
"""
session.query(Node_Ngram).filter(Node_Ngram.node_id==list_id).filter(Node_Ngram.ngram_id==ngram_id).delete()
session.commit()
return Response(None, 204)
class NgramCreate(APIView):
"""
Create a new Ngram in one list
"""
renderer_classes = (JSONRenderer,)
authentication_classes = (SessionAuthentication, BasicAuthentication)
def post(self, request, list_id):
"""
create NGram in a given list
""" """
# TODO - if Ngram is in miam-list, and adding it to stop-list,
# then remove it from the previous list
list_id = int(list_id) list_id = int(list_id)
# format the ngram's text # format the ngram's text
ngram_text = request.data.get('annotation', {}).get('text', None) ngram_text = request.data.get('text', None)
if ngram_text is None:
raise APIException("Could not create a new Ngram without one \
text key in the json body")
ngram_text = ngram_text.strip().lower() ngram_text = ngram_text.strip().lower()
ngram_text = ' '.join(ngram_text.split()) ngram_text = ' '.join(ngram_text.split())
# retrieve the ngram's id # check if the ngram exists with the same terms
ngram = session.query(Ngram).filter(Ngram.terms == ngram_text).first() ngram = session.query(Ngram).filter(Ngram.terms == ngram_text).first()
if ngram is None: if ngram is None:
ngram = Ngram(n=len(ngram_text.split()), terms=ngram_text) ngram = Ngram(n=len(ngram_text.split()), terms=ngram_text)
session.add(ngram) else:
session.commit() # make sure the n value is correct
ngram.n = len(ngram_text.split())
session.add(ngram)
session.commit()
ngram_id = ngram.id ngram_id = ngram.id
# add the ngram to the list if not already done # create the new node_ngram relation
node_ngram = session.query(Node_Ngram).filter(Node_Ngram.node_id==list_id).filter(Node_Ngram.ngram_id==ngram_id).first() # TODO check existing Node_Ngram ?
if node_ngram is None: node_ngram = Node_Ngram(node_id=list_id, ngram_id=ngram_id, weight=1.0)
node_ngram = Node_Ngram(node_id=list_id, ngram_id=ngram_id, weight=1.0) session.add(node_ngram)
session.add(node_ngram) session.commit()
session.commit()
ngram_occurrences = node_ngram.weight
# return the response # return the response
return Response({ return Response({
'uuid': ngram_id, 'uuid': ngram_id,
'text': ngram_text, 'text': ngram_text,
'occurrences': ngram_occurrences,
'list_id': list_id, 'list_id': list_id,
}) })
def delete(self, request, list_id, ngram_id):
"""
Delete a ngram from a list
"""
session.query(Node_Ngram).filter(Node_Ngram.node_id==list_id).filter(Node_Ngram.ngram_id==ngram_id).delete()
return Response(None, 204)
def deleteMultiple(request, list_id): def deleteMultiple(request, list_id):
results = ["hola","mundo"] results = ["hola","mundo"]
......
...@@ -103,6 +103,7 @@ def listNgramIds(list_id=None, typeList=None, ...@@ -103,6 +103,7 @@ def listNgramIds(list_id=None, typeList=None,
.group_by(Ngram.id, ListNgram.node_id) .group_by(Ngram.id, ListNgram.node_id)
) )
# FIXME this is only used to filter on 1 document
if doc_id is not None: if doc_id is not None:
Doc = aliased(Node) Doc = aliased(Node)
DocNgram = aliased(NodeNgram) DocNgram = aliased(NodeNgram)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment