mirror of https://github.com/vector-im/riot-web
Merge remote-tracking branch 'origin/develop' into develop
commit
729d3f7608
|
@ -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
|
||||
|
|
|
@ -26,7 +26,7 @@ are currently filed against vector-im/riot-web rather than this project).
|
|||
|
||||
Translation Status
|
||||
==================
|
||||
[](https://translate.nordgedanken.de/engage/riot-web/?utm_source=widget)
|
||||
[](https://translate.riot.im/engage/riot-web/?utm_source=widget)
|
||||
|
||||
Developer Guide
|
||||
===============
|
||||
|
|
134
src/Invite.js
134
src/Invite.js
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
keys: ['name'],
|
||||
});
|
||||
this.matcher = new FuzzyMatcher([], {
|
||||
keys: ['name'],
|
||||
keys: ['name', 'userId'],
|
||||
shouldMatchPrefix: true,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {},
|
||||
};
|
||||
|
|
|
@ -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}>β</span>
|
||||
{/* Edit widget */}
|
||||
{showEditButton && <img
|
||||
src="img/edit.svg"
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -171,7 +171,7 @@ const Pill = React.createClass({
|
|||
}
|
||||
pillClass = 'mx_UserPill';
|
||||
href = null;
|
||||
onClick = this.onUserPillClicked.bind(this);
|
||||
onClick = this.onUserPillClicked;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "Σύνδεση ως επισκέπτης",
|
||||
|
|
|
@ -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>"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -811,7 +811,6 @@
|
|||
"device id: ": "identifiant appareil : ",
|
||||
"Device key:": "Clé de l’appareil :",
|
||||
"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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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": "손님으로 로그인",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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've followed the link it contains, click below": "После перехода по ссылке, нажмите на кнопку ниже",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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": "కోడ్",
|
||||
|
|
|
@ -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": "ออกจากระบบ",
|
||||
|
|
|
@ -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ı",
|
||||
|
|
|
@ -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": "登出",
|
||||
|
|
|
@ -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';
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue