Merge remote-tracking branch 'origin/develop' into luke/feature-flair
This commit is contained in:
commit
5087da9247
73 changed files with 3335 additions and 3724 deletions
|
@ -72,8 +72,17 @@ export default React.createClass({
|
|||
|
||||
// Returns true if props.url is a scalar URL, typically https://scalar.vector.im/api
|
||||
isScalarUrl: function() {
|
||||
const scalarUrl = SdkConfig.get().integrations_rest_url;
|
||||
return scalarUrl && this.props.url.startsWith(scalarUrl);
|
||||
let scalarUrls = SdkConfig.get().integrations_widgets_urls;
|
||||
if (!scalarUrls || scalarUrls.length == 0) {
|
||||
scalarUrls = [SdkConfig.get().integrations_rest_url];
|
||||
}
|
||||
|
||||
for (let i = 0; i < scalarUrls.length; i++) {
|
||||
if (this.props.url.startsWith(scalarUrls[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
isMixedContent: function() {
|
||||
|
@ -127,7 +136,8 @@ export default React.createClass({
|
|||
_onEditClick: function(e) {
|
||||
console.log("Edit widget ID ", this.props.id);
|
||||
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
||||
const src = this._scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'type_' + this.props.type);
|
||||
const src = this._scalarClient.getScalarInterfaceUrlForRoom(
|
||||
this.props.room.roomId, 'type_' + this.props.type, this.props.id);
|
||||
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
|
||||
src: src,
|
||||
}, "mx_IntegrationsManager");
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,12 +16,19 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {emojifyText} from '../../../HtmlUtils';
|
||||
import {emojifyText, containsEmoji} from '../../../HtmlUtils';
|
||||
|
||||
export default function EmojiText(props) {
|
||||
const {element, children, ...restProps} = props;
|
||||
restProps.dangerouslySetInnerHTML = emojifyText(children);
|
||||
return React.createElement(element, restProps);
|
||||
|
||||
// fast path: simple regex to detect strings that don't contain
|
||||
// emoji and just return them
|
||||
if (containsEmoji(children)) {
|
||||
restProps.dangerouslySetInnerHTML = emojifyText(children);
|
||||
return React.createElement(element, restProps);
|
||||
} else {
|
||||
return React.createElement(element, restProps, children);
|
||||
}
|
||||
}
|
||||
|
||||
EmojiText.propTypes = {
|
||||
|
|
|
@ -75,7 +75,7 @@ export default class ManageIntegsButton extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
let integrationsButton;
|
||||
let integrationsButton = <div />;
|
||||
let integrationsError;
|
||||
if (this.scalarClient !== null) {
|
||||
if (this.state.showIntegrationsError && this.state.scalarError) {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { _t, _tJsx } from '../../../languageHandler';
|
||||
|
||||
var DIV_ID = 'mx_recaptcha';
|
||||
|
@ -66,11 +67,10 @@ module.exports = React.createClass({
|
|||
// * jumping straight to a hosted captcha page (but we don't support that yet)
|
||||
// * embedding the captcha in an iframe (if that works)
|
||||
// * using a better captcha lib
|
||||
warning.innerHTML = _tJsx(
|
||||
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
|
||||
/<a>(.*?)<\/a>/,
|
||||
(sub) => { return "<a href='https://riot.im/app'>{ sub }</a>"; }
|
||||
);
|
||||
ReactDOM.render(_tJsx(
|
||||
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
|
||||
/<a>(.*?)<\/a>/,
|
||||
(sub) => { return <a href='https://riot.im/app'>{ sub }</a>; }), warning);
|
||||
this.refs.recaptchaContainer.appendChild(warning);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -31,6 +31,7 @@ import dis from '../../../dispatcher';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import UserSettingsStore from "../../../UserSettingsStore";
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import ContextualMenu from '../../structures/ContextualMenu';
|
||||
import {RoomMember} from 'matrix-js-sdk';
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
@ -72,12 +73,16 @@ module.exports = React.createClass({
|
|||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
|
||||
let successful = false;
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
successful = document.execCommand('copy');
|
||||
} catch (err) {
|
||||
console.log('Unable to copy');
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
return successful;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -113,14 +118,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
}, 10);
|
||||
}
|
||||
// add event handlers to the 'copy code' buttons
|
||||
const buttons = ReactDOM.findDOMNode(this).getElementsByClassName("mx_EventTile_copyButton");
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
buttons[i].onclick = (e) => {
|
||||
const copyCode = buttons[i].parentNode.getElementsByTagName("code")[0];
|
||||
this.copyToClipboard(copyCode.textContent);
|
||||
};
|
||||
}
|
||||
this._addCodeCopyButton();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -257,6 +255,33 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_addCodeCopyButton() {
|
||||
// Add 'copy' buttons to pre blocks
|
||||
ReactDOM.findDOMNode(this).querySelectorAll('.mx_EventTile_body pre').forEach((p) => {
|
||||
const button = document.createElement("span");
|
||||
button.className = "mx_EventTile_copyButton";
|
||||
button.onclick = (e) => {
|
||||
const copyCode = button.parentNode.getElementsByTagName("code")[0];
|
||||
const successful = this.copyToClipboard(copyCode.textContent);
|
||||
|
||||
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
||||
const buttonRect = e.target.getBoundingClientRect();
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const x = buttonRect.right + window.pageXOffset;
|
||||
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
|
||||
const {close} = ContextualMenu.createMenu(GenericTextContextMenu, {
|
||||
chevronOffset: 10,
|
||||
left: x,
|
||||
top: y,
|
||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
||||
});
|
||||
e.target.onmouseout = close;
|
||||
};
|
||||
p.appendChild(button);
|
||||
});
|
||||
},
|
||||
|
||||
onCancelClick: function(event) {
|
||||
this.setState({ widgetHidden: true });
|
||||
// FIXME: persist this somewhere smarter than local storage
|
||||
|
|
|
@ -53,14 +53,14 @@ module.exports = React.createClass({
|
|||
this.scalarClient = null;
|
||||
if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) {
|
||||
this.scalarClient = new ScalarAuthClient();
|
||||
this.scalarClient.connect().done(() => {
|
||||
this.scalarClient.connect().then(() => {
|
||||
this.forceUpdate();
|
||||
// TODO -- Handle Scalar errors
|
||||
// },
|
||||
// (err) => {
|
||||
// this.setState({
|
||||
// scalar_error: err,
|
||||
// });
|
||||
}).catch((e) => {
|
||||
console.log("Failed to connect to integrations server");
|
||||
// TODO -- Handle Scalar errors
|
||||
// this.setState({
|
||||
// scalar_error: err,
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -143,7 +143,6 @@ export default class Autocomplete extends React.Component {
|
|||
return null;
|
||||
}
|
||||
this.setSelection(selectionOffset);
|
||||
return selectionOffset === COMPOSER_SELECTED ? null : this.state.completionList[selectionOffset - 1];
|
||||
}
|
||||
|
||||
// called from MessageComposerInput
|
||||
|
@ -155,7 +154,6 @@ export default class Autocomplete extends React.Component {
|
|||
return null;
|
||||
}
|
||||
this.setSelection(selectionOffset);
|
||||
return selectionOffset === COMPOSER_SELECTED ? null : this.state.completionList[selectionOffset - 1];
|
||||
}
|
||||
|
||||
onEscape(e): boolean {
|
||||
|
@ -201,6 +199,9 @@ export default class Autocomplete extends React.Component {
|
|||
|
||||
setSelection(selectionOffset: number) {
|
||||
this.setState({selectionOffset, hide: false});
|
||||
if (this.props.onSelectionChange) {
|
||||
this.props.onSelectionChange(this.state.completionList[selectionOffset - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
|
|
|
@ -139,7 +139,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={ appsDrawer ? "mx_RoomView_auxPanel mx_RoomView_auxPanel_apps" : "mx_RoomView_auxPanel" } style={{maxHeight: this.props.maxHeight}} >
|
||||
<div className="mx_RoomView_auxPanel" style={{maxHeight: this.props.maxHeight}} >
|
||||
{ appsDrawer }
|
||||
{ fileDropTarget }
|
||||
{ callView }
|
||||
|
|
|
@ -949,8 +949,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
};
|
||||
|
||||
moveAutocompleteSelection = (up) => {
|
||||
const completion = up ? this.autocomplete.onUpArrow() : this.autocomplete.onDownArrow();
|
||||
return this.setDisplayedCompletion(completion);
|
||||
up ? this.autocomplete.onUpArrow() : this.autocomplete.onDownArrow();
|
||||
};
|
||||
|
||||
onEscape = async (e) => {
|
||||
|
@ -1133,6 +1132,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
<Autocomplete
|
||||
ref={(e) => this.autocomplete = e}
|
||||
onConfirm={this.setDisplayedCompletion}
|
||||
onSelectionChange={this.setDisplayedCompletion}
|
||||
query={this.getAutocompleteQuery(content)}
|
||||
selection={selection}/>
|
||||
</div>
|
||||
|
|
|
@ -123,7 +123,19 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
var newElement = ReactDOM.findDOMNode(this);
|
||||
var startTopOffset = oldTop - newElement.offsetParent.getBoundingClientRect().top;
|
||||
let startTopOffset;
|
||||
if (!newElement.offsetParent) {
|
||||
// this seems to happen sometimes for reasons I don't understand
|
||||
// the docs for `offsetParent` say it may be null if `display` is
|
||||
// `none`, but I can't see why that would happen.
|
||||
console.warn(
|
||||
`ReadReceiptMarker for ${this.props.member.userId} in ` +
|
||||
`${this.props.member.roomId} has no offsetParent`,
|
||||
);
|
||||
startTopOffset = 0;
|
||||
} else {
|
||||
startTopOffset = oldTop - newElement.offsetParent.getBoundingClientRect().top;
|
||||
}
|
||||
|
||||
var startStyles = [];
|
||||
var enterTransitionOpts = [];
|
||||
|
@ -131,13 +143,12 @@ module.exports = React.createClass({
|
|||
if (oldInfo && oldInfo.left) {
|
||||
// start at the old height and in the old h pos
|
||||
|
||||
var leftOffset = oldInfo.left;
|
||||
startStyles.push({ top: startTopOffset+"px",
|
||||
left: oldInfo.left+"px" });
|
||||
|
||||
var reorderTransitionOpts = {
|
||||
duration: 100,
|
||||
easing: 'easeOut'
|
||||
easing: 'easeOut',
|
||||
};
|
||||
|
||||
enterTransitionOpts.push(reorderTransitionOpts);
|
||||
|
@ -175,7 +186,7 @@ module.exports = React.createClass({
|
|||
if (this.props.timestamp) {
|
||||
title = _t(
|
||||
"Seen by %(userName)s at %(dateTime)s",
|
||||
{userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp), this.props.showTwelveHour)}
|
||||
{userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp), this.props.showTwelveHour)},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,6 @@ module.exports = React.createClass({
|
|||
propTypes: {
|
||||
ConferenceHandler: React.PropTypes.any,
|
||||
collapsed: React.PropTypes.bool.isRequired,
|
||||
currentRoom: React.PropTypes.string,
|
||||
searchFilter: React.PropTypes.string,
|
||||
},
|
||||
|
||||
|
@ -88,6 +87,7 @@ module.exports = React.createClass({
|
|||
cli.on("Room.receipt", this.onRoomReceipt);
|
||||
cli.on("RoomState.events", this.onRoomStateEvents);
|
||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||
cli.on("Event.decrypted", this.onEventDecrypted);
|
||||
cli.on("accountData", this.onAccountData);
|
||||
|
||||
this.refreshRoomList();
|
||||
|
@ -155,6 +155,7 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
|
||||
MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName);
|
||||
MatrixClientPeg.get().removeListener("Event.decrypted", this.onEventDecrypted);
|
||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
}
|
||||
// cancel any pending calls to the rate_limited_funcs
|
||||
|
@ -224,6 +225,11 @@ module.exports = React.createClass({
|
|||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
onEventDecrypted: function(ev) {
|
||||
// An event being decrypted may mean we need to re-order the room list
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
onAccountData: function(ev) {
|
||||
if (ev.getType() == 'm.direct') {
|
||||
this._delayedRefreshRoomList();
|
||||
|
@ -571,6 +577,7 @@ module.exports = React.createClass({
|
|||
label={ _t('Invites') }
|
||||
editable={ false }
|
||||
order="recent"
|
||||
isInvite={true}
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
incomingCall={ self.state.incomingCall }
|
||||
collapsed={ self.props.collapsed }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -27,6 +28,8 @@ var RoomNotifs = require('../../../RoomNotifs');
|
|||
var FormattingUtils = require('../../../utils/FormattingUtils');
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||
import ActiveRoomObserver from '../../../ActiveRoomObserver';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomTile',
|
||||
|
@ -39,7 +42,6 @@ module.exports = React.createClass({
|
|||
|
||||
room: React.PropTypes.object.isRequired,
|
||||
collapsed: React.PropTypes.bool.isRequired,
|
||||
selected: React.PropTypes.bool.isRequired,
|
||||
unread: React.PropTypes.bool.isRequired,
|
||||
highlight: React.PropTypes.bool.isRequired,
|
||||
isInvite: React.PropTypes.bool.isRequired,
|
||||
|
@ -58,6 +60,7 @@ module.exports = React.createClass({
|
|||
badgeHover : false,
|
||||
menuDisplayed: false,
|
||||
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||
selected: this.props.room.roomId === RoomViewStore.getRoomId(),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -87,8 +90,15 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_onActiveRoomChange: function() {
|
||||
this.setState({
|
||||
selected: this.props.room.roomId === RoomViewStore.getRoomId(),
|
||||
});
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -96,6 +106,7 @@ module.exports = React.createClass({
|
|||
if (cli) {
|
||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
}
|
||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
|
@ -174,7 +185,7 @@ module.exports = React.createClass({
|
|||
|
||||
var classes = classNames({
|
||||
'mx_RoomTile': true,
|
||||
'mx_RoomTile_selected': this.props.selected,
|
||||
'mx_RoomTile_selected': this.state.selected,
|
||||
'mx_RoomTile_unread': this.props.unread,
|
||||
'mx_RoomTile_unreadNotify': notifBadges,
|
||||
'mx_RoomTile_highlight': mentionBadges,
|
||||
|
@ -221,7 +232,7 @@ module.exports = React.createClass({
|
|||
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
||||
});
|
||||
|
||||
if (this.props.selected) {
|
||||
if (this.state.selected) {
|
||||
let nameSelected = <EmojiText>{name}</EmojiText>;
|
||||
|
||||
label = <div title={ name } className={ nameClasses } dir="auto">{ nameSelected }</div>;
|
||||
|
|
97
src/components/views/voip/CallPreview.js
Normal file
97
src/components/views/voip/CallPreview.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
Copyright 2017 New Vector 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import CallHandler from '../../../CallHandler';
|
||||
import dis from '../../../dispatcher';
|
||||
import sdk from '../../../index';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CallPreview',
|
||||
|
||||
propTypes: {
|
||||
// A Conference Handler implementation
|
||||
// Must have a function signature:
|
||||
// getConferenceCallForRoom(roomId: string): MatrixCall
|
||||
ConferenceHandler: React.PropTypes.object,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
roomId: RoomViewStore.getRoomId(),
|
||||
activeCall: CallHandler.getAnyActiveCall(),
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this.dispatcherRef = dis.register(this._onAction);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (this._roomStoreToken) {
|
||||
this._roomStoreToken.remove();
|
||||
}
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
_onRoomViewStoreUpdate: function(payload) {
|
||||
if (RoomViewStore.getRoomId() === this.state.roomId) return;
|
||||
this.setState({
|
||||
roomId: RoomViewStore.getRoomId(),
|
||||
});
|
||||
},
|
||||
|
||||
_onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
// listen for call state changes to prod the render method, which
|
||||
// may hide the global CallView if the call it is tracking is dead
|
||||
case 'call_state':
|
||||
this.setState({
|
||||
activeCall: CallHandler.getAnyActiveCall(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_onCallViewClick: function() {
|
||||
const call = CallHandler.getAnyActiveCall();
|
||||
if (call) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: call.groupRoomId || call.roomId,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const callForRoom = CallHandler.getCallForRoom(this.state.roomId);
|
||||
const showCall = (this.state.activeCall && this.state.activeCall.call_state === 'connected' && !callForRoom);
|
||||
|
||||
if (showCall) {
|
||||
const CallView = sdk.getComponent('voip.CallView');
|
||||
return (
|
||||
<CallView
|
||||
className="mx_LeftPanel_callView" showVoice={true} onClick={this._onCallViewClick}
|
||||
ConferenceHandler={this.props.ConferenceHandler}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue