diff --git a/vipra-cmd/runcfg/CMD.launch b/vipra-cmd/runcfg/CMD.launch index d44936f9e02af0536d75e1e3a8bce5342cc2a200..b36db2e1f99f6ae77ceb1a8501b802a2a37c5e24 100644 --- a/vipra-cmd/runcfg/CMD.launch +++ b/vipra-cmd/runcfg/CMD.launch @@ -11,7 +11,7 @@ </listAttribute> <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.m2e.launchconfig.classpathProvider"/> <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="de.vipra.cmd.Main"/> -<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-R /home/eike/Downloads/vipra-1462648299922.zip"/> +<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-t"/> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="vipra-cmd"/> <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/> <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea"/> diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java index d656552d4ba1bf2f84028f6ce61029c359ba7cea..57e6875873b2408ac3ca376863776f40de999d08 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/BackupCommand.java @@ -3,9 +3,12 @@ package de.vipra.cmd.option; import java.io.File; import java.util.Date; +import org.fusesource.jansi.Ansi; +import org.fusesource.jansi.Ansi.Color; import org.zeroturnaround.zip.ZipUtil; import de.vipra.util.Config; +import de.vipra.util.ConsoleUtils; import de.vipra.util.FileUtils; public class BackupCommand implements Command { @@ -18,19 +21,43 @@ public class BackupCommand implements Command { @Override public void run() throws Exception { - final Config config = Config.getConfig(); - final File tmpTarget = FileUtils.getTempFile("vipra-dump"); - org.apache.commons.io.FileUtils.deleteDirectory(tmpTarget); - final Process p = Runtime.getRuntime().exec("mongodump -d " + config.getDatabaseName() + " -h " + config.getDatabaseHost() + " --port " - + config.getDatabasePort() + " -o " + new File(tmpTarget, "db")); - p.waitFor(); - org.apache.commons.io.FileUtils.copyDirectory(config.getDataDirectory(), new File(tmpTarget, "fb")); - org.apache.commons.io.FileUtils.copyDirectory(Config.getGenericConfigDir(), new File(tmpTarget, "config")); - File target = new File(path); - if (target.exists() && target.isDirectory()) - target = new File(target, "vipra-" + new Date().getTime() + ".zip"); - ZipUtil.pack(tmpTarget, target); - org.apache.commons.io.FileUtils.deleteDirectory(tmpTarget); + try { + final Config config = Config.getConfig(); + final File tmpTarget = FileUtils.getTempFile("vipra-dump"); + org.apache.commons.io.FileUtils.deleteDirectory(tmpTarget); + + ConsoleUtils.infoNOLF(" backup database..."); + final Process p = Runtime.getRuntime().exec("mongodump -d " + config.getDatabaseName() + " -h " + config.getDatabaseHost() + " --port " + + config.getDatabasePort() + " -o " + new File(tmpTarget, "db")); + p.waitFor(); + ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString()); + + ConsoleUtils.infoNOLF(" backup filebase..."); + org.apache.commons.io.FileUtils.copyDirectory(config.getDataDirectory(), new File(tmpTarget, "fb")); + ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString()); + + ConsoleUtils.infoNOLF(" backup configuration..."); + org.apache.commons.io.FileUtils.copyDirectory(Config.getGenericConfigDir(), new File(tmpTarget, "config")); + ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString()); + + ConsoleUtils.infoNOLF(" compressing..."); + File target = new File(path); + if (target.exists() && target.isDirectory()) + target = new File(target, "vipra-" + new Date().getTime() + ".zip"); + ZipUtil.pack(tmpTarget, target); + org.apache.commons.io.FileUtils.deleteDirectory(tmpTarget); + ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString()); + + ConsoleUtils.info("completed: " + target.getAbsolutePath()); + } catch (final Exception e) { + ConsoleUtils.print(Ansi.ansi().fg(Color.RED).a("FAILED").reset().toString()); + if (e.getMessage().contains("mongodump")) { + ConsoleUtils.error("mongodump not installed"); + return; + } else { + throw e; + } + } } @Override diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java index 857a35cd6fdbf323e959b7fb38ac9933a9ba9782..c1bc3024516bcc7c4c3d87290044d67644a0afd8 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/RestoreCommand.java @@ -3,9 +3,12 @@ package de.vipra.cmd.option; import java.io.File; import java.io.FileNotFoundException; +import org.fusesource.jansi.Ansi; +import org.fusesource.jansi.Ansi.Color; import org.zeroturnaround.zip.ZipUtil; import de.vipra.util.Config; +import de.vipra.util.ConsoleUtils; import de.vipra.util.FileUtils; public class RestoreCommand implements Command { @@ -18,17 +21,36 @@ public class RestoreCommand implements Command { @Override public void run() throws Exception { - final File zip = new File(path); - if (!zip.isFile()) - throw new FileNotFoundException(path); - final File tmpTarget = FileUtils.getTempFile("vipra-dump"); - ZipUtil.unpack(zip, tmpTarget); - final Config config = Config.getConfig(); - final Process p = Runtime.getRuntime() - .exec("mongorestore --drop -h " + config.getDatabaseHost() + " --port " + config.getDatabasePort() + " " + new File(tmpTarget, "db")); - p.waitFor(); - org.apache.commons.io.FileUtils.copyDirectory(new File(tmpTarget, "fb"), config.getDataDirectory()); - org.apache.commons.io.FileUtils.copyDirectory(new File(tmpTarget, "config"), Config.getGenericConfigDir()); + try { + final File zip = new File(path); + if (!zip.isFile()) + throw new FileNotFoundException(path); + final File tmpTarget = FileUtils.getTempFile("vipra-dump"); + ZipUtil.unpack(zip, tmpTarget); + final Config config = Config.getConfig(); + + ConsoleUtils.infoNOLF(" restore database..."); + final Process p = Runtime.getRuntime().exec( + "mongorestore --drop -h " + config.getDatabaseHost() + " --port " + config.getDatabasePort() + " " + new File(tmpTarget, "db")); + p.waitFor(); + ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString()); + + ConsoleUtils.infoNOLF(" restore filebase..."); + org.apache.commons.io.FileUtils.copyDirectory(new File(tmpTarget, "fb"), config.getDataDirectory()); + ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString()); + + ConsoleUtils.infoNOLF(" restore configuration..."); + org.apache.commons.io.FileUtils.copyDirectory(new File(tmpTarget, "config"), Config.getGenericConfigDir()); + ConsoleUtils.print(Ansi.ansi().fg(Color.GREEN).a("OK").reset().toString()); + } catch (final Exception e) { + ConsoleUtils.print(Ansi.ansi().fg(Color.RED).a("FAILED").reset().toString()); + if (e.getMessage().contains("mongorestore")) { + ConsoleUtils.error("mongorestore not installed"); + return; + } else { + throw e; + } + } } @Override diff --git a/vipra-ui/app/html/directives/article-link.html b/vipra-ui/app/html/directives/article-link.html index 6573ef9fe67d929dd4b276ff98e338875d26fb4a..13791ba674658cd0ccff6086e0e9e9b0b96c2e0c 100644 --- a/vipra-ui/app/html/directives/article-link.html +++ b/vipra-ui/app/html/directives/article-link.html @@ -1,4 +1,4 @@ -<span> +<div class="link-wrapper"> <a class="article-link" ui-sref="articles.show({id:article.id})"> <span ng-bind="article.title"></span> <ng-transclude/> @@ -8,4 +8,4 @@ <span class="badge" ng-bind="::article.topicsCount" ng-attr-title="{{::article.topicsCount}} topic(s)" ng-if="::showBadge"></span> </div> <div ng-bind="excerpt" ng-if="excerptShown" class="excerpt"></div> -</span> +</div> diff --git a/vipra-ui/app/html/directives/entity-link.html b/vipra-ui/app/html/directives/entity-link.html index 569cdb436328a4bb02514abebe883a6356121148..b0090b88d4327a846a0243059131b356f13ba7a0 100644 --- a/vipra-ui/app/html/directives/entity-link.html +++ b/vipra-ui/app/html/directives/entity-link.html @@ -1,7 +1,9 @@ -<span> - <entity-menu entity="entity" ng-if="::showMenu" /> - <a class="entity-link" ui-sref="entities.show({id:entity.id})"> - <span ng-bind="entity.id"></span> - <ng-transclude/> - </a> -</span> \ No newline at end of file +<div class="link-wrapper"> + <span class="ellipsis menu-padding"> + <a class="entity-link" ui-sref="entities.show({id:entity.id})"> + <span ng-bind="entity.id"></span> + <ng-transclude/> + </a> + </span> + <entity-menu class="menu-button" entity="entity" ng-if="::showMenu" /> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/directives/topic-link.html b/vipra-ui/app/html/directives/topic-link.html index 411e1e14cf765c4deb571ba7f3d487ee0413c434..f4595e813bb01455f55d0f78355a9b38b808aaaf 100644 --- a/vipra-ui/app/html/directives/topic-link.html +++ b/vipra-ui/app/html/directives/topic-link.html @@ -1,10 +1,10 @@ -<span> - <topic-menu topic="topic" class="menu-button" ng-if="::showMenu" /> - <span class="ellipsis"> +<div class="link-wrapper"> + <span class="ellipsis menu-padding"> <a class="topic-link" ui-sref="topics.show({id:topic.id})"> <span ng-bind="topic.name"></span> <ng-transclude/> </a> <span class="badge pull-right" ng-bind="::topic.articlesCount" ng-attr-title="{{::topic.articlesCount}} article(s)" ng-if="::showBadge"></span> </span> -</span> + <topic-menu topic="topic" class="menu-button" ng-if="::showMenu" /> +</div> diff --git a/vipra-ui/app/html/directives/word-link.html b/vipra-ui/app/html/directives/word-link.html index 268cec13ec0ac7f7cb68b1c15e950b3e11410e3e..9e852ae3fa198f96e46c418f07a84981eb8495c1 100644 --- a/vipra-ui/app/html/directives/word-link.html +++ b/vipra-ui/app/html/directives/word-link.html @@ -1,6 +1,6 @@ -<span> - <word-menu class="menu-button" word="word" ng-if="::showMenu" /> - <span class="ellipsis"> +<div class="link-wrapper"> + <span class="ellipsis menu-padding"> <a ui-sref="words.show({id: word.id})" ng-bind="word.id"></a> </span> -</span> \ No newline at end of file + <word-menu class="menu-button" word="word" ng-if="::showMenu" /> +</div> \ No newline at end of file diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html index 175de2910553996dbe8d8c19822a4653987ccd5f..12e07f0b4e64f4514508a95ceec54bf5d81b38bb 100644 --- a/vipra-ui/app/index.html +++ b/vipra-ui/app/index.html @@ -66,6 +66,11 @@ <i class="fa fa-question-circle"></i> </a> </li> + <li title="Feedback"> + <a tabindex="0" ng-click="showFeedbackModal()"> + <i class="fa fa-comment-o"></i> + </a> + </li> </ul> </div> </div> @@ -116,9 +121,20 @@ </div> </div> </div> - <div class="alerts"> - <bs-alert ng-model="alert" type="alert.type" ng-repeat="alert in alerts"/> + <div id="feedbackModal" class="modal" tabindex="-1" role="dialog" data-backdrop="static"> + <div class="modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-show="rootModels.topicModel" ng-cloak><span aria-hidden="true">×</span></button> + <h4 class="modal-title">Evaluation</h4> + </div> + <div class="modal-body"> + <iframe id="feedbackIframe" frameborder="0" marginheight="0" marginwidth="0">Wird geladen...</iframe> + </div> + </div> + </div> </div> + <div class="alerts"></div> <div class="overlay-message" ng-show="fatal"> <div class="message" ng-bind="fatal"></div> </div> @@ -135,5 +151,13 @@ <script src="js/config.js"></script> <script src="js/app.js"></script> <script src="js/templates.js"></script> + <script> + /* jshint ignore:start */ + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + ga('create', 'UA-77619702-1', 'auto'); + </script> </body> </html> \ No newline at end of file diff --git a/vipra-ui/app/js/alerter.js b/vipra-ui/app/js/alerter.js new file mode 100644 index 0000000000000000000000000000000000000000..6e9c1e589f2e6eb379729a69d09c6b1f848c1e60 --- /dev/null +++ b/vipra-ui/app/js/alerter.js @@ -0,0 +1,311 @@ +/****************************************************************************** + * Vipra Application + * Alerter library + ******************************************************************************/ +/* globals $, Alerter */ +;(function(g) { + 'use strict'; + + if(g.Alerter) return; + + g.Alerter = function(area, create, classes, style) { + this.area = area || 'alerts'; + this.alerts = []; + this.queue = []; + this.settings = $.extend({}, Alerter.defaultSettings); + this.alertDefaults = $.extend({}, Alerter.alertDefaults); + if(create) + this.createAlertArea(classes, style); + Alerter.instances.push(this); + }; + + Alerter.instances = []; + + Alerter.defaultSettings = { + prepend: true, + maxAlerts: 0, + queue: true, + merge: false + }; + + Alerter.alertDefaults = { + dismissible: true, + fade: true, + fadeTime: 300, + fadeAfter: 5000 + }; + + Alerter.prototype.setSettings = function(s, opts) { + if(s) + this.settings = $.extend({}, this.settings, s); + if(opts) + this.setAlertDefaults(opts); + return this; + }; + + Alerter.prototype.getSettings = function() { + return this.settings; + }; + + Alerter.prototype.setAlertDefaults = function(opts) { + this.alertDefaults = $.extend({}, this.alertDefaults, opts); + return this; + }; + + Alerter.prototype.showAlert = function(type, title, msg, opts) { + return this.showAlerts(type, [{title: title, msg: msg}], opts); + }; + + Alerter.prototype.showAlerts = function(type, body, opts) { + opts = $.extend({}, this.alertDefaults, opts); + + if(this.settings.merge && this.mergeAlerts(type, body, opts)) { + return; + } + + var id = '_' + Math.random().toString(36).substr(2, 9), + c = { + instance: this, + id: id, + type: type, + opts: opts, + time: new Date().getTime(), + body: body + }; + + if(this.settings.maxAlerts > 0 && Object.keys(this.alerts).length >= this.settings.maxAlerts) { + if(this.settings.queue) { + this.queue.push(c); + return; + } else { + this.removeOldestAlert(); + } + } + + var html = this.createAlert(c), + el = c.el = this.settings.prepend ? $(html).prependTo('.' + this.area) : $(html).appendTo('.' + this.area); + + this.alerts[id] = c; + + if(opts.fade) { + el.hide().fadeIn(opts.fadeTime, function() { + this.restartAlert(id); + }.bind(this)); + } else { + this.restartAlert(id); + } + + return c; + }; + + Alerter.prototype.showSuccess = function(title, msg, opts) { + return this.showAlert('success', title, msg, opts); + }; + + Alerter.prototype.showInfo = function(title, msg, opts) { + return this.showAlert('info', title, msg, opts); + }; + + Alerter.prototype.showWarning = function(title, msg, opts) { + return this.showAlert('warning', title, msg, opts); + }; + + Alerter.prototype.showDanger = function(title, msg, opts) { + return this.showAlert('danger', title, msg, opts); + }; + + Alerter.prototype.showPrimary = function(title, msg, opts) { + return this.showAlert('primary', title, msg, opts); + }; + + Alerter.prototype.unqueueAlert = function() { + if(this.queue.length) { + var c = this.queue.shift(); + this.showAlerts(c.type, c.body, c.opts); + } + return this; + }; + + Alerter.prototype.mergeAlert = function(type, title, msg, opts) { + this.mergeAlerts(type, [{title: title, msg: msg}], opts); + return this; + }; + + Alerter.prototype.mergeAlerts = function(type, body, opts) { + for(var id in this.alerts) { + if(this.alerts[id].type === type) { + return this.editAlerts(id, body, opts, true); + } + } + if(this.settings.queue) { + for(var i = 0; i < this.queue.length; i++) { + if(this.queue[i].type === type) { + return this.editAlerts(this.queue[i].id, body, opts, false); + } + } + } + return false; + }; + + Alerter.prototype.getAlert = function(id) { + var c = this.alerts[id]; + if(c) return c; + if(this.settings.queue) { + for(var i = 0; i < this.queue.length; i++) { + if(this.queue[i].id === id) { + return this.queue[i]; + } + } + } + return null; + }; + + Alerter.prototype.editAlert = function(id, title, msg, opts, restart) { + this.editAlerts(id, [{title: title, msg: msg}], restart); + return this; + }; + + Alerter.prototype.editAlerts = function(id, body, opts, restart) { + var c = this.getAlert(id); + if(c) { + c.body.push.apply(c.body, body); + if(opts) { + for(var key in opts) { + if(key !== 'body') { + c[key] = opts[key]; + } + } + } + if(c.el) { + var elBody = c.el.find('.alert-body'); + for(var i = 0; i < body.length; i++) { + elBody.append(this.createAlertBody(body[i].title, body[i].msg)); + } + } + if(restart) { + this.restartAlert(id); + } + return true; + } + return false; + }; + + Alerter.prototype.restartAlert = function(id) { + var c = this.getAlert(id); + if(c) { + clearTimeout(c.timeout); + if(c.opts.fadeAfter && c.opts.fadeAfter > 0) { + c.timeout = setTimeout(function() { + this.removeAlert(id); + }.bind(this), c.opts.fadeAfter); + } + } + return this; + }; + + Alerter.prototype.removeAlert = function(id) { + var c = this.getAlert(id); + if(c) { + if(c.opts.fade) { + c.el.fadeOut(c.opts.fadeTime, function() { + $(this).remove(); + delete this.alerts[id]; + this.unqueueAlert(); + }.bind(this)); + } else { + c.el.remove(); + delete this.alerts[id]; + this.unqueueAlert(); + } + } + return this; + }; + + Alerter.prototype.removeAlerts = function() { + this.queue = []; + for(var id in this.alerts) + this.removeAlert(id); + return this; + }; + + Alerter.prototype.getOldestAlert = function() { + var oldest = null; + for(var id in this.alerts) { + if(!oldest || this.alerts[id].time < oldest.time) + oldest = this.alerts[id]; + } + return oldest; + }; + + Alerter.prototype.removeOldestAlert = function() { + var oldest = this.getOldestAlert(); + if(oldest) { + this.removeAlert(oldest.id); + } + return this; + }; + + Alerter.prototype.createAlert = function(c) { + var msgs = ""; + for(var i = 0; i < c.body.length; i++) + msgs += this.createAlertBody(c.body[i].title, c.body[i].msg); + return '<div id="' + c.id + '" class="alert alert-' + c.type + ' ' + (c.opts.dismissible ? 'alert-dismissible' : '') + + '" role="alert">' + (c.opts.dismissible ? '<button onclick="Alerter.removeAlertById(\'' + + c.id + '\')" type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>' : '') + + '<div class="alert-body">' + msgs + '</div></div>'; + }; + + Alerter.prototype.createAlertBody = function(title, msg) { + return '<div><strong>' + (title ? title : '') + '</strong> ' + msg + '</div>'; + }; + + Alerter.prototype.createAlertArea = function(classes, style) { + $('<div class="' + this.area + ' ' + classes + '" style="' + style + '"></div>').appendTo('body'); + return this; + }; + + /** + * Singleton + */ + + var _A = new Alerter('alerts'); + Alerter.instances.push(_A); + + Alerter.setSettings = _A.setSettings.bind(_A); + Alerter.getSettings = _A.getSettings.bind(_A); + Alerter.setAlertDefaults = _A.setAlertDefaults.bind(_A); + Alerter.showAlert = _A.showAlert.bind(_A); + Alerter.showAlerts = _A.showAlerts.bind(_A); + Alerter.showSuccess = _A.showSuccess.bind(_A); + Alerter.showInfo = _A.showInfo.bind(_A); + Alerter.showWarning = _A.showWarning.bind(_A); + Alerter.showDanger = _A.showDanger.bind(_A); + Alerter.showPrimary = _A.showPrimary.bind(_A); + Alerter.unqueueAlert = _A.unqueueAlert.bind(_A); + Alerter.mergeAlert = _A.mergeAlert.bind(_A); + Alerter.mergeAlerts = _A.mergeAlerts.bind(_A); + Alerter.getAlert = _A.getAlert.bind(_A); + Alerter.editAlert = _A.editAlert.bind(_A); + Alerter.editAlerts = _A.editAlerts.bind(_A); + Alerter.restartAlert = _A.restartAlert.bind(_A); + Alerter.removeAlert = _A.removeAlert.bind(_A); + Alerter.removeAlerts = _A.removeAlerts.bind(_A); + Alerter.getOldestAlert = _A.getOldestAlert.bind(_A); + Alerter.removeOldestAlert = _A.removeOldestAlert.bind(_A); + Alerter.createAlert = _A.createAlert.bind(_A); + Alerter.createAlertBody = _A.createAlertBody.bind(_A); + Alerter.createAlertArea = _A.createAlertArea.bind(_A); + + Alerter.removeAlertById = function(id) { + for(var i = 0; i < Alerter.instances.length; i++) { + Alerter.instances[i].removeAlert(id); + } + }; + + Alerter.removeAllAlerts = function() { + for(var i = 0; i < Alerter.instances.length; i++) { + Alerter.instances[i].removeAlerts(); + } + }; + +})(this); \ No newline at end of file diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js index 6754e3f7ddf41904aff5718b64686304201b36ef..55301b93cbe6c9a77f5cc91fdd68a7e5e9151583 100644 --- a/vipra-ui/app/js/app.js +++ b/vipra-ui/app/js/app.js @@ -2,7 +2,7 @@ * Vipra Application * Main application file ******************************************************************************/ -/* globals angular, $, Vipra, ReconnectingWebSocket */ +/* globals angular, $, Vipra, ReconnectingWebSocket, Alerter */ (function() { "use strict"; @@ -14,6 +14,8 @@ 'ui.router', 'cfp.hotkeys', 'nya.bootstrap.select', + 'angulartics', + 'angulartics.google.analytics', 'vipra.controllers', 'vipra.directives', 'vipra.factories', @@ -182,14 +184,11 @@ } else if (rejection.data) { if (angular.isArray(rejection.data)) { for (var i = 0; i < rejection.data.length; i++) { - $rootScope.alerts.push(angular.extend({ - type: 'danger' - }, rejection.data[i])); + var rd = rejection.data[i]; + Alerter.showDanger(rd.title, rd.detail); } } else { - $rootScope.alerts.push(angular.extend({ - type: 'danger' - }, rejection.data)); + Alerter.showDanger(rejection.data.title, rejection.data.detail); } } return $q.reject(rejection); @@ -197,10 +196,17 @@ }; }); + Alerter.setSettings({ + prepend: false, + merge: true + }, { + fadeAfter: 0 + }); + } ]); - app.run(['$rootScope', '$state', 'AlertFactory', function($rootScope, $state, AlertFactory) { + app.run(['$rootScope', '$state', function($rootScope, $state) { $rootScope.loading = {}; @@ -217,6 +223,7 @@ $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState) { $rootScope.oldState = fromState; $rootScope.state = toState; + Alerter.removeAlerts(); }); $rootScope.$on('$stateChangeStart', function() { @@ -224,11 +231,11 @@ }); $rootScope.handleWSMessage = function(data) { - AlertFactory.showAlert(data.message, {time:7000}); + Alerter.showInfo(null, data.message, {fadeAfter: 7000}); }; $rootScope.showMessage = function(msg) { - AlertFactory.showAlert(msg, {time:7000}); + Alerter.showInfo(null, msg, {fadeAfter: 7000}); }; var socket = new ReconnectingWebSocket(Vipra.getWSURL(), null, { diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index 0ea47e94957666a4de36fe6e3f9d1790457f0a43..ea0a1d647815b812ea5ae6e565bebc90d3e19ed1 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -62,6 +62,11 @@ localStorage.tm = topicModel.id; }; + $scope.showFeedbackModal = function() { + $('#feedbackModal').modal(); + $('#feedbackIframe').attr('src', 'https://docs.google.com/forms/d/1RjXyGgw8F3v7QsfgOQKz0Koa2Dkr1wDNx3veRZr9I3o/viewform?embedded=true'); + }; + $scope.menubarSearch = function(query) { $state.transitionTo('index', { q: query @@ -281,15 +286,9 @@ randomSeed: 1 }, physics: { - maxVelocity: 70, barnesHut: { - centralGravity: 0.5, - springConstant: 0.01, - gravitationalConstant: -7000, - avoidOverlap: 0.2 - }, - stabilization: { - fit: false + springConstant: 0.008, + gravitationalConstant: -3500 } }, interaction: { @@ -1585,7 +1584,7 @@ $scope.calculatePages(); $scope.changePage = function(page) { - $scope.page = page; + $scope.page = parseInt(page, 10); }; $scope.toPage = function() { diff --git a/vipra-ui/app/js/factories.js b/vipra-ui/app/js/factories.js index 430f6af6acfa1ee836f697e63d7f83f17dab5943..49c0546dfed7b99b76585eaadc8bad979e37b5df 100644 --- a/vipra-ui/app/js/factories.js +++ b/vipra-ui/app/js/factories.js @@ -88,35 +88,4 @@ return $myResource(Vipra.config.restUrl + '/windows/:id'); }]); - app.factory('AlertFactory', [function() { - var alerts = $("#alerts"); - if(alerts.length === 0) { - alerts = $('<div id="alerts" class="alerts"></div>').appendTo('body'); - } - - function showAlert(msg, config) { - config = angular.merge({}, { - type: 'info', - time: 0, - dismissible: true - }, config); - var classes = 'alert alert-' + config.type; - if(config.dismissible) { - classes += ' alert-dismissible'; - } - var alert = $('<div class="' + classes + '" role="alert" style="display:none">' + (config.dismissible ? - '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>' : '') + - msg + '</div>').appendTo(alerts).fadeIn(); - if(config.time > 0) { - setTimeout(function() { - alert.fadeOut(300, function() { $(this).remove(); }); - }, config.time); - } - } - - return { - showAlert: showAlert - }; - }]); - })(); \ No newline at end of file diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index 525ddd12b3219419577a353745b85d89e9a99d41..7927ebaf59bfb86c9ac3db59f0b719e6f8feed8d 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -631,11 +631,16 @@ entity-menu { cursor: text !important; } +.link-wrapper { + position: relative; +} + .menu-button { position: absolute; + top: 0; } -.menu-button + * { +.menu-padding { padding-left: 15px; } @@ -862,6 +867,31 @@ entity-menu { margin: 10px auto; } +#feedbackModal { + .modal-dialog { + height: 100%; + margin-top: 0; + margin-bottom: 0; + padding-top: 30px; + padding-bottom: 30px; + } + .modal-content { + height: 100%; + } + .modal-body { + position: absolute; + top: 56px; + left: 0; + right: 0; + bottom: 0; + padding: 0; + } + iframe { + width: 100%; + height: 100%; + } +} + @keyframes spin { 100% { -webkit-transform: rotateY(360deg); diff --git a/vipra-ui/bower.json b/vipra-ui/bower.json index 8c9f679495ef4808a7334a6cdaabac02eddf1bbe..42042c28c8c789ac235ed23d8352d766df5da2df 100644 --- a/vipra-ui/bower.json +++ b/vipra-ui/bower.json @@ -34,6 +34,7 @@ "bootbox.js": "bootbox#^4.x", "angular-hotkeys": "chieffancypants/angular-hotkeys#^1.x", "eonasdan-bootstrap-datetimepicker": "^4.x", - "reconnectingWebsocket": "joewalnes/reconnecting-websocket#^1.0.0" + "reconnectingWebsocket": "joewalnes/reconnecting-websocket#^1.0.0", + "angulartics-google-analytics": "^0.1.4" } } diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js index 01da53265233c67e0b2a18762ce9a7a3b110160a..28223ec704fb022461bd78ba4cf1ce07d1d7139b 100644 --- a/vipra-ui/gulpfile.js +++ b/vipra-ui/gulpfile.js @@ -28,7 +28,9 @@ var assets = { 'bower_components/randomcolor/randomColor.js', 'bower_components/bootbox.js/bootbox.js', 'bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js', - 'bower_components/reconnectingWebsocket/reconnecting-websocket.min.js' + 'bower_components/reconnectingWebsocket/reconnecting-websocket.min.js', + 'bower_components/angulartics/dist/angulartics.min.js', + 'bower_components/angulartics-google-analytics/dist/angulartics-google-analytics.min.js' ], css: [ 'bower_components/bootstrap/dist/css/bootstrap.min.css', diff --git a/vipra-util/src/main/java/de/vipra/util/Mongo.java b/vipra-util/src/main/java/de/vipra/util/Mongo.java index cc0c78643769256fd4c7fe4e2994df8c548d0a89..93ee04414396672cb3a711213a53c72b62f46ff5 100644 --- a/vipra-util/src/main/java/de/vipra/util/Mongo.java +++ b/vipra-util/src/main/java/de/vipra/util/Mongo.java @@ -41,8 +41,11 @@ public class Mongo { client = new MongoClient(host + ":" + port, options); morphia = new Morphia(); - morphia.mapPackage("de.vipra.util.model"); datastore = morphia.createDatastore(client, databaseName); + + morphia.mapPackage("de.vipra.util.model"); + datastore.ensureIndexes(); + datastore.ensureCaps(); } public MongoClient getClient() { diff --git a/vipra-util/src/main/java/de/vipra/util/model/Article.java b/vipra-util/src/main/java/de/vipra/util/model/Article.java index b2c16747384b43ca7b885f97604be30c5ce7494e..3f47d2fbe31431659fec38966f559461ed6e88f5 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/Article.java +++ b/vipra-util/src/main/java/de/vipra/util/model/Article.java @@ -13,7 +13,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) @SuppressWarnings("serial") @Entity(value = "articles", noClassnameStored = true) -@Indexes({ @Index("title"), @Index("date"), @Index("-created") }) +@Indexes({ @Index("title"), @Index("-title"), @Index("topicsCount"), @Index("-topicsCount") }) public class Article implements Model<ObjectId>, Serializable { @Id diff --git a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java index a4bf05c0ce1aa10a927616d10f2305eb03809045..38d202dec42a3e5bd2d2cd5053c8c614224895ed 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java +++ b/vipra-util/src/main/java/de/vipra/util/model/ArticleFull.java @@ -32,7 +32,8 @@ import de.vipra.util.an.QueryIgnore; @JsonIgnoreProperties(ignoreUnknown = true) @SuppressWarnings("serial") @Entity(value = "articles", noClassnameStored = true) -@Indexes({ @Index("title"), @Index("date"), @Index("-created") }) +@Indexes({ @Index("title"), @Index("-title"), @Index("date"), @Index("-date"), @Index("topicsCount"), @Index("-topicsCount"), @Index("created"), + @Index("-created"), @Index("modified"), @Index("-modified") }) public class ArticleFull implements Model<ObjectId>, Serializable { public static final Logger log = LoggerFactory.getLogger(ArticleFull.class); diff --git a/vipra-util/src/main/java/de/vipra/util/model/Sequence.java b/vipra-util/src/main/java/de/vipra/util/model/Sequence.java index f83d0c66916041fd64d18163a4f970ce22d1dfa3..46f61d39fd0af184470c1af6f2f522a87ce0bfe5 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/Sequence.java +++ b/vipra-util/src/main/java/de/vipra/util/model/Sequence.java @@ -6,12 +6,15 @@ import org.bson.types.ObjectId; import org.mongodb.morphia.annotations.Embedded; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; +import org.mongodb.morphia.annotations.Index; +import org.mongodb.morphia.annotations.Indexes; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) @SuppressWarnings("serial") @Entity(value = "sequences", noClassnameStored = true) +@Indexes({ @Index("relevance"), @Index("-relevance"), @Index("relevanceChange"), @Index("-relevanceChange") }) public class Sequence implements Model<ObjectId>, Comparable<Sequence>, Serializable { @Id diff --git a/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java b/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java index dbdfd71a4011077e8c8ccd899b58d416802609de..9af2291bd4566caa8486f7f705877e28dce71cdb 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java +++ b/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java @@ -8,6 +8,8 @@ import org.bson.types.ObjectId; import org.mongodb.morphia.annotations.Embedded; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; +import org.mongodb.morphia.annotations.Index; +import org.mongodb.morphia.annotations.Indexes; import org.mongodb.morphia.annotations.PrePersist; import org.mongodb.morphia.annotations.Reference; @@ -18,6 +20,8 @@ import de.vipra.util.an.QueryIgnore; @JsonIgnoreProperties(ignoreUnknown = true) @SuppressWarnings("serial") @Entity(value = "sequences", noClassnameStored = true) +@Indexes({ @Index("relevance"), @Index("-relevance"), @Index("relevanceChange"), @Index("-relevanceChange"), @Index("created"), @Index("-created"), + @Index("modified"), @Index("-modified") }) public class SequenceFull implements Model<ObjectId>, Comparable<SequenceFull>, Serializable { @Id diff --git a/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java b/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java index e5423b2969afcc52db6304a17baa2dd27c855c78..e8313bc25afcb29b17626ae173def6cc59d26b25 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TextEntity.java @@ -6,12 +6,15 @@ import java.util.List; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; +import org.mongodb.morphia.annotations.Index; +import org.mongodb.morphia.annotations.Indexes; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) @SuppressWarnings("serial") @Entity(value = "textentities", noClassnameStored = true) +@Indexes({ @Index("url"), @Index("-url") }) public class TextEntity implements Model<String>, Serializable { @Id diff --git a/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java b/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java index 2058565eed7f57fd44259bcf3be60dad50b275a6..44e6484ffef899328db596ded9122b6c31f41c02 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TextEntityFull.java @@ -6,6 +6,8 @@ import java.util.List; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; +import org.mongodb.morphia.annotations.Index; +import org.mongodb.morphia.annotations.Indexes; import org.mongodb.morphia.annotations.PrePersist; import org.mongodb.morphia.annotations.Reference; @@ -13,6 +15,7 @@ import de.vipra.util.an.QueryIgnore; @SuppressWarnings("serial") @Entity(value = "textentities", noClassnameStored = true) +@Indexes({ @Index("url"), @Index("-url"), @Index("created"), @Index("-created"), @Index("modified"), @Index("-modified") }) public class TextEntityFull implements Model<String>, Serializable { @Id diff --git a/vipra-util/src/main/java/de/vipra/util/model/Topic.java b/vipra-util/src/main/java/de/vipra/util/model/Topic.java index a52d6ca6fbd799f5139d52d4f08426b370c83814..ef9696e175b1e6f0f96924ad2abdd352f380221c 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/Topic.java +++ b/vipra-util/src/main/java/de/vipra/util/model/Topic.java @@ -15,7 +15,7 @@ import de.vipra.util.MongoUtils; @JsonIgnoreProperties(ignoreUnknown = true) @SuppressWarnings("serial") @Entity(value = "topics", noClassnameStored = true) -@Indexes({ @Index("name"), @Index("-created") }) +@Indexes({ @Index("name"), @Index("-name"), @Index("articlesCount"), @Index("-articlesCount") }) public class Topic implements Model<ObjectId>, Serializable { @Id diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java index 5947d68087714e087ccc46dca28fc46927103f1c..77fec9e13684e501dffd96675a36fb80e17a4398 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java @@ -23,7 +23,8 @@ import de.vipra.util.an.QueryIgnore; @JsonIgnoreProperties(ignoreUnknown = true) @SuppressWarnings("serial") @Entity(value = "topics", noClassnameStored = true) -@Indexes({ @Index("name"), @Index("-created") }) +@Indexes({ @Index("name"), @Index("-name"), @Index("articlesCount"), @Index("-articlesCount"), @Index("created"), @Index("-created"), + @Index("modified"), @Index("-modified") }) public class TopicFull implements Model<ObjectId>, Serializable { @Id