diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/file/FilebaseWordIndex.java b/vipra-cmd/src/main/java/de/vipra/cmd/file/FilebaseWordIndex.java index 11a199feb78dd87024d3d3ff21765454e7856ba5..51bb48cc593cf1559867d9ef75e6905478fc2237 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/file/FilebaseWordIndex.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/file/FilebaseWordIndex.java @@ -53,9 +53,11 @@ public class FilebaseWordIndex implements Iterable<String> { public String transform(final String[] words, final boolean dbInsert) { final CountMap<String> countMap = new CountMap<>(); for (final String word : words) { - countMap.count(word); - if (dbInsert) - newWords.add(word); + if (!word.trim().isEmpty()) { + countMap.count(word); + if (dbInsert) + newWords.add(word); + } } final StringBuilder sb = new StringBuilder(); diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/text/ProcessedText.java b/vipra-cmd/src/main/java/de/vipra/cmd/text/ProcessedText.java index 3564c6c2b8810a313bbaf178a429576a783a5ea1..65d93b958cdf0bc9fd946e1474544b57ecc8f1a8 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/text/ProcessedText.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/text/ProcessedText.java @@ -18,7 +18,12 @@ public class ProcessedText { private final List<ArticleWord> articleWords; public ProcessedText(final String text, final long wordCount) { - words = text.toLowerCase().trim().split("\\s+"); + final String[] allWords = text.toLowerCase().trim().split("\\s+"); + final List<String> wordList = new ArrayList<>(allWords.length); + for (final String word : allWords) + if (!word.trim().isEmpty()) + wordList.add(word); + words = wordList.toArray(new String[allWords.length]); originalWordCount = wordCount; reducedWordCount = words.length; reductionRatio = 1 - ((double) reducedWordCount / wordCount); diff --git a/vipra-ui/app/html/directives/article-link.html b/vipra-ui/app/html/directives/article-link.html index 13791ba674658cd0ccff6086e0e9e9b0b96c2e0c..6bf36a2e32c631019376df82baa4d18f8979aed6 100644 --- a/vipra-ui/app/html/directives/article-link.html +++ b/vipra-ui/app/html/directives/article-link.html @@ -1,11 +1,14 @@ -<div class="link-wrapper"> - <a class="article-link" ui-sref="articles.show({id:article.id})"> - <span ng-bind="article.title"></span> - <ng-transclude/> - </a> - <div class="pull-right"> - <i class="fa text-muted pointer" ng-class="{'fa-chevron-down':!excerptShown, 'fa-chevron-up':excerptShown}" ng-click="toggleExcerpt()" ng-if="::showExcerpt"></i> - <span class="badge" ng-bind="::article.topicsCount" ng-attr-title="{{::article.topicsCount}} topic(s)" ng-if="::showBadge"></span> - </div> +<div class="link-wrapper" ng-attr-title="{{::article.title}}"> + <span class="ellipsis menu-padding"> + <a class="article-link" ui-sref="articles.show({id:article.id})"> + <span ng-bind="article.title"></span> + <ng-transclude/> + </a> + <div class="pull-right article-dropdown"> + <i class="fa text-muted pointer" ng-class="{'fa-chevron-down':!excerptShown, 'fa-chevron-up':excerptShown}" ng-click="toggleExcerpt()" ng-if="::showExcerpt"></i> + <span class="badge" ng-bind="::article.topicsCount" ng-attr-title="{{::article.topicsCount}} topic(s)" ng-if="::showBadge"></span> + </div> + </span> + <article-menu class="menu-button" article="article" ng-if="::showMenu" /> <div ng-bind="excerpt" ng-if="excerptShown" class="excerpt"></div> -</div> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/directives/article-menu.html b/vipra-ui/app/html/directives/article-menu.html new file mode 100644 index 0000000000000000000000000000000000000000..ec69dd6ccef849f20c80951aff9dd880c4a08490 --- /dev/null +++ b/vipra-ui/app/html/directives/article-menu.html @@ -0,0 +1,10 @@ +<div class="dropdown inline-block"> + <a data-toggle="dropdown"> + <i class="fa fa-caret-down"></i> + </a> + <ul class="dropdown-menu" ng-class="{'dropdown-menu-right':dropdownRight}"> + <li><a ui-sref="articles.show({id:article.id})">Show</a></li> + <li role="separator" class="divider"></li> + <li><a ui-sref="network({type:'topics',id:article.id})">Network</a></li> + </ul> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/explorer.html b/vipra-ui/app/html/explorer.html index 963d0126cbeeb14796513a3abc2adbcb92f3416d..ef29ffe7c102cb3aff71f69626d601ef2674b42c 100644 --- a/vipra-ui/app/html/explorer.html +++ b/vipra-ui/app/html/explorer.html @@ -116,7 +116,7 @@ <table class="table table-bordered table-condensed table-fixed"> <thead> <tr> - <th ng-model="explorerModels.articlesSort" sort-by="article.title">Article</th> + <th ng-model="explorerModels.articlesSort" sort-by="title">Article</th> </tr> </thead> <tbody> diff --git a/vipra-ui/app/html/index.html b/vipra-ui/app/html/index.html index ea7d9d9102e0de2649614aed50c932afe39a2334..2cca09e34663d1c30b5430e6fde19d8c710e42e4 100644 --- a/vipra-ui/app/html/index.html +++ b/vipra-ui/app/html/index.html @@ -8,18 +8,18 @@ </div> <div class="row row-spaced"> <div class="col-md-12"> - <div class="input-group"> + <div ng-class="{'input-group':advancedSearchEnabled}"> <div class="form-group has-feedback"> <input type="text" class="form-control input-lg" placeholder="Search..." ng-model="search" ng-model-options="{debounce:500}" id="searchBox"> <i class="form-control-feedback glyphicon glyphicon-search text-muted"></i> </div> - <span class="input-group-btn"> + <span class="input-group-btn" ng-show="advancedSearchEnabled"> <button class="btn btn-default btn-lg" type="button" title="Advanced" ng-click="advancedSearch=!advancedSearch"><i class="fa text-muted" ng-class="{'fa-chevron-down':!advancedSearch,'fa-chevron-up':advancedSearch}"></i></button> </span> </div> </div> </div> - <div class="row row-spaced" ng-show="advancedSearch" ng-cloak> + <div class="row row-spaced" ng-show="advancedSearch&&advancedSearchEnabled" ng-cloak> <div class="col-md-6 form-horizontal"> <label for="advFromDate" class="col-sm-2 control-label">From</label> <div class="input-group date col-sm-10" id="advFromDate" bs-datetimepicker ng-model="rootModels.advFromDate"> @@ -43,18 +43,14 @@ <div class="col-md-8 text-center"> <h4>Latest articles</h4> <ul class="list-unstyled"> - <li class="ellipsis" ng-repeat="article in latestArticles"> - <article-link article="::article" badge="false" menu="false" excerpt="false"/> - </li> + <article-link article="::article" badge="false" menu="false" excerpt="false" ng-repeat="article in latestArticles"/> </ul> <p class="text-center" ng-if="!latestArticles.length">No articles</p> </div> <div class="col-md-4 text-center"> <h4>Latest topics</h4> <ul class="list-unstyled"> - <li class="ellipsis" ng-repeat="topic in latestTopics"> - <topic-link topic="::topic" badge="false" menu="false"/> - </li> + <topic-link topic="::topic" badge="false" menu="false" ng-repeat="topic in latestTopics"/> </ul> <p class="text-center" ng-if="!latestTopics.length">No topics</p> </div> @@ -96,6 +92,9 @@ </p> </li> </ul> + <div class="text-center" ng-hide="noMoreResults"> + <button class="btn btn-default" ng-click="loadMoreResults()">Load more</button> + </div> </div> </div> </div> diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index ea0a1d647815b812ea5ae6e565bebc90d3e19ed1..43dc465c4a5c6c5e29b7b398f16836b7ede106e9 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -179,6 +179,8 @@ if (!$scope.rootModels.topicModel) $scope.chooseTopicModel(); + $scope.searchResultsStep = 10; + $scope.advancedSearchEnabled = false; $scope.search = $stateParams.q || $scope.search; $scope.$watch('rootModels.topicModel', function() { @@ -213,21 +215,29 @@ $scope.goSearch = function() { $scope.searching = true; + $scope.skip = 0; + $scope.searchResults = []; + $scope.loadMoreResults(); + }; + $scope.loadMoreResults = function() { SearchFactory.query({ topicModel: $scope.rootModels.topicModel.id, - limit: 10, + skip: $scope.skip, + limit: $scope.searchResultsStep, query: $scope.search, from: $scope.rootModels.advFromDate ? $scope.rootModels.advFromDate.getTime() : null, to: $scope.rootModels.advToDate ? $scope.rootModels.advToDate.getTime() : null - }, function(data) { + }, function(data, headers) { $scope.searching = false; - $scope.searchResults = data; + $scope.searchResults.push.apply($scope.searchResults, data); + $scope.skip += $scope.searchResultsStep; + $scope.totalResults = headers("V-Total"); + $scope.noMoreResults = data.length < $scope.searchResultsStep; }, function() { $scope.searching = false; }); }; - } ]); @@ -421,8 +431,9 @@ // on node select $scope.select = function(props) { var node = $scope.nodes.get(props.nodes[0]); - if (node) { + if (node && !node.loaded) { node.loader(node, props); + node.loaded = true; $scope.nodes.update(node); } }; @@ -465,7 +476,8 @@ } if ($scope.shown.words) { TopicFactory.get({ - id: node.dbid + id: node.dbid, + limit: 50 }, function(data) { constructor(data.words, wordNode, node, props); }); @@ -476,7 +488,8 @@ if ($scope.shown.articles) { ArticleFactory.query({ word: node.dbid, - topicModel: $scope.rootModels.topicModel.id + topicModel: $scope.rootModels.topicModel.id, + limit: 50 }, function(data) { constructor(data, articleNode, node, props); }); @@ -484,7 +497,8 @@ if ($scope.shown.topics) { TopicFactory.query({ word: node.dbid, - topicModel: $scope.rootModels.topicModel.id + topicModel: $scope.rootModels.topicModel.id, + limit: 50 }, function(data) { constructor(data, topicNode, node, props); }); @@ -493,7 +507,6 @@ // on node open $scope.open = function(props) { - $timeout.cancel(selectTimeout); var node = $scope.nodes.get(props.nodes[0]); $state.transitionTo(node.show, { id: node.dbid @@ -668,7 +681,8 @@ sortdir: false, seqstyle: 'absolute', chartstyle: 'areaspline', - chartstack: 'none' + chartstack: 'none', + articlesSort: 'title' }; $scope.$watch('rootModels.topicModel', function() { @@ -1700,7 +1714,7 @@ } }, tooltip: { - headerFormat: '', + headerFormat: '<b>{series.name}</b><br>', pointFormat: '{point.x:%Y}: {point.y:.4f}' }, legend: { diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index ea7f9b94083fc789886efa435a6d59585f7276df..6b79e4408b8f019596da9533ac6b9b58730ebf8e 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -51,7 +51,8 @@ scope: { article: '=', excerpt: '@', - badge: '@' + badge: '@', + menu: '@' }, restrict: 'E', replace: true, @@ -60,6 +61,7 @@ link: function($scope) { $scope.showExcerpt = $scope.excerpt !== 'false'; $scope.showBadge = $scope.badge !== 'false'; + $scope.showMenu = $scope.menu !== 'false'; $scope.toggleExcerpt = function() { if (!$scope.excerptShown) { if ($scope.excerpt) { @@ -395,6 +397,20 @@ }; }]); + app.directive('articleMenu', [function() { + return { + scope: { + article: '=', + right: '@' + }, + restrict: 'E', + templateUrl: 'html/directives/article-menu.html', + link: function($scope) { + $scope.dropdownRight = $scope.right === 'true'; + } + }; + }]); + app.directive('sortDir', [function() { return { scope: { diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index b7da60f87bccdb3f1cc3190cd3f698e5f4a1a601..b7ea8434324dfdc0e188fa69090523d227392987 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -43,13 +43,8 @@ a:hover { .search-results { padding-top: 15px; - .search-result { - &:not(: last-child) { - margin-bottom: 20px; - } - a { - font-size: 1.5rem; - } + .search-result + .search-result { + margin-top: 25px; } } @@ -802,14 +797,7 @@ entity-menu { .index { .navbar-default { - border-color: rgba(255,255,255,.2); - background: 0; - .navbar-nav>li>a { - color: #555; - &:hover { - background-color: rgba(255,255,255,.2); - } - } + border: 0; } } @@ -906,6 +894,10 @@ entity-menu { position: relative; } +.article-dropdown { + width: 40px; +} + @keyframes spin { 100% { -webkit-transform: rotateY(360deg);