Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
729d3f7608
47 changed files with 581 additions and 488 deletions
|
@ -33,7 +33,6 @@ src/components/views/create_room/CreateRoomButton.js
|
||||||
src/components/views/create_room/Presets.js
|
src/components/views/create_room/Presets.js
|
||||||
src/components/views/create_room/RoomAlias.js
|
src/components/views/create_room/RoomAlias.js
|
||||||
src/components/views/dialogs/ChatCreateOrReuseDialog.js
|
src/components/views/dialogs/ChatCreateOrReuseDialog.js
|
||||||
src/components/views/dialogs/ChatInviteDialog.js
|
|
||||||
src/components/views/dialogs/DeactivateAccountDialog.js
|
src/components/views/dialogs/DeactivateAccountDialog.js
|
||||||
src/components/views/dialogs/InteractiveAuthDialog.js
|
src/components/views/dialogs/InteractiveAuthDialog.js
|
||||||
src/components/views/dialogs/SetMxIdDialog.js
|
src/components/views/dialogs/SetMxIdDialog.js
|
||||||
|
@ -114,7 +113,6 @@ src/components/views/settings/EnableNotificationsButton.js
|
||||||
src/ContentMessages.js
|
src/ContentMessages.js
|
||||||
src/HtmlUtils.js
|
src/HtmlUtils.js
|
||||||
src/ImageUtils.js
|
src/ImageUtils.js
|
||||||
src/Invite.js
|
|
||||||
src/languageHandler.js
|
src/languageHandler.js
|
||||||
src/linkify-matrix.js
|
src/linkify-matrix.js
|
||||||
src/Login.js
|
src/Login.js
|
||||||
|
|
|
@ -26,7 +26,7 @@ are currently filed against vector-im/riot-web rather than this project).
|
||||||
|
|
||||||
Translation Status
|
Translation Status
|
||||||
==================
|
==================
|
||||||
[](https://translate.nordgedanken.de/engage/riot-web/?utm_source=widget)
|
[](https://translate.riot.im/engage/riot-web/?utm_source=widget)
|
||||||
|
|
||||||
Developer Guide
|
Developer Guide
|
||||||
===============
|
===============
|
||||||
|
|
134
src/Invite.js
134
src/Invite.js
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 MatrixClientPeg from './MatrixClientPeg';
|
||||||
import MultiInviter from './utils/MultiInviter';
|
import MultiInviter from './utils/MultiInviter';
|
||||||
|
import Modal from './Modal';
|
||||||
const emailRegex = /^\S+@\S+\.\S+$/;
|
import { getAddressType } from './UserAddress';
|
||||||
|
import createRoom from './createRoom';
|
||||||
const mxidRegex = /^@\S+:\S+$/
|
import sdk from './';
|
||||||
|
import { _t } from './languageHandler';
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function inviteToRoom(roomId, addr) {
|
export function inviteToRoom(roomId, addr) {
|
||||||
const addrType = getAddressType(addr);
|
const addrType = getAddressType(addr);
|
||||||
|
@ -52,12 +40,116 @@ export function inviteToRoom(roomId, addr) {
|
||||||
* Simpler interface to utils/MultiInviter but with
|
* Simpler interface to utils/MultiInviter but with
|
||||||
* no option to cancel.
|
* no option to cancel.
|
||||||
*
|
*
|
||||||
* @param {roomId} The ID of the room to invite to
|
* @param {string} roomId The ID of the room to invite to
|
||||||
* @param {array} Array of strings of addresses to invite. May be matrix IDs or 3pids.
|
* @param {string[]} addrs Array of strings of addresses to invite. May be matrix IDs or 3pids.
|
||||||
* @returns Promise
|
* @returns {Promise} Promise
|
||||||
*/
|
*/
|
||||||
export function inviteMultipleToRoom(roomId, addrs) {
|
export function inviteMultipleToRoom(roomId, addrs) {
|
||||||
const inviter = new MultiInviter(roomId);
|
const inviter = new MultiInviter(roomId);
|
||||||
return inviter.invite(addrs);
|
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);
|
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
|
||||||
}
|
}
|
||||||
|
|
||||||
createTrackedDialogAsync(analyticsId, loader, props, className) {
|
createTrackedDialogAsync(analyticsAction, analyticsInfo, loader, props, className) {
|
||||||
Analytics.trackEvent('Modal', analyticsId);
|
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
|
||||||
return this.createDialogAsync(loader, props, className);
|
return this.createDialogAsync(loader, props, className);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,10 +76,13 @@ class ScalarAuthClient {
|
||||||
return defer.promise;
|
return defer.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
getScalarInterfaceUrlForRoom(roomId, screen) {
|
getScalarInterfaceUrlForRoom(roomId, screen, id) {
|
||||||
var url = SdkConfig.get().integrations_ui_url;
|
var url = SdkConfig.get().integrations_ui_url;
|
||||||
url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
|
url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
|
||||||
url += "&room_id=" + encodeURIComponent(roomId);
|
url += "&room_id=" + encodeURIComponent(roomId);
|
||||||
|
if (id) {
|
||||||
|
url += '&integ_id=' + encodeURIComponent(id);
|
||||||
|
}
|
||||||
if (screen) {
|
if (screen) {
|
||||||
url += '&screen=' + encodeURIComponent(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 = {
|
var handlers = {
|
||||||
'm.room.message': textForMessageEvent,
|
'm.room.message': textForMessageEvent,
|
||||||
'm.room.name': textForRoomNameEvent,
|
'm.room.name': textForRoomNameEvent,
|
||||||
|
@ -260,6 +283,8 @@ var handlers = {
|
||||||
'm.room.history_visibility': textForHistoryVisibilityEvent,
|
'm.room.history_visibility': textForHistoryVisibilityEvent,
|
||||||
'm.room.encryption': textForEncryptionEvent,
|
'm.room.encryption': textForEncryptionEvent,
|
||||||
'm.room.power_levels': textForPowerEvent,
|
'm.room.power_levels': textForPowerEvent,
|
||||||
|
|
||||||
|
'im.vector.modular.widgets': textForWidgetEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
54
src/UserAddress.js
Normal file
54
src/UserAddress.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ export default {
|
||||||
{
|
{
|
||||||
name: "-",
|
name: "-",
|
||||||
id: 'matrix_apps',
|
id: 'matrix_apps',
|
||||||
default: false,
|
default: true,
|
||||||
|
|
||||||
// XXX: Always use default, ignore localStorage and remove from labs
|
// XXX: Always use default, ignore localStorage and remove from labs
|
||||||
override: true,
|
override: true,
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
keys: ['name'],
|
keys: ['name'],
|
||||||
});
|
});
|
||||||
this.matcher = new FuzzyMatcher([], {
|
this.matcher = new FuzzyMatcher([], {
|
||||||
keys: ['name'],
|
keys: ['name', 'userId'],
|
||||||
shouldMatchPrefix: true,
|
shouldMatchPrefix: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 Modal from "../../Modal";
|
||||||
import Tinter from "../../Tinter";
|
import Tinter from "../../Tinter";
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
|
import { showStartChatInviteDialog, showRoomInviteDialog } from '../../Invite';
|
||||||
import * as Rooms from '../../Rooms';
|
import * as Rooms from '../../Rooms';
|
||||||
import linkifyMatrix from "../../linkify-matrix";
|
import linkifyMatrix from "../../linkify-matrix";
|
||||||
import * as Lifecycle from '../../Lifecycle';
|
import * as Lifecycle from '../../Lifecycle';
|
||||||
|
@ -512,7 +514,7 @@ module.exports = React.createClass({
|
||||||
this._createChat();
|
this._createChat();
|
||||||
break;
|
break;
|
||||||
case 'view_invite':
|
case 'view_invite':
|
||||||
this._invite(payload.roomId);
|
showRoomInviteDialog(payload.roomId);
|
||||||
break;
|
break;
|
||||||
case 'notifier_enabled':
|
case 'notifier_enabled':
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
@ -766,13 +768,7 @@ module.exports = React.createClass({
|
||||||
dis.dispatch({action: 'view_set_mxid'});
|
dis.dispatch({action: 'view_set_mxid'});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
showStartChatInviteDialog();
|
||||||
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"),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_createRoom: function() {
|
_createRoom: function() {
|
||||||
|
@ -857,17 +853,6 @@ module.exports = React.createClass({
|
||||||
}).close;
|
}).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) {
|
_leaveRoom: function(roomId) {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
|
|
@ -339,6 +339,15 @@ module.exports = React.createClass({
|
||||||
for (;i + 1 < this.props.events.length; i++) {
|
for (;i + 1 < this.props.events.length; i++) {
|
||||||
const collapsedMxEv = this.props.events[i + 1];
|
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) ||
|
if (!isMembershipChange(collapsedMxEv) ||
|
||||||
this._wantsDateSeparator(this.props.events[i], collapsedMxEv.getDate())) {
|
this._wantsDateSeparator(this.props.events[i], collapsedMxEv.getDate())) {
|
||||||
break;
|
break;
|
||||||
|
@ -349,11 +358,6 @@ module.exports = React.createClass({
|
||||||
readMarkerInMels = true;
|
readMarkerInMels = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore redacted/hidden member events
|
|
||||||
if (!this._shouldShowEvent(collapsedMxEv)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
summarisedEvents.push(collapsedMxEv);
|
summarisedEvents.push(collapsedMxEv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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 React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
|
|
||||||
import createRoom from '../../../createRoom';
|
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
|
||||||
import Modal from '../../../Modal';
|
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import dis from '../../../dispatcher';
|
import { addressTypes, getAddressType } from '../../../UserAddress.js';
|
||||||
|
|
||||||
const TRUNCATE_QUERY_LIST = 40;
|
const TRUNCATE_QUERY_LIST = 40;
|
||||||
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
|
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: "ChatInviteDialog",
|
displayName: "UserPickerDialog",
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
title: React.PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
description: React.PropTypes.oneOfType([
|
description: PropTypes.node,
|
||||||
React.PropTypes.element,
|
value: PropTypes.string,
|
||||||
React.PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
]),
|
roomId: PropTypes.string,
|
||||||
value: React.PropTypes.string,
|
button: PropTypes.string,
|
||||||
placeholder: React.PropTypes.string,
|
focus: PropTypes.bool,
|
||||||
roomId: React.PropTypes.string,
|
validAddressTypes: PropTypes.arrayOf(PropTypes.oneOf(addressTypes)),
|
||||||
button: React.PropTypes.string,
|
onFinished: PropTypes.func.isRequired,
|
||||||
focus: React.PropTypes.bool,
|
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
value: "",
|
value: "",
|
||||||
focus: true,
|
focus: true,
|
||||||
|
validAddressTypes: addressTypes,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -56,9 +54,9 @@ module.exports = React.createClass({
|
||||||
return {
|
return {
|
||||||
error: false,
|
error: false,
|
||||||
|
|
||||||
// List of AddressTile.InviteAddressType objects representing
|
// List of UserAddressType objects representing
|
||||||
// the list of addresses we're going to invite
|
// the list of addresses we're going to invite
|
||||||
inviteList: [],
|
userList: [],
|
||||||
|
|
||||||
// Whether a search is ongoing
|
// Whether a search is ongoing
|
||||||
busy: false,
|
busy: false,
|
||||||
|
@ -68,7 +66,7 @@ module.exports = React.createClass({
|
||||||
serverSupportsUserDirectory: true,
|
serverSupportsUserDirectory: true,
|
||||||
// The query being searched for
|
// The query being searched for
|
||||||
query: "",
|
query: "",
|
||||||
// List of AddressTile.InviteAddressType objects representing
|
// List of UserAddressType objects representing
|
||||||
// the set of auto-completion results for the current search
|
// the set of auto-completion results for the current search
|
||||||
// query.
|
// query.
|
||||||
queryList: [],
|
queryList: [],
|
||||||
|
@ -83,57 +81,14 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onButtonClick: function() {
|
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
|
// 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 !== '') {
|
if (this.refs.textinput.value !== '') {
|
||||||
inviteList = this._addInputToList();
|
userList = this._addInputToList();
|
||||||
if (inviteList === null) return;
|
if (userList === 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 });
|
|
||||||
}
|
}
|
||||||
|
this.props.onFinished(true, userList);
|
||||||
},
|
},
|
||||||
|
|
||||||
onCancel: function() {
|
onCancel: function() {
|
||||||
|
@ -157,10 +112,10 @@ module.exports = React.createClass({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.addressSelector) this.addressSelector.chooseSelection();
|
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.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.onDismissed(this.state.inviteList.length - 1)();
|
this.onDismissed(this.state.userList.length - 1)();
|
||||||
} else if (e.keyCode === 13) { // enter
|
} else if (e.keyCode === 13) { // enter
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -201,12 +156,11 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onDismissed: function(index) {
|
onDismissed: function(index) {
|
||||||
var self = this;
|
|
||||||
return () => {
|
return () => {
|
||||||
var inviteList = self.state.inviteList.slice();
|
const userList = this.state.userList.slice();
|
||||||
inviteList.splice(index, 1);
|
userList.splice(index, 1);
|
||||||
self.setState({
|
this.setState({
|
||||||
inviteList: inviteList,
|
userList: userList,
|
||||||
queryList: [],
|
queryList: [],
|
||||||
query: "",
|
query: "",
|
||||||
});
|
});
|
||||||
|
@ -215,17 +169,16 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function(index) {
|
onClick: function(index) {
|
||||||
var self = this;
|
return () => {
|
||||||
return function() {
|
this.onSelected(index);
|
||||||
self.onSelected(index);
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
onSelected: function(index) {
|
onSelected: function(index) {
|
||||||
var inviteList = this.state.inviteList.slice();
|
const userList = this.state.userList.slice();
|
||||||
inviteList.push(this.state.queryList[index]);
|
userList.push(this.state.queryList[index]);
|
||||||
this.setState({
|
this.setState({
|
||||||
inviteList: inviteList,
|
userList: userList,
|
||||||
queryList: [],
|
queryList: [],
|
||||||
query: "",
|
query: "",
|
||||||
});
|
});
|
||||||
|
@ -297,7 +250,7 @@ module.exports = React.createClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Return objects, structure of which is defined
|
// Return objects, structure of which is defined
|
||||||
// by InviteAddressType
|
// by UserAddressType
|
||||||
queryList.push({
|
queryList.push({
|
||||||
addressType: 'mx',
|
addressType: 'mx',
|
||||||
address: user.user_id,
|
address: user.user_id,
|
||||||
|
@ -311,7 +264,7 @@ module.exports = React.createClass({
|
||||||
// This is important, otherwise there's no way to invite
|
// This is important, otherwise there's no way to invite
|
||||||
// a perfectly valid address if there are close matches.
|
// a perfectly valid address if there are close matches.
|
||||||
const addrType = getAddressType(query);
|
const addrType = getAddressType(query);
|
||||||
if (addrType !== null) {
|
if (this.props.validAddressTypes.includes(addrType)) {
|
||||||
queryList.unshift({
|
queryList.unshift({
|
||||||
addressType: addrType,
|
addressType: addrType,
|
||||||
address: query,
|
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() {
|
_addInputToList: function() {
|
||||||
const addressText = this.refs.textinput.value.trim();
|
const addressText = this.refs.textinput.value.trim();
|
||||||
const addrType = getAddressType(addressText);
|
const addrType = getAddressType(addressText);
|
||||||
|
@ -476,15 +303,15 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const inviteList = this.state.inviteList.slice();
|
const userList = this.state.userList.slice();
|
||||||
inviteList.push(addrObj);
|
userList.push(addrObj);
|
||||||
this.setState({
|
this.setState({
|
||||||
inviteList: inviteList,
|
userList: userList,
|
||||||
queryList: [],
|
queryList: [],
|
||||||
query: "",
|
query: "",
|
||||||
});
|
});
|
||||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||||
return inviteList;
|
return userList;
|
||||||
},
|
},
|
||||||
|
|
||||||
_lookupThreepid: function(medium, address) {
|
_lookupThreepid: function(medium, address) {
|
||||||
|
@ -495,7 +322,7 @@ module.exports = React.createClass({
|
||||||
// not like they leak.
|
// not like they leak.
|
||||||
this._cancelThreepidLookup = function() {
|
this._cancelThreepidLookup = function() {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
// wait a bit to let the user finish typing
|
// wait a bit to let the user finish typing
|
||||||
return Promise.delay(500).then(() => {
|
return Promise.delay(500).then(() => {
|
||||||
|
@ -511,7 +338,7 @@ module.exports = React.createClass({
|
||||||
if (cancelled) return null;
|
if (cancelled) return null;
|
||||||
this.setState({
|
this.setState({
|
||||||
queryList: [{
|
queryList: [{
|
||||||
// an InviteAddressType
|
// a UserAddressType
|
||||||
addressType: medium,
|
addressType: medium,
|
||||||
address: address,
|
address: address,
|
||||||
displayName: res.displayname,
|
displayName: res.displayname,
|
||||||
|
@ -527,20 +354,20 @@ module.exports = React.createClass({
|
||||||
const AddressSelector = sdk.getComponent("elements.AddressSelector");
|
const AddressSelector = sdk.getComponent("elements.AddressSelector");
|
||||||
this.scrollElement = null;
|
this.scrollElement = null;
|
||||||
|
|
||||||
var query = [];
|
const query = [];
|
||||||
// create the invite list
|
// create the invite list
|
||||||
if (this.state.inviteList.length > 0) {
|
if (this.state.userList.length > 0) {
|
||||||
var AddressTile = sdk.getComponent("elements.AddressTile");
|
const AddressTile = sdk.getComponent("elements.AddressTile");
|
||||||
for (let i = 0; i < this.state.inviteList.length; i++) {
|
for (let i = 0; i < this.state.userList.length; i++) {
|
||||||
query.push(
|
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
|
// Add the query at the end
|
||||||
query.push(
|
query.push(
|
||||||
<textarea key={this.state.inviteList.length}
|
<textarea key={this.state.userList.length}
|
||||||
rows="1"
|
rows="1"
|
||||||
id="textinput"
|
id="textinput"
|
||||||
ref="textinput"
|
ref="textinput"
|
||||||
|
@ -555,7 +382,9 @@ module.exports = React.createClass({
|
||||||
let error;
|
let error;
|
||||||
let addressSelector;
|
let addressSelector;
|
||||||
if (this.state.error) {
|
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) {
|
} else if (this.state.searchError) {
|
||||||
error = <div className="mx_ChatInviteDialog_error">{this.state.searchError}</div>;
|
error = <div className="mx_ChatInviteDialog_error">{this.state.searchError}</div>;
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -598,5 +427,5 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
|
@ -20,7 +20,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { InviteAddressType } from './AddressTile';
|
import { UserAddressType } from '../../../UserAddress';
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'AddressSelector',
|
displayName: 'AddressSelector',
|
||||||
|
@ -29,7 +29,7 @@ export default React.createClass({
|
||||||
onSelected: React.PropTypes.func.isRequired,
|
onSelected: React.PropTypes.func.isRequired,
|
||||||
|
|
||||||
// List of the addresses to display
|
// List of the addresses to display
|
||||||
addressList: React.PropTypes.arrayOf(InviteAddressType).isRequired,
|
addressList: React.PropTypes.arrayOf(UserAddressType).isRequired,
|
||||||
truncateAt: React.PropTypes.number.isRequired,
|
truncateAt: React.PropTypes.number.isRequired,
|
||||||
selected: React.PropTypes.number,
|
selected: React.PropTypes.number,
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import sdk from "../../../index";
|
import sdk from "../../../index";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import { UserAddressType } from '../../../UserAddress.js';
|
||||||
// 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,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'AddressTile',
|
displayName: 'AddressTile',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
address: InviteAddressType.isRequired,
|
address: UserAddressType.isRequired,
|
||||||
canDismiss: React.PropTypes.bool,
|
canDismiss: React.PropTypes.bool,
|
||||||
onDismissed: React.PropTypes.func,
|
onDismissed: React.PropTypes.func,
|
||||||
justified: React.PropTypes.bool,
|
justified: React.PropTypes.bool,
|
||||||
|
|
|
@ -47,13 +47,19 @@ export default class AppPermission extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let e2eWarningText;
|
||||||
|
if (this.props.isRoomEncrypted) {
|
||||||
|
e2eWarningText =
|
||||||
|
<span className='mx_AppPermissionWarningTextLabel'>{_t('NOTE: Apps are not end-to-end encrypted')}</span>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className='mx_AppPermissionWarning'>
|
<div className='mx_AppPermissionWarning'>
|
||||||
<div className='mx_AppPermissionWarningImage'>
|
<div className='mx_AppPermissionWarningImage'>
|
||||||
<img src='img/warning.svg' alt={_t('Warning!')}/>
|
<img src='img/warning.svg' alt={_t('Warning!')}/>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_AppPermissionWarningText'>
|
<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>
|
</div>
|
||||||
<input
|
<input
|
||||||
className='mx_AppPermissionButton'
|
className='mx_AppPermissionButton'
|
||||||
|
@ -67,9 +73,11 @@ export default class AppPermission extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
AppPermission.propTypes = {
|
AppPermission.propTypes = {
|
||||||
|
isRoomEncrypted: PropTypes.bool,
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
onPermissionGranted: PropTypes.func.isRequired,
|
onPermissionGranted: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
AppPermission.defaultProps = {
|
AppPermission.defaultProps = {
|
||||||
|
isRoomEncrypted: false,
|
||||||
onPermissionGranted: function() {},
|
onPermissionGranted: function() {},
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,9 +28,9 @@ import AppPermission from './AppPermission';
|
||||||
import AppWarning from './AppWarning';
|
import AppWarning from './AppWarning';
|
||||||
import MessageSpinner from './MessageSpinner';
|
import MessageSpinner from './MessageSpinner';
|
||||||
import WidgetUtils from '../../../WidgetUtils';
|
import WidgetUtils from '../../../WidgetUtils';
|
||||||
|
import dis from '../../../dispatcher';
|
||||||
|
|
||||||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
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({
|
export default React.createClass({
|
||||||
displayName: 'AppTile',
|
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.
|
// 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.
|
// 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,
|
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() {
|
getDefaultProps: function() {
|
||||||
|
@ -59,7 +63,8 @@ export default React.createClass({
|
||||||
loading: false,
|
loading: false,
|
||||||
widgetUrl: this.props.url,
|
widgetUrl: this.props.url,
|
||||||
widgetPermissionId: widgetPermissionId,
|
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,
|
error: null,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
};
|
};
|
||||||
|
@ -122,7 +127,8 @@ export default React.createClass({
|
||||||
_onEditClick: function(e) {
|
_onEditClick: function(e) {
|
||||||
console.log("Edit widget ID ", this.props.id);
|
console.log("Edit widget ID ", this.props.id);
|
||||||
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
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, {
|
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
|
||||||
src: src,
|
src: src,
|
||||||
}, "mx_IntegrationsManager");
|
}, "mx_IntegrationsManager");
|
||||||
|
@ -177,11 +183,25 @@ export default React.createClass({
|
||||||
let appTileName = "No name";
|
let appTileName = "No name";
|
||||||
if(this.props.name && this.props.name.trim()) {
|
if(this.props.name && this.props.name.trim()) {
|
||||||
appTileName = this.props.name.trim();
|
appTileName = this.props.name.trim();
|
||||||
appTileName = appTileName[0].toUpperCase() + appTileName.slice(1).toLowerCase();
|
|
||||||
}
|
}
|
||||||
return appTileName;
|
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() {
|
render: function() {
|
||||||
let appTileBody;
|
let appTileBody;
|
||||||
|
|
||||||
|
@ -203,6 +223,7 @@ export default React.createClass({
|
||||||
safeWidgetUrl = url.format(parsedWidgetUrl);
|
safeWidgetUrl = url.format(parsedWidgetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.show) {
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
appTileBody = (
|
appTileBody = (
|
||||||
<div className='mx_AppTileBody mx_AppLoading'>
|
<div className='mx_AppTileBody mx_AppLoading'>
|
||||||
|
@ -231,15 +252,18 @@ export default React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||||
appTileBody = (
|
appTileBody = (
|
||||||
<div className="mx_AppTileBody">
|
<div className="mx_AppTileBody">
|
||||||
<AppPermission
|
<AppPermission
|
||||||
|
isRoomEncrypted={isRoomEncrypted}
|
||||||
url={this.state.widgetUrl}
|
url={this.state.widgetUrl}
|
||||||
onPermissionGranted={this._grantWidgetPermission}
|
onPermissionGranted={this._grantWidgetPermission}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// editing is done in scalar
|
// editing is done in scalar
|
||||||
const showEditButton = Boolean(this._scalarClient && this._canUserModify());
|
const showEditButton = Boolean(this._scalarClient && this._canUserModify());
|
||||||
|
@ -253,10 +277,9 @@ export default React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
|
<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()}
|
{this.formatAppTileName()}
|
||||||
<span className="mx_AppTileMenuBarWidgets">
|
<span className="mx_AppTileMenuBarWidgets">
|
||||||
<span className="mx_Beta" alt={betaHelpMsg} title={betaHelpMsg}>β</span>
|
|
||||||
{/* Edit widget */}
|
{/* Edit widget */}
|
||||||
{showEditButton && <img
|
{showEditButton && <img
|
||||||
src="img/edit.svg"
|
src="img/edit.svg"
|
||||||
|
|
117
src/components/views/elements/ManageIntegsButton.js
Normal file
117
src/components/views/elements/ManageIntegsButton.js
Normal 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,
|
||||||
|
};
|
|
@ -171,7 +171,7 @@ const Pill = React.createClass({
|
||||||
}
|
}
|
||||||
pillClass = 'mx_UserPill';
|
pillClass = 'mx_UserPill';
|
||||||
href = null;
|
href = null;
|
||||||
onClick = this.onUserPillClicked.bind(this);
|
onClick = this.onUserPillClicked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
|
|
||||||
var DIV_ID = 'mx_recaptcha';
|
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)
|
// * jumping straight to a hosted captcha page (but we don't support that yet)
|
||||||
// * embedding the captcha in an iframe (if that works)
|
// * embedding the captcha in an iframe (if that works)
|
||||||
// * using a better captcha lib
|
// * 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);
|
this.refs.recaptchaContainer.appendChild(warning);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -28,6 +28,8 @@ import ScalarMessaging from '../../../ScalarMessaging';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import WidgetUtils from '../../../WidgetUtils';
|
import WidgetUtils from '../../../WidgetUtils';
|
||||||
|
|
||||||
|
// The maximum number of widgets that can be added in a room
|
||||||
|
const MAX_WIDGETS = 2;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'AppsDrawer',
|
displayName: 'AppsDrawer',
|
||||||
|
@ -53,9 +55,6 @@ module.exports = React.createClass({
|
||||||
this.scalarClient = new ScalarAuthClient();
|
this.scalarClient = new ScalarAuthClient();
|
||||||
this.scalarClient.connect().done(() => {
|
this.scalarClient.connect().done(() => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
if (this.state.apps && this.state.apps.length < 1) {
|
|
||||||
this.onClickAddWidget();
|
|
||||||
}
|
|
||||||
// TODO -- Handle Scalar errors
|
// TODO -- Handle Scalar errors
|
||||||
// },
|
// },
|
||||||
// (err) => {
|
// (err) => {
|
||||||
|
@ -64,6 +63,8 @@ module.exports = React.createClass({
|
||||||
// });
|
// });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
|
@ -71,6 +72,27 @@ module.exports = React.createClass({
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
|
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;
|
return pathTemplate;
|
||||||
},
|
},
|
||||||
|
|
||||||
_initAppConfig: function(appId, app) {
|
_initAppConfig: function(appId, app, sender) {
|
||||||
const user = MatrixClientPeg.get().getUser(this.props.userId);
|
const user = MatrixClientPeg.get().getUser(this.props.userId);
|
||||||
const params = {
|
const params = {
|
||||||
'$matrix_user_id': this.props.userId,
|
'$matrix_user_id': this.props.userId,
|
||||||
|
@ -111,6 +133,7 @@ module.exports = React.createClass({
|
||||||
app.id = appId;
|
app.id = appId;
|
||||||
app.name = app.name || app.type;
|
app.name = app.name || app.type;
|
||||||
app.url = this.encodeUri(app.url, params);
|
app.url = this.encodeUri(app.url, params);
|
||||||
|
app.creatorUserId = (sender && sender.userId) ? sender.userId : null;
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
},
|
},
|
||||||
|
@ -131,18 +154,12 @@ module.exports = React.createClass({
|
||||||
return appsStateEvents.filter((ev) => {
|
return appsStateEvents.filter((ev) => {
|
||||||
return ev.getContent().type && ev.getContent().url;
|
return ev.getContent().type && ev.getContent().url;
|
||||||
}).map((ev) => {
|
}).map((ev) => {
|
||||||
return this._initAppConfig(ev.getStateKey(), ev.getContent());
|
return this._initAppConfig(ev.getStateKey(), ev.getContent(), ev.sender);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateApps: function() {
|
_updateApps: function() {
|
||||||
const apps = this._getApps();
|
const apps = this._getApps();
|
||||||
if (apps.length < 1) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'appsDrawer',
|
|
||||||
show: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
apps: apps,
|
apps: apps,
|
||||||
});
|
});
|
||||||
|
@ -157,11 +174,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onClickAddWidget: function(e) {
|
_launchManageIntegrations: function() {
|
||||||
if (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
||||||
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
|
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
|
||||||
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') :
|
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') :
|
||||||
|
@ -171,6 +184,23 @@ module.exports = React.createClass({
|
||||||
}, "mx_IntegrationsManager");
|
}, "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() {
|
render: function() {
|
||||||
const apps = this.state.apps.map(
|
const apps = this.state.apps.map(
|
||||||
(app, index, arr) => {
|
(app, index, arr) => {
|
||||||
|
@ -183,24 +213,34 @@ module.exports = React.createClass({
|
||||||
fullWidth={arr.length<2 ? true : false}
|
fullWidth={arr.length<2 ? true : false}
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
userId={this.props.userId}
|
userId={this.props.userId}
|
||||||
|
show={this.props.showApps}
|
||||||
|
creatorUserId={app.creatorUserId}
|
||||||
/>);
|
/>);
|
||||||
});
|
});
|
||||||
|
|
||||||
const addWidget = this.state.apps && this.state.apps.length < 2 && this._canUserModify() &&
|
let addWidget;
|
||||||
(<div onClick={this.onClickAddWidget}
|
if (this.props.showApps &&
|
||||||
|
this._canUserModify()
|
||||||
|
) {
|
||||||
|
addWidget = <div
|
||||||
|
onClick={this.onClickAddWidget}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
className="mx_AddWidget_button"
|
className={this.state.apps.length<2 ?
|
||||||
|
"mx_AddWidget_button mx_AddWidget_button_full_width" :
|
||||||
|
"mx_AddWidget_button"
|
||||||
|
}
|
||||||
title={_t('Add a widget')}>
|
title={_t('Add a widget')}>
|
||||||
[+] {_t('Add a widget')}
|
[+] {_t('Add a widget')}
|
||||||
</div>);
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_AppsDrawer">
|
<div className="mx_AppsDrawer">
|
||||||
<div id="apps" className="mx_AppsContainer">
|
<div id="apps" className="mx_AppsContainer">
|
||||||
{apps}
|
{apps}
|
||||||
</div>
|
</div>
|
||||||
{addWidget}
|
{this._canUserModify() && addWidget}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -129,11 +129,13 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
let appsDrawer = null;
|
let appsDrawer = null;
|
||||||
if(UserSettingsStore.isFeatureEnabled('matrix_apps') && this.props.showApps) {
|
if(UserSettingsStore.isFeatureEnabled('matrix_apps')) {
|
||||||
appsDrawer = <AppsDrawer ref="appsDrawer"
|
appsDrawer = <AppsDrawer ref="appsDrawer"
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
userId={this.props.userId}
|
userId={this.props.userId}
|
||||||
maxHeight={this.props.maxHeight}/>;
|
maxHeight={this.props.maxHeight}
|
||||||
|
showApps={this.props.showApps}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -44,6 +44,8 @@ var eventTileTypes = {
|
||||||
'm.room.history_visibility' : 'messages.TextualEvent',
|
'm.room.history_visibility' : 'messages.TextualEvent',
|
||||||
'm.room.encryption' : 'messages.TextualEvent',
|
'm.room.encryption' : 'messages.TextualEvent',
|
||||||
'm.room.power_levels' : 'messages.TextualEvent',
|
'm.room.power_levels' : 'messages.TextualEvent',
|
||||||
|
|
||||||
|
'im.vector.modular.widgets': 'messages.TextualEvent',
|
||||||
};
|
};
|
||||||
|
|
||||||
var MAX_READ_AVATARS = 5;
|
var MAX_READ_AVATARS = 5;
|
||||||
|
|
|
@ -289,12 +289,12 @@ export default class MessageComposer extends React.Component {
|
||||||
if (this.props.showApps) {
|
if (this.props.showApps) {
|
||||||
hideAppsButton =
|
hideAppsButton =
|
||||||
<div key="controls_hide_apps" className="mx_MessageComposer_apps" onClick={this.onHideAppsClick} title={_t("Hide Apps")}>
|
<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>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
showAppsButton =
|
showAppsButton =
|
||||||
<div key="show_apps" className="mx_MessageComposer_apps" onClick={this.onShowAppsClick} title={_t("Show Apps")}>
|
<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>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import * as linkify from 'linkifyjs';
|
||||||
import linkifyElement from 'linkifyjs/element';
|
import linkifyElement from 'linkifyjs/element';
|
||||||
import linkifyMatrix from '../../../linkify-matrix';
|
import linkifyMatrix from '../../../linkify-matrix';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import ManageIntegsButton from '../elements/ManageIntegsButton';
|
||||||
import {CancelButton} from './SimpleRoomHeader';
|
import {CancelButton} from './SimpleRoomHeader';
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
@ -47,6 +48,7 @@ module.exports = React.createClass({
|
||||||
onSaveClick: React.PropTypes.func,
|
onSaveClick: React.PropTypes.func,
|
||||||
onSearchClick: React.PropTypes.func,
|
onSearchClick: React.PropTypes.func,
|
||||||
onLeaveClick: React.PropTypes.func,
|
onLeaveClick: React.PropTypes.func,
|
||||||
|
onCancelClick: React.PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -54,6 +56,7 @@ module.exports = React.createClass({
|
||||||
editing: false,
|
editing: false,
|
||||||
inRoom: false,
|
inRoom: false,
|
||||||
onSaveClick: function() {},
|
onSaveClick: function() {},
|
||||||
|
onCancelClick: function() {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -320,10 +323,18 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
let rightRow;
|
let rightRow;
|
||||||
|
let manageIntegsButton;
|
||||||
|
if(this.props.room && this.props.room.roomId) {
|
||||||
|
manageIntegsButton = <ManageIntegsButton
|
||||||
|
roomId={this.props.room.roomId}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.props.editing) {
|
if (!this.props.editing) {
|
||||||
rightRow =
|
rightRow =
|
||||||
<div className="mx_RoomHeader_rightRow">
|
<div className="mx_RoomHeader_rightRow">
|
||||||
{ settingsButton }
|
{ settingsButton }
|
||||||
|
{ manageIntegsButton }
|
||||||
{ forgetButton }
|
{ forgetButton }
|
||||||
{ searchButton }
|
{ searchButton }
|
||||||
{ rightPanelButtons }
|
{ rightPanelButtons }
|
||||||
|
|
|
@ -24,8 +24,6 @@ import sdk from '../../../index';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import ObjectUtils from '../../../ObjectUtils';
|
import ObjectUtils from '../../../ObjectUtils';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import ScalarAuthClient from '../../../ScalarAuthClient';
|
|
||||||
import ScalarMessaging from '../../../ScalarMessaging';
|
|
||||||
import UserSettingsStore from '../../../UserSettingsStore';
|
import UserSettingsStore from '../../../UserSettingsStore';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
|
||||||
|
@ -92,7 +90,6 @@ module.exports = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
room: React.PropTypes.object.isRequired,
|
room: React.PropTypes.object.isRequired,
|
||||||
onSaveClick: React.PropTypes.func,
|
onSaveClick: React.PropTypes.func,
|
||||||
onCancelClick: React.PropTypes.func,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -118,14 +115,10 @@ module.exports = React.createClass({
|
||||||
// Default to false if it's undefined, otherwise react complains about changing
|
// Default to false if it's undefined, otherwise react complains about changing
|
||||||
// components from uncontrolled to controlled
|
// components from uncontrolled to controlled
|
||||||
isRoomPublished: this._originalIsRoomPublished || false,
|
isRoomPublished: this._originalIsRoomPublished || false,
|
||||||
scalar_error: null,
|
|
||||||
showIntegrationsError: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
ScalarMessaging.startListening();
|
|
||||||
|
|
||||||
MatrixClientPeg.get().on("RoomMember.membership", this._onRoomMemberMembership);
|
MatrixClientPeg.get().on("RoomMember.membership", this._onRoomMemberMembership);
|
||||||
|
|
||||||
MatrixClientPeg.get().getRoomDirectoryVisibility(
|
MatrixClientPeg.get().getRoomDirectoryVisibility(
|
||||||
|
@ -137,18 +130,6 @@ module.exports = React.createClass({
|
||||||
console.error("Failed to get room visibility: " + err);
|
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({
|
dis.dispatch({
|
||||||
action: 'ui_opacity',
|
action: 'ui_opacity',
|
||||||
sideOpacity: 0.3,
|
sideOpacity: 0.3,
|
||||||
|
@ -157,8 +138,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
ScalarMessaging.stopListening();
|
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
cli.removeListener("RoomMember.membership", this._onRoomMemberMembership);
|
cli.removeListener("RoomMember.membership", this._onRoomMemberMembership);
|
||||||
|
@ -513,28 +492,6 @@ module.exports = React.createClass({
|
||||||
roomState.mayClientSendStateEvent("m.room.guest_access", cli));
|
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() {
|
onLeaveClick() {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'leave_room',
|
action: 'leave_room',
|
||||||
|
@ -796,46 +753,10 @@ module.exports = React.createClass({
|
||||||
</div>;
|
</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 (
|
return (
|
||||||
<div className="mx_RoomSettings">
|
<div className="mx_RoomSettings">
|
||||||
|
|
||||||
{ leaveButton }
|
{ leaveButton }
|
||||||
{ integrationsButton }
|
|
||||||
|
|
||||||
{ tagsSection }
|
{ tagsSection }
|
||||||
|
|
||||||
|
@ -871,7 +792,7 @@ module.exports = React.createClass({
|
||||||
<input type="checkbox" disabled={ !roomState.mayClientSendStateEvent("m.room.aliases", cli) }
|
<input type="checkbox" disabled={ !roomState.mayClientSendStateEvent("m.room.aliases", cli) }
|
||||||
onChange={ this._onToggle.bind(this, "isRoomPublished", true, false)}
|
onChange={ this._onToggle.bind(this, "isRoomPublished", true, false)}
|
||||||
checked={this.state.isRoomPublished}/>
|
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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_settings">
|
<div className="mx_RoomSettings_settings">
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default class DevicesPanelEntry extends React.Component {
|
||||||
// pop up an interactive auth dialog
|
// pop up an interactive auth dialog
|
||||||
var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||||
|
|
||||||
Modal.createTrackedDialog('Delete Device Dialog', InteractiveAuthDialog, {
|
Modal.createTrackedDialog('Delete Device Dialog', '', InteractiveAuthDialog, {
|
||||||
title: _t("Authentication"),
|
title: _t("Authentication"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: error.data,
|
authData: error.data,
|
||||||
|
|
|
@ -851,7 +851,7 @@
|
||||||
"device id: ": "Geräte-ID: ",
|
"device id: ": "Geräte-ID: ",
|
||||||
"Device key:": "Geräte-Schlüssel:",
|
"Device key:": "Geräte-Schlüssel:",
|
||||||
"Email address (optional)": "E-Mail-Adresse (optional)",
|
"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)",
|
"Mobile phone number (optional)": "Mobilfunknummer (optional)",
|
||||||
"Password:": "Passwort:",
|
"Password:": "Passwort:",
|
||||||
"Register": "Registrieren",
|
"Register": "Registrieren",
|
||||||
|
|
|
@ -297,7 +297,6 @@
|
||||||
"left": "έφυγε",
|
"left": "έφυγε",
|
||||||
"%(targetName)s left the room.": "Ο χρήστης %(targetName)s έφυγε από το δωμάτιο.",
|
"%(targetName)s left the room.": "Ο χρήστης %(targetName)s έφυγε από το δωμάτιο.",
|
||||||
"Level": "Επίπεδο",
|
"Level": "Επίπεδο",
|
||||||
"List this room in %(domain)s's room directory?": "Να εμφανίζεται το δωμάτιο στο γενικό ευρετήριο του διακομιστή %(domain)s;",
|
|
||||||
"Local addresses for this room:": "Τοπική διεύθυνση για το δωμάτιο:",
|
"Local addresses for this room:": "Τοπική διεύθυνση για το δωμάτιο:",
|
||||||
"Logged in as:": "Συνδεθήκατε ως:",
|
"Logged in as:": "Συνδεθήκατε ως:",
|
||||||
"Login as guest": "Σύνδεση ως επισκέπτης",
|
"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 - 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 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",
|
"Can't load user settings": "Can't load user settings",
|
||||||
|
"Cannot add any more widgets": "Cannot add any more widgets",
|
||||||
"Change Password": "Change Password",
|
"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 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.",
|
"%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.",
|
||||||
|
@ -263,6 +264,7 @@
|
||||||
"Disinvite": "Disinvite",
|
"Disinvite": "Disinvite",
|
||||||
"Display name": "Display name",
|
"Display name": "Display name",
|
||||||
"Displays action": "Displays action",
|
"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",
|
"Don't send typing notifications": "Don't send typing notifications",
|
||||||
"Download %(text)s": "Download %(text)s",
|
"Download %(text)s": "Download %(text)s",
|
||||||
"Drop File Here": "Drop File Here",
|
"Drop File Here": "Drop File Here",
|
||||||
|
@ -361,6 +363,7 @@
|
||||||
"Incoming voice call from %(name)s": "Incoming voice call from %(name)s",
|
"Incoming voice call from %(name)s": "Incoming voice call from %(name)s",
|
||||||
"Incorrect username and/or password.": "Incorrect username and/or password.",
|
"Incorrect username and/or password.": "Incorrect username and/or password.",
|
||||||
"Incorrect verification code": "Incorrect verification code",
|
"Incorrect verification code": "Incorrect verification code",
|
||||||
|
"Integrations Error": "Integrations Error",
|
||||||
"Interface Language": "Interface Language",
|
"Interface Language": "Interface Language",
|
||||||
"Invalid alias format": "Invalid alias format",
|
"Invalid alias format": "Invalid alias format",
|
||||||
"Invalid address format": "Invalid address format",
|
"Invalid address format": "Invalid address format",
|
||||||
|
@ -392,7 +395,7 @@
|
||||||
"left": "left",
|
"left": "left",
|
||||||
"%(targetName)s left the room.": "%(targetName)s left the room.",
|
"%(targetName)s left the room.": "%(targetName)s left the room.",
|
||||||
"Level:": "Level:",
|
"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:",
|
"Local addresses for this room:": "Local addresses for this room:",
|
||||||
"Logged in as:": "Logged in as:",
|
"Logged in as:": "Logged in as:",
|
||||||
"Login as guest": "Login as guest",
|
"Login as guest": "Login as guest",
|
||||||
|
@ -432,6 +435,7 @@
|
||||||
"AM": "AM",
|
"AM": "AM",
|
||||||
"PM": "PM",
|
"PM": "PM",
|
||||||
"NOT verified": "NOT verified",
|
"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 devices with registered encryption keys": "No devices with registered encryption keys",
|
||||||
"No display name": "No display name",
|
"No display name": "No display name",
|
||||||
"No more results": "No more results",
|
"No more results": "No more results",
|
||||||
|
@ -547,6 +551,7 @@
|
||||||
"Tagged as: ": "Tagged as: ",
|
"Tagged as: ": "Tagged as: ",
|
||||||
"The default role for new room members is": "The default role for new room members is",
|
"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 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 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.",
|
"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.",
|
"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",
|
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
|
||||||
"Failed to upload image": "Failed to upload image",
|
"Failed to upload image": "Failed to upload image",
|
||||||
"Failed to update group": "Failed to update group",
|
"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",
|
"left": "left",
|
||||||
"%(targetName)s left the room.": "%(targetName)s left the room.",
|
"%(targetName)s left the room.": "%(targetName)s left the room.",
|
||||||
"Level": "Level",
|
"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:",
|
"Local addresses for this room:": "Local addresses for this room:",
|
||||||
"Logged in as:": "Logged in as:",
|
"Logged in as:": "Logged in as:",
|
||||||
"Login as guest": "Login as guest",
|
"Login as guest": "Login as guest",
|
||||||
|
|
|
@ -498,7 +498,6 @@
|
||||||
"Drop File Here": "Deje el fichero aquí",
|
"Drop File Here": "Deje el fichero aquí",
|
||||||
"Guest access is disabled on this Home Server.": "El acceso de invitados está desactivado en este servidor.",
|
"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>.",
|
"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",
|
"Manage Integrations": "Gestionar integraciones",
|
||||||
"Markdown is disabled": "Markdown está desactivado",
|
"Markdown is disabled": "Markdown está desactivado",
|
||||||
"Markdown is enabled": "Markdown está activado",
|
"Markdown is enabled": "Markdown está activado",
|
||||||
|
|
|
@ -464,7 +464,6 @@
|
||||||
"left": "atera da",
|
"left": "atera da",
|
||||||
"%(targetName)s left the room.": "%(targetName)s erabiltzailea gelatik atera da.",
|
"%(targetName)s left the room.": "%(targetName)s erabiltzailea gelatik atera da.",
|
||||||
"Level:": "Maila:",
|
"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:",
|
"Local addresses for this room:": "Gela honen tokiko helbideak:",
|
||||||
"Logged in as:": "Saioa hasteko erabiltzailea:",
|
"Logged in as:": "Saioa hasteko erabiltzailea:",
|
||||||
"Login as guest": "Hasi saioa bisitari gisa",
|
"Login as guest": "Hasi saioa bisitari gisa",
|
||||||
|
|
|
@ -811,7 +811,6 @@
|
||||||
"device id: ": "identifiant appareil : ",
|
"device id: ": "identifiant appareil : ",
|
||||||
"Device key:": "Clé de l’appareil :",
|
"Device key:": "Clé de l’appareil :",
|
||||||
"Email address (optional)": "Adresse e-mail (facultatif)",
|
"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)",
|
"Mobile phone number (optional)": "Numéro de téléphone (facultatif)",
|
||||||
"Password:": "Mot de passe :",
|
"Password:": "Mot de passe :",
|
||||||
"Register": "S'inscrire",
|
"Register": "S'inscrire",
|
||||||
|
|
|
@ -402,7 +402,6 @@
|
||||||
"left": "kilépett",
|
"left": "kilépett",
|
||||||
"%(targetName)s left the room.": "%(targetName)s elhagyta a szobát.",
|
"%(targetName)s left the room.": "%(targetName)s elhagyta a szobát.",
|
||||||
"Level:": "Szint:",
|
"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:",
|
"Local addresses for this room:": "A szoba helyi címe:",
|
||||||
"Logged in as:": "Bejelentkezve mint:",
|
"Logged in as:": "Bejelentkezve mint:",
|
||||||
"Login as guest": "Belépés vendégként",
|
"Login as guest": "Belépés vendégként",
|
||||||
|
|
|
@ -48,7 +48,6 @@
|
||||||
"%(count)s new messages.other": "新しい発言 %(count)s",
|
"%(count)s new messages.other": "新しい発言 %(count)s",
|
||||||
"Don't send typing notifications": "文字入力中であることを公表しない",
|
"Don't send typing notifications": "文字入力中であることを公表しない",
|
||||||
"Filter room members": "参加者検索",
|
"Filter room members": "参加者検索",
|
||||||
"List this room in %(domain)s's room directory?": "この部屋を %(domain)s サーバの部屋一覧に公開する?",
|
|
||||||
"Send a message (unencrypted)": "ここに送信文を入力 (暗号化なし)",
|
"Send a message (unencrypted)": "ここに送信文を入力 (暗号化なし)",
|
||||||
"Send an encrypted message": "暗号文を送る",
|
"Send an encrypted message": "暗号文を送る",
|
||||||
"Show timestamps in 12 hour format (e.g. 2:30pm)": "発言時刻を12時間形式で表示 (例 2:30PM)",
|
"Show timestamps in 12 hour format (e.g. 2:30pm)": "発言時刻を12時間形式で表示 (例 2:30PM)",
|
||||||
|
|
|
@ -405,7 +405,6 @@
|
||||||
"left": "떠났음",
|
"left": "떠났음",
|
||||||
"%(targetName)s left the room.": "%(targetName)s님이 방을 떠나셨어요.",
|
"%(targetName)s left the room.": "%(targetName)s님이 방을 떠나셨어요.",
|
||||||
"Level:": "등급:",
|
"Level:": "등급:",
|
||||||
"List this room in %(domain)s's room directory?": "%(domain)s's 방 목록에 이 방을 놓으시겠어요?",
|
|
||||||
"Local addresses for this room:": "이 방의 로컬 주소:",
|
"Local addresses for this room:": "이 방의 로컬 주소:",
|
||||||
"Logged in as:": "로그인:",
|
"Logged in as:": "로그인:",
|
||||||
"Login as guest": "손님으로 로그인",
|
"Login as guest": "손님으로 로그인",
|
||||||
|
|
|
@ -380,7 +380,6 @@
|
||||||
"left": "atstāja",
|
"left": "atstāja",
|
||||||
"%(targetName)s left the room.": "%(targetName)s atstāja istabu.",
|
"%(targetName)s left the room.": "%(targetName)s atstāja istabu.",
|
||||||
"Level:": "Līmenis:",
|
"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:",
|
"Local addresses for this room:": "Šīs istabas lokālās adreses:",
|
||||||
"Logged in as:": "Pierakstījās kā:",
|
"Logged in as:": "Pierakstījās kā:",
|
||||||
"Login as guest": "Pierakstīties kā viesis",
|
"Login as guest": "Pierakstīties kā viesis",
|
||||||
|
|
|
@ -480,7 +480,6 @@
|
||||||
"left": "verlaten",
|
"left": "verlaten",
|
||||||
"%(targetName)s left the room.": "%(targetName)s heeft de ruimte verlaten.",
|
"%(targetName)s left the room.": "%(targetName)s heeft de ruimte verlaten.",
|
||||||
"Level:": "Niveau:",
|
"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:",
|
"Local addresses for this room:": "Lokale adressen voor deze ruimte:",
|
||||||
"Logged in as:": "Ingelogd als:",
|
"Logged in as:": "Ingelogd als:",
|
||||||
"Login as guest": "Als gast inloggen",
|
"Login as guest": "Als gast inloggen",
|
||||||
|
|
|
@ -862,7 +862,6 @@
|
||||||
"device id: ": "id do dispositivo: ",
|
"device id: ": "id do dispositivo: ",
|
||||||
"Device key:": "Chave do dispositivo:",
|
"Device key:": "Chave do dispositivo:",
|
||||||
"Email address (optional)": "Endereço de e-mail (opcional)",
|
"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)",
|
"Mobile phone number (optional)": "Número de telefone celular (opcional)",
|
||||||
"Password:": "Senha:",
|
"Password:": "Senha:",
|
||||||
"Register": "Registre-se",
|
"Register": "Registre-se",
|
||||||
|
|
|
@ -863,7 +863,6 @@
|
||||||
"device id: ": "id do dispositivo: ",
|
"device id: ": "id do dispositivo: ",
|
||||||
"Device key:": "Chave do dispositivo:",
|
"Device key:": "Chave do dispositivo:",
|
||||||
"Email address (optional)": "Endereço de e-mail (opcional)",
|
"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)",
|
"Mobile phone number (optional)": "Número de telefone celular (opcional)",
|
||||||
"Password:": "Senha:",
|
"Password:": "Senha:",
|
||||||
"Register": "Registre-se",
|
"Register": "Registre-se",
|
||||||
|
|
|
@ -702,7 +702,6 @@
|
||||||
"Invalid file%(extra)s": "Недопустимый файл%(extra)s",
|
"Invalid file%(extra)s": "Недопустимый файл%(extra)s",
|
||||||
"Invited": "Приглашен",
|
"Invited": "Приглашен",
|
||||||
"Jump to first unread message.": "Перейти к первому непрочитанному сообщению.",
|
"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": "Сообщение не отправлено из-за присутствия неизвестных устройств",
|
"Message not sent due to unknown devices being present": "Сообщение не отправлено из-за присутствия неизвестных устройств",
|
||||||
"Mobile phone number (optional)": "Номер мобильного телефона (не обязательно)",
|
"Mobile phone number (optional)": "Номер мобильного телефона (не обязательно)",
|
||||||
"Once you've followed the link it contains, click below": "После перехода по ссылке, нажмите на кнопку ниже",
|
"Once you've followed the link it contains, click below": "После перехода по ссылке, нажмите на кнопку ниже",
|
||||||
|
|
|
@ -385,7 +385,6 @@
|
||||||
"left": "lämnade",
|
"left": "lämnade",
|
||||||
"%(targetName)s left the room.": "%(targetName)s lämnade rummet.",
|
"%(targetName)s left the room.": "%(targetName)s lämnade rummet.",
|
||||||
"Level:": "Nivå:",
|
"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:",
|
"Local addresses for this room:": "Lokala adresser för rummet:",
|
||||||
"Logged in as:": "Inloggad som:",
|
"Logged in as:": "Inloggad som:",
|
||||||
"Login as guest": "Logga in som gäst",
|
"Login as guest": "Logga in som gäst",
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
"a room": "ఓ గది",
|
"a room": "ఓ గది",
|
||||||
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "ఒక టెక్స్ట్ సందేశం +%(msisdn)s కు పంపబడింది. దయచేసి దీనిలో ఉన్న ధృవీకరణ కోడ్ను నమోదు చేయండి",
|
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "ఒక టెక్స్ట్ సందేశం +%(msisdn)s కు పంపబడింది. దయచేసి దీనిలో ఉన్న ధృవీకరణ కోడ్ను నమోదు చేయండి",
|
||||||
"Accept": "అంగీకరించు",
|
"Accept": "అంగీకరించు",
|
||||||
"%(targetName)s accepted an invitation.": "% (టర్గెట్పెరు) s ఆహ్వానాన్ని అంగీకరించింది.",
|
"%(targetName)s accepted an invitation.": "%(targetName)s ఆహ్వానాన్ని అంగీకరించింది.",
|
||||||
"Account": "ఖాతా",
|
"Account": "ఖాతా",
|
||||||
"Access Token:": "యాక్సెస్ టోకెన్:",
|
"Access Token:": "యాక్సెస్ టోకెన్:",
|
||||||
"Add": "చేర్చు",
|
"Add": "చేర్చు",
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
"Alias (optional)": "అలియాస్ (ఇవచు ఇవకపపోవచు)",
|
"Alias (optional)": "అలియాస్ (ఇవచు ఇవకపపోవచు)",
|
||||||
"all room members": "అన్ని గదుల సభ్యులు",
|
"all room members": "అన్ని గదుల సభ్యులు",
|
||||||
"You do not have permission to post to this room": "మీకు ఈ గదికి పోస్ట్ చేయడానికి అనుమతి లేదు",
|
"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-ec": "స్పానిష్ (ఈక్వెడార్)",
|
||||||
"es-gt": "స్పానిష్ (గ్వాటెమాల)",
|
"es-gt": "స్పానిష్ (గ్వాటెమాల)",
|
||||||
"es-hn": "స్పానిష్ (హోండురాస్)",
|
"es-hn": "స్పానిష్ (హోండురాస్)",
|
||||||
|
@ -147,17 +147,17 @@
|
||||||
"it": "ఇటాలియన్",
|
"it": "ఇటాలియన్",
|
||||||
"ja": "జపనీస్",
|
"ja": "జపనీస్",
|
||||||
"ko": "కొరియన్",
|
"ko": "కొరియన్",
|
||||||
"Active call (%(roomName)s)": "క్రియాశీల కాల్ల్ (% (రూంపెరు) స్)",
|
"Active call (%(roomName)s)": "క్రియాశీల కాల్ల్ (%(roomName)s)",
|
||||||
"And %(count)s more...": "మరియు% (మొత్తం)స్ ఇంకా ...",
|
"And %(count)s more...": "మరియు %(count)s ఇంకా ...",
|
||||||
"all room members, from the point they are invited": "అన్ని గది సభ్యులు, పాయింట్ నుండి వారు ఆహ్వానించబడ్డారు",
|
"all room members, from the point they are invited": "అన్ని గది సభ్యులు, పాయింట్ నుండి వారు ఆహ్వానించబడ్డారు",
|
||||||
"all room members, from the point they joined": "అన్ని గది సభ్యులు, పాయింట్ నుండి వారు చేరారు",
|
"all room members, from the point they joined": "అన్ని గది సభ్యులు, పాయింట్ నుండి వారు చేరారు",
|
||||||
"and": "మరియు",
|
"and": "మరియు",
|
||||||
"and one other...": "మరియు మరొకటి ...",
|
"and one other...": "మరియు మరొకటి ...",
|
||||||
"%(names)s and one other are typing": "% (పేర్లు) లు మరియు మరొకటి టైప్ చేస్తున్నారు",
|
"%(names)s and one other are typing": "%(names)s మరియు మరొకటి టైప్ చేస్తున్నారు",
|
||||||
"%(names)s and %(count)s others are typing": "% (పేర్లు) లు మరియు% (లెక్క) లు ఇతరులు టైప్ చేస్తున్నారు",
|
"%(names)s and %(count)s others are typing": "%(names)s మరియు %(count)s ఇతరులు టైప్ చేస్తున్నారు",
|
||||||
"An email has been sent to": "ఒక ఇమెయిల్ పంపబడింది",
|
"An email has been sent to": "ఒక ఇమెయిల్ పంపబడింది",
|
||||||
"A new password must be entered.": "కొత్త పాస్ వర్డ్ ను తప్పక నమోదు చేయాలి.",
|
"A new password must be entered.": "కొత్త పాస్ వర్డ్ ను తప్పక నమోదు చేయాలి.",
|
||||||
"%(senderName)s answered the call.": "% (SenderName) s కు సమాధానం ఇచ్చారు.",
|
"%(senderName)s answered the call.": "%(senderName)s కు సమాధానం ఇచ్చారు.",
|
||||||
"anyone": "ఎవరైనా",
|
"anyone": "ఎవరైనా",
|
||||||
"An error has occurred.": "ఒక లోపము సంభవించినది.",
|
"An error has occurred.": "ఒక లోపము సంభవించినది.",
|
||||||
"Anyone": "ఎవరైనా",
|
"Anyone": "ఎవరైనా",
|
||||||
|
@ -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 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": "వినియోగదారు సెట్టింగ్లను లోడ్ చేయలేరు",
|
"Can't load user settings": "వినియోగదారు సెట్టింగ్లను లోడ్ చేయలేరు",
|
||||||
"Change Password": "పాస్వర్డ్ మార్చండి",
|
"Change Password": "పాస్వర్డ్ మార్చండి",
|
||||||
"%(senderName)s changed their profile picture.": "% (SenderName) వారి ప్రొఫైల్ చిత్రాన్ని మార్చారు.",
|
"%(senderName)s changed their profile picture.": "%(senderName)s వారి ప్రొఫైల్ చిత్రాన్ని మార్చారు.",
|
||||||
"%(senderDisplayName)s removed the room name.": "% (SenderDisplayName) 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 to who can read history will only apply to future messages in this room": "చరిత్ర చదివేవారికి మార్పులు ఈ గదిలో భవిష్య సందేశాలకు మాత్రమే వర్తిస్తాయి",
|
||||||
"Changes your display nickname": "మీ ప్రదర్శన మారుపేరుని మారుస్తుంది",
|
"Changes your display nickname": "మీ ప్రదర్శన మారుపేరుని మారుస్తుంది",
|
||||||
"changing room on a RoomView is not supported": "ఒక రూమ్వ్యూలో గది మార్చుకునేకి మద్దతు లేదు",
|
"changing room on a RoomView is not supported": "ఒక రూమ్వ్యూలో గది మార్చుకునేకి మద్దతు లేదు",
|
||||||
"You cannot place a call with yourself.": "మీరు మీతో కాల్ చేయలేరు.",
|
"You cannot place a call with yourself.": "మీరు మీతో కాల్ చేయలేరు.",
|
||||||
"You are already in a call.": "మీరు ఇప్పటికే కాల్లో ఉన్నారు.",
|
"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 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 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": "మీకు కనిపించే నోటిఫికేషన్లు లేవు",
|
"You have no visible notifications": "మీకు కనిపించే నోటిఫికేషన్లు లేవు",
|
||||||
|
@ -214,8 +214,8 @@
|
||||||
"Confirm your new password": "మీ క్రొత్త పాస్వర్డ్ను నిర్ధారించండి",
|
"Confirm your new password": "మీ క్రొత్త పాస్వర్డ్ను నిర్ధారించండి",
|
||||||
"Continue": "కొనసాగించు",
|
"Continue": "కొనసాగించు",
|
||||||
"Could not connect to the integration server": "ఇంటిగ్రేషన్ సర్వర్కు కనెక్ట్ చేయడం సాధ్యం కాలేదు",
|
"Could not connect to the integration server": "ఇంటిగ్రేషన్ సర్వర్కు కనెక్ట్ చేయడం సాధ్యం కాలేదు",
|
||||||
"%(count)s new messages.one": "% (లెక్కింపు) కొత్త సందేశం",
|
"%(count)s new messages.one": "%(count)s కొత్త సందేశం",
|
||||||
"%(count)s new messages.other": "% (లెక్కింపు) కొత్త సందేశాలు",
|
"%(count)s new messages.other": "%(count)s కొత్త సందేశాలు",
|
||||||
"Create a new chat or reuse an existing one": "క్రొత్త చాట్ ను సృష్టించుకోండి లేదా ఇప్పటికే ఉన్న ఒకదాన్ని తిరిగి ఉపయోగించండి",
|
"Create a new chat or reuse an existing one": "క్రొత్త చాట్ ను సృష్టించుకోండి లేదా ఇప్పటికే ఉన్న ఒకదాన్ని తిరిగి ఉపయోగించండి",
|
||||||
"Create an account": "ఒక ఎకౌంటు ను సృష్టించండి",
|
"Create an account": "ఒక ఎకౌంటు ను సృష్టించండి",
|
||||||
"Create Room": "రూమ్ ని సృష్టించండి",
|
"Create Room": "రూమ్ ని సృష్టించండి",
|
||||||
|
@ -266,9 +266,9 @@
|
||||||
"Oct": "అక్టోబర్",
|
"Oct": "అక్టోబర్",
|
||||||
"Nov": "నవంబర్",
|
"Nov": "నవంబర్",
|
||||||
"Dec": "డిసంబర్",
|
"Dec": "డిసంబర్",
|
||||||
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(వారమురోజుపేరు) 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, %(monthName)s %(day)s %(fullYear)s %(time)s",
|
||||||
"%(weekDayName)s %(time)s": "%(వారమురోజుపేరు)లు %(సమయం)లు",
|
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
|
||||||
"Set a display name:": "ప్రదర్శన పేరుని సెట్ చేయండి:",
|
"Set a display name:": "ప్రదర్శన పేరుని సెట్ చేయండి:",
|
||||||
"Set a Display Name": "ప్రదర్శన పేరుని సెట్ చేయండి",
|
"Set a Display Name": "ప్రదర్శన పేరుని సెట్ చేయండి",
|
||||||
"Upload avatar": "అవతార్ను అప్లోడ్ చేయండి",
|
"Upload avatar": "అవతార్ను అప్లోడ్ చేయండి",
|
||||||
|
@ -305,7 +305,7 @@
|
||||||
"strike": "సమ్మె",
|
"strike": "సమ్మె",
|
||||||
"underline": "అండర్లైన్",
|
"underline": "అండర్లైన్",
|
||||||
"Enter Code": "కోడ్ వ్రాయండి",
|
"Enter Code": "కోడ్ వ్రాయండి",
|
||||||
"Failed to forget room %(errCode)s": "గది %(errCode) లు మర్చిపోవడంలో విఫలమైంది",
|
"Failed to forget room %(errCode)s": "గది %(errCode)s మర్చిపోవడంలో విఫలమైంది",
|
||||||
"Incorrect verification code": "ధృవీకరణ కోడ్ సరిగా లెదు",
|
"Incorrect verification code": "ధృవీకరణ కోడ్ సరిగా లెదు",
|
||||||
"unknown error code": "తెలియని కోడ్ లోపం",
|
"unknown error code": "తెలియని కోడ్ లోపం",
|
||||||
"code": "కోడ్",
|
"code": "కోడ్",
|
||||||
|
|
|
@ -231,7 +231,6 @@
|
||||||
"left and rejoined": "ออกแล้วกลับเข้าร่วมอีกครั้ง",
|
"left and rejoined": "ออกแล้วกลับเข้าร่วมอีกครั้ง",
|
||||||
"left": "ออกไปแล้ว",
|
"left": "ออกไปแล้ว",
|
||||||
"%(targetName)s left the room.": "%(targetName)s ออกจากห้องแล้ว",
|
"%(targetName)s left the room.": "%(targetName)s ออกจากห้องแล้ว",
|
||||||
"List this room in %(domain)s's room directory?": "แสดงห้องนี้ในไดเรกทอรีห้องของ %(domain)s?",
|
|
||||||
"Logged in as:": "เข้าสู่ระบบในชื่อ:",
|
"Logged in as:": "เข้าสู่ระบบในชื่อ:",
|
||||||
"Login as guest": "เข้าสู่ระบบในฐานะแขก",
|
"Login as guest": "เข้าสู่ระบบในฐานะแขก",
|
||||||
"Logout": "ออกจากระบบ",
|
"Logout": "ออกจากระบบ",
|
||||||
|
|
|
@ -380,7 +380,6 @@
|
||||||
"left": "ayrıldı",
|
"left": "ayrıldı",
|
||||||
"%(targetName)s left the room.": "%(targetName)s odadan ayrıldı.",
|
"%(targetName)s left the room.": "%(targetName)s odadan ayrıldı.",
|
||||||
"Level:": "Seviye :",
|
"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 :",
|
"Local addresses for this room:": "Bu oda için yerel adresler :",
|
||||||
"Logged in as:": "Olarak giriş yaptı :",
|
"Logged in as:": "Olarak giriş yaptı :",
|
||||||
"Login as guest": "Misafir olarak giriş yaptı",
|
"Login as guest": "Misafir olarak giriş yaptı",
|
||||||
|
|
|
@ -517,7 +517,6 @@
|
||||||
"left and rejoined": "離開並重新加入",
|
"left and rejoined": "離開並重新加入",
|
||||||
"left": "離開",
|
"left": "離開",
|
||||||
"Level:": "等級:",
|
"Level:": "等級:",
|
||||||
"List this room in %(domain)s's room directory?": "在 %(domain)s 的房間目錄中列出此房間嗎?",
|
|
||||||
"Local addresses for this room:": "此房間的本機地址:",
|
"Local addresses for this room:": "此房間的本機地址:",
|
||||||
"Logged in as:": "登入為:",
|
"Logged in as:": "登入為:",
|
||||||
"Logout": "登出",
|
"Logout": "登出",
|
||||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getAddressType, inviteToRoom} from '../Invite';
|
import {getAddressType} from '../UserAddress';
|
||||||
|
import {inviteToRoom} from '../Invite';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue