Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Weblate 2017-08-22 16:47:01 +00:00
commit 729d3f7608
47 changed files with 581 additions and 488 deletions

View file

@ -33,7 +33,6 @@ src/components/views/create_room/CreateRoomButton.js
src/components/views/create_room/Presets.js
src/components/views/create_room/RoomAlias.js
src/components/views/dialogs/ChatCreateOrReuseDialog.js
src/components/views/dialogs/ChatInviteDialog.js
src/components/views/dialogs/DeactivateAccountDialog.js
src/components/views/dialogs/InteractiveAuthDialog.js
src/components/views/dialogs/SetMxIdDialog.js
@ -114,7 +113,6 @@ src/components/views/settings/EnableNotificationsButton.js
src/ContentMessages.js
src/HtmlUtils.js
src/ImageUtils.js
src/Invite.js
src/languageHandler.js
src/linkify-matrix.js
src/Login.js

View file

@ -26,7 +26,7 @@ are currently filed against vector-im/riot-web rather than this project).
Translation Status
==================
[![translationsstatus](https://translate.nordgedanken.de/widgets/riot-web/-/multi-auto.svg)](https://translate.nordgedanken.de/engage/riot-web/?utm_source=widget)
[![Translation status](https://translate.riot.im/widgets/riot-web/-/multi-auto.svg)](https://translate.riot.im/engage/riot-web/?utm_source=widget)
Developer Guide
===============

View file

@ -1,5 +1,6 @@
/*
Copyright 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.
@ -16,24 +17,11 @@ limitations under the License.
import MatrixClientPeg from './MatrixClientPeg';
import MultiInviter from './utils/MultiInviter';
const emailRegex = /^\S+@\S+\.\S+$/;
const mxidRegex = /^@\S+:\S+$/
export function getAddressType(inputText) {
const isEmailAddress = emailRegex.test(inputText);
const isMatrixId = mxidRegex.test(inputText);
// sanity check the input for user IDs
if (isEmailAddress) {
return 'email';
} else if (isMatrixId) {
return 'mx';
} else {
return null;
}
}
import Modal from './Modal';
import { getAddressType } from './UserAddress';
import createRoom from './createRoom';
import sdk from './';
import { _t } from './languageHandler';
export function inviteToRoom(roomId, addr) {
const addrType = getAddressType(addr);
@ -52,12 +40,116 @@ export function inviteToRoom(roomId, addr) {
* Simpler interface to utils/MultiInviter but with
* no option to cancel.
*
* @param {roomId} The ID of the room to invite to
* @param {array} Array of strings of addresses to invite. May be matrix IDs or 3pids.
* @returns Promise
* @param {string} roomId The ID of the room to invite to
* @param {string[]} addrs Array of strings of addresses to invite. May be matrix IDs or 3pids.
* @returns {Promise} Promise
*/
export function inviteMultipleToRoom(roomId, addrs) {
const inviter = new MultiInviter(roomId);
return inviter.invite(addrs);
}
export function showStartChatInviteDialog() {
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog");
Modal.createTrackedDialog('Start a chat', '', UserPickerDialog, {
title: _t('Start a chat'),
description: _t("Who would you like to communicate with?"),
placeholder: _t("Email, name or matrix ID"),
button: _t("Start Chat"),
onFinished: _onStartChatFinished,
});
}
export function showRoomInviteDialog(roomId) {
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog");
Modal.createTrackedDialog('Chat Invite', '', UserPickerDialog, {
title: _t('Invite new room members'),
description: _t('Who would you like to add to this room?'),
button: _t('Send Invites'),
placeholder: _t("Email, name or matrix ID"),
onFinished: (shouldInvite, addrs) => {
_onRoomInviteFinished(roomId, shouldInvite, addrs);
},
});
}
function _onStartChatFinished(shouldInvite, addrs) {
if (!shouldInvite) return;
const addrTexts = addrs.map((addr) => addr.address);
if (_isDmChat(addrTexts)) {
// Start a new DM chat
createRoom({dmUserId: addrTexts[0]}).catch((err) => {
console.error(err.stack);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, {
title: _t("Failed to invite user"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
});
} else {
// Start multi user chat
let room;
createRoom().then((roomId) => {
room = MatrixClientPeg.get().getRoom(roomId);
return inviteMultipleToRoom(roomId, addrTexts);
}).then((addrs) => {
return _showAnyInviteErrors(addrs, room);
}).catch((err) => {
console.error(err.stack);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
title: _t("Failed to invite"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
});
}
}
function _onRoomInviteFinished(roomId, shouldInvite, addrs) {
if (!shouldInvite) return;
const addrTexts = addrs.map((addr) => addr.address);
// Invite new users to a room
inviteMultipleToRoom(roomId, addrTexts).then((addrs) => {
const room = MatrixClientPeg.get().getRoom(roomId);
return _showAnyInviteErrors(addrs, room);
}).catch((err) => {
console.error(err.stack);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
title: _t("Failed to invite"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
});
}
function _isDmChat(addrTexts) {
if (addrTexts.length === 1 && getAddressType(addrTexts[0])) {
return true;
} else {
return false;
}
}
function _showAnyInviteErrors(addrs, room) {
// Show user any errors
const errorList = [];
for (const addr of Object.keys(addrs)) {
if (addrs[addr] === "error") {
errorList.push(addr);
}
}
if (errorList.length > 0) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite the following users to the room', '', ErrorDialog, {
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
description: errorList.join(", "),
});
}
return addrs;
}

View file

@ -112,8 +112,8 @@ class ModalManager {
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
}
createTrackedDialogAsync(analyticsId, loader, props, className) {
Analytics.trackEvent('Modal', analyticsId);
createTrackedDialogAsync(analyticsAction, analyticsInfo, loader, props, className) {
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
return this.createDialogAsync(loader, props, className);
}

View file

@ -76,10 +76,13 @@ class ScalarAuthClient {
return defer.promise;
}
getScalarInterfaceUrlForRoom(roomId, screen) {
getScalarInterfaceUrlForRoom(roomId, screen, id) {
var url = SdkConfig.get().integrations_ui_url;
url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
url += "&room_id=" + encodeURIComponent(roomId);
if (id) {
url += '&integ_id=' + encodeURIComponent(id);
}
if (screen) {
url += '&screen=' + encodeURIComponent(screen);
}

View file

@ -248,6 +248,29 @@ function textForPowerEvent(event) {
});
}
function textForWidgetEvent(event) {
const senderName = event.sender ? event.sender.name : event.getSender();
const previousContent = event.getPrevContent() || {};
const {name, type, url} = event.getContent() || {};
let widgetName = name || previousContent.name || type || previousContent.type || '';
// Apply sentence case to widget name
if (widgetName && widgetName.length > 0) {
widgetName = widgetName[0].toUpperCase() + widgetName.slice(1) + ' ';
}
// If the widget was removed, its content should be {}, but this is sufficiently
// equivalent to that condition.
if (url) {
return _t('%(widgetName)s widget added by %(senderName)s', {
widgetName, senderName,
});
} else {
return _t('%(widgetName)s widget removed by %(senderName)s', {
widgetName, senderName,
});
}
}
var handlers = {
'm.room.message': textForMessageEvent,
'm.room.name': textForRoomNameEvent,
@ -260,6 +283,8 @@ var handlers = {
'm.room.history_visibility': textForHistoryVisibilityEvent,
'm.room.encryption': textForEncryptionEvent,
'm.room.power_levels': textForPowerEvent,
'im.vector.modular.widgets': textForWidgetEvent,
};
module.exports = {

54
src/UserAddress.js Normal file
View file

@ -0,0 +1,54 @@
/*
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.
*/
const emailRegex = /^\S+@\S+\.\S+$/;
const mxidRegex = /^@\S+:\S+$/;
import PropTypes from 'prop-types';
export const addressTypes = [
'mx', 'email',
];
// PropType definition for an object describing
// an address that can be invited to a room (which
// could be a third party identifier or a matrix ID)
// along with some additional information about the
// address / target.
export const UserAddressType = PropTypes.shape({
addressType: PropTypes.oneOf(addressTypes).isRequired,
address: PropTypes.string.isRequired,
displayName: PropTypes.string,
avatarMxc: PropTypes.string,
// true if the address is known to be a valid address (eg. is a real
// user we've seen) or false otherwise (eg. is just an address the
// user has entered)
isKnown: PropTypes.bool,
});
export function getAddressType(inputText) {
const isEmailAddress = emailRegex.test(inputText);
const isMatrixId = mxidRegex.test(inputText);
// sanity check the input for user IDs
if (isEmailAddress) {
return 'email';
} else if (isMatrixId) {
return 'mx';
} else {
return null;
}
}

View file

@ -28,7 +28,7 @@ export default {
{
name: "-",
id: 'matrix_apps',
default: false,
default: true,
// XXX: Always use default, ignore localStorage and remove from labs
override: true,

View file

@ -40,7 +40,7 @@ export default class UserProvider extends AutocompleteProvider {
keys: ['name'],
});
this.matcher = new FuzzyMatcher([], {
keys: ['name'],
keys: ['name', 'userId'],
shouldMatchPrefix: true,
});
}

View file

@ -1,6 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations 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.
@ -31,6 +32,7 @@ import dis from "../../dispatcher";
import Modal from "../../Modal";
import Tinter from "../../Tinter";
import sdk from '../../index';
import { showStartChatInviteDialog, showRoomInviteDialog } from '../../Invite';
import * as Rooms from '../../Rooms';
import linkifyMatrix from "../../linkify-matrix";
import * as Lifecycle from '../../Lifecycle';
@ -512,7 +514,7 @@ module.exports = React.createClass({
this._createChat();
break;
case 'view_invite':
this._invite(payload.roomId);
showRoomInviteDialog(payload.roomId);
break;
case 'notifier_enabled':
this.forceUpdate();
@ -766,13 +768,7 @@ module.exports = React.createClass({
dis.dispatch({action: 'view_set_mxid'});
return;
}
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
Modal.createTrackedDialog('Start a chat', '', ChatInviteDialog, {
title: _t('Start a chat'),
description: _t("Who would you like to communicate with?"),
placeholder: _t("Email, name or matrix ID"),
button: _t("Start Chat"),
});
showStartChatInviteDialog();
},
_createRoom: function() {
@ -857,17 +853,6 @@ module.exports = React.createClass({
}).close;
},
_invite: function(roomId) {
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
Modal.createTrackedDialog('Chat Invite', '', ChatInviteDialog, {
title: _t('Invite new room members'),
description: _t('Who would you like to add to this room?'),
button: _t('Send Invites'),
placeholder: _t("Email, name or matrix ID"),
roomId: roomId,
});
},
_leaveRoom: function(roomId) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");

View file

@ -339,6 +339,15 @@ module.exports = React.createClass({
for (;i + 1 < this.props.events.length; i++) {
const collapsedMxEv = this.props.events[i + 1];
// Ignore redacted/hidden member events
if (!this._shouldShowEvent(collapsedMxEv)) {
// If this hidden event is the RM and in or at end of a MELS put RM after MELS.
if (collapsedMxEv.getId() === this.props.readMarkerEventId) {
readMarkerInMels = true;
}
continue;
}
if (!isMembershipChange(collapsedMxEv) ||
this._wantsDateSeparator(this.props.events[i], collapsedMxEv.getDate())) {
break;
@ -349,11 +358,6 @@ module.exports = React.createClass({
readMarkerInMels = true;
}
// Ignore redacted/hidden member events
if (!this._shouldShowEvent(collapsedMxEv)) {
continue;
}
summarisedEvents.push(collapsedMxEv);
}

View file

@ -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.
@ -15,40 +16,37 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
import createRoom from '../../../createRoom';
import MatrixClientPeg from '../../../MatrixClientPeg';
import DMRoomMap from '../../../utils/DMRoomMap';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';
import Promise from 'bluebird';
import dis from '../../../dispatcher';
import { addressTypes, getAddressType } from '../../../UserAddress.js';
const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
module.exports = React.createClass({
displayName: "ChatInviteDialog",
displayName: "UserPickerDialog",
propTypes: {
title: React.PropTypes.string.isRequired,
description: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.string,
]),
value: React.PropTypes.string,
placeholder: React.PropTypes.string,
roomId: React.PropTypes.string,
button: React.PropTypes.string,
focus: React.PropTypes.bool,
onFinished: React.PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.node,
value: PropTypes.string,
placeholder: PropTypes.string,
roomId: PropTypes.string,
button: PropTypes.string,
focus: PropTypes.bool,
validAddressTypes: PropTypes.arrayOf(PropTypes.oneOf(addressTypes)),
onFinished: PropTypes.func.isRequired,
},
getDefaultProps: function() {
return {
value: "",
focus: true,
validAddressTypes: addressTypes,
};
},
@ -56,9 +54,9 @@ module.exports = React.createClass({
return {
error: false,
// List of AddressTile.InviteAddressType objects representing
// List of UserAddressType objects representing
// the list of addresses we're going to invite
inviteList: [],
userList: [],
// Whether a search is ongoing
busy: false,
@ -68,7 +66,7 @@ module.exports = React.createClass({
serverSupportsUserDirectory: true,
// The query being searched for
query: "",
// List of AddressTile.InviteAddressType objects representing
// List of UserAddressType objects representing
// the set of auto-completion results for the current search
// query.
queryList: [],
@ -83,57 +81,14 @@ module.exports = React.createClass({
},
onButtonClick: function() {
let inviteList = this.state.inviteList.slice();
let userList = this.state.userList.slice();
// Check the text input field to see if user has an unconverted address
// If there is and it's valid add it to the local inviteList
// If there is and it's valid add it to the local userList
if (this.refs.textinput.value !== '') {
inviteList = this._addInputToList();
if (inviteList === null) return;
}
const addrTexts = inviteList.map(addr => addr.address);
if (inviteList.length > 0) {
if (this._isDmChat(addrTexts)) {
const userId = inviteList[0].address;
// Direct Message chat
const rooms = this._getDirectMessageRooms(userId);
if (rooms.length > 0) {
// A Direct Message room already exists for this user, so select a
// room from a list that is similar to the one in MemberInfo panel
const ChatCreateOrReuseDialog = sdk.getComponent(
"views.dialogs.ChatCreateOrReuseDialog",
);
const close = Modal.createTrackedDialog('Create or Reuse', '', ChatCreateOrReuseDialog, {
userId: userId,
onFinished: (success) => {
this.props.onFinished(success);
},
onNewDMClick: () => {
dis.dispatch({
action: 'start_chat',
user_id: userId,
});
close(true);
},
onExistingRoomSelected: (roomId) => {
dis.dispatch({
action: 'view_room',
room_id: roomId,
});
close(true);
},
}).close;
} else {
this._startChat(inviteList);
}
} else {
// Multi invite chat
this._startChat(inviteList);
}
} else {
// No addresses supplied
this.setState({ error: true });
userList = this._addInputToList();
if (userList === null) return;
}
this.props.onFinished(true, userList);
},
onCancel: function() {
@ -157,10 +112,10 @@ module.exports = React.createClass({
e.stopPropagation();
e.preventDefault();
if (this.addressSelector) this.addressSelector.chooseSelection();
} else if (this.refs.textinput.value.length === 0 && this.state.inviteList.length && e.keyCode === 8) { // backspace
} else if (this.refs.textinput.value.length === 0 && this.state.userList.length && e.keyCode === 8) { // backspace
e.stopPropagation();
e.preventDefault();
this.onDismissed(this.state.inviteList.length - 1)();
this.onDismissed(this.state.userList.length - 1)();
} else if (e.keyCode === 13) { // enter
e.stopPropagation();
e.preventDefault();
@ -201,12 +156,11 @@ module.exports = React.createClass({
},
onDismissed: function(index) {
var self = this;
return () => {
var inviteList = self.state.inviteList.slice();
inviteList.splice(index, 1);
self.setState({
inviteList: inviteList,
const userList = this.state.userList.slice();
userList.splice(index, 1);
this.setState({
userList: userList,
queryList: [],
query: "",
});
@ -215,17 +169,16 @@ module.exports = React.createClass({
},
onClick: function(index) {
var self = this;
return function() {
self.onSelected(index);
return () => {
this.onSelected(index);
};
},
onSelected: function(index) {
var inviteList = this.state.inviteList.slice();
inviteList.push(this.state.queryList[index]);
const userList = this.state.userList.slice();
userList.push(this.state.queryList[index]);
this.setState({
inviteList: inviteList,
userList: userList,
queryList: [],
query: "",
});
@ -297,7 +250,7 @@ module.exports = React.createClass({
return;
}
// Return objects, structure of which is defined
// by InviteAddressType
// by UserAddressType
queryList.push({
addressType: 'mx',
address: user.user_id,
@ -311,7 +264,7 @@ module.exports = React.createClass({
// This is important, otherwise there's no way to invite
// a perfectly valid address if there are close matches.
const addrType = getAddressType(query);
if (addrType !== null) {
if (this.props.validAddressTypes.includes(addrType)) {
queryList.unshift({
addressType: addrType,
address: query,
@ -330,132 +283,6 @@ module.exports = React.createClass({
});
},
_getDirectMessageRooms: function(addr) {
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
const dmRooms = dmRoomMap.getDMRoomsForUserId(addr);
const rooms = [];
dmRooms.forEach(dmRoom => {
let room = MatrixClientPeg.get().getRoom(dmRoom);
if (room) {
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
if (me.membership == 'join') {
rooms.push(room);
}
}
});
return rooms;
},
_startChat: function(addrs) {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'view_set_mxid'});
return;
}
const addrTexts = addrs.map((addr) => {
return addr.address;
});
if (this.props.roomId) {
// Invite new user to a room
var self = this;
inviteMultipleToRoom(this.props.roomId, addrTexts)
.then(function(addrs) {
var room = MatrixClientPeg.get().getRoom(self.props.roomId);
return self._showAnyInviteErrors(addrs, room);
})
.catch(function(err) {
console.error(err.stack);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
title: _t("Failed to invite"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
return null;
})
.done();
} else if (this._isDmChat(addrTexts)) {
// Start the DM chat
createRoom({dmUserId: addrTexts[0]})
.catch(function(err) {
console.error(err.stack);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, {
title: _t("Failed to invite user"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
return null;
})
.done();
} else {
// Start multi user chat
var self = this;
var room;
createRoom().then(function(roomId) {
room = MatrixClientPeg.get().getRoom(roomId);
return inviteMultipleToRoom(roomId, addrTexts);
})
.then(function(addrs) {
return self._showAnyInviteErrors(addrs, room);
})
.catch(function(err) {
console.error(err.stack);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
title: _t("Failed to invite"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
return null;
})
.done();
}
// Close - this will happen before the above, as that is async
this.props.onFinished(true, addrTexts);
},
_isOnInviteList: function(uid) {
for (let i = 0; i < this.state.inviteList.length; i++) {
if (
this.state.inviteList[i].addressType == 'mx' &&
this.state.inviteList[i].address.toLowerCase() === uid
) {
return true;
}
}
return false;
},
_isDmChat: function(addrTexts) {
if (addrTexts.length === 1 &&
getAddressType(addrTexts[0]) === "mx" &&
!this.props.roomId
) {
return true;
} else {
return false;
}
},
_showAnyInviteErrors: function(addrs, room) {
// Show user any errors
var errorList = [];
for (var addr in addrs) {
if (addrs.hasOwnProperty(addr) && addrs[addr] === "error") {
errorList.push(addr);
}
}
if (errorList.length > 0) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite the following users to the room', '', ErrorDialog, {
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
description: errorList.join(", "),
});
}
return addrs;
},
_addInputToList: function() {
const addressText = this.refs.textinput.value.trim();
const addrType = getAddressType(addressText);
@ -476,15 +303,15 @@ module.exports = React.createClass({
}
}
const inviteList = this.state.inviteList.slice();
inviteList.push(addrObj);
const userList = this.state.userList.slice();
userList.push(addrObj);
this.setState({
inviteList: inviteList,
userList: userList,
queryList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
return inviteList;
return userList;
},
_lookupThreepid: function(medium, address) {
@ -495,7 +322,7 @@ module.exports = React.createClass({
// not like they leak.
this._cancelThreepidLookup = function() {
cancelled = true;
}
};
// wait a bit to let the user finish typing
return Promise.delay(500).then(() => {
@ -511,7 +338,7 @@ module.exports = React.createClass({
if (cancelled) return null;
this.setState({
queryList: [{
// an InviteAddressType
// a UserAddressType
addressType: medium,
address: address,
displayName: res.displayname,
@ -527,20 +354,20 @@ module.exports = React.createClass({
const AddressSelector = sdk.getComponent("elements.AddressSelector");
this.scrollElement = null;
var query = [];
const query = [];
// create the invite list
if (this.state.inviteList.length > 0) {
var AddressTile = sdk.getComponent("elements.AddressTile");
for (let i = 0; i < this.state.inviteList.length; i++) {
if (this.state.userList.length > 0) {
const AddressTile = sdk.getComponent("elements.AddressTile");
for (let i = 0; i < this.state.userList.length; i++) {
query.push(
<AddressTile key={i} address={this.state.inviteList[i]} canDismiss={true} onDismissed={ this.onDismissed(i) } />
<AddressTile key={i} address={this.state.userList[i]} canDismiss={true} onDismissed={ this.onDismissed(i) } />,
);
}
}
// Add the query at the end
query.push(
<textarea key={this.state.inviteList.length}
<textarea key={this.state.userList.length}
rows="1"
id="textinput"
ref="textinput"
@ -555,7 +382,9 @@ module.exports = React.createClass({
let error;
let addressSelector;
if (this.state.error) {
error = <div className="mx_ChatInviteDialog_error">{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}</div>;
error = <div className="mx_ChatInviteDialog_error">
{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}
</div>;
} else if (this.state.searchError) {
error = <div className="mx_ChatInviteDialog_error">{this.state.searchError}</div>;
} else if (
@ -598,5 +427,5 @@ module.exports = React.createClass({
</div>
</div>
);
}
},
});

View file

@ -20,7 +20,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import classNames from 'classnames';
import { InviteAddressType } from './AddressTile';
import { UserAddressType } from '../../../UserAddress';
export default React.createClass({
displayName: 'AddressSelector',
@ -29,7 +29,7 @@ export default React.createClass({
onSelected: React.PropTypes.func.isRequired,
// List of the addresses to display
addressList: React.PropTypes.arrayOf(InviteAddressType).isRequired,
addressList: React.PropTypes.arrayOf(UserAddressType).isRequired,
truncateAt: React.PropTypes.number.isRequired,
selected: React.PropTypes.number,

View file

@ -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.
@ -14,38 +15,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
import React from 'react';
import classNames from 'classnames';
import sdk from "../../../index";
import MatrixClientPeg from "../../../MatrixClientPeg";
import { _t } from '../../../languageHandler';
// React PropType definition for an object describing
// an address that can be invited to a room (which
// could be a third party identifier or a matrix ID)
// along with some additional information about the
// address / target.
export const InviteAddressType = React.PropTypes.shape({
addressType: React.PropTypes.oneOf([
'mx', 'email'
]).isRequired,
address: React.PropTypes.string.isRequired,
displayName: React.PropTypes.string,
avatarMxc: React.PropTypes.string,
// true if the address is known to be a valid address (eg. is a real
// user we've seen) or false otherwise (eg. is just an address the
// user has entered)
isKnown: React.PropTypes.bool,
});
import { UserAddressType } from '../../../UserAddress.js';
export default React.createClass({
displayName: 'AddressTile',
propTypes: {
address: InviteAddressType.isRequired,
address: UserAddressType.isRequired,
canDismiss: React.PropTypes.bool,
onDismissed: React.PropTypes.func,
justified: React.PropTypes.bool,

View file

@ -47,13 +47,19 @@ export default class AppPermission extends React.Component {
}
render() {
let e2eWarningText;
if (this.props.isRoomEncrypted) {
e2eWarningText =
<span className='mx_AppPermissionWarningTextLabel'>{_t('NOTE: Apps are not end-to-end encrypted')}</span>;
}
return (
<div className='mx_AppPermissionWarning'>
<div className='mx_AppPermissionWarningImage'>
<img src='img/warning.svg' alt={_t('Warning!')}/>
</div>
<div className='mx_AppPermissionWarningText'>
<span className='mx_AppPermissionWarningTextLabel'>Do you want to load widget from URL:</span> <span className='mx_AppPermissionWarningTextURL'>{this.state.curlBase}</span>
<span className='mx_AppPermissionWarningTextLabel'>{_t('Do you want to load widget from URL:')}</span> <span className='mx_AppPermissionWarningTextURL'>{this.state.curlBase}</span>
{e2eWarningText}
</div>
<input
className='mx_AppPermissionButton'
@ -67,9 +73,11 @@ export default class AppPermission extends React.Component {
}
AppPermission.propTypes = {
isRoomEncrypted: PropTypes.bool,
url: PropTypes.string.isRequired,
onPermissionGranted: PropTypes.func.isRequired,
};
AppPermission.defaultProps = {
isRoomEncrypted: false,
onPermissionGranted: function() {},
};

View file

@ -28,9 +28,9 @@ import AppPermission from './AppPermission';
import AppWarning from './AppWarning';
import MessageSpinner from './MessageSpinner';
import WidgetUtils from '../../../WidgetUtils';
import dis from '../../../dispatcher';
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const betaHelpMsg = 'This feature is currently experimental and is intended for beta testing only';
export default React.createClass({
displayName: 'AppTile',
@ -44,6 +44,10 @@ export default React.createClass({
// Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer.
// This should be set to true when there is only one widget in the app drawer, otherwise it should be false.
fullWidth: React.PropTypes.bool,
// UserId of the current user
userId: React.PropTypes.string.isRequired,
// UserId of the entity that added / modified the widget
creatorUserId: React.PropTypes.string,
},
getDefaultProps: function() {
@ -59,7 +63,8 @@ export default React.createClass({
loading: false,
widgetUrl: this.props.url,
widgetPermissionId: widgetPermissionId,
hasPermissionToLoad: Boolean(hasPermissionToLoad === 'true'),
// Assume that widget has permission to load if we are the user who added it to the room, or if explicitly granted by the user
hasPermissionToLoad: hasPermissionToLoad === 'true' || this.props.userId === this.props.creatorUserId,
error: null,
deleting: false,
};
@ -122,7 +127,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");
@ -177,11 +183,25 @@ export default React.createClass({
let appTileName = "No name";
if(this.props.name && this.props.name.trim()) {
appTileName = this.props.name.trim();
appTileName = appTileName[0].toUpperCase() + appTileName.slice(1).toLowerCase();
}
return appTileName;
},
onClickMenuBar: function(ev) {
ev.preventDefault();
// Ignore clicks on menu bar children
if (ev.target !== this.refs.menu_bar) {
return;
}
// Toggle the view state of the apps drawer
dis.dispatch({
action: 'appsDrawer',
show: !this.props.show,
});
},
render: function() {
let appTileBody;
@ -203,42 +223,46 @@ export default React.createClass({
safeWidgetUrl = url.format(parsedWidgetUrl);
}
if (this.state.loading) {
appTileBody = (
<div className='mx_AppTileBody mx_AppLoading'>
<MessageSpinner msg='Loading...'/>
</div>
);
} else if (this.state.hasPermissionToLoad == true) {
if (this.isMixedContent()) {
if (this.props.show) {
if (this.state.loading) {
appTileBody = (
<div className='mx_AppTileBody mx_AppLoading'>
<MessageSpinner msg='Loading...'/>
</div>
);
} else if (this.state.hasPermissionToLoad == true) {
if (this.isMixedContent()) {
appTileBody = (
<div className="mx_AppTileBody">
<AppWarning
errorMsg="Error - Mixed content"
/>
</div>
);
} else {
appTileBody = (
<div className="mx_AppTileBody">
<iframe
ref="appFrame"
src={safeWidgetUrl}
allowFullScreen="true"
sandbox={sandboxFlags}
></iframe>
</div>
);
}
} else {
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
appTileBody = (
<div className="mx_AppTileBody">
<AppWarning
errorMsg="Error - Mixed content"
<AppPermission
isRoomEncrypted={isRoomEncrypted}
url={this.state.widgetUrl}
onPermissionGranted={this._grantWidgetPermission}
/>
</div>
);
} else {
appTileBody = (
<div className="mx_AppTileBody">
<iframe
ref="appFrame"
src={safeWidgetUrl}
allowFullScreen="true"
sandbox={sandboxFlags}
></iframe>
</div>
);
}
} else {
appTileBody = (
<div className="mx_AppTileBody">
<AppPermission
url={this.state.widgetUrl}
onPermissionGranted={this._grantWidgetPermission}
/>
</div>
);
}
// editing is done in scalar
@ -253,10 +277,9 @@ export default React.createClass({
return (
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
<div className="mx_AppTileMenuBar">
<div ref="menu_bar" className="mx_AppTileMenuBar" onClick={this.onClickMenuBar}>
{this.formatAppTileName()}
<span className="mx_AppTileMenuBarWidgets">
<span className="mx_Beta" alt={betaHelpMsg} title={betaHelpMsg}>&#946;</span>
{/* Edit widget */}
{showEditButton && <img
src="img/edit.svg"

View file

@ -0,0 +1,117 @@
/*
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 PropTypes from 'prop-types';
import sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import ScalarAuthClient from '../../../ScalarAuthClient';
import ScalarMessaging from '../../../ScalarMessaging';
import Modal from "../../../Modal";
import { _t } from '../../../languageHandler';
import AccessibleButton from './AccessibleButton';
import TintableSvg from './TintableSvg';
export default class ManageIntegsButton extends React.Component {
constructor(props) {
super(props);
this.state = {
scalarError: null,
showIntegrationsError: false,
};
this.onManageIntegrations = this.onManageIntegrations.bind(this);
this.onShowIntegrationsError = this.onShowIntegrationsError.bind(this);
}
componentWillMount() {
ScalarMessaging.startListening();
this.scalarClient = null;
if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) {
this.scalarClient = new ScalarAuthClient();
this.scalarClient.connect().done(() => {
this.forceUpdate();
}, (err) => {
this.setState({ scalarError: err});
console.error(err);
});
}
}
componentWillUnmount() {
ScalarMessaging.stopListening();
}
onManageIntegrations(ev) {
ev.preventDefault();
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
Modal.createDialog(IntegrationsManager, {
src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.roomId) :
null,
}, "mx_IntegrationsManager");
}
onShowIntegrationsError(ev) {
ev.preventDefault();
this.setState({
showIntegrationsError: !this.state.showIntegrationsError,
});
}
render() {
let integrationsButton;
let integrationsError;
if (this.scalarClient !== null) {
if (this.state.showIntegrationsError && this.state.scalarError) {
integrationsError = (
<span className="mx_RoomSettings_integrationsButton_errorPopup">
{ _t('Could not connect to the integration server') }
</span>
);
}
if (this.scalarClient.hasCredentials()) {
integrationsButton = (
<AccessibleButton className="mx_RoomHeader_button" onClick={this.onManageIntegrations} title={ _t('Manage Integrations') }>
<TintableSvg src="img/icons-apps.svg" width="35" height="35"/>
</AccessibleButton>
);
} else if (this.state.scalarError) {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton_error" onClick={ this.onShowIntegrationsError }>
<img src="img/warning.svg" title={_t('Integrations Error')} width="17"/>
{ integrationsError }
</div>
);
} else {
integrationsButton = (
<AccessibleButton className="mx_RoomHeader_button" onClick={this.onManageIntegrations} title={ _t('Manage Integrations') }>
<TintableSvg src="img/icons-apps.svg" width="35" height="35"/>
</AccessibleButton>
);
}
}
return integrationsButton;
}
}
ManageIntegsButton.propTypes = {
roomId: PropTypes.string.isRequired,
};

View file

@ -171,7 +171,7 @@ const Pill = React.createClass({
}
pillClass = 'mx_UserPill';
href = null;
onClick = this.onUserPillClicked.bind(this);
onClick = this.onUserPillClicked;
}
}
break;

View file

@ -17,7 +17,7 @@ limitations under the License.
'use strict';
import React from 'react';
import { _t } from '../../../languageHandler';
import { _t, _tJsx } from '../../../languageHandler';
var DIV_ID = 'mx_recaptcha';
@ -66,7 +66,11 @@ 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 = "Robot check is currently unavailable on desktop - please use a <a href='https://riot.im/app'>web browser</a>.";
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>"; }
);
this.refs.recaptchaContainer.appendChild(warning);
}
else {

View file

@ -28,6 +28,8 @@ import ScalarMessaging from '../../../ScalarMessaging';
import { _t } from '../../../languageHandler';
import WidgetUtils from '../../../WidgetUtils';
// The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2;
module.exports = React.createClass({
displayName: 'AppsDrawer',
@ -53,9 +55,6 @@ module.exports = React.createClass({
this.scalarClient = new ScalarAuthClient();
this.scalarClient.connect().done(() => {
this.forceUpdate();
if (this.state.apps && this.state.apps.length < 1) {
this.onClickAddWidget();
}
// TODO -- Handle Scalar errors
// },
// (err) => {
@ -64,6 +63,8 @@ module.exports = React.createClass({
// });
});
}
this.dispatcherRef = dis.register(this.onAction);
},
componentWillUnmount: function() {
@ -71,6 +72,27 @@ module.exports = React.createClass({
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
}
dis.unregister(this.dispatcherRef);
},
componentWillReceiveProps(newProps) {
// Room has changed probably, update apps
this._updateApps();
},
onAction: function(action) {
switch (action.action) {
case 'appsDrawer':
// When opening the app draw when there aren't any apps, auto-launch the
// integrations manager to skip the awkward click on "Add widget"
if (action.show) {
const apps = this._getApps();
if (apps.length === 0) {
this._launchManageIntegrations();
}
}
break;
}
},
/**
@ -93,7 +115,7 @@ module.exports = React.createClass({
return pathTemplate;
},
_initAppConfig: function(appId, app) {
_initAppConfig: function(appId, app, sender) {
const user = MatrixClientPeg.get().getUser(this.props.userId);
const params = {
'$matrix_user_id': this.props.userId,
@ -111,6 +133,7 @@ module.exports = React.createClass({
app.id = appId;
app.name = app.name || app.type;
app.url = this.encodeUri(app.url, params);
app.creatorUserId = (sender && sender.userId) ? sender.userId : null;
return app;
},
@ -131,18 +154,12 @@ module.exports = React.createClass({
return appsStateEvents.filter((ev) => {
return ev.getContent().type && ev.getContent().url;
}).map((ev) => {
return this._initAppConfig(ev.getStateKey(), ev.getContent());
return this._initAppConfig(ev.getStateKey(), ev.getContent(), ev.sender);
});
},
_updateApps: function() {
const apps = this._getApps();
if (apps.length < 1) {
dis.dispatch({
action: 'appsDrawer',
show: false,
});
}
this.setState({
apps: apps,
});
@ -157,11 +174,7 @@ module.exports = React.createClass({
}
},
onClickAddWidget: function(e) {
if (e) {
e.preventDefault();
}
_launchManageIntegrations: function() {
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') :
@ -171,6 +184,23 @@ module.exports = React.createClass({
}, "mx_IntegrationsManager");
},
onClickAddWidget: function(e) {
e.preventDefault();
// Display a warning dialog if the max number of widgets have already been added to the room
const apps = this._getApps();
if (apps && apps.length >= MAX_WIDGETS) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`;
console.error(errorMsg);
Modal.createDialog(ErrorDialog, {
title: _t("Cannot add any more widgets"),
description: _t("The maximum permitted number of widgets have already been added to this room."),
});
return;
}
this._launchManageIntegrations();
},
render: function() {
const apps = this.state.apps.map(
(app, index, arr) => {
@ -183,24 +213,34 @@ module.exports = React.createClass({
fullWidth={arr.length<2 ? true : false}
room={this.props.room}
userId={this.props.userId}
show={this.props.showApps}
creatorUserId={app.creatorUserId}
/>);
});
const addWidget = this.state.apps && this.state.apps.length < 2 && this._canUserModify() &&
(<div onClick={this.onClickAddWidget}
role="button"
tabIndex="0"
className="mx_AddWidget_button"
title={_t('Add a widget')}>
[+] {_t('Add a widget')}
</div>);
let addWidget;
if (this.props.showApps &&
this._canUserModify()
) {
addWidget = <div
onClick={this.onClickAddWidget}
role="button"
tabIndex="0"
className={this.state.apps.length<2 ?
"mx_AddWidget_button mx_AddWidget_button_full_width" :
"mx_AddWidget_button"
}
title={_t('Add a widget')}>
[+] {_t('Add a widget')}
</div>;
}
return (
<div className="mx_AppsDrawer">
<div id="apps" className="mx_AppsContainer">
{apps}
</div>
{addWidget}
{this._canUserModify() && addWidget}
</div>
);
},

View file

@ -129,11 +129,13 @@ module.exports = React.createClass({
);
let appsDrawer = null;
if(UserSettingsStore.isFeatureEnabled('matrix_apps') && this.props.showApps) {
if(UserSettingsStore.isFeatureEnabled('matrix_apps')) {
appsDrawer = <AppsDrawer ref="appsDrawer"
room={this.props.room}
userId={this.props.userId}
maxHeight={this.props.maxHeight}/>;
maxHeight={this.props.maxHeight}
showApps={this.props.showApps}
/>;
}
return (

View file

@ -44,6 +44,8 @@ var eventTileTypes = {
'm.room.history_visibility' : 'messages.TextualEvent',
'm.room.encryption' : 'messages.TextualEvent',
'm.room.power_levels' : 'messages.TextualEvent',
'im.vector.modular.widgets': 'messages.TextualEvent',
};
var MAX_READ_AVATARS = 5;

View file

@ -289,12 +289,12 @@ export default class MessageComposer extends React.Component {
if (this.props.showApps) {
hideAppsButton =
<div key="controls_hide_apps" className="mx_MessageComposer_apps" onClick={this.onHideAppsClick} title={_t("Hide Apps")}>
<TintableSvg src="img/icons-apps-active.svg" width="35" height="35"/>
<TintableSvg src="img/icons-hide-apps.svg" width="35" height="35"/>
</div>;
} else {
showAppsButton =
<div key="show_apps" className="mx_MessageComposer_apps" onClick={this.onShowAppsClick} title={_t("Show Apps")}>
<TintableSvg src="img/icons-apps.svg" width="35" height="35"/>
<TintableSvg src="img/icons-show-apps.svg" width="35" height="35"/>
</div>;
}
}

View file

@ -29,6 +29,7 @@ import * as linkify from 'linkifyjs';
import linkifyElement from 'linkifyjs/element';
import linkifyMatrix from '../../../linkify-matrix';
import AccessibleButton from '../elements/AccessibleButton';
import ManageIntegsButton from '../elements/ManageIntegsButton';
import {CancelButton} from './SimpleRoomHeader';
linkifyMatrix(linkify);
@ -47,6 +48,7 @@ module.exports = React.createClass({
onSaveClick: React.PropTypes.func,
onSearchClick: React.PropTypes.func,
onLeaveClick: React.PropTypes.func,
onCancelClick: React.PropTypes.func,
},
getDefaultProps: function() {
@ -54,6 +56,7 @@ module.exports = React.createClass({
editing: false,
inRoom: false,
onSaveClick: function() {},
onCancelClick: function() {},
};
},
@ -320,10 +323,18 @@ module.exports = React.createClass({
}
let rightRow;
let manageIntegsButton;
if(this.props.room && this.props.room.roomId) {
manageIntegsButton = <ManageIntegsButton
roomId={this.props.room.roomId}
/>;
}
if (!this.props.editing) {
rightRow =
<div className="mx_RoomHeader_rightRow">
{ settingsButton }
{ manageIntegsButton }
{ forgetButton }
{ searchButton }
{ rightPanelButtons }

View file

@ -24,8 +24,6 @@ import sdk from '../../../index';
import Modal from '../../../Modal';
import ObjectUtils from '../../../ObjectUtils';
import dis from '../../../dispatcher';
import ScalarAuthClient from '../../../ScalarAuthClient';
import ScalarMessaging from '../../../ScalarMessaging';
import UserSettingsStore from '../../../UserSettingsStore';
import AccessibleButton from '../elements/AccessibleButton';
@ -92,7 +90,6 @@ module.exports = React.createClass({
propTypes: {
room: React.PropTypes.object.isRequired,
onSaveClick: React.PropTypes.func,
onCancelClick: React.PropTypes.func,
},
getInitialState: function() {
@ -118,14 +115,10 @@ module.exports = React.createClass({
// Default to false if it's undefined, otherwise react complains about changing
// components from uncontrolled to controlled
isRoomPublished: this._originalIsRoomPublished || false,
scalar_error: null,
showIntegrationsError: false,
};
},
componentWillMount: function() {
ScalarMessaging.startListening();
MatrixClientPeg.get().on("RoomMember.membership", this._onRoomMemberMembership);
MatrixClientPeg.get().getRoomDirectoryVisibility(
@ -137,18 +130,6 @@ module.exports = React.createClass({
console.error("Failed to get room visibility: " + err);
});
this.scalarClient = null;
if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) {
this.scalarClient = new ScalarAuthClient();
this.scalarClient.connect().done(() => {
this.forceUpdate();
}, (err) => {
this.setState({
scalar_error: err
});
});
}
dis.dispatch({
action: 'ui_opacity',
sideOpacity: 0.3,
@ -157,8 +138,6 @@ module.exports = React.createClass({
},
componentWillUnmount: function() {
ScalarMessaging.stopListening();
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener("RoomMember.membership", this._onRoomMemberMembership);
@ -513,28 +492,6 @@ module.exports = React.createClass({
roomState.mayClientSendStateEvent("m.room.guest_access", cli));
},
onManageIntegrations(ev) {
ev.preventDefault();
var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
Modal.createTrackedDialog('Integrations Manager', 'onManageIntegrations', IntegrationsManager, {
src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) :
null,
onFinished: ()=>{
if (this._calcSavePromises().length === 0) {
this.props.onCancelClick(ev);
}
},
}, "mx_IntegrationsManager");
},
onShowIntegrationsError(ev) {
ev.preventDefault();
this.setState({
showIntegrationsError: !this.state.showIntegrationsError,
});
},
onLeaveClick() {
dis.dispatch({
action: 'leave_room',
@ -796,46 +753,10 @@ module.exports = React.createClass({
</div>;
}
let integrationsButton;
let integrationsError;
if (this.scalarClient !== null) {
if (this.state.showIntegrationsError && this.state.scalar_error) {
console.error(this.state.scalar_error);
integrationsError = (
<span className="mx_RoomSettings_integrationsButton_errorPopup">
{ _t('Could not connect to the integration server') }
</span>
);
}
if (this.scalarClient.hasCredentials()) {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton" onClick={ this.onManageIntegrations }>
{ _t('Manage Integrations') }
</div>
);
} else if (this.state.scalar_error) {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton_error" onClick={ this.onShowIntegrationsError }>
Integrations Error <img src="img/warning.svg" width="17"/>
{ integrationsError }
</div>
);
} else {
integrationsButton = (
<div className="mx_RoomSettings_integrationsButton" style={{opacity: 0.5}}>
{ _t('Manage Integrations') }
</div>
);
}
}
return (
<div className="mx_RoomSettings">
{ leaveButton }
{ integrationsButton }
{ tagsSection }
@ -871,7 +792,7 @@ module.exports = React.createClass({
<input type="checkbox" disabled={ !roomState.mayClientSendStateEvent("m.room.aliases", cli) }
onChange={ this._onToggle.bind(this, "isRoomPublished", true, false)}
checked={this.state.isRoomPublished}/>
{_t("List this room in %(domain)s's room directory?", { domain: MatrixClientPeg.get().getDomain() })}
{_t("Publish this room to the public in %(domain)s's room directory?", { domain: MatrixClientPeg.get().getDomain() })}
</label>
</div>
<div className="mx_RoomSettings_settings">

View file

@ -71,7 +71,7 @@ export default class DevicesPanelEntry extends React.Component {
// pop up an interactive auth dialog
var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
Modal.createTrackedDialog('Delete Device Dialog', InteractiveAuthDialog, {
Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, {
title: _t("Authentication"),
matrixClient: MatrixClientPeg.get(),
authData: error.data,

View file

@ -851,7 +851,7 @@
"device id: ": "Geräte-ID: ",
"Device key:": "Geräte-Schlüssel:",
"Email address (optional)": "E-Mail-Adresse (optional)",
"List this room in %(domain)s's room directory?": "Diesen Raum zum Raum-Verzeichnis von %(domain)s hinzufügen?",
"Publish this room to the public in %(domain)s's room directory?": "Diesen Raum mittels Raum-Verzeichnis von %(domain)s veröffentlichen?",
"Mobile phone number (optional)": "Mobilfunknummer (optional)",
"Password:": "Passwort:",
"Register": "Registrieren",

View file

@ -297,7 +297,6 @@
"left": "έφυγε",
"%(targetName)s left the room.": "Ο χρήστης %(targetName)s έφυγε από το δωμάτιο.",
"Level": "Επίπεδο",
"List this room in %(domain)s's room directory?": "Να εμφανίζεται το δωμάτιο στο γενικό ευρετήριο του διακομιστή %(domain)s;",
"Local addresses for this room:": "Τοπική διεύθυνση για το δωμάτιο:",
"Logged in as:": "Συνδεθήκατε ως:",
"Login as guest": "Σύνδεση ως επισκέπτης",

View file

@ -190,6 +190,7 @@
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.",
"Can't load user settings": "Can't load user settings",
"Cannot add any more widgets": "Cannot add any more widgets",
"Change Password": "Change Password",
"%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.": "%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.",
"%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.",
@ -263,6 +264,7 @@
"Disinvite": "Disinvite",
"Display name": "Display name",
"Displays action": "Displays action",
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
"Don't send typing notifications": "Don't send typing notifications",
"Download %(text)s": "Download %(text)s",
"Drop File Here": "Drop File Here",
@ -361,6 +363,7 @@
"Incoming voice call from %(name)s": "Incoming voice call from %(name)s",
"Incorrect username and/or password.": "Incorrect username and/or password.",
"Incorrect verification code": "Incorrect verification code",
"Integrations Error": "Integrations Error",
"Interface Language": "Interface Language",
"Invalid alias format": "Invalid alias format",
"Invalid address format": "Invalid address format",
@ -392,7 +395,7 @@
"left": "left",
"%(targetName)s left the room.": "%(targetName)s left the room.",
"Level:": "Level:",
"List this room in %(domain)s's room directory?": "List this room in %(domain)s's room directory?",
"Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?",
"Local addresses for this room:": "Local addresses for this room:",
"Logged in as:": "Logged in as:",
"Login as guest": "Login as guest",
@ -432,6 +435,7 @@
"AM": "AM",
"PM": "PM",
"NOT verified": "NOT verified",
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
"No devices with registered encryption keys": "No devices with registered encryption keys",
"No display name": "No display name",
"No more results": "No more results",
@ -547,6 +551,7 @@
"Tagged as: ": "Tagged as: ",
"The default role for new room members is": "The default role for new room members is",
"The main address for this room is": "The main address for this room is",
"The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
"The phone number entered looks invalid": "The phone number entered looks invalid",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.",
"This action cannot be performed by a guest user. Please register to be able to do this.": "This action cannot be performed by a guest user. Please register to be able to do this.",
@ -969,5 +974,8 @@
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
"Failed to upload image": "Failed to upload image",
"Failed to update group": "Failed to update group",
"Hide avatars in user and room mentions": "Hide avatars in user and room mentions"
"Hide avatars in user and room mentions": "Hide avatars in user and room mentions",
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>"
}

View file

@ -359,7 +359,7 @@
"left": "left",
"%(targetName)s left the room.": "%(targetName)s left the room.",
"Level": "Level",
"List this room in %(domain)s's room directory?": "List this room in %(domain)s's room directory?",
"Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?",
"Local addresses for this room:": "Local addresses for this room:",
"Logged in as:": "Logged in as:",
"Login as guest": "Login as guest",

View file

@ -498,7 +498,6 @@
"Drop File Here": "Deje el fichero aquí",
"Guest access is disabled on this Home Server.": "El acceso de invitados está desactivado en este servidor.",
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Conecte con <voiceText>voz</voiceText> o <videoText>vídeo</videoText>.",
"List this room in %(domain)s's room directory?": "¿Mostrar esta sala en el directorio de %(domain)s?",
"Manage Integrations": "Gestionar integraciones",
"Markdown is disabled": "Markdown está desactivado",
"Markdown is enabled": "Markdown está activado",

View file

@ -464,7 +464,6 @@
"left": "atera da",
"%(targetName)s left the room.": "%(targetName)s erabiltzailea gelatik atera da.",
"Level:": "Maila:",
"List this room in %(domain)s's room directory?": "Gela hau %(domain)s's domeinuko gelen direktorioan zerrendatu?",
"Local addresses for this room:": "Gela honen tokiko helbideak:",
"Logged in as:": "Saioa hasteko erabiltzailea:",
"Login as guest": "Hasi saioa bisitari gisa",

View file

@ -811,7 +811,6 @@
"device id: ": "identifiant appareil : ",
"Device key:": "Clé de lappareil :",
"Email address (optional)": "Adresse e-mail (facultatif)",
"List this room in %(domain)s's room directory?": "Lister ce salon dans le répertoire de %(domain)s ?",
"Mobile phone number (optional)": "Numéro de téléphone (facultatif)",
"Password:": "Mot de passe :",
"Register": "S'inscrire",

View file

@ -402,7 +402,6 @@
"left": "kilépett",
"%(targetName)s left the room.": "%(targetName)s elhagyta a szobát.",
"Level:": "Szint:",
"List this room in %(domain)s's room directory?": "%(domain)s szobát feltüntessük a szobák listájában?",
"Local addresses for this room:": "A szoba helyi címe:",
"Logged in as:": "Bejelentkezve mint:",
"Login as guest": "Belépés vendégként",

View file

@ -48,7 +48,6 @@
"%(count)s new messages.other": "新しい発言 %(count)s",
"Don't send typing notifications": "文字入力中であることを公表しない",
"Filter room members": "参加者検索",
"List this room in %(domain)s's room directory?": "この部屋を %(domain)s サーバの部屋一覧に公開する?",
"Send a message (unencrypted)": "ここに送信文を入力 (暗号化なし)",
"Send an encrypted message": "暗号文を送る",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "発言時刻を12時間形式で表示 (例 2:30PM)",

View file

@ -405,7 +405,6 @@
"left": "떠났음",
"%(targetName)s left the room.": "%(targetName)s님이 방을 떠나셨어요.",
"Level:": "등급:",
"List this room in %(domain)s's room directory?": "%(domain)s's 방 목록에 이 방을 놓으시겠어요?",
"Local addresses for this room:": "이 방의 로컬 주소:",
"Logged in as:": "로그인:",
"Login as guest": "손님으로 로그인",

View file

@ -380,7 +380,6 @@
"left": "atstāja",
"%(targetName)s left the room.": "%(targetName)s atstāja istabu.",
"Level:": "Līmenis:",
"List this room in %(domain)s's room directory?": "Rādīt šo istabu %(domain)s kataloga sarakstā?",
"Local addresses for this room:": "Šīs istabas lokālās adreses:",
"Logged in as:": "Pierakstījās kā:",
"Login as guest": "Pierakstīties kā viesis",

View file

@ -480,7 +480,6 @@
"left": "verlaten",
"%(targetName)s left the room.": "%(targetName)s heeft de ruimte verlaten.",
"Level:": "Niveau:",
"List this room in %(domain)s's room directory?": "Deze ruimte in %(domain)s's ruimte catalogus vermelden?",
"Local addresses for this room:": "Lokale adressen voor deze ruimte:",
"Logged in as:": "Ingelogd als:",
"Login as guest": "Als gast inloggen",

View file

@ -862,7 +862,6 @@
"device id: ": "id do dispositivo: ",
"Device key:": "Chave do dispositivo:",
"Email address (optional)": "Endereço de e-mail (opcional)",
"List this room in %(domain)s's room directory?": "Deseja listar esta sala na lista pública de salas de %(domain)s?",
"Mobile phone number (optional)": "Número de telefone celular (opcional)",
"Password:": "Senha:",
"Register": "Registre-se",

View file

@ -863,7 +863,6 @@
"device id: ": "id do dispositivo: ",
"Device key:": "Chave do dispositivo:",
"Email address (optional)": "Endereço de e-mail (opcional)",
"List this room in %(domain)s's room directory?": "Deseja listar esta sala na lista pública de salas de %(domain)s?",
"Mobile phone number (optional)": "Número de telefone celular (opcional)",
"Password:": "Senha:",
"Register": "Registre-se",

View file

@ -702,7 +702,6 @@
"Invalid file%(extra)s": "Недопустимый файл%(extra)s",
"Invited": "Приглашен",
"Jump to first unread message.": "Перейти к первому непрочитанному сообщению.",
"List this room in %(domain)s's room directory?": "Показывать эту комнату в каталоге комнат %(domain)s?",
"Message not sent due to unknown devices being present": "Сообщение не отправлено из-за присутствия неизвестных устройств",
"Mobile phone number (optional)": "Номер мобильного телефона (не обязательно)",
"Once you&#39;ve followed the link it contains, click below": "После перехода по ссылке, нажмите на кнопку ниже",

View file

@ -385,7 +385,6 @@
"left": "lämnade",
"%(targetName)s left the room.": "%(targetName)s lämnade rummet.",
"Level:": "Nivå:",
"List this room in %(domain)s's room directory?": "Visa det här rummet i katalogen på %(domain)s?",
"Local addresses for this room:": "Lokala adresser för rummet:",
"Logged in as:": "Inloggad som:",
"Login as guest": "Logga in som gäst",

View file

@ -87,7 +87,7 @@
"a room": "ఓ గది",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "ఒక టెక్స్ట్ సందేశం +%(msisdn)s కు పంపబడింది. దయచేసి దీనిలో ఉన్న ధృవీకరణ కోడ్ను నమోదు చేయండి",
"Accept": "అంగీకరించు",
"%(targetName)s accepted an invitation.": "% (టర్గెట్పెరు) s ఆహ్వానాన్ని అంగీకరించింది.",
"%(targetName)s accepted an invitation.": "%(targetName)s ఆహ్వానాన్ని అంగీకరించింది.",
"Account": "ఖాతా",
"Access Token:": "యాక్సెస్ టోకెన్:",
"Add": "చేర్చు",
@ -113,7 +113,7 @@
"Alias (optional)": "అలియాస్ (ఇవచు ఇవకపపోవచు)",
"all room members": "అన్ని గదుల సభ్యులు",
"You do not have permission to post to this room": "మీకు ఈ గదికి పోస్ట్ చేయడానికి అనుమతి లేదు",
"You have been invited to join this room by %(inviterName)s": "% (InviterName) లు ఈ గదిలో చేరడానికి మీరు ఆహ్వానించబడ్డారు",
"You have been invited to join this room by %(inviterName)s": "%(inviterName)s ఈ గదిలో చేరడానికి మీరు ఆహ్వానించబడ్డారు",
"es-ec": "స్పానిష్ (ఈక్వెడార్)",
"es-gt": "స్పానిష్ (గ్వాటెమాల)",
"es-hn": "స్పానిష్ (హోండురాస్)",
@ -147,24 +147,24 @@
"it": "ఇటాలియన్",
"ja": "జపనీస్",
"ko": "కొరియన్",
"Active call (%(roomName)s)": "క్రియాశీల కాల్ల్ (% (రూంపెరు) స్)",
"And %(count)s more...": "మరియు% (మొత్తం)స్ ఇంకా ...",
"Active call (%(roomName)s)": "క్రియాశీల కాల్ల్ (%(roomName)s)",
"And %(count)s more...": "మరియు %(count)s ఇంకా ...",
"all room members, from the point they are invited": "అన్ని గది సభ్యులు, పాయింట్ నుండి వారు ఆహ్వానించబడ్డారు",
"all room members, from the point they joined": "అన్ని గది సభ్యులు, పాయింట్ నుండి వారు చేరారు",
"and": "మరియు",
"and one other...": "మరియు మరొకటి ...",
"%(names)s and one other are typing": "% (పేర్లు) లు మరియు మరొకటి టైప్ చేస్తున్నారు",
"%(names)s and %(count)s others are typing": "% (పేర్లు) లు మరియు% (లెక్క) లు ఇతరులు టైప్ చేస్తున్నారు",
"%(names)s and one other are typing": "%(names)s మరియు మరొకటి టైప్ చేస్తున్నారు",
"%(names)s and %(count)s others are typing": "%(names)s మరియు %(count)s ఇతరులు టైప్ చేస్తున్నారు",
"An email has been sent to": "ఒక ఇమెయిల్ పంపబడింది",
"A new password must be entered.": "కొత్త పాస్ వర్డ్ ను తప్పక నమోదు చేయాలి.",
"%(senderName)s answered the call.": "% (SenderName) s కు సమాధానం ఇచ్చారు.",
"%(senderName)s answered the call.": "%(senderName)s కు సమాధానం ఇచ్చారు.",
"anyone": "ఎవరైనా",
"An error has occurred.": "ఒక లోపము సంభవించినది.",
"Anyone": "ఎవరైనా",
"Anyone who knows the room's link, apart from guests": "అతిథులు కాకుండా గది యొక్క లింక్ తెలిసిన వారు ఎవరైనా",
"Anyone who knows the room's link, including guests": "అతిథులతో సహా, గది లింక్ తెలిసిన వారు ఎవరైనా",
"Are you sure?": "మీరు చెప్పేది నిజమా?",
"Are you sure you want to leave the room '%(roomName)s'?": "మీరు ఖచ్చితంగా గది '% (roomName) s' వదిలివేయాలనుకుంటున్నారా?",
"Are you sure you want to leave the room '%(roomName)s'?": "మీరు ఖచ్చితంగా గది '%(roomName)s' వదిలివేయాలనుకుంటున్నారా?",
"Are you sure you want to reject the invitation?": "మీరు ఖచ్చితంగా ఆహ్వానాన్ని తిరస్కరించాలనుకుంటున్నారా?",
"Are you sure you want to upload the following files?": "మీరు ఖచ్చితంగా ఈ క్రింది ఫైళ్ళను అప్లోడ్ చేయాలనుకుంటున్నారా?",
"Attachment": "జోడింపు",
@ -179,14 +179,14 @@
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "గృహనిర్వాహకులకు కనెక్ట్ చేయలేరు - దయచేసి మీ కనెక్టివిటీని తనిఖీ చేయండి, మీ <a> 1 హోమరుసు యొక్క ఎస్ఎస్ఎల్ సర్టిఫికేట్ </a> 2 ని విశ్వసనీయపరుచుకొని, బ్రౌజర్ పొడిగింపు అభ్యర్థనలను నిరోధించబడదని నిర్ధారించుకోండి.",
"Can't load user settings": "వినియోగదారు సెట్టింగ్లను లోడ్ చేయలేరు",
"Change Password": "పాస్వర్డ్ మార్చండి",
"%(senderName)s changed their profile picture.": "% (SenderName) వారి ప్రొఫైల్ చిత్రాన్ని మార్చారు.",
"%(senderDisplayName)s removed the room name.": "% (SenderDisplayName) s గది పేరు తొలగించబడింది.",
"%(senderName)s changed their profile picture.": "%(senderName)s వారి ప్రొఫైల్ చిత్రాన్ని మార్చారు.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s గది పేరు తొలగించబడింది.",
"Changes to who can read history will only apply to future messages in this room": "చరిత్ర చదివేవారికి మార్పులు ఈ గదిలో భవిష్య సందేశాలకు మాత్రమే వర్తిస్తాయి",
"Changes your display nickname": "మీ ప్రదర్శన మారుపేరుని మారుస్తుంది",
"changing room on a RoomView is not supported": "ఒక రూమ్వ్యూలో గది మార్చుకునేకి మద్దతు లేదు",
"You cannot place a call with yourself.": "మీరు మీతో కాల్ చేయలేరు.",
"You are already in a call.": "మీరు ఇప్పటికే కాల్లో ఉన్నారు.",
"You are trying to access %(roomName)s.": "మీరు% (గధిపేరు) లను యాక్సెస్ చేయడానికి ప్రయత్నిస్తున్నారు.",
"You are trying to access %(roomName)s.": "మీరు %(roomName)s లను యాక్సెస్ చేయడానికి ప్రయత్నిస్తున్నారు.",
"You cannot place VoIP calls in this browser.": "మీరు ఈ బ్రౌజర్లో VoIP కాల్లను ఉంచలేరు.",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "మీరు అన్ని పరికరాల నుండి లాగ్ అవుట్ అయ్యారు మరియు ఇకపై పుష్ ఉండదు.\nప్రకటనలను నోటిఫికేషన్లను పునఃప్రారంభించడానికి, ప్రతి పరికరంలో మళ్లీ సైన్ ఇన్ చేయండి",
"You have no visible notifications": "మీకు కనిపించే నోటిఫికేషన్లు లేవు",
@ -214,8 +214,8 @@
"Confirm your new password": "మీ క్రొత్త పాస్వర్డ్ను నిర్ధారించండి",
"Continue": "కొనసాగించు",
"Could not connect to the integration server": "ఇంటిగ్రేషన్ సర్వర్కు కనెక్ట్ చేయడం సాధ్యం కాలేదు",
"%(count)s new messages.one": "% (లెక్కింపు) కొత్త సందేశం",
"%(count)s new messages.other": "% (లెక్కింపు) కొత్త సందేశాలు",
"%(count)s new messages.one": "%(count)s కొత్త సందేశం",
"%(count)s new messages.other": "%(count)s కొత్త సందేశాలు",
"Create a new chat or reuse an existing one": "క్రొత్త చాట్ ను సృష్టించుకోండి లేదా ఇప్పటికే ఉన్న ఒకదాన్ని తిరిగి ఉపయోగించండి",
"Create an account": "ఒక ఎకౌంటు ను సృష్టించండి",
"Create Room": "రూమ్ ని సృష్టించండి",
@ -266,9 +266,9 @@
"Oct": "అక్టోబర్",
"Nov": "నవంబర్",
"Dec": "డిసంబర్",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(వారమురోజుపేరు) s,%(నెలపేరు)లు %(రోజులు)లు% (సమయం)లు",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(వారమురోజుపేరు)లు, %(నెలపేరు)లు %(రోజు)లు %(పూర్తిసంవత్సరం)లు %(సమయం)లు",
"%(weekDayName)s %(time)s": "%(వారమురోజుపేరు)లు %(సమయం)లు",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s ,%(monthName)s %(day)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
"Set a display name:": "ప్రదర్శన పేరుని సెట్ చేయండి:",
"Set a Display Name": "ప్రదర్శన పేరుని సెట్ చేయండి",
"Upload avatar": "అవతార్ను అప్లోడ్ చేయండి",
@ -277,7 +277,7 @@
"Missing password.": "పాస్వర్డ్ లేదు.",
"New passwords don't match": "కొత్త పాస్వర్డ్లు సరిపోలడం లేదు",
"Passwords don't match.": "పాస్వర్డ్లు సరిపోలడం లేదు.",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "పాస్వర్డ్ చాలా చిన్నగ ఉంది (min% (MIN_PASSWORD_LENGTH) s).",
"Password too short (min %(MIN_PASSWORD_LENGTH)s).": "పాస్వర్డ్ చాలా చిన్నగ ఉంది (min %(MIN_PASSWORD_LENGTH)s).",
"This doesn't look like a valid email address.": "ది చెల్లుబాటు అయ్యే ఇమెయిల్ చిరునామా లాగా లేదు.",
"This doesn't look like a valid phone number.": "ఇది చెల్లుబాటు అయ్యే ఫోన్ నంబర్ లాగా లేదు.",
"User names may only contain letters, numbers, dots, hyphens and underscores.": "వినియోగదారు పేర్లు అక్షరాలు, సంఖ్యలు, చుక్కలు, హైపన్లు మరియు అండర్ స్కోర్లను మాత్రమే కలిగి ఉండవచ్చు.",
@ -305,7 +305,7 @@
"strike": "సమ్మె",
"underline": "అండర్లైన్",
"Enter Code": "కోడ్ వ్రాయండి",
"Failed to forget room %(errCode)s": "గది %(errCode) లు మర్చిపోవడంలో విఫలమైంది",
"Failed to forget room %(errCode)s": "గది %(errCode)s మర్చిపోవడంలో విఫలమైంది",
"Incorrect verification code": "ధృవీకరణ కోడ్ సరిగా లెదు",
"unknown error code": "తెలియని కోడ్ లోపం",
"code": "కోడ్",

View file

@ -231,7 +231,6 @@
"left and rejoined": "ออกแล้วกลับเข้าร่วมอีกครั้ง",
"left": "ออกไปแล้ว",
"%(targetName)s left the room.": "%(targetName)s ออกจากห้องแล้ว",
"List this room in %(domain)s's room directory?": "แสดงห้องนี้ในไดเรกทอรีห้องของ %(domain)s?",
"Logged in as:": "เข้าสู่ระบบในชื่อ:",
"Login as guest": "เข้าสู่ระบบในฐานะแขก",
"Logout": "ออกจากระบบ",

View file

@ -380,7 +380,6 @@
"left": "ayrıldı",
"%(targetName)s left the room.": "%(targetName)s odadan ayrıldı.",
"Level:": "Seviye :",
"List this room in %(domain)s's room directory?": "Bu oda %(domain)s' in oda dizininde listelensin mi ?",
"Local addresses for this room:": "Bu oda için yerel adresler :",
"Logged in as:": "Olarak giriş yaptı :",
"Login as guest": "Misafir olarak giriş yaptı",

View file

@ -517,7 +517,6 @@
"left and rejoined": "離開並重新加入",
"left": "離開",
"Level:": "等級:",
"List this room in %(domain)s's room directory?": "在 %(domain)s 的房間目錄中列出此房間嗎?",
"Local addresses for this room:": "此房間的本機地址:",
"Logged in as:": "登入為:",
"Logout": "登出",

View file

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {getAddressType, inviteToRoom} from '../Invite';
import {getAddressType} from '../UserAddress';
import {inviteToRoom} from '../Invite';
import Promise from 'bluebird';
/**