diff --git a/bower.json b/bower.json index c0e9df018..dd08732fd 100644 --- a/bower.json +++ b/bower.json @@ -21,6 +21,7 @@ "angular-ui-tree": "~2.10.0", "angular-gettext": "~2.1.2", "angular-xeditable": "~0.1.9", + "angular-scroll-glue": "~2.0.6", "ngBootbox": "~0.1.2", "sockjs": "~0.3.4", "font-awesome-bower": "~4.4.0", diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index 879f6147e..3e030c968 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -71,6 +71,10 @@ body { margin-top: 5px; } +.inline { + display: inline; +} + /* ngAnimate classes */ .animate-item.ng-enter { @@ -93,8 +97,23 @@ body { 100% { opacity: 1; background: none; } } - - +/* Chatbox */ +#chatbox { + position: fixed; + top: 50px; + right: 0; + width: 40%; + border-color: #dddddd; + border-width: 1px; + box-shadow: -5px 5px 5px rgba(0, 0, 0, 0.2); + height: 200px; + padding: 0 10px 10px 10px; + z-index: 99; +} +#chatbox-text { + overflow-y: scroll; + height: 190px; +} /* Header */ #header { background-color: #333333; diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js index d8ebf59db..9e86c1ca5 100644 --- a/openslides/core/static/js/core/base.js +++ b/openslides/core/static/js/core/base.js @@ -80,9 +80,10 @@ angular.module('OpenSlidesApp.core', [ .factory('loadGlobalData', [ '$rootScope', '$http', + 'ChatMessage', 'Config', 'Projector', - function ($rootScope, $http, Config, Projector) { + function ($rootScope, $http, ChatMessage, Config, Projector) { return function () { // Puts the config object into each scope. Config.findAll().then(function() { @@ -100,6 +101,18 @@ angular.module('OpenSlidesApp.core', [ // Loads all projector data Projector.findAll(); + // Loads all chat messages data and their user_ids + // TODO: add permission check if user has required chat permission + // error if include 'operator' here: + // "Circular dependency found: loadGlobalData <- operator <- loadGlobalData" + //if (operator.hasPerms("core.can_use_chat")) { + ChatMessage.findAll().then( function(chatmessages) { + angular.forEach(chatmessages, function (chatmessage) { + ChatMessage.loadRelations(chatmessage, 'user'); + }); + }); + //} + // Loads server time and calculates server offset $http.get('/core/servertime/').then(function(data) { $rootScope.serverOffset = Math.floor( Date.now() / 1000 - data.data ); @@ -168,18 +181,41 @@ angular.module('OpenSlidesApp.core', [ } ]) -.factory('Tag', ['DS', function(DS) { - return DS.defineResource({ - name: 'core/tag', - }); -}]) +.factory('Tag', [ + 'DS', + function(DS) { + return DS.defineResource({ + name: 'core/tag', + }); + } +]) -.factory('Config', ['DS', function(DS) { - return DS.defineResource({ - name: 'core/config', - idAttribute: 'key', - }); -}]) +.factory('Config', [ + 'DS', + function(DS) { + return DS.defineResource({ + name: 'core/config', + idAttribute: 'key', + }); + } +]) + +.factory('ChatMessage', [ + 'DS', + function(DS) { + return DS.defineResource({ + name: 'core/chatmessage', + relations: { + belongsTo: { + 'users/user': { + localField: 'user', + localKey: 'user_id', + } + } + } + }); + } +]) /* Model for a projector. * @@ -258,6 +294,13 @@ angular.module('OpenSlidesApp.core', [ }) // Make sure that the DS factories are loaded by making them a dependency -.run(['Projector', 'Config', 'Tag', 'Customslide', function(Projector, Config, Tag, Customslide){}]); +.run([ + 'ChatMessage', + 'Config', + 'Customslide', + 'Projector', + 'Tag', + function (ChatMessage, Config, Customslide, Projector, Tag) {} +]); }()); diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index 5946bd4bd..97c812706 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -13,6 +13,7 @@ angular.module('OpenSlidesApp.core.site', [ 'ngCsvImport', 'ngSanitize', // TODO: only use this in functions that need it. 'ui.select', + 'luegg.directives', 'xeditable', 'ckeditor', ]) @@ -752,6 +753,53 @@ angular.module('OpenSlidesApp.core.site', [ }; }) +// counter of new (unread) chat messages +.value('NewChatMessages', []) + +// ChatMessage Controller +.controller('ChatMessageCtrl', [ + '$scope', + '$http', + 'ChatMessage', + 'NewChatMessages', + function ($scope, $http, ChatMessage, NewChatMessages) { + ChatMessage.bindAll({}, $scope, 'chatmessages'); + $scope.unreadMessages = NewChatMessages.length; + $scope.chatboxIsCollapsed = true; + $scope.openChatbox = function () { + $scope.chatboxIsCollapsed = !$scope.chatboxIsCollapsed; + NewChatMessages = []; + $scope.unreadMessages = NewChatMessages.length; + } + $scope.sendMessage = function () { + angular.element('#messageSendButton').addClass('disabled'); + angular.element('#messageInput').attr('disabled', ''); + $http.post( + '/rest/core/chatmessage/', + {message: $scope.newMessage} + ) + .success(function () { + $scope.newMessage = ''; + angular.element('#messageSendButton').removeClass('disabled'); + angular.element('#messageInput').removeAttr('disabled'); + }) + .error(function () { + angular.element('#messageSendButton').removeClass('disabled'); + angular.element('#messageInput').removeAttr('disabled'); + }); + }; + // increment unread messages counter for each new message + $scope.$watch('chatmessages', function (newVal, oldVal) { + // add new message id if there is really a new message which is not yet tracked + if ((oldVal[oldVal.length-1].id != newVal[newVal.length-1].id) && + ($.inArray(newVal[newVal.length-1].id, NewChatMessages) == -1)) { + NewChatMessages.push(newVal[newVal.length-1].id); + $scope.unreadMessages = NewChatMessages.length; + } + }) + } +]) + .directive('osFocusMe', function ($timeout) { return { link: function (scope, element, attrs, model) { diff --git a/openslides/core/static/templates/index.html b/openslides/core/static/templates/index.html index 0281d79a1..ef34563a6 100644 --- a/openslides/core/static/templates/index.html +++ b/openslides/core/static/templates/index.html @@ -21,10 +21,43 @@