diff options
Diffstat (limited to 'syweb/webclient/room')
-rw-r--r-- | syweb/webclient/room/room-controller.js | 581 | ||||
-rw-r--r-- | syweb/webclient/room/room-directive.js | 276 | ||||
-rw-r--r-- | syweb/webclient/room/room.html | 282 |
3 files changed, 0 insertions, 1139 deletions
diff --git a/syweb/webclient/room/room-controller.js b/syweb/webclient/room/room-controller.js deleted file mode 100644 index df05a8e573..0000000000 --- a/syweb/webclient/room/room-controller.js +++ /dev/null @@ -1,581 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput', 'angular-peity']) -.controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'modelService', 'recentsService', 'commandsService', 'mUserDisplayNameFilter', - function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, modelService, recentsService, commandsService, mUserDisplayNameFilter) { - 'use strict'; - var MESSAGES_PER_PAGINATION = 30; - var THUMBNAIL_SIZE = 320; - - // .html needs this - $scope.containsBingWord = eventHandlerService.eventContainsBingWord; - - // Room ids. Computed and resolved in onInit - $scope.room_id = undefined; - - $scope.state = { - user_id: matrixService.config().user_id, - permission_denied: undefined, // If defined, this string contains the reason why the user cannot join the room - first_pagination: true, // this is toggled off when the first pagination is done - can_paginate: false, // this is toggled off when we are not ready yet to paginate or when we run out of items - paginating: false, // used to avoid concurrent pagination requests pulling in dup contents - stream_failure: undefined, // the response when the stream fails - waiting_for_joined_event: false, // true when the join request is pending. Back to false once the corresponding m.room.member event is received - messages_visibility: "hidden", // In order to avoid flickering when scrolling down the message table at the page opening, delay the message table display - }; - - $scope.imageURLToSend = ""; - - - // vars and functions for updating the name - $scope.name = { - isEditing: false, - newNameText: "", - editName: function() { - if ($scope.name.isEditing) { - console.log("Warning: Already editing name."); - return; - }; - - var nameEvent = $scope.room.current_room_state.state_events['m.room.name']; - if (nameEvent) { - $scope.name.newNameText = nameEvent.content.name; - } - else { - $scope.name.newNameText = ""; - } - - // Force focus to the input - $timeout(function() { - angular.element('.roomNameInput').focus(); - }, 0); - - $scope.name.isEditing = true; - }, - updateName: function() { - console.log("Updating name to "+$scope.name.newNameText); - matrixService.setName($scope.room_id, $scope.name.newNameText).then( - function() { - }, - function(error) { - $scope.feedback = "Request failed: " + error.data.error; - } - ); - - $scope.name.isEditing = false; - }, - cancelEdit: function() { - $scope.name.isEditing = false; - } - }; - - // vars and functions for updating the topic - $scope.topic = { - isEditing: false, - newTopicText: "", - editTopic: function() { - if ($scope.topic.isEditing) { - console.log("Warning: Already editing topic."); - return; - } - var topicEvent = $scope.room.current_room_state.state_events['m.room.topic']; - if (topicEvent) { - $scope.topic.newTopicText = topicEvent.content.topic; - } - else { - $scope.topic.newTopicText = ""; - } - - // Force focus to the input - $timeout(function() { - angular.element('.roomTopicInput').focus(); - }, 0); - - $scope.topic.isEditing = true; - }, - updateTopic: function() { - console.log("Updating topic to "+$scope.topic.newTopicText); - matrixService.setTopic($scope.room_id, $scope.topic.newTopicText).then( - function() { - }, - function(error) { - $scope.feedback = "Request failed: " + error.data.error; - } - ); - - $scope.topic.isEditing = false; - }, - cancelEdit: function() { - $scope.topic.isEditing = false; - } - }; - - var scrollToBottom = function(force) { - console.log("Scrolling to bottom"); - - // Do not autoscroll to the bottom to display the new event if the user is not at the bottom. - // Exception: in case where the event is from the user, we want to force scroll to the bottom - var objDiv = document.getElementById("messageTableWrapper"); - // add a 10px buffer to this check so if the message list is not *quite* - // at the bottom it still scrolls since it basically is at the bottom. - if ((10 + objDiv.offsetHeight + objDiv.scrollTop >= objDiv.scrollHeight) || force) { - - $timeout(function() { - objDiv.scrollTop = objDiv.scrollHeight; - - // Show the message table once the first scrolldown is done - if ("visible" !== $scope.state.messages_visibility) { - $timeout(function() { - $scope.state.messages_visibility = "visible"; - }, 0); - } - }, 0); - } - }; - - $scope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) { - if (isLive && event.room_id === $scope.room_id) { - scrollToBottom(); - } - }); - - $scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) { - // if there is a live event affecting us - if (isLive && event.room_id === $scope.room_id && event.state_key === $scope.state.user_id) { - // if someone else changed our state.. - if (event.user_id !== $scope.state.user_id && "invite" !== event.content.membership && "join" !== event.content.membership) { - if ("ban" === event.content.membership) { - $scope.state.permission_denied = "You have been banned by " + mUserDisplayNameFilter(event.user_id); - } - else { - $scope.state.permission_denied = "You have been kicked by " + mUserDisplayNameFilter(event.user_id); - } - } - else { - scrollToBottom(); - } - } - }); - - $scope.memberCount = function() { - return Object.keys($scope.room.now.members).length; - }; - - $scope.paginateMore = function() { - if ($scope.state.can_paginate) { - // console.log("Paginating more."); - paginate(MESSAGES_PER_PAGINATION); - } - }; - - var paginate = function(numItems) { - //console.log("paginate " + numItems + " and first_pagination is " + $scope.state.first_pagination); - if ($scope.state.paginating || !$scope.room_id) { - return; - } - else { - $scope.state.paginating = true; - } - - console.log("paginateBackMessages from " + $scope.room.old_room_state.pagination_token + " for " + numItems); - var originalTopRow = $("#messageTable>tbody>tr:first")[0]; - - // Paginate events from the point in cache - matrixService.paginateBackMessages($scope.room_id, $scope.room.old_room_state.pagination_token, numItems).then( - function(response) { - - eventHandlerService.handleRoomMessages($scope.room_id, response.data, false, 'b'); - if (response.data.chunk.length < MESSAGES_PER_PAGINATION) { - // no more messages to paginate. this currently never gets turned true again, as we never - // expire paginated contents in the current implementation. - $scope.state.can_paginate = false; - } - - $scope.state.paginating = false; - - var wrapper = $("#messageTableWrapper")[0]; - var table = $("#messageTable")[0]; - // console.log("wrapper height=" + wrapper.clientHeight + ", table scrollHeight=" + table.scrollHeight); - - if ($scope.state.can_paginate) { - // check we don't have to pull in more messages - // n.b. we dispatch through a timeout() to allow the digest to run otherwise the .height methods are stale - $timeout(function() { - if (table.scrollHeight < wrapper.clientHeight) { - paginate(MESSAGES_PER_PAGINATION); - scrollToBottom(); - } - }, 0); - } - - if ($scope.state.first_pagination) { - scrollToBottom(true); - $scope.state.first_pagination = false; - } - else { - // lock the scroll position - $timeout(function() { - // FIXME: this risks a flicker before the scrollTop is actually updated, but we have to - // dispatch it into a function in order to first update the layout. The right solution - // might be to implement it as a directive, more like - // http://stackoverflow.com/questions/23736647/how-to-retain-scroll-position-of-ng-repeat-in-angularjs - // however, this specific solution breaks because it measures the rows height before - // the contents are interpolated. - wrapper.scrollTop = originalTopRow ? (originalTopRow.offsetTop + wrapper.scrollTop) : 0; - }, 0); - } - }, - function(error) { - console.log("Failed to paginateBackMessages: " + JSON.stringify(error)); - $scope.state.paginating = false; - } - ); - }; - - var updatePresenceTimes = function() { - $scope.now = new Date().getTime(); - // TODO: don't bother polling every 5s if we know none of our counters are younger than 1 minute - $timeout(updatePresenceTimes, 5 * 1000); - }; - - $scope.send = function() { - var input = $('#mainInput').val(); - - if (undefined === input || input === "") { - return; - } - - scrollToBottom(true); - - // Store the command in the history - $rootScope.$broadcast("commandHistory:BROADCAST_NEW_HISTORY_ITEM(item)", - input); - - var isEmote = input.indexOf("/me ") === 0; - var promise; - if (!isEmote) { - promise = commandsService.processInput($scope.room_id, input); - } - var echo = false; - - - if (!promise) { // not a non-echoable command - echo = true; - if (isEmote) { - promise = matrixService.sendEmoteMessage($scope.room_id, input.substring(4)); - } - else { - promise = matrixService.sendTextMessage($scope.room_id, input); - } - } - - if (echo) { - // Echo the message to the room - // To do so, create a minimalist fake text message event and add it to the in-memory list of room messages - var echoMessage = { - content: { - body: (isEmote ? input.substring(4) : input), - msgtype: (isEmote ? "m.emote" : "m.text"), - }, - origin_server_ts: new Date().getTime(), // fake a timestamp - room_id: $scope.room_id, - type: "m.room.message", - user_id: $scope.state.user_id, - echo_msg_state: "messagePending" // Add custom field to indicate the state of this fake message to HTML - }; - - $('#mainInput').val(''); - $scope.room.addMessageEvent(echoMessage); - scrollToBottom(); - } - - if (promise) { - // Reset previous feedback - $scope.feedback = ""; - - promise.then( - function(response) { - console.log("Request successfully sent"); - - if (echo) { - // Mark this fake message event with its allocated event_id - // When the true message event will come from the events stream (in handleMessage), - // we will be able to replace the fake one by the true one - echoMessage.event_id = response.data.event_id; - } - else { - $('#mainInput').val(''); - } - }, - function(error) { - $scope.feedback = error.data.error; - - if (echoMessage) { - // Mark the message as unsent for the rest of the page life - echoMessage.origin_server_ts = "Unsent"; - echoMessage.echo_msg_state = "messageUnSent"; - } - }); - } - }; - - // Tries to find a suitable room ID for this room. - $scope.onInit = function() { - console.log("onInit"); - - // Extract the room identifier being loaded - var room_id_or_alias; - if ($routeParams.room_id_or_alias) { // provided in the url - room_id_or_alias = decodeURIComponent($routeParams.room_id_or_alias); - } - - eventHandlerService.joinRoom(room_id_or_alias).then(function(roomId) { - $scope.room_id = roomId; - $scope.room = modelService.getRoom($scope.room_id); - - var messages = $scope.room.events; - - if (0 === messages.length - || (1 === messages.length && "m.room.member" === messages[0].type && "invite" === messages[0].content.membership && $scope.state.user_id === messages[0].state_key)) { - // If we just joined a room, we won't have this history from initial sync, so we should try to paginate it anyway - $scope.state.first_pagination = true; - } - else { - // There is no need to do a 1st pagination (initialSync provided enough to fill a page) - $scope.state.first_pagination = false; - } - - // Make recents highlight the current room - recentsService.setSelectedRoomId($scope.room_id); - - updatePresenceTimes(); - - // Allow pagination - $scope.state.can_paginate = true; - - // Do a first pagination only if it is required (e.g. we've JUST joined a room and have no messages to display.) - // FIXME: Should be no more require when initialSync/{room_id} will be available - if ($scope.state.first_pagination) { - paginate(MESSAGES_PER_PAGINATION); - } - - // Scroll down as soon as possible so that we point to the last message - // if it already exists in memory - scrollToBottom(true); - }, - function(err) { - if (err.data.errcode === "M_FORBIDDEN") { - $scope.state.permission_denied = "You do not have permission to join this room"; - } - else { - console.log("Error: cannot join room: "+JSON.stringify(err)); - $location.url("/"); - } - }); - }; - - $scope.leaveRoom = function() { - - matrixService.leave($scope.room_id).then( - function(response) { - console.log("Left room " + $scope.room_id); - $location.url("home"); - }, - function(error) { - $scope.feedback = "Failed to leave room: " + error.data.error; - }); - }; - - // used to send an image based on just a URL, rather than uploading one - $scope.sendImage = function(url, body) { - scrollToBottom(true); - - matrixService.sendImageMessage($scope.room_id, url, body).then( - function() { - console.log("Image sent"); - }, - function(error) { - $scope.feedback = "Failed to send image: " + error.data.error; - }); - }; - - $scope.fileToSend; - $scope.$watch("fileToSend", function(newValue, oldValue) { - if ($scope.fileToSend) { - // Upload this file - mFileUpload.uploadFileAndThumbnail($scope.fileToSend, THUMBNAIL_SIZE).then( - function(fileMessage) { - // fileMessage is complete message structure, send it as is - matrixService.sendMessage($scope.room_id, undefined, fileMessage).then( - function() { - console.log("File message sent"); - }, - function(error) { - $scope.feedback = "Failed to send file message: " + error.data.error; - }); - }, - function(error) { - $scope.feedback = "Can't upload file"; - } - ); - } - }); - - $scope.loadMoreHistory = function() { - paginate(MESSAGES_PER_PAGINATION); - }; - - $scope.checkWebRTC = function() { - if (!$rootScope.isWebRTCSupported()) { - alert("Your browser does not support WebRTC"); - return false; - } - if ($scope.memberCount() != 2) { - alert("WebRTC calls are currently only supported on rooms with two members"); - return false; - } - return true; - }; - - $scope.startVoiceCall = function() { - if (!$scope.checkWebRTC()) return; - var call = new MatrixCall($scope.room_id); - call.onError = $rootScope.onCallError; - call.onHangup = $rootScope.onCallHangup; - // remote video element is used for playing audio in voice calls - call.remoteVideoSelector = angular.element('#remoteVideo')[0]; - call.placeVoiceCall(); - $rootScope.currentCall = call; - }; - - $scope.startVideoCall = function() { - if (!$scope.checkWebRTC()) return; - - var call = new MatrixCall($scope.room_id); - call.onError = $rootScope.onCallError; - call.onHangup = $rootScope.onCallHangup; - call.localVideoSelector = '#localVideo'; - call.remoteVideoSelector = '#remoteVideo'; - call.placeVideoCall(); - $rootScope.currentCall = call; - }; - - $scope.openJson = function(content) { - $scope.event_selected = angular.copy(content); - - // FIXME: Pre-calculated event data should be stripped in a nicer way. - $scope.event_selected.__room_member = undefined; - $scope.event_selected.__target_room_member = undefined; - - // scope this so the template can check power levels and enable/disable - // buttons - $scope.pow = modelService.getUserPowerLevel; - - var modalInstance = $modal.open({ - templateUrl: 'eventInfoTemplate.html', - controller: 'EventInfoController', - scope: $scope - }); - - modalInstance.result.then(function(action) { - if (action === "redact") { - var eventId = $scope.event_selected.event_id; - console.log("Redacting event ID " + eventId); - matrixService.redactEvent( - $scope.event_selected.room_id, - eventId - ).then(function(response) { - console.log("Redaction = " + JSON.stringify(response)); - }, function(error) { - console.error("Failed to redact event: "+JSON.stringify(error)); - if (error.data.error) { - $scope.feedback = error.data.error; - } - }); - } - }, function() { - // any dismiss code - }); - }; - - $scope.openRoomInfo = function() { - $scope.roomInfo = {}; - $scope.roomInfo.newEvent = { - content: {}, - type: "", - state_key: "" - }; - - var stateEvents = $scope.room.current_room_state.state_events; - // The modal dialog will 2-way bind this field, so we MUST make a deep - // copy of the state events else we will be *actually adjusing our view - // of the world* when fiddling with the JSON!! Apparently parse/stringify - // is faster than jQuery's extend when doing deep copies. - $scope.roomInfo.stateEvents = JSON.parse(JSON.stringify(stateEvents)); - var modalInstance = $modal.open({ - templateUrl: 'roomInfoTemplate.html', - controller: 'RoomInfoController', - size: 'lg', - scope: $scope - }); - }; - -}]) -.controller('EventInfoController', function($scope, $modalInstance) { - console.log("Displaying modal dialog for >>>> " + JSON.stringify($scope.event_selected)); - $scope.redact = function() { - console.log("User level = "+$scope.pow($scope.room_id, $scope.state.user_id)+ - " Redact level = "+$scope.room.current_room_state.state_events["m.room.ops_levels"].content.redact_level); - console.log("Redact event >> " + JSON.stringify($scope.event_selected)); - $modalInstance.close("redact"); - }; - $scope.dismiss = $modalInstance.dismiss; -}) -.controller('RoomInfoController', function($scope, $modalInstance, $filter, matrixService) { - console.log("Displaying room info."); - - $scope.userIDToInvite = ""; - - $scope.inviteUser = function() { - - matrixService.invite($scope.room_id, $scope.userIDToInvite).then( - function() { - console.log("Invited."); - $scope.feedback = "Invite successfully sent to " + $scope.userIDToInvite; - $scope.userIDToInvite = ""; - }, - function(reason) { - $scope.feedback = "Failure: " + reason.data.error; - }); - }; - - $scope.submit = function(event) { - if (event.content) { - console.log("submit >>> " + JSON.stringify(event.content)); - matrixService.sendStateEvent($scope.room_id, event.type, - event.content, event.state_key).then(function(response) { - $modalInstance.dismiss(); - }, function(err) { - $scope.feedback = err.data.error; - } - ); - } - }; - - $scope.dismiss = $modalInstance.dismiss; - -}); diff --git a/syweb/webclient/room/room-directive.js b/syweb/webclient/room/room-directive.js deleted file mode 100644 index 4707d12c8c..0000000000 --- a/syweb/webclient/room/room-directive.js +++ /dev/null @@ -1,276 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -angular.module('RoomController') -// XXX FIXME : This has tight coupling with $scope.room.now.members -.directive('tabComplete', ['$timeout', function ($timeout) { - return function (scope, element, attrs) { - element.bind("keydown keypress", function (event) { - // console.log("event: " + event.which); - var TAB = 9; - var SHIFT = 16; - var keypressCode = event.which; - if (keypressCode === TAB) { - if (!scope.tabCompleting) { // cache our starting text - scope.tabCompleteOriginal = element[0].value; - scope.tabCompleting = true; - scope.tabCompleteIndex = 0; - } - - // loop in the right direction - if (event.shiftKey) { - scope.tabCompleteIndex--; - if (scope.tabCompleteIndex < 0) { - // wrap to the last search match, and fix up to a real - // index value after we've matched - scope.tabCompleteIndex = Number.MAX_VALUE; - } - } - else { - scope.tabCompleteIndex++; - } - - - var searchIndex = 0; - var targetIndex = scope.tabCompleteIndex; - var text = scope.tabCompleteOriginal; - - // console.log("targetIndex: " + targetIndex + ", - // text=" + text); - - // FIXME: use the correct regexp to recognise userIDs --M - // - // XXX: I don't really know what the point of this is. You - // WANT to match freeform text given you want to match display - // names AND user IDs. Surely you just want to get the last - // word out of the input text and that's that? - // Am I missing something here? -- Kegan - // - // You're not missing anything - my point was that we should - // explicitly define the syntax for user IDs /somewhere/. - // Meanwhile as long as the delimeters are well defined, we - // could just pick "the last word". But to know what the - // correct delimeters are, we probably do need a formal - // syntax for user IDs to refer to... --Matthew - - var search = /@?([a-zA-Z0-9_\-:\.]+)$/.exec(text); - - if (targetIndex === 0) { // 0 is always the original text - element[0].value = text; - // Force angular to wake up and update the input ng-model - // by firing up input event - angular.element(element[0]).triggerHandler('input'); - } - else if (search && search[1]) { - // console.log("search found: " + search+" from "+text); - var expansion; - - // FIXME: could do better than linear search here - angular.forEach(scope.room.now.members, function(item, name) { - if (item.event.content.displayname && searchIndex < targetIndex) { - if (item.event.content.displayname.toLowerCase().indexOf(search[1].toLowerCase()) === 0) { - expansion = item.event.content.displayname; - searchIndex++; - } - } - }); - if (searchIndex < targetIndex) { // then search raw mxids - angular.forEach(scope.room.now.members, function(item, name) { - if (searchIndex < targetIndex) { - // === 1 because mxids are @username - if (name.toLowerCase().indexOf(search[1].toLowerCase()) === 1) { - expansion = name; - searchIndex++; - } - } - }); - } - - if (searchIndex === targetIndex || - targetIndex === Number.MAX_VALUE) { - // xchat-style tab complete, add a colon if tab - // completing at the start of the text - if (search[0].length === text.length) - expansion += ": "; - else - expansion += " "; - element[0].value = text.replace(/@?([a-zA-Z0-9_\-:\.]+)$/, expansion); - // cancel blink - element[0].className = ""; - if (targetIndex === Number.MAX_VALUE) { - // wrap the index around to the last index found - scope.tabCompleteIndex = searchIndex; - targetIndex = searchIndex; - } - } - else { - // console.log("wrapped!"); - element[0].className = "blink"; // XXX: slightly naughty to bypass angular - $timeout(function() { - element[0].className = ""; - }, 150); - element[0].value = text; - scope.tabCompleteIndex = 0; - } - - // Force angular to wak up and update the input ng-model by - // firing up input event - angular.element(element[0]).triggerHandler('input'); - } - else { - scope.tabCompleteIndex = 0; - } - // prevent the default TAB operation (typically focus shifting) - event.preventDefault(); - } - else if (keypressCode !== SHIFT && scope.tabCompleting) { - scope.tabCompleting = false; - scope.tabCompleteIndex = 0; - } - }); - }; -}]) -// A directive which stores text sent into it and restores it via up/down arrows -.directive('commandHistory', [ function() { - var BROADCAST_NEW_HISTORY_ITEM = "commandHistory:BROADCAST_NEW_HISTORY_ITEM(item)"; - - // Manage history of typed messages - // History is saved in sessionStorage so that it survives when the user - // navigates through the rooms and when it refreshes the page - var history = { - // The list of typed messages. Index 0 is the more recents - data: [], - - // The position in the history currently displayed - position: -1, - - element: undefined, - roomId: undefined, - - // The message the user has started to type before going into the history - typingMessage: undefined, - - // Init/load data for the current room - init: function(element, roomId) { - this.roomId = roomId; - this.element = element; - var data = sessionStorage.getItem("history_" + this.roomId); - if (data) { - this.data = JSON.parse(data); - } - }, - - // Store a message in the history - push: function(message) { - this.data.unshift(message); - - // Update the session storage - sessionStorage.setItem("history_" + this.roomId, JSON.stringify(this.data)); - - // Reset history position - this.position = -1; - this.typingMessage = undefined; - }, - - // Move in the history - go: function(offset) { - - if (-1 === this.position) { - // User starts to go to into the history, save the current line - this.typingMessage = this.element.val(); - } - else { - // If the user modified this line in history, keep the change - this.data[this.position] = this.element.val(); - } - - // Bounds the new position to valid data - var newPosition = this.position + offset; - newPosition = Math.max(-1, newPosition); - newPosition = Math.min(newPosition, this.data.length - 1); - this.position = newPosition; - - if (-1 !== this.position) { - // Show the message from the history - this.element.val(this.data[this.position]); - } - else if (undefined !== this.typingMessage) { - // Go back to the message the user started to type - this.element.val(this.typingMessage); - } - } - }; - - return { - restrict: "AE", - scope: { - roomId: "=commandHistory" - }, - link: function (scope, element, attrs) { - element.bind("keydown", function (event) { - var keycodePressed = event.which; - var UP_ARROW = 38; - var DOWN_ARROW = 40; - if (scope.roomId) { - if (keycodePressed === UP_ARROW) { - history.go(1); - event.preventDefault(); - } - else if (keycodePressed === DOWN_ARROW) { - history.go(-1); - event.preventDefault(); - } - } - }); - - scope.$on(BROADCAST_NEW_HISTORY_ITEM, function(ngEvent, item) { - history.push(item); - }); - - history.init(element, scope.roomId); - }, - - } -}]) - -// A directive to anchor the scroller position at the bottom when the browser is resizing. -// When the screen resizes, the bottom of the element remains the same, not the top. -.directive('keepScroll', ['$window', function($window) { - return { - link: function(scope, elem, attrs) { - - scope.windowHeight = $window.innerHeight; - - // Listen to window size change - angular.element($window).bind('resize', function() { - - // If the scroller is scrolled to the bottom, there is nothing to do. - // The browser will move it as expected - if (elem.scrollTop() + elem.height() !== elem[0].scrollHeight) { - // Else, move the scroller position according to the window height change delta - var windowHeightDelta = $window.innerHeight - scope.windowHeight; - elem.scrollTop(elem.scrollTop() - windowHeightDelta); - } - - // Store the new window height for the next screen size change - scope.windowHeight = $window.innerHeight; - }); - } - }; -}]); - diff --git a/syweb/webclient/room/room.html b/syweb/webclient/room/room.html deleted file mode 100644 index 8baf9eda71..0000000000 --- a/syweb/webclient/room/room.html +++ /dev/null @@ -1,282 +0,0 @@ -<div ng-controller="RoomController" data-ng-init="onInit()" class="room" style="height: 100%;"> - - <script type="text/ng-template" id="eventInfoTemplate.html"> - <div class="modal-body"> - <pre> {{event_selected | json}} </pre> - </div> - <div class="modal-footer"> - <button ng-click="redact()" type="button" class="btn btn-danger redact-button" - ng-disabled="!room.current_room_state.state('m.room.ops_levels').content.redact_level || !pow(room_id, state.user_id) || pow(room_id, state.user_id) < room.current_room_state.state('m.room.ops_levels').content.redact_level" - title="Delete this event on all home servers. This cannot be undone."> - Redact - </button> - - <button ng-click="dismiss()" type="button" class="btn"> - Close - </button> - </div> - </script> - - <script type="text/ng-template" id="roomInfoTemplate.html"> - <div class="modal-body"> - <span> - Invite a user: - <input ng-model="userIDToInvite" size="32" type="text" ng-enter="inviteUser()" ng-disabled="state.permission_denied" placeholder="User ID (ex:@user:homeserver)"/> - <button ng-click="inviteUser()" ng-disabled="state.permission_denied">Invite</button> - </span> - <br/> - <br/> - <button ng-click="leaveRoom()" ng-disabled="state.permission_denied">Leave Room</button> - </br/> - <table class="room-info"> - <tr ng-repeat="(key, event) in roomInfo.stateEvents" class="room-info-event"> - <td class="room-info-event-meta" width="30%"> - <span class="monospace">{{ event.type }}</span> - <span ng-show="event.state_key" class="monospace"> ({{event.state_key}})</span> - <br/> - {{ (event.origin_server_ts) | date:'MMM d HH:mm' }} - <br/> - Set by: <span class="monospace">{{ event.user_id }}</span> - <br/> - <span ng-show="event.required_power_level >= 0">Required power level: {{event.required_power_level}}<br/></span> - <button ng-click="submit(event)" type="button" class="btn btn-success" ng-disabled="!event.content"> - Submit - </button> - </td> - <td class="room-info-event-content" width="70%"> - <textarea class="room-info-textarea-content" msd-elastic ng-model="event.content" asjson></textarea> - </td> - </tr> - <tr> - <td class="room-info-event-meta" width="30%"> - <input ng-model="roomInfo.newEvent.type" placeholder="your.event.type" /> - <br/> - <button ng-click="submit(roomInfo.newEvent)" type="button" class="btn btn-success" ng-disabled="!roomInfo.newEvent.content || !roomInfo.newEvent.type"> - Submit - </button> - </td> - <td class="room-info-event-content" width="70%"> - <textarea class="room-info-textarea-content" msd-elastic ng-model="roomInfo.newEvent.content" asjson></textarea> - </td> - </tr> - </table> - </div> - <div class="modal-footer"> - <button ng-click="dismiss()" type="button" class="btn"> - Close - </button> - </div> - </script> - - <div id="roomHeader"> - <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a> - - <div id="controlButtons"> - <button ng-click="startVoiceCall()" class="controlButton" - style="background: url('img/voice.png')" - ng-show="(currentCall == undefined || currentCall.state == 'ended')" - ng-disabled="state.permission_denied" - > - </button> - <button ng-click="startVideoCall()" class="controlButton" - style="background: url('img/video.png')" - ng-show="(currentCall == undefined || currentCall.state == 'ended')" - ng-disabled="state.permission_denied" - > - </button> - <button ng-click="openRoomInfo()" class="controlButton" - style="background: url('img/settings.png')" - > - </button> - </div> - - <div class="roomHeaderInfo"> - - <div class="roomNameSection"> - <div ng-hide="name.isEditing" ng-dblclick="name.editName()" id="roomName"> - {{ room_id | mRoomName }} - </div> - <form ng-submit="name.updateName()" ng-show="name.isEditing" class="roomNameForm"> - <input ng-model="name.newNameText" ng-blur="name.cancelEdit()" class="roomNameInput" placeholder="Room name"/> - </form> - </div> - - <div class="roomTopicSection"> - <button ng-hide="room.current_room_state.state_events['m.room.topic'].content.topic || topic.isEditing" - ng-click="topic.editTopic()" class="roomTopicSetNew"> - Set Topic - </button> - <div ng-show="room.current_room_state.state_events['m.room.topic'].content.topic || topic.isEditing"> - <div ng-hide="topic.isEditing" ng-dblclick="topic.editTopic()" id="roomTopic" - ng-bind-html="room.current_room_state.state_events['m.room.topic'].content.topic | limitTo: 200 | linky:'_blank'"> - </div> - <form ng-submit="topic.updateTopic()" ng-show="topic.isEditing" class="roomTopicForm"> - <input ng-model="topic.newTopicText" ng-blur="topic.cancelEdit()" class="roomTopicInput" placeholder="Topic"/> - </form> - </div> - </div> - </div> - </div> - - <div id="roomPage"> - <div id="roomWrapper"> - - <div id="roomRecentsTableWrapper"> - <div ng-include="'recents/recents.html'"></div> - </div> - - <div id="usersTableWrapper" ng-hide="state.permission_denied"> - <div ng-repeat="member in room.now.members | orderMembersList" class="userAvatar"> - <div class="userAvatarFrame" ng-class="(member.user.event.content.presence === 'online' ? 'online' : (member.user.event.content.presence === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.event.content.membership == 'invite' ? 'invited' : '')"> - <img class="userAvatarImage mouse-pointer" - ng-click="$parent.goToUserPage(member.id)" - ng-src="{{member.user.event.content.avatar_url || 'img/default-profile.png'}}" - alt="{{ member.user.event.content.displayname || member.id.substr(0, member.id.indexOf(':')) }}" - title="{{ member.id }} - power: {{ member.power_level }}" - width="80" height="80"/> - <!-- <div class="userPowerLevel" ng-style="{'width': member.powerLevelNorm +'%'}"></div> --> - </div> - <div class="userName"> - <pie-chart ng-show="member.power_level_norm" data="[ (member.power_level_norm + 0), (100 - member.power_level_norm) ]"></pie-chart> - {{ member.id | mUserDisplayName:room_id:true }} - <span ng-show="member.user.event.content.last_active_ago" style="color: #aaa">({{ member.user.event.content.last_active_ago + (now - member.user.last_updated) | duration }})</span> - </div> - </div> - </div> - - <div id="messageTableWrapper" - ng-hide="state.permission_denied" - ng-style="{ 'visibility': state.messages_visibility }" - keep-scroll> - <table id="messageTable" infinite-scroll="paginateMore()"> - <tr ng-repeat="msg in room.events" - ng-class="(room.events[$index - 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item> - <td class="leftBlock" ng-mouseover="state.showTs = 1" ng-mouseout="state.showTs = 0"> - <div class="timestamp" - ng-style="{ 'opacity': state.showTs ? 1.0 : 0.0 }" - ng-class="msg.echo_msg_state"> - {{ (msg.origin_server_ts) | date:'MMM d HH:mm' }} - </div> - <div class="sender" ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"> {{ msg.__room_member.cnt.displayname || msg.user_id | mUserDisplayName:room_id:true }}</div> - </td> - <td class="avatar"> - <!-- room.now.members[msg.user_id].user.event.content.avatar_url is just backwards compat, and can be removed in the future. Synapse didn't used to - send m.room.member updates when avatar urls changed, so the image which should be visible here just going off room state isn't visible. We fix - this by reading off the m.presence url --> - <img class="avatarImage" ng-src="{{ msg.__room_member.cnt.avatar_url || msg.__room_member.content.avatar_url || room.now.members[msg.user_id].user.event.content.avatar_url || 'img/default-profile.png' }}" width="32" height="32" title="{{msg.user_id}}" - ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/> - </td> - <td class="msg" ng-class="(!msg.content.membership && ('m.room.topic' !== msg.type && 'm.room.name' !== msg.type))? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'"> - <div class="bubble" ng-dblclick="openJson(msg)"> - <span ng-if="'join' === msg.content.membership && msg.changedKey === 'membership'"> - {{ msg.content.displayname || room.now.members[msg.state_key].user.event.content.displayname || msg.state_key }} joined - </span> - <span ng-if="'leave' === msg.content.membership && msg.changedKey === 'membership'"> - <span ng-if="msg.user_id === msg.state_key"> - <!-- FIXME: This seems like a synapse bug that the 'leave' content doesn't give the displayname... --> - {{ msg.__room_member.cnt.displayname || room.now.members[msg.state_key].user.event.content.displayname || msg.state_key }} left - </span> - <span ng-if="msg.user_id !== msg.state_key && msg.prev_content"> - {{ msg.content.displayname || room.now.members[msg.user_id].user.event.content.displayname || msg.user_id }} - {{ {"invite": "kicked", "join": "kicked", "ban": "unbanned"}[msg.prev_content.membership] }} - {{ msg.__target_room_member.content.displayname || msg.state_key }} - <span ng-if="'join' === msg.prev_content.membership && msg.content.reason"> - : {{ msg.content.reason }} - </span> - </span> - </span> - <span ng-if="'invite' === msg.content.membership && msg.changedKey === 'membership' || - 'ban' === msg.content.membership && msg.changedKey === 'membership'"> - {{ msg.__room_member.cnt.displayname || msg.user_id }} - {{ {"invite": "invited", "ban": "banned"}[msg.content.membership] }} - {{ msg.__target_room_member.cnt.displayname || msg.state_key }} - <span ng-if="msg.prev_content && 'ban' === msg.prev_content.membership && msg.content.reason"> - : {{ msg.content.reason }} - </span> - </span> - <span ng-if="msg.changedKey === 'displayname'"> - {{ msg.user_id }} changed their display name from {{ msg.prev_content.displayname }} to {{ msg.content.displayname }} - </span> - - <span ng-show='msg.type === "m.room.message"' ng-switch='msg.content.msgtype'> - <span ng-switch-when="m.emote" - ng-class="containsBingWord(msg) && msg.user_id != state.user_id ? msg.echo_msg_state + ' messageBing' : msg.echo_msg_state" - ng-bind-html="'* ' + (msg.__room_member.cnt.displayname || msg.user_id) + ' ' + msg.content.body | linky:'_blank'" - /> - - <span ng-switch-when="m.text" - class="message" - ng-class="containsBingWord(msg) && msg.user_id != state.user_id ? msg.echo_msg_state + ' messageBing' : msg.echo_msg_state" - ng-bind-html="(msg.content.format === 'org.matrix.custom.html') ? (msg.content.formatted_body | unsanitizedLinky) : (msg.content.body | linky:'_blank') "/> - - <div ng-switch-when="m.image"> - <div ng-hide='msg.content.thumbnail_url' ng-style="msg.content.body.h && { 'height' : (msg.content.body.h < 320) ? msg.content.body.h : 320}"> - <img class="image" ng-src="{{ msg.content.url }}"/> - </div> - <div ng-show='msg.content.thumbnail_url' ng-style="{ 'height' : msg.content.thumbnail_info.h }"> - <img class="image mouse-pointer" ng-src="{{ msg.content.thumbnail_url }}" - ng-click="$parent.$parent.fullScreenImageURL = msg.content.url; $event.stopPropagation();"/> - </div> - </div> - - <span ng-switch-when="m.file" ng-class="msg.echo_msg_state"> - <a href="{{ msg.content.url}}" target="_blank">{{ msg.content.body }}</a> - <div ng-show='msg.content.thumbnail_url' ng-style="{ 'height' : msg.content.thumbnail_info.h }"> - <a href="{{ msg.content.url}}" target="_blank"> - <img class="image mouse-pointer" ng-src="{{ msg.content.thumbnail_url }}"/> - </a> - </div> - </span> - - <span ng-switch-default - ng-class="containsBingWord(msg) && msg.user_id != state.user_id ? msg.echo_msg_state + ' messageBing' : msg.echo_msg_state" - ng-bind-html="msg.content.body | linky:'_blank'" - /> - </span> - - <span ng-show='msg.type === "m.call.invite" && msg.user_id == state.user_id'>Outgoing Call{{ isWebRTCSupported() ? '' : ' (But your browser does not support VoIP)' }}</span> - <span ng-show='msg.type === "m.call.invite" && msg.user_id != state.user_id'>Incoming Call{{ isWebRTCSupported() ? '' : ' (But your browser does not support VoIP)' }}</span> - - <span ng-if="'m.room.topic' === msg.type"> - {{ msg.__room_member.cnt.displayname || msg.user_id }} changed the topic to: {{ msg.content.topic }} - </span> - - <span ng-if="'m.room.name' === msg.type"> - {{ msg.__room_member.cnt.displayname || msg.user_id }} changed the room name to: {{ msg.content.name }} - </span> - - </div> - </td> - <td class="rightBlock"> - <img class="avatarImage" ng-src="{{ room.now.members[msg.user_id].user.event.content.avatar_url || 'img/default-profile.png' }}" width="32" height="32" - ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/> - </td> - </tr> - </table> - </div> - - <div ng-show="state.permission_denied"> - {{ state.permission_denied }} - </div> - - </div> - </div> - - <div id="controlPanel"> - <div id="controls"> - <button id="attachButton" m-file-input="fileToSend" class="extraControls" ng-disabled="state.permission_denied"></button> - <textarea id="mainInput" rows="1" ng-enter="send()" - ng-disabled="state.permission_denied" - ng-focus="true" autocomplete="off" tab-complete command-history="room_id"/> - {{ feedback }} - <div ng-show="state.stream_failure"> - {{ state.stream_failure.data.error || "Connection failure" }} - </div> - </div> - </div> - - <div id="room-fullscreen-image" ng-show="fullScreenImageURL" ng-click="fullScreenImageURL = undefined;"> - <img ng-src="{{ fullScreenImageURL }}"/> - </div> - - </div> |