From dd016e97810e226bfe2f3ff4e9295297629061ea Mon Sep 17 00:00:00 2001
From: Elias <elias@stupeflix.com>
Date: Sat, 27 Jun 2015 16:24:31 +0200
Subject: [PATCH] Annotations : refactored lists, generic lists now

---
 annotations/static/annotations/app.css        |  42 +-
 annotations/static/annotations/app.js         | 385 ++++++++++--------
 .../static/annotations/keyword_tpl.html       |   6 +-
 .../static/annotations/selection_tpl.html     |   9 +-
 annotations/templates/annotations/main.html   |  61 ++-
 annotations/views.py                          |   6 +-
 6 files changed, 267 insertions(+), 242 deletions(-)

diff --git a/annotations/static/annotations/app.css b/annotations/static/annotations/app.css
index 2433f49b..98aa0972 100644
--- a/annotations/static/annotations/app.css
+++ b/annotations/static/annotations/app.css
@@ -1,5 +1,21 @@
 /* app css stylesheet */
 
+/*
+* Class names corresponding to server-side list names
+* To display another list name, please add a new class under this
+*/
+.MiamList {
+  color: black;
+  background-color: rgba(60, 118, 61, 0.5);
+  cursor: pointer;
+}
+
+.StopList {
+  color: black;
+  background-color: rgba(169, 68, 66, 0.2);
+  cursor: pointer;
+}
+
 .delete-keyword, .occurrences {
     vertical-align: super;
     font-size: 70%;
@@ -27,27 +43,9 @@
   border-bottom: none;
 }
 
-.miamword {
-  color: black;
-  background-color: rgba(60, 118, 61, 0.5);
-  cursor: pointer;
-}
-
-.stopword {
-  color: black;
-  background-color: rgba(169, 68, 66, 0.2);
-  cursor: pointer;
-}
-
-.global-stopword {
-  color: black;
-  background-color: rgba(169, 68, 66, 0.05);
-  cursor: pointer;
-}
-
 .main-panel, .text-panel, .words-panel {
   height: 800px;
-  margin: 10px 0px;
+  margin: 10px 0;
 }
 
 .text-panel {
@@ -59,11 +57,7 @@
   height: 250px;
 }
 
-.keyword-container {
-  /*display: inline-block;*/
-}
-
-.keyword {
+.keyword-text {
   word-break: break-all;
 }
 
diff --git a/annotations/static/annotations/app.js b/annotations/static/annotations/app.js
index 1492d3cd..eabffca6 100644
--- a/annotations/static/annotations/app.js
+++ b/annotations/static/annotations/app.js
@@ -10,6 +10,9 @@
     $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) {
@@ -18,6 +21,9 @@
     };
   });
 
+  /*
+  * For ngram elements displayed in the flat lists (called "extra-text")
+  */
   window.annotationsApp.controller('ExtraAnnotationController',
     ['$scope', '$rootScope', '$element', 'NgramHttpService',
     function ($scope, $rootScope, $element, NgramHttpService) {
@@ -34,9 +40,12 @@
             }
           });
         });
-      }
+      };
     }]);
 
+  /*
+  * For mouse selection on the text
+  */
   window.annotationsApp.controller('AnnotationController',
     ['$scope', '$rootScope', '$element',
       function ($scope, $rootScope, $element) {
@@ -57,14 +66,9 @@
       };
   }]);
 
-  window.annotationsApp.directive('selectionTemplate', function () {
-    return {
-      templateUrl: function ($element, $attributes) {
-        return S + 'annotations/selection_tpl.html';
-      }
-    };
-  });
-
+  /*
+  * Controller of the menu over the current mouse selection
+  */
   window.annotationsApp.controller('AnnotationMenuController',
     ['$scope', '$rootScope', '$element', '$timeout', 'NgramHttpService',
     function ($scope, $rootScope, $element, $timeout, NgramHttpService) {
@@ -87,9 +91,12 @@
           }
           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");
@@ -98,53 +105,68 @@
         }
       }
 
+      /*
+      * 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)) {
-              $scope.level = angular.copy(annotation.level || 'global');
-              $scope.category = $rootScope.lists[annotation.list_id].toLowerCase();
-              $scope.listId = angular.copy(annotation.list_id);
-              // used in onClick
-              $scope.selection_text = angular.copy(annotation);
-
-              if ($scope.category == "miamlist") {
-                $scope.local_miamlist = false;
-                $scope.global_stoplist = true;
-                $scope.local_stoplist = true;
-              } else if ($scope.category == "stoplist") {
-
-                if ($scope.level == "local") {
-                  $scope.local_stoplist = false;
-                  $scope.global_stoplist = true;
+              // 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 ($scope.level == "global") {
-                  $scope.global_stoplist = false;
-                  $scope.local_stoplist = true;
-                }
-                $scope.local_miamlist = true;
+              ];
+              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 menu
+              // show the menu
               $element.fadeIn(100);
-            }
-            else if (annotation.trim() !== "") {
-              $scope.selection_text = angular.copy(annotation);
-              $scope.level = "New Ngram from selection";
-              $scope.category = null;
-              $scope.local_miamlist = true;
-              $scope.local_stoplist = true;
-              $scope.global_stoplist = true;
-              // show menu
+            } 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 {
-              // close menu
+              $scope.menuItems = [];
+              // close the menu
               $element.fadeOut(100);
             }
           });
         });
       }
-      var elt = $(".text-panel")[0];
+
       var pos = $(".text-panel").position();
 
       function positionElement(context, x, y) {
@@ -157,18 +179,26 @@
         positionElement(null, e.pageX, e.pageY);
       }
 
-      // TODO is mousedown necessary ?
-      $(".text-panel").mousedown(function(){
-        $(".text-panel").mousemove(positionMenu);
+      /*
+      * Dynamically position the menu
+      */
+      $(".text-container").mousedown(function(){
+        $(".text-container").mousemove(positionMenu);
       });
 
-      $(".text-panel").mouseup(function(){
-        $(".text-panel").unbind("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());
       });
 
-      $(".text-panel").delegate(':not("#selection")', "click", function(e) {
+      /*
+      * 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());
@@ -178,7 +208,10 @@
       $rootScope.$on("positionAnnotationMenu", positionElement);
       $rootScope.$on("toggleAnnotationMenu", toggleMenu);
 
-      $scope.onClick = function($event, action, listId, level) {
+      /*
+      * Menu click action
+      */
+      $scope.onMenuClick = function($event, action, listId) {
         if (angular.isObject($scope.selection_text)) {
           // delete from the current list
           NgramHttpService[action]({
@@ -204,43 +237,63 @@
             $rootScope.annotations.push(data);
           });
         }
-        // hide selection highlighted text and the menu
+        // 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) {
 
-      $scope.extra_stoplist = [];
-      $scope.extra_miamlist = [];
-      $scope.currentStopPage = 0;
-      $scope.currentMiamPage = 0;
+      $scope.extraNgramList = {};
+      $scope.currentListPage = angular.forEach($rootScope.activeLists, function(name, id) {
+        this[id] = 0;
+      }, {});
+
       $scope.pageSize = 15;
       var counter = 0;
 
       /*
-      * Replace the text by and html template
+      * Replace the text by an html template for ngram keywords
       */
-      function replaceTextByTemplate(text, annotation, template, pattern, lists) {
+      function replaceTextByTemplate(text, ngram, template, pattern, lists) {
         return text.replace(pattern, function(matched) {
           var tpl = angular.element(template);
           tpl.append(matched);
-          tpl.attr('title', annotation.tooltip_content);
-          tpl.attr('uuid', annotation.uuid);
-
-          if ('MiamList' == lists[annotation.list_id]) tpl.addClass("miamword");
-          if ('StopList' == lists[annotation.list_id]) tpl.addClass("stopword");
-          //if (annotation.category == 'stoplist' && annotation.level == 'global') tpl.addClass("global-stopword");
-
+          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;
         });
       }
 
-      function compileText(annotations, fullText, abstractText, $rootScope) {
+      /*
+       * 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 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'>";
@@ -251,97 +304,93 @@
         var startPattern = "\\b((?:"+templateBeginRegexp+")*";
         var middlePattern = "(?:<\/span>)*\\s(?:"+templateBeginRegexp+")*";
         var endPattern = "(?:<\/span>)*)\\b";
-        /*
-         * Sorts annotations on the number of words
-         */
-        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;
-        }
 
-        var sortedSizeAnnotations = lengthSort(annotations, "text");
-        var extra_stoplist = [],
-            extra_miamlist = [];
+        var sortedSizeAnnotations = lengthSort(annotations, "text"),
+
+            extraNgramList = angular.copy($scope.extraNgramList);
+
+        extraNgramList = angular.forEach(extraNgramList, function(name, id) {
+          extraNgramList[id] = [];
+        });
+
 
         _.each(sortedSizeAnnotations, function (annotation) {
-          // TODO better split to manage two-words with minus sign
-          annotation.category = $rootScope.lists[annotation.list_id].toLowerCase();
+          // 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 (pattern.test(fullText) === true) {
-            fullText = replaceTextByTemplate(fullText, annotation, template, pattern, $rootScope.lists);
-            // TODO remove debug
-            counter++;
-          } else if (pattern.test(abstractText) === true) {
-            abstractText = replaceTextByTemplate(abstractText, annotation, template, pattern, $rootScope.lists);
-            counter++;
-          } else if (!textRegexp.test($rootScope.full_text) && !textRegexp.test($rootScope.abstract_text)) {
-            if (annotation.category == "stoplist") {
-              // Deactivated stoplist for the moment
-              // if ($.inArray(annotation.uuid, $scope.extra_stoplist.map(function (item) {
-              //      return item.uuid;
-              //    })) == -1) {
-              //   extra_stoplist = lengthSort(extra_stoplist.concat(annotation), "text");
-              // }
-            } else if (annotation.category == "miamlist") {
-              if ($.inArray(annotation.uuid, $scope.extra_miamlist.map(function (item) {
-                  return item.uuid;
-                })) == -1) {
-                extra_miamlist = lengthSort(extra_miamlist.concat(annotation), "text");
-              }
+          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] = lengthSort(extraNgramList[annotation.list_id].concat(annotation), "text");
             }
           }
         });
-        $scope.extra_stoplist = extra_stoplist;
-        $scope.extra_miamlist = extra_miamlist;
-
-        return {
-          'fullTextHtml': fullText,
-          'abstractTextHtml': abstractText
-        };
+        // update extraNgramList
+        $scope.extraNgramList = extraNgramList;
+        // 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;
 
-        $rootScope.miamListId = _.invert($rootScope.lists)['MiamList'];
-        $rootScope.stopListId = _.invert($rootScope.lists)['StopList'];
-
-        $scope.extra_stoplist = [];
-        $scope.extra_miamlist = [];
-
-        var result = compileText(
+        // initialize extraNgramList
+        $scope.extraNgramList = angular.copy($rootScope.activeLists);
+        $scope.extraNgramList = angular.forEach($scope.extraNgramList, function(name, id) {
+          $scope.extraNgramList[id] = [];
+        });
+        /*
+        * Transform text into HTML with higlighted ngrams
+        */
+        var result = compileNgramsHtml(
           $rootScope.annotations,
-          angular.copy($rootScope.full_text),
-          angular.copy($rootScope.abstract_text),
+          {
+            '#full-text': angular.copy($rootScope.full_text),
+            '#abstract-text': angular.copy($rootScope.abstract_text),
+            '#title': angular.copy($rootScope.title)
+          },
           $rootScope
         );
-
-        $.each($rootScope.annotations, function(index, element) {
-          if (element.list_id == $rootScope.stopListId) {
-            $scope.extra_stoplist.push(element);
-          } else if (element.list_id == $rootScope.miamListId) {
-            $scope.extra_miamlist.push(element);
-          }
+        // inject highlighted HTML
+        angular.forEach(result, function(html, eltId) {
+          angular.element(eltId).html(html);
         });
-
-        angular.element('#full-text').html(result.fullTextHtml);
-        angular.element('#abstract-text').html(result.abstractTextHtml);
-
+        // 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)));
         });
       });
 
-      function submitNewAnnotation($event, inputEltId, listId) {
+      /*
+      * Add a new NGram from the free 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;
 
@@ -350,47 +399,42 @@
             'listId': listId,
             'ngramId': 'new'
           },
-          {'annotation' : {'text': value}},
+          {
+            'annotation' : {'text': value}
+          },
           function(data) {
             // on success
             if (data) {
               $rootScope.annotations.push(data);
+              $(inputEltId).val("");
+            } else {
+              $(inputEltId).addClass("error");
             }
-        });
-
-        $(inputEltId).val("");
-      }
-
-      $scope.onMiamlistSubmit = function ($event) {
-        submitNewAnnotation($event, "#miamlist-input", _.invert($rootScope.lists)['MiamList']);
-      };
-      // TODO refactor
-      $scope.onStoplistSubmit = function ($event) {
-        submitNewAnnotation($event, "#stoplist-input", _.invert($rootScope.lists)['MiamList']);
-      };
-      $scope.numStopPages = function () {
-        if ($scope.extra_stoplist === undefined) return 0;
-        return Math.ceil($scope.extra_stoplist.length / $scope.pageSize);
-      };
-      $scope.numMiamPages = function () {
-        if ($scope.extra_miamlist === undefined) return 0;
-        return Math.ceil($scope.extra_miamlist.length / $scope.pageSize);
-      };
-      $scope.nextMiamPage = function() {
-        $scope.currentMiamPage = $scope.currentMiamPage + 1;
+          }, function(data) {
+            // on error
+            $(inputEltId).addClass("error");
+          }
+        );
       };
-      $scope.previousMiamPage = function() {
-        $scope.currentMiamPage = $scope.currentMiamPage - 1;
+
+      $scope.totalListPages = function (listId) {
+        if ($scope.extraNgramList[listId] === undefined) return 0;
+        return Math.ceil($scope.extraNgramList[listId].length / $scope.pageSize);
       };
-      $scope.nextStopPage = function() {
-        $scope.currentStopPage = $scope.currentStopPage + 1;
+
+      $scope.nextListPage = function(listId) {
+        $scope.currentListPage[listId] = $scope.currentListPage[listId] + 1;
       };
-      $scope.previousStopPage = function() {
-        $scope.currentStopPage = $scope.currentStopPage - 1;
+
+      $scope.previousListPage = function(list) {
+        $scope.currentListPage[listId] = $scope.currentListPage[listId] - 1;
       };
     }
   ]);
 
+  /*
+  * Filter used in Ngram flat lists pagination (extra-text panel)
+  */
   window.annotationsApp.filter('startFrom', function () {
     return function (input, start) {
       if (input === undefined) return;
@@ -405,28 +449,32 @@
       $rootScope.documentResource = DocumentHttpService.get(
         {'docId': $rootScope.docId},
         function(data, responseHeaders) {
-          $scope.title = data.title;
+
           $scope.authors = data.authors;
           $scope.journal = data.journal;
           $scope.publication_date = data.publication_date;
-          // TODO this data have to be deleted
           //$scope.current_page_number = data.current_page_number;
           //$scope.last_page_number = data.last_page_number;
-          // put in rootScope because used by many components
+          $rootScope.title = data.title;
           $rootScope.docId = data.id;
           $rootScope.full_text = data.full_text;
           $rootScope.abstract_text = data.abstract_text;
-          // GET the annotations
-          // TODO
+          // GET the annotationss
           $rootScope.annotationsResource = NgramListHttpService.get(
-            {'corpusId': $rootScope.corpusId, 'docId': $rootScope.docId}
+            {
+              'corpusId': $rootScope.corpusId,
+              'docId': $rootScope.docId
+            }
           ).$promise.then(function(data) {
             $rootScope.annotations = data[$rootScope.corpusId.toString()][$rootScope.docId.toString()];
-            $rootScope.lists = data[$rootScope.corpusId.toString()]['lists'];
+            $rootScope.lists = data[$rootScope.corpusId.toString()].lists;
+            // TODO active list selection controller
+            $rootScope.activeLists = $rootScope.lists;
+            $rootScope.mainListId = _.invert($rootScope.activeLists).MiamList;
           });
       });
 
-    // TODO setup pagination client-side
+    // TODO setup article pagination
     $scope.onPreviousClick = function () {
       DocumentHttpService.get($scope.docId - 1);
     };
@@ -435,8 +483,11 @@
     };
   }]);
 
+  /*
+  * Main function
+  * GET the document node and all its ngrams
+  */
   window.annotationsApp.run(function ($rootScope) {
-    /* GET the document node and all the annotations in the list associated */
     var path = window.location.pathname.match(/\/project\/(.*)\/corpus\/(.*)\/document\/(.*)\//);
     $rootScope.projectId = path[1];
     $rootScope.corpusId = path[2];
diff --git a/annotations/static/annotations/keyword_tpl.html b/annotations/static/annotations/keyword_tpl.html
index 07180369..b96a5121 100644
--- a/annotations/static/annotations/keyword_tpl.html
+++ b/annotations/static/annotations/keyword_tpl.html
@@ -1,5 +1,3 @@
-<span ng-if="keyword.category == 'miamlist'" ng-click='onDeleteClick()' class="delete-keyword" data-keyword-id="{[{keyword.uuid}]}" data-keyword-text="{[{keyword.text}]}" data-keyword-category="miamlist">×</span>
-<span ng-if="keyword.category == 'miamlist'" data-toggle="tooltip" class="keyword miamword">{[{keyword.text}]}</span>
-<span ng-if="keyword.category == 'stoplist'" ng-click='onDeleteClick()' class="delete-keyword" data-keyword-id="{[{keyword.uuid}]}" data-keyword-text="{[{keyword.text}]}" data-keyword-category="stoplist">×</span>
-<span ng-if="keyword.category == 'stoplist'" data-toggle="tooltip" class="keyword stopword">{[{keyword.text}]}</span>
+<span ng-click='onDeleteClick()' class="delete-keyword">×</span>
+<span data-toggle="tooltip" class="keyword-text {[{keyword.listName}]}">{[{keyword.text}]}</span>
 <span class="occurrences" data-keyword-id="{[{keyword.uuid}]}">{[{keyword.occurrences}]}</span>
diff --git a/annotations/static/annotations/selection_tpl.html b/annotations/static/annotations/selection_tpl.html
index 71432a61..308065dc 100644
--- a/annotations/static/annotations/selection_tpl.html
+++ b/annotations/static/annotations/selection_tpl.html
@@ -1,10 +1,3 @@
 <ul class="noselection">
-  <li class="miamword" ng-if="local_miamlist === true" ng-click="onClick($event, 'post', miamListId, 'local')">add to miam-list</li>
-  <li class="miamword"  ng-if="local_miamlist === false" ng-click="onClick($event, 'delete', miamListId, 'local')">remove from miam-list</li>
-
-  <li class="stopword" ng-if="local_stoplist === true" ng-click="onClick($event, 'post', stopListId, 'local')">add to stop-list</li>
-  <li class="stopword" ng-if="local_stoplist === false" ng-click="onClick($event, 'delete', stopListId, 'local')">remove from stop-list</li>
-
-  <!--<li class="stopword" ng-if="global_stoplist === true" ng-click="onClick($event, 'post', 'stoplist', 'global')">add to global stop-list</li>
-  <li class="stopword" ng-if="global_stoplist === false" ng-click="onClick($event, 'delete', 'stoplist', 'global')">remove from global stop-list</li>-->
+  <li ng-repeat="item in menuItems" class="{[{item.listName}]}" ng-click="onMenuClick($event, item.action, item.listId)">{[{item.verb}]} {[{item.listName}]}</li>
 </ul>
diff --git a/annotations/templates/annotations/main.html b/annotations/templates/annotations/main.html
index 74cfed88..9d8fe5a6 100644
--- a/annotations/templates/annotations/main.html
+++ b/annotations/templates/annotations/main.html
@@ -9,8 +9,8 @@
     <head>
         <meta charset="utf-8">
         <meta http-equiv="X-UA-Compatible" content="IE=edge">
-        <title>Gargantext corpus annotations editor</title>
-        <meta name="description" content="">
+        <title>Gargantext article editor</title>
+        <meta name="description" content="Gargantext">
         <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/angular/angular-csp.css' %}">
@@ -18,51 +18,36 @@
         <script src="{% static 'bower_components/jquery/dist/jquery.min.js' %}"></script>
     </head>
     <body>
-    <!-- TODO integrate this later into the corpus.html django template -->
+    <!-- TODO integrate this later into the any other django template -->
     <div id="annotationsApp">
       <div class="container-fluid">
         <div class="row-fluid main-panel" ng-controller="IntraTextController">
           <div class="col-md-4 col-xs-4 tabbable words-panel">
             <ul class="nav nav-pills nav-justified">
-              <li class="active"><a href="#tab1" data-toggle="tab">Miamwords</a></li>
-              <li><a href="#tab2" data-toggle="tab">Stopwords</a></li>
+              <li ng-repeat="(listId, listName) in activeLists" ng-class="{active: $first == true}">
+                <a href="#tab-{[{listId}]}" data-toggle="tab">{[{listName}]}</a>
+              </li>
             </ul>
             <div class="tab-content">
-              <div class="tab-pane active" id="tab1">
-                <div ng-if="extra_miamlist.length == 0" class="alert alert-info" role="alert">No extra text miam-word yet</div>
-                <ul class="list-group words-list">
+              <div ng-class="{active: $first == true}" class="tab-pane" ng-repeat="(listId, listName) in activeLists" id="tab-{[{listId}]}">
+                <div ng-if="extraNgramList[listId].length == 0" class="alert alert-info" role="alert">No Ngram in {[{listName}]} yet</div>
 
-                  <li ng-repeat="keyword in extra_miamlist | startFrom:currentMiamPage*pageSize | limitTo:pageSize" class="list-group-item">
-                    <div ng-controller="ExtraAnnotationController" keyword-template class="keyword-container"></div>
-                  </li>
-                </ul>
-                <nav ng-class="{invisible: numMiamPages() - 1 == 0}" class="clearfix">
-                  <ul class="pagination pagination-s pull-right words-pagination">
-                    <li ng-class="{disabled: currentMiamPage == 0}"><a ng-click="previousMiamPage()" class="glyphicon glyphicon-backward"></a></li>
-                    <li ng-class="{disabled: currentMiamPage >= numMiamPages()-1}"><a ng-click="nextMiamPage()" class="glyphicon glyphicon-forward"></a></li>
-                  </ul>
-                </nav>
-                <div class="form-group">
-                  <input type="text" class="form-control" id="miamlist-input" ng-keypress="onMiamlistSubmit($event)">
-                  <button type="submit" class="btn btn-default btn-primary" ng-click="onMiamlistSubmit($event)">Add</button>
-                </div>
-              </div>
-              <div class="tab-pane" id="tab2">
-                <div ng-if="extra_stoplist.length == 0" class="alert alert-info" role="alert">No extra text stop-word yet</div>
                 <ul class="list-group words-list">
-                  <li ng-repeat="keyword in extra_stoplist | startFrom:currentStopPage*pageSize | limitTo:pageSize" class="list-group-item">
+                  <li ng-repeat="keyword in extraNgramList[listId] | startFrom:currentListPage[listId]*pageSize | limitTo:pageSize" class="list-group-item">
                     <div ng-controller="ExtraAnnotationController" keyword-template class="keyword-container"></div>
                   </li>
                 </ul>
-                <nav ng-class="{invisible: numStopPages() - 1 == 0}" class="clearfix">
+
+                <nav ng-class="{invisible: totalListPages(listId) - 1 == 0}" class="clearfix">
                   <ul class="pagination pagination-s pull-right words-pagination">
-                    <li ng-class="{disabled: currentStopPage == 0}"><a ng-click="previousMiamPage()" class="glyphicon glyphicon-backward"></a></li>
-                    <li ng-class="{disabled: currentStopPage >= numStopPages()-1}"><a ng-click="nextStopPage()" class="glyphicon glyphicon-forward"></a></li>
+                    <li ng-class="{disabled: currentListPage[listId] == 0}"><a ng-click="previousListPage(listId)" class="glyphicon glyphicon-backward"></a></li>
+                    <li ng-class="{disabled: currentListPage[listId] >= totalListPages(listId)-1}"><a ng-click="nextListPage(listId)" class="glyphicon glyphicon-forward"></a></li>
                   </ul>
                 </nav>
+
                 <div class="form-group">
-                  <input type="text" class="form-control" id="stoplist-input" ng-keypress="onStoplistSubmit($event)">
-                  <button type="submit" class="btn btn-default btn-primary" ng-click="onStoplistSubmit($event)">Add</button>
+                  <input type="text" class="form-control" id="{[{ listId }]}-input" ng-keypress="onListSubmit($event, listId)">
+                  <button type="submit" class="btn btn-default btn-primary" ng-click="onListSubmit($event, listId)">Add to {[{listName}]}</button>
                 </div>
               </div>
             </div>
@@ -70,7 +55,7 @@
           <div class="col-md-8 col-xs-8 text-panel" ng-controller="DocController" id="document">
             <div class="row-fluid clearfix">
             <div class="col-md-7 col-xs-7">
-              <h3>{[{title}]}</h3>
+              <h3 class="text-container" id="title">{[{title}]}</h3>
             </div>
             <div class="col-md-5 col-xs-5">
               <nav>
@@ -90,17 +75,21 @@
             </div>
             <h4 ng-if="abstract_text != null">Abstract</h4>
             <p id="abstract-text" class="text-container">
-                <div ng-if="abstract_text == null" class="alert alert-info" role="alert">No abstract text</div>
+                <div ng-if="abstract_text == null" class="alert alert-info" role="alert">Empty abstract text</div>
             </p>
             <h4 ng-if="full_text != null">Full Article</h4>
             <p id="full-text" class="text-container">
-                <div ng-if="full_text == null" class="alert alert-info" role="alert">No full text</div>
+                <div ng-if="full_text == null" class="alert alert-info" role="alert">Empty full text</div>
             </p>
           </div>
         </div> <!-- end of the main row -->
       </div>
-      <!-- this menu is over the text -->
-      <div ng-controller="AnnotationMenuController" id="selection" class="selection-menu" selection-template></div>
+      <!-- this menu is over the text on mouse selection -->
+      <div ng-controller="AnnotationMenuController" id="selection" class="selection-menu">
+        <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>
+        </ul>
+      </div>
     </div>
     <!--[if lt IE 7]>
     <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
diff --git a/annotations/views.py b/annotations/views.py
index cd485bd5..b07e360b 100644
--- a/annotations/views.py
+++ b/annotations/views.py
@@ -40,15 +40,15 @@ class NgramList(APIView):
         """Get All for a doc id"""
         corpus_id = int(corpus_id)
         doc_id = int(doc_id)
-        lists = dict()
+        lists = {}
         for list_type in ['MiamList', 'StopList']:
-            list_id = list()
+
             list_id = listIds(user_id=request.user.id, corpus_id=int(corpus_id), typeList=list_type)
             lists["%s" % list_id[0][0]] = list_type
 
         # ngrams of list_id of corpus_id:
         doc_ngram_list = listNgramIds(corpus_id=corpus_id, doc_id=doc_id, user_id=request.user.id)
-        #doc_ngram_list = [(1, 'miam', 2, 1931), (2, 'stop', 2, 1932), (3, 'Potassium channels', 4, 1931)]
+        doc_ngram_list = [(1, 'miam', 2, 1931), (2, 'stop', 2, 1932), (3, 'Potassium channels', 4, 1931)]
 
         data = { '%s' % corpus_id : {
             '%s' % doc_id : [
-- 
2.21.0