Merge pull request #1679 from emanuelschuetze/chat

Chatbox
This commit is contained in:
Oskar Hahn 2015-11-23 10:35:26 +01:00
commit 984022baa4
5 changed files with 172 additions and 28 deletions

View File

@ -21,6 +21,7 @@
"angular-ui-tree": "~2.10.0", "angular-ui-tree": "~2.10.0",
"angular-gettext": "~2.1.2", "angular-gettext": "~2.1.2",
"angular-xeditable": "~0.1.9", "angular-xeditable": "~0.1.9",
"angular-scroll-glue": "~2.0.6",
"ngBootbox": "~0.1.2", "ngBootbox": "~0.1.2",
"sockjs": "~0.3.4", "sockjs": "~0.3.4",
"font-awesome-bower": "~4.4.0", "font-awesome-bower": "~4.4.0",

View File

@ -71,6 +71,10 @@ body {
margin-top: 5px; margin-top: 5px;
} }
.inline {
display: inline;
}
/* ngAnimate classes */ /* ngAnimate classes */
.animate-item.ng-enter { .animate-item.ng-enter {
@ -93,8 +97,23 @@ body {
100% { opacity: 1; background: none; } 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 */
#header { #header {
background-color: #333333; background-color: #333333;

View File

@ -80,9 +80,10 @@ angular.module('OpenSlidesApp.core', [
.factory('loadGlobalData', [ .factory('loadGlobalData', [
'$rootScope', '$rootScope',
'$http', '$http',
'ChatMessage',
'Config', 'Config',
'Projector', 'Projector',
function ($rootScope, $http, Config, Projector) { function ($rootScope, $http, ChatMessage, Config, Projector) {
return function () { return function () {
// Puts the config object into each scope. // Puts the config object into each scope.
Config.findAll().then(function() { Config.findAll().then(function() {
@ -100,6 +101,18 @@ angular.module('OpenSlidesApp.core', [
// Loads all projector data // Loads all projector data
Projector.findAll(); 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 // Loads server time and calculates server offset
$http.get('/core/servertime/').then(function(data) { $http.get('/core/servertime/').then(function(data) {
$rootScope.serverOffset = Math.floor( Date.now() / 1000 - data.data ); $rootScope.serverOffset = Math.floor( Date.now() / 1000 - data.data );
@ -168,18 +181,41 @@ angular.module('OpenSlidesApp.core', [
} }
]) ])
.factory('Tag', ['DS', function(DS) { .factory('Tag', [
'DS',
function(DS) {
return DS.defineResource({ return DS.defineResource({
name: 'core/tag', name: 'core/tag',
}); });
}]) }
])
.factory('Config', ['DS', function(DS) { .factory('Config', [
'DS',
function(DS) {
return DS.defineResource({ return DS.defineResource({
name: 'core/config', name: 'core/config',
idAttribute: 'key', 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. /* 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 // 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) {}
]);
}()); }());

View File

@ -13,6 +13,7 @@ angular.module('OpenSlidesApp.core.site', [
'ngCsvImport', 'ngCsvImport',
'ngSanitize', // TODO: only use this in functions that need it. 'ngSanitize', // TODO: only use this in functions that need it.
'ui.select', 'ui.select',
'luegg.directives',
'xeditable', 'xeditable',
'ckeditor', '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) { .directive('osFocusMe', function ($timeout) {
return { return {
link: function (scope, element, attrs, model) { link: function (scope, element, attrs, model) {

View File

@ -21,10 +21,43 @@
<span class="navbar-text optional">{{ config('general_event_name') }}</span> <span class="navbar-text optional">{{ config('general_event_name') }}</span>
</div> </div>
<div class="navbar-right" ng-controller="userMenu"> <div class="navbar-right" ng-controller="userMenu">
<!-- login/logout button -->
<div class="btn-group"> <div class="btn-group">
<div ng-if="operator.isAuthenticated()"> <div ng-if="operator.isAuthenticated()">
<!-- chatbox -->
<div ng-controller="ChatMessageCtrl" os-perms="core.can_use_chat" class="inline">
<button ng-click="openChatbox()" class="btn btn-default">
<i class="fa fa-comment"></i>
<translate>Chat</translate>
<span ng-if="unreadMessages > 0 && chatboxIsCollapsed" class="badge">
{{ unreadMessages }}
</span>
</button>
<div id="chatbox" class="well" uib-collapse="chatboxIsCollapsed">
<div id="chatbox-text" scroll-glue>
<ul class="list-unstyled">
<li ng-repeat="chatmessage in chatmessages">
<small class="grey">{{ chatmessage.timestamp | date:'HH:mm:ss' }}</small>
<small>{{ chatmessage.user.short_name }}:</small>
{{ chatmessage.message }}
</ul>
</div>
<div id="chatbox-form">
<form ng-submit="sendMessage()">
<div class="input-group">
<input ng-model="newMessage" id="messageInput" class="form-control" type="text">
<span class="input-group-btn">
<button type="submit" class="btn btn-default" id="messageSendButton">
<i class="fa fa-comment"></i>
</button>
</span>
</div>
</form>
</div>
</div>
</div>
<!-- user settings / logout button -->
<div class="btn-group" dropdown is-open="status.isopen"> <div class="btn-group" dropdown is-open="status.isopen">
<button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle> <button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle>
<i class="fa fa-user"></i> <i class="fa fa-user"></i>
@ -127,9 +160,10 @@
</nav> </nav>
<!-- Container --> <!-- Container -->
<div class="container-fluid" id="container"> <div id="container" class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-2 leftmenu lefticon"> <!-- Main menu -->
<div id="main-menu" class="col-md-2 leftmenu lefticon">
<ul ng-controller="MainMenuCtrl"> <ul ng-controller="MainMenuCtrl">
<li ng-repeat="element in elements"> <li ng-repeat="element in elements">
<a ui-sref="{{ element.ui_sref }}"> <a ui-sref="{{ element.ui_sref }}">
@ -137,8 +171,7 @@
<span class="text" translate>{{ element.title }}</span> <span class="text" translate>{{ element.title }}</span>
</a> </a>
</ul> </ul>
</div> </div><!--/#main-menu-->
<!-- Content --> <!-- Content -->
<div id="content" class="col-md-10"> <div id="content" class="col-md-10">
<div class="row"> <div class="row">
@ -146,16 +179,16 @@
<div ui-view></div> <div ui-view></div>
</div> </div>
</div> </div>
<hr /> </div><!--/#content-->
</div><!--/.row-->
<hr>
<footer> <footer>
<small> <small>
&copy; Copyright 2011-2015 | &copy; Copyright by <a href="http://www.openslides.org" target="_blank">OpenSlides</a> |
Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> |
<a ui-sref="version">Version</a> <a ui-sref="version">Version</a>
</small> </small>
</footer> </footer>
</div><!--/#content-->
</div><!--/.row-->
</div><!--/#container--> </div><!--/#container-->
<script src="/angular_js/site/"></script> <script src="/angular_js/site/"></script>