From cde9eb1e5090bdb5ee89e6b9a32532aefe92be07 Mon Sep 17 00:00:00 2001 From: Eike Cochu <eike@cochu.com> Date: Thu, 12 May 2016 02:57:07 +0200 Subject: [PATCH] fixed bugs added proper model indexes to mongodb models added missed indexing and capped collection call to mongodb datastore fixed paginator go to page bug, where all pages would be shown replaced ng-repeat alert schema with alerter, javascript library clearing alerts on state change reverted network settings to slower convergence fixed item-link menu dropdowns unusable fit index page to full hd added google analytics + angular library added feedback modal and google forms survey as iframe --- vipra-cmd/runcfg/CMD.launch | 2 +- .../de/vipra/cmd/option/BackupCommand.java | 53 ++- .../de/vipra/cmd/option/RestoreCommand.java | 44 ++- .../app/html/directives/article-link.html | 4 +- vipra-ui/app/html/directives/entity-link.html | 16 +- vipra-ui/app/html/directives/topic-link.html | 8 +- vipra-ui/app/html/directives/word-link.html | 8 +- vipra-ui/app/index.html | 28 +- vipra-ui/app/js/alerter.js | 311 ++++++++++++++++++ vipra-ui/app/js/app.js | 27 +- vipra-ui/app/js/controllers.js | 17 +- vipra-ui/app/js/factories.js | 31 -- vipra-ui/app/less/app.less | 32 +- vipra-ui/bower.json | 3 +- vipra-ui/gulpfile.js | 4 +- .../src/main/java/de/vipra/util/Mongo.java | 5 +- .../java/de/vipra/util/model/Article.java | 2 +- .../java/de/vipra/util/model/ArticleFull.java | 3 +- .../java/de/vipra/util/model/Sequence.java | 3 + .../de/vipra/util/model/SequenceFull.java | 4 + .../java/de/vipra/util/model/TextEntity.java | 3 + .../de/vipra/util/model/TextEntityFull.java | 3 + .../main/java/de/vipra/util/model/Topic.java | 2 +- .../java/de/vipra/util/model/TopicFull.java | 3 +- 24 files changed, 514 insertions(+), 102 deletions(-) create mode 100644 vipra-ui/app/js/alerter.js diff --git a/vipra-cmd/runcfg/CMD.launch b/vipra-cmd/runcfg/CMD.launch index d44936f9..b36db2e1 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 d656552d..57e68758 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 857a35cd..c1bc3024 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 6573ef9f..13791ba6 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 569cdb43..b0090b88 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 411e1e14..f4595e81 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 268cec13..9e852ae3 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 175de291..12e07f0b 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 00000000..6e9c1e58 --- /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 6754e3f7..55301b93 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 0ea47e94..ea0a1d64 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 430f6af6..49c0546d 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 525ddd12..7927ebaf 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 8c9f6794..42042c28 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 01da5326..28223ec7 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 cc0c7864..93ee0441 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 b2c16747..3f47d2fb 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 a4bf05c0..38d202de 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 f83d0c66..46f61d39 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 dbdfd71a..9af2291b 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 e5423b29..e8313bc2 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 2058565e..44e6484f 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 a52d6ca6..ef9696e1 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 5947d680..77fec9e1 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 -- GitLab