forked from MirrorHub/synapse
Merge branch 'develop' of github.com:matrix-org/synapse into federation_authorization
This commit is contained in:
commit
d9a9e9eb30
8 changed files with 356 additions and 23 deletions
|
@ -40,4 +40,45 @@ angular.module('matrixWebClient')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}]);
|
}])
|
||||||
|
.directive('asjson', function() {
|
||||||
|
return {
|
||||||
|
restrict: 'A',
|
||||||
|
require: 'ngModel',
|
||||||
|
link: function (scope, element, attrs, ngModelCtrl) {
|
||||||
|
function isValidJson(model) {
|
||||||
|
var flag = true;
|
||||||
|
try {
|
||||||
|
angular.fromJson(model);
|
||||||
|
} catch (err) {
|
||||||
|
flag = false;
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
};
|
||||||
|
|
||||||
|
function string2JSON(text) {
|
||||||
|
try {
|
||||||
|
var j = angular.fromJson(text);
|
||||||
|
ngModelCtrl.$setValidity('json', true);
|
||||||
|
return j;
|
||||||
|
} catch (err) {
|
||||||
|
//returning undefined results in a parser error as of angular-1.3-rc.0, and will not go through $validators
|
||||||
|
//return undefined
|
||||||
|
ngModelCtrl.$setValidity('json', false);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function JSON2String(object) {
|
||||||
|
return angular.toJson(object, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
//$validators is an object, where key is the error
|
||||||
|
//ngModelCtrl.$validators.json = isValidJson;
|
||||||
|
|
||||||
|
//array pipelines
|
||||||
|
ngModelCtrl.$parsers.push(string2JSON);
|
||||||
|
ngModelCtrl.$formatters.push(JSON2String);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -418,6 +418,37 @@ textarea, input {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** Room Info Dialog ***/
|
||||||
|
|
||||||
|
.room-info {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-info-event {
|
||||||
|
border-bottom: 1pt solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-info-event-meta {
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-info-event-content {
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monospace {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-info-textarea-content {
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
/*** Participant list ***/
|
/*** Participant list ***/
|
||||||
|
|
||||||
#usersTableWrapper {
|
#usersTableWrapper {
|
||||||
|
|
|
@ -31,7 +31,8 @@ var matrixWebClient = angular.module('matrixWebClient', [
|
||||||
'eventStreamService',
|
'eventStreamService',
|
||||||
'eventHandlerService',
|
'eventHandlerService',
|
||||||
'infinite-scroll',
|
'infinite-scroll',
|
||||||
'ui.bootstrap'
|
'ui.bootstrap',
|
||||||
|
'monospaced.elastic'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
|
matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
|
||||||
|
|
|
@ -564,6 +564,13 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
|
||||||
handleRedaction(event, isLiveEvent);
|
handleRedaction(event, isLiveEvent);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
// if it is a state event, then just add it in so it
|
||||||
|
// displays on the Room Info screen.
|
||||||
|
if (typeof(event.state_key) === "string") { // incls. 0-len strings
|
||||||
|
if (event.room_id) {
|
||||||
|
handleRoomDateEvent(event, isLiveEvent, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
console.log("Unable to handle event type " + event.type);
|
console.log("Unable to handle event type " + event.type);
|
||||||
console.log(JSON.stringify(event, undefined, 4));
|
console.log(JSON.stringify(event, undefined, 4));
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
<script type='text/javascript' src="js/ui-bootstrap-tpls-0.11.2.js"></script>
|
<script type='text/javascript' src="js/ui-bootstrap-tpls-0.11.2.js"></script>
|
||||||
<script type='text/javascript' src='js/ng-infinite-scroll-matrix.js'></script>
|
<script type='text/javascript' src='js/ng-infinite-scroll-matrix.js'></script>
|
||||||
<script type='text/javascript' src='js/autofill-event.js'></script>
|
<script type='text/javascript' src='js/autofill-event.js'></script>
|
||||||
|
<script type='text/javascript' src='js/elastic.js'></script>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
<script src="config.js"></script>
|
<script src="config.js"></script>
|
||||||
<script src="app-controller.js"></script>
|
<script src="app-controller.js"></script>
|
||||||
|
|
216
webclient/js/elastic.js
Normal file
216
webclient/js/elastic.js
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
/*
|
||||||
|
* angular-elastic v2.4.0
|
||||||
|
* (c) 2014 Monospaced http://monospaced.com
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
angular.module('monospaced.elastic', [])
|
||||||
|
|
||||||
|
.constant('msdElasticConfig', {
|
||||||
|
append: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
.directive('msdElastic', [
|
||||||
|
'$timeout', '$window', 'msdElasticConfig',
|
||||||
|
function($timeout, $window, config) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
return {
|
||||||
|
require: 'ngModel',
|
||||||
|
restrict: 'A, C',
|
||||||
|
link: function(scope, element, attrs, ngModel) {
|
||||||
|
|
||||||
|
// cache a reference to the DOM element
|
||||||
|
var ta = element[0],
|
||||||
|
$ta = element;
|
||||||
|
|
||||||
|
// ensure the element is a textarea, and browser is capable
|
||||||
|
if (ta.nodeName !== 'TEXTAREA' || !$window.getComputedStyle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set these properties before measuring dimensions
|
||||||
|
$ta.css({
|
||||||
|
'overflow': 'hidden',
|
||||||
|
'overflow-y': 'hidden',
|
||||||
|
'word-wrap': 'break-word'
|
||||||
|
});
|
||||||
|
|
||||||
|
// force text reflow
|
||||||
|
var text = ta.value;
|
||||||
|
ta.value = '';
|
||||||
|
ta.value = text;
|
||||||
|
|
||||||
|
var append = attrs.msdElastic ? attrs.msdElastic.replace(/\\n/g, '\n') : config.append,
|
||||||
|
$win = angular.element($window),
|
||||||
|
mirrorInitStyle = 'position: absolute; top: -999px; right: auto; bottom: auto;' +
|
||||||
|
'left: 0; overflow: hidden; -webkit-box-sizing: content-box;' +
|
||||||
|
'-moz-box-sizing: content-box; box-sizing: content-box;' +
|
||||||
|
'min-height: 0 !important; height: 0 !important; padding: 0;' +
|
||||||
|
'word-wrap: break-word; border: 0;',
|
||||||
|
$mirror = angular.element('<textarea tabindex="-1" ' +
|
||||||
|
'style="' + mirrorInitStyle + '"/>').data('elastic', true),
|
||||||
|
mirror = $mirror[0],
|
||||||
|
taStyle = getComputedStyle(ta),
|
||||||
|
resize = taStyle.getPropertyValue('resize'),
|
||||||
|
borderBox = taStyle.getPropertyValue('box-sizing') === 'border-box' ||
|
||||||
|
taStyle.getPropertyValue('-moz-box-sizing') === 'border-box' ||
|
||||||
|
taStyle.getPropertyValue('-webkit-box-sizing') === 'border-box',
|
||||||
|
boxOuter = !borderBox ? {width: 0, height: 0} : {
|
||||||
|
width: parseInt(taStyle.getPropertyValue('border-right-width'), 10) +
|
||||||
|
parseInt(taStyle.getPropertyValue('padding-right'), 10) +
|
||||||
|
parseInt(taStyle.getPropertyValue('padding-left'), 10) +
|
||||||
|
parseInt(taStyle.getPropertyValue('border-left-width'), 10),
|
||||||
|
height: parseInt(taStyle.getPropertyValue('border-top-width'), 10) +
|
||||||
|
parseInt(taStyle.getPropertyValue('padding-top'), 10) +
|
||||||
|
parseInt(taStyle.getPropertyValue('padding-bottom'), 10) +
|
||||||
|
parseInt(taStyle.getPropertyValue('border-bottom-width'), 10)
|
||||||
|
},
|
||||||
|
minHeightValue = parseInt(taStyle.getPropertyValue('min-height'), 10),
|
||||||
|
heightValue = parseInt(taStyle.getPropertyValue('height'), 10),
|
||||||
|
minHeight = Math.max(minHeightValue, heightValue) - boxOuter.height,
|
||||||
|
maxHeight = parseInt(taStyle.getPropertyValue('max-height'), 10),
|
||||||
|
mirrored,
|
||||||
|
active,
|
||||||
|
copyStyle = ['font-family',
|
||||||
|
'font-size',
|
||||||
|
'font-weight',
|
||||||
|
'font-style',
|
||||||
|
'letter-spacing',
|
||||||
|
'line-height',
|
||||||
|
'text-transform',
|
||||||
|
'word-spacing',
|
||||||
|
'text-indent'];
|
||||||
|
|
||||||
|
// exit if elastic already applied (or is the mirror element)
|
||||||
|
if ($ta.data('elastic')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opera returns max-height of -1 if not set
|
||||||
|
maxHeight = maxHeight && maxHeight > 0 ? maxHeight : 9e4;
|
||||||
|
|
||||||
|
// append mirror to the DOM
|
||||||
|
if (mirror.parentNode !== document.body) {
|
||||||
|
angular.element(document.body).append(mirror);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set resize and apply elastic
|
||||||
|
$ta.css({
|
||||||
|
'resize': (resize === 'none' || resize === 'vertical') ? 'none' : 'horizontal'
|
||||||
|
}).data('elastic', true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
function initMirror() {
|
||||||
|
var mirrorStyle = mirrorInitStyle;
|
||||||
|
|
||||||
|
mirrored = ta;
|
||||||
|
// copy the essential styles from the textarea to the mirror
|
||||||
|
taStyle = getComputedStyle(ta);
|
||||||
|
angular.forEach(copyStyle, function(val) {
|
||||||
|
mirrorStyle += val + ':' + taStyle.getPropertyValue(val) + ';';
|
||||||
|
});
|
||||||
|
mirror.setAttribute('style', mirrorStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjust() {
|
||||||
|
var taHeight,
|
||||||
|
taComputedStyleWidth,
|
||||||
|
mirrorHeight,
|
||||||
|
width,
|
||||||
|
overflow;
|
||||||
|
|
||||||
|
if (mirrored !== ta) {
|
||||||
|
initMirror();
|
||||||
|
}
|
||||||
|
|
||||||
|
// active flag prevents actions in function from calling adjust again
|
||||||
|
if (!active) {
|
||||||
|
active = true;
|
||||||
|
|
||||||
|
mirror.value = ta.value + append; // optional whitespace to improve animation
|
||||||
|
mirror.style.overflowY = ta.style.overflowY;
|
||||||
|
|
||||||
|
taHeight = ta.style.height === '' ? 'auto' : parseInt(ta.style.height, 10);
|
||||||
|
|
||||||
|
taComputedStyleWidth = getComputedStyle(ta).getPropertyValue('width');
|
||||||
|
|
||||||
|
// ensure getComputedStyle has returned a readable 'used value' pixel width
|
||||||
|
if (taComputedStyleWidth.substr(taComputedStyleWidth.length - 2, 2) === 'px') {
|
||||||
|
// update mirror width in case the textarea width has changed
|
||||||
|
width = parseInt(taComputedStyleWidth, 10) - boxOuter.width;
|
||||||
|
mirror.style.width = width + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
mirrorHeight = mirror.scrollHeight;
|
||||||
|
|
||||||
|
if (mirrorHeight > maxHeight) {
|
||||||
|
mirrorHeight = maxHeight;
|
||||||
|
overflow = 'scroll';
|
||||||
|
} else if (mirrorHeight < minHeight) {
|
||||||
|
mirrorHeight = minHeight;
|
||||||
|
}
|
||||||
|
mirrorHeight += boxOuter.height;
|
||||||
|
|
||||||
|
ta.style.overflowY = overflow || 'hidden';
|
||||||
|
|
||||||
|
if (taHeight !== mirrorHeight) {
|
||||||
|
ta.style.height = mirrorHeight + 'px';
|
||||||
|
scope.$emit('elastic:resize', $ta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// small delay to prevent an infinite loop
|
||||||
|
$timeout(function() {
|
||||||
|
active = false;
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function forceAdjust() {
|
||||||
|
active = false;
|
||||||
|
adjust();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* initialise
|
||||||
|
*/
|
||||||
|
|
||||||
|
// listen
|
||||||
|
if ('onpropertychange' in ta && 'oninput' in ta) {
|
||||||
|
// IE9
|
||||||
|
ta['oninput'] = ta.onkeyup = adjust;
|
||||||
|
} else {
|
||||||
|
ta['oninput'] = adjust;
|
||||||
|
}
|
||||||
|
|
||||||
|
$win.bind('resize', forceAdjust);
|
||||||
|
|
||||||
|
scope.$watch(function() {
|
||||||
|
return ngModel.$modelValue;
|
||||||
|
}, function(newValue) {
|
||||||
|
forceAdjust();
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.$on('elastic:adjust', function() {
|
||||||
|
initMirror();
|
||||||
|
forceAdjust();
|
||||||
|
});
|
||||||
|
|
||||||
|
$timeout(adjust);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* destroy
|
||||||
|
*/
|
||||||
|
|
||||||
|
scope.$on('$destroy', function() {
|
||||||
|
$mirror.remove();
|
||||||
|
$win.unbind('resize', forceAdjust);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]);
|
|
@ -1018,6 +1018,20 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.openRoomInfo = function() {
|
$scope.openRoomInfo = function() {
|
||||||
|
$scope.roomInfo = {};
|
||||||
|
$scope.roomInfo.newEvent = {
|
||||||
|
content: {},
|
||||||
|
type: "",
|
||||||
|
state_key: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
var stateFilter = $filter("stateEventsFilter");
|
||||||
|
var stateEvents = stateFilter($scope.events.rooms[$scope.room_id]);
|
||||||
|
// 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({
|
var modalInstance = $modal.open({
|
||||||
templateUrl: 'roomInfoTemplate.html',
|
templateUrl: 'roomInfoTemplate.html',
|
||||||
controller: 'RoomInfoController',
|
controller: 'RoomInfoController',
|
||||||
|
@ -1036,12 +1050,21 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
|
||||||
$modalInstance.close("redact");
|
$modalInstance.close("redact");
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.controller('RoomInfoController', function($scope, $modalInstance, $filter) {
|
.controller('RoomInfoController', function($scope, $modalInstance, $filter, matrixService) {
|
||||||
console.log("Displaying room info.");
|
console.log("Displaying room info.");
|
||||||
|
|
||||||
$scope.submitState = function(eventType, content) {
|
$scope.submit = function(event) {
|
||||||
console.log("Submitting " + eventType + " with " + content);
|
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;
|
$scope.dismiss = $modalInstance.dismiss;
|
||||||
|
|
||||||
|
|
|
@ -15,21 +15,34 @@
|
||||||
|
|
||||||
<script type="text/ng-template" id="roomInfoTemplate.html">
|
<script type="text/ng-template" id="roomInfoTemplate.html">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<table id="roomInfoTable">
|
<table class="room-info">
|
||||||
<tr>
|
<tr ng-repeat="(key, event) in roomInfo.stateEvents" class="room-info-event">
|
||||||
<th>
|
<td class="room-info-event-meta" width="30%">
|
||||||
Event Type
|
<span class="monospace">{{ key }}</span>
|
||||||
</th>
|
<br/>
|
||||||
<th>
|
{{ (event.origin_server_ts) | date:'MMM d HH:mm' }}
|
||||||
Content
|
<br/>
|
||||||
</th>
|
Set by: <span class="monospace">{{ event.user_id }}</span>
|
||||||
</tr>
|
<br/>
|
||||||
<tr ng-repeat="(key, event) in events.rooms[room_id] | stateEventsFilter">
|
<span ng-show="event.required_power_level >= 0">Required power level: {{event.required_power_level}}<br/></span>
|
||||||
<td>
|
<button ng-click="submit(event)" type="button" class="btn btn-success" ng-disabled="!event.content">
|
||||||
<pre>{{ key }}</pre>
|
Submit
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="room-info-event-content" width="70%">
|
||||||
<pre>{{ event.content | json }}</pre>
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
Loading…
Reference in a new issue