[WEBLATE] merge develop
This commit is contained in:
commit
c764b96378
66 changed files with 820 additions and 350 deletions
187
.eslintignore.errorfiles
Normal file
187
.eslintignore.errorfiles
Normal file
|
@ -0,0 +1,187 @@
|
|||
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
|
||||
|
||||
src/AddThreepid.js
|
||||
src/async-components/views/dialogs/EncryptedEventDialog.js
|
||||
src/autocomplete/AutocompleteProvider.js
|
||||
src/autocomplete/Autocompleter.js
|
||||
src/autocomplete/Components.js
|
||||
src/autocomplete/DuckDuckGoProvider.js
|
||||
src/autocomplete/EmojiProvider.js
|
||||
src/autocomplete/RoomProvider.js
|
||||
src/autocomplete/UserProvider.js
|
||||
src/Avatar.js
|
||||
src/BasePlatform.js
|
||||
src/CallHandler.js
|
||||
src/component-index.js
|
||||
src/components/structures/ContextualMenu.js
|
||||
src/components/structures/CreateRoom.js
|
||||
src/components/structures/FilePanel.js
|
||||
src/components/structures/InteractiveAuth.js
|
||||
src/components/structures/LoggedInView.js
|
||||
src/components/structures/login/ForgotPassword.js
|
||||
src/components/structures/login/Login.js
|
||||
src/components/structures/login/PostRegistration.js
|
||||
src/components/structures/login/Registration.js
|
||||
src/components/structures/MatrixChat.js
|
||||
src/components/structures/MessagePanel.js
|
||||
src/components/structures/NotificationPanel.js
|
||||
src/components/structures/RoomStatusBar.js
|
||||
src/components/structures/RoomView.js
|
||||
src/components/structures/ScrollPanel.js
|
||||
src/components/structures/TimelinePanel.js
|
||||
src/components/structures/UploadBar.js
|
||||
src/components/structures/UserSettings.js
|
||||
src/components/views/avatars/BaseAvatar.js
|
||||
src/components/views/avatars/MemberAvatar.js
|
||||
src/components/views/avatars/RoomAvatar.js
|
||||
src/components/views/create_room/CreateRoomButton.js
|
||||
src/components/views/create_room/Presets.js
|
||||
src/components/views/create_room/RoomAlias.js
|
||||
src/components/views/dialogs/ChatCreateOrReuseDialog.js
|
||||
src/components/views/dialogs/ChatInviteDialog.js
|
||||
src/components/views/dialogs/DeactivateAccountDialog.js
|
||||
src/components/views/dialogs/InteractiveAuthDialog.js
|
||||
src/components/views/dialogs/SetMxIdDialog.js
|
||||
src/components/views/dialogs/UnknownDeviceDialog.js
|
||||
src/components/views/elements/AccessibleButton.js
|
||||
src/components/views/elements/ActionButton.js
|
||||
src/components/views/elements/AddressSelector.js
|
||||
src/components/views/elements/AddressTile.js
|
||||
src/components/views/elements/CreateRoomButton.js
|
||||
src/components/views/elements/DeviceVerifyButtons.js
|
||||
src/components/views/elements/DirectorySearchBox.js
|
||||
src/components/views/elements/Dropdown.js
|
||||
src/components/views/elements/EditableText.js
|
||||
src/components/views/elements/EditableTextContainer.js
|
||||
src/components/views/elements/HomeButton.js
|
||||
src/components/views/elements/LanguageDropdown.js
|
||||
src/components/views/elements/MemberEventListSummary.js
|
||||
src/components/views/elements/PowerSelector.js
|
||||
src/components/views/elements/ProgressBar.js
|
||||
src/components/views/elements/RoomDirectoryButton.js
|
||||
src/components/views/elements/SettingsButton.js
|
||||
src/components/views/elements/StartChatButton.js
|
||||
src/components/views/elements/TintableSvg.js
|
||||
src/components/views/elements/TruncatedList.js
|
||||
src/components/views/elements/UserSelector.js
|
||||
src/components/views/login/CaptchaForm.js
|
||||
src/components/views/login/CasLogin.js
|
||||
src/components/views/login/CountryDropdown.js
|
||||
src/components/views/login/CustomServerDialog.js
|
||||
src/components/views/login/InteractiveAuthEntryComponents.js
|
||||
src/components/views/login/LoginHeader.js
|
||||
src/components/views/login/PasswordLogin.js
|
||||
src/components/views/login/RegistrationForm.js
|
||||
src/components/views/login/ServerConfig.js
|
||||
src/components/views/messages/MAudioBody.js
|
||||
src/components/views/messages/MessageEvent.js
|
||||
src/components/views/messages/MFileBody.js
|
||||
src/components/views/messages/MImageBody.js
|
||||
src/components/views/messages/MVideoBody.js
|
||||
src/components/views/messages/RoomAvatarEvent.js
|
||||
src/components/views/messages/TextualBody.js
|
||||
src/components/views/messages/TextualEvent.js
|
||||
src/components/views/room_settings/AliasSettings.js
|
||||
src/components/views/room_settings/ColorSettings.js
|
||||
src/components/views/room_settings/UrlPreviewSettings.js
|
||||
src/components/views/rooms/Autocomplete.js
|
||||
src/components/views/rooms/AuxPanel.js
|
||||
src/components/views/rooms/EntityTile.js
|
||||
src/components/views/rooms/EventTile.js
|
||||
src/components/views/rooms/LinkPreviewWidget.js
|
||||
src/components/views/rooms/MemberDeviceInfo.js
|
||||
src/components/views/rooms/MemberInfo.js
|
||||
src/components/views/rooms/MemberList.js
|
||||
src/components/views/rooms/MemberTile.js
|
||||
src/components/views/rooms/MessageComposer.js
|
||||
src/components/views/rooms/MessageComposerInput.js
|
||||
src/components/views/rooms/MessageComposerInputOld.js
|
||||
src/components/views/rooms/PresenceLabel.js
|
||||
src/components/views/rooms/ReadReceiptMarker.js
|
||||
src/components/views/rooms/RoomHeader.js
|
||||
src/components/views/rooms/RoomList.js
|
||||
src/components/views/rooms/RoomNameEditor.js
|
||||
src/components/views/rooms/RoomPreviewBar.js
|
||||
src/components/views/rooms/RoomSettings.js
|
||||
src/components/views/rooms/RoomTile.js
|
||||
src/components/views/rooms/RoomTopicEditor.js
|
||||
src/components/views/rooms/SearchableEntityList.js
|
||||
src/components/views/rooms/SearchResultTile.js
|
||||
src/components/views/rooms/TabCompleteBar.js
|
||||
src/components/views/rooms/TopUnreadMessagesBar.js
|
||||
src/components/views/rooms/UserTile.js
|
||||
src/components/views/settings/AddPhoneNumber.js
|
||||
src/components/views/settings/ChangeAvatar.js
|
||||
src/components/views/settings/ChangeDisplayName.js
|
||||
src/components/views/settings/ChangePassword.js
|
||||
src/components/views/settings/DevicesPanel.js
|
||||
src/components/views/settings/DevicesPanelEntry.js
|
||||
src/components/views/settings/EnableNotificationsButton.js
|
||||
src/components/views/voip/CallView.js
|
||||
src/components/views/voip/IncomingCallBox.js
|
||||
src/components/views/voip/VideoFeed.js
|
||||
src/components/views/voip/VideoView.js
|
||||
src/ContentMessages.js
|
||||
src/createRoom.js
|
||||
src/DateUtils.js
|
||||
src/email.js
|
||||
src/Entities.js
|
||||
src/extend.js
|
||||
src/HtmlUtils.js
|
||||
src/ImageUtils.js
|
||||
src/Invite.js
|
||||
src/languageHandler.js
|
||||
src/linkify-matrix.js
|
||||
src/Login.js
|
||||
src/Markdown.js
|
||||
src/MatrixClientPeg.js
|
||||
src/Modal.js
|
||||
src/Notifier.js
|
||||
src/ObjectUtils.js
|
||||
src/PasswordReset.js
|
||||
src/PlatformPeg.js
|
||||
src/Presence.js
|
||||
src/ratelimitedfunc.js
|
||||
src/Resend.js
|
||||
src/RichText.js
|
||||
src/Roles.js
|
||||
src/RoomListSorter.js
|
||||
src/RoomNotifs.js
|
||||
src/Rooms.js
|
||||
src/RtsClient.js
|
||||
src/ScalarAuthClient.js
|
||||
src/ScalarMessaging.js
|
||||
src/SdkConfig.js
|
||||
src/Skinner.js
|
||||
src/SlashCommands.js
|
||||
src/stores/LifecycleStore.js
|
||||
src/TabComplete.js
|
||||
src/TabCompleteEntries.js
|
||||
src/TextForEvent.js
|
||||
src/Tinter.js
|
||||
src/UiEffects.js
|
||||
src/Unread.js
|
||||
src/UserActivity.js
|
||||
src/utils/DecryptFile.js
|
||||
src/utils/DMRoomMap.js
|
||||
src/utils/FormattingUtils.js
|
||||
src/utils/MultiInviter.js
|
||||
src/utils/Receipt.js
|
||||
src/Velociraptor.js
|
||||
src/VelocityBounce.js
|
||||
src/WhoIsTyping.js
|
||||
src/wrappers/WithMatrixClient.js
|
||||
test/all-tests.js
|
||||
test/components/structures/login/Registration-test.js
|
||||
test/components/structures/MessagePanel-test.js
|
||||
test/components/structures/ScrollPanel-test.js
|
||||
test/components/structures/TimelinePanel-test.js
|
||||
test/components/stub-component.js
|
||||
test/components/views/dialogs/InteractiveAuthDialog-test.js
|
||||
test/components/views/elements/MemberEventListSummary-test.js
|
||||
test/components/views/login/RegistrationForm-test.js
|
||||
test/components/views/rooms/MessageComposerInput-test.js
|
||||
test/mock-clock.js
|
||||
test/skinned-sdk.js
|
||||
test/stores/RoomViewStore-test.js
|
||||
test/test-utils.js
|
|
@ -5,6 +5,4 @@ install:
|
|||
- npm install
|
||||
- (cd node_modules/matrix-js-sdk && npm install)
|
||||
script:
|
||||
# don't run the riot tests unless the react-sdk tests pass, otherwise
|
||||
# the output is confusing.
|
||||
- npm run test && ./.travis-test-riot.sh
|
||||
./scripts/travis.sh
|
||||
|
|
|
@ -21,6 +21,11 @@ npm run test
|
|||
# run eslint
|
||||
npm run lintall -- -f checkstyle -o eslint.xml || true
|
||||
|
||||
# re-run the linter, excluding any files known to have errors or warnings.
|
||||
./node_modules/.bin/eslint --max-warnings 0 \
|
||||
--ignore-path .eslintignore.errorfiles \
|
||||
src test
|
||||
|
||||
# delete the old tarball, if it exists
|
||||
rm -f matrix-react-sdk-*.tgz
|
||||
|
||||
|
|
|
@ -66,6 +66,11 @@ Some of your messages have not been sent.
|
|||
This room is private or inaccessible to guests. You may be able to join if you register.
|
||||
Tried to load a specific point in this room's timeline, but was unable to find it.
|
||||
Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.
|
||||
This action cannot be performed by a guest user. Please register to be able to do this.
|
||||
Tried to load a specific point in this room's timeline, but was unable to find it.
|
||||
Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.
|
||||
You are trying to access %(roomName)s.
|
||||
You will not be able to undo this change as you are promoting the user to have the same power level as yourself.
|
||||
EOT
|
||||
)];
|
||||
}
|
||||
|
@ -89,7 +94,7 @@ if ($_ =~ m/^(\s+)"(.*?)"(: *)"(.*?)"(,?)$/) {
|
|||
$sub = 1;
|
||||
}
|
||||
|
||||
if ($src eq $fixup && $dst !~ /\.$/) {
|
||||
if ($ARGV !~ /(zh_Hans|zh_Hant|th)\.json$/ && $src eq $fixup && $dst !~ /\.$/) {
|
||||
print STDERR "fixing up dst: $dst\n";
|
||||
$dst .= '.';
|
||||
$sub = 1;
|
||||
|
|
21
scripts/generate-eslint-error-ignore-file
Executable file
21
scripts/generate-eslint-error-ignore-file
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# generates .eslintignore.errorfiles to list the files which have errors in,
|
||||
# so that they can be ignored in future automated linting.
|
||||
|
||||
out=.eslintignore.errorfiles
|
||||
|
||||
cd `dirname $0`/..
|
||||
|
||||
echo "generating $out"
|
||||
|
||||
{
|
||||
cat <<EOF
|
||||
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
|
||||
|
||||
EOF
|
||||
|
||||
./node_modules/.bin/eslint --no-ignore -f json src test |
|
||||
jq -r '.[] | select((.errorCount + .warningCount) > 0) | .filePath' |
|
||||
sed -e 's/.*matrix-react-sdk\///';
|
||||
} > "$out"
|
11
scripts/travis.sh
Executable file
11
scripts/travis.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
npm run test
|
||||
./.travis-test-riot.sh
|
||||
|
||||
# run the linter, but exclude any files known to have errors or warnings.
|
||||
./node_modules/.bin/eslint --max-warnings 0 \
|
||||
--ignore-path .eslintignore.errorfiles \
|
||||
src test
|
|
@ -345,6 +345,7 @@ export function bodyToHtml(content, highlights, opts) {
|
|||
}
|
||||
safeBody = sanitizeHtml(body, sanitizeHtmlParams);
|
||||
safeBody = unicodeToImage(safeBody);
|
||||
safeBody = addCodeCopyButton(safeBody);
|
||||
}
|
||||
finally {
|
||||
delete sanitizeHtmlParams.textFilter;
|
||||
|
@ -363,6 +364,23 @@ export function bodyToHtml(content, highlights, opts) {
|
|||
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" />;
|
||||
}
|
||||
|
||||
function addCodeCopyButton(safeBody) {
|
||||
// Adds 'copy' buttons to pre blocks
|
||||
// Note that this only manipulates the markup to add the buttons:
|
||||
// we need to add the event handlers once the nodes are in the DOM
|
||||
// since we can't save functions in the markup.
|
||||
// This is done in TextualBody
|
||||
const el = document.createElement("div");
|
||||
el.innerHTML = safeBody;
|
||||
const codeBlocks = Array.from(el.getElementsByTagName("pre"));
|
||||
codeBlocks.forEach(p => {
|
||||
const button = document.createElement("span");
|
||||
button.className = "mx_EventTile_copyButton";
|
||||
p.appendChild(button);
|
||||
});
|
||||
return el.innerHTML;
|
||||
}
|
||||
|
||||
export function emojifyText(text) {
|
||||
return {
|
||||
__html: unicodeToImage(escape(text)),
|
||||
|
|
20
src/Login.js
20
src/Login.js
|
@ -97,11 +97,6 @@ export default class Login {
|
|||
guest: true
|
||||
};
|
||||
}, (error) => {
|
||||
if (error.httpStatus === 403) {
|
||||
error.friendlyText = _t("Guest access is disabled on this Home Server.");
|
||||
} else {
|
||||
error.friendlyText = _t("Failed to register as guest:") + ' ' + error.data;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
@ -157,15 +152,7 @@ export default class Login {
|
|||
accessToken: data.access_token
|
||||
});
|
||||
}, function(error) {
|
||||
if (error.httpStatus == 400 && loginParams.medium) {
|
||||
error.friendlyText = (
|
||||
_t('This Home Server does not support login using email address.')
|
||||
);
|
||||
}
|
||||
else if (error.httpStatus === 403) {
|
||||
error.friendlyText = (
|
||||
_t('Incorrect username and/or password.')
|
||||
);
|
||||
if (error.httpStatus === 403) {
|
||||
if (self._fallbackHsUrl) {
|
||||
var fbClient = Matrix.createClient({
|
||||
baseUrl: self._fallbackHsUrl,
|
||||
|
@ -186,11 +173,6 @@ export default class Login {
|
|||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
error.friendlyText = (
|
||||
_t("There was a problem logging in.") + ' (HTTP ' + error.httpStatus + ")"
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ export default React.createClass({
|
|||
'you have received in encrypted rooms to a local file. You ' +
|
||||
'will then be able to import the file into another Matrix ' +
|
||||
'client in the future, so that client will also be able to ' +
|
||||
'decrypt these messages.'
|
||||
'decrypt these messages.',
|
||||
) }
|
||||
</p>
|
||||
<p>
|
||||
|
@ -130,7 +130,7 @@ export default React.createClass({
|
|||
'careful to keep it secure. To help with this, you should enter ' +
|
||||
'a passphrase below, which will be used to encrypt the exported ' +
|
||||
'data. It will only be possible to import the data by using the ' +
|
||||
'same passphrase.'
|
||||
'same passphrase.',
|
||||
) }
|
||||
</p>
|
||||
<div className='error'>
|
||||
|
|
|
@ -122,13 +122,13 @@ export default React.createClass({
|
|||
'This process allows you to import encryption keys ' +
|
||||
'that you had previously exported from another Matrix ' +
|
||||
'client. You will then be able to decrypt any ' +
|
||||
'messages that the other client could decrypt.'
|
||||
'messages that the other client could decrypt.',
|
||||
) }
|
||||
</p>
|
||||
<p>
|
||||
{ _t(
|
||||
'The export file will be protected with a passphrase. ' +
|
||||
'You should enter the passphrase here, to decrypt the file.'
|
||||
'You should enter the passphrase here, to decrypt the file.',
|
||||
) }
|
||||
</p>
|
||||
<div className='error'>
|
||||
|
|
|
@ -223,10 +223,8 @@ export default React.createClass({
|
|||
ref='roomView'
|
||||
autoJoin={this.props.autoJoin}
|
||||
onRegistered={this.props.onRegistered}
|
||||
eventId={this.props.initialEventId}
|
||||
thirdPartyInvite={this.props.thirdPartyInvite}
|
||||
oobData={this.props.roomOobData}
|
||||
highlightedEventId={this.props.highlightedEventId}
|
||||
eventPixelOffset={this.props.initialEventPixelOffset}
|
||||
key={this.props.currentRoomId || 'roomview'}
|
||||
opacity={this.props.middleOpacity}
|
||||
|
|
|
@ -607,6 +607,8 @@ module.exports = React.createClass({
|
|||
// @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog.
|
||||
// @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the
|
||||
// context of that particular event.
|
||||
// @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL
|
||||
// and alter the EventTile to appear highlighted.
|
||||
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party
|
||||
// we received to join the room, if any.
|
||||
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||
|
@ -618,40 +620,20 @@ module.exports = React.createClass({
|
|||
this.focusComposer = true;
|
||||
|
||||
const newState = {
|
||||
initialEventId: roomInfo.event_id,
|
||||
highlightedEventId: roomInfo.event_id,
|
||||
initialEventPixelOffset: undefined,
|
||||
page_type: PageTypes.RoomView,
|
||||
thirdPartyInvite: roomInfo.third_party_invite,
|
||||
roomOobData: roomInfo.oob_data,
|
||||
currentRoomAlias: roomInfo.room_alias,
|
||||
autoJoin: roomInfo.auto_join,
|
||||
};
|
||||
|
||||
if (!roomInfo.room_alias) {
|
||||
newState.currentRoomId = roomInfo.room_id;
|
||||
}
|
||||
|
||||
// if we aren't given an explicit event id, look for one in the
|
||||
// scrollStateMap.
|
||||
//
|
||||
// TODO: do this in RoomView rather than here
|
||||
if (!roomInfo.event_id && this.refs.loggedInView) {
|
||||
const scrollState = this.refs.loggedInView.getScrollStateForRoom(roomInfo.room_id);
|
||||
if (scrollState) {
|
||||
newState.initialEventId = scrollState.focussedEvent;
|
||||
newState.initialEventPixelOffset = scrollState.pixelOffset;
|
||||
}
|
||||
}
|
||||
|
||||
if (roomInfo.room_alias) {
|
||||
console.log(
|
||||
`Switching to room alias ${roomInfo.room_alias} at event ` +
|
||||
newState.initialEventId,
|
||||
roomInfo.event_id,
|
||||
);
|
||||
} else {
|
||||
console.log(`Switching to room id ${roomInfo.room_id} at event ` +
|
||||
newState.initialEventId,
|
||||
roomInfo.event_id,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -680,7 +662,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
if (roomInfo.event_id) {
|
||||
if (roomInfo.event_id && roomInfo.highlighted) {
|
||||
presentedId += "/" + roomInfo.event_id;
|
||||
}
|
||||
this.notifyNewScreen('room/' + presentedId);
|
||||
|
@ -1137,6 +1119,10 @@ module.exports = React.createClass({
|
|||
const payload = {
|
||||
action: 'view_room',
|
||||
event_id: eventId,
|
||||
// If an event ID is given in the URL hash, notify RoomViewStore to mark
|
||||
// it as highlighted, which will propagate to RoomView and highlight the
|
||||
// associated EventTile.
|
||||
highlighted: Boolean(eventId),
|
||||
third_party_invite: thirdPartyInvite,
|
||||
oob_data: oobData,
|
||||
};
|
||||
|
|
|
@ -83,36 +83,8 @@ module.exports = React.createClass({
|
|||
// * invited us tovthe room
|
||||
oobData: React.PropTypes.object,
|
||||
|
||||
// id of an event to jump to. If not given, will go to the end of the
|
||||
// live timeline.
|
||||
eventId: React.PropTypes.string,
|
||||
|
||||
// where to position the event given by eventId, in pixels from the
|
||||
// bottom of the viewport. If not given, will try to put the event
|
||||
// 1/3 of the way down the viewport.
|
||||
eventPixelOffset: React.PropTypes.number,
|
||||
|
||||
// ID of an event to highlight. If undefined, no event will be highlighted.
|
||||
// Typically this will either be the same as 'eventId', or undefined.
|
||||
highlightedEventId: React.PropTypes.string,
|
||||
|
||||
// is the RightPanel collapsed?
|
||||
collapsedRhs: React.PropTypes.bool,
|
||||
|
||||
// a map from room id to scroll state, which will be updated on unmount.
|
||||
//
|
||||
// If there is no special scroll state (ie, we are following the live
|
||||
// timeline), the scroll state is null. Otherwise, it is an object with
|
||||
// the following properties:
|
||||
//
|
||||
// focussedEvent: the ID of the 'focussed' event. Typically this is
|
||||
// the last event fully visible in the viewport, though if we
|
||||
// have done an explicit scroll to an explicit event, it will be
|
||||
// that event.
|
||||
//
|
||||
// pixelOffset: the number of pixels the window is scrolled down
|
||||
// from the focussedEvent.
|
||||
scrollStateMap: React.PropTypes.object,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -122,6 +94,13 @@ module.exports = React.createClass({
|
|||
roomLoading: true,
|
||||
peekLoading: false,
|
||||
|
||||
// The event to be scrolled to initially
|
||||
initialEventId: null,
|
||||
// The offset in pixels from the event with which to scroll vertically
|
||||
initialEventPixelOffset: null,
|
||||
// Whether to highlight the event scrolled to
|
||||
isInitialEventHighlighted: null,
|
||||
|
||||
forwardingEvent: null,
|
||||
editingRoomSettings: false,
|
||||
uploadingRoomSettings: false,
|
||||
|
@ -180,13 +159,32 @@ module.exports = React.createClass({
|
|||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
const newState = {
|
||||
roomId: RoomViewStore.getRoomId(),
|
||||
roomAlias: RoomViewStore.getRoomAlias(),
|
||||
roomLoading: RoomViewStore.isRoomLoading(),
|
||||
roomLoadError: RoomViewStore.getRoomLoadError(),
|
||||
joining: RoomViewStore.isJoining(),
|
||||
}, () => {
|
||||
initialEventId: RoomViewStore.getInitialEventId(),
|
||||
initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(),
|
||||
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
|
||||
};
|
||||
|
||||
// Clear the search results when clicking a search result (which changes the
|
||||
// currently scrolled to event, this.state.initialEventId).
|
||||
if (this.state.initialEventId !== newState.initialEventId) {
|
||||
newState.searchResults = null;
|
||||
}
|
||||
|
||||
// Store the scroll state for the previous room so that we can return to this
|
||||
// position when viewing this room in future.
|
||||
if (this.state.roomId !== newState.roomId) {
|
||||
this._updateScrollMap(this.state.roomId);
|
||||
}
|
||||
|
||||
this.setState(newState, () => {
|
||||
// At this point, this.state.roomId could be null (e.g. the alias might not
|
||||
// have been resolved yet) so anything called here must handle this case.
|
||||
this._onHaveRoom();
|
||||
this.onRoom(MatrixClientPeg.get().getRoom(this.state.roomId));
|
||||
});
|
||||
|
@ -287,13 +285,6 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
if (newProps.eventId != this.props.eventId) {
|
||||
// when we change focussed event id, hide the search results.
|
||||
this.setState({searchResults: null});
|
||||
}
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
|
||||
!ObjectUtils.shallowEqual(this.state, nextState));
|
||||
|
@ -319,7 +310,7 @@ module.exports = React.createClass({
|
|||
this.unmounted = true;
|
||||
|
||||
// update the scroll map before we get unmounted
|
||||
this._updateScrollMap();
|
||||
this._updateScrollMap(this.state.roomId);
|
||||
|
||||
if (this.refs.roomView) {
|
||||
// disconnect the D&D event listeners from the room view. This
|
||||
|
@ -598,6 +589,18 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
_updateScrollMap(roomId) {
|
||||
// No point updating scroll state if the room ID hasn't been resolved yet
|
||||
if (!roomId) {
|
||||
return;
|
||||
}
|
||||
dis.dispatch({
|
||||
action: 'update_scroll_state',
|
||||
room_id: roomId,
|
||||
scroll_state: this._getScrollState(),
|
||||
});
|
||||
},
|
||||
|
||||
onRoom: function(room) {
|
||||
if (!room || room.roomId !== this.state.roomId) {
|
||||
return;
|
||||
|
@ -1240,21 +1243,6 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
// update scrollStateMap on unmount
|
||||
_updateScrollMap: function() {
|
||||
if (!this.state.room) {
|
||||
// we were instantiated on a room alias and haven't yet joined the room.
|
||||
return;
|
||||
}
|
||||
if (!this.props.scrollStateMap) return;
|
||||
|
||||
var roomId = this.state.room.roomId;
|
||||
|
||||
var state = this._getScrollState();
|
||||
this.props.scrollStateMap[roomId] = state;
|
||||
},
|
||||
|
||||
|
||||
// get the current scroll position of the room, so that it can be
|
||||
// restored when we switch back to it.
|
||||
//
|
||||
|
@ -1677,6 +1665,14 @@ module.exports = React.createClass({
|
|||
hideMessagePanel = true;
|
||||
}
|
||||
|
||||
const shouldHighlight = this.state.isInitialEventHighlighted;
|
||||
let highlightedEventId = null;
|
||||
if (this.state.forwardingEvent) {
|
||||
highlightedEventId = this.state.forwardingEvent.getId();
|
||||
} else if (shouldHighlight) {
|
||||
highlightedEventId = this.state.initialEventId;
|
||||
}
|
||||
|
||||
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
||||
var messagePanel = (
|
||||
<TimelinePanel ref={this._gatherTimelinePanelRef}
|
||||
|
@ -1684,9 +1680,9 @@ module.exports = React.createClass({
|
|||
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
|
||||
manageReadMarkers={true}
|
||||
hidden={hideMessagePanel}
|
||||
highlightedEventId={this.state.forwardingEvent ? this.state.forwardingEvent.getId() : this.props.highlightedEventId}
|
||||
eventId={this.props.eventId}
|
||||
eventPixelOffset={this.props.eventPixelOffset}
|
||||
highlightedEventId={highlightedEventId}
|
||||
eventId={this.state.initialEventId}
|
||||
eventPixelOffset={this.state.initialEventPixelOffset}
|
||||
onScroll={ this.onMessageListScroll }
|
||||
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
|
||||
showUrlPreview = { this.state.showUrlPreview }
|
||||
|
|
|
@ -87,7 +87,27 @@ module.exports = React.createClass({
|
|||
).then((data) => {
|
||||
this.props.onLoggedIn(data);
|
||||
}, (error) => {
|
||||
this._setStateFromError(error, true);
|
||||
let errorText;
|
||||
|
||||
// Some error strings only apply for logging in
|
||||
const usingEmail = username.indexOf("@") > 0;
|
||||
if (error.httpStatus == 400 && usingEmail) {
|
||||
errorText = _t('This Home Server does not support login using email address.');
|
||||
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
|
||||
errorText = _t('Incorrect username and/or password.');
|
||||
} else {
|
||||
// other errors, not specific to doing a password login
|
||||
errorText = this._errorTextFromError(error);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
errorText: errorText,
|
||||
// 401 would be the sensible status code for 'incorrect password'
|
||||
// but the login API gives a 403 https://matrix.org/jira/browse/SYN-744
|
||||
// mentions this (although the bug is for UI auth which is not this)
|
||||
// We treat both as an incorrect password
|
||||
loginIncorrect: error.httpStatus === 401 || error.httpStatus == 403,
|
||||
});
|
||||
}).finally(() => {
|
||||
this.setState({
|
||||
busy: false
|
||||
|
@ -110,7 +130,16 @@ module.exports = React.createClass({
|
|||
this._loginLogic.loginAsGuest().then(function(data) {
|
||||
self.props.onLoggedIn(data);
|
||||
}, function(error) {
|
||||
self._setStateFromError(error, true);
|
||||
let errorText;
|
||||
if (error.httpStatus === 403) {
|
||||
errorText = _t("Guest access is disabled on this Home Server.");
|
||||
} else {
|
||||
errorText = self._errorTextFromError(error);
|
||||
}
|
||||
self.setState({
|
||||
errorText: errorText,
|
||||
loginIncorrect: false,
|
||||
});
|
||||
}).finally(function() {
|
||||
self.setState({
|
||||
busy: false
|
||||
|
@ -183,31 +212,22 @@ module.exports = React.createClass({
|
|||
currentFlow: self._getCurrentFlowStep(),
|
||||
});
|
||||
}, function(err) {
|
||||
self._setStateFromError(err, false);
|
||||
self.setState({
|
||||
errorText: self._errorTextFromError(err),
|
||||
loginIncorrect: false,
|
||||
});
|
||||
}).finally(function() {
|
||||
self.setState({
|
||||
busy: false,
|
||||
});
|
||||
});
|
||||
}).done();
|
||||
},
|
||||
|
||||
_getCurrentFlowStep: function() {
|
||||
return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null;
|
||||
},
|
||||
|
||||
_setStateFromError: function(err, isLoginAttempt) {
|
||||
this.setState({
|
||||
errorText: this._errorTextFromError(err),
|
||||
// https://matrix.org/jira/browse/SYN-744
|
||||
loginIncorrect: isLoginAttempt && (err.httpStatus == 401 || err.httpStatus == 403)
|
||||
});
|
||||
},
|
||||
|
||||
_errorTextFromError(err) {
|
||||
if (err.friendlyText) {
|
||||
return err.friendlyText;
|
||||
}
|
||||
|
||||
let errCode = err.errcode;
|
||||
if (!errCode && err.httpStatus) {
|
||||
errCode = "HTTP " + err.httpStatus;
|
||||
|
@ -219,8 +239,8 @@ module.exports = React.createClass({
|
|||
if (err.cors === 'rejected') {
|
||||
if (window.location.protocol === 'https:' &&
|
||||
(this.state.enteredHomeserverUrl.startsWith("http:") ||
|
||||
!this.state.enteredHomeserverUrl.startsWith("http")))
|
||||
{
|
||||
!this.state.enteredHomeserverUrl.startsWith("http"))
|
||||
) {
|
||||
errorText = <span>
|
||||
{ _tJsx("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>.",
|
||||
|
@ -228,10 +248,9 @@ module.exports = React.createClass({
|
|||
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; }
|
||||
)}
|
||||
</span>;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
errorText = <span>
|
||||
{ _tJsx("Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
|
||||
{ _tJsx("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>(.*?)<\/a>/,
|
||||
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
|
||||
)}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
var Presets = {
|
||||
PrivateChat: "private_chat",
|
||||
|
@ -46,9 +47,9 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
return (
|
||||
<select className="mx_Presets" onChange={this.onValueChanged} value={this.props.preset}>
|
||||
<option value={this.Presets.PrivateChat}>Private Chat</option>
|
||||
<option value={this.Presets.PublicChat}>Public Chat</option>
|
||||
<option value={this.Presets.Custom}>Custom</option>
|
||||
<option value={this.Presets.PrivateChat}>{_t("Private Chat")}</option>
|
||||
<option value={this.Presets.PublicChat}>{_t("Public Chat")}</option>
|
||||
<option value={this.Presets.Custom}>{_t("Custom")}</option>
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
var React = require('react');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomAlias',
|
||||
|
@ -94,7 +95,7 @@ module.exports = React.createClass({
|
|||
|
||||
render: function() {
|
||||
return (
|
||||
<input type="text" className="mx_RoomAlias" placeholder="Alias (optional)"
|
||||
<input type="text" className="mx_RoomAlias" placeholder={_t("Alias (optional)")}
|
||||
onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur}
|
||||
value={this.props.alias}/>
|
||||
);
|
||||
|
|
|
@ -86,7 +86,7 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||
passwordBoxClass = 'error';
|
||||
}
|
||||
|
||||
const okLabel = this.state.busy ? <Loader /> : 'Deactivate Account';
|
||||
const okLabel = this.state.busy ? <Loader /> : _t('Deactivate Account');
|
||||
const okEnabled = this.state.confirmButtonEnabled && !this.state.busy;
|
||||
|
||||
let cancelButton = null;
|
||||
|
|
|
@ -78,7 +78,7 @@ export default React.createClass({
|
|||
<AccessibleButton onClick={this._onDismissClick}
|
||||
className="mx_UserSettings_button"
|
||||
>
|
||||
Dismiss
|
||||
{_t("Dismiss")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ import React from 'react';
|
|||
import sdk from '../../../index';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { _t, _tJsx } from '../../../languageHandler';
|
||||
|
||||
|
||||
export default React.createClass({
|
||||
|
@ -44,8 +44,11 @@ export default React.createClass({
|
|||
|
||||
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||
bugreport = (
|
||||
<p>Otherwise, <a onClick={this._sendBugReport} href='#'>
|
||||
click here</a> to send a bug report.
|
||||
<p>
|
||||
{_tJsx(
|
||||
"Otherwise, <a>click here</a> to send a bug report.",
|
||||
/<a>(.*?)<\/a>/, (sub) => <a onClick={this._sendBugReport} key="bugreport" href='#'>{sub}</a>,
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ export default React.createClass({
|
|||
console.log("UnknownDeviceDialog closed by escape");
|
||||
this.props.onFinished();
|
||||
}}
|
||||
title='Room contains unknown devices'
|
||||
title={_t('Room contains unknown devices')}
|
||||
>
|
||||
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
|
||||
<h4>
|
||||
|
@ -162,7 +162,7 @@ export default React.createClass({
|
|||
this.props.onFinished();
|
||||
Resend.resendUnsentEvents(this.props.room);
|
||||
}}>
|
||||
Send anyway
|
||||
{_t("Send anyway")}
|
||||
</button>
|
||||
<button className="mx_Dialog_primary" autoFocus={ true }
|
||||
onClick={() => {
|
||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
@ -111,9 +112,13 @@ module.exports = React.createClass({
|
|||
return null;
|
||||
}
|
||||
|
||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
|
||||
return (
|
||||
<span className="mx_TextualEvent mx_MemberEventListSummary_summary">
|
||||
{summaries.join(", ")}
|
||||
<EmojiText>
|
||||
{summaries.join(", ")}
|
||||
</EmojiText>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
var React = require('react');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'TruncatedList',
|
||||
|
@ -33,7 +34,7 @@ module.exports = React.createClass({
|
|||
truncateAt: 2,
|
||||
createOverflowElement: function(overflowCount, totalCount) {
|
||||
return (
|
||||
<div>And {overflowCount} more...</div>
|
||||
<div>{_t("And %(count)s more...", {count: overflowCount})}</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -440,7 +440,7 @@ export const FallbackAuthEntry = React.createClass({
|
|||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<a onClick={this._onShowFallbackClick}>Start authentication</a>
|
||||
<a onClick={this._onShowFallbackClick}>{_t("Start authentication")}</a>
|
||||
<div className="error">
|
||||
{this.props.errorText}
|
||||
</div>
|
||||
|
|
|
@ -101,7 +101,7 @@ module.exports = React.createClass({
|
|||
if (this.refs.email.value == '') {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Warning!",
|
||||
title: _t("Warning!"),
|
||||
description:
|
||||
<div>
|
||||
{_t("If you don't specify an email address, you won't be able to reset your password. " +
|
||||
|
@ -335,7 +335,7 @@ module.exports = React.createClass({
|
|||
);
|
||||
|
||||
const registerButton = (
|
||||
<input className="mx_Login_submit" type="submit" value="Register" />
|
||||
<input className="mx_Login_submit" type="submit" value={_t("Register")} />
|
||||
);
|
||||
|
||||
let placeholderUserName = _t("User name");
|
||||
|
|
|
@ -30,7 +30,7 @@ export default function SenderProfile(props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<EmojiText className="mx_SenderProfile"
|
||||
<EmojiText className="mx_SenderProfile" dir="auto"
|
||||
onClick={props.onClick}>{`${name || ''} ${props.aux || ''}`}</EmojiText>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -63,6 +63,19 @@ module.exports = React.createClass({
|
|||
};
|
||||
},
|
||||
|
||||
copyToClipboard: function(text) {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
} catch (err) {
|
||||
console.log('Unable to copy');
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
|
||||
|
@ -81,6 +94,14 @@ module.exports = React.createClass({
|
|||
}
|
||||
}, 10);
|
||||
}
|
||||
// add event handlers to the 'copy code' buttons
|
||||
const buttons = ReactDOM.findDOMNode(this).getElementsByClassName("mx_EventTile_copyButton");
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
buttons[i].onclick = (e) => {
|
||||
const copyCode = buttons[i].parentNode.getElementsByTagName("code")[0];
|
||||
this.copyToClipboard(copyCode.textContent);
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import MatrixClientPeg from "../../../MatrixClientPeg";
|
|||
import sdk from '../../../index';
|
||||
import dis from "../../../dispatcher";
|
||||
import ObjectUtils from '../../../ObjectUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { _t, _tJsx} from '../../../languageHandler';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -78,7 +78,7 @@ module.exports = React.createClass({
|
|||
fileDropTarget = (
|
||||
<div className="mx_RoomView_fileDropTarget">
|
||||
<div className="mx_RoomView_fileDropTargetLabel"
|
||||
title="Drop File Here">
|
||||
title={_t("Drop File Here")}>
|
||||
<TintableSvg src="img/upload-big.svg" width="45" height="59"/>
|
||||
<br/>
|
||||
{_t("Drop file here to upload")}
|
||||
|
@ -95,9 +95,14 @@ module.exports = React.createClass({
|
|||
}
|
||||
else {
|
||||
joinText = (<span>
|
||||
Join as <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice');}}
|
||||
href="#">voice</a> or <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video'); }}
|
||||
href="#">video</a>.
|
||||
{_tJsx(
|
||||
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
||||
[/<voiceText>(.*?)<\/voiceText>/, /<videoText>(.*?)<\/videoText>/],
|
||||
[
|
||||
(sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice');}} href="#">{sub}</a>,
|
||||
(sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video');}} href="#">{sub}</a>,
|
||||
]
|
||||
)}
|
||||
</span>);
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ var React = require('react');
|
|||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
var sdk = require('../../../index');
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
||||
var PRESENCE_CLASS = {
|
||||
|
@ -115,7 +116,7 @@ module.exports = React.createClass({
|
|||
nameEl = (
|
||||
<div className="mx_EntityTile_details">
|
||||
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12"/>
|
||||
<EmojiText element="div" className="mx_EntityTile_name_hover">{name}</EmojiText>
|
||||
<EmojiText element="div" className="mx_EntityTile_name_hover" dir="auto">{name}</EmojiText>
|
||||
<PresenceLabel activeAgo={ activeAgo }
|
||||
currentlyActive={this.props.presenceCurrentlyActive}
|
||||
presenceState={this.props.presenceState} />
|
||||
|
@ -124,7 +125,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
else {
|
||||
nameEl = (
|
||||
<EmojiText element="div" className="mx_EntityTile_name">{name}</EmojiText>
|
||||
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">{name}</EmojiText>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -140,10 +141,10 @@ module.exports = React.createClass({
|
|||
var power;
|
||||
var powerLevel = this.props.powerLevel;
|
||||
if (powerLevel >= 50 && powerLevel < 99) {
|
||||
power = <img src="img/mod.svg" className="mx_EntityTile_power" width="16" height="17" alt="Mod"/>;
|
||||
power = <img src="img/mod.svg" className="mx_EntityTile_power" width="16" height="17" alt={_t("Moderator")}/>;
|
||||
}
|
||||
if (powerLevel >= 99) {
|
||||
power = <img src="img/admin.svg" className="mx_EntityTile_power" width="16" height="17" alt="Admin"/>;
|
||||
power = <img src="img/admin.svg" className="mx_EntityTile_power" width="16" height="17" alt={_t("Admin")}/>;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -381,6 +381,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
event_id: this.props.mxEvent.getId(),
|
||||
highlighted: true,
|
||||
room_id: this.props.mxEvent.getRoomId(),
|
||||
});
|
||||
},
|
||||
|
@ -487,22 +488,22 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
let e2e;
|
||||
// cosmetic padlocks:
|
||||
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
|
||||
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt="Encrypted by verified device" src="img/e2e-verified.svg" width="10" height="12" />;
|
||||
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12" />;
|
||||
}
|
||||
// real padlocks
|
||||
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
|
||||
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Undecryptable" src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Undecryptable")} src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
||||
}
|
||||
else if (this.state.verified == true || (e2eEnabled && this.props.eventSendStatus)) {
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Encrypted by verified device" src="img/e2e-verified.svg" width="10" height="12"/>;
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12"/>;
|
||||
}
|
||||
else {
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Encrypted by unverified device" src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by an unverified device")} src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
|
||||
}
|
||||
}
|
||||
else if (e2eEnabled) {
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Unencrypted message" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Unencrypted message")} src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
||||
}
|
||||
const timestamp = this.props.mxEvent.getTs() ?
|
||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||
|
|
|
@ -26,19 +26,19 @@ export default class MemberDeviceInfo extends React.Component {
|
|||
if (this.props.device.isBlocked()) {
|
||||
indicator = (
|
||||
<div className="mx_MemberDeviceInfo_blacklisted">
|
||||
<img src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} alt="Blacklisted"/>
|
||||
<img src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} alt={_t("Blacklisted")}/>
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.device.isVerified()) {
|
||||
indicator = (
|
||||
<div className="mx_MemberDeviceInfo_verified">
|
||||
<img src="img/e2e-verified.svg" width="10" height="12" alt="Verified"/>
|
||||
<img src="img/e2e-verified.svg" width="10" height="12" alt={_t("Verified")}/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
indicator = (
|
||||
<div className="mx_MemberDeviceInfo_unverified">
|
||||
<img src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }} alt="Unverified"/>
|
||||
<img src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }} alt={_t("Unverified")}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ import Unread from '../../../Unread';
|
|||
import { findReadReceiptFromUserId } from '../../../utils/Receipt';
|
||||
import WithMatrixClient from '../../../wrappers/WithMatrixClient';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||
|
||||
|
||||
module.exports = WithMatrixClient(React.createClass({
|
||||
displayName: 'MemberInfo',
|
||||
|
@ -432,7 +434,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
title: _t("Warning!"),
|
||||
description:
|
||||
<div>
|
||||
{ _t("You will not be able to undo this change as you are promoting the user to have the same power level as yourself") }.<br/>
|
||||
{ _t("You will not be able to undo this change as you are promoting the user to have the same power level as yourself.") }<br/>
|
||||
{ _t("Are you sure?") }
|
||||
</div>,
|
||||
button: _t("Continue"),
|
||||
|
@ -701,7 +703,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
if (kickButton || banButton || muteButton || giveModButton) {
|
||||
adminTools =
|
||||
<div>
|
||||
<h3>Admin tools</h3>
|
||||
<h3>{_t("Admin tools")}</h3>
|
||||
|
||||
<div className="mx_MemberInfo_buttons">
|
||||
{muteButton}
|
||||
|
@ -727,34 +729,36 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
return (
|
||||
<div className="mx_MemberInfo">
|
||||
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}> <img src="img/cancel.svg" width="18" height="18"/></AccessibleButton>
|
||||
<div className="mx_MemberInfo_avatar">
|
||||
<MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} />
|
||||
</div>
|
||||
|
||||
<EmojiText element="h2">{memberName}</EmojiText>
|
||||
|
||||
<div className="mx_MemberInfo_profile">
|
||||
<div className="mx_MemberInfo_profileField">
|
||||
{ this.props.member.userId }
|
||||
<GeminiScrollbar autoshow={true}>
|
||||
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}> <img src="img/cancel.svg" width="18" height="18"/></AccessibleButton>
|
||||
<div className="mx_MemberInfo_avatar">
|
||||
<MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} />
|
||||
</div>
|
||||
<div className="mx_MemberInfo_profileField">
|
||||
{ _t("Level") }: <b><PowerSelector controlled={true} value={ parseInt(this.props.member.powerLevel) } disabled={ !this.state.can.modifyLevel } onChange={ this.onPowerChange }/></b>
|
||||
|
||||
<EmojiText element="h2">{memberName}</EmojiText>
|
||||
|
||||
<div className="mx_MemberInfo_profile">
|
||||
<div className="mx_MemberInfo_profileField">
|
||||
{ this.props.member.userId }
|
||||
</div>
|
||||
<div className="mx_MemberInfo_profileField">
|
||||
{ _t("Level:") } <b><PowerSelector controlled={true} value={ parseInt(this.props.member.powerLevel) } disabled={ !this.state.can.modifyLevel } onChange={ this.onPowerChange }/></b>
|
||||
</div>
|
||||
<div className="mx_MemberInfo_profileField">
|
||||
<PresenceLabel activeAgo={ presenceLastActiveAgo }
|
||||
currentlyActive={ presenceCurrentlyActive }
|
||||
presenceState={ presenceState } />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_MemberInfo_profileField">
|
||||
<PresenceLabel activeAgo={ presenceLastActiveAgo }
|
||||
currentlyActive={ presenceCurrentlyActive }
|
||||
presenceState={ presenceState } />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ adminTools }
|
||||
{ adminTools }
|
||||
|
||||
{ startChat }
|
||||
{ startChat }
|
||||
|
||||
{ this._renderDevices() }
|
||||
{ this._renderDevices() }
|
||||
|
||||
{ spinner }
|
||||
{ spinner }
|
||||
</GeminiScrollbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ var MatrixClientPeg = require('../../../MatrixClientPeg');
|
|||
var sdk = require('../../../index');
|
||||
var dis = require('../../../dispatcher');
|
||||
var Modal = require("../../../Modal");
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MemberTile',
|
||||
|
@ -63,7 +64,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
getPowerLabel: function() {
|
||||
return this.props.member.userId + " (power " + this.props.member.powerLevel + ")";
|
||||
return _t("%(userName)s (power %(powerLevelNumber)s)", {userName: this.props.member.userId, powerLevelNumber: this.props.member.powerLevel});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -109,7 +109,7 @@ export default class MessageComposer extends React.Component {
|
|||
let fileList = [];
|
||||
for (let i=0; i<files.length; i++) {
|
||||
fileList.push(<li key={i}>
|
||||
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name || 'Attachment'}
|
||||
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name || _t('Attachment')}
|
||||
</li>);
|
||||
}
|
||||
|
||||
|
@ -287,7 +287,7 @@ export default class MessageComposer extends React.Component {
|
|||
|
||||
const formattingButton = (
|
||||
<img className="mx_MessageComposer_formatting"
|
||||
title="Show Text Formatting Toolbar"
|
||||
title={_t("Show Text Formatting Toolbar")}
|
||||
src="img/button-text-formatting.svg"
|
||||
onClick={this.onToggleFormattingClicked}
|
||||
style={{visibility: this.state.showFormatting ||
|
||||
|
|
|
@ -721,6 +721,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
title={ this.state.isRichtextEnabled ? _t("Markdown is disabled") : _t("Markdown is enabled")}
|
||||
src={`img/button-md-${!this.state.isRichtextEnabled}.png`} />
|
||||
<Editor ref="editor"
|
||||
dir="auto"
|
||||
placeholder={this.props.placeholder}
|
||||
editorState={this.state.editorState}
|
||||
onChange={this.onEditorContentChanged}
|
||||
|
|
|
@ -461,7 +461,7 @@ export default React.createClass({
|
|||
render: function() {
|
||||
return (
|
||||
<div className="mx_MessageComposer_input" onClick={ this.onInputClick }>
|
||||
<textarea autoFocus ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder={this.props.placeholder}
|
||||
<textarea dir="auto" autoFocus ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder={this.props.placeholder}
|
||||
onPaste={this._onPaste}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -23,6 +23,7 @@ var sdk = require('../../../index');
|
|||
|
||||
var Velociraptor = require('../../../Velociraptor');
|
||||
require('../../../VelocityBounce');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
import DateUtils from '../../../DateUtils';
|
||||
|
||||
|
@ -169,8 +170,10 @@ module.exports = React.createClass({
|
|||
|
||||
let title;
|
||||
if (this.props.timestamp) {
|
||||
title = "Seen by " + this.props.member.userId + " at " +
|
||||
DateUtils.formatDate(new Date(this.props.timestamp));
|
||||
title = _t(
|
||||
"Seen by %(userName)s at %(dateTime)s",
|
||||
{userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp))}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -213,7 +213,7 @@ module.exports = React.createClass({
|
|||
// don't display the search count until the search completes and
|
||||
// gives us a valid (possibly zero) searchCount.
|
||||
if (this.props.searchInfo && this.props.searchInfo.searchCount !== undefined && this.props.searchInfo.searchCount !== null) {
|
||||
searchStatus = <div className="mx_RoomHeader_searchStatus"> { _t("(~%(searchCount)s results)", { searchCount: this.props.searchInfo.searchCount }) }</div>;
|
||||
searchStatus = <div className="mx_RoomHeader_searchStatus"> { _t("(~%(count)s results)", { count: this.props.searchInfo.searchCount }) }</div>;
|
||||
}
|
||||
|
||||
// XXX: this is a bit inefficient - we could just compare room.name for 'Empty room'...
|
||||
|
@ -238,7 +238,7 @@ module.exports = React.createClass({
|
|||
const emojiTextClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint });
|
||||
name =
|
||||
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
|
||||
<EmojiText element="div" className={emojiTextClasses} title={roomName}>{ roomName }</EmojiText>
|
||||
<EmojiText dir="auto" element="div" className={emojiTextClasses} title={roomName}>{ roomName }</EmojiText>
|
||||
{ searchStatus }
|
||||
</div>;
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
if (topic) {
|
||||
topic_el = <div className="mx_RoomHeader_topic" ref="topic" title={ topic }>{ topic }</div>;
|
||||
topic_el = <div className="mx_RoomHeader_topic" ref="topic" title={ topic } dir="auto">{ topic }</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,7 +288,7 @@ module.exports = React.createClass({
|
|||
var settings_button;
|
||||
if (this.props.onSettingsClick) {
|
||||
settings_button =
|
||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title="Settings">
|
||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title={_t("Settings")}>
|
||||
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
var React = require('react');
|
||||
var sdk = require('../../../index');
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomNameEditor',
|
||||
|
@ -35,8 +36,8 @@ module.exports = React.createClass({
|
|||
|
||||
this._initialName = name ? name.getContent().name : '';
|
||||
|
||||
this._placeholderName = "Unnamed Room";
|
||||
if (defaultName && defaultName !== 'Empty room') {
|
||||
this._placeholderName = _t("Unnamed Room");
|
||||
if (defaultName && defaultName !== 'Empty room') { // default name from JS SDK, needs no translation as we don't ever show it.
|
||||
this._placeholderName += " (" + defaultName + ")";
|
||||
}
|
||||
},
|
||||
|
@ -55,9 +56,9 @@ module.exports = React.createClass({
|
|||
placeholderClassName="mx_RoomHeader_placeholder"
|
||||
placeholder={ this._placeholderName }
|
||||
blurToCancel={ false }
|
||||
initialValue={ this._initialName }/>
|
||||
initialValue={ this._initialName }
|
||||
dir="auto" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ var React = require('react');
|
|||
var sdk = require('../../../index');
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { _t, _tJsx } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomPreviewBar',
|
||||
|
@ -84,7 +84,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_roomNameElement: function(fallback) {
|
||||
fallback = fallback || 'a room';
|
||||
fallback = fallback || _t('a room');
|
||||
const name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
||||
return name ? name : fallback;
|
||||
},
|
||||
|
@ -114,8 +114,7 @@ module.exports = React.createClass({
|
|||
if (this.props.invitedEmail) {
|
||||
if (this.state.threePidFetchError) {
|
||||
emailMatchBlock = <div className="error">
|
||||
Unable to ascertain that the address this invite was
|
||||
sent to matches one associated with your account.
|
||||
{_t("Unable to ascertain that the address this invite was sent to matches one associated with your account.")}
|
||||
</div>;
|
||||
} else if (this.state.invitedEmailMxid != MatrixClientPeg.get().credentials.userId) {
|
||||
emailMatchBlock =
|
||||
|
@ -124,28 +123,35 @@ module.exports = React.createClass({
|
|||
<img src="img/warning.svg" width="24" height="23" title= "/!\\" alt="/!\\" />
|
||||
</div>
|
||||
<div className="mx_RoomPreviewBar_warningText">
|
||||
This invitation was sent to <b><span className="email">{this.props.invitedEmail}</span></b>, which is not associated with this account.<br/>
|
||||
You may wish to login with a different account, or add this email to this account.
|
||||
{_t("This invitation was sent to an email address which is not associated with this account:")}
|
||||
<b><span className="email">{this.props.invitedEmail}</span></b>
|
||||
<br/>
|
||||
{_t("You may wish to login with a different account, or add this email to this account.")}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
// TODO: find a way to respect HTML in counterpart!
|
||||
joinBlock = (
|
||||
<div>
|
||||
<div className="mx_RoomPreviewBar_invite_text">
|
||||
{ _t('You have been invited to join this room by %(inviterName)s', {inviterName: this.props.inviterName}) }
|
||||
</div>
|
||||
<div className="mx_RoomPreviewBar_join_text">
|
||||
{ _t('Would you like to') } <a onClick={ this.props.onJoinClick }>{ _t('accept') }</a> { _t('or') } <a onClick={ this.props.onRejectClick }>{ _t('decline') }</a> { _t('this invitation?') }
|
||||
{ _tJsx(
|
||||
'Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?',
|
||||
[/<acceptText>(.*?)<\/acceptText>/, /<declineText>(.*?)<\/declineText>/],
|
||||
[
|
||||
(sub) => <a onClick={ this.props.onJoinClick }>{sub}</a>,
|
||||
(sub) => <a onClick={ this.props.onRejectClick }>{sub}</a>
|
||||
]
|
||||
)}
|
||||
</div>
|
||||
{emailMatchBlock}
|
||||
</div>
|
||||
);
|
||||
|
||||
} else if (kicked || banned) {
|
||||
const verb = kicked ? 'kicked' : 'banned';
|
||||
const roomName = this._roomNameElement('this room');
|
||||
const roomName = this._roomNameElement(_t('This room'));
|
||||
const kickerMember = this.props.room.currentState.getMember(
|
||||
myMember.events.member.getSender()
|
||||
);
|
||||
|
@ -153,29 +159,39 @@ module.exports = React.createClass({
|
|||
kickerMember.name : myMember.events.member.getSender();
|
||||
let reason;
|
||||
if (myMember.events.member.getContent().reason) {
|
||||
reason = <div>Reason: {myMember.events.member.getContent().reason}</div>
|
||||
reason = <div>{_t("Reason: %(reasonText)s", {reasonText: myMember.events.member.getContent().reason})}</div>
|
||||
}
|
||||
let rejoinBlock;
|
||||
if (!banned) {
|
||||
rejoinBlock = <div><a onClick={ this.props.onJoinClick }><b>Rejoin</b></a></div>;
|
||||
rejoinBlock = <div><a onClick={ this.props.onJoinClick }><b>{_t("Rejoin")}</b></a></div>;
|
||||
}
|
||||
|
||||
let actionText;
|
||||
if (kicked) {
|
||||
actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||
}
|
||||
else if (banned) {
|
||||
actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||
} // no other options possible due to the kicked || banned check above.
|
||||
|
||||
joinBlock = (
|
||||
<div>
|
||||
<div className="mx_RoomPreviewBar_join_text">
|
||||
You have been {verb} from {roomName} by {kickerName}.<br />
|
||||
{actionText}
|
||||
<br />
|
||||
{reason}
|
||||
{rejoinBlock}
|
||||
<a onClick={ this.props.onForgetClick }><b>Forget</b></a>
|
||||
<a onClick={ this.props.onForgetClick }><b>{_t("Forget room")}</b></a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.error) {
|
||||
var name = this.props.roomAlias || "This room";
|
||||
var name = this.props.roomAlias || _t("This room");
|
||||
var error;
|
||||
if (this.props.error.errcode == 'M_NOT_FOUND') {
|
||||
error = name + " does not exist";
|
||||
error = _t("%(roomName)s does not exist.", {roomName: name});
|
||||
} else {
|
||||
error = name + " is not accessible at this time";
|
||||
error = _t("%(roomName)s is not accessible at this time.", {roomName: name});
|
||||
}
|
||||
joinBlock = (
|
||||
<div>
|
||||
|
@ -189,8 +205,12 @@ module.exports = React.createClass({
|
|||
joinBlock = (
|
||||
<div>
|
||||
<div className="mx_RoomPreviewBar_join_text">
|
||||
{ _t('You are trying to access %(roomName)s', {roomName: name}) }.<br/>
|
||||
<a onClick={ this.props.onJoinClick }><b>{ _t('Click here') }</b></a> { _t('to join the discussion') }!
|
||||
{ _t('You are trying to access %(roomName)s.', {roomName: name}) }
|
||||
<br/>
|
||||
{ _tJsx("<a>Click here</a> to join the discussion!",
|
||||
/<a>(.*?)<\/a>/,
|
||||
(sub) => <a onClick={ this.props.onJoinClick }><b>{sub}</b></a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
|
||||
import q from 'q';
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { _t, _tJsx } from '../../../languageHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import sdk from '../../../index';
|
||||
|
@ -46,7 +46,7 @@ const BannedUser = React.createClass({
|
|||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createDialog(ConfirmUserActionDialog, {
|
||||
member: this.props.member,
|
||||
action: 'Unban',
|
||||
action: _t('Unban'),
|
||||
danger: false,
|
||||
onFinished: (proceed) => {
|
||||
if (!proceed) return;
|
||||
|
@ -597,7 +597,7 @@ module.exports = React.createClass({
|
|||
? <img className="mx_RoomSettings_e2eIcon" src="img/e2e-verified.svg" width="10" height="12" />
|
||||
: <img className="mx_RoomSettings_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12" />
|
||||
}
|
||||
{ isEncrypted ? "Encryption is enabled in this room" : "Encryption is not enabled in this room" }.
|
||||
{ isEncrypted ? _t("Encryption is enabled in this room") : _t("Encryption is not enabled in this room") }.
|
||||
</label>
|
||||
{ settings }
|
||||
</div>
|
||||
|
@ -653,7 +653,7 @@ module.exports = React.createClass({
|
|||
{Object.keys(user_levels).map(function(user, i) {
|
||||
return (
|
||||
<li className="mx_RoomSettings_userLevel" key={user}>
|
||||
{ user } { _t('is a') } <PowerSelector value={ user_levels[user] } disabled={true}/>
|
||||
{ _t("%(user)s is a", {user: user}) } <PowerSelector value={ user_levels[user] } disabled={true}/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
@ -754,7 +754,11 @@ module.exports = React.createClass({
|
|||
if (this.state.join_rule === "public" && aliasCount == 0) {
|
||||
addressWarning =
|
||||
<div className="mx_RoomSettings_warning">
|
||||
{ _t('To link to a room it must have') } <a href="#addresses"> { _t('an address') }</a>.
|
||||
{ _tJsx(
|
||||
'To link to a room it must have <a>an address</a>.',
|
||||
/<a>(.*?)<\/a>/,
|
||||
(sub) => <a href="#addresses">{sub}</a>
|
||||
)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
|
|
@ -224,13 +224,13 @@ module.exports = React.createClass({
|
|||
if (this.props.selected) {
|
||||
let nameSelected = <EmojiText>{name}</EmojiText>;
|
||||
|
||||
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
|
||||
label = <div title={ name } className={ nameClasses } dir="auto">{ nameSelected }</div>;
|
||||
} else {
|
||||
label = <EmojiText element="div" title={ name } className={ nameClasses }>{name}</EmojiText>;
|
||||
label = <EmojiText element="div" title={ name } className={ nameClasses } dir="auto">{name}</EmojiText>;
|
||||
}
|
||||
} else if (this.state.hover) {
|
||||
var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
||||
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" room={this.props.room} />;
|
||||
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" room={this.props.room} dir="auto" />;
|
||||
}
|
||||
|
||||
//var incomingCallBox;
|
||||
|
|
|
@ -46,7 +46,8 @@ module.exports = React.createClass({
|
|||
placeholderClassName="mx_RoomHeader_placeholder"
|
||||
placeholder={_t("Add a topic")}
|
||||
blurToCancel={ false }
|
||||
initialValue={ this._initialTopic }/>
|
||||
initialValue={ this._initialTopic }
|
||||
dir="auto" />
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ import React from 'react';
|
|||
import dis from '../../../dispatcher';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
// cancel button which is shared between room header and simple room header
|
||||
export function CancelButton(props) {
|
||||
|
@ -28,7 +29,7 @@ export function CancelButton(props) {
|
|||
return (
|
||||
<AccessibleButton className='mx_RoomHeader_cancelButton' onClick={onClick}>
|
||||
<img src="img/cancel.svg" className='mx_filterFlipColor'
|
||||
width="18" height="18" alt="Cancel"/>
|
||||
width="18" height="18" alt={_t("Cancel")}/>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
<img className="mx_TopUnreadMessagesBar_close mx_filterFlipColor"
|
||||
src="img/cancel.svg" width="18" height="18"
|
||||
alt="Close" title="Close"
|
||||
alt={_t("Close")} title={_t("Close")}
|
||||
onClick={this.props.onCloseClick} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -165,7 +165,7 @@ export default WithMatrixClient(React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||
<input type="image" value="Add" src="img/plus.svg" width="14" height="14" />
|
||||
<input type="image" value={_t("Add")} src="img/plus.svg" width="14" height="14" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
var React = require('react');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
var sdk = require('../../../index');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ChangeAvatar',
|
||||
|
@ -105,7 +106,7 @@ module.exports = React.createClass({
|
|||
|
||||
onError: function(error) {
|
||||
this.setState({
|
||||
errorText: "Failed to upload profile picture!"
|
||||
errorText: _t("Failed to upload profile picture!")
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -127,7 +128,7 @@ module.exports = React.createClass({
|
|||
if (this.props.showUploadSection) {
|
||||
uploadSection = (
|
||||
<div className={this.props.className}>
|
||||
Upload new:
|
||||
{_t("Upload new:")}
|
||||
<input type="file" accept="image/*" onChange={this.onFileSelected}/>
|
||||
{this.state.errorText}
|
||||
</div>
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
var React = require('react');
|
||||
var sdk = require('../../../index');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ChangeDisplayName',
|
||||
|
@ -52,7 +53,7 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<EditableTextContainer
|
||||
getInitialValue={this._getDisplayName}
|
||||
placeholder="No display name"
|
||||
placeholder={_t("No display name")}
|
||||
blurToSubmit={true}
|
||||
onSubmit={this._changeDisplayName} />
|
||||
);
|
||||
|
|
|
@ -53,7 +53,7 @@ module.exports = React.createClass({
|
|||
onCheckPassword: function(oldPass, newPass, confirmPass) {
|
||||
if (newPass !== confirmPass) {
|
||||
return {
|
||||
error: _t("New passwords don't match") + "."
|
||||
error: _t("New passwords don't match")
|
||||
};
|
||||
} else if (!newPass || newPass.length === 0) {
|
||||
return {
|
||||
|
|
|
@ -19,6 +19,7 @@ import classNames from 'classnames';
|
|||
|
||||
import sdk from '../../../index';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
||||
export default class DevicesPanel extends React.Component {
|
||||
|
@ -54,10 +55,10 @@ export default class DevicesPanel extends React.Component {
|
|||
var errtxt;
|
||||
if (error.httpStatus == 404) {
|
||||
// 404 probably means the HS doesn't yet support the API.
|
||||
errtxt = "Your home server does not support device management.";
|
||||
errtxt = _t("Your home server does not support device management.");
|
||||
} else {
|
||||
console.error("Error loading devices:", error);
|
||||
errtxt = "Unable to load device list.";
|
||||
errtxt = _t("Unable to load device list");
|
||||
}
|
||||
this.setState({deviceLoadError: errtxt});
|
||||
}
|
||||
|
@ -127,9 +128,9 @@ export default class DevicesPanel extends React.Component {
|
|||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_DevicesPanel_header">
|
||||
<div className="mx_DevicesPanel_deviceId">ID</div>
|
||||
<div className="mx_DevicesPanel_deviceName">Name</div>
|
||||
<div className="mx_DevicesPanel_deviceLastSeen">Last seen</div>
|
||||
<div className="mx_DevicesPanel_deviceId">{_t("Device ID")}</div>
|
||||
<div className="mx_DevicesPanel_deviceName">{_t("Device Name")}</div>
|
||||
<div className="mx_DevicesPanel_deviceLastSeen">{_t("Last seen")}</div>
|
||||
<div className="mx_DevicesPanel_deviceButtons"></div>
|
||||
</div>
|
||||
{devices.map(this._renderDevice)}
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
var React = require("react");
|
||||
var Notifier = require("../../../Notifier");
|
||||
var dis = require("../../../dispatcher");
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'EnableNotificationsButton',
|
||||
|
@ -60,13 +61,13 @@ module.exports = React.createClass({
|
|||
if (this.enabled()) {
|
||||
return (
|
||||
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>
|
||||
Disable Notifications
|
||||
{_t("Disable Notifications")}
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>
|
||||
Enable Notifications
|
||||
{_t("Enable Notifications")}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ var dis = require("../../../dispatcher");
|
|||
var CallHandler = require("../../../CallHandler");
|
||||
var sdk = require('../../../index');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CallView',
|
||||
|
@ -130,7 +131,11 @@ module.exports = React.createClass({
|
|||
var voice;
|
||||
if (this.state.call && this.state.call.type === "voice" && this.props.showVoice) {
|
||||
var callRoom = MatrixClientPeg.get().getRoom(this.state.call.roomId);
|
||||
voice = <div className="mx_CallView_voice" onClick={ this.props.onClick }>Active call ({ callRoom.name })</div>;
|
||||
voice = (
|
||||
<div className="mx_CallView_voice" onClick={ this.props.onClick }>
|
||||
{_t("Active call (%(roomName)s)", {roomName: callRoom.name})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -17,6 +17,7 @@ var React = require('react');
|
|||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
var dis = require("../../../dispatcher");
|
||||
var CallHandler = require("../../../CallHandler");
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'IncomingCallBox',
|
||||
|
@ -45,23 +46,36 @@ module.exports = React.createClass({
|
|||
room = MatrixClientPeg.get().getRoom(this.props.incomingCall.roomId);
|
||||
}
|
||||
|
||||
var caller = room ? room.name : "unknown";
|
||||
var caller = room ? room.name : _t("unknown caller");
|
||||
|
||||
let incomingCallText = null;
|
||||
if (this.props.incomingCall) {
|
||||
if (this.props.incomingCall.type === "voice") {
|
||||
incomingCallText = _t("Incoming voice call from %(name)s", {name: caller});
|
||||
}
|
||||
else if (this.props.incomingCall.type === "video") {
|
||||
incomingCallText = _t("Incoming video call from %(name)s", {name: caller});
|
||||
}
|
||||
else {
|
||||
incomingCallText = _t("Incoming call from %(name)s", {name: caller});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_IncomingCallBox" id="incomingCallBox">
|
||||
<img className="mx_IncomingCallBox_chevron" src="img/chevron-left.png" width="9" height="16" />
|
||||
<div className="mx_IncomingCallBox_title">
|
||||
Incoming { this.props.incomingCall ? this.props.incomingCall.type : '' } call from { caller }
|
||||
{incomingCallText}
|
||||
</div>
|
||||
<div className="mx_IncomingCallBox_buttons">
|
||||
<div className="mx_IncomingCallBox_buttons_cell">
|
||||
<div className="mx_IncomingCallBox_buttons_decline" onClick={this.onRejectClick}>
|
||||
Decline
|
||||
{_t("Decline")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_IncomingCallBox_buttons_cell">
|
||||
<div className="mx_IncomingCallBox_buttons_accept" onClick={this.onAnswerClick}>
|
||||
Accept
|
||||
{_t("Accept")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -427,7 +427,7 @@
|
|||
"You're not in any rooms yet! Press": "Du bist noch keinem Raum beigetreten! Drücke",
|
||||
"click to reveal": "Klicke zum anzeigen",
|
||||
"To remove other users' messages": "Um Nachrichten anderer Nutzer zu verbergen",
|
||||
"You are trying to access %(roomName)s": "Du versuchst, auf den Raum \"%(roomName)s\" zuzugreifen",
|
||||
"You are trying to access %(roomName)s.": "Du versuchst, auf den Raum \"%(roomName)s\" zuzugreifen.",
|
||||
"af": "Afrikaans",
|
||||
"ar-ae": "Arabisch (VAE)",
|
||||
"ar-bh": "Arabisch (Bahrain)",
|
||||
|
@ -611,7 +611,7 @@
|
|||
"Some of your messages have not been sent.": "Einige deiner Nachrichten wurden nicht gesendet.",
|
||||
"Submit": "Absenden",
|
||||
"The main address for this room is: %(canonical_alias_section)s": "Die Hauptadresse für diesen Raum ist: %(canonical_alias_section)s",
|
||||
"This action cannot be performed by a guest user. Please register to be able to do this": "Diese Aktion kann nicht von einem Gast ausgeführt werden. Bitte registriere dich um dies zu tun",
|
||||
"This action cannot be performed by a guest user. Please register to be able to do this.": "Diese Aktion kann nicht von einem Gast ausgeführt werden. Bitte registriere dich um dies zu tun.",
|
||||
"%(actionVerb)s this person?": "Diese Person %(actionVerb)s?",
|
||||
"This room has no local addresses": "Dieser Raum hat keine lokale Adresse",
|
||||
"This room is private or inaccessible to guests. You may be able to join if you register": "Dieser Raum ist privat oder für Gäste nicht zugänglich. Du kannst jedoch eventuell beitreten, wenn du dich registrierst",
|
||||
|
@ -625,7 +625,7 @@
|
|||
"Usage: /markdown on|off": "Verwendung: /markdown on|off",
|
||||
"You seem to be in a call, are you sure you want to quit?": "Du scheinst in einem Anruf zu sein. Bist du sicher schließen zu wollen?",
|
||||
"You seem to be uploading files, are you sure you want to quit?": "Du scheinst Dateien hochzuladen. Bist du sicher schließen zu wollen?",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself": "Du wirst diese Änderung nicht rückgängig machen können, da der Nutzer dasselbe Berechtigungslevel wie du selbst erhalten wird",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Du wirst diese Änderung nicht rückgängig machen können, da der Nutzer dasselbe Berechtigungslevel wie du selbst erhalten wird.",
|
||||
"Make Moderator": "Zum Moderator machen",
|
||||
"Room": "Raum",
|
||||
"(~%(searchCount)s results)": "(~%(searchCount)s Ergebnisse)",
|
||||
|
|
|
@ -120,17 +120,21 @@
|
|||
"zh-tw":"Chinese (Taiwan)",
|
||||
"zu":"Zulu",
|
||||
"A registered account is required for this action": "A registered account is required for this action",
|
||||
"a room": "a room",
|
||||
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains",
|
||||
"accept": "accept",
|
||||
"Accept": "Accept",
|
||||
"%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.",
|
||||
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.",
|
||||
"Account": "Account",
|
||||
"Access Token:": "Access Token:",
|
||||
"Active call (%(roomName)s)": "Active call (%(roomName)s)",
|
||||
"Add": "Add",
|
||||
"Add a topic": "Add a topic",
|
||||
"Add email address": "Add email address",
|
||||
"Add phone number": "Add phone number",
|
||||
"Admin": "Admin",
|
||||
"Admin tools": "Admin tools",
|
||||
"And %(count)s more...": "And %(count)s more...",
|
||||
"VoIP": "VoIP",
|
||||
"Missing Media Permissions, click here to request.": "Missing Media Permissions, click here to request.",
|
||||
"No Microphones detected": "No Microphones detected",
|
||||
|
@ -145,10 +149,10 @@
|
|||
"Hide removed messages": "Hide removed messages",
|
||||
"Always show message timestamps": "Always show message timestamps",
|
||||
"Authentication": "Authentication",
|
||||
"Alias (optional)": "Alias (optional)",
|
||||
"all room members": "all room members",
|
||||
"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",
|
||||
"an address": "an address",
|
||||
"and": "and",
|
||||
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others",
|
||||
"%(items)s and one other": "%(items)s and one other",
|
||||
|
@ -180,7 +184,7 @@
|
|||
"Bug Report": "Bug Report",
|
||||
"Bulk Options": "Bulk Options",
|
||||
"Call Timeout": "Call Timeout",
|
||||
"Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.": "Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
|
||||
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.",
|
||||
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.",
|
||||
"Can't load user settings": "Can't load user settings",
|
||||
"Change Password": "Change Password",
|
||||
|
@ -197,13 +201,14 @@
|
|||
"Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key",
|
||||
"Clear Cache and Reload": "Clear Cache and Reload",
|
||||
"Clear Cache": "Clear Cache",
|
||||
"Click here": "Click here",
|
||||
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
|
||||
"Click here to fix": "Click here to fix",
|
||||
"Click to mute audio": "Click to mute audio",
|
||||
"Click to mute video": "Click to mute video",
|
||||
"click to reveal": "click to reveal",
|
||||
"Click to unmute video": "Click to unmute video",
|
||||
"Click to unmute audio": "Click to unmute audio",
|
||||
"Close": "Close",
|
||||
"Command error": "Command error",
|
||||
"Commands": "Commands",
|
||||
"Conference call failed.": "Conference call failed.",
|
||||
|
@ -218,16 +223,18 @@
|
|||
"one": "%(count)s new message",
|
||||
"other": "%(count)s new messages"
|
||||
},
|
||||
"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",
|
||||
"Cryptography": "Cryptography",
|
||||
"Current password": "Current password",
|
||||
"Curve25519 identity key": "Curve25519 identity key",
|
||||
"Custom": "Custom",
|
||||
"Custom level": "Custom level",
|
||||
"/ddg is not a command": "/ddg is not a command",
|
||||
"Deactivate Account": "Deactivate Account",
|
||||
"Deactivate my account": "Deactivate my account",
|
||||
"decline": "decline",
|
||||
"Decline": "Decline",
|
||||
"Decrypt %(text)s": "Decrypt %(text)s",
|
||||
"Decryption error": "Decryption error",
|
||||
"(default: %(userName)s)": "(default: %(userName)s)",
|
||||
|
@ -244,6 +251,7 @@
|
|||
"Devices will not yet be able to decrypt history from before they joined the room": "Devices will not yet be able to decrypt history from before they joined the room",
|
||||
"Direct Chat": "Direct Chat",
|
||||
"Direct chats": "Direct chats",
|
||||
"Disable Notifications": "Disable Notifications",
|
||||
"disabled": "disabled",
|
||||
"Disable inline URL previews by default": "Disable inline URL previews by default",
|
||||
"Disable markdown formatting": "Disable markdown formatting",
|
||||
|
@ -252,6 +260,7 @@
|
|||
"Displays action": "Displays action",
|
||||
"Don't send typing notifications": "Don't send typing notifications",
|
||||
"Download %(text)s": "Download %(text)s",
|
||||
"Drop File Here": "Drop File Here",
|
||||
"Drop here %(toAction)s": "Drop here %(toAction)s",
|
||||
"Drop here to tag %(section)s": "Drop here to tag %(section)s",
|
||||
"Ed25519 fingerprint": "Ed25519 fingerprint",
|
||||
|
@ -261,9 +270,14 @@
|
|||
"Email, name or matrix ID": "Email, name or matrix ID",
|
||||
"Emoji": "Emoji",
|
||||
"Enable encryption": "Enable encryption",
|
||||
"Enable Notifications": "Enable Notifications",
|
||||
"enabled": "enabled",
|
||||
"Encrypted by a verified device": "Encrypted by a verified device",
|
||||
"Encrypted by an unverified device": "Encrypted by an unverified device",
|
||||
"Encrypted messages will not be visible on clients that do not yet implement encryption": "Encrypted messages will not be visible on clients that do not yet implement encryption",
|
||||
"Encrypted room": "Encrypted room",
|
||||
"Encryption is enabled in this room": "Encryption is enabled in this room",
|
||||
"Encryption is not enabled in this room": "Encryption is not enabled in this room",
|
||||
"%(senderName)s ended the call.": "%(senderName)s ended the call.",
|
||||
"End-to-end encryption information": "End-to-end encryption information",
|
||||
"End-to-end encryption is in beta and may not be reliable": "End-to-end encryption is in beta and may not be reliable",
|
||||
|
@ -301,6 +315,7 @@
|
|||
"Failed to toggle moderator status": "Failed to toggle moderator status",
|
||||
"Failed to unban": "Failed to unban",
|
||||
"Failed to upload file": "Failed to upload file",
|
||||
"Failed to upload profile picture!": "Failed to upload profile picture!",
|
||||
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
|
||||
"Failure to create room": "Failure to create room",
|
||||
"Favourite": "Favourite",
|
||||
|
@ -331,6 +346,9 @@
|
|||
"I have verified my email address": "I have verified my email address",
|
||||
"Import": "Import",
|
||||
"Import E2E room keys": "Import E2E room keys",
|
||||
"Incoming call from %(name)s": "Incoming call from %(name)s",
|
||||
"Incoming video call from %(name)s": "Incoming video 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 verification code": "Incorrect verification code",
|
||||
"Interface Language": "Interface Language",
|
||||
|
@ -343,11 +361,11 @@
|
|||
"Invited": "Invited",
|
||||
"Invites": "Invites",
|
||||
"Invites user with given id to current room": "Invites user with given id to current room",
|
||||
"is a": "is a",
|
||||
"'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
|
||||
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
|
||||
"%(displayName)s is typing": "%(displayName)s is typing",
|
||||
"Sign in with": "Sign in with",
|
||||
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
||||
"Join Room": "Join Room",
|
||||
"joined and left": "joined and left",
|
||||
"joined": "joined",
|
||||
|
@ -358,11 +376,12 @@
|
|||
"Kick": "Kick",
|
||||
"Kicks user with given id": "Kicks user with given id",
|
||||
"Labs": "Labs",
|
||||
"Last seen": "Last seen",
|
||||
"Leave room": "Leave room",
|
||||
"left and rejoined": "left and rejoined",
|
||||
"left": "left",
|
||||
"%(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?",
|
||||
"Local addresses for this room:": "Local addresses for this room:",
|
||||
"Logged in as:": "Logged in as:",
|
||||
|
@ -401,6 +420,7 @@
|
|||
"<not supported>": "<not supported>",
|
||||
"NOT verified": "NOT verified",
|
||||
"No devices with registered encryption keys": "No devices with registered encryption keys",
|
||||
"No display name": "No display name",
|
||||
"No more results": "No more results",
|
||||
"No results": "No results",
|
||||
"No users have specific privileges in this room": "No users have specific privileges in this room",
|
||||
|
@ -410,6 +430,7 @@
|
|||
"Once you've followed the link it contains, click below": "Once you've followed the link it contains, click below",
|
||||
"Only people who have been invited": "Only people who have been invited",
|
||||
"Operation failed": "Operation failed",
|
||||
"Otherwise, <a>click here</a> to send a bug report.": "Otherwise, <a>click here</a> to send a bug report.",
|
||||
"Password": "Password",
|
||||
"Password:": "Password:",
|
||||
"Passwords can't be empty": "Passwords can't be empty",
|
||||
|
@ -422,9 +443,12 @@
|
|||
"Power level must be positive integer.": "Power level must be positive integer.",
|
||||
"Press": "Press",
|
||||
"Privacy warning": "Privacy warning",
|
||||
"Private Chat": "Private Chat",
|
||||
"Privileged Users": "Privileged Users",
|
||||
"Profile": "Profile",
|
||||
"Public Chat": "Public Chat",
|
||||
"Reason": "Reason",
|
||||
"Reason: %(reasonText)s": "Reason: %(reasonText)s",
|
||||
"Revoke Moderator": "Revoke Moderator",
|
||||
"Refer a friend to Riot:": "Refer a friend to Riot:",
|
||||
"Register": "Register",
|
||||
|
@ -432,6 +456,7 @@
|
|||
"rejected": "rejected",
|
||||
"%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.",
|
||||
"Reject invitation": "Reject invitation",
|
||||
"Rejoin": "Rejoin",
|
||||
"Remote addresses for this room:": "Remote addresses for this room:",
|
||||
"Remove Contact Information?": "Remove Contact Information?",
|
||||
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).",
|
||||
|
@ -450,7 +475,10 @@
|
|||
"riot-web version:": "riot-web version:",
|
||||
"Room %(roomId)s not visible": "Room %(roomId)s not visible",
|
||||
"Room Colour": "Room Colour",
|
||||
"Room contains unknown devices": "Room contains unknown devices",
|
||||
"Room name (optional)": "Room name (optional)",
|
||||
"%(roomName)s does not exist.": "%(roomName)s does not exist.",
|
||||
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
|
||||
"Rooms": "Rooms",
|
||||
"Save": "Save",
|
||||
"Scroll to bottom of page": "Scroll to bottom of page",
|
||||
|
@ -458,8 +486,11 @@
|
|||
"Search": "Search",
|
||||
"Search failed": "Search failed",
|
||||
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
|
||||
"Searching known users": "Searching known users",
|
||||
"Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s",
|
||||
"Send a message (unencrypted)": "Send a message (unencrypted)",
|
||||
"Send an encrypted message": "Send an encrypted message",
|
||||
"Send anyway": "Send anyway",
|
||||
"Sender device information": "Sender device information",
|
||||
"Send Invites": "Send Invites",
|
||||
"Send Reset Email": "Send Reset Email",
|
||||
|
@ -476,9 +507,11 @@
|
|||
"Session ID": "Session ID",
|
||||
"%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.",
|
||||
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.",
|
||||
"Set": "Set",
|
||||
"Setting a user name will create a fresh account": "Setting a user name will create a fresh account",
|
||||
"Settings": "Settings",
|
||||
"Show panel": "Show panel",
|
||||
"Show Text Formatting Toolbar": "Show Text Formatting Toolbar",
|
||||
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)",
|
||||
"Signed Out": "Signed Out",
|
||||
"Sign in": "Sign in",
|
||||
|
@ -490,6 +523,7 @@
|
|||
"Someone": "Someone",
|
||||
"Sorry, this homeserver is using a login which is not recognised ": "Sorry, this homeserver is using a login which is not recognised ",
|
||||
"Start a chat": "Start a chat",
|
||||
"Start authentication": "Start authentication",
|
||||
"Start Chat": "Start Chat",
|
||||
"Submit": "Submit",
|
||||
"Success": "Success",
|
||||
|
@ -500,7 +534,7 @@
|
|||
"The main address for this room is": "The main address for this room is",
|
||||
"The phone number entered looks invalid": "The phone number entered looks invalid",
|
||||
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.",
|
||||
"This action cannot be performed by a guest user. Please register to be able to do this": "This action cannot be performed by a guest user. Please register to be able to do this",
|
||||
"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 email address is already in use": "This email address is already in use",
|
||||
"This email address was not found": "This email address was not found",
|
||||
"%(actionVerb)s this person?": "%(actionVerb)s this person?",
|
||||
|
@ -509,15 +543,16 @@
|
|||
"The file '%(fileName)s' failed to upload": "The file '%(fileName)s' failed to upload",
|
||||
"The remote side failed to pick up": "The remote side failed to pick up",
|
||||
"This Home Server does not support login using email address.": "This Home Server does not support login using email address.",
|
||||
"This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:",
|
||||
"There was a problem logging in.": "There was a problem logging in.",
|
||||
"This room has no local addresses": "This room has no local addresses",
|
||||
"This room is not recognised.": "This room is not recognised.",
|
||||
"These are experimental features that may break in unexpected ways": "These are experimental features that may break in unexpected ways",
|
||||
"The visibility of existing history will be unchanged": "The visibility of existing history will be unchanged",
|
||||
"This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address",
|
||||
"this invitation?": "this invitation?",
|
||||
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
|
||||
"This phone number is already in use": "This phone number is already in use",
|
||||
"This room": "This room",
|
||||
"This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers",
|
||||
"This room's internal ID is": "This room's internal ID is",
|
||||
"times": "times",
|
||||
|
@ -527,9 +562,8 @@
|
|||
"to demote": "to demote",
|
||||
"to favourite": "to favourite",
|
||||
"To invite users into the room": "To invite users into the room",
|
||||
"to join the discussion": "to join the discussion",
|
||||
"To kick users": "To kick users",
|
||||
"To link to a room it must have": "To link to a room it must have",
|
||||
"To link to a room it must have <a>an address</a>.": "To link to a room it must have <a>an address</a>.",
|
||||
"to make a room or": "to make a room or",
|
||||
"To remove other users' messages": "To remove other users' messages",
|
||||
"To reset your password, enter the email address linked to your account": "To reset your password, enter the email address linked to your account",
|
||||
|
@ -551,11 +585,15 @@
|
|||
"Unable to verify email address.": "Unable to verify email address.",
|
||||
"Unban": "Unban",
|
||||
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.",
|
||||
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Unable to ascertain that the address this invite was sent to matches one associated with your account.",
|
||||
"Unable to capture screen": "Unable to capture screen",
|
||||
"Unable to enable Notifications": "Unable to enable Notifications",
|
||||
"Unable to load device list": "Unable to load device list",
|
||||
"Undecryptable": "Undecryptable",
|
||||
"Unencrypted room": "Unencrypted room",
|
||||
"unencrypted": "unencrypted",
|
||||
"Unencrypted message": "Unencrypted message",
|
||||
"unknown caller": "unknown caller",
|
||||
"Unknown command": "Unknown command",
|
||||
"unknown device": "unknown device",
|
||||
"unknown error code": "unknown error code",
|
||||
|
@ -563,8 +601,10 @@
|
|||
"Unknown (user, device) pair:": "Unknown (user, device) pair:",
|
||||
"unknown": "unknown",
|
||||
"Unmute": "Unmute",
|
||||
"Unnamed Room": "Unnamed Room",
|
||||
"Unrecognised command:": "Unrecognised command:",
|
||||
"Unrecognised room alias:": "Unrecognised room alias:",
|
||||
"Unverified": "Unverified",
|
||||
"Uploading %(filename)s and %(count)s others": {
|
||||
"zero": "Uploading %(filename)s",
|
||||
"one": "Uploading %(filename)s and %(count)s other",
|
||||
|
@ -575,18 +615,22 @@
|
|||
"Upload Failed": "Upload Failed",
|
||||
"Upload Files": "Upload Files",
|
||||
"Upload file": "Upload file",
|
||||
"Upload new:": "Upload new:",
|
||||
"Usage": "Usage",
|
||||
"Use compact timeline layout": "Use compact timeline layout",
|
||||
"Use with caution": "Use with caution",
|
||||
"User ID": "User ID",
|
||||
"User Interface": "User Interface",
|
||||
"%(user)s is a": "%(user)s is a",
|
||||
"User name": "User name",
|
||||
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)",
|
||||
"Username invalid: %(errMessage)s": "Username invalid: %(errMessage)s",
|
||||
"Users": "Users",
|
||||
"User": "User",
|
||||
"Verification Pending": "Verification Pending",
|
||||
"Verification": "Verification",
|
||||
"verified": "verified",
|
||||
"Verified": "Verified",
|
||||
"Verified key": "Verified key",
|
||||
"Video call": "Video call",
|
||||
"Voice call": "Voice call",
|
||||
|
@ -601,21 +645,24 @@
|
|||
"Who can read history?": "Who can read history?",
|
||||
"Who would you like to add to this room?": "Who would you like to add to this room?",
|
||||
"Who would you like to communicate with?": "Who would you like to communicate with?",
|
||||
"Searching known users": "Searching known users",
|
||||
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.",
|
||||
"Would you like to": "Would you like to",
|
||||
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?",
|
||||
"You already have existing direct chats with this user:": "You already have existing direct chats with this user:",
|
||||
"You are already in a call.": "You are already in a call.",
|
||||
"You're not in any rooms yet! Press": "You're not in any rooms yet! Press",
|
||||
"You are trying to access %(roomName)s": "You are trying to access %(roomName)s",
|
||||
"You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.",
|
||||
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
|
||||
"You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.",
|
||||
"You do not have permission to post to this room": "You do not have permission to post to this room",
|
||||
"You have been banned from %(roomName)s by %(userName)s.": "You have been banned from %(roomName)s by %(userName)s.",
|
||||
"You have been invited to join this room by %(inviterName)s": "You have been invited to join this room by %(inviterName)s",
|
||||
"You have been kicked from %(roomName)s by %(userName)s.": "You have been kicked from %(roomName)s by %(userName)s.",
|
||||
"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": "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",
|
||||
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
|
||||
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
|
||||
"You have entered an invalid contact. Try using their Matrix ID or email address.": "You have entered an invalid contact. Try using their Matrix ID or email address.",
|
||||
"You have no visible notifications": "You have no visible notifications",
|
||||
"You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.",
|
||||
"you must be a": "you must be a",
|
||||
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
|
||||
"You need to be able to invite users to do that.": "You need to be able to invite users to do that.",
|
||||
|
@ -628,7 +675,8 @@
|
|||
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
|
||||
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
|
||||
"You should not yet trust it to secure data": "You should not yet trust it to secure data",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
|
||||
"Your home server does not support device management.": "Your home server does not support device management.",
|
||||
"Sun": "Sun",
|
||||
"Mon": "Mon",
|
||||
"Tue": "Tue",
|
||||
|
@ -675,7 +723,10 @@
|
|||
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
|
||||
"Auto-complete": "Auto-complete",
|
||||
"<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.": "<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
|
||||
"(~%(searchCount)s results)": "(~%(searchCount)s results)",
|
||||
"(~%(count)s results)": {
|
||||
"one": "(~%(count)s result)",
|
||||
"other": "(~%(count)s results)"
|
||||
},
|
||||
"Cancel": "Cancel",
|
||||
"or": "or",
|
||||
"Active call": "Active call",
|
||||
|
@ -765,7 +816,6 @@
|
|||
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.",
|
||||
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.",
|
||||
"You must join the room to see its files": "You must join the room to see its files",
|
||||
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
|
||||
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
|
||||
"Start new chat": "Start new chat",
|
||||
"Guest users can't invite users. Please register.": "Guest users can't invite users. Please register.",
|
||||
|
@ -781,6 +831,7 @@
|
|||
"To continue, please enter your password.": "To continue, please enter your password.",
|
||||
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:",
|
||||
"Device name": "Device name",
|
||||
"Device Name": "Device Name",
|
||||
"Device key": "Device key",
|
||||
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.",
|
||||
"In future this verification process will be more sophisticated.": "In future this verification process will be more sophisticated.",
|
||||
|
@ -844,15 +895,9 @@
|
|||
"Online": "Online",
|
||||
"Idle": "Idle",
|
||||
"Offline": "Offline",
|
||||
"disabled": "disabled",
|
||||
"enabled": "enabled",
|
||||
"Start chatting": "Start chatting",
|
||||
"Start Chatting": "Start Chatting",
|
||||
"Click on the button below to start chatting!": "Click on the button below to start chatting!",
|
||||
"Create a new chat or reuse an existing one": "Create a new chat or reuse an existing one",
|
||||
"You already have existing direct chats with this user:": "You already have existing direct chats with this user:",
|
||||
"Start new chat": "Start new chat",
|
||||
"Disable URL previews for this room (affects only you)": "Disable URL previews for this room (affects only you)",
|
||||
"$senderDisplayName changed the room avatar to <img/>": "$senderDisplayName changed the room avatar to <img/>",
|
||||
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.",
|
||||
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s",
|
||||
|
|
|
@ -489,7 +489,7 @@
|
|||
"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 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.",
|
||||
"This email address is already in use": "This email address is already in use",
|
||||
"This email address was not found": "This email address was not found",
|
||||
"%(actionVerb)s this person?": "%(actionVerb)s this person?",
|
||||
|
@ -588,7 +588,7 @@
|
|||
"Would you like to": "Would you like to",
|
||||
"You are already in a call.": "You are already in a call.",
|
||||
"You're not in any rooms yet! Press": "You're not in any rooms yet! Press",
|
||||
"You are trying to access %(roomName)s": "You are trying to access %(roomName)s",
|
||||
"You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.",
|
||||
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
|
||||
"You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.",
|
||||
"You do not have permission to post to this room": "You do not have permission to post to this room",
|
||||
|
@ -609,7 +609,7 @@
|
|||
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
|
||||
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
|
||||
"You should not yet trust it to secure data": "You should not yet trust it to secure data",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
|
||||
"Sun": "Sun",
|
||||
"Mon": "Mon",
|
||||
"Tue": "Tue",
|
||||
|
|
|
@ -468,7 +468,7 @@
|
|||
"tag direct chat": "marquer comme conversation directe",
|
||||
"The default role for new room members is": "Le rôle par défaut des nouveaux membres est",
|
||||
"The main address for this room is": "L'adresse principale pour ce salon est",
|
||||
"This action cannot be performed by a guest user. Please register to be able to do this": "Cette action ne peut être effectuée par un visiteur. Merci de vous enregistrer afin de pouvoir effectuer cette action",
|
||||
"This action cannot be performed by a guest user. Please register to be able to do this.": "Cette action ne peut être effectuée par un visiteur. Merci de vous enregistrer afin de pouvoir effectuer cette action.",
|
||||
"This email address is already in use": "Cette adresse e-mail est déjà utilisée",
|
||||
"This email address was not found": "Cette adresse e-mail n’a pas été trouvée",
|
||||
"%(actionVerb)s this person?": "%(actionVerb)s cette personne ?",
|
||||
|
@ -557,7 +557,7 @@
|
|||
"Would you like to": "Voulez-vous",
|
||||
"You are already in a call.": "Vous êtes déjà dans un appel.",
|
||||
"You're not in any rooms yet! Press": "Vous n’êtes dans aucun salon ! Cliquez",
|
||||
"You are trying to access %(roomName)s": "Vous essayez d'accéder à %(roomName)s",
|
||||
"You are trying to access %(roomName)s.": "Vous essayez d'accéder à %(roomName)s.",
|
||||
"You cannot place a call with yourself.": "Vous ne pouvez pas passer d'appel avec vous-même.",
|
||||
"You cannot place VoIP calls in this browser.": "Vous ne pouvez pas passer d'appel voix dans cet explorateur.",
|
||||
"You do not have permission to post to this room": "Vous n’avez pas la permission de poster dans ce salon",
|
||||
|
@ -576,7 +576,7 @@
|
|||
"You seem to be uploading files, are you sure you want to quit?": "Vous semblez être en train de télécharger des fichiers, êtes-vous sûr(e) de vouloir quitter ?",
|
||||
"You should not yet trust it to secure data": "Vous ne pouvez pas encore lui faire confiance pour sécuriser vos données",
|
||||
"changing room on a RoomView is not supported": "changer de salon sur un RoomView n'est pas supporté",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself": "Vous ne pourrez pas défaire ce changement car vous promouvez l’utilisateur aux mêmes pouvoirs que vous",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Vous ne pourrez pas défaire ce changement car vous promouvez l’utilisateur aux mêmes pouvoirs que vous.",
|
||||
"Sun": "Dim",
|
||||
"Mon": "Lun",
|
||||
"Tue": "Mar",
|
||||
|
|
|
@ -399,7 +399,7 @@
|
|||
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s desfez o convite a %(targetName)s.",
|
||||
"You are already in a call.": "Você já está em uma chamada.",
|
||||
"You're not in any rooms yet! Press": "Você ainda não está em nenhuma sala! Pressione",
|
||||
"You are trying to access %(roomName)s": "Você está tentando acessar a sala %(roomName)s",
|
||||
"You are trying to access %(roomName)s.": "Você está tentando acessar a sala %(roomName)s.",
|
||||
"You cannot place a call with yourself.": "Você não pode iniciar uma chamada.",
|
||||
"You cannot place VoIP calls in this browser.": "Você não pode fazer chamadas de voz neste navegador.",
|
||||
"You need to be able to invite users to do that.": "Para fazer isso, você tem que ter permissão para convidar outras pessoas.",
|
||||
|
@ -630,7 +630,7 @@
|
|||
"Some of your messages have not been sent.": "Algumas das suas mensagens não foram enviadas.",
|
||||
"Submit": "Enviar",
|
||||
"The main address for this room is": "O endereço principal desta sala é",
|
||||
"This action cannot be performed by a guest user. Please register to be able to do this": "Esta ação não pode ser realizada por um/a usuário/a visitante. Por favor, registre-se para poder fazer isso",
|
||||
"This action cannot be performed by a guest user. Please register to be able to do this.": "Esta ação não pode ser realizada por um/a usuário/a visitante. Por favor, registre-se para poder fazer isso.",
|
||||
"%(actionVerb)s this person?": "%(actionVerb)s esta pessoa?",
|
||||
"This room has no local addresses": "Esta sala não tem endereços locais",
|
||||
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tentei carregar um ponto específico na linha do tempo desta sala, mas parece que você não tem permissões para ver a mensagem em questão.",
|
||||
|
@ -643,7 +643,7 @@
|
|||
"You have been invited to join this room by %(inviterName)s": "Você foi convidada/o por %(inviterName)s a ingressar nesta sala",
|
||||
"You seem to be in a call, are you sure you want to quit?": "Parece que você está em uma chamada. Tem certeza que quer sair?",
|
||||
"You seem to be uploading files, are you sure you want to quit?": "Parece que você está enviando arquivos. Tem certeza que quer sair?",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself": "Você não poderá desfazer esta mudança, pois estará dando a este(a) usuário(a) o mesmo nível de permissões que você",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Você não poderá desfazer esta mudança, pois estará dando a este(a) usuário(a) o mesmo nível de permissões que você.",
|
||||
"Make Moderator": "Tornar moderador(a)",
|
||||
"Room": "Sala",
|
||||
"(~%(searchCount)s results)": "(±%(searchCount)s resultados)",
|
||||
|
|
|
@ -399,7 +399,7 @@
|
|||
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s desfez o convite a %(targetName)s.",
|
||||
"You are already in a call.": "Você já está em uma chamada.",
|
||||
"You're not in any rooms yet! Press": "Você ainda não está em nenhuma sala! Pressione",
|
||||
"You are trying to access %(roomName)s": "Você está tentando acessar a sala %(roomName)s",
|
||||
"You are trying to access %(roomName)s.": "Você está tentando acessar a sala %(roomName)s.",
|
||||
"You cannot place a call with yourself.": "Você não pode iniciar uma chamada.",
|
||||
"You cannot place VoIP calls in this browser.": "Você não pode fazer chamadas de voz neste navegador.",
|
||||
"You need to be able to invite users to do that.": "Para fazer isso, você tem que ter permissão para convidar outras pessoas.",
|
||||
|
@ -630,7 +630,7 @@
|
|||
"Some of your messages have not been sent.": "Algumas das suas mensagens não foram enviadas.",
|
||||
"Submit": "Enviar",
|
||||
"The main address for this room is": "O endereço principal desta sala é",
|
||||
"This action cannot be performed by a guest user. Please register to be able to do this": "Esta ação não pode ser realizada por um/a usuário/a visitante. Por favor, registre-se para poder fazer isso",
|
||||
"This action cannot be performed by a guest user. Please register to be able to do this.": "Esta ação não pode ser realizada por um/a usuário/a visitante. Por favor, registre-se para poder fazer isso.",
|
||||
"%(actionVerb)s this person?": "%(actionVerb)s esta pessoa?",
|
||||
"This room has no local addresses": "Esta sala não tem endereços locais",
|
||||
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tentei carregar um ponto específico na linha do tempo desta sala, mas parece que você não tem permissões para ver a mensagem em questão.",
|
||||
|
@ -643,7 +643,7 @@
|
|||
"You have been invited to join this room by %(inviterName)s": "Você foi convidada/o por %(inviterName)s a ingressar nesta sala",
|
||||
"You seem to be in a call, are you sure you want to quit?": "Parece que você está em uma chamada. Tem certeza que quer sair?",
|
||||
"You seem to be uploading files, are you sure you want to quit?": "Parece que você está enviando arquivos. Tem certeza que quer sair?",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself": "Você não poderá desfazer esta mudança, pois estará dando a este(a) usuário(a) o mesmo nível de permissões que você",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Você não poderá desfazer esta mudança, pois estará dando a este(a) usuário(a) o mesmo nível de permissões que você.",
|
||||
"Make Moderator": "Tornar moderador(a)",
|
||||
"Room": "Sala",
|
||||
"(~%(searchCount)s results)": "(±%(searchCount)s resultados)",
|
||||
|
|
|
@ -362,7 +362,7 @@
|
|||
"You cannot place VoIP calls in this browser.": "Вы не можете сделать вызовы VoIP с этим браузером.",
|
||||
"You are already in a call.": "Вы уже находитесь в разговоре.",
|
||||
"You're not in any rooms yet! Press": "Вы еще не находитесь ни в каких комнатах! Нажать",
|
||||
"You are trying to access %(roomName)s": "Вы пытаетесь получить доступ %(roomName)s",
|
||||
"You are trying to access %(roomName)s.": "Вы пытаетесь получить доступ %(roomName)s.",
|
||||
"You cannot place a call with yourself.": "Вы не можете позвонить самим себе.",
|
||||
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s анулировал %(targetName)s's преглашение.",
|
||||
"Sep": "Сен.",
|
||||
|
@ -575,7 +575,7 @@
|
|||
"tag direct chat": "отметить прямое общение",
|
||||
"The default role for new room members is": "Роль по умолчанию для новых участников комнаты",
|
||||
"The main address for this room is": "Основной адрес для этой комнаты",
|
||||
"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 email address is already in use": "Этот адрес электронной почты уже используется",
|
||||
"This email address was not found": "Этот адрес электронной почты не найден",
|
||||
"The email address linked to your account must be entered.": "Необходимо ввести адрес электронной почты, связанный с вашей учётной записью.",
|
||||
|
@ -752,8 +752,8 @@
|
|||
"To remove other users' messages": "Удалять сообщения других пользователей",
|
||||
"To reset your password, enter the email address linked to your account": "Чтобы сбросить ваш пароль введите адрес email, который используется аккаунтом",
|
||||
"to tag as %(tagName)s": "отметить как %(tagName)s",
|
||||
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question": "Вы попытались загрузить указанное сообщение в комнате, однако у вас нету разрешений для его просмотра",
|
||||
"Tried to load a specific point in this room's timeline, but was unable to find it": "Вы попытались загрузить указанное сообщение в комнате, однако сервер не смог его найти",
|
||||
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Вы попытались загрузить указанное сообщение в комнате, однако у вас нету разрешений для его просмотра.",
|
||||
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Вы попытались загрузить указанное сообщение в комнате, однако сервер не смог его найти.",
|
||||
"Unable to load device list": "Невозможно загрузить список устройств",
|
||||
"Unknown (user, device) pair:": "Неизвестная пара пользователь-устройство:",
|
||||
"Unmute": "Разглушить",
|
||||
|
@ -767,7 +767,7 @@
|
|||
"You have entered an invalid contact. Try using their Matrix ID or email address.": "Вы ввели неправильный адрес. Попробуйте использовать Matrix ID или адрес email.",
|
||||
"You need to enter a user name.": "Необходимо ввести имя пользователя.",
|
||||
"You seem to be in a call, are you sure you want to quit?": "Вы учавствуете в звонке, вы уверены, что хотите выйти?",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself": "Вы не сможете отменить это действие так как даете пользователю такой же уровень доступа как и у вас",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Вы не сможете отменить это действие так как даете пользователю такой же уровень доступа как и у вас.",
|
||||
"Set a Display Name": "Установить отображаемое имя",
|
||||
"(~%(searchCount)s results)": "(~%(searchCount)s результатов)",
|
||||
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)s отозвали свои приглашения %(repeats)s раз",
|
||||
|
|
|
@ -147,7 +147,7 @@
|
|||
"Success": "成功",
|
||||
"The default role for new room members is": "此聊天室新成员的默认角色是",
|
||||
"The main address for this room is": "此聊天室的主要地址是",
|
||||
"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 email address is already in use": "此邮箱地址已经被使用",
|
||||
"This email address was not found": "未找到此邮箱地址",
|
||||
"%(actionVerb)s this person?": "%(actionVerb)s 这个用户?",
|
||||
|
|
|
@ -274,7 +274,7 @@
|
|||
"Success": "成功",
|
||||
"The default role for new room members is": "此聊天室新成員的默認角色是",
|
||||
"The main address for this room is": "此聊天室的主要地址是",
|
||||
"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 email address is already in use": "此郵箱地址已經被使用",
|
||||
"This email address was not found": "未找到此郵箱地址",
|
||||
"%(actionVerb)s this person?": "%(actionVerb)s 這個用戶?",
|
||||
|
@ -290,7 +290,7 @@
|
|||
"Would you like to": "你要",
|
||||
"You are already in a call.": "你已在電話通話中",
|
||||
"You're not in any rooms yet! Press": "你尚未加入任何聊天室!請按",
|
||||
"You are trying to access %(roomName)s": "你將進入 %(roomName)聊天室",
|
||||
"You are trying to access %(roomName)s.": "你將進入 %(roomName)聊天室",
|
||||
"You cannot place a call with yourself.": "你不能打電話給自已",
|
||||
"You cannot place VoIP calls in this browser.": "在此瀏覽器下無法置入網路電話通話",
|
||||
"Sun": "星期日",
|
||||
|
|
|
@ -23,16 +23,38 @@ import { _t } from '../languageHandler';
|
|||
const INITIAL_STATE = {
|
||||
// Whether we're joining the currently viewed room
|
||||
joining: false,
|
||||
// Any error occurred during joining
|
||||
// Any error that has occurred during joining
|
||||
joinError: null,
|
||||
// The room ID of the room
|
||||
// The room ID of the room currently being viewed
|
||||
roomId: null,
|
||||
|
||||
// The event to scroll to when the room is first viewed
|
||||
initialEventId: null,
|
||||
// The offset to display the initial event at (see scrollStateMap)
|
||||
initialEventPixelOffset: null,
|
||||
// Whether to highlight the initial event
|
||||
isInitialEventHighlighted: false,
|
||||
|
||||
// The room alias of the room (or null if not originally specified in view_room)
|
||||
roomAlias: null,
|
||||
// Whether the current room is loading
|
||||
roomLoading: false,
|
||||
// Any error that has occurred during loading
|
||||
roomLoadError: null,
|
||||
// A map from room id to scroll state.
|
||||
//
|
||||
// If there is no special scroll state (ie, we are following the live
|
||||
// timeline), the scroll state is null. Otherwise, it is an object with
|
||||
// the following properties:
|
||||
//
|
||||
// focussedEvent: the ID of the 'focussed' event. Typically this is
|
||||
// the last event fully visible in the viewport, though if we
|
||||
// have done an explicit scroll to an explicit event, it will be
|
||||
// that event.
|
||||
//
|
||||
// pixelOffset: the number of pixels the window is scrolled down
|
||||
// from the focussedEvent.
|
||||
scrollStateMap: {},
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -56,8 +78,11 @@ class RoomViewStore extends Store {
|
|||
__onDispatch(payload) {
|
||||
switch (payload.action) {
|
||||
// view_room:
|
||||
// - room_alias: '#somealias:matrix.org'
|
||||
// - room_id: '!roomid123:matrix.org'
|
||||
// - room_alias: '#somealias:matrix.org'
|
||||
// - room_id: '!roomid123:matrix.org'
|
||||
// - event_id: '$213456782:matrix.org'
|
||||
// - event_offset: 100
|
||||
// - highlighted: true
|
||||
case 'view_room':
|
||||
this._viewRoom(payload);
|
||||
break;
|
||||
|
@ -88,20 +113,41 @@ class RoomViewStore extends Store {
|
|||
case 'on_logged_out':
|
||||
this.reset();
|
||||
break;
|
||||
case 'update_scroll_state':
|
||||
this._updateScrollState(payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_viewRoom(payload) {
|
||||
// Always set the room ID if present
|
||||
if (payload.room_id) {
|
||||
this._setState({
|
||||
const newState = {
|
||||
roomId: payload.room_id,
|
||||
initialEventId: payload.event_id,
|
||||
initialEventPixelOffset: undefined,
|
||||
isInitialEventHighlighted: payload.highlighted,
|
||||
roomLoading: false,
|
||||
roomLoadError: null,
|
||||
});
|
||||
};
|
||||
|
||||
// If an event ID wasn't specified, default to the one saved for this room
|
||||
// via update_scroll_state. Assume initialEventPixelOffset should be set.
|
||||
if (!newState.initialEventId) {
|
||||
const roomScrollState = this._state.scrollStateMap[payload.room_id];
|
||||
if (roomScrollState) {
|
||||
newState.initialEventId = roomScrollState.focussedEvent;
|
||||
newState.initialEventPixelOffset = roomScrollState.pixelOffset;
|
||||
}
|
||||
}
|
||||
|
||||
this._setState(newState);
|
||||
} else if (payload.room_alias) {
|
||||
// Resolve the alias and then do a second dispatch with the room ID acquired
|
||||
this._setState({
|
||||
roomId: null,
|
||||
initialEventId: null,
|
||||
initialEventPixelOffset: null,
|
||||
isInitialEventHighlighted: null,
|
||||
roomAlias: payload.room_alias,
|
||||
roomLoading: true,
|
||||
roomLoadError: null,
|
||||
|
@ -111,6 +157,8 @@ class RoomViewStore extends Store {
|
|||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: result.room_id,
|
||||
event_id: payload.event_id,
|
||||
highlighted: payload.highlighted,
|
||||
room_alias: payload.room_alias,
|
||||
});
|
||||
}, (err) => {
|
||||
|
@ -137,7 +185,9 @@ class RoomViewStore extends Store {
|
|||
this._setState({
|
||||
joining: true,
|
||||
});
|
||||
MatrixClientPeg.get().joinRoom(this._state.roomId, payload.opts).done(() => {
|
||||
MatrixClientPeg.get().joinRoom(
|
||||
this._state.roomAlias || this._state.roomId, payload.opts,
|
||||
).done(() => {
|
||||
dis.dispatch({
|
||||
action: 'joined_room',
|
||||
});
|
||||
|
@ -168,34 +218,63 @@ class RoomViewStore extends Store {
|
|||
});
|
||||
}
|
||||
|
||||
_updateScrollState(payload) {
|
||||
// Clobber existing scroll state for the given room ID
|
||||
const newScrollStateMap = this._state.scrollStateMap;
|
||||
newScrollStateMap[payload.room_id] = payload.scroll_state;
|
||||
this._setState({
|
||||
scrollStateMap: newScrollStateMap,
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._state = Object.assign({}, INITIAL_STATE);
|
||||
}
|
||||
|
||||
// The room ID of the room currently being viewed
|
||||
getRoomId() {
|
||||
return this._state.roomId;
|
||||
}
|
||||
|
||||
// The event to scroll to when the room is first viewed
|
||||
getInitialEventId() {
|
||||
return this._state.initialEventId;
|
||||
}
|
||||
|
||||
// The offset to display the initial event at (see scrollStateMap)
|
||||
getInitialEventPixelOffset() {
|
||||
return this._state.initialEventPixelOffset;
|
||||
}
|
||||
|
||||
// Whether to highlight the initial event
|
||||
isInitialEventHighlighted() {
|
||||
return this._state.isInitialEventHighlighted;
|
||||
}
|
||||
|
||||
// The room alias of the room (or null if not originally specified in view_room)
|
||||
getRoomAlias() {
|
||||
return this._state.roomAlias;
|
||||
}
|
||||
|
||||
// Whether the current room is loading (true whilst resolving an alias)
|
||||
isRoomLoading() {
|
||||
return this._state.roomLoading;
|
||||
}
|
||||
|
||||
// Any error that has occurred during loading
|
||||
getRoomLoadError() {
|
||||
return this._state.roomLoadError;
|
||||
}
|
||||
|
||||
// Whether we're joining the currently viewed room
|
||||
isJoining() {
|
||||
return this._state.joining;
|
||||
}
|
||||
|
||||
// Any error that has occurred during joining
|
||||
getJoinError() {
|
||||
return this._state.joinError;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let singletonRoomViewStore = null;
|
||||
|
|
|
@ -32,7 +32,7 @@ const subtleCrypto = window.crypto.subtle || window.crypto.webkitSubtle;
|
|||
/**
|
||||
* Decrypt a megolm key file
|
||||
*
|
||||
* @param {ArrayBuffer} file
|
||||
* @param {ArrayBuffer} data file to decrypt
|
||||
* @param {String} password
|
||||
* @return {Promise<String>} promise for decrypted output
|
||||
*/
|
||||
|
@ -61,12 +61,12 @@ export function decryptMegolmKeyFile(data, password) {
|
|||
const hmac = body.subarray(-32);
|
||||
|
||||
return deriveKeys(salt, iterations, password).then((keys) => {
|
||||
const [aes_key, hmac_key] = keys;
|
||||
const [aesKey, hmacKey] = keys;
|
||||
|
||||
const toVerify = body.subarray(0, -32);
|
||||
return subtleCrypto.verify(
|
||||
{name: 'HMAC'},
|
||||
hmac_key,
|
||||
hmacKey,
|
||||
hmac,
|
||||
toVerify,
|
||||
).then((isValid) => {
|
||||
|
@ -80,7 +80,7 @@ export function decryptMegolmKeyFile(data, password) {
|
|||
counter: iv,
|
||||
length: 64,
|
||||
},
|
||||
aes_key,
|
||||
aesKey,
|
||||
ciphertext,
|
||||
);
|
||||
});
|
||||
|
@ -102,7 +102,7 @@ export function decryptMegolmKeyFile(data, password) {
|
|||
*/
|
||||
export function encryptMegolmKeyFile(data, password, options) {
|
||||
options = options || {};
|
||||
const kdf_rounds = options.kdf_rounds || 500000;
|
||||
const kdfRounds = options.kdf_rounds || 500000;
|
||||
|
||||
const salt = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(salt);
|
||||
|
@ -115,8 +115,8 @@ export function encryptMegolmKeyFile(data, password, options) {
|
|||
// of a single bit of iv is a price we have to pay.
|
||||
iv[9] &= 0x7f;
|
||||
|
||||
return deriveKeys(salt, kdf_rounds, password).then((keys) => {
|
||||
const [aes_key, hmac_key] = keys;
|
||||
return deriveKeys(salt, kdfRounds, password).then((keys) => {
|
||||
const [aesKey, hmacKey] = keys;
|
||||
|
||||
return subtleCrypto.encrypt(
|
||||
{
|
||||
|
@ -124,7 +124,7 @@ export function encryptMegolmKeyFile(data, password, options) {
|
|||
counter: iv,
|
||||
length: 64,
|
||||
},
|
||||
aes_key,
|
||||
aesKey,
|
||||
new TextEncoder().encode(data),
|
||||
).then((ciphertext) => {
|
||||
const cipherArray = new Uint8Array(ciphertext);
|
||||
|
@ -134,17 +134,17 @@ export function encryptMegolmKeyFile(data, password, options) {
|
|||
resultBuffer[idx++] = 1; // version
|
||||
resultBuffer.set(salt, idx); idx += salt.length;
|
||||
resultBuffer.set(iv, idx); idx += iv.length;
|
||||
resultBuffer[idx++] = kdf_rounds >> 24;
|
||||
resultBuffer[idx++] = (kdf_rounds >> 16) & 0xff;
|
||||
resultBuffer[idx++] = (kdf_rounds >> 8) & 0xff;
|
||||
resultBuffer[idx++] = kdf_rounds & 0xff;
|
||||
resultBuffer[idx++] = kdfRounds >> 24;
|
||||
resultBuffer[idx++] = (kdfRounds >> 16) & 0xff;
|
||||
resultBuffer[idx++] = (kdfRounds >> 8) & 0xff;
|
||||
resultBuffer[idx++] = kdfRounds & 0xff;
|
||||
resultBuffer.set(cipherArray, idx); idx += cipherArray.length;
|
||||
|
||||
const toSign = resultBuffer.subarray(0, idx);
|
||||
|
||||
return subtleCrypto.sign(
|
||||
{name: 'HMAC'},
|
||||
hmac_key,
|
||||
hmacKey,
|
||||
toSign,
|
||||
).then((hmac) => {
|
||||
hmac = new Uint8Array(hmac);
|
||||
|
@ -170,7 +170,7 @@ function deriveKeys(salt, iterations, password) {
|
|||
new TextEncoder().encode(password),
|
||||
{name: 'PBKDF2'},
|
||||
false,
|
||||
['deriveBits']
|
||||
['deriveBits'],
|
||||
).then((key) => {
|
||||
return subtleCrypto.deriveBits(
|
||||
{
|
||||
|
@ -180,33 +180,33 @@ function deriveKeys(salt, iterations, password) {
|
|||
hash: 'SHA-512',
|
||||
},
|
||||
key,
|
||||
512
|
||||
512,
|
||||
);
|
||||
}).then((keybits) => {
|
||||
const now = new Date();
|
||||
console.log("E2e import/export: deriveKeys took " + (now - start) + "ms");
|
||||
|
||||
const aes_key = keybits.slice(0, 32);
|
||||
const hmac_key = keybits.slice(32);
|
||||
const aesKey = keybits.slice(0, 32);
|
||||
const hmacKey = keybits.slice(32);
|
||||
|
||||
const aes_prom = subtleCrypto.importKey(
|
||||
const aesProm = subtleCrypto.importKey(
|
||||
'raw',
|
||||
aes_key,
|
||||
aesKey,
|
||||
{name: 'AES-CTR'},
|
||||
false,
|
||||
['encrypt', 'decrypt']
|
||||
['encrypt', 'decrypt'],
|
||||
);
|
||||
const hmac_prom = subtleCrypto.importKey(
|
||||
const hmacProm = subtleCrypto.importKey(
|
||||
'raw',
|
||||
hmac_key,
|
||||
hmacKey,
|
||||
{
|
||||
name: 'HMAC',
|
||||
hash: {name: 'SHA-256'},
|
||||
},
|
||||
false,
|
||||
['sign', 'verify']
|
||||
['sign', 'verify'],
|
||||
);
|
||||
return Promise.all([aes_prom, hmac_prom]);
|
||||
return Promise.all([aesProm, hmacProm]);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -301,7 +301,7 @@ function packMegolmKeyFile(data) {
|
|||
function encodeBase64(uint8Array) {
|
||||
// Misinterpt the Uint8Array as Latin-1.
|
||||
// window.btoa expects a unicode string with codepoints in the range 0-255.
|
||||
var latin1String = String.fromCharCode.apply(null, uint8Array);
|
||||
const latin1String = String.fromCharCode.apply(null, uint8Array);
|
||||
// Use the builtin base64 encoder.
|
||||
return window.btoa(latin1String);
|
||||
}
|
||||
|
@ -313,10 +313,10 @@ function encodeBase64(uint8Array) {
|
|||
*/
|
||||
function decodeBase64(base64) {
|
||||
// window.atob returns a unicode string with codepoints in the range 0-255.
|
||||
var latin1String = window.atob(base64);
|
||||
const latin1String = window.atob(base64);
|
||||
// Encode the string as a Uint8Array
|
||||
var uint8Array = new Uint8Array(latin1String.length);
|
||||
for (var i = 0; i < latin1String.length; i++) {
|
||||
const uint8Array = new Uint8Array(latin1String.length);
|
||||
for (let i = 0; i < latin1String.length; i++) {
|
||||
uint8Array[i] = latin1String.charCodeAt(i);
|
||||
}
|
||||
return uint8Array;
|
||||
|
|
|
@ -2,4 +2,9 @@ module.exports = {
|
|||
env: {
|
||||
mocha: true,
|
||||
},
|
||||
}
|
||||
|
||||
// mocha defines a 'this'
|
||||
rules: {
|
||||
"babel/no-invalid-this": "off",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -28,8 +28,8 @@ describe('RoomViewStore', function() {
|
|||
});
|
||||
|
||||
it('can be used to view a room by ID and join', function(done) {
|
||||
peg.get().joinRoom = (roomId) => {
|
||||
expect(roomId).toBe("!randomcharacters:aser.ver");
|
||||
peg.get().joinRoom = (roomAddress) => {
|
||||
expect(roomAddress).toBe("!randomcharacters:aser.ver");
|
||||
done();
|
||||
};
|
||||
|
||||
|
@ -40,8 +40,8 @@ describe('RoomViewStore', function() {
|
|||
|
||||
it('can be used to view a room by alias and join', function(done) {
|
||||
peg.get().getRoomIdForAlias.returns(q({room_id: "!randomcharacters:aser.ver"}));
|
||||
peg.get().joinRoom = (roomId) => {
|
||||
expect(roomId).toBe("!randomcharacters:aser.ver");
|
||||
peg.get().joinRoom = (roomAddress) => {
|
||||
expect(roomAddress).toBe("#somealias2:aser.ver");
|
||||
done();
|
||||
};
|
||||
|
||||
|
|
|
@ -25,23 +25,40 @@ const TEST_VECTORS=[
|
|||
[
|
||||
"plain",
|
||||
"password",
|
||||
"-----BEGIN MEGOLM SESSION DATA-----\nAXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\ncissyYBxjsfsAndErh065A8=\n-----END MEGOLM SESSION DATA-----"
|
||||
"-----BEGIN MEGOLM SESSION DATA-----\n" +
|
||||
"AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx\n" +
|
||||
"cissyYBxjsfsAndErh065A8=\n" +
|
||||
"-----END MEGOLM SESSION DATA-----",
|
||||
],
|
||||
[
|
||||
"Hello, World",
|
||||
"betterpassword",
|
||||
"-----BEGIN MEGOLM SESSION DATA-----\nAW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\nKYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n-----END MEGOLM SESSION DATA-----"
|
||||
"-----BEGIN MEGOLM SESSION DATA-----\n" +
|
||||
"AW1vcmVzYWx0bW9yZXNhbHT//////////wAAAAAAAAAAAAAD6KyBpe1Niv5M5NPm4ZATsJo5nghk\n" +
|
||||
"KYu63a0YQ5DRhUWEKk7CcMkrKnAUiZny\n" +
|
||||
"-----END MEGOLM SESSION DATA-----",
|
||||
],
|
||||
[
|
||||
"alphanumericallyalphanumericallyalphanumericallyalphanumerically",
|
||||
"SWORDFISH",
|
||||
"-----BEGIN MEGOLM SESSION DATA-----\nAXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\nMgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\nPgg29363BGR+/Ripq/VCLKGNbw==\n-----END MEGOLM SESSION DATA-----"
|
||||
"-----BEGIN MEGOLM SESSION DATA-----\n" +
|
||||
"AXllc3NhbHR5Z29vZG5lc3P//////////wAAAAAAAAAAAAAD6OIW+Je7gwvjd4kYrb+49gKCfExw\n" +
|
||||
"MgJBMD4mrhLkmgAngwR1pHjbWXaoGybtiAYr0moQ93GrBQsCzPbvl82rZhaXO3iH5uHo/RCEpOqp\n" +
|
||||
"Pgg29363BGR+/Ripq/VCLKGNbw==\n" +
|
||||
"-----END MEGOLM SESSION DATA-----",
|
||||
],
|
||||
[
|
||||
"alphanumericallyalphanumericallyalphanumericallyalphanumerically",
|
||||
"passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword",
|
||||
"-----BEGIN MEGOLM SESSION DATA-----\nAf//////////////////////////////////////////AAAD6IAZJy7IQ7Y0idqSw/bmpngEEVVh\ngsH+8ptgqxw6ZVWQnohr8JsuwH9SwGtiebZuBu5smPCO+RFVWH2cQYslZijXv/BEH/txvhUrrtCd\nbWnSXS9oymiqwUIGs08sXI33ZA==\n-----END MEGOLM SESSION DATA-----"
|
||||
]
|
||||
"passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" +
|
||||
"passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" +
|
||||
"passwordpasswordpasswordpasswordpasswordpasswordpasswordpasswordpassword" +
|
||||
"passwordpasswordpasswordpasswordpassword",
|
||||
"-----BEGIN MEGOLM SESSION DATA-----\n" +
|
||||
"Af//////////////////////////////////////////AAAD6IAZJy7IQ7Y0idqSw/bmpngEEVVh\n" +
|
||||
"gsH+8ptgqxw6ZVWQnohr8JsuwH9SwGtiebZuBu5smPCO+RFVWH2cQYslZijXv/BEH/txvhUrrtCd\n" +
|
||||
"bWnSXS9oymiqwUIGs08sXI33ZA==\n" +
|
||||
"-----END MEGOLM SESSION DATA-----",
|
||||
],
|
||||
]
|
||||
;
|
||||
|
||||
|
@ -55,7 +72,7 @@ describe('MegolmExportEncryption', function() {
|
|||
if (!window.crypto.subtle && !window.crypto.webkitSubtle) {
|
||||
this.skip();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
testUtils.beforeEach(this);
|
||||
|
@ -64,14 +81,14 @@ describe('MegolmExportEncryption', function() {
|
|||
describe('decrypt', function() {
|
||||
it('should handle missing header', function() {
|
||||
const input=stringToArray(`-----`);
|
||||
expect(()=>{MegolmExportEncryption.decryptMegolmKeyFile(input, '')})
|
||||
expect(()=>MegolmExportEncryption.decryptMegolmKeyFile(input, ''))
|
||||
.toThrow('Header line not found');
|
||||
});
|
||||
|
||||
it('should handle missing trailer', function() {
|
||||
const input=stringToArray(`-----BEGIN MEGOLM SESSION DATA-----
|
||||
-----`);
|
||||
expect(()=>{MegolmExportEncryption.decryptMegolmKeyFile(input, '')})
|
||||
expect(()=>MegolmExportEncryption.decryptMegolmKeyFile(input, ''))
|
||||
.toThrow('Trailer line not found');
|
||||
});
|
||||
|
||||
|
@ -81,7 +98,7 @@ AXNhbHRzYWx0c2FsdHNhbHSIiIiIiIiIiIiIiIiIiIiIAAAACmIRUW2OjZ3L2l6j9h0lHlV3M2dx
|
|||
cissyYBxjsfsAn
|
||||
-----END MEGOLM SESSION DATA-----
|
||||
`);
|
||||
expect(()=>{MegolmExportEncryption.decryptMegolmKeyFile(input, '')})
|
||||
expect(()=>MegolmExportEncryption.decryptMegolmKeyFile(input, ''))
|
||||
.toThrow('Invalid file: too short');
|
||||
});
|
||||
|
||||
|
@ -94,12 +111,12 @@ cissyYBxjsfsAn
|
|||
|
||||
const [plain, password, input] = TEST_VECTORS[i];
|
||||
return MegolmExportEncryption.decryptMegolmKeyFile(
|
||||
stringToArray(input), password
|
||||
stringToArray(input), password,
|
||||
).then((decrypted) => {
|
||||
expect(decrypted).toEqual(plain);
|
||||
return next(i+1);
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
return next(0).catch(done);
|
||||
});
|
||||
});
|
||||
|
@ -115,7 +132,7 @@ cissyYBxjsfsAn
|
|||
input, password, {kdf_rounds: 1000},
|
||||
).then((ciphertext) => {
|
||||
return MegolmExportEncryption.decryptMegolmKeyFile(
|
||||
ciphertext, password
|
||||
ciphertext, password,
|
||||
);
|
||||
}).then((plaintext) => {
|
||||
expect(plaintext).toEqual(input);
|
||||
|
|
Loading…
Reference in a new issue