Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into webrtc_settings
and i18nize webrtc stufffs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> # Conflicts: # src/components/structures/UserSettings.js
This commit is contained in:
commit
04b86e5d1d
135 changed files with 8102 additions and 1115 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -10,5 +10,5 @@ npm-debug.log
|
|||
# test reports created by karma
|
||||
/karma-reports
|
||||
|
||||
# ignore auto-generated component index
|
||||
/.idea
|
||||
/src/component-index.js
|
||||
|
|
|
@ -5,5 +5,6 @@ install:
|
|||
- npm install
|
||||
- (cd node_modules/matrix-js-sdk && npm install)
|
||||
script:
|
||||
- npm run test
|
||||
- ./.travis-test-riot.sh
|
||||
# don't run the riot tests unless the react-sdk tests pass, otherwise
|
||||
# the output is confusing.
|
||||
- npm run test && ./.travis-test-riot.sh
|
||||
|
|
|
@ -24,6 +24,10 @@ In the interim, `vector-im/riot-web` and `matrix-org/matrix-react-sdk` should
|
|||
be considered as a single project (for instance, matrix-react-sdk bugs
|
||||
are currently filed against vector-im/riot-web rather than this project).
|
||||
|
||||
Translation Status
|
||||
==================
|
||||
[![translationsstatus](https://translate.nordgedanken.de/widgets/riot-web/-/multi-auto.svg)](https://translate.nordgedanken.de/engage/riot-web/?utm_source=widget)
|
||||
|
||||
Developer Guide
|
||||
===============
|
||||
|
||||
|
@ -190,4 +194,3 @@ Alternative instructions:
|
|||
* Create an index.html file pulling in your compiled javascript and the
|
||||
CSS bundle from the skin you use. For now, you'll also need to manually
|
||||
import CSS from any skins that your skin inherts from.
|
||||
|
||||
|
|
|
@ -55,11 +55,18 @@ module.exports = function (config) {
|
|||
// some images to reduce noise from the tests
|
||||
{pattern: 'test/img/*', watched: false, included: false,
|
||||
served: true, nocache: false},
|
||||
// translation files
|
||||
{pattern: 'src/i18n/strings/*', watcheed: false, included: false, served: true},
|
||||
{pattern: 'test/i18n/*', watched: false, included: false, served: true},
|
||||
],
|
||||
|
||||
// redirect img links to the karma server
|
||||
proxies: {
|
||||
// redirect img links to the karma server
|
||||
"/img/": "/base/test/img/",
|
||||
// special languages.json file for the tests
|
||||
"/i18n/languages.json": "/base/test/i18n/languages.json",
|
||||
// and redirect i18n requests
|
||||
"/i18n/": "/base/src/i18n/strings/",
|
||||
},
|
||||
|
||||
// list of files to exclude
|
||||
|
@ -166,11 +173,15 @@ module.exports = function (config) {
|
|||
'sinon': 'sinon/pkg/sinon.js',
|
||||
},
|
||||
root: [
|
||||
path.resolve('./src'),
|
||||
path.resolve('./test'),
|
||||
],
|
||||
},
|
||||
devtool: 'inline-source-map',
|
||||
externals: {
|
||||
// Don't try to bundle electron: leave it as a commonjs dependency
|
||||
// (the 'commonjs' here means it will output a 'require')
|
||||
"electron": "commonjs electron",
|
||||
},
|
||||
},
|
||||
|
||||
webpackMiddleware: {
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
"browser-request": "^0.3.3",
|
||||
"classnames": "^2.1.2",
|
||||
"commonmark": "^0.27.0",
|
||||
"counterpart": "^0.18.0",
|
||||
"draft-js": "^0.8.1",
|
||||
"draft-js-export-html": "^0.5.0",
|
||||
"draft-js-export-markdown": "^0.2.0",
|
||||
|
@ -63,7 +64,7 @@
|
|||
"isomorphic-fetch": "^2.2.1",
|
||||
"linkifyjs": "^2.1.3",
|
||||
"lodash": "^4.13.1",
|
||||
"matrix-js-sdk": "0.7.8",
|
||||
"matrix-js-sdk": "0.7.9",
|
||||
"optimist": "^0.6.1",
|
||||
"q": "^1.4.1",
|
||||
"react": "^15.4.0",
|
||||
|
|
192
scripts/check-i18n.pl
Executable file
192
scripts/check-i18n.pl
Executable file
|
@ -0,0 +1,192 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Cwd 'abs_path';
|
||||
|
||||
# script which checks how out of sync the i18ns are drifting
|
||||
|
||||
# example i18n format:
|
||||
# "%(oneUser)sleft": "%(oneUser)sleft",
|
||||
|
||||
$|=1;
|
||||
|
||||
$0 =~ /^(.*\/)/;
|
||||
my $i18ndir = abs_path($1."/../src/i18n/strings");
|
||||
my $srcdir = abs_path($1."/../src");
|
||||
|
||||
my $en = read_i18n($i18ndir."/en_EN.json");
|
||||
|
||||
my $src_strings = read_src_strings($srcdir);
|
||||
my $src = {};
|
||||
|
||||
print "Checking strings in src\n";
|
||||
foreach my $tuple (@$src_strings) {
|
||||
my ($s, $file) = (@$tuple);
|
||||
$src->{$s} = $file;
|
||||
if (!$en->{$s}) {
|
||||
if ($en->{$s . '.'}) {
|
||||
printf ("%50s %24s\t%s\n", $file, "en_EN has fullstop!", $s);
|
||||
}
|
||||
else {
|
||||
$s =~ /^(.*)\.?$/;
|
||||
if ($en->{$1}) {
|
||||
printf ("%50s %24s\t%s\n", $file, "en_EN lacks fullstop!", $s);
|
||||
}
|
||||
else {
|
||||
printf ("%50s %24s\t%s\n", $file, "Translation missing!", $s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print "\nChecking en_EN\n";
|
||||
my $count = 0;
|
||||
my $remaining_src = {};
|
||||
foreach (keys %$src) { $remaining_src->{$_}++ };
|
||||
|
||||
foreach my $k (sort keys %$en) {
|
||||
# crappy heuristic to ignore country codes for now...
|
||||
next if ($k =~ /^(..|..-..)$/);
|
||||
|
||||
if ($en->{$k} ne $k) {
|
||||
printf ("%50s %24s\t%s\n", "en_EN", "en_EN is not symmetrical", $k);
|
||||
}
|
||||
|
||||
if (!$src->{$k}) {
|
||||
if ($src->{$k. '.'}) {
|
||||
printf ("%50s %24s\t%s\n", $src->{$k. '.'}, "src has fullstop!", $k);
|
||||
}
|
||||
else {
|
||||
$k =~ /^(.*)\.?$/;
|
||||
if ($src->{$1}) {
|
||||
printf ("%50s %24s\t%s\n", $src->{$1}, "src lacks fullstop!", $k);
|
||||
}
|
||||
else {
|
||||
printf ("%50s %24s\t%s\n", '???', "Not present in src?", $k);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$count++;
|
||||
delete $remaining_src->{$k};
|
||||
}
|
||||
}
|
||||
printf ("$count/" . (scalar keys %$src) . " strings found in src are present in en_EN\n");
|
||||
foreach (keys %$remaining_src) {
|
||||
print "missing: $_\n";
|
||||
}
|
||||
|
||||
opendir(DIR, $i18ndir) || die $!;
|
||||
my @files = readdir(DIR);
|
||||
closedir(DIR);
|
||||
foreach my $lang (grep { -f "$i18ndir/$_" && !/(basefile|en_EN)\.json/ } @files) {
|
||||
print "\nChecking $lang\n";
|
||||
|
||||
my $map = read_i18n($i18ndir."/".$lang);
|
||||
my $count = 0;
|
||||
|
||||
my $remaining_en = {};
|
||||
foreach (keys %$en) { $remaining_en->{$_}++ };
|
||||
|
||||
foreach my $k (sort keys %$map) {
|
||||
{
|
||||
no warnings 'uninitialized';
|
||||
my $vars = {};
|
||||
while ($k =~ /%\((.*?)\)s/g) {
|
||||
$vars->{$1}++;
|
||||
}
|
||||
while ($map->{$k} =~ /%\((.*?)\)s/g) {
|
||||
$vars->{$1}--;
|
||||
}
|
||||
foreach my $var (keys %$vars) {
|
||||
if ($vars->{$var} != 0) {
|
||||
printf ("%10s %24s\t%s\n", $lang, "Broken var ($var)s", $k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($en->{$k}) {
|
||||
if ($map->{$k} eq $k) {
|
||||
printf ("%10s %24s\t%s\n", $lang, "Untranslated string?", $k);
|
||||
}
|
||||
$count++;
|
||||
delete $remaining_en->{$k};
|
||||
}
|
||||
else {
|
||||
if ($en->{$k . "."}) {
|
||||
printf ("%10s %24s\t%s\n", $lang, "en_EN has fullstop!", $k);
|
||||
next;
|
||||
}
|
||||
|
||||
$k =~ /^(.*)\.?$/;
|
||||
if ($en->{$1}) {
|
||||
printf ("%10s %24s\t%s\n", $lang, "en_EN lacks fullstop!", $k);
|
||||
next;
|
||||
}
|
||||
|
||||
printf ("%10s %24s\t%s\n", $lang, "Not present in en_EN", $k);
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar keys %$remaining_en < 100) {
|
||||
foreach (keys %$remaining_en) {
|
||||
printf ("%10s %24s\t%s\n", $lang, "Not yet translated", $_);
|
||||
}
|
||||
}
|
||||
|
||||
printf ("$count/" . (scalar keys %$en) . " strings translated\n");
|
||||
}
|
||||
|
||||
sub read_i18n {
|
||||
my $path = shift;
|
||||
my $map = {};
|
||||
$path =~ /.*\/(.*)$/;
|
||||
my $lang = $1;
|
||||
|
||||
open(FILE, "<", $path) || die $!;
|
||||
while(<FILE>) {
|
||||
if ($_ =~ m/^(\s+)"(.*?)"(: *)"(.*?)"(,?)$/) {
|
||||
my ($indent, $src, $colon, $dst, $comma) = ($1, $2, $3, $4, $5);
|
||||
$src =~ s/\\"/"/g;
|
||||
$dst =~ s/\\"/"/g;
|
||||
|
||||
if ($map->{$src}) {
|
||||
printf ("%10s %24s\t%s\n", $lang, "Duplicate translation!", $src);
|
||||
}
|
||||
$map->{$src} = $dst;
|
||||
}
|
||||
}
|
||||
close(FILE);
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
sub read_src_strings {
|
||||
my $path = shift;
|
||||
|
||||
use File::Find;
|
||||
use File::Slurp;
|
||||
|
||||
my $strings = [];
|
||||
|
||||
my @files;
|
||||
find( sub { push @files, $File::Find::name if (-f $_ && /\.jsx?$/) }, $path );
|
||||
foreach my $file (@files) {
|
||||
my $src = read_file($file);
|
||||
$src =~ s/'\s*\+\s*'//g;
|
||||
$src =~ s/"\s*\+\s*"//g;
|
||||
|
||||
$file =~ s/^.*\/src/src/;
|
||||
while ($src =~ /_t(?:Jsx)?\(\s*'(.*?[^\\])'/sg) {
|
||||
my $s = $1;
|
||||
$s =~ s/\\'/'/g;
|
||||
push @$strings, [$s, $file];
|
||||
}
|
||||
while ($src =~ /_t(?:Jsx)?\(\s*"(.*?[^\\])"/sg) {
|
||||
push @$strings, [$1, $file];
|
||||
}
|
||||
}
|
||||
|
||||
return $strings;
|
||||
}
|
104
scripts/fix-i18n.pl
Executable file
104
scripts/fix-i18n.pl
Executable file
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/perl -ni
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# script which synchronises i18n strings to include punctuation.
|
||||
# i've cherry-picked ones which seem to have diverged between the different translations
|
||||
# from TextForEvent, causing missing events all over the place
|
||||
|
||||
BEGIN {
|
||||
$::fixups = [split(/\n/, <<EOT
|
||||
%(targetName)s accepted the invitation for %(displayName)s.
|
||||
%(targetName)s accepted an invitation.
|
||||
%(senderName)s requested a VoIP conference.
|
||||
%(senderName)s invited %(targetName)s.
|
||||
%(senderName)s banned %(targetName)s.
|
||||
%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.
|
||||
%(senderName)s set their display name to %(displayName)s.
|
||||
%(senderName)s removed their display name (%(oldDisplayName)s).
|
||||
%(senderName)s removed their profile picture.
|
||||
%(senderName)s changed their profile picture.
|
||||
%(senderName)s set a profile picture.
|
||||
VoIP conference started.
|
||||
%(targetName)s joined the room.
|
||||
VoIP conference finished.
|
||||
%(targetName)s rejected the invitation.
|
||||
%(targetName)s left the room.
|
||||
%(senderName)s unbanned %(targetName)s.
|
||||
%(senderName)s kicked %(targetName)s.
|
||||
%(senderName)s withdrew %(targetName)s's inivitation.
|
||||
%(targetName)s left the room.
|
||||
%(senderDisplayName)s changed the topic to "%(topic)s".
|
||||
%(senderDisplayName)s changed the room name to %(roomName)s.
|
||||
%(senderDisplayName)s sent an image.
|
||||
%(senderName)s answered the call.
|
||||
%(senderName)s ended the call.
|
||||
%(senderName)s placed a %(callType)s call.
|
||||
%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.
|
||||
%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).
|
||||
%(senderName)s changed the power level of %(powerLevelDiffText)s.
|
||||
For security, this session has been signed out. Please sign in again.
|
||||
You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.
|
||||
A new password must be entered.
|
||||
Guests can't set avatars. Please register.
|
||||
Failed to set avatar.
|
||||
Unable to verify email address.
|
||||
Guests can't use labs features. Please register.
|
||||
A new password must be entered.
|
||||
Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.
|
||||
Guests cannot join this room even if explicitly invited.
|
||||
Guest users can't invite users. Please register to invite.
|
||||
This room is inaccessible to guests. You may be able to join if you register.
|
||||
delete the alias.
|
||||
remove %(name)s from the directory.
|
||||
Conference call failed.
|
||||
Conference calling is in development and may not be reliable.
|
||||
Guest users can't create new rooms. Please register to create room and start a chat.
|
||||
Server may be unavailable, overloaded, or you hit a bug.
|
||||
Server unavailable, overloaded, or something else went wrong.
|
||||
You are already in a call.
|
||||
You cannot place VoIP calls in this browser.
|
||||
You cannot place a call with yourself.
|
||||
Your email address does not appear to be associated with a Matrix ID on this Homeserver.
|
||||
EOT
|
||||
)];
|
||||
}
|
||||
|
||||
# example i18n format:
|
||||
# "%(oneUser)sleft": "%(oneUser)sleft",
|
||||
|
||||
# script called with the line of the file to be checked
|
||||
my $sub = 0;
|
||||
if ($_ =~ m/^(\s+)"(.*?)"(: *)"(.*?)"(,?)$/) {
|
||||
my ($indent, $src, $colon, $dst, $comma) = ($1, $2, $3, $4, $5);
|
||||
$src =~ s/\\"/"/g;
|
||||
$dst =~ s/\\"/"/g;
|
||||
|
||||
foreach my $fixup (@{$::fixups}) {
|
||||
my $dotless_fixup = substr($fixup, 0, -1);
|
||||
|
||||
if ($src eq $dotless_fixup) {
|
||||
print STDERR "fixing up src: $src\n";
|
||||
$src .= '.';
|
||||
$sub = 1;
|
||||
}
|
||||
|
||||
if ($src eq $fixup && $dst !~ /\.$/) {
|
||||
print STDERR "fixing up dst: $dst\n";
|
||||
$dst .= '.';
|
||||
$sub = 1;
|
||||
}
|
||||
|
||||
if ($sub) {
|
||||
$src =~ s/"/\\"/g;
|
||||
$dst =~ s/"/\\"/g;
|
||||
print qq($indent"$src"$colon"$dst"$comma\n);
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$sub) {
|
||||
print $_;
|
||||
}
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
/**
|
||||
* Allows a user to add a third party identifier to their Home Server and,
|
||||
|
@ -44,7 +45,7 @@ class AddThreepid {
|
|||
return res;
|
||||
}, function(err) {
|
||||
if (err.errcode == 'M_THREEPID_IN_USE') {
|
||||
err.message = "This email address is already in use";
|
||||
err.message = _t('This email address is already in use');
|
||||
} else if (err.httpStatus) {
|
||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
||||
}
|
||||
|
@ -69,7 +70,7 @@ class AddThreepid {
|
|||
return res;
|
||||
}, function(err) {
|
||||
if (err.errcode == 'M_THREEPID_IN_USE') {
|
||||
err.message = "This phone number is already in use";
|
||||
err.message = _t('This phone number is already in use');
|
||||
} else if (err.httpStatus) {
|
||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
||||
}
|
||||
|
@ -91,7 +92,7 @@ class AddThreepid {
|
|||
id_server: identityServerDomain
|
||||
}, this.bind).catch(function(err) {
|
||||
if (err.httpStatus === 401) {
|
||||
err.message = "Failed to verify email address: make sure you clicked the link in the email";
|
||||
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
|
||||
}
|
||||
else if (err.httpStatus) {
|
||||
err.message += ` (Status ${err.httpStatus})`;
|
||||
|
|
145
src/Analytics.js
Normal file
145
src/Analytics.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { getCurrentLanguage } from './languageHandler';
|
||||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import PlatformPeg from './PlatformPeg';
|
||||
import SdkConfig from './SdkConfig';
|
||||
|
||||
function redact(str) {
|
||||
return str.replace(/#\/(room|user)\/(.+)/, "#/$1/<redacted>");
|
||||
}
|
||||
|
||||
const customVariables = {
|
||||
'App Platform': 1,
|
||||
'App Version': 2,
|
||||
'User Type': 3,
|
||||
'Chosen Language': 4,
|
||||
};
|
||||
|
||||
|
||||
class Analytics {
|
||||
constructor() {
|
||||
this._paq = null;
|
||||
this.disabled = true;
|
||||
this.firstPage = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable Analytics if initialized but disabled
|
||||
* otherwise try and initalize, no-op if piwik config missing
|
||||
*/
|
||||
enable() {
|
||||
if (this._paq || this._init()) {
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Analytics calls, will not fully unload Piwik until a refresh,
|
||||
* but this is second best, Piwik should not pull anything implicitly.
|
||||
*/
|
||||
disable() {
|
||||
this.disabled = true;
|
||||
}
|
||||
|
||||
_init() {
|
||||
const config = SdkConfig.get();
|
||||
if (!config || !config.piwik || !config.piwik.url || !config.piwik.siteId) return;
|
||||
|
||||
const url = config.piwik.url;
|
||||
const siteId = config.piwik.siteId;
|
||||
const self = this;
|
||||
|
||||
window._paq = this._paq = window._paq || [];
|
||||
|
||||
this._paq.push(['setTrackerUrl', url+'piwik.php']);
|
||||
this._paq.push(['setSiteId', siteId]);
|
||||
|
||||
this._paq.push(['trackAllContentImpressions']);
|
||||
this._paq.push(['discardHashTag', false]);
|
||||
this._paq.push(['enableHeartBeatTimer']);
|
||||
this._paq.push(['enableLinkTracking', true]);
|
||||
|
||||
const platform = PlatformPeg.get();
|
||||
this._setVisitVariable('App Platform', platform.getHumanReadableName());
|
||||
platform.getAppVersion().then((version) => {
|
||||
this._setVisitVariable('App Version', version);
|
||||
}).catch(() => {
|
||||
this._setVisitVariable('App Version', 'unknown');
|
||||
});
|
||||
|
||||
this._setVisitVariable('Chosen Language', getCurrentLanguage());
|
||||
|
||||
(function() {
|
||||
const g = document.createElement('script');
|
||||
const s = document.getElementsByTagName('script')[0];
|
||||
g.type='text/javascript'; g.async=true; g.defer=true; g.src=url+'piwik.js';
|
||||
|
||||
g.onload = function() {
|
||||
console.log('Initialised anonymous analytics');
|
||||
self._paq = window._paq;
|
||||
};
|
||||
|
||||
s.parentNode.insertBefore(g, s);
|
||||
})();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
trackPageChange() {
|
||||
if (this.disabled) return;
|
||||
if (this.firstPage) {
|
||||
// De-duplicate first page
|
||||
// router seems to hit the fn twice
|
||||
this.firstPage = false;
|
||||
return;
|
||||
}
|
||||
this._paq.push(['setCustomUrl', redact(window.location.href)]);
|
||||
this._paq.push(['trackPageView']);
|
||||
}
|
||||
|
||||
trackEvent(category, action, name) {
|
||||
if (this.disabled) return;
|
||||
this._paq.push(['trackEvent', category, action, name]);
|
||||
}
|
||||
|
||||
logout() {
|
||||
if (this.disabled) return;
|
||||
this._paq.push(['deleteCookies']);
|
||||
}
|
||||
|
||||
login() { // not used currently
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (this.disabled || !cli) return;
|
||||
|
||||
this._paq.push(['setUserId', `@${cli.getUserIdLocalpart()}:${cli.getDomain()}`]);
|
||||
}
|
||||
|
||||
_setVisitVariable(key, value) {
|
||||
this._paq.push(['setCustomVariable', customVariables[key], key, value, 'visit']);
|
||||
}
|
||||
|
||||
setGuest(guest) {
|
||||
if (this.disabled) return;
|
||||
this._setVisitVariable('User Type', guest ? 'Guest' : 'Logged In');
|
||||
}
|
||||
}
|
||||
|
||||
if (!global.mxAnalytics) {
|
||||
global.mxAnalytics = new Analytics();
|
||||
}
|
||||
module.exports = global.mxAnalytics;
|
|
@ -29,6 +29,11 @@ export default class BasePlatform {
|
|||
this.errorDidOccur = false;
|
||||
}
|
||||
|
||||
// Used primarily for Analytics
|
||||
getHumanReadableName(): string {
|
||||
return 'Base Platform';
|
||||
}
|
||||
|
||||
setNotificationCount(count: number) {
|
||||
this.notificationCount = count;
|
||||
}
|
||||
|
@ -66,11 +71,14 @@ export default class BasePlatform {
|
|||
displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
|
||||
}
|
||||
|
||||
loudNotification(ev: Event, room: Object) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to a string representing
|
||||
* the current version of the application.
|
||||
*/
|
||||
getAppVersion() {
|
||||
getAppVersion(): Promise<string> {
|
||||
throw new Error("getAppVersion not implemented!");
|
||||
}
|
||||
|
||||
|
@ -79,10 +87,12 @@ export default class BasePlatform {
|
|||
* with getUserMedia, return a string explaining why not.
|
||||
* Otherwise, return null.
|
||||
*/
|
||||
screenCaptureErrorString() {
|
||||
screenCaptureErrorString(): string {
|
||||
return "Not implemented";
|
||||
}
|
||||
|
||||
isElectron(): boolean { return false; }
|
||||
|
||||
/**
|
||||
* Restarts the application, without neccessarily reloading
|
||||
* any application code
|
||||
|
|
|
@ -55,6 +55,7 @@ var MatrixClientPeg = require('./MatrixClientPeg');
|
|||
var PlatformPeg = require("./PlatformPeg");
|
||||
var Modal = require('./Modal');
|
||||
var sdk = require('./index');
|
||||
import { _t } from './languageHandler';
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var dis = require("./dispatcher");
|
||||
|
||||
|
@ -142,8 +143,8 @@ function _setCallListeners(call) {
|
|||
play("busyAudio");
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Call Timeout",
|
||||
description: "The remote side failed to pick up."
|
||||
title: _t('Call Timeout'),
|
||||
description: _t('The remote side failed to pick up') + '.',
|
||||
});
|
||||
}
|
||||
else if (oldState === "invite_sent") {
|
||||
|
@ -179,7 +180,8 @@ function _setCallState(call, roomId, status) {
|
|||
}
|
||||
dis.dispatch({
|
||||
action: 'call_state',
|
||||
room_id: roomId
|
||||
room_id: roomId,
|
||||
state: status,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -203,8 +205,8 @@ function _onAction(payload) {
|
|||
console.log("Can't capture screen: " + screenCapErrorString);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unable to capture screen",
|
||||
description: screenCapErrorString
|
||||
title: _t('Unable to capture screen'),
|
||||
description: screenCapErrorString,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -223,8 +225,8 @@ function _onAction(payload) {
|
|||
if (module.exports.getAnyActiveCall()) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Existing Call",
|
||||
description: "You are already in a call."
|
||||
title: _t('Existing Call'),
|
||||
description: _t('You are already in a call.'),
|
||||
});
|
||||
return; // don't allow >1 call to be placed.
|
||||
}
|
||||
|
@ -233,8 +235,8 @@ function _onAction(payload) {
|
|||
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "VoIP is unsupported",
|
||||
description: "You cannot place VoIP calls in this browser."
|
||||
title: _t('VoIP is unsupported'),
|
||||
description: _t('You cannot place VoIP calls in this browser.'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -249,7 +251,7 @@ function _onAction(payload) {
|
|||
if (members.length <= 1) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
description: "You cannot place a call with yourself."
|
||||
description: _t('You cannot place a call with yourself.'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -275,14 +277,14 @@ function _onAction(payload) {
|
|||
if (!ConferenceHandler) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
description: "Conference calls are not supported in this client"
|
||||
description: _t('Conference calls are not supported in this client'),
|
||||
});
|
||||
}
|
||||
else if (!MatrixClientPeg.get().supportsVoip()) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "VoIP is unsupported",
|
||||
description: "You cannot place VoIP calls in this browser."
|
||||
title: _t('VoIP is unsupported'),
|
||||
description: _t('You cannot place VoIP calls in this browser.'),
|
||||
});
|
||||
}
|
||||
else if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
|
||||
|
@ -294,14 +296,14 @@ function _onAction(payload) {
|
|||
// Therefore we disable conference calling in E2E rooms.
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
description: "Conference calls are not supported in encrypted rooms",
|
||||
description: _t('Conference calls are not supported in encrypted rooms'),
|
||||
});
|
||||
}
|
||||
else {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Warning!",
|
||||
description: "Conference calling is in development and may not be reliable.",
|
||||
title: _t('Warning!'),
|
||||
description: _t('Conference calling is in development and may not be reliable.'),
|
||||
onFinished: confirm=>{
|
||||
if (confirm) {
|
||||
ConferenceHandler.createNewMatrixCall(
|
||||
|
@ -312,8 +314,8 @@ function _onAction(payload) {
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Conference call failed: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to set up conference call",
|
||||
description: "Conference call failed. " + ((err && err.message) ? err.message : ""),
|
||||
title: _t('Failed to set up conference call'),
|
||||
description: _t('Conference call failed.') + ' ' + ((err && err.message) ? err.message : ''),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ var extend = require('./extend');
|
|||
var dis = require('./dispatcher');
|
||||
var MatrixClientPeg = require('./MatrixClientPeg');
|
||||
var sdk = require('./index');
|
||||
import { _t } from './languageHandler';
|
||||
var Modal = require('./Modal');
|
||||
|
||||
var encrypt = require("browser-encrypt-attachment");
|
||||
|
@ -347,14 +348,14 @@ class ContentMessages {
|
|||
}, function(err) {
|
||||
error = err;
|
||||
if (!upload.canceled) {
|
||||
var desc = "The file '"+upload.fileName+"' failed to upload.";
|
||||
var desc = _t('The file \'%(fileName)s\' failed to upload', {fileName: upload.fileName}) + '.';
|
||||
if (err.http_status == 413) {
|
||||
desc = "The file '"+upload.fileName+"' exceeds this home server's size limit for uploads";
|
||||
desc = _t('The file \'%(fileName)s\' exceeds this home server\'s size limit for uploads', {fileName: upload.fileName});
|
||||
}
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Upload Failed",
|
||||
description: desc
|
||||
title: _t('Upload Failed'),
|
||||
description: desc,
|
||||
});
|
||||
}
|
||||
}).finally(() => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,40 +16,89 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
'use strict';
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
function getDaysArray() {
|
||||
return [
|
||||
_t('Sun'),
|
||||
_t('Mon'),
|
||||
_t('Tue'),
|
||||
_t('Wed'),
|
||||
_t('Thu'),
|
||||
_t('Fri'),
|
||||
_t('Sat'),
|
||||
];
|
||||
}
|
||||
|
||||
function getMonthsArray() {
|
||||
return [
|
||||
_t('Jan'),
|
||||
_t('Feb'),
|
||||
_t('Mar'),
|
||||
_t('Apr'),
|
||||
_t('May'),
|
||||
_t('Jun'),
|
||||
_t('Jul'),
|
||||
_t('Aug'),
|
||||
_t('Sep'),
|
||||
_t('Oct'),
|
||||
_t('Nov'),
|
||||
_t('Dec'),
|
||||
];
|
||||
}
|
||||
|
||||
function pad(n) {
|
||||
return (n < 10 ? '0' : '') + n;
|
||||
}
|
||||
|
||||
function twelveHourTime(date) {
|
||||
let hours = date.getHours() % 12;
|
||||
const minutes = pad(date.getMinutes());
|
||||
const ampm = date.getHours() >= 12 ? 'PM' : 'AM';
|
||||
hours = pad(hours ? hours : 12);
|
||||
return `${hours}:${minutes}${ampm}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatDate: function(date) {
|
||||
// date.toLocaleTimeString is completely system dependent.
|
||||
// just go 24h for now
|
||||
|
||||
var now = new Date();
|
||||
const days = getDaysArray();
|
||||
const months = getMonthsArray();
|
||||
if (date.toDateString() === now.toDateString()) {
|
||||
return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
return this.formatTime(date);
|
||||
}
|
||||
else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
|
||||
return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
// TODO: use standard date localize function provided in counterpart
|
||||
return _t('%(weekDayName)s %(time)s', {weekDayName: days[date.getDay()], time: this.formatTime(date)});
|
||||
}
|
||||
else if (now.getFullYear() === date.getFullYear()) {
|
||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
else {
|
||||
return this.formatFullDate(date);
|
||||
// TODO: use standard date localize function provided in counterpart
|
||||
return _t('%(weekDayName)s, %(monthName)s %(day)s %(time)s', {
|
||||
weekDayName: days[date.getDay()],
|
||||
monthName: months[date.getMonth()],
|
||||
day: date.getDate(),
|
||||
time: this.formatTime(date),
|
||||
});
|
||||
}
|
||||
return this.formatFullDate(date);
|
||||
},
|
||||
|
||||
formatFullDate: function(date) {
|
||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
formatFullDate: function(date, showTwelveHour=false) {
|
||||
const days = getDaysArray();
|
||||
const months = getMonthsArray();
|
||||
return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', {
|
||||
weekDayName: days[date.getDay()],
|
||||
monthName: months[date.getMonth()],
|
||||
day: date.getDate(),
|
||||
fullYear: date.getFullYear(),
|
||||
time: showTwelveHour ? twelveHourTime(date) : this.formatTime(date),
|
||||
});
|
||||
},
|
||||
|
||||
formatTime: function(date) {
|
||||
formatTime: function(date, showTwelveHour=false) {
|
||||
if (showTwelveHour) {
|
||||
return twelveHourTime(date);
|
||||
}
|
||||
return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -360,7 +360,7 @@ export function bodyToHtml(content, highlights, opts) {
|
|||
'mx_EventTile_bigEmoji': emojiBody,
|
||||
'markdown-body': isHtml,
|
||||
});
|
||||
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} />;
|
||||
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" />;
|
||||
}
|
||||
|
||||
export function emojifyText(text) {
|
||||
|
|
|
@ -19,6 +19,7 @@ import q from 'q';
|
|||
import Matrix from 'matrix-js-sdk';
|
||||
|
||||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import Analytics from './Analytics';
|
||||
import Notifier from './Notifier';
|
||||
import UserActivity from './UserActivity';
|
||||
import Presence from './Presence';
|
||||
|
@ -27,6 +28,7 @@ import DMRoomMap from './utils/DMRoomMap';
|
|||
import RtsClient from './RtsClient';
|
||||
import Modal from './Modal';
|
||||
import sdk from './index';
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
/**
|
||||
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
||||
|
@ -229,14 +231,16 @@ function _handleRestoreFailure(e) {
|
|||
|
||||
let msg = e.message;
|
||||
if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
|
||||
msg = "You need to log back in to generate end-to-end encryption keys "
|
||||
+ "for this device and submit the public key to your homeserver. "
|
||||
+ "This is a once off; sorry for the inconvenience.";
|
||||
msg = _t(
|
||||
'You need to log back in to generate end-to-end encryption keys'
|
||||
+ ' for this device and submit the public key to your homeserver.'
|
||||
+ ' This is a once off; sorry for the inconvenience.',
|
||||
);
|
||||
|
||||
_clearLocalStorage();
|
||||
_clearStorage();
|
||||
|
||||
return q.reject(new Error(
|
||||
"Unable to restore previous session: " + msg,
|
||||
_t('Unable to restore previous session') + ': ' + msg,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -254,7 +258,7 @@ function _handleRestoreFailure(e) {
|
|||
return def.promise.then((success) => {
|
||||
if (success) {
|
||||
// user clicked continue.
|
||||
_clearLocalStorage();
|
||||
_clearStorage();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -275,6 +279,8 @@ export function initRtsClient(url) {
|
|||
export function setLoggedIn(credentials) {
|
||||
credentials.guest = Boolean(credentials.guest);
|
||||
|
||||
Analytics.setGuest(credentials.guest);
|
||||
|
||||
console.log(
|
||||
"setLoggedIn: mxid:", credentials.userId,
|
||||
"deviceId:", credentials.deviceId,
|
||||
|
@ -326,6 +332,10 @@ export function setLoggedIn(credentials) {
|
|||
}
|
||||
|
||||
// stop any running clients before we create a new one with these new credentials
|
||||
//
|
||||
// XXX: why do we have any running clients here? Maybe on sign-in after
|
||||
// initial use as a guest? but what about our persistent storage? we need to
|
||||
// be careful not to leak e2e data created as one user into another session.
|
||||
stopMatrixClient();
|
||||
|
||||
MatrixClientPeg.replaceUsingCreds(credentials);
|
||||
|
@ -396,12 +406,19 @@ export function startMatrixClient() {
|
|||
* a session has been logged out / ended.
|
||||
*/
|
||||
export function onLoggedOut() {
|
||||
_clearLocalStorage();
|
||||
stopMatrixClient();
|
||||
stopMatrixClient(true);
|
||||
dis.dispatch({action: 'on_logged_out'});
|
||||
}
|
||||
|
||||
function _clearLocalStorage() {
|
||||
function _clearStorage() {
|
||||
Analytics.logout();
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
// TODO: *really* ought to wait for the promise to complete
|
||||
cli.clearStores().done();
|
||||
}
|
||||
|
||||
if (!window.localStorage) {
|
||||
return;
|
||||
}
|
||||
|
@ -418,9 +435,13 @@ function _clearLocalStorage() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Stop all the background processes related to the current client
|
||||
* Stop all the background processes related to the current client.
|
||||
*
|
||||
* Optionally clears persistent stores.
|
||||
*
|
||||
* @param {boolean} clearStores true to clear the persistent stores.
|
||||
*/
|
||||
export function stopMatrixClient() {
|
||||
export function stopMatrixClient(clearStores) {
|
||||
Notifier.stop();
|
||||
UserActivity.stop();
|
||||
Presence.stop();
|
||||
|
@ -429,7 +450,13 @@ export function stopMatrixClient() {
|
|||
if (cli) {
|
||||
cli.stopClient();
|
||||
cli.removeAllListeners();
|
||||
cli.store.deleteAllData();
|
||||
MatrixClientPeg.unset();
|
||||
}
|
||||
|
||||
if (clearStores) {
|
||||
// note that we have to do this *after* stopping the client, but
|
||||
// *before* clearing the MatrixClientPeg.
|
||||
_clearStorage();
|
||||
}
|
||||
|
||||
MatrixClientPeg.unset();
|
||||
}
|
||||
|
|
11
src/Login.js
11
src/Login.js
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import Matrix from "matrix-js-sdk";
|
||||
import { _t } from "./languageHandler";
|
||||
|
||||
import q from 'q';
|
||||
import url from 'url';
|
||||
|
@ -97,9 +98,9 @@ export default class Login {
|
|||
};
|
||||
}, (error) => {
|
||||
if (error.httpStatus === 403) {
|
||||
error.friendlyText = "Guest access is disabled on this Home Server.";
|
||||
error.friendlyText = _t("Guest access is disabled on this Home Server.");
|
||||
} else {
|
||||
error.friendlyText = "Failed to register as guest: " + error.data;
|
||||
error.friendlyText = _t("Failed to register as guest:") + ' ' + error.data;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
@ -158,12 +159,12 @@ export default class Login {
|
|||
}, function(error) {
|
||||
if (error.httpStatus == 400 && loginParams.medium) {
|
||||
error.friendlyText = (
|
||||
'This Home Server does not support login using email address.'
|
||||
_t('This Home Server does not support login using email address.')
|
||||
);
|
||||
}
|
||||
else if (error.httpStatus === 403) {
|
||||
error.friendlyText = (
|
||||
'Incorrect username and/or password.'
|
||||
_t('Incorrect username and/or password.')
|
||||
);
|
||||
if (self._fallbackHsUrl) {
|
||||
var fbClient = Matrix.createClient({
|
||||
|
@ -187,7 +188,7 @@ export default class Login {
|
|||
}
|
||||
else {
|
||||
error.friendlyText = (
|
||||
'There was a problem logging in. (HTTP ' + error.httpStatus + ")"
|
||||
_t("There was a problem logging in.") + ' (HTTP ' + error.httpStatus + ")"
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
import Analytics from './Analytics';
|
||||
import sdk from './index';
|
||||
|
||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||
|
@ -104,6 +105,9 @@ class ModalManager {
|
|||
}
|
||||
|
||||
createDialog(Element, props, className) {
|
||||
if (props && props.title) {
|
||||
Analytics.trackEvent('Modal', props.title, 'createDialog');
|
||||
}
|
||||
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,9 +18,11 @@ limitations under the License.
|
|||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import PlatformPeg from './PlatformPeg';
|
||||
import TextForEvent from './TextForEvent';
|
||||
import Analytics from './Analytics';
|
||||
import Avatar from './Avatar';
|
||||
import dis from './dispatcher';
|
||||
import sdk from './index';
|
||||
import { _t } from './languageHandler';
|
||||
import Modal from './Modal';
|
||||
|
||||
/*
|
||||
|
@ -120,6 +122,9 @@ const Notifier = {
|
|||
setEnabled: function(enable, callback) {
|
||||
const plaf = PlatformPeg.get();
|
||||
if (!plaf) return;
|
||||
|
||||
Analytics.trackEvent('Notifier', 'Set Enabled', enable);
|
||||
|
||||
// make sure that we persist the current setting audio_enabled setting
|
||||
// before changing anything
|
||||
if (global.localStorage) {
|
||||
|
@ -134,13 +139,11 @@ const Notifier = {
|
|||
if (result !== 'granted') {
|
||||
// The permission request was dismissed or denied
|
||||
const description = result === 'denied'
|
||||
? 'Riot does not have permission to send you notifications'
|
||||
+ ' - please check your browser settings'
|
||||
: 'Riot was not given permission to send notifications'
|
||||
+ ' - please try again';
|
||||
? _t('Riot does not have permission to send you notifications - please check your browser settings')
|
||||
: _t('Riot was not given permission to send notifications - please try again');
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: 'Unable to enable Notifications',
|
||||
title: _t('Unable to enable Notifications'),
|
||||
description,
|
||||
});
|
||||
return;
|
||||
|
@ -200,6 +203,8 @@ const Notifier = {
|
|||
setToolbarHidden: function(hidden, persistent = true) {
|
||||
this.toolbarHidden = hidden;
|
||||
|
||||
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden);
|
||||
|
||||
// XXX: why are we dispatching this here?
|
||||
// this is nothing to do with notifier_enabled
|
||||
dis.dispatch({
|
||||
|
@ -245,6 +250,7 @@ const Notifier = {
|
|||
this._displayPopupNotification(ev, room);
|
||||
}
|
||||
if (actions.tweaks.sound && this.isAudioEnabled()) {
|
||||
PlatformPeg.get().loudNotification(ev, room);
|
||||
this._playAudioNotification(ev, room);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
/**
|
||||
* Allows a user to reset their password on a homeserver.
|
||||
|
@ -53,7 +54,7 @@ class PasswordReset {
|
|||
return res;
|
||||
}, function(err) {
|
||||
if (err.errcode == 'M_THREEPID_NOT_FOUND') {
|
||||
err.message = "This email address was not found";
|
||||
err.message = _t('This email address was not found');
|
||||
} else if (err.httpStatus) {
|
||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
||||
}
|
||||
|
@ -78,10 +79,10 @@ class PasswordReset {
|
|||
}
|
||||
}, this.password).catch(function(err) {
|
||||
if (err.httpStatus === 401) {
|
||||
err.message = "Failed to verify email address: make sure you clicked the link in the email";
|
||||
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
|
||||
}
|
||||
else if (err.httpStatus === 404) {
|
||||
err.message = "Your email address does not appear to be associated with a Matrix ID on this Homeserver.";
|
||||
err.message = _t('Your email address does not appear to be associated with a Matrix ID on this Homeserver.');
|
||||
}
|
||||
else if (err.httpStatus) {
|
||||
err.message += ` (Status ${err.httpStatus})`;
|
||||
|
|
17
src/Roles.js
17
src/Roles.js
|
@ -13,14 +13,19 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
export const LEVEL_ROLE_MAP = {
|
||||
undefined: 'Default',
|
||||
0: 'User',
|
||||
50: 'Moderator',
|
||||
100: 'Admin',
|
||||
};
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
export function levelRoleMap() {
|
||||
return {
|
||||
undefined: _t('Default'),
|
||||
0: _t('User'),
|
||||
50: _t('Moderator'),
|
||||
100: _t('Admin'),
|
||||
};
|
||||
}
|
||||
|
||||
export function textualPowerLevel(level, userDefault) {
|
||||
const LEVEL_ROLE_MAP = this.levelRoleMap();
|
||||
if (LEVEL_ROLE_MAP[level]) {
|
||||
return LEVEL_ROLE_MAP[level] + (level !== undefined ? ` (${level})` : ` (${userDefault})`);
|
||||
} else {
|
||||
|
|
|
@ -125,6 +125,7 @@ const SdkConfig = require('./SdkConfig');
|
|||
const MatrixClientPeg = require("./MatrixClientPeg");
|
||||
const MatrixEvent = require("matrix-js-sdk").MatrixEvent;
|
||||
const dis = require("./dispatcher");
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
function sendResponse(event, res) {
|
||||
const data = JSON.parse(JSON.stringify(event.data));
|
||||
|
@ -150,7 +151,7 @@ function inviteUser(event, roomId, userId) {
|
|||
console.log(`Received request to invite ${userId} into room ${roomId}`);
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
sendError(event, "You need to be logged in.");
|
||||
sendError(event, _t('You need to be logged in.'));
|
||||
return;
|
||||
}
|
||||
const room = client.getRoom(roomId);
|
||||
|
@ -170,7 +171,7 @@ function inviteUser(event, roomId, userId) {
|
|||
success: true,
|
||||
});
|
||||
}, function(err) {
|
||||
sendError(event, "You need to be able to invite users to do that.", err);
|
||||
sendError(event, _t('You need to be able to invite users to do that.'), err);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -181,7 +182,7 @@ function setPlumbingState(event, roomId, status) {
|
|||
console.log(`Received request to set plumbing state to status "${status}" in room ${roomId}`);
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
sendError(event, "You need to be logged in.");
|
||||
sendError(event, _t('You need to be logged in.'));
|
||||
return;
|
||||
}
|
||||
client.sendStateEvent(roomId, "m.room.plumbing", { status : status }).done(() => {
|
||||
|
@ -189,7 +190,7 @@ function setPlumbingState(event, roomId, status) {
|
|||
success: true,
|
||||
});
|
||||
}, (err) => {
|
||||
sendError(event, err.message ? err.message : "Failed to send request.", err);
|
||||
sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -197,7 +198,7 @@ function setBotOptions(event, roomId, userId) {
|
|||
console.log(`Received request to set options for bot ${userId} in room ${roomId}`);
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
sendError(event, "You need to be logged in.");
|
||||
sendError(event, _t('You need to be logged in.'));
|
||||
return;
|
||||
}
|
||||
client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => {
|
||||
|
@ -205,20 +206,20 @@ function setBotOptions(event, roomId, userId) {
|
|||
success: true,
|
||||
});
|
||||
}, (err) => {
|
||||
sendError(event, err.message ? err.message : "Failed to send request.", err);
|
||||
sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
|
||||
});
|
||||
}
|
||||
|
||||
function setBotPower(event, roomId, userId, level) {
|
||||
if (!(Number.isInteger(level) && level >= 0)) {
|
||||
sendError(event, "Power level must be positive integer.");
|
||||
sendError(event, _t('Power level must be positive integer.'));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Received request to set power level to ${level} for bot ${userId} in room ${roomId}.`);
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
sendError(event, "You need to be logged in.");
|
||||
sendError(event, _t('You need to be logged in.'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -235,7 +236,7 @@ function setBotPower(event, roomId, userId, level) {
|
|||
success: true,
|
||||
});
|
||||
}, (err) => {
|
||||
sendError(event, err.message ? err.message : "Failed to send request.", err);
|
||||
sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -258,12 +259,12 @@ function botOptions(event, roomId, userId) {
|
|||
function returnStateEvent(event, roomId, eventType, stateKey) {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
sendError(event, "You need to be logged in.");
|
||||
sendError(event, _t('You need to be logged in.'));
|
||||
return;
|
||||
}
|
||||
const room = client.getRoom(roomId);
|
||||
if (!room) {
|
||||
sendError(event, "This room is not recognised.");
|
||||
sendError(event, _t('This room is not recognised.'));
|
||||
return;
|
||||
}
|
||||
const stateEvent = room.currentState.getStateEvents(eventType, stateKey);
|
||||
|
@ -313,13 +314,13 @@ const onMessage = function(event) {
|
|||
const roomId = event.data.room_id;
|
||||
const userId = event.data.user_id;
|
||||
if (!roomId) {
|
||||
sendError(event, "Missing room_id in request");
|
||||
sendError(event, _t('Missing room_id in request'));
|
||||
return;
|
||||
}
|
||||
let promise = Promise.resolve(currentRoomId);
|
||||
if (!currentRoomId) {
|
||||
if (!currentRoomAlias) {
|
||||
sendError(event, "Must be viewing a room");
|
||||
sendError(event, _t('Must be viewing a room'));
|
||||
return;
|
||||
}
|
||||
// no room ID but there is an alias, look it up.
|
||||
|
@ -331,7 +332,7 @@ const onMessage = function(event) {
|
|||
|
||||
promise.then((viewingRoomId) => {
|
||||
if (roomId !== viewingRoomId) {
|
||||
sendError(event, "Room " + roomId + " not visible");
|
||||
sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -345,7 +346,7 @@ const onMessage = function(event) {
|
|||
}
|
||||
|
||||
if (!userId) {
|
||||
sendError(event, "Missing user_id in request");
|
||||
sendError(event, _t('Missing user_id in request'));
|
||||
return;
|
||||
}
|
||||
switch (event.data.action) {
|
||||
|
@ -370,7 +371,7 @@ const onMessage = function(event) {
|
|||
}
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
sendError(event, "Failed to lookup current room.");
|
||||
sendError(event, _t('Failed to lookup current room') + '.');
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -23,22 +23,28 @@ class Skinner {
|
|||
if (this.components === null) {
|
||||
throw new Error(
|
||||
"Attempted to get a component before a skin has been loaded."+
|
||||
"This is probably because either:"+
|
||||
" This is probably because either:"+
|
||||
" a) Your app has not called sdk.loadSkin(), or"+
|
||||
" b) A component has called getComponent at the root level"
|
||||
" b) A component has called getComponent at the root level",
|
||||
);
|
||||
}
|
||||
var comp = this.components[name];
|
||||
if (comp) {
|
||||
return comp;
|
||||
}
|
||||
let comp = this.components[name];
|
||||
// XXX: Temporarily also try 'views.' as we're currently
|
||||
// leaving the 'views.' off views.
|
||||
var comp = this.components['views.'+name];
|
||||
if (comp) {
|
||||
return comp;
|
||||
if (!comp) {
|
||||
comp = this.components['views.'+name];
|
||||
}
|
||||
throw new Error("No such component: "+name);
|
||||
|
||||
if (!comp) {
|
||||
throw new Error("No such component: "+name);
|
||||
}
|
||||
|
||||
// components have to be functions.
|
||||
const validType = typeof comp === 'function';
|
||||
if (!validType) {
|
||||
throw new Error(`Not a valid component: ${name}.`);
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
|
||||
load(skinObject) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import MatrixClientPeg from "./MatrixClientPeg";
|
|||
import dis from "./dispatcher";
|
||||
import Tinter from "./Tinter";
|
||||
import sdk from './index';
|
||||
import { _t } from './languageHandler';
|
||||
import Modal from './Modal';
|
||||
|
||||
|
||||
|
@ -41,7 +42,7 @@ class Command {
|
|||
}
|
||||
|
||||
getUsage() {
|
||||
return "Usage: " + this.getCommandWithArgs();
|
||||
return _t('Usage') + ': ' + this.getCommandWithArgs();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,8 +69,8 @@ const commands = {
|
|||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
// TODO Don't explain this away, actually show a search UI here.
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "/ddg is not a command",
|
||||
description: "To use it, just wait for autocomplete results to load and tab through them.",
|
||||
title: _t('/ddg is not a command'),
|
||||
description: _t('To use it, just wait for autocomplete results to load and tab through them.'),
|
||||
});
|
||||
return success();
|
||||
}),
|
||||
|
@ -143,7 +144,7 @@ const commands = {
|
|||
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
roomAlias: roomAlias,
|
||||
room_alias: roomAlias,
|
||||
auto_join: true,
|
||||
});
|
||||
|
||||
|
@ -185,7 +186,7 @@ const commands = {
|
|||
if (targetRoomId) { break; }
|
||||
}
|
||||
if (!targetRoomId) {
|
||||
return reject("Unrecognised room alias: " + roomAlias);
|
||||
return reject(_t("Unrecognised room alias:") + ' ' + roomAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -302,14 +303,14 @@ const commands = {
|
|||
|
||||
const device = MatrixClientPeg.get().getStoredDevice(userId, deviceId);
|
||||
if (!device) {
|
||||
return reject(`Unknown (user, device) pair: (${userId}, ${deviceId})`);
|
||||
return reject(_t(`Unknown (user, device) pair:`) + ` (${userId}, ${deviceId})`);
|
||||
}
|
||||
|
||||
if (device.isVerified()) {
|
||||
if (device.getFingerprint() === fingerprint) {
|
||||
return reject(`Device already verified!`);
|
||||
return reject(_t(`Device already verified!`));
|
||||
} else {
|
||||
return reject(`WARNING: Device already verified, but keys do NOT MATCH!`);
|
||||
return reject(_t(`WARNING: Device already verified, but keys do NOT MATCH!`));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,12 +322,15 @@ const commands = {
|
|||
// Tell the user we verified everything!
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Verified key",
|
||||
title: _t("Verified key"),
|
||||
description: (
|
||||
<div>
|
||||
<p>
|
||||
The signing key you provided matches the signing key you received
|
||||
from { userId }'s device { deviceId }. Device marked as verified.
|
||||
{
|
||||
_t("The signing key you provided matches the signing key you received " +
|
||||
"from %(userId)s's device %(deviceId)s. Device marked as verified.",
|
||||
{userId: userId, deviceId: deviceId})
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
|
@ -335,9 +339,13 @@ const commands = {
|
|||
|
||||
return success();
|
||||
} else {
|
||||
return reject(`WARNING: KEY VERIFICATION FAILED! The signing key for ${userId} and device
|
||||
${deviceId} is "${device.getFingerprint()}" which does not match the provided key
|
||||
"${fingerprint}". This could mean your communications are being intercepted!`);
|
||||
const fprint = device.getFingerprint();
|
||||
return reject(
|
||||
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' +
|
||||
' %(deviceId)s is "%(fprint)s" which does not match the provided key' +
|
||||
' "%(fingerprint)s". This could mean your communications are being intercepted!',
|
||||
{deviceId: deviceId, fprint: fprint, userId: userId, fingerprint: fingerprint})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -382,7 +390,7 @@ module.exports = {
|
|||
if (commands[cmd]) {
|
||||
return commands[cmd].run(roomId, args);
|
||||
} else {
|
||||
return reject("Unrecognised command: " + input);
|
||||
return reject(_t("Unrecognised command:") + ' ' + input);
|
||||
}
|
||||
}
|
||||
return null; // not a command
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||
var CallHandler = require("./CallHandler");
|
||||
|
||||
import { _t } from './languageHandler';
|
||||
import * as Roles from './Roles';
|
||||
|
||||
function textForMemberEvent(ev) {
|
||||
|
@ -25,45 +25,45 @@ function textForMemberEvent(ev) {
|
|||
var targetName = ev.target ? ev.target.name : ev.getStateKey();
|
||||
var ConferenceHandler = CallHandler.getConferenceHandler();
|
||||
var reason = ev.getContent().reason ? (
|
||||
" Reason: " + ev.getContent().reason
|
||||
_t('Reason') + ': ' + ev.getContent().reason
|
||||
) : "";
|
||||
switch (ev.getContent().membership) {
|
||||
case 'invite':
|
||||
var threePidContent = ev.getContent().third_party_invite;
|
||||
if (threePidContent) {
|
||||
if (threePidContent.display_name) {
|
||||
return targetName + " accepted the invitation for " +
|
||||
threePidContent.display_name + ".";
|
||||
return _t('%(targetName)s accepted the invitation for %(displayName)s.', {targetName: targetName, displayName: threePidContent.display_name});
|
||||
} else {
|
||||
return targetName + " accepted an invitation.";
|
||||
return _t('%(targetName)s accepted an invitation.', {targetName: targetName});
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
|
||||
return senderName + " requested a VoIP conference";
|
||||
return _t('%(senderName)s requested a VoIP conference.', {senderName: senderName});
|
||||
}
|
||||
else {
|
||||
return senderName + " invited " + targetName + ".";
|
||||
return _t('%(senderName)s invited %(targetName)s.', {senderName: senderName, targetName: targetName});
|
||||
}
|
||||
}
|
||||
case 'ban':
|
||||
return senderName + " banned " + targetName + "." + reason;
|
||||
return _t(
|
||||
'%(senderName)s banned %(targetName)s.',
|
||||
{senderName: senderName, targetName: targetName}
|
||||
) + ' ' + reason;
|
||||
case 'join':
|
||||
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') {
|
||||
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) {
|
||||
return ev.getSender() + " changed their display name from " +
|
||||
ev.getPrevContent().displayname + " to " +
|
||||
ev.getContent().displayname;
|
||||
return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname, displayName: ev.getContent().displayname});
|
||||
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) {
|
||||
return ev.getSender() + " set their display name to " + ev.getContent().displayname;
|
||||
return _t('%(senderName)s set their display name to %(displayName)s.', {senderName: ev.getSender(), displayName: ev.getContent().displayname});
|
||||
} else if (ev.getPrevContent().displayname && !ev.getContent().displayname) {
|
||||
return ev.getSender() + " removed their display name (" + ev.getPrevContent().displayname + ")";
|
||||
return _t('%(senderName)s removed their display name (%(oldDisplayName)s).', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname});
|
||||
} else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) {
|
||||
return senderName + " removed their profile picture";
|
||||
return _t('%(senderName)s removed their profile picture.', {senderName: senderName});
|
||||
} else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) {
|
||||
return senderName + " changed their profile picture";
|
||||
return _t('%(senderName)s changed their profile picture.', {senderName: senderName});
|
||||
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
|
||||
return senderName + " set a profile picture";
|
||||
return _t('%(senderName)s set a profile picture.', {senderName: senderName});
|
||||
} else {
|
||||
// suppress null rejoins
|
||||
return '';
|
||||
|
@ -71,49 +71,57 @@ function textForMemberEvent(ev) {
|
|||
} else {
|
||||
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
|
||||
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
|
||||
return "VoIP conference started";
|
||||
return _t('VoIP conference started.');
|
||||
}
|
||||
else {
|
||||
return targetName + " joined the room.";
|
||||
return _t('%(targetName)s joined the room.', {targetName: targetName});
|
||||
}
|
||||
}
|
||||
case 'leave':
|
||||
if (ev.getSender() === ev.getStateKey()) {
|
||||
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
|
||||
return "VoIP conference finished";
|
||||
return _t('VoIP conference finished.');
|
||||
}
|
||||
else if (ev.getPrevContent().membership === "invite") {
|
||||
return targetName + " rejected the invitation.";
|
||||
return _t('%(targetName)s rejected the invitation.', {targetName: targetName});
|
||||
}
|
||||
else {
|
||||
return targetName + " left the room.";
|
||||
return _t('%(targetName)s left the room.', {targetName: targetName});
|
||||
}
|
||||
}
|
||||
else if (ev.getPrevContent().membership === "ban") {
|
||||
return senderName + " unbanned " + targetName + ".";
|
||||
return _t('%(senderName)s unbanned %(targetName)s.', {senderName: senderName, targetName: targetName});
|
||||
}
|
||||
else if (ev.getPrevContent().membership === "join") {
|
||||
return senderName + " kicked " + targetName + "." + reason;
|
||||
return _t(
|
||||
'%(senderName)s kicked %(targetName)s.',
|
||||
{senderName: senderName, targetName: targetName}
|
||||
) + ' ' + reason;
|
||||
}
|
||||
else if (ev.getPrevContent().membership === "invite") {
|
||||
return senderName + " withdrew " + targetName + "'s invitation." + reason;
|
||||
return _t(
|
||||
'%(senderName)s withdrew %(targetName)s\'s invitation.',
|
||||
{senderName: senderName, targetName: targetName}
|
||||
) + ' ' + reason;
|
||||
}
|
||||
else {
|
||||
return targetName + " left the room.";
|
||||
return _t('%(targetName)s left the room.', {targetName: targetName});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function textForTopicEvent(ev) {
|
||||
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||
|
||||
return senderDisplayName + ' changed the topic to "' + ev.getContent().topic + '"';
|
||||
return _t('%(senderDisplayName)s changed the topic to "%(topic)s".', {senderDisplayName: senderDisplayName, topic: ev.getContent().topic});
|
||||
}
|
||||
|
||||
function textForRoomNameEvent(ev) {
|
||||
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||
|
||||
return senderDisplayName + ' changed the room name to "' + ev.getContent().name + '"';
|
||||
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
|
||||
return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName: senderDisplayName});
|
||||
}
|
||||
return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', {senderDisplayName: senderDisplayName, roomName: ev.getContent().name});
|
||||
}
|
||||
|
||||
function textForMessageEvent(ev) {
|
||||
|
@ -122,66 +130,66 @@ function textForMessageEvent(ev) {
|
|||
if (ev.getContent().msgtype === "m.emote") {
|
||||
message = "* " + senderDisplayName + " " + message;
|
||||
} else if (ev.getContent().msgtype === "m.image") {
|
||||
message = senderDisplayName + " sent an image.";
|
||||
message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName: senderDisplayName});
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function textForCallAnswerEvent(event) {
|
||||
var senderName = event.sender ? event.sender.name : "Someone";
|
||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)";
|
||||
return senderName + " answered the call." + supported;
|
||||
var senderName = event.sender ? event.sender.name : _t('Someone');
|
||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
|
||||
return _t('%(senderName)s answered the call.', {senderName: senderName}) + ' ' + supported;
|
||||
}
|
||||
|
||||
function textForCallHangupEvent(event) {
|
||||
var senderName = event.sender ? event.sender.name : "Someone";
|
||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)";
|
||||
return senderName + " ended the call." + supported;
|
||||
var senderName = event.sender ? event.sender.name : _t('Someone');
|
||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
|
||||
return _t('%(senderName)s ended the call.', {senderName: senderName}) + ' ' + supported;
|
||||
}
|
||||
|
||||
function textForCallInviteEvent(event) {
|
||||
var senderName = event.sender ? event.sender.name : "Someone";
|
||||
var senderName = event.sender ? event.sender.name : _t('Someone');
|
||||
// FIXME: Find a better way to determine this from the event?
|
||||
var type = "voice";
|
||||
if (event.getContent().offer && event.getContent().offer.sdp &&
|
||||
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
|
||||
type = "video";
|
||||
}
|
||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)";
|
||||
return senderName + " placed a " + type + " call." + supported;
|
||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
|
||||
return _t('%(senderName)s placed a %(callType)s call.', {senderName: senderName, callType: type}) + ' ' + supported;
|
||||
}
|
||||
|
||||
function textForThreePidInviteEvent(event) {
|
||||
var senderName = event.sender ? event.sender.name : event.getSender();
|
||||
return senderName + " sent an invitation to " + event.getContent().display_name +
|
||||
" to join the room.";
|
||||
return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {senderName: senderName, targetDisplayName: event.getContent().display_name});
|
||||
}
|
||||
|
||||
function textForHistoryVisibilityEvent(event) {
|
||||
var senderName = event.sender ? event.sender.name : event.getSender();
|
||||
var vis = event.getContent().history_visibility;
|
||||
var text = senderName + " made future room history visible to ";
|
||||
// XXX: This i18n just isn't going to work for languages with different sentence structure.
|
||||
var text = _t('%(senderName)s made future room history visible to', {senderName: senderName}) + ' ';
|
||||
if (vis === "invited") {
|
||||
text += "all room members, from the point they are invited.";
|
||||
text += _t('all room members, from the point they are invited') + '.';
|
||||
}
|
||||
else if (vis === "joined") {
|
||||
text += "all room members, from the point they joined.";
|
||||
text += _t('all room members, from the point they joined') + '.';
|
||||
}
|
||||
else if (vis === "shared") {
|
||||
text += "all room members.";
|
||||
text += _t('all room members') + '.';
|
||||
}
|
||||
else if (vis === "world_readable") {
|
||||
text += "anyone.";
|
||||
text += _t('anyone') + '.';
|
||||
}
|
||||
else {
|
||||
text += " unknown (" + vis + ")";
|
||||
text += ' ' + _t('unknown') + ' (' + vis + ').';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function textForEncryptionEvent(event) {
|
||||
var senderName = event.sender ? event.sender.name : event.getSender();
|
||||
return senderName + " turned on end-to-end encryption (algorithm " + event.getContent().algorithm + ")";
|
||||
return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', {senderName: senderName, algorithm: event.getContent().algorithm});
|
||||
}
|
||||
|
||||
// Currently will only display a change if a user's power level is changed
|
||||
|
@ -204,6 +212,7 @@ function textForPowerEvent(event) {
|
|||
}
|
||||
);
|
||||
let diff = [];
|
||||
// XXX: This is also surely broken for i18n
|
||||
users.forEach((userId) => {
|
||||
// Previous power level
|
||||
const from = event.getPrevContent().users[userId];
|
||||
|
@ -211,16 +220,21 @@ function textForPowerEvent(event) {
|
|||
const to = event.getContent().users[userId];
|
||||
if (to !== from) {
|
||||
diff.push(
|
||||
userId +
|
||||
' from ' + Roles.textualPowerLevel(from, userDefault) +
|
||||
' to ' + Roles.textualPowerLevel(to, userDefault)
|
||||
_t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {
|
||||
userId: userId,
|
||||
fromPowerLevel: Roles.textualPowerLevel(from, userDefault),
|
||||
toPowerLevel: Roles.textualPowerLevel(to, userDefault)
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
if (!diff.length) {
|
||||
return '';
|
||||
}
|
||||
return senderName + ' changed the power level of ' + diff.join(', ');
|
||||
return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', {
|
||||
senderName: senderName,
|
||||
powerLevelDiffText: diff.join(", ")
|
||||
});
|
||||
}
|
||||
|
||||
var handlers = {
|
||||
|
|
|
@ -22,7 +22,7 @@ let isDialogOpen = false;
|
|||
|
||||
const onAction = function(payload) {
|
||||
if (payload.action === 'unknown_device_error' && !isDialogOpen) {
|
||||
var UnknownDeviceDialog = sdk.getComponent("dialogs.UnknownDeviceDialog");
|
||||
const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog');
|
||||
isDialogOpen = true;
|
||||
Modal.createDialog(UnknownDeviceDialog, {
|
||||
devices: payload.err.devices,
|
||||
|
@ -33,17 +33,17 @@ const onAction = function(payload) {
|
|||
// https://github.com/vector-im/riot-web/issues/3148
|
||||
console.log('UnknownDeviceDialog closed with '+r);
|
||||
},
|
||||
}, "mx_Dialog_unknownDevice");
|
||||
}, 'mx_Dialog_unknownDevice');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ref = null;
|
||||
|
||||
export function startListening () {
|
||||
export function startListening() {
|
||||
ref = dis.register(onAction);
|
||||
}
|
||||
|
||||
export function stopListening () {
|
||||
export function stopListening() {
|
||||
if (ref) {
|
||||
dis.unregister(ref);
|
||||
ref = null;
|
||||
|
|
|
@ -14,24 +14,29 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
import q from 'q';
|
||||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import Notifier from './Notifier';
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
/*
|
||||
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
LABS_FEATURES: [
|
||||
{
|
||||
name: 'New Composer & Autocomplete',
|
||||
name: "-",
|
||||
id: 'rich_text_editor',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
|
||||
// horrible but it works. The locality makes this somewhat more palatable.
|
||||
doTranslations: function() {
|
||||
this.LABS_FEATURES[0].name = _t("New Composer & Autocomplete");
|
||||
},
|
||||
|
||||
loadProfileInfo: function() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
return cli.getProfileInfo(cli.credentials.userId);
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
module.exports = {
|
||||
usersTypingApartFromMe: function(room) {
|
||||
|
@ -56,18 +57,18 @@ module.exports = {
|
|||
if (whoIsTyping.length == 0) {
|
||||
return '';
|
||||
} else if (whoIsTyping.length == 1) {
|
||||
return whoIsTyping[0].name + ' is typing';
|
||||
return _t('%(displayName)s is typing', {displayName: whoIsTyping[0].name});
|
||||
}
|
||||
const names = whoIsTyping.map(function(m) {
|
||||
return m.name;
|
||||
});
|
||||
if (othersCount) {
|
||||
const other = ' other' + (othersCount > 1 ? 's' : '');
|
||||
return names.slice(0, limit - 1).join(', ') + ' and ' +
|
||||
othersCount + other + ' are typing';
|
||||
if (othersCount==1) {
|
||||
return _t('%(names)s and one other are typing', {names: names.slice(0, limit - 1).join(', ')});
|
||||
} else if (othersCount>1) {
|
||||
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
|
||||
} else {
|
||||
const lastPerson = names.pop();
|
||||
return names.join(', ') + ' and ' + lastPerson + ' are typing';
|
||||
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
var React = require("react");
|
||||
import { _t } from '../../../languageHandler';
|
||||
var sdk = require('../../../index');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
|
||||
|
@ -78,33 +79,33 @@ module.exports = React.createClass({
|
|||
_renderDeviceInfo: function() {
|
||||
var device = this.state.device;
|
||||
if (!device) {
|
||||
return (<i>unknown device</i>);
|
||||
return (<i>{ _t('unknown device') }</i>);
|
||||
}
|
||||
|
||||
var verificationStatus = (<b>NOT verified</b>);
|
||||
var verificationStatus = (<b>{ _t('NOT verified') }</b>);
|
||||
if (device.isBlocked()) {
|
||||
verificationStatus = (<b>Blacklisted</b>);
|
||||
verificationStatus = (<b>{ _t('Blacklisted') }</b>);
|
||||
} else if (device.isVerified()) {
|
||||
verificationStatus = "verified";
|
||||
verificationStatus = _t('verified');
|
||||
}
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{ _t('Name') }</td>
|
||||
<td>{ device.getDisplayName() }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Device ID</td>
|
||||
<td>{ _t('Device ID') }</td>
|
||||
<td><code>{ device.deviceId }</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Verification</td>
|
||||
<td>{ _t('Verification') }</td>
|
||||
<td>{ verificationStatus }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ed25519 fingerprint</td>
|
||||
<td>{ _t('Ed25519 fingerprint') }</td>
|
||||
<td><code>{device.getFingerprint()}</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -119,32 +120,32 @@ module.exports = React.createClass({
|
|||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>User ID</td>
|
||||
<td>{ _t('User ID') }</td>
|
||||
<td>{ event.getSender() }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Curve25519 identity key</td>
|
||||
<td><code>{ event.getSenderKey() || <i>none</i> }</code></td>
|
||||
<td>{ _t('Curve25519 identity key') }</td>
|
||||
<td><code>{ event.getSenderKey() || <i>{ _t('none') }</i> }</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Claimed Ed25519 fingerprint key</td>
|
||||
<td><code>{ event.getKeysClaimed().ed25519 || <i>none</i> }</code></td>
|
||||
<td>{ _t('Claimed Ed25519 fingerprint key') }</td>
|
||||
<td><code>{ event.getKeysClaimed().ed25519 || <i>{ _t('none') }</i> }</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Algorithm</td>
|
||||
<td>{ event.getWireContent().algorithm || <i>unencrypted</i> }</td>
|
||||
<td>{ _t('Algorithm') }</td>
|
||||
<td>{ event.getWireContent().algorithm || <i>{ _t('unencrypted') }</i> }</td>
|
||||
</tr>
|
||||
{
|
||||
event.getContent().msgtype === 'm.bad.encrypted' ? (
|
||||
<tr>
|
||||
<td>Decryption error</td>
|
||||
<td>{ _t('Decryption error') }</td>
|
||||
<td>{ event.getContent().body }</td>
|
||||
</tr>
|
||||
) : null
|
||||
}
|
||||
<tr>
|
||||
<td>Session ID</td>
|
||||
<td><code>{ event.getWireContent().session_id || <i>none</i> }</code></td>
|
||||
<td>{ _t('Session ID') }</td>
|
||||
<td><code>{ event.getWireContent().session_id || <i>{ _t('none') }</i> }</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -166,18 +167,18 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<div className="mx_EncryptedEventDialog" onKeyDown={ this.onKeyDown }>
|
||||
<div className="mx_Dialog_title">
|
||||
End-to-end encryption information
|
||||
{ _t('End-to-end encryption information') }
|
||||
</div>
|
||||
<div className="mx_Dialog_content">
|
||||
<h4>Event information</h4>
|
||||
<h4>{ _t('Event information') }</h4>
|
||||
{this._renderEventInfo()}
|
||||
|
||||
<h4>Sender device information</h4>
|
||||
<h4>{ _t('Sender device information') }</h4>
|
||||
{this._renderDeviceInfo()}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button className="mx_Dialog_primary" onClick={ this.props.onFinished } autoFocus={ true }>
|
||||
OK
|
||||
{ _t('OK') }
|
||||
</button>
|
||||
{buttons}
|
||||
</div>
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import FileSaver from 'file-saver';
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
import * as Matrix from 'matrix-js-sdk';
|
||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
||||
|
@ -52,11 +53,11 @@ export default React.createClass({
|
|||
|
||||
const passphrase = this.refs.passphrase1.value;
|
||||
if (passphrase !== this.refs.passphrase2.value) {
|
||||
this.setState({errStr: 'Passphrases must match'});
|
||||
this.setState({errStr: _t('Passphrases must match')});
|
||||
return false;
|
||||
}
|
||||
if (!passphrase) {
|
||||
this.setState({errStr: 'Passphrase must not be empty'});
|
||||
this.setState({errStr: _t('Passphrase must not be empty')});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -109,24 +110,28 @@ export default React.createClass({
|
|||
return (
|
||||
<BaseDialog className='mx_exportE2eKeysDialog'
|
||||
onFinished={this.props.onFinished}
|
||||
title="Export room keys"
|
||||
title={_t("Export room keys")}
|
||||
>
|
||||
<form onSubmit={this._onPassphraseFormSubmit}>
|
||||
<div className="mx_Dialog_content">
|
||||
<p>
|
||||
This process allows you to export the keys for messages
|
||||
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.
|
||||
{ _t(
|
||||
'This process allows you to export the keys for messages ' +
|
||||
'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.'
|
||||
) }
|
||||
</p>
|
||||
<p>
|
||||
The exported file will allow anyone who can read it to decrypt
|
||||
any encrypted messages that you can see, so you should be
|
||||
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.
|
||||
{ _t(
|
||||
'The exported file will allow anyone who can read it to decrypt ' +
|
||||
'any encrypted messages that you can see, so you should be ' +
|
||||
'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.'
|
||||
) }
|
||||
</p>
|
||||
<div className='error'>
|
||||
{this.state.errStr}
|
||||
|
@ -135,7 +140,7 @@ export default React.createClass({
|
|||
<div className='mx_E2eKeysDialog_inputRow'>
|
||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
||||
<label htmlFor='passphrase1'>
|
||||
Enter passphrase
|
||||
{_t("Enter passphrase")}
|
||||
</label>
|
||||
</div>
|
||||
<div className='mx_E2eKeysDialog_inputCell'>
|
||||
|
@ -148,7 +153,7 @@ export default React.createClass({
|
|||
<div className='mx_E2eKeysDialog_inputRow'>
|
||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
||||
<label htmlFor='passphrase2'>
|
||||
Confirm passphrase
|
||||
{_t("Confirm passphrase")}
|
||||
</label>
|
||||
</div>
|
||||
<div className='mx_E2eKeysDialog_inputCell'>
|
||||
|
@ -161,11 +166,11 @@ export default React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
<div className='mx_Dialog_buttons'>
|
||||
<input className='mx_Dialog_primary' type='submit' value='Export'
|
||||
<input className='mx_Dialog_primary' type='submit' value={_t('Export')}
|
||||
disabled={disableForm}
|
||||
/>
|
||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||
Cancel
|
||||
{_t("Cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -19,6 +19,7 @@ import React from 'react';
|
|||
import * as Matrix from 'matrix-js-sdk';
|
||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
function readFileAsArrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -112,20 +113,23 @@ export default React.createClass({
|
|||
return (
|
||||
<BaseDialog className='mx_importE2eKeysDialog'
|
||||
onFinished={this.props.onFinished}
|
||||
title="Import room keys"
|
||||
title={_t("Import room keys")}
|
||||
>
|
||||
<form onSubmit={this._onFormSubmit}>
|
||||
<div className="mx_Dialog_content">
|
||||
<p>
|
||||
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.
|
||||
{ _t(
|
||||
'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.'
|
||||
) }
|
||||
</p>
|
||||
<p>
|
||||
The export file will be protected with a passphrase.
|
||||
You should enter the passphrase here, to decrypt the
|
||||
file.
|
||||
{ _t(
|
||||
'The export file will be protected with a passphrase. ' +
|
||||
'You should enter the passphrase here, to decrypt the file.'
|
||||
) }
|
||||
</p>
|
||||
<div className='error'>
|
||||
{this.state.errStr}
|
||||
|
@ -134,7 +138,7 @@ export default React.createClass({
|
|||
<div className='mx_E2eKeysDialog_inputRow'>
|
||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
||||
<label htmlFor='importFile'>
|
||||
File to import
|
||||
{_t("File to import")}
|
||||
</label>
|
||||
</div>
|
||||
<div className='mx_E2eKeysDialog_inputCell'>
|
||||
|
@ -147,7 +151,7 @@ export default React.createClass({
|
|||
<div className='mx_E2eKeysDialog_inputRow'>
|
||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
||||
<label htmlFor='passphrase'>
|
||||
Enter passphrase
|
||||
{_t("Enter passphrase")}
|
||||
</label>
|
||||
</div>
|
||||
<div className='mx_E2eKeysDialog_inputCell'>
|
||||
|
@ -160,11 +164,11 @@ export default React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
<div className='mx_Dialog_buttons'>
|
||||
<input className='mx_Dialog_primary' type='submit' value='Import'
|
||||
<input className='mx_Dialog_primary' type='submit' value={_t('Import')}
|
||||
disabled={!this.state.enableSubmit || disableForm}
|
||||
/>
|
||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||
Cancel
|
||||
{_t("Cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type {Completion, SelectionRange} from './Autocompleter';
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import type {Component} from 'react';
|
||||
|
|
|
@ -1,8 +1,27 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import Fuse from 'fuse.js';
|
||||
import {TextualCompletion} from './Components';
|
||||
|
||||
// Warning: Since the description string will be translated in _t(result.description), all these strings below must be in i18n/strings/en_EN.json file
|
||||
const COMMANDS = [
|
||||
{
|
||||
command: '/me',
|
||||
|
@ -43,10 +62,10 @@ const COMMANDS = [
|
|||
command: '/ddg',
|
||||
args: '<query>',
|
||||
description: 'Searches DuckDuckGo for results',
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
let COMMAND_RE = /(^\/\w*)/g;
|
||||
const COMMAND_RE = /(^\/\w*)/g;
|
||||
|
||||
let instance = null;
|
||||
|
||||
|
@ -60,15 +79,15 @@ export default class CommandProvider extends AutocompleteProvider {
|
|||
|
||||
async getCompletions(query: string, selection: {start: number, end: number}) {
|
||||
let completions = [];
|
||||
let {command, range} = this.getCurrentCommand(query, selection);
|
||||
const {command, range} = this.getCurrentCommand(query, selection);
|
||||
if (command) {
|
||||
completions = this.fuse.search(command[0]).map(result => {
|
||||
completions = this.fuse.search(command[0]).map((result) => {
|
||||
return {
|
||||
completion: result.command + ' ',
|
||||
component: (<TextualCompletion
|
||||
title={result.command}
|
||||
subtitle={result.args}
|
||||
description={result.description}
|
||||
description={ _t(result.description) }
|
||||
/>),
|
||||
range,
|
||||
};
|
||||
|
@ -78,12 +97,11 @@ export default class CommandProvider extends AutocompleteProvider {
|
|||
}
|
||||
|
||||
getName() {
|
||||
return '*️⃣ Commands';
|
||||
return '*️⃣ ' + _t('Commands');
|
||||
}
|
||||
|
||||
static getInstance(): CommandProvider {
|
||||
if (instance == null)
|
||||
{instance = new CommandProvider();}
|
||||
if (instance === null) instance = new CommandProvider();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
|
@ -75,7 +93,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
|
|||
}
|
||||
|
||||
getName() {
|
||||
return '🔍 Results from DuckDuckGo';
|
||||
return '🔍 ' + _t('Results from DuckDuckGo');
|
||||
}
|
||||
|
||||
static getInstance(): DuckDuckGoProvider {
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import {emojioneList, shortnameToImage, shortnameToUnicode} from 'emojione';
|
||||
import Fuse from 'fuse.js';
|
||||
|
@ -39,7 +57,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
}
|
||||
|
||||
getName() {
|
||||
return '😃 Emoji';
|
||||
return '😃 ' + _t('Emoji');
|
||||
}
|
||||
|
||||
static getInstance() {
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import MatrixClientPeg from '../MatrixClientPeg';
|
||||
import Fuse from 'fuse.js';
|
||||
|
@ -50,7 +68,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
}
|
||||
|
||||
getName() {
|
||||
return '💬 Rooms';
|
||||
return '💬 ' + _t('Rooms');
|
||||
}
|
||||
|
||||
static getInstance() {
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import Q from 'q';
|
||||
import Fuse from 'fuse.js';
|
||||
|
@ -51,7 +69,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
}
|
||||
|
||||
getName() {
|
||||
return '👥 Users';
|
||||
return '👥 ' + _t('Users');
|
||||
}
|
||||
|
||||
setUserList(users) {
|
||||
|
|
|
@ -16,15 +16,16 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require("react");
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var PresetValues = {
|
||||
import React from 'react';
|
||||
import q from 'q';
|
||||
import { _t } from '../../languageHandler';
|
||||
import sdk from '../../index';
|
||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||
const PresetValues = {
|
||||
PrivateChat: "private_chat",
|
||||
PublicChat: "public_chat",
|
||||
Custom: "custom",
|
||||
};
|
||||
var q = require('q');
|
||||
var sdk = require('../../index');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CreateRoom',
|
||||
|
@ -231,7 +232,7 @@ module.exports = React.createClass({
|
|||
if (curr_phase == this.phases.ERROR) {
|
||||
error_box = (
|
||||
<div className="mx_Error">
|
||||
An error occured: {this.state.error_string}
|
||||
{_t('An error occured: %(error_string)s', {error_string: this.state.error_string})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -248,27 +249,27 @@ module.exports = React.createClass({
|
|||
<div className="mx_CreateRoom">
|
||||
<SimpleRoomHeader title="CreateRoom" collapsedRhs={ this.props.collapsedRhs }/>
|
||||
<div className="mx_CreateRoom_body">
|
||||
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder="Name"/> <br />
|
||||
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder="Topic"/> <br />
|
||||
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder={_t('Name')}/> <br />
|
||||
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder={_t('Topic')}/> <br />
|
||||
<RoomAlias ref="alias" alias={this.state.alias} homeserver={ domain } onChange={this.onAliasChanged}/> <br />
|
||||
<UserSelector ref="user_selector" selected_users={this.state.invited_users} onChange={this.onInviteChanged}/> <br />
|
||||
<Presets ref="presets" onChange={this.onPresetChanged} preset={this.state.preset}/> <br />
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged}/>
|
||||
Make this room private
|
||||
{_t('Make this room private')}
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" ref="share_history" checked={this.state.share_history} onChange={this.onShareHistoryChanged}/>
|
||||
Share message history with new users
|
||||
{_t('Share message history with new users')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="mx_CreateRoom_encrypt">
|
||||
<label>
|
||||
<input type="checkbox" ref="encrypt" checked={this.state.encrypt} onChange={this.onEncryptChanged}/>
|
||||
Encrypt room
|
||||
{_t('Encrypt room')}
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -14,13 +14,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require("react-dom");
|
||||
import React from 'react';
|
||||
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var sdk = require('../../index');
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var dis = require("../../dispatcher");
|
||||
import Matrix from 'matrix-js-sdk';
|
||||
import sdk from '../../index';
|
||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
/*
|
||||
* Component which shows the filtered file using a TimelinePanel
|
||||
|
@ -96,7 +95,7 @@ var FilePanel = React.createClass({
|
|||
</div>;
|
||||
} else if (this.noRoom) {
|
||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||
<div className="mx_RoomView_empty">You must join the room to see its files</div>
|
||||
<div className="mx_RoomView_empty">{_t("You must join the room to see its files")}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
@ -116,7 +115,7 @@ var FilePanel = React.createClass({
|
|||
showUrlPreview = { false }
|
||||
tileShape="file_grid"
|
||||
opacity={ this.props.opacity }
|
||||
empty="There are no visible files in this room"
|
||||
empty={_t('There are no visible files in this room')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
import * as Matrix from 'matrix-js-sdk';
|
||||
import React from 'react';
|
||||
|
||||
import UserSettingsStore from '../../UserSettingsStore';
|
||||
import KeyCode from '../../KeyCode';
|
||||
import Notifier from '../../Notifier';
|
||||
import PageTypes from '../../PageTypes';
|
||||
|
@ -64,6 +65,13 @@ export default React.createClass({
|
|||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
// use compact timeline view
|
||||
useCompactLayout: UserSettingsStore.getSyncedSetting('useCompactLayout'),
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// stash the MatrixClient in case we log out before we are unmounted
|
||||
this._matrixClient = this.props.matrixClient;
|
||||
|
@ -75,10 +83,12 @@ export default React.createClass({
|
|||
CallMediaHandler.loadDevices();
|
||||
|
||||
document.addEventListener('keydown', this._onKeyDown);
|
||||
this._matrixClient.on("accountData", this.onAccountData);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
document.removeEventListener('keydown', this._onKeyDown);
|
||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||
},
|
||||
|
||||
getScrollStateForRoom: function(roomId) {
|
||||
|
@ -92,6 +102,14 @@ export default React.createClass({
|
|||
return this.refs.roomView.canResetTimeline();
|
||||
},
|
||||
|
||||
onAccountData: function(event) {
|
||||
if (event.getType() === "im.vector.web.settings") {
|
||||
this.setState({
|
||||
useCompactLayout: event.getContent().useCompactLayout
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyDown: function(ev) {
|
||||
/*
|
||||
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
|
||||
|
@ -186,7 +204,7 @@ export default React.createClass({
|
|||
ConferenceHandler={this.props.ConferenceHandler}
|
||||
scrollStateMap={this._scrollStateMap}
|
||||
/>;
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.sideOpacity} />;
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.rightOpacity} />;
|
||||
break;
|
||||
|
||||
case PageTypes.UserSettings:
|
||||
|
@ -198,7 +216,7 @@ export default React.createClass({
|
|||
referralBaseUrl={this.props.config.referralBaseUrl}
|
||||
teamToken={this.props.teamToken}
|
||||
/>;
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
|
||||
break;
|
||||
|
||||
case PageTypes.CreateRoom:
|
||||
|
@ -206,7 +224,7 @@ export default React.createClass({
|
|||
onRoomCreated={this.props.onRoomCreated}
|
||||
collapsedRhs={this.props.collapse_rhs}
|
||||
/>;
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
|
||||
break;
|
||||
|
||||
case PageTypes.RoomDirectory:
|
||||
|
@ -222,12 +240,12 @@ export default React.createClass({
|
|||
teamServerUrl={this.props.config.teamServerConfig.teamServerURL}
|
||||
teamToken={this.props.teamToken}
|
||||
/>
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>
|
||||
break;
|
||||
|
||||
case PageTypes.UserView:
|
||||
page_element = null; // deliberately null for now
|
||||
right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.sideOpacity} />;
|
||||
right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.rightOpacity} />;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -248,6 +266,9 @@ export default React.createClass({
|
|||
if (topBar) {
|
||||
bodyClasses += ' mx_MatrixChat_toolbarShowing';
|
||||
}
|
||||
if (this.state.useCompactLayout) {
|
||||
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mx_MatrixChat_wrapper'>
|
||||
|
@ -256,7 +277,7 @@ export default React.createClass({
|
|||
<LeftPanel
|
||||
selectedRoom={this.props.currentRoomId}
|
||||
collapsed={this.props.collapse_lhs || false}
|
||||
opacity={this.props.sideOpacity}
|
||||
opacity={this.props.leftOpacity}
|
||||
teamToken={this.props.teamToken}
|
||||
/>
|
||||
<main className='mx_MatrixChat_middlePanel'>
|
||||
|
|
|
@ -20,6 +20,8 @@ import q from 'q';
|
|||
import React from 'react';
|
||||
import Matrix from "matrix-js-sdk";
|
||||
|
||||
import Analytics from "../../Analytics";
|
||||
import UserSettingsStore from '../../UserSettingsStore';
|
||||
import MatrixClientPeg from "../../MatrixClientPeg";
|
||||
import PlatformPeg from "../../PlatformPeg";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
|
@ -36,6 +38,7 @@ import PageTypes from '../../PageTypes';
|
|||
|
||||
import createRoom from "../../createRoom";
|
||||
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MatrixChat',
|
||||
|
@ -116,8 +119,9 @@ module.exports = React.createClass({
|
|||
collapse_rhs: false,
|
||||
ready: false,
|
||||
width: 10000,
|
||||
sideOpacity: 1.0,
|
||||
leftOpacity: 1.0,
|
||||
middleOpacity: 1.0,
|
||||
rightOpacity: 1.0,
|
||||
|
||||
version: null,
|
||||
newVersion: null,
|
||||
|
@ -187,6 +191,8 @@ module.exports = React.createClass({
|
|||
componentWillMount: function() {
|
||||
SdkConfig.put(this.props.config);
|
||||
|
||||
if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable();
|
||||
|
||||
// Used by _viewRoom before getting state from sync
|
||||
this.firstSyncComplete = false;
|
||||
this.firstSyncPromise = q.defer();
|
||||
|
@ -246,7 +252,6 @@ module.exports = React.createClass({
|
|||
UDEHandler.startListening();
|
||||
|
||||
this.focusComposer = false;
|
||||
window.addEventListener("focus", this.onFocus);
|
||||
|
||||
// this can technically be done anywhere but doing this here keeps all
|
||||
// the routing url path logic together.
|
||||
|
@ -287,7 +292,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
Lifecycle.stopMatrixClient();
|
||||
Lifecycle.stopMatrixClient(false);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
UDEHandler.stopListening();
|
||||
window.removeEventListener("focus", this.onFocus);
|
||||
|
@ -359,7 +364,7 @@ module.exports = React.createClass({
|
|||
// is completed in another browser, we'll be 401ed for using
|
||||
// a guest access token for a non-guest account.
|
||||
// It will be restarted in onReturnToGuestClick
|
||||
Lifecycle.stopMatrixClient();
|
||||
Lifecycle.stopMatrixClient(false);
|
||||
|
||||
this.notifyNewScreen('register');
|
||||
break;
|
||||
|
@ -375,8 +380,8 @@ module.exports = React.createClass({
|
|||
break;
|
||||
case 'reject_invite':
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Reject invitation",
|
||||
description: "Are you sure you want to reject the invitation?",
|
||||
title: _t('Reject invitation'),
|
||||
description: _t('Are you sure you want to reject the invitation?'),
|
||||
onFinished: (confirm) => {
|
||||
if (confirm) {
|
||||
// FIXME: controller shouldn't be loading a view :(
|
||||
|
@ -391,7 +396,7 @@ module.exports = React.createClass({
|
|||
}, (err) => {
|
||||
modal.close();
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to reject invitation",
|
||||
title: _t('Failed to reject invitation'),
|
||||
description: err.toString(),
|
||||
});
|
||||
});
|
||||
|
@ -437,9 +442,9 @@ module.exports = React.createClass({
|
|||
//this._setPage(PageTypes.CreateRoom);
|
||||
//this.notifyNewScreen('new');
|
||||
Modal.createDialog(TextInputDialog, {
|
||||
title: "Create Room",
|
||||
description: "Room name (optional)",
|
||||
button: "Create Room",
|
||||
title: _t('Create Room'),
|
||||
description: _t('Room name (optional)'),
|
||||
button: _t('Create Room'),
|
||||
onFinished: (shouldCreate, name) => {
|
||||
if (shouldCreate) {
|
||||
const createOpts = {};
|
||||
|
@ -490,12 +495,14 @@ module.exports = React.createClass({
|
|||
collapse_rhs: false,
|
||||
});
|
||||
break;
|
||||
case 'ui_opacity':
|
||||
case 'ui_opacity': {
|
||||
const sideDefault = payload.sideOpacity >= 0.0 ? payload.sideOpacity : 1.0;
|
||||
this.setState({
|
||||
sideOpacity: payload.sideOpacity,
|
||||
middleOpacity: payload.middleOpacity,
|
||||
leftOpacity: payload.leftOpacity >= 0.0 ? payload.leftOpacity : sideDefault,
|
||||
middleOpacity: payload.middleOpacity || 1.0,
|
||||
rightOpacity: payload.rightOpacity >= 0.0 ? payload.rightOpacity : sideDefault,
|
||||
});
|
||||
break;
|
||||
break; }
|
||||
case 'set_theme':
|
||||
this._onSetTheme(payload.value);
|
||||
break;
|
||||
|
@ -572,44 +579,44 @@ module.exports = React.createClass({
|
|||
|
||||
// switch view to the given room
|
||||
//
|
||||
// @param {Object} room_info Object containing data about the room to be joined
|
||||
// @param {string=} room_info.room_id ID of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {string=} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {boolean=} room_info.auto_join If true, automatically attempt to join the room if not already a member.
|
||||
// @param {boolean=} room_info.show_settings Makes RoomView show the room settings dialog.
|
||||
// @param {string=} room_info.event_id ID of the event in this room to show: this will cause a switch to the
|
||||
// @param {Object} roomInfo Object containing data about the room to be joined
|
||||
// @param {string=} roomInfo.room_id ID of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {string=} roomInfo.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {boolean=} roomInfo.auto_join If true, automatically attempt to join the room if not already a member.
|
||||
// @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 {Object=} room_info.third_party_invite Object containing data about the third party
|
||||
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party
|
||||
// we received to join the room, if any.
|
||||
// @param {string=} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||
// @param {string=} room_info.third_party_invite.invitedEmail The email address the invite was sent to
|
||||
// @param {Object=} room_info.oob_data Object of additional data about the room
|
||||
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||
// @param {string=} roomInfo.third_party_invite.invitedEmail The email address the invite was sent to
|
||||
// @param {Object=} roomInfo.oob_data Object of additional data about the room
|
||||
// that has been passed out-of-band (eg.
|
||||
// room name and avatar from an invite email)
|
||||
_viewRoom: function(room_info) {
|
||||
_viewRoom: function(roomInfo) {
|
||||
this.focusComposer = true;
|
||||
|
||||
const newState = {
|
||||
initialEventId: room_info.event_id,
|
||||
highlightedEventId: room_info.event_id,
|
||||
initialEventId: roomInfo.event_id,
|
||||
highlightedEventId: roomInfo.event_id,
|
||||
initialEventPixelOffset: undefined,
|
||||
page_type: PageTypes.RoomView,
|
||||
thirdPartyInvite: room_info.third_party_invite,
|
||||
roomOobData: room_info.oob_data,
|
||||
currentRoomAlias: room_info.room_alias,
|
||||
autoJoin: room_info.auto_join,
|
||||
thirdPartyInvite: roomInfo.third_party_invite,
|
||||
roomOobData: roomInfo.oob_data,
|
||||
currentRoomAlias: roomInfo.room_alias,
|
||||
autoJoin: roomInfo.auto_join,
|
||||
};
|
||||
|
||||
if (!room_info.room_alias) {
|
||||
newState.currentRoomId = room_info.room_id;
|
||||
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 (!room_info.event_id && this.refs.loggedInView) {
|
||||
const scrollState = this.refs.loggedInView.getScrollStateForRoom(room_info.room_id);
|
||||
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;
|
||||
|
@ -621,15 +628,15 @@ module.exports = React.createClass({
|
|||
let waitFor = q(null);
|
||||
if (!this.firstSyncComplete) {
|
||||
if (!this.firstSyncPromise) {
|
||||
console.warn('Cannot view a room before first sync. room_id:', room_info.room_id);
|
||||
console.warn('Cannot view a room before first sync. room_id:', roomInfo.room_id);
|
||||
return;
|
||||
}
|
||||
waitFor = this.firstSyncPromise.promise;
|
||||
}
|
||||
|
||||
waitFor.done(() => {
|
||||
let presentedId = room_info.room_alias || room_info.room_id;
|
||||
const room = MatrixClientPeg.get().getRoom(room_info.room_id);
|
||||
let presentedId = roomInfo.room_alias || roomInfo.room_id;
|
||||
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
||||
if (room) {
|
||||
const theAlias = Rooms.getDisplayAliasForRoom(room);
|
||||
if (theAlias) presentedId = theAlias;
|
||||
|
@ -641,8 +648,8 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
if (room_info.event_id) {
|
||||
presentedId += "/" + room_info.event_id;
|
||||
if (roomInfo.event_id) {
|
||||
presentedId += "/" + roomInfo.event_id;
|
||||
}
|
||||
this.notifyNewScreen('room/' + presentedId);
|
||||
newState.ready = true;
|
||||
|
@ -653,16 +660,20 @@ module.exports = React.createClass({
|
|||
_createChat: function() {
|
||||
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||
Modal.createDialog(ChatInviteDialog, {
|
||||
title: "Start a new chat",
|
||||
title: _t('Start a chat'),
|
||||
description: _t("Who would you like to communicate with?"),
|
||||
placeholder: _t("Email, name or matrix ID"),
|
||||
button: _t("Start Chat"),
|
||||
});
|
||||
},
|
||||
|
||||
_invite: function(roomId) {
|
||||
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||
Modal.createDialog(ChatInviteDialog, {
|
||||
title: "Invite new room members",
|
||||
button: "Send Invites",
|
||||
description: "Who would you like to add to this room?",
|
||||
title: _t('Invite new room members'),
|
||||
description: _t('Who would you like to add to this room?'),
|
||||
button: _t('Send Invites'),
|
||||
placeholder: _t("Email, name or matrix ID"),
|
||||
roomId: roomId,
|
||||
});
|
||||
},
|
||||
|
@ -692,9 +703,9 @@ module.exports = React.createClass({
|
|||
modal.close();
|
||||
console.error("Failed to leave room " + roomId + " " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to leave room",
|
||||
title: _t("Failed to leave room"),
|
||||
description: (err && err.message ? err.message :
|
||||
"Server may be unavailable, overloaded, or you hit a bug."),
|
||||
_t("Server may be unavailable, overloaded, or you hit a bug.")),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -886,8 +897,8 @@ module.exports = React.createClass({
|
|||
cli.on('Session.logged_out', function(call) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Signed Out",
|
||||
description: "For security, this session has been signed out. Please sign in again.",
|
||||
title: _t('Signed Out'),
|
||||
description: _t('For security, this session has been signed out. Please sign in again.'),
|
||||
});
|
||||
dis.dispatch({
|
||||
action: 'logout',
|
||||
|
@ -905,10 +916,6 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onFocus: function(ev) {
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
},
|
||||
|
||||
showScreen: function(screen, params) {
|
||||
if (screen == 'register') {
|
||||
dis.dispatch({
|
||||
|
@ -999,6 +1006,7 @@ module.exports = React.createClass({
|
|||
if (this.props.onNewScreen) {
|
||||
this.props.onNewScreen(screen);
|
||||
}
|
||||
Analytics.trackPageChange();
|
||||
},
|
||||
|
||||
onAliasClick: function(event, alias) {
|
||||
|
@ -1199,7 +1207,7 @@ module.exports = React.createClass({
|
|||
<div className="mx_MatrixChat_splash">
|
||||
<Spinner />
|
||||
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
|
||||
Logout
|
||||
{ _t('Logout') }
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -84,6 +84,12 @@ module.exports = React.createClass({
|
|||
|
||||
// shape parameter to be passed to EventTiles
|
||||
tileShape: React.PropTypes.string,
|
||||
|
||||
// show twelve hour timestamps
|
||||
isTwelveHour: React.PropTypes.bool,
|
||||
|
||||
// show timestamps always
|
||||
alwaysShowTimestamps: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
@ -230,8 +236,8 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_getEventTiles: function() {
|
||||
var EventTile = sdk.getComponent('rooms.EventTile');
|
||||
var DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
||||
|
||||
this.eventNodes = {};
|
||||
|
@ -310,7 +316,7 @@ module.exports = React.createClass({
|
|||
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
||||
|
||||
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
||||
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1}/></li>;
|
||||
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
|
||||
ret.push(dateSeparator);
|
||||
}
|
||||
|
||||
|
@ -413,8 +419,8 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_getTilesForEvent: function(prevEvent, mxEv, last) {
|
||||
var EventTile = sdk.getComponent('rooms.EventTile');
|
||||
var DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
var ret = [];
|
||||
|
||||
// is this a continuation of the previous message?
|
||||
|
@ -452,7 +458,7 @@ module.exports = React.createClass({
|
|||
|
||||
// do we need a date separator since the last event?
|
||||
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
||||
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1}/></li>;
|
||||
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
|
||||
ret.push(dateSeparator);
|
||||
continuation = false;
|
||||
}
|
||||
|
@ -468,7 +474,6 @@ module.exports = React.createClass({
|
|||
if (this.props.manageReadReceipts) {
|
||||
readReceipts = this._getReadReceiptsForEvent(mxEv);
|
||||
}
|
||||
|
||||
ret.push(
|
||||
<li key={eventId}
|
||||
ref={this._collectEventNode.bind(this, eventId)}
|
||||
|
@ -482,6 +487,7 @@ module.exports = React.createClass({
|
|||
checkUnmounting={this._isUnmounting}
|
||||
eventSendStatus={mxEv.status}
|
||||
tileShape={this.props.tileShape}
|
||||
isTwelveHour={this.props.isTwelveHour}
|
||||
last={last} isSelectedEvent={highlight}/>
|
||||
</li>
|
||||
);
|
||||
|
@ -615,8 +621,13 @@ module.exports = React.createClass({
|
|||
var style = this.props.hidden ? { display: 'none' } : {};
|
||||
style.opacity = this.props.opacity;
|
||||
|
||||
var className = this.props.className + " mx_fadable";
|
||||
if (this.props.alwaysShowTimestamps) {
|
||||
className += " mx_MessagePanel_alwaysShowTimestamps";
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollPanel ref="scrollPanel" className={ this.props.className + " mx_fadable" }
|
||||
<ScrollPanel ref="scrollPanel" className={ className }
|
||||
onScroll={ this.props.onScroll }
|
||||
onResize={ this.onResize }
|
||||
onFillRequest={ this.props.onFillRequest }
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
var React = require('react');
|
||||
var ReactDOM = require("react-dom");
|
||||
|
||||
import { _t } from '../../languageHandler';
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var sdk = require('../../index');
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
|
@ -37,7 +37,6 @@ var NotificationPanel = React.createClass({
|
|||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
|
||||
var timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
|
||||
|
||||
if (timelineSet) {
|
||||
return (
|
||||
<TimelinePanel key={"NotificationPanel_" + this.props.roomId}
|
||||
|
@ -48,7 +47,7 @@ var NotificationPanel = React.createClass({
|
|||
showUrlPreview = { false }
|
||||
opacity={ this.props.opacity }
|
||||
tileShape="notif"
|
||||
empty="You have no visible notifications"
|
||||
empty={ _t('You have no visible notifications') }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('../../index');
|
||||
var dis = require("../../dispatcher");
|
||||
var WhoIsTyping = require("../../WhoIsTyping");
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
const MemberAvatar = require("../views/avatars/MemberAvatar");
|
||||
import React from 'react';
|
||||
import { _t } from '../../languageHandler';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import WhoIsTyping from '../../WhoIsTyping';
|
||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||
import MemberAvatar from '../views/avatars/MemberAvatar';
|
||||
|
||||
const HIDE_DEBOUNCE_MS = 10000;
|
||||
const STATUS_BAR_HIDDEN = 0;
|
||||
|
@ -175,8 +176,8 @@ module.exports = React.createClass({
|
|||
<div className="mx_RoomStatusBar_scrollDownIndicator"
|
||||
onClick={ this.props.onScrollToBottomClick }>
|
||||
<img src="img/scrolldown.svg" width="24" height="24"
|
||||
alt="Scroll to bottom of page"
|
||||
title="Scroll to bottom of page"/>
|
||||
alt={ _t("Scroll to bottom of page") }
|
||||
title={ _t("Scroll to bottom of page") }/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -250,10 +251,10 @@ module.exports = React.createClass({
|
|||
<div className="mx_RoomStatusBar_connectionLostBar">
|
||||
<img src="img/warning.svg" width="24" height="23" title="/!\ " alt="/!\ "/>
|
||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||
Connectivity to the server has been lost.
|
||||
{_t('Connectivity to the server has been lost.')}
|
||||
</div>
|
||||
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
||||
Sent messages will be stored until your connection has returned.
|
||||
{_t('Sent messages will be stored until your connection has returned.')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -266,7 +267,7 @@ module.exports = React.createClass({
|
|||
<TabCompleteBar tabComplete={this.props.tabComplete} />
|
||||
<div className="mx_RoomStatusBar_tabCompleteEol" title="->|">
|
||||
<TintableSvg src="img/eol.svg" width="22" height="16"/>
|
||||
Auto-complete
|
||||
{_t('Auto-complete')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -283,13 +284,12 @@ module.exports = React.createClass({
|
|||
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
||||
<a className="mx_RoomStatusBar_resend_link"
|
||||
onClick={ this.props.onResendAllClick }>
|
||||
Resend all
|
||||
</a> or <a
|
||||
{_t('Resend all')}
|
||||
</a> {_t('or')} <a
|
||||
className="mx_RoomStatusBar_resend_link"
|
||||
onClick={ this.props.onCancelAllClick }>
|
||||
cancel all
|
||||
</a> now. You can also select individual messages to
|
||||
resend or cancel.
|
||||
{_t('cancel all')}
|
||||
</a> {_t('now. You can also select individual messages to resend or cancel.')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -324,7 +324,7 @@ module.exports = React.createClass({
|
|||
if (this.props.hasActiveCall) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar_callBar">
|
||||
<b>Active call</b>
|
||||
<b>{_t('Active call')}</b>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ var ReactDOM = require("react-dom");
|
|||
var q = require("q");
|
||||
var classNames = require("classnames");
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
var UserSettingsStore = require('../../UserSettingsStore');
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
|
@ -124,6 +125,8 @@ module.exports = React.createClass({
|
|||
room: null,
|
||||
roomId: null,
|
||||
roomLoading: true,
|
||||
|
||||
forwardingEvent: null,
|
||||
editingRoomSettings: false,
|
||||
uploadingRoomSettings: false,
|
||||
numUnreadMessages: 0,
|
||||
|
@ -296,7 +299,7 @@ module.exports = React.createClass({
|
|||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
if (newProps.roomAddress != this.props.roomAddress) {
|
||||
throw new Error("changing room on a RoomView is not supported");
|
||||
throw new Error(_t("changing room on a RoomView is not supported"));
|
||||
}
|
||||
|
||||
if (newProps.eventId != this.props.eventId) {
|
||||
|
@ -370,10 +373,10 @@ module.exports = React.createClass({
|
|||
onPageUnload(event) {
|
||||
if (ContentMessages.getCurrentUploads().length > 0) {
|
||||
return event.returnValue =
|
||||
'You seem to be uploading files, are you sure you want to quit?';
|
||||
_t("You seem to be uploading files, are you sure you want to quit?");
|
||||
} else if (this._getCallForRoom() && this.state.callState !== 'ended') {
|
||||
return event.returnValue =
|
||||
'You seem to be in a call, are you sure you want to quit?';
|
||||
_t("You seem to be in a call, are you sure you want to quit?");
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -451,6 +454,11 @@ module.exports = React.createClass({
|
|||
callState: callState
|
||||
});
|
||||
|
||||
break;
|
||||
case 'forward_event':
|
||||
this.setState({
|
||||
forwardingEvent: payload.content,
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@ -530,14 +538,14 @@ module.exports = React.createClass({
|
|||
if (!userHasUsedEncryption) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Warning!",
|
||||
title: _t("Warning!"),
|
||||
hasCancelButton: false,
|
||||
description: (
|
||||
<div>
|
||||
<p>End-to-end encryption is in beta and may not be reliable.</p>
|
||||
<p>You should <b>not</b> yet trust it to secure data.</p>
|
||||
<p>Devices will <b>not</b> yet be able to decrypt history from before they joined the room.</p>
|
||||
<p>Encrypted messages will not be visible on clients that do not yet implement encryption.</p>
|
||||
<p>{ _t("End-to-end encryption is in beta and may not be reliable") }.</p>
|
||||
<p>{ _t("You should not yet trust it to secure data") }.</p>
|
||||
<p>{ _t("Devices will not yet be able to decrypt history from before they joined the room") }.</p>
|
||||
<p>{ _t("Encrypted messages will not be visible on clients that do not yet implement encryption") }.</p>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
@ -708,10 +716,10 @@ module.exports = React.createClass({
|
|||
if (!unsentMessages.length) return "";
|
||||
for (const event of unsentMessages) {
|
||||
if (!event.error || event.error.name !== "UnknownDeviceError") {
|
||||
return "Some of your messages have not been sent.";
|
||||
return _t("Some of your messages have not been sent") + ".";
|
||||
}
|
||||
}
|
||||
return "Message not sent due to unknown devices being present";
|
||||
return _t("Message not sent due to unknown devices being present");
|
||||
},
|
||||
|
||||
_getUnsentMessages: function(room) {
|
||||
|
@ -871,15 +879,15 @@ module.exports = React.createClass({
|
|||
) {
|
||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Failed to join the room",
|
||||
description: "This room is private or inaccessible to guests. You may be able to join if you register."
|
||||
title: _t("Failed to join the room"),
|
||||
description: _t("This room is private or inaccessible to guests. You may be able to join if you register") + "."
|
||||
});
|
||||
} else {
|
||||
var msg = error.message ? error.message : JSON.stringify(error);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to join room",
|
||||
description: msg
|
||||
title: _t("Failed to join room"),
|
||||
description: msg,
|
||||
});
|
||||
}
|
||||
}).done();
|
||||
|
@ -939,8 +947,8 @@ module.exports = React.createClass({
|
|||
if (MatrixClientPeg.get().isGuest()) {
|
||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Please Register",
|
||||
description: "Guest users can't upload files. Please register to upload."
|
||||
title: _t("Please Register"),
|
||||
description: _t("Guest users can't upload files. Please register to upload") + "."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -959,8 +967,8 @@ module.exports = React.createClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to upload file " + file + " " + error);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to upload file",
|
||||
description: ((error && error.message) ? error.message : "Server may be unavailable, overloaded, or the file too big"),
|
||||
title: _t('Failed to upload file'),
|
||||
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or the file too big")),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -1046,8 +1054,8 @@ module.exports = React.createClass({
|
|||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Search failed: " + error);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Search failed",
|
||||
description: ((error && error.message) ? error.message : "Server may be unavailable, overloaded, or search timed out :("),
|
||||
title: _t("Search failed"),
|
||||
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")),
|
||||
});
|
||||
}).finally(function() {
|
||||
self.setState({
|
||||
|
@ -1082,12 +1090,12 @@ module.exports = React.createClass({
|
|||
if (!this.state.searchResults.next_batch) {
|
||||
if (this.state.searchResults.results.length == 0) {
|
||||
ret.push(<li key="search-top-marker">
|
||||
<h2 className="mx_RoomView_topMarker">No results</h2>
|
||||
<h2 className="mx_RoomView_topMarker">{ _t("No results") }</h2>
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
ret.push(<li key="search-top-marker">
|
||||
<h2 className="mx_RoomView_topMarker">No more results</h2>
|
||||
<h2 className="mx_RoomView_topMarker">{ _t("No more results") }</h2>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
@ -1124,10 +1132,10 @@ module.exports = React.createClass({
|
|||
// it. We should tell the js sdk to go and find out about
|
||||
// it. But that's not an issue currently, as synapse only
|
||||
// returns results for rooms we're joined to.
|
||||
var roomName = room ? room.name : "Unknown room "+roomId;
|
||||
var roomName = room ? room.name : _t("Unknown room %(roomId)s", { roomId: roomId });
|
||||
|
||||
ret.push(<li key={mxEv.getId() + "-room"}>
|
||||
<h1>Room: { roomName }</h1>
|
||||
<h1>{ _t("Room") }: { roomName }</h1>
|
||||
</li>);
|
||||
lastRoomId = roomId;
|
||||
}
|
||||
|
@ -1173,7 +1181,7 @@ module.exports = React.createClass({
|
|||
});
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to save settings",
|
||||
title: _t("Failed to save settings"),
|
||||
description: fails.map(function(result) { return result.reason; }).join("\n"),
|
||||
});
|
||||
// still editing room settings
|
||||
|
@ -1194,7 +1202,10 @@ module.exports = React.createClass({
|
|||
onCancelClick: function() {
|
||||
console.log("updateTint from onCancelClick");
|
||||
this.updateTint();
|
||||
this.setState({editingRoomSettings: false});
|
||||
this.setState({
|
||||
editingRoomSettings: false,
|
||||
forwardingEvent: null,
|
||||
});
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
},
|
||||
|
||||
|
@ -1209,11 +1220,11 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
|
||||
dis.dispatch({ action: 'view_next_room' });
|
||||
}, function(err) {
|
||||
var errCode = err.errcode || "unknown error code";
|
||||
var errCode = err.errcode || _t("unknown error code");
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
description: `Failed to forget room (${errCode})`
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -1234,8 +1245,8 @@ module.exports = React.createClass({
|
|||
var msg = error.message ? error.message : JSON.stringify(error);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to reject invite",
|
||||
description: msg
|
||||
title: _t("Failed to reject invite"),
|
||||
description: msg,
|
||||
});
|
||||
|
||||
self.setState({
|
||||
|
@ -1472,61 +1483,61 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
var RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||
var MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||
var RoomSettings = sdk.getComponent("rooms.RoomSettings");
|
||||
var AuxPanel = sdk.getComponent("rooms.AuxPanel");
|
||||
var SearchBar = sdk.getComponent("rooms.SearchBar");
|
||||
var ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
var RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||
const RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||
const ForwardMessage = sdk.getComponent("rooms.ForwardMessage");
|
||||
const RoomSettings = sdk.getComponent("rooms.RoomSettings");
|
||||
const AuxPanel = sdk.getComponent("rooms.AuxPanel");
|
||||
const SearchBar = sdk.getComponent("rooms.SearchBar");
|
||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||
|
||||
if (!this.state.room) {
|
||||
if (this.state.roomLoading) {
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
if (this.state.roomLoading) {
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
var inviterName = undefined;
|
||||
if (this.props.oobData) {
|
||||
inviterName = this.props.oobData.inviterName;
|
||||
}
|
||||
var invitedEmail = undefined;
|
||||
if (this.props.thirdPartyInvite) {
|
||||
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
|
||||
}
|
||||
else {
|
||||
var inviterName = undefined;
|
||||
if (this.props.oobData) {
|
||||
inviterName = this.props.oobData.inviterName;
|
||||
}
|
||||
var invitedEmail = undefined;
|
||||
if (this.props.thirdPartyInvite) {
|
||||
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
|
||||
}
|
||||
|
||||
// We have no room object for this room, only the ID.
|
||||
// We've got to this room by following a link, possibly a third party invite.
|
||||
var room_alias = this.props.roomAddress[0] == '#' ? this.props.roomAddress : null;
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<RoomHeader ref="header"
|
||||
room={this.state.room}
|
||||
oobData={this.props.oobData}
|
||||
collapsedRhs={ this.props.collapsedRhs }
|
||||
// We have no room object for this room, only the ID.
|
||||
// We've got to this room by following a link, possibly a third party invite.
|
||||
var room_alias = this.props.roomAddress[0] == '#' ? this.props.roomAddress : null;
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<RoomHeader ref="header"
|
||||
room={this.state.room}
|
||||
oobData={this.props.oobData}
|
||||
collapsedRhs={ this.props.collapsedRhs }
|
||||
/>
|
||||
<div className="mx_RoomView_auxPanel">
|
||||
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
||||
onForgetClick={ this.onForgetClick }
|
||||
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
|
||||
canPreview={ false } error={ this.state.roomLoadError }
|
||||
roomAlias={room_alias}
|
||||
spinner={this.state.joining}
|
||||
inviterName={inviterName}
|
||||
invitedEmail={invitedEmail}
|
||||
room={this.state.room}
|
||||
/>
|
||||
<div className="mx_RoomView_auxPanel">
|
||||
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
||||
onForgetClick={ this.onForgetClick }
|
||||
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
|
||||
canPreview={ false } error={ this.state.roomLoadError }
|
||||
roomAlias={room_alias}
|
||||
spinner={this.state.joining}
|
||||
inviterName={inviterName}
|
||||
invitedEmail={invitedEmail}
|
||||
room={this.state.room}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_RoomView_messagePanel"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div className="mx_RoomView_messagePanel"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
|
@ -1609,17 +1620,18 @@ module.exports = React.createClass({
|
|||
/>;
|
||||
}
|
||||
|
||||
var aux = null;
|
||||
let aux = null;
|
||||
let hideCancel = false;
|
||||
if (this.state.editingRoomSettings) {
|
||||
aux = <RoomSettings ref="room_settings" onSaveClick={this.onSettingsSaveClick} onCancelClick={this.onCancelClick} room={this.state.room} />;
|
||||
}
|
||||
else if (this.state.uploadingRoomSettings) {
|
||||
} else if (this.state.uploadingRoomSettings) {
|
||||
aux = <Loader/>;
|
||||
}
|
||||
else if (this.state.searching) {
|
||||
} else if (this.state.forwardingEvent !== null) {
|
||||
aux = <ForwardMessage onCancelClick={this.onCancelClick} currentRoomId={this.state.room.roomId} mxEvent={this.state.forwardingEvent} />;
|
||||
} else if (this.state.searching) {
|
||||
hideCancel = true; // has own cancel
|
||||
aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress } onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch}/>;
|
||||
}
|
||||
else if (!myMember || myMember.membership !== "join") {
|
||||
} else if (!myMember || myMember.membership !== "join") {
|
||||
// We do have a room object for this room, but we're not currently in it.
|
||||
// We may have a 3rd party invite to it.
|
||||
var inviterName = undefined;
|
||||
|
@ -1630,6 +1642,7 @@ module.exports = React.createClass({
|
|||
if (this.props.thirdPartyInvite) {
|
||||
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
|
||||
}
|
||||
hideCancel = true;
|
||||
aux = (
|
||||
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
|
||||
onForgetClick={ this.onForgetClick }
|
||||
|
@ -1681,7 +1694,7 @@ module.exports = React.createClass({
|
|||
|
||||
if (call.type === "video") {
|
||||
zoomButton = (
|
||||
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title="Fill screen">
|
||||
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title={ _t("Fill screen") }>
|
||||
<TintableSvg src="img/fullscreen.svg" width="29" height="22" style={{ marginTop: 1, marginRight: 4 }}/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1689,14 +1702,14 @@ module.exports = React.createClass({
|
|||
videoMuteButton =
|
||||
<div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}>
|
||||
<TintableSvg src={call.isLocalVideoMuted() ? "img/video-unmute.svg" : "img/video-mute.svg"}
|
||||
alt={call.isLocalVideoMuted() ? "Click to unmute video" : "Click to mute video"}
|
||||
alt={call.isLocalVideoMuted() ? _t("Click to unmute video") : _t("Click to mute video")}
|
||||
width="31" height="27"/>
|
||||
</div>;
|
||||
}
|
||||
voiceMuteButton =
|
||||
<div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}>
|
||||
<TintableSvg src={call.isMicrophoneMuted() ? "img/voice-unmute.svg" : "img/voice-mute.svg"}
|
||||
alt={call.isMicrophoneMuted() ? "Click to unmute audio" : "Click to mute audio"}
|
||||
alt={call.isMicrophoneMuted() ? _t("Click to unmute audio") : _t("Click to mute audio")}
|
||||
width="21" height="26"/>
|
||||
</div>;
|
||||
|
||||
|
@ -1732,14 +1745,13 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
||||
|
||||
var messagePanel = (
|
||||
<TimelinePanel ref={this._gatherTimelinePanelRef}
|
||||
timelineSet={this.state.room.getUnfilteredTimelineSet()}
|
||||
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
|
||||
manageReadMarkers={true}
|
||||
hidden={hideMessagePanel}
|
||||
highlightedEventId={this.props.highlightedEventId}
|
||||
highlightedEventId={this.state.forwardingEvent ? this.state.forwardingEvent.getId() : this.props.highlightedEventId}
|
||||
eventId={this.props.eventId}
|
||||
eventPixelOffset={this.props.eventPixelOffset}
|
||||
onScroll={ this.onMessageListScroll }
|
||||
|
@ -1777,13 +1789,10 @@ module.exports = React.createClass({
|
|||
onSearchClick={this.onSearchClick}
|
||||
onSettingsClick={this.onSettingsClick}
|
||||
onSaveClick={this.onSettingsSaveClick}
|
||||
onCancelClick={this.onCancelClick}
|
||||
onForgetClick={
|
||||
(myMember && myMember.membership === "leave") ? this.onForgetClick : null
|
||||
}
|
||||
onLeaveClick={
|
||||
(myMember && myMember.membership === "join") ? this.onLeaveClick : null
|
||||
} />
|
||||
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
||||
onForgetClick={(myMember && myMember.membership === "leave") ? this.onForgetClick : null}
|
||||
onLeaveClick={(myMember && myMember.membership === "join") ? this.onLeaveClick : null}
|
||||
/>
|
||||
{ auxPanel }
|
||||
{ topUnreadMessagesBar }
|
||||
{ messagePanel }
|
||||
|
|
|
@ -23,12 +23,14 @@ var Matrix = require("matrix-js-sdk");
|
|||
var EventTimeline = Matrix.EventTimeline;
|
||||
|
||||
var sdk = require('../../index');
|
||||
import { _t } from '../../languageHandler';
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var dis = require("../../dispatcher");
|
||||
var ObjectUtils = require('../../ObjectUtils');
|
||||
var Modal = require("../../Modal");
|
||||
var UserActivity = require("../../UserActivity");
|
||||
var KeyCode = require('../../KeyCode');
|
||||
import UserSettingsStore from '../../UserSettingsStore';
|
||||
|
||||
var PAGINATE_SIZE = 20;
|
||||
var INITIAL_SIZE = 20;
|
||||
|
@ -122,7 +124,7 @@ var TimelinePanel = React.createClass({
|
|||
let initialReadMarker = null;
|
||||
if (this.props.manageReadMarkers) {
|
||||
const readmarker = this.props.timelineSet.room.getAccountData('m.fully_read');
|
||||
if (readmarker){
|
||||
if (readmarker) {
|
||||
initialReadMarker = readmarker.getContent().event_id;
|
||||
} else {
|
||||
initialReadMarker = this._getCurrentReadReceipt();
|
||||
|
@ -171,6 +173,12 @@ var TimelinePanel = React.createClass({
|
|||
|
||||
// cache of matrixClient.getSyncState() (but from the 'sync' event)
|
||||
clientSyncState: MatrixClientPeg.get().getSyncState(),
|
||||
|
||||
// should the event tiles have twelve hour times
|
||||
isTwelveHour: UserSettingsStore.getSyncedSetting('showTwelveHourTimestamps'),
|
||||
|
||||
// always show timestamps on event tiles?
|
||||
alwaysShowTimestamps: UserSettingsStore.getSyncedSetting('alwaysShowTimestamps'),
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -907,14 +915,11 @@ var TimelinePanel = React.createClass({
|
|||
});
|
||||
};
|
||||
}
|
||||
var message = "Tried to load a specific point in this room's timeline, but ";
|
||||
if (error.errcode == 'M_FORBIDDEN') {
|
||||
message += "you do not have permission to view the message in question.";
|
||||
} else {
|
||||
message += "was unable to find it.";
|
||||
}
|
||||
var message = (error.errcode == 'M_FORBIDDEN')
|
||||
? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question") + "."
|
||||
: _t("Tried to load a specific point in this room's timeline, but was unable to find it") + ".";
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to load timeline position",
|
||||
title: _t("Failed to load timeline position"),
|
||||
description: message,
|
||||
onFinished: onFinished,
|
||||
});
|
||||
|
@ -1106,7 +1111,6 @@ var TimelinePanel = React.createClass({
|
|||
const forwardPaginating = (
|
||||
this.state.forwardPaginating || this.state.clientSyncState == 'PREPARED'
|
||||
);
|
||||
|
||||
return (
|
||||
<MessagePanel ref="messagePanel"
|
||||
hidden={ this.props.hidden }
|
||||
|
@ -1125,6 +1129,8 @@ var TimelinePanel = React.createClass({
|
|||
onFillRequest={ this.onMessageListFillRequest }
|
||||
onUnfillRequest={ this.onMessageListUnfillRequest }
|
||||
opacity={ this.props.opacity }
|
||||
isTwelveHour={ this.state.isTwelveHour }
|
||||
alwaysShowTimestamps={ this.state.alwaysShowTimestamps }
|
||||
className={ this.props.className }
|
||||
tileShape={ this.props.tileShape }
|
||||
/>
|
||||
|
|
|
@ -29,7 +29,10 @@ const GeminiScrollbar = require('react-gemini-scrollbar');
|
|||
const Email = require('../../email');
|
||||
const AddThreepid = require('../../AddThreepid');
|
||||
const SdkConfig = require('../../SdkConfig');
|
||||
import Analytics from '../../Analytics';
|
||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
import { _t } from '../../languageHandler';
|
||||
import * as languageHandler from '../../languageHandler';
|
||||
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||
|
||||
// if this looks like a release, use the 'version' from package.json; else use
|
||||
|
@ -54,6 +57,8 @@ const gHVersionLabel = function(repo, token='') {
|
|||
// Enumerate some simple 'flip a bit' UI settings (if any).
|
||||
// 'id' gives the key name in the im.vector.web.settings account data event
|
||||
// 'label' is how we describe it in the UI.
|
||||
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
|
||||
// since they will be translated when rendered.
|
||||
const SETTINGS_LABELS = [
|
||||
{
|
||||
id: 'autoplayGifsAndVideos',
|
||||
|
@ -67,7 +72,6 @@ const SETTINGS_LABELS = [
|
|||
id: 'dontSendTypingNotifications',
|
||||
label: "Don't send typing notifications",
|
||||
},
|
||||
/*
|
||||
{
|
||||
id: 'alwaysShowTimestamps',
|
||||
label: 'Always show message timestamps',
|
||||
|
@ -80,6 +84,7 @@ const SETTINGS_LABELS = [
|
|||
id: 'useCompactLayout',
|
||||
label: 'Use compact timeline layout',
|
||||
},
|
||||
/*
|
||||
{
|
||||
id: 'useFixedWidthFont',
|
||||
label: 'Use fixed width font',
|
||||
|
@ -87,10 +92,25 @@ const SETTINGS_LABELS = [
|
|||
*/
|
||||
];
|
||||
|
||||
const ANALYTICS_SETTINGS_LABELS = [
|
||||
{
|
||||
id: 'analyticsOptOut',
|
||||
label: 'Opt out of analytics',
|
||||
fn: function(checked) {
|
||||
Analytics[checked ? 'disable' : 'enable']();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
|
||||
// since they will be translated when rendered.
|
||||
const CRYPTO_SETTINGS_LABELS = [
|
||||
{
|
||||
id: 'blacklistUnverifiedDevices',
|
||||
label: 'Never send encrypted messages to unverified devices from this device',
|
||||
fn: function(checked) {
|
||||
MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
|
||||
},
|
||||
},
|
||||
// XXX: this is here for documentation; the actual setting is managed via RoomSettings
|
||||
// {
|
||||
|
@ -200,6 +220,17 @@ module.exports = React.createClass({
|
|||
this._syncedSettings = syncedSettings;
|
||||
|
||||
this._localSettings = UserSettingsStore.getLocalSettings();
|
||||
|
||||
if (PlatformPeg.get().isElectron()) {
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
ipcRenderer.on('settings', this._electronSettings);
|
||||
ipcRenderer.send('settings_get');
|
||||
}
|
||||
|
||||
this.setState({
|
||||
language: languageHandler.getCurrentLanguage(),
|
||||
});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -219,6 +250,15 @@ module.exports = React.createClass({
|
|||
if (cli) {
|
||||
cli.removeListener("RoomMember.membership", this._onInviteStateChange);
|
||||
}
|
||||
|
||||
if (PlatformPeg.get().isElectron()) {
|
||||
const {ipcRenderer} = require('electron');
|
||||
ipcRenderer.removeListener('settings', this._electronSettings);
|
||||
}
|
||||
},
|
||||
|
||||
_electronSettings: function(ev, settings) {
|
||||
this.setState({ electron_settings: settings });
|
||||
},
|
||||
|
||||
_refreshMediaDevices: function() {
|
||||
|
@ -249,8 +289,8 @@ module.exports = React.createClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to load user settings: " + error);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Can't load user settings",
|
||||
description: ((error && error.message) ? error.message : "Server may be unavailable or overloaded"),
|
||||
title: _t("Can't load user settings"),
|
||||
description: ((error && error.message) ? error.message : _t("Server may be unavailable or overloaded")),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -265,8 +305,8 @@ module.exports = React.createClass({
|
|||
if (MatrixClientPeg.get().isGuest()) {
|
||||
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Please Register",
|
||||
description: "Guests can't set avatars. Please register.",
|
||||
title: _t("Please Register"),
|
||||
description: _t("Guests can't set avatars. Please register."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -291,8 +331,8 @@ module.exports = React.createClass({
|
|||
console.error("Failed to set avatar: " + err);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to set avatar",
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
title: _t("Failed to set avatar."),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -300,19 +340,19 @@ module.exports = React.createClass({
|
|||
onLogoutClicked: function(ev) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Sign out?",
|
||||
title: _t("Sign out"),
|
||||
description:
|
||||
<div>
|
||||
For security, logging out will delete any end-to-end encryption keys from this browser.
|
||||
|
||||
If you want to be able to decrypt your conversation history from future Riot sessions,
|
||||
please export your room keys for safe-keeping.
|
||||
{ _t("For security, logging out will delete any end-to-end " +
|
||||
"encryption keys from this browser. If you want to be able " +
|
||||
"to decrypt your conversation history from future Riot sessions, " +
|
||||
"please export your room keys for safe-keeping.") }.
|
||||
</div>,
|
||||
button: "Sign out",
|
||||
button: _t("Sign out"),
|
||||
extraButtons: [
|
||||
<button key="export" className="mx_Dialog_primary"
|
||||
onClick={this._onExportE2eKeysClicked}>
|
||||
Export E2E room keys
|
||||
{ _t("Export E2E room keys") }
|
||||
</button>,
|
||||
],
|
||||
onFinished: (confirmed) => {
|
||||
|
@ -329,14 +369,14 @@ module.exports = React.createClass({
|
|||
onPasswordChangeError: function(err) {
|
||||
let errMsg = err.error || "";
|
||||
if (err.httpStatus === 403) {
|
||||
errMsg = "Failed to change password. Is your password correct?";
|
||||
errMsg = _t("Failed to change password. Is your password correct?");
|
||||
} else if (err.httpStatus) {
|
||||
errMsg += ` (HTTP status ${err.httpStatus})`;
|
||||
}
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to change password: " + errMsg);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
title: _t("Error"),
|
||||
description: errMsg,
|
||||
});
|
||||
},
|
||||
|
@ -344,10 +384,8 @@ module.exports = React.createClass({
|
|||
onPasswordChanged: function() {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Success",
|
||||
description: `Your password was successfully changed. You will not
|
||||
receive push notifications on other devices until you
|
||||
log back in to them.`,
|
||||
title: _t("Success"),
|
||||
description: _t("Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them") + ".",
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -373,8 +411,8 @@ module.exports = React.createClass({
|
|||
const emailAddress = this.refs.add_email_input.value;
|
||||
if (!Email.looksValid(emailAddress)) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Invalid Email Address",
|
||||
description: "This doesn't appear to be a valid email address",
|
||||
title: _t("Invalid Email Address"),
|
||||
description: _t("This doesn't appear to be a valid email address"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -383,17 +421,17 @@ module.exports = React.createClass({
|
|||
// same here.
|
||||
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Verification Pending",
|
||||
description: "Please check your email and click on the link it contains. Once this is done, click continue.",
|
||||
button: 'Continue',
|
||||
title: _t("Verification Pending"),
|
||||
description: _t("Please check your email and click on the link it contains. Once this is done, click continue."),
|
||||
button: _t('Continue'),
|
||||
onFinished: this.onEmailDialogFinished,
|
||||
});
|
||||
}, (err) => {
|
||||
this.setState({email_add_pending: false});
|
||||
console.error("Unable to add email address " + emailAddress + " " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unable to add email address",
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
title: _t("Unable to add email address"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
});
|
||||
ReactDOM.findDOMNode(this.refs.add_email_input).blur();
|
||||
|
@ -403,9 +441,9 @@ module.exports = React.createClass({
|
|||
onRemoveThreepidClicked: function(threepid) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Remove Contact Information?",
|
||||
description: "Remove " + threepid.address + "?",
|
||||
button: 'Remove',
|
||||
title: _t("Remove Contact Information?"),
|
||||
description: _t("Remove %(threePid)s?", { threePid : threepid.address }),
|
||||
button: _t('Remove'),
|
||||
onFinished: (submit) => {
|
||||
if (submit) {
|
||||
this.setState({
|
||||
|
@ -417,8 +455,8 @@ module.exports = React.createClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Unable to remove contact information: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unable to remove contact information",
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
title: _t("Unable to remove contact information"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
}).done();
|
||||
}
|
||||
|
@ -444,22 +482,22 @@ module.exports = React.createClass({
|
|||
this.setState({email_add_pending: false});
|
||||
}, (err) => {
|
||||
this.setState({email_add_pending: false});
|
||||
if (err.errcode === 'M_THREEPID_AUTH_FAILED') {
|
||||
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
let message = "Unable to verify email address. ";
|
||||
message += "Please check your email and click on the link it contains. Once this is done, click continue.";
|
||||
let message = _t("Unable to verify email address.") + " " +
|
||||
_t("Please check your email and click on the link it contains. Once this is done, click continue.");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Verification Pending",
|
||||
title: _t("Verification Pending"),
|
||||
description: message,
|
||||
button: 'Continue',
|
||||
button: _t('Continue'),
|
||||
onFinished: this.onEmailDialogFinished,
|
||||
});
|
||||
} else {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Unable to verify email address: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unable to verify email address",
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
title: _t("Unable to verify email address."),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -549,20 +587,43 @@ module.exports = React.createClass({
|
|||
<div>
|
||||
<h3>Referral</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
Refer a friend to Riot: <a href={href}>{href}</a>
|
||||
{_t("Refer a friend to Riot:")} <a href={href}>{href}</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
onLanguageChange: function(newLang) {
|
||||
if(this.state.language !== newLang) {
|
||||
UserSettingsStore.setLocalSetting('language', newLang);
|
||||
this.setState({
|
||||
language: newLang,
|
||||
});
|
||||
PlatformPeg.get().reload();
|
||||
}
|
||||
},
|
||||
|
||||
_renderLanguageSetting: function () {
|
||||
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
|
||||
return <div>
|
||||
<label htmlFor="languageSelector">{_t('Interface Language')}</label>
|
||||
<LanguageDropdown ref="language" onOptionChange={this.onLanguageChange}
|
||||
className="mx_UserSettings_language"
|
||||
value={this.state.language}
|
||||
/>
|
||||
</div>;
|
||||
},
|
||||
|
||||
_renderUserInterfaceSettings: function() {
|
||||
return (
|
||||
<div>
|
||||
<h3>User Interface</h3>
|
||||
<h3>{ _t("User Interface") }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
{ this._renderUrlPreviewSelector() }
|
||||
{ SETTINGS_LABELS.map( this._renderSyncedSetting ) }
|
||||
{ THEMES.map( this._renderThemeSelector ) }
|
||||
{ this._renderLanguageSetting() }
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -576,7 +637,7 @@ module.exports = React.createClass({
|
|||
onChange={ (e) => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
||||
/>
|
||||
<label htmlFor="urlPreviewsDisabled">
|
||||
Disable inline URL previews by default
|
||||
{ _t("Disable inline URL previews by default") }
|
||||
</label>
|
||||
</div>;
|
||||
},
|
||||
|
@ -586,10 +647,15 @@ module.exports = React.createClass({
|
|||
<input id={ setting.id }
|
||||
type="checkbox"
|
||||
defaultChecked={ this._syncedSettings[setting.id] }
|
||||
onChange={ (e) => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
|
||||
onChange={
|
||||
(e) => {
|
||||
UserSettingsStore.setSyncedSetting(setting.id, e.target.checked);
|
||||
if (setting.fn) setting.fn(e.target.checked);
|
||||
}
|
||||
}
|
||||
/>
|
||||
<label htmlFor={ setting.id }>
|
||||
{ setting.label }
|
||||
{ _t(setting.label) }
|
||||
</label>
|
||||
</div>;
|
||||
},
|
||||
|
@ -623,7 +689,7 @@ module.exports = React.createClass({
|
|||
const deviceId = client.deviceId;
|
||||
let identityKey = client.getDeviceEd25519Key();
|
||||
if (!identityKey) {
|
||||
identityKey = "<not supported>";
|
||||
identityKey = _t("<not supported>");
|
||||
} else {
|
||||
identityKey = FormattingUtils.formatCryptoKey(identityKey);
|
||||
}
|
||||
|
@ -635,18 +701,18 @@ module.exports = React.createClass({
|
|||
<div className="mx_UserSettings_importExportButtons">
|
||||
<AccessibleButton className="mx_UserSettings_button"
|
||||
onClick={this._onExportE2eKeysClicked}>
|
||||
Export E2E room keys
|
||||
{ _t("Export E2E room keys") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className="mx_UserSettings_button"
|
||||
onClick={this._onImportE2eKeysClicked}>
|
||||
Import E2E room keys
|
||||
{ _t("Import E2E room keys") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h3>Cryptography</h3>
|
||||
<h3>{ _t("Cryptography") }</h3>
|
||||
<div className="mx_UserSettings_section mx_UserSettings_cryptoSection">
|
||||
<ul>
|
||||
<li><label>Device ID:</label> <span><code>{deviceId}</code></span></li>
|
||||
|
@ -662,7 +728,6 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_renderLocalSetting: function(setting) {
|
||||
const client = MatrixClientPeg.get();
|
||||
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
||||
<input id={ setting.id }
|
||||
type="checkbox"
|
||||
|
@ -670,14 +735,12 @@ module.exports = React.createClass({
|
|||
onChange={
|
||||
(e) => {
|
||||
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
|
||||
if (setting.id === 'blacklistUnverifiedDevices') { // XXX: this is a bit ugly
|
||||
client.setGlobalBlacklistUnverifiedDevices(e.target.checked);
|
||||
}
|
||||
if (setting.fn) setting.fn(e.target.checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
<label htmlFor={ setting.id }>
|
||||
{ setting.label }
|
||||
{ _t(setting.label) }
|
||||
</label>
|
||||
</div>;
|
||||
},
|
||||
|
@ -698,20 +761,31 @@ module.exports = React.createClass({
|
|||
}
|
||||
return (
|
||||
<div>
|
||||
<h3>Bug Report</h3>
|
||||
<h3>{ _t("Bug Report") }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<p>Found a bug?</p>
|
||||
<p>{ _t("Found a bug?") }</p>
|
||||
<button className="mx_UserSettings_button danger"
|
||||
onClick={this._onBugReportClicked}>Report it
|
||||
onClick={this._onBugReportClicked}>{_t('Report it')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_renderAnalyticsControl: function() {
|
||||
return <div>
|
||||
<h3>{ _t('Analytics') }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
{_t('Riot collects anonymous analytics to allow us to improve the application.')}
|
||||
{ANALYTICS_SETTINGS_LABELS.map( this._renderLocalSetting )}
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
_renderLabs: function() {
|
||||
// default to enabled if undefined
|
||||
if (this.props.enableLabs === false) return null;
|
||||
UserSettingsStore.doTranslations();
|
||||
|
||||
const features = UserSettingsStore.LABS_FEATURES.map((feature) => (
|
||||
<div key={feature.id} className="mx_UserSettings_toggle">
|
||||
|
@ -725,8 +799,8 @@ module.exports = React.createClass({
|
|||
e.target.checked = false;
|
||||
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Please Register",
|
||||
description: "Guests can't use labs features. Please register.",
|
||||
title: _t("Please Register"),
|
||||
description: _t("Guests can't use labs features. Please register."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -739,9 +813,9 @@ module.exports = React.createClass({
|
|||
));
|
||||
return (
|
||||
<div>
|
||||
<h3>Labs</h3>
|
||||
<h3>{ _t("Labs") }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<p>These are experimental features that may break in unexpected ways. Use with caution.</p>
|
||||
<p>{ _t("These are experimental features that may break in unexpected ways") }. { _t("Use with caution") }.</p>
|
||||
{features}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -753,10 +827,10 @@ module.exports = React.createClass({
|
|||
if (MatrixClientPeg.get().isGuest()) return null;
|
||||
|
||||
return <div>
|
||||
<h3>Deactivate Account</h3>
|
||||
<h3>{ _t("Deactivate Account") }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<AccessibleButton className="mx_UserSettings_button danger"
|
||||
onClick={this._onDeactivateAccountClicked}>Deactivate my account
|
||||
onClick={this._onDeactivateAccountClicked}> { _t("Deactivate my account") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
|
@ -764,11 +838,11 @@ module.exports = React.createClass({
|
|||
|
||||
_renderClearCache: function() {
|
||||
return <div>
|
||||
<h3>Clear Cache</h3>
|
||||
<h3>{ _t("Clear Cache") }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<AccessibleButton className="mx_UserSettings_button danger"
|
||||
onClick={this._onClearCacheClicked}>
|
||||
Clear Cache and Reload
|
||||
{ _t("Clear Cache and Reload") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
|
@ -791,19 +865,42 @@ module.exports = React.createClass({
|
|||
reject = (
|
||||
<AccessibleButton className="mx_UserSettings_button danger"
|
||||
onClick={this._onRejectAllInvitesClicked.bind(this, invitedRooms)}>
|
||||
Reject all {invitedRooms.length} invites
|
||||
{_t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length})}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
return <div>
|
||||
<h3>Bulk Options</h3>
|
||||
<h3>{ _t("Bulk Options") }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
{reject}
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
_renderElectronSettings: function() {
|
||||
const settings = this.state.electron_settings;
|
||||
if (!settings) return;
|
||||
|
||||
const {ipcRenderer} = require('electron');
|
||||
|
||||
return <div>
|
||||
<h3>{ _t('Desktop specific') }</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_toggle">
|
||||
<input type="checkbox"
|
||||
name="auto-launch"
|
||||
defaultChecked={settings['auto-launch']}
|
||||
onChange={(e) => {
|
||||
ipcRenderer.send('settings_set', 'auto-launch', e.target.checked);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="auto-launch">{_t('Start automatically after system login')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
_mapWebRtcDevicesToSpans: function(devices) {
|
||||
return Object.keys(devices).map(
|
||||
(deviceId) => <span key={deviceId}>{devices[deviceId]}</span>
|
||||
|
@ -904,7 +1001,8 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
nameForMedium: function(medium) {
|
||||
if (medium === 'msisdn') return 'Phone';
|
||||
if (medium === 'msisdn') return _t('Phone');
|
||||
if (medium === 'email') return _t('Email');
|
||||
return medium[0].toUpperCase() + medium.slice(1);
|
||||
},
|
||||
|
||||
|
@ -953,7 +1051,7 @@ module.exports = React.createClass({
|
|||
/>
|
||||
</div>
|
||||
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||
<img src="img/cancel-small.svg" width="14" height="14" alt="Remove" onClick={this.onRemoveThreepidClicked.bind(this, val)} />
|
||||
<img src="img/cancel-small.svg" width="14" height="14" alt={ _t("Remove") } onClick={this.onRemoveThreepidClicked.bind(this, val)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -965,14 +1063,14 @@ module.exports = React.createClass({
|
|||
addEmailSection = (
|
||||
<div className="mx_UserSettings_profileTableRow" key="_newEmail">
|
||||
<div className="mx_UserSettings_profileLabelCell">
|
||||
<label>Email</label>
|
||||
<label>{_t('Email')}</label>
|
||||
</div>
|
||||
<div className="mx_UserSettings_profileInputCell">
|
||||
<EditableText
|
||||
ref="add_email_input"
|
||||
className="mx_UserSettings_editable"
|
||||
placeholderClassName="mx_UserSettings_threepidPlaceholder"
|
||||
placeholder={ "Add email address" }
|
||||
placeholder={ _t("Add email address") }
|
||||
blurToCancel={ false }
|
||||
onValueChanged={ this._onAddEmailEditFinished } />
|
||||
</div>
|
||||
|
@ -994,7 +1092,7 @@ module.exports = React.createClass({
|
|||
if (MatrixClientPeg.get().isGuest()) {
|
||||
accountJsx = (
|
||||
<div className="mx_UserSettings_button" onClick={this.onUpgradeClicked}>
|
||||
Create an account
|
||||
{ _t("Create an account") }
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
@ -1012,7 +1110,7 @@ module.exports = React.createClass({
|
|||
let notificationArea;
|
||||
if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) {
|
||||
notificationArea = (<div>
|
||||
<h3>Notifications</h3>
|
||||
<h3>{ _t("Notifications") }</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
<Notifications threepids={this.state.threepids} brand={this.props.brand} />
|
||||
|
@ -1031,7 +1129,7 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<div className="mx_UserSettings">
|
||||
<SimpleRoomHeader
|
||||
title="Settings"
|
||||
title={ _t("Settings") }
|
||||
collapsedRhs={ this.props.collapsedRhs }
|
||||
onCancelClick={ this.props.onClose }
|
||||
/>
|
||||
|
@ -1039,13 +1137,13 @@ module.exports = React.createClass({
|
|||
<GeminiScrollbar className="mx_UserSettings_body"
|
||||
autoshow={true}>
|
||||
|
||||
<h3>Profile</h3>
|
||||
<h3>{ _t("Profile") }</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_profileTable">
|
||||
<div className="mx_UserSettings_profileTableRow">
|
||||
<div className="mx_UserSettings_profileLabelCell">
|
||||
<label htmlFor="displayName">Display name</label>
|
||||
<label htmlFor="displayName">{ _t('Display name') }</label>
|
||||
</div>
|
||||
<div className="mx_UserSettings_profileInputCell">
|
||||
<ChangeDisplayName />
|
||||
|
@ -1062,7 +1160,7 @@ module.exports = React.createClass({
|
|||
<div className="mx_UserSettings_avatarPicker_edit">
|
||||
<label htmlFor="avatarInput" ref="file_label">
|
||||
<img src="img/camera.svg" className="mx_filterFlipColor"
|
||||
alt="Upload avatar" title="Upload avatar"
|
||||
alt={ _t("Upload avatar") } title={ _t("Upload avatar") }
|
||||
width="17" height="15" />
|
||||
</label>
|
||||
<input id="avatarInput" type="file" onChange={this.onAvatarSelected}/>
|
||||
|
@ -1070,12 +1168,12 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Account</h3>
|
||||
<h3>{ _t("Account") }</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_section cadcampoHide">
|
||||
|
||||
<AccessibleButton className="mx_UserSettings_logout mx_UserSettings_button" onClick={this.onLogoutClicked}>
|
||||
Sign out
|
||||
{ _t("Sign out") }
|
||||
</AccessibleButton>
|
||||
|
||||
{accountJsx}
|
||||
|
@ -1093,34 +1191,35 @@ module.exports = React.createClass({
|
|||
{this._renderBulkOptions()}
|
||||
{this._renderBugReport()}
|
||||
|
||||
<h3>Advanced</h3>
|
||||
{PlatformPeg.get().isElectron() && this._renderElectronSettings()}
|
||||
|
||||
{this._renderAnalyticsControl()}
|
||||
|
||||
<h3>{ _t("Advanced") }</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_advanced">
|
||||
Logged in as {this._me}
|
||||
{ _t("Logged in as:") } {this._me}
|
||||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
Access Token: <span className="mx_UserSettings_advanced_spoiler"
|
||||
onClick={this._showSpoiler}
|
||||
data-spoiler={ MatrixClientPeg.get().getAccessToken() }
|
||||
><click to reveal></span>
|
||||
{_t('Access Token:')} <span className="mx_UserSettings_advanced_spoiler" onClick={this._showSpoiler} data-spoiler={ MatrixClientPeg.get().getAccessToken() }><{ _t("click to reveal") }></span>
|
||||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
Homeserver is { MatrixClientPeg.get().getHomeserverUrl() }
|
||||
{ _t("Homeserver is") } { MatrixClientPeg.get().getHomeserverUrl() }
|
||||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
Identity Server is { MatrixClientPeg.get().getIdentityServerUrl() }
|
||||
{ _t("Identity Server is") } { MatrixClientPeg.get().getIdentityServerUrl() }
|
||||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
matrix-react-sdk version: {(REACT_SDK_VERSION !== '<local>')
|
||||
{_t('matrix-react-sdk version:')} {(REACT_SDK_VERSION !== '<local>')
|
||||
? gHVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION)
|
||||
: REACT_SDK_VERSION
|
||||
}<br/>
|
||||
riot-web version: {(this.state.vectorVersion !== undefined)
|
||||
{_t('riot-web version:')} {(this.state.vectorVersion !== undefined)
|
||||
? gHVersionLabel('vector-im/riot-web', this.state.vectorVersion)
|
||||
: 'unknown'
|
||||
}<br/>
|
||||
olm version: {olmVersionString}<br/>
|
||||
{ _t("olm version:") } {olmVersionString}<br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
import { _t } from '../../../languageHandler';
|
||||
var sdk = require('../../../index');
|
||||
var Modal = require("../../../Modal");
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
|
@ -54,7 +55,7 @@ module.exports = React.createClass({
|
|||
progress: "sent_email"
|
||||
});
|
||||
}, (err) => {
|
||||
this.showErrorDialog("Failed to send email: " + err.message);
|
||||
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
||||
this.setState({
|
||||
progress: null
|
||||
});
|
||||
|
@ -78,30 +79,33 @@ module.exports = React.createClass({
|
|||
ev.preventDefault();
|
||||
|
||||
if (!this.state.email) {
|
||||
this.showErrorDialog("The email address linked to your account must be entered.");
|
||||
this.showErrorDialog(_t('The email address linked to your account must be entered.'));
|
||||
}
|
||||
else if (!this.state.password || !this.state.password2) {
|
||||
this.showErrorDialog("A new password must be entered.");
|
||||
this.showErrorDialog(_t('A new password must be entered.'));
|
||||
}
|
||||
else if (this.state.password !== this.state.password2) {
|
||||
this.showErrorDialog("New passwords must match each other.");
|
||||
this.showErrorDialog(_t('New passwords must match each other.'));
|
||||
}
|
||||
else {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Warning",
|
||||
title: _t('Warning!'),
|
||||
description:
|
||||
<div>
|
||||
Resetting password will currently reset any end-to-end encryption keys on all devices,
|
||||
making encrypted chat history unreadable, unless you first export your room keys
|
||||
and re-import them afterwards.
|
||||
In future this <a href="https://github.com/vector-im/riot-web/issues/2671">will be improved</a>.
|
||||
{ _t(
|
||||
'Resetting password will currently reset any ' +
|
||||
'end-to-end encryption keys on all devices, ' +
|
||||
'making encrypted chat history unreadable, ' +
|
||||
'unless you first export your room keys and re-import ' +
|
||||
'them afterwards. In future this will be improved.'
|
||||
) }
|
||||
</div>,
|
||||
button: "Continue",
|
||||
button: _t('Continue'),
|
||||
extraButtons: [
|
||||
<button className="mx_Dialog_primary"
|
||||
onClick={this._onExportE2eKeysClicked}>
|
||||
Export E2E room keys
|
||||
{ _t('Export E2E room keys') }
|
||||
</button>
|
||||
],
|
||||
onFinished: (confirmed) => {
|
||||
|
@ -150,7 +154,7 @@ module.exports = React.createClass({
|
|||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: title,
|
||||
description: body
|
||||
description: body,
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -168,22 +172,20 @@ module.exports = React.createClass({
|
|||
else if (this.state.progress === "sent_email") {
|
||||
resetPasswordJsx = (
|
||||
<div>
|
||||
An email has been sent to {this.state.email}. Once you've followed
|
||||
the link it contains, click below.
|
||||
{ _t('An email has been sent to') } {this.state.email}. { _t('Once you've followed the link it contains, click below') }.
|
||||
<br />
|
||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||
value="I have verified my email address" />
|
||||
value={ _t('I have verified my email address') } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if (this.state.progress === "complete") {
|
||||
resetPasswordJsx = (
|
||||
<div>
|
||||
<p>Your password has been reset.</p>
|
||||
<p>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.</p>
|
||||
<p>{ _t('Your password has been reset') }.</p>
|
||||
<p>{ _t('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') }.</p>
|
||||
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
||||
value="Return to login screen" />
|
||||
value={ _t('Return to login screen') } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -191,7 +193,7 @@ module.exports = React.createClass({
|
|||
resetPasswordJsx = (
|
||||
<div>
|
||||
<div className="mx_Login_prompt">
|
||||
To reset your password, enter the email address linked to your account:
|
||||
{ _t('To reset your password, enter the email address linked to your account') }:
|
||||
</div>
|
||||
<div>
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
|
@ -199,21 +201,21 @@ module.exports = React.createClass({
|
|||
name="reset_email" // define a name so browser's password autofill gets less confused
|
||||
value={this.state.email}
|
||||
onChange={this.onInputChanged.bind(this, "email")}
|
||||
placeholder="Email address" autoFocus />
|
||||
placeholder={ _t('Email address') } autoFocus />
|
||||
<br />
|
||||
<input className="mx_Login_field" ref="pass" type="password"
|
||||
name="reset_password"
|
||||
value={this.state.password}
|
||||
onChange={this.onInputChanged.bind(this, "password")}
|
||||
placeholder="New password" />
|
||||
placeholder={ _t('New password') } />
|
||||
<br />
|
||||
<input className="mx_Login_field" ref="pass" type="password"
|
||||
name="reset_password_confirm"
|
||||
value={this.state.password2}
|
||||
onChange={this.onInputChanged.bind(this, "password2")}
|
||||
placeholder="Confirm your new password" />
|
||||
placeholder={ _t('Confirm your new password') } />
|
||||
<br />
|
||||
<input className="mx_Login_submit" type="submit" value="Send Reset Email" />
|
||||
<input className="mx_Login_submit" type="submit" value={ _t('Send Reset Email') } />
|
||||
</form>
|
||||
<ServerConfig ref="serverConfig"
|
||||
withToggleButton={true}
|
||||
|
@ -230,7 +232,7 @@ module.exports = React.createClass({
|
|||
Return to login
|
||||
</a>
|
||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||
Create a new account
|
||||
{ _t('Create an account') }
|
||||
</a>
|
||||
<LoginFooter />
|
||||
</div>
|
||||
|
|
|
@ -18,8 +18,8 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { _t, _tJsx } from '../../../languageHandler';
|
||||
import ReactDOM from 'react-dom';
|
||||
import url from 'url';
|
||||
import sdk from '../../../index';
|
||||
import Login from '../../../Login';
|
||||
|
||||
|
@ -223,14 +223,19 @@ module.exports = React.createClass({
|
|||
!this.state.enteredHomeserverUrl.startsWith("http")))
|
||||
{
|
||||
errorText = <span>
|
||||
Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar.
|
||||
Either use HTTPS or <a href='https://www.google.com/search?&q=enable%20unsafe%20scripts'>enable unsafe scripts</a>
|
||||
{ _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>.",
|
||||
/<a>(.*?)<\/a>/,
|
||||
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; }
|
||||
)}
|
||||
</span>;
|
||||
}
|
||||
else {
|
||||
errorText = <span>
|
||||
Can't connect to homeserver - please check your connectivity and ensure
|
||||
your <a href={ this.state.enteredHomeserverUrl }>homeserver's SSL certificate</a> is trusted.
|
||||
{ _tJsx("Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
|
||||
/<a>(.*?)<\/a>/,
|
||||
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
|
||||
)}
|
||||
</span>;
|
||||
}
|
||||
}
|
||||
|
@ -242,12 +247,6 @@ module.exports = React.createClass({
|
|||
switch (step) {
|
||||
case 'm.login.password':
|
||||
const PasswordLogin = sdk.getComponent('login.PasswordLogin');
|
||||
// HSs that are not matrix.org may not be configured to have their
|
||||
// domain name === domain part.
|
||||
let hsDomain = url.parse(this.state.enteredHomeserverUrl).hostname;
|
||||
if (hsDomain !== 'matrix.org') {
|
||||
hsDomain = null;
|
||||
}
|
||||
return (
|
||||
<PasswordLogin
|
||||
onSubmit={this.onPasswordLogin}
|
||||
|
@ -259,7 +258,6 @@ module.exports = React.createClass({
|
|||
onPhoneNumberChanged={this.onPhoneNumberChanged}
|
||||
onForgotPasswordClick={this.props.onForgotPasswordClick}
|
||||
loginIncorrect={this.state.loginIncorrect}
|
||||
hsDomain={hsDomain}
|
||||
/>
|
||||
);
|
||||
case 'm.login.cas':
|
||||
|
@ -273,8 +271,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
return (
|
||||
<div>
|
||||
Sorry, this homeserver is using a login which is not
|
||||
recognised ({step})
|
||||
{ _t('Sorry, this homeserver is using a login which is not recognised ')}({step})
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -291,7 +288,7 @@ module.exports = React.createClass({
|
|||
if (this.props.enableGuest) {
|
||||
loginAsGuestJsx =
|
||||
<a className="mx_Login_create" onClick={this._onLoginAsGuestClick} href="#">
|
||||
Login as guest
|
||||
{ _t('Login as guest')}
|
||||
</a>;
|
||||
}
|
||||
|
||||
|
@ -299,7 +296,7 @@ module.exports = React.createClass({
|
|||
if (this.props.onCancelClick) {
|
||||
returnToAppJsx =
|
||||
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
||||
Return to app
|
||||
{ _t('Return to app')}
|
||||
</a>;
|
||||
}
|
||||
|
||||
|
@ -308,7 +305,7 @@ module.exports = React.createClass({
|
|||
<div className="mx_Login_box">
|
||||
<LoginHeader />
|
||||
<div>
|
||||
<h2>Sign in
|
||||
<h2>{ _t('Sign in')}
|
||||
{ loader }
|
||||
</h2>
|
||||
{ this.componentForStep(this.state.currentFlow) }
|
||||
|
@ -324,7 +321,7 @@ module.exports = React.createClass({
|
|||
{ this.state.errorText }
|
||||
</div>
|
||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||
Create a new account
|
||||
{ _t('Create an account')}
|
||||
</a>
|
||||
{ loginAsGuestJsx }
|
||||
{ returnToAppJsx }
|
||||
|
|
|
@ -16,9 +16,10 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('../../../index');
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'PostRegistration',
|
||||
|
@ -64,12 +65,12 @@ module.exports = React.createClass({
|
|||
<div className="mx_Login_box">
|
||||
<LoginHeader />
|
||||
<div className="mx_Login_profile">
|
||||
Set a display name:
|
||||
{ _t('Set a display name:') }
|
||||
<ChangeDisplayName />
|
||||
Upload an avatar:
|
||||
{ _t('Upload an avatar:') }
|
||||
<ChangeAvatar
|
||||
initialAvatarUrl={this.state.avatarUrl} />
|
||||
<button onClick={this.props.onComplete}>Continue</button>
|
||||
<button onClick={this.props.onComplete}>{ _t('Continue') }</button>
|
||||
{this.state.errorString}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -27,6 +27,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
|||
import RegistrationForm from '../../views/login/RegistrationForm';
|
||||
import CaptchaForm from '../../views/login/CaptchaForm';
|
||||
import RtsClient from '../../../RtsClient';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
const MIN_PASSWORD_LENGTH = 6;
|
||||
|
||||
|
@ -162,7 +163,7 @@ module.exports = React.createClass({
|
|||
msisdn_available |= flow.stages.indexOf('m.login.msisdn') > -1;
|
||||
}
|
||||
if (!msisdn_available) {
|
||||
msg = "This server does not support authentication with a phone number";
|
||||
msg = _t('This server does not support authentication with a phone number.');
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
|
@ -260,29 +261,29 @@ module.exports = React.createClass({
|
|||
var errMsg;
|
||||
switch (errCode) {
|
||||
case "RegistrationForm.ERR_PASSWORD_MISSING":
|
||||
errMsg = "Missing password.";
|
||||
errMsg = _t('Missing password.');
|
||||
break;
|
||||
case "RegistrationForm.ERR_PASSWORD_MISMATCH":
|
||||
errMsg = "Passwords don't match.";
|
||||
errMsg = _t('Passwords don\'t match.');
|
||||
break;
|
||||
case "RegistrationForm.ERR_PASSWORD_LENGTH":
|
||||
errMsg = `Password too short (min ${MIN_PASSWORD_LENGTH}).`;
|
||||
errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH: MIN_PASSWORD_LENGTH});
|
||||
break;
|
||||
case "RegistrationForm.ERR_EMAIL_INVALID":
|
||||
errMsg = "This doesn't look like a valid email address";
|
||||
errMsg = _t('This doesn\'t look like a valid email address.');
|
||||
break;
|
||||
case "RegistrationForm.ERR_PHONE_NUMBER_INVALID":
|
||||
errMsg = "This doesn't look like a valid phone number";
|
||||
errMsg = _t('This doesn\'t look like a valid phone number.');
|
||||
break;
|
||||
case "RegistrationForm.ERR_USERNAME_INVALID":
|
||||
errMsg = "User names may only contain letters, numbers, dots, hyphens and underscores.";
|
||||
errMsg = _t('User names may only contain letters, numbers, dots, hyphens and underscores.');
|
||||
break;
|
||||
case "RegistrationForm.ERR_USERNAME_BLANK":
|
||||
errMsg = "You need to enter a user name";
|
||||
errMsg = _t('You need to enter a user name.');
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown error code: %s", errCode);
|
||||
errMsg = "An unknown error occurred.";
|
||||
errMsg = _t('An unknown error occurred.');
|
||||
break;
|
||||
}
|
||||
this.setState({
|
||||
|
@ -400,7 +401,7 @@ module.exports = React.createClass({
|
|||
if (this.props.onCancelClick) {
|
||||
returnToAppJsx = (
|
||||
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
||||
Return to app
|
||||
{_t('Return to app')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
@ -413,10 +414,10 @@ module.exports = React.createClass({
|
|||
this.state.teamSelected.domain + "/icon.png" :
|
||||
null}
|
||||
/>
|
||||
<h2>Create an account</h2>
|
||||
<h2>{_t('Create an account')}</h2>
|
||||
{registerBody}
|
||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||
I already have an account
|
||||
{_t('I already have an account')}
|
||||
</a>
|
||||
{returnToAppJsx}
|
||||
<LoginFooter />
|
||||
|
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CreateRoomButton',
|
||||
propTypes: {
|
||||
|
@ -36,7 +36,7 @@ module.exports = React.createClass({
|
|||
|
||||
render: function() {
|
||||
return (
|
||||
<button className="mx_CreateRoomButton" onClick={this.onClick}>Create Room</button>
|
||||
<button className="mx_CreateRoomButton" onClick={this.onClick}>{_t("Create Room")}</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -86,7 +86,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
|
|||
<div className="mx_RoomTile_avatar">
|
||||
<img src="img/create-big.svg" width="26" height="26" />
|
||||
</div>
|
||||
<div className={labelClasses}><i>Start new chat</i></div>
|
||||
<div className={labelClasses}><i>{_("Start new chat")}</i></div>
|
||||
</AccessibleButton>;
|
||||
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
|
||||
import createRoom from '../../../createRoom';
|
||||
|
@ -26,14 +27,13 @@ import dis from '../../../dispatcher';
|
|||
import Modal from '../../../Modal';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import q from 'q';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
const TRUNCATE_QUERY_LIST = 40;
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: "ChatInviteDialog",
|
||||
propTypes: {
|
||||
title: React.PropTypes.string,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
description: React.PropTypes.oneOfType([
|
||||
React.PropTypes.element,
|
||||
React.PropTypes.string,
|
||||
|
@ -48,11 +48,7 @@ module.exports = React.createClass({
|
|||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
title: "Start a chat",
|
||||
description: "Who would you like to communicate with?",
|
||||
value: "",
|
||||
placeholder: "Email, name or matrix ID",
|
||||
button: "Start Chat",
|
||||
focus: true
|
||||
};
|
||||
},
|
||||
|
@ -77,19 +73,6 @@ module.exports = React.createClass({
|
|||
// Set the cursor at the end of the text input
|
||||
this.refs.textinput.value = this.props.value;
|
||||
}
|
||||
// Create a Fuse instance for fuzzy searching this._userList
|
||||
this._fuse = new Fuse(
|
||||
// Use an empty list at first that will later be populated
|
||||
// (see this._updateUserList)
|
||||
[],
|
||||
{
|
||||
shouldSort: true,
|
||||
location: 0, // The index of the query in the test string
|
||||
distance: 5, // The distance away from location the query can be
|
||||
// 0.0 = exact match, 1.0 = match anything
|
||||
threshold: 0.3,
|
||||
}
|
||||
);
|
||||
this._updateUserList();
|
||||
},
|
||||
|
||||
|
@ -178,7 +161,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onQueryChanged: function(ev) {
|
||||
const query = ev.target.value;
|
||||
const query = ev.target.value.toLowerCase();
|
||||
let queryList = [];
|
||||
|
||||
if (query.length < 2) {
|
||||
|
@ -191,24 +174,27 @@ module.exports = React.createClass({
|
|||
this.queryChangedDebouncer = setTimeout(() => {
|
||||
// Only do search if there is something to search
|
||||
if (query.length > 0 && query != '@') {
|
||||
// Weighted keys prefer to match userIds when first char is @
|
||||
this._fuse.options.keys = [{
|
||||
name: 'displayName',
|
||||
weight: query[0] === '@' ? 0.1 : 0.9,
|
||||
},{
|
||||
name: 'userId',
|
||||
weight: query[0] === '@' ? 0.9 : 0.1,
|
||||
}];
|
||||
queryList = this._fuse.search(query).map((user) => {
|
||||
this._userList.forEach((user) => {
|
||||
if (user.userId.toLowerCase().indexOf(query) === -1 &&
|
||||
user.displayName.toLowerCase().indexOf(query) === -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return objects, structure of which is defined
|
||||
// by InviteAddressType
|
||||
return {
|
||||
queryList.push({
|
||||
addressType: 'mx',
|
||||
address: user.userId,
|
||||
displayName: user.displayName,
|
||||
avatarMxc: user.avatarUrl,
|
||||
isKnown: true,
|
||||
}
|
||||
order: user.getLastActiveTs(),
|
||||
});
|
||||
});
|
||||
|
||||
queryList = queryList.sort((a,b) => {
|
||||
return a.order < b.order;
|
||||
});
|
||||
|
||||
// If the query is a valid address, add an entry for that
|
||||
|
@ -286,8 +272,8 @@ module.exports = React.createClass({
|
|||
if (MatrixClientPeg.get().isGuest()) {
|
||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Please Register",
|
||||
description: "Guest users can't invite users. Please register."
|
||||
title: _t("Please Register"),
|
||||
description: _t("Guest users can't invite users. Please register."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -308,8 +294,8 @@ module.exports = React.createClass({
|
|||
console.error(err.stack);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to invite",
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
title: _t("Failed to invite"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
return null;
|
||||
})
|
||||
|
@ -321,8 +307,8 @@ module.exports = React.createClass({
|
|||
console.error(err.stack);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to invite user",
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
title: _t("Failed to invite user"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
return null;
|
||||
})
|
||||
|
@ -342,8 +328,8 @@ module.exports = React.createClass({
|
|||
console.error(err.stack);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to invite",
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
title: _t("Failed to invite"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
return null;
|
||||
})
|
||||
|
@ -354,7 +340,7 @@ module.exports = React.createClass({
|
|||
this.props.onFinished(true, addrTexts);
|
||||
},
|
||||
|
||||
_updateUserList: new rate_limited_func(function() {
|
||||
_updateUserList: function() {
|
||||
// Get all the users
|
||||
this._userList = MatrixClientPeg.get().getUsers();
|
||||
// Remove current user
|
||||
|
@ -362,9 +348,7 @@ module.exports = React.createClass({
|
|||
return u.userId === MatrixClientPeg.get().credentials.userId;
|
||||
});
|
||||
this._userList.splice(meIx, 1);
|
||||
|
||||
this._fuse.set(this._userList);
|
||||
}, 500),
|
||||
},
|
||||
|
||||
_isOnInviteList: function(uid) {
|
||||
for (let i = 0; i < this.state.inviteList.length; i++) {
|
||||
|
@ -401,7 +385,7 @@ module.exports = React.createClass({
|
|||
if (errorList.length > 0) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to invite the following users to the " + room.name + " room:",
|
||||
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
|
||||
description: errorList.join(", "),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
import classnames from 'classnames';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
/*
|
||||
* A dialog for confirming a redaction.
|
||||
|
@ -42,7 +43,7 @@ export default React.createClass({
|
|||
render: function() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
|
||||
const title = "Confirm Redaction";
|
||||
const title = _t("Confirm Removal");
|
||||
|
||||
const confirmButtonClass = classnames({
|
||||
'mx_Dialog_primary': true,
|
||||
|
@ -55,16 +56,16 @@ export default React.createClass({
|
|||
title={title}
|
||||
>
|
||||
<div className="mx_Dialog_content">
|
||||
Are you sure you wish to redact (delete) this event?
|
||||
Note that if you redact a room name or topic change, it could undo the change.
|
||||
{_t("Are you sure you wish to remove (delete) this event? " +
|
||||
"Note that if you delete a room name or topic change, it could undo the change.")}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button className={confirmButtonClass} onClick={this.onOk}>
|
||||
Redact
|
||||
{_t("Remove")}
|
||||
</button>
|
||||
|
||||
<button onClick={this.onCancel}>
|
||||
Cancel
|
||||
{_t("Cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import classnames from 'classnames';
|
||||
|
||||
/*
|
||||
|
@ -69,7 +70,7 @@ export default React.createClass({
|
|||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
||||
|
||||
const title = this.props.action + " this person?";
|
||||
const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action});
|
||||
const confirmButtonClass = classnames({
|
||||
'mx_Dialog_primary': true,
|
||||
'danger': this.props.danger,
|
||||
|
@ -82,7 +83,7 @@ export default React.createClass({
|
|||
<form onSubmit={this.onOk}>
|
||||
<input className="mx_ConfirmUserActionDialog_reasonField"
|
||||
ref={this._collectReasonField}
|
||||
placeholder="Reason"
|
||||
placeholder={ _t("Reason") }
|
||||
autoFocus={true}
|
||||
/>
|
||||
</form>
|
||||
|
@ -111,7 +112,7 @@ export default React.createClass({
|
|||
</button>
|
||||
|
||||
<button onClick={this.onCancel}>
|
||||
Cancel
|
||||
{ _t("Cancel") }
|
||||
</button>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
|
|
|
@ -20,6 +20,7 @@ import sdk from '../../../index';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import * as Lifecycle from '../../../Lifecycle';
|
||||
import Velocity from 'velocity-vector';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default class DeactivateAccountDialog extends React.Component {
|
||||
constructor(props, context) {
|
||||
|
@ -56,10 +57,10 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||
Lifecycle.onLoggedOut();
|
||||
this.props.onFinished(false);
|
||||
}, (err) => {
|
||||
let errStr = 'Unknown error';
|
||||
let errStr = _t('Unknown error');
|
||||
// https://matrix.org/jira/browse/SYN-744
|
||||
if (err.httpStatus == 401 || err.httpStatus == 403) {
|
||||
errStr = 'Incorrect password';
|
||||
errStr = _t('Incorrect password');
|
||||
Velocity(this._passwordField, "callout.shake", 300);
|
||||
}
|
||||
this.setState({
|
||||
|
@ -91,23 +92,23 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||
let cancelButton = null;
|
||||
if (!this.state.busy) {
|
||||
cancelButton = <button onClick={this._onCancel} autoFocus={true}>
|
||||
Cancel
|
||||
{_t("Cancel")}
|
||||
</button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_DeactivateAccountDialog">
|
||||
<div className="mx_Dialog_title danger">
|
||||
Deactivate Account
|
||||
{_t("Deactivate Account")}
|
||||
</div>
|
||||
<div className="mx_Dialog_content">
|
||||
<p>This will make your account permanently unusable. You will not be able to re-register the same user ID.</p>
|
||||
<p>{_t("This will make your account permanently unusable. You will not be able to re-register the same user ID.")}</p>
|
||||
|
||||
<p>This action is irreversible.</p>
|
||||
<p>{_t("This action is irreversible.")}</p>
|
||||
|
||||
<p>To continue, please enter your password.</p>
|
||||
<p>{_t("To continue, please enter your password.")}</p>
|
||||
|
||||
<p>Password:</p>
|
||||
<p>{_t("Password")}:</p>
|
||||
<input
|
||||
type="password"
|
||||
onChange={this._onPasswordFieldChange}
|
||||
|
|
|
@ -19,6 +19,7 @@ import React from 'react';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default function DeviceVerifyDialog(props) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
@ -27,25 +28,25 @@ export default function DeviceVerifyDialog(props) {
|
|||
const body = (
|
||||
<div>
|
||||
<p>
|
||||
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:
|
||||
{_t("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:")}
|
||||
</p>
|
||||
<div className="mx_UserSettings_cryptoSection">
|
||||
<ul>
|
||||
<li><label>Device name:</label> <span>{ props.device.getDisplayName() }</span></li>
|
||||
<li><label>Device ID:</label> <span><code>{ props.device.deviceId}</code></span></li>
|
||||
<li><label>Device key:</label> <span><code><b>{ key }</b></code></span></li>
|
||||
<li><label>{_t("Device name")}:</label> <span>{ props.device.getDisplayName() }</span></li>
|
||||
<li><label>{_t("Device ID")}:</label> <span><code>{ props.device.deviceId}</code></span></li>
|
||||
<li><label>{_t("Device key")}:</label> <span><code><b>{ key }</b></code></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
If it matches, press the verify button below.
|
||||
If it doesnt, then someone else is intercepting this device
|
||||
and you probably want to press the blacklist button instead.
|
||||
{_t("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.")}
|
||||
</p>
|
||||
<p>
|
||||
In future this verification process will be more sophisticated.
|
||||
{_t("In future this verification process will be more sophisticated.")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
@ -61,9 +62,9 @@ export default function DeviceVerifyDialog(props) {
|
|||
|
||||
return (
|
||||
<QuestionDialog
|
||||
title="Verify device"
|
||||
title={_t("Verify device")}
|
||||
description={body}
|
||||
button="I verify that the keys match"
|
||||
button={_t("I verify that the keys match")}
|
||||
onFinished={onFinished}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -27,6 +27,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'ErrorDialog',
|
||||
|
@ -43,10 +44,10 @@ export default React.createClass({
|
|||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
title: "Error",
|
||||
description: "An error has occurred.",
|
||||
button: "OK",
|
||||
focus: true,
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -60,13 +61,13 @@ export default React.createClass({
|
|||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
return (
|
||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
||||
title={this.props.title}>
|
||||
title={this.props.title || _t('Error')}>
|
||||
<div className="mx_Dialog_content">
|
||||
{this.props.description}
|
||||
{this.props.description || _t('An error has occurred.')}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button ref="button" className="mx_Dialog_primary" onClick={this.props.onFinished}>
|
||||
{this.props.button}
|
||||
{this.props.button || _t('OK')}
|
||||
</button>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
|
|
|
@ -20,6 +20,7 @@ import Matrix from 'matrix-js-sdk';
|
|||
import React from 'react';
|
||||
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
|
@ -46,12 +47,6 @@ export default React.createClass({
|
|||
title: React.PropTypes.string,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
title: "Authentication",
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
authError: null,
|
||||
|
@ -105,7 +100,7 @@ export default React.createClass({
|
|||
return (
|
||||
<BaseDialog className="mx_InteractiveAuthDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={this.state.authError ? 'Error' : this.props.title}
|
||||
title={this.state.authError ? 'Error' : (this.props.title || _t('Authentication'))}
|
||||
>
|
||||
{content}
|
||||
</BaseDialog>
|
||||
|
|
|
@ -26,6 +26,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import dis from '../../../dispatcher';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'NeedToRegisterDialog',
|
||||
|
@ -38,13 +39,6 @@ module.exports = React.createClass({
|
|||
onFinished: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
title: "Registration required",
|
||||
description: "A registered account is required for this action",
|
||||
};
|
||||
},
|
||||
|
||||
onRegisterClicked: function() {
|
||||
dis.dispatch({
|
||||
action: "start_upgrade_registration",
|
||||
|
@ -59,10 +53,10 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<BaseDialog className="mx_NeedToRegisterDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={this.props.title}
|
||||
title={this.props.title || _t('Registration required')}
|
||||
>
|
||||
<div className="mx_Dialog_content">
|
||||
{this.props.description}
|
||||
{this.props.description || _t('A registered account is required for this action')}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={true}>
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'QuestionDialog',
|
||||
|
@ -33,7 +34,6 @@ export default React.createClass({
|
|||
title: "",
|
||||
description: "",
|
||||
extraButtons: null,
|
||||
button: "OK",
|
||||
focus: true,
|
||||
hasCancelButton: true,
|
||||
};
|
||||
|
@ -64,7 +64,7 @@ export default React.createClass({
|
|||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
|
||||
{this.props.button}
|
||||
{this.props.button || _t('OK')}
|
||||
</button>
|
||||
{this.props.extraButtons}
|
||||
{cancelButton}
|
||||
|
|
|
@ -18,6 +18,7 @@ import React from 'react';
|
|||
import sdk from '../../../index';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
||||
export default React.createClass({
|
||||
|
@ -51,21 +52,21 @@ export default React.createClass({
|
|||
|
||||
return (
|
||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
||||
title='Unable to restore session'>
|
||||
title={_t('Unable to restore session')}>
|
||||
<div className="mx_Dialog_content">
|
||||
<p>We encountered an error trying to restore your previous session. If
|
||||
you continue, you will need to log in again, and encrypted chat
|
||||
history will be unreadable.</p>
|
||||
<p>{_t("We encountered an error trying to restore your previous session. If " +
|
||||
"you continue, you will need to log in again, and encrypted chat " +
|
||||
"history will be unreadable.")}</p>
|
||||
|
||||
<p>If you have previously used a more recent version of Riot, your session
|
||||
may be incompatible with this version. Close this window and return
|
||||
to the more recent version.</p>
|
||||
<p>{_t("If you have previously used a more recent version of Riot, your session " +
|
||||
"may be incompatible with this version. Close this window and return " +
|
||||
"to the more recent version.")}</p>
|
||||
|
||||
{bugreport}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button className="mx_Dialog_primary" onClick={this._continueClicked}>
|
||||
Continue anyway
|
||||
{_t("Continue anyway")}
|
||||
</button>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
|
|
|
@ -18,6 +18,8 @@ import React from 'react';
|
|||
import sdk from '../../../index';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
/**
|
||||
* Prompt the user to set a display name.
|
||||
*
|
||||
|
@ -64,11 +66,11 @@ export default React.createClass({
|
|||
return (
|
||||
<BaseDialog className="mx_SetDisplayNameDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title="Set a Display Name"
|
||||
title={_t("Set a Display Name")}
|
||||
>
|
||||
<div className="mx_Dialog_content">
|
||||
Your display name is how you'll appear to others when you speak in rooms.<br/>
|
||||
What would you like it to be?
|
||||
{_t("Your display name is how you'll appear to others when you speak in rooms. " +
|
||||
"What would you like it to be?")}
|
||||
</div>
|
||||
<form onSubmit={this.onFormSubmit}>
|
||||
<div className="mx_Dialog_content">
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'TextInputDialog',
|
||||
|
@ -36,7 +37,6 @@ export default React.createClass({
|
|||
title: "",
|
||||
value: "",
|
||||
description: "",
|
||||
button: "OK",
|
||||
focus: true,
|
||||
};
|
||||
},
|
||||
|
@ -73,7 +73,7 @@ export default React.createClass({
|
|||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.onCancel}>
|
||||
Cancel
|
||||
{ _t("Cancel") }
|
||||
</button>
|
||||
<button className="mx_Dialog_primary" onClick={this.onOk}>
|
||||
{this.props.button}
|
||||
|
|
|
@ -20,6 +20,7 @@ import dis from '../../../dispatcher';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||
import Resend from '../../../Resend';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
function DeviceListEntry(props) {
|
||||
const {userId, device} = props;
|
||||
|
@ -120,17 +121,17 @@ export default React.createClass({
|
|||
if (blacklistUnverified) {
|
||||
warning = (
|
||||
<h4>
|
||||
You are currently blacklisting unverified devices; to send
|
||||
messages to these devices you must verify them.
|
||||
{_t("You are currently blacklisting unverified devices; to send " +
|
||||
"messages to these devices you must verify them.")}
|
||||
</h4>
|
||||
);
|
||||
} else {
|
||||
warning = (
|
||||
<div>
|
||||
<p>
|
||||
We recommend you go through the verification process
|
||||
for each device to confirm they belong to their legitimate owner,
|
||||
but you can resend the message without verifying if you prefer.
|
||||
{_t("We recommend you go through the verification process " +
|
||||
"for each device to confirm they belong to their legitimate owner, " +
|
||||
"but you can resend the message without verifying if you prefer.")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
@ -149,10 +150,10 @@ export default React.createClass({
|
|||
>
|
||||
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
|
||||
<h4>
|
||||
"{this.props.room.name}" contains devices that you haven't seen before.
|
||||
{_t('"%(RoomName)s" contains devices that you haven\'t seen before.', {RoomName: this.props.room.name})}
|
||||
</h4>
|
||||
{ warning }
|
||||
Unknown devices:
|
||||
{_t("Unknown devices")}:
|
||||
|
||||
<UnknownDeviceList devices={this.props.devices} />
|
||||
</GeminiScrollbar>
|
||||
|
|
|
@ -16,12 +16,13 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
var sdk = require("../../../index");
|
||||
var Invite = require("../../../Invite");
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
var Avatar = require('../../../Avatar');
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import sdk from "../../../index";
|
||||
import Invite from "../../../Invite";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import Avatar from '../../../Avatar';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
// React PropType definition for an object describing
|
||||
// an address that can be invited to a room (which
|
||||
|
@ -142,7 +143,7 @@ export default React.createClass({
|
|||
});
|
||||
|
||||
info = (
|
||||
<div className={unknownClasses}>Unknown Address</div>
|
||||
<div className={unknownClasses}>{_t("Unknown Address")}</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import React from 'react';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'DeviceVerifyButtons',
|
||||
|
@ -82,14 +83,14 @@ export default React.createClass({
|
|||
blacklistButton = (
|
||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblacklist"
|
||||
onClick={this.onUnblacklistClick}>
|
||||
Unblacklist
|
||||
{_t("Unblacklist")}
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
blacklistButton = (
|
||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_blacklist"
|
||||
onClick={this.onBlacklistClick}>
|
||||
Blacklist
|
||||
{_t("Blacklist")}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -98,14 +99,14 @@ export default React.createClass({
|
|||
verifyButton = (
|
||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
||||
onClick={this.onUnverifyClick}>
|
||||
Unverify
|
||||
{_t("Unverify")}
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
verifyButton = (
|
||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
|
||||
onClick={this.onVerifyClick}>
|
||||
Verify...
|
||||
{_t("Verify...")}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import AccessibleButton from './AccessibleButton';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
class MenuOption extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -255,7 +256,7 @@ export default class Dropdown extends React.Component {
|
|||
});
|
||||
if (options.length === 0) {
|
||||
return [<div key="0" className="mx_Dropdown_option">
|
||||
No results
|
||||
{_t("No results")}
|
||||
</div>];
|
||||
}
|
||||
return options;
|
||||
|
|
121
src/components/views/elements/LanguageDropdown.js
Normal file
121
src/components/views/elements/LanguageDropdown.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
Copyright 2017 Marcel Radzio (MTRNord)
|
||||
Copyright 2017 Vector Creations Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import sdk from '../../../index';
|
||||
import UserSettingsStore from '../../../UserSettingsStore';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as languageHandler from '../../../languageHandler';
|
||||
|
||||
function languageMatchesSearchQuery(query, language) {
|
||||
if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
|
||||
if (language.value.toUpperCase() == query.toUpperCase()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export default class LanguageDropdown extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._onSearchChange = this._onSearchChange.bind(this);
|
||||
|
||||
this.state = {
|
||||
searchQuery: '',
|
||||
langs: null,
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
languageHandler.getAllLanguagesFromJson().then((langs) => {
|
||||
langs.sort(function(a, b){
|
||||
if(a.label < b.label) return -1;
|
||||
if(a.label > b.label) return 1;
|
||||
return 0;
|
||||
});
|
||||
this.setState({langs});
|
||||
}).catch(() => {
|
||||
this.setState({langs: ['en']});
|
||||
}).done();
|
||||
|
||||
if (!this.props.value) {
|
||||
// If no value is given, we start with the first
|
||||
// country selected, but our parent component
|
||||
// doesn't know this, therefore we do this.
|
||||
const _localSettings = UserSettingsStore.getLocalSettings();
|
||||
if (_localSettings.hasOwnProperty('language')) {
|
||||
this.props.onOptionChange(_localSettings.language);
|
||||
}else {
|
||||
const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser());
|
||||
this.props.onOptionChange(language);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onSearchChange(search) {
|
||||
this.setState({
|
||||
searchQuery: search,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.langs === null) {
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
const Dropdown = sdk.getComponent('elements.Dropdown');
|
||||
|
||||
let displayedLanguages;
|
||||
if (this.state.searchQuery) {
|
||||
displayedLanguages = this.state.langs.filter((lang) => {
|
||||
return languageMatchesSearchQuery(this.state.searchQuery, lang);
|
||||
});
|
||||
} else {
|
||||
displayedLanguages = this.state.langs;
|
||||
}
|
||||
|
||||
const options = displayedLanguages.map((language) => {
|
||||
return <div key={language.value}>
|
||||
{language.label}
|
||||
</div>;
|
||||
});
|
||||
|
||||
// default value here too, otherwise we need to handle null / undefined
|
||||
// values between mounting and the initial value propgating
|
||||
let value = null;
|
||||
const _localSettings = UserSettingsStore.getLocalSettings();
|
||||
if (_localSettings.hasOwnProperty('language')) {
|
||||
value = this.props.value || _localSettings.language;
|
||||
} else {
|
||||
const language = navigator.language || navigator.userLanguage;
|
||||
value = this.props.value || language;
|
||||
}
|
||||
|
||||
return <Dropdown className={this.props.className}
|
||||
onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange}
|
||||
searchEnabled={true} value={value}
|
||||
>
|
||||
{options}
|
||||
</Dropdown>
|
||||
}
|
||||
}
|
||||
|
||||
LanguageDropdown.propTypes = {
|
||||
className: React.PropTypes.string,
|
||||
onOptionChange: React.PropTypes.func.isRequired,
|
||||
value: React.PropTypes.string,
|
||||
};
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
import React from 'react';
|
||||
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MemberEventListSummary',
|
||||
|
@ -203,30 +204,146 @@ module.exports = React.createClass({
|
|||
* @param {boolean} plural whether there were multiple users undergoing the same
|
||||
* transition.
|
||||
* @param {number} repeats the number of times the transition was repeated in a row.
|
||||
* @returns {string} the written English equivalent of the transition.
|
||||
* @returns {string} the written Human Readable equivalent of the transition.
|
||||
*/
|
||||
_getDescriptionForTransition(t, plural, repeats) {
|
||||
const beConjugated = plural ? "were" : "was";
|
||||
const invitation = "their invitation" + (plural || (repeats > 1) ? "s" : "");
|
||||
|
||||
// The empty interpolations 'severalUsers' and 'oneUser'
|
||||
// are there only to show translators to non-English languages
|
||||
// that the verb is conjugated to plural or singular Subject.
|
||||
let res = null;
|
||||
const map = {
|
||||
"joined": "joined",
|
||||
"left": "left",
|
||||
"joined_and_left": "joined and left",
|
||||
"left_and_joined": "left and rejoined",
|
||||
"invite_reject": "rejected " + invitation,
|
||||
"invite_withdrawal": "had " + invitation + " withdrawn",
|
||||
"invited": beConjugated + " invited",
|
||||
"banned": beConjugated + " banned",
|
||||
"unbanned": beConjugated + " unbanned",
|
||||
"kicked": beConjugated + " kicked",
|
||||
"changed_name": "changed name",
|
||||
"changed_avatar": "changed avatar",
|
||||
};
|
||||
switch(t) {
|
||||
case "joined":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sjoined %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)sjoined %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sjoined", { severalUsers: "" })
|
||||
: _t("%(oneUser)sjoined", { oneUser: "" });
|
||||
}
|
||||
|
||||
if (Object.keys(map).includes(t)) {
|
||||
res = map[t] + (repeats > 1 ? " " + repeats + " times" : "" );
|
||||
break;
|
||||
case "left":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sleft %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)sleft %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sleft", { severalUsers: "" })
|
||||
: _t("%(oneUser)sleft", { oneUser: "" });
|
||||
} break;
|
||||
case "joined_and_left":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sjoined and left %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)sjoined and left %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sjoined and left", { severalUsers: "" })
|
||||
: _t("%(oneUser)sjoined and left", { oneUser: "" });
|
||||
}
|
||||
break;
|
||||
case "left_and_joined":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sleft and rejoined %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)sleft and rejoined %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sleft and rejoined", { severalUsers: "" })
|
||||
: _t("%(oneUser)sleft and rejoined", { oneUser: "" });
|
||||
} break;
|
||||
break;
|
||||
case "invite_reject":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)srejected their invitations %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)srejected their invitation %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)srejected their invitations", { severalUsers: "" })
|
||||
: _t("%(oneUser)srejected their invitation", { oneUser: "" });
|
||||
}
|
||||
break;
|
||||
case "invite_withdrawal":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)shad their invitations withdrawn %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)shad their invitation withdrawn %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)shad their invitations withdrawn", { severalUsers: "" })
|
||||
: _t("%(oneUser)shad their invitation withdrawn", { oneUser: "" });
|
||||
}
|
||||
break;
|
||||
case "invited":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("were invited %(repeats)s times", { repeats: repeats })
|
||||
: _t("was invited %(repeats)s times", { repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("were invited")
|
||||
: _t("was invited");
|
||||
}
|
||||
break;
|
||||
case "banned":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("were banned %(repeats)s times", { repeats: repeats })
|
||||
: _t("was banned %(repeats)s times", { repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("were banned")
|
||||
: _t("was banned");
|
||||
}
|
||||
break;
|
||||
case "unbanned":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("were unbanned %(repeats)s times", { repeats: repeats })
|
||||
: _t("was unbanned %(repeats)s times", { repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("were unbanned")
|
||||
: _t("was unbanned");
|
||||
}
|
||||
break;
|
||||
case "kicked":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("were kicked %(repeats)s times", { repeats: repeats })
|
||||
: _t("was kicked %(repeats)s times", { repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("were kicked")
|
||||
: _t("was kicked");
|
||||
}
|
||||
break;
|
||||
case "changed_name":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)schanged their name %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)schanged their name %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)schanged their name", { severalUsers: "" })
|
||||
: _t("%(oneUser)schanged their name", { oneUser: "" });
|
||||
}
|
||||
break;
|
||||
case "changed_avatar":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)schanged their avatar %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)schanged their avatar %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)schanged their avatar", { severalUsers: "" })
|
||||
: _t("%(oneUser)schanged their avatar", { oneUser: "" });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -254,11 +371,12 @@ module.exports = React.createClass({
|
|||
return items[0];
|
||||
} else if (remaining) {
|
||||
items = items.slice(0, itemLimit);
|
||||
const other = " other" + (remaining > 1 ? "s" : "");
|
||||
return items.join(', ') + ' and ' + remaining + other;
|
||||
return (remaining > 1)
|
||||
? _t("%(items)s and %(remaining)s others", { items: items.join(', '), remaining: remaining } )
|
||||
: _t("%(items)s and one other", { items: items.join(', ') });
|
||||
} else {
|
||||
const lastItem = items.pop();
|
||||
return items.join(', ') + ' and ' + lastItem;
|
||||
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -19,10 +19,8 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import * as Roles from '../../../Roles';
|
||||
|
||||
var LEVEL_ROLE_MAP = {};
|
||||
var reverseRoles = {};
|
||||
Object.keys(Roles.LEVEL_ROLE_MAP).forEach(function(key) {
|
||||
reverseRoles[Roles.LEVEL_ROLE_MAP[key]] = key;
|
||||
});
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'PowerSelector',
|
||||
|
@ -44,10 +42,17 @@ module.exports = React.createClass({
|
|||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
custom: (Roles.LEVEL_ROLE_MAP[this.props.value] === undefined),
|
||||
custom: (LEVEL_ROLE_MAP[this.props.value] === undefined),
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
LEVEL_ROLE_MAP = Roles.levelRoleMap();
|
||||
Object.keys(LEVEL_ROLE_MAP).forEach(function(key) {
|
||||
reverseRoles[LEVEL_ROLE_MAP[key]] = key;
|
||||
});
|
||||
},
|
||||
|
||||
onSelectChange: function(event) {
|
||||
this.setState({ custom: event.target.value === "Custom" });
|
||||
if (event.target.value !== "Custom") {
|
||||
|
@ -94,7 +99,7 @@ module.exports = React.createClass({
|
|||
selectValue = "Custom";
|
||||
}
|
||||
else {
|
||||
selectValue = Roles.LEVEL_ROLE_MAP[this.props.value] || "Custom";
|
||||
selectValue = LEVEL_ROLE_MAP[this.props.value] || "Custom";
|
||||
}
|
||||
var select;
|
||||
if (this.props.disabled) {
|
||||
|
@ -105,7 +110,7 @@ module.exports = React.createClass({
|
|||
const levels = [0, 50, 100];
|
||||
let options = levels.map((level) => {
|
||||
return {
|
||||
value: Roles.LEVEL_ROLE_MAP[level],
|
||||
value: LEVEL_ROLE_MAP[level],
|
||||
// Give a userDefault (users_default in the power event) of 0 but
|
||||
// because level !== undefined, this should never be used.
|
||||
text: Roles.textualPowerLevel(level, 0),
|
||||
|
@ -113,7 +118,7 @@ module.exports = React.createClass({
|
|||
});
|
||||
options.push({ value: "Custom", text: "Custom level" });
|
||||
options = options.map((op) => {
|
||||
return <option value={op.value}>{op.text}</option>;
|
||||
return <option value={op.value} key={op.value}>{op.text}</option>;
|
||||
});
|
||||
|
||||
select =
|
||||
|
|
|
@ -16,7 +16,8 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'UserSelector',
|
||||
|
@ -59,9 +60,9 @@ module.exports = React.createClass({
|
|||
return <li key={user_id}>{user_id} - <span onClick={function() {self.removeUser(user_id);}}>X</span></li>;
|
||||
})}
|
||||
</ul>
|
||||
<input type="text" ref="user_id_input" defaultValue="" className="mx_UserSelector_userIdInput" placeholder="ex. @bob:example.com"/>
|
||||
<input type="text" ref="user_id_input" defaultValue="" className="mx_UserSelector_userIdInput" placeholder={_t("ex. @bob:example.com")}/>
|
||||
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">
|
||||
Add User
|
||||
{_t("Add User")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -16,7 +16,9 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
var DIV_ID = 'mx_recaptcha';
|
||||
|
||||
/**
|
||||
|
@ -117,7 +119,7 @@ module.exports = React.createClass({
|
|||
|
||||
return (
|
||||
<div ref="recaptchaContainer">
|
||||
This Home Server would like to make sure you are not a robot
|
||||
{_t("This Home Server would like to make sure you are not a robot")}
|
||||
<br/>
|
||||
<div id={DIV_ID}></div>
|
||||
{error}
|
||||
|
|
|
@ -16,7 +16,8 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CasLogin',
|
||||
|
@ -28,7 +29,7 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.props.onSubmit}>Sign in with CAS</button>
|
||||
<button onClick={this.props.onSubmit}>{_t("Sign in with CAS")}</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
var React = require("react");
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CustomServerDialog',
|
||||
|
@ -23,24 +24,24 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<div className="mx_ErrorDialog">
|
||||
<div className="mx_Dialog_title">
|
||||
Custom Server Options
|
||||
{_t("Custom Server Options")}
|
||||
</div>
|
||||
<div className="mx_Dialog_content">
|
||||
<span>
|
||||
You can use the custom server options to sign into other Matrix
|
||||
servers by specifying a different Home server URL.
|
||||
{_t("You can use the custom server options to sign into other Matrix " +
|
||||
"servers by specifying a different Home server URL.")}
|
||||
<br/>
|
||||
This allows you to use this app with an existing Matrix account on
|
||||
a different home server.
|
||||
{_t("This allows you to use this app with an existing Matrix account on " +
|
||||
"a different home server.")}
|
||||
<br/>
|
||||
<br/>
|
||||
You can also set a custom identity server but this will typically prevent
|
||||
interaction with users based on email address.
|
||||
{_t("You can also set a custom identity server but this will typically prevent " +
|
||||
"interaction with users based on email address.")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.props.onFinished} autoFocus={true}>
|
||||
Dismiss
|
||||
{_t("Dismiss")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,7 @@ import url from 'url';
|
|||
import classnames from 'classnames';
|
||||
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
/* This file contains a collection of components which are used by the
|
||||
* InteractiveAuth to prompt the user to enter the information needed
|
||||
|
@ -255,8 +256,8 @@ export const EmailIdentityAuthEntry = React.createClass({
|
|||
} else {
|
||||
return (
|
||||
<div>
|
||||
<p>An email has been sent to <i>{this.props.inputs.emailAddress}</i></p>
|
||||
<p>Please check your email to continue registration.</p>
|
||||
<p>{_t("An email has been sent to")} <i>{this.props.inputs.emailAddress}</i></p>
|
||||
<p>{_t("Please check your email to continue registration.")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -348,7 +349,7 @@ export const MsisdnAuthEntry = React.createClass({
|
|||
});
|
||||
} else {
|
||||
this.setState({
|
||||
errorText: "Token incorrect",
|
||||
errorText: _t("Token incorrect"),
|
||||
});
|
||||
}
|
||||
}).catch((e) => {
|
||||
|
@ -369,8 +370,8 @@ export const MsisdnAuthEntry = React.createClass({
|
|||
});
|
||||
return (
|
||||
<div>
|
||||
<p>A text message has been sent to +<i>{this._msisdn}</i></p>
|
||||
<p>Please enter the code it contains:</p>
|
||||
<p>{_t("A text message has been sent to")} +<i>{this._msisdn}</i></p>
|
||||
<p>{_t("Please enter the code it contains:")}</p>
|
||||
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
||||
<form onSubmit={this._onFormSubmit}>
|
||||
<input type="text"
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
import React from 'react';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'LoginFooter',
|
||||
|
@ -24,7 +24,7 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
return (
|
||||
<div className="mx_Login_links">
|
||||
<a href="https://matrix.org">powered by Matrix</a>
|
||||
<a href="https://matrix.org">{_t("powered by Matrix")}</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {field_input_incorrect} from '../../../UiEffects';
|
||||
|
||||
|
||||
|
@ -121,32 +122,16 @@ class PasswordLogin extends React.Component {
|
|||
autoFocus
|
||||
/>;
|
||||
case PasswordLogin.LOGIN_FIELD_MXID:
|
||||
const mxidInputClasses = classNames({
|
||||
"mx_Login_field": true,
|
||||
"mx_Login_username": true,
|
||||
"mx_Login_field_has_prefix": true,
|
||||
"mx_Login_field_has_suffix": Boolean(this.props.hsDomain),
|
||||
});
|
||||
let suffix = null;
|
||||
if (this.props.hsDomain) {
|
||||
suffix = <div className="mx_Login_field_suffix">
|
||||
:{this.props.hsDomain}
|
||||
</div>;
|
||||
}
|
||||
return <div className="mx_Login_field_group">
|
||||
<div className="mx_Login_field_prefix">@</div>
|
||||
<input
|
||||
className={mxidInputClasses}
|
||||
key="username_input"
|
||||
type="text"
|
||||
name="username" // make it a little easier for browser's remember-password
|
||||
onChange={this.onUsernameChanged}
|
||||
placeholder="username"
|
||||
value={this.state.username}
|
||||
autoFocus
|
||||
/>
|
||||
{suffix}
|
||||
</div>;
|
||||
return <input
|
||||
className="mx_Login_field mx_Login_username"
|
||||
key="username_input"
|
||||
type="text"
|
||||
name="username" // make it a little easier for browser's remember-password
|
||||
onChange={this.onUsernameChanged}
|
||||
placeholder={_t('User name')}
|
||||
value={this.state.username}
|
||||
autoFocus
|
||||
/>;
|
||||
case PasswordLogin.LOGIN_FIELD_PHONE:
|
||||
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
|
||||
return <div className="mx_Login_phoneSection">
|
||||
|
@ -179,7 +164,7 @@ class PasswordLogin extends React.Component {
|
|||
if (this.props.onForgotPasswordClick) {
|
||||
forgotPasswordJsx = (
|
||||
<a className="mx_Login_forgot" onClick={this.props.onForgotPasswordClick} href="#">
|
||||
Forgot your password?
|
||||
{ _t('Forgot your password?') }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
@ -197,24 +182,24 @@ class PasswordLogin extends React.Component {
|
|||
<div>
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
<div className="mx_Login_type_container">
|
||||
<label className="mx_Login_type_label">I want to sign in with my</label>
|
||||
<label className="mx_Login_type_label">{ _t('I want to sign in with') }</label>
|
||||
<Dropdown
|
||||
className="mx_Login_type_dropdown"
|
||||
value={this.state.loginType}
|
||||
onOptionChange={this.onLoginTypeChange}>
|
||||
<span key={PasswordLogin.LOGIN_FIELD_MXID}>Matrix ID</span>
|
||||
<span key={PasswordLogin.LOGIN_FIELD_EMAIL}>Email Address</span>
|
||||
<span key={PasswordLogin.LOGIN_FIELD_PHONE}>Phone</span>
|
||||
<span key={PasswordLogin.LOGIN_FIELD_MXID}>{ _t('my Matrix ID') }</span>
|
||||
<span key={PasswordLogin.LOGIN_FIELD_EMAIL}>{ _t('Email address') }</span>
|
||||
<span key={PasswordLogin.LOGIN_FIELD_PHONE}>{ _t('Phone') }</span>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{loginField}
|
||||
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
|
||||
name="password"
|
||||
value={this.state.password} onChange={this.onPasswordChanged}
|
||||
placeholder="Password" />
|
||||
placeholder={ _t('Password') } />
|
||||
<br />
|
||||
{forgotPasswordJsx}
|
||||
<input className="mx_Login_submit" type="submit" value="Sign in" />
|
||||
<input className="mx_Login_submit" type="submit" value={ _t('Sign in') } />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
@ -237,7 +222,6 @@ PasswordLogin.propTypes = {
|
|||
onPhoneNumberChanged: React.PropTypes.func,
|
||||
onPasswordChanged: React.PropTypes.func,
|
||||
loginIncorrect: React.PropTypes.bool,
|
||||
hsDomain: React.PropTypes.string,
|
||||
};
|
||||
|
||||
module.exports = PasswordLogin;
|
||||
|
|
|
@ -21,6 +21,7 @@ import sdk from '../../../index';
|
|||
import Email from '../../../email';
|
||||
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
|
||||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
const FIELD_EMAIL = 'field_email';
|
||||
const FIELD_PHONE_COUNTRY = 'field_phone_country';
|
||||
|
@ -100,13 +101,13 @@ module.exports = React.createClass({
|
|||
if (this.refs.email.value == '') {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Warning",
|
||||
title: "Warning!",
|
||||
description:
|
||||
<div>
|
||||
If you don't specify an email address, you won't be able to reset your password.<br/>
|
||||
Are you sure?
|
||||
{_t("If you don't specify an email address, you won't be able to reset your password. " +
|
||||
"Are you sure?")}
|
||||
</div>,
|
||||
button: "Continue",
|
||||
button: _t("Continue"),
|
||||
onFinished: function(confirmed) {
|
||||
if (confirmed) {
|
||||
self._doSubmit();
|
||||
|
@ -304,7 +305,7 @@ module.exports = React.createClass({
|
|||
} else if (this.state.selectedTeam) {
|
||||
belowEmailSection = (
|
||||
<p className="mx_Login_support">
|
||||
You are registering with {this.state.selectedTeam.name}
|
||||
{_t("You are registering with %(SelectedTeamName)s", {SelectedTeamName: this.state.selectedTeam.name})}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
var React = require('react');
|
||||
var Modal = require('../../../Modal');
|
||||
var sdk = require('../../../index');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
/**
|
||||
* A pure UI component which displays the HS and IS to use.
|
||||
|
@ -136,14 +137,14 @@ module.exports = React.createClass({
|
|||
checked={!this.state.configVisible}
|
||||
onChange={this.onServerConfigVisibleChange.bind(this, false)} />
|
||||
<label className="mx_Login_label" htmlFor="basic">
|
||||
Default server
|
||||
{_t("Default server")}
|
||||
</label>
|
||||
|
||||
<input className="mx_Login_radio" id="advanced" name="configVisible" type="radio"
|
||||
checked={this.state.configVisible}
|
||||
onChange={this.onServerConfigVisibleChange.bind(this, true)} />
|
||||
<label className="mx_Login_label" htmlFor="advanced">
|
||||
Custom server
|
||||
{_t("Custom server")}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
@ -155,7 +156,7 @@ module.exports = React.createClass({
|
|||
<div style={serverConfigStyle}>
|
||||
<div className="mx_ServerConfig">
|
||||
<label className="mx_Login_label mx_ServerConfig_hslabel" htmlFor="hsurl">
|
||||
Home server URL
|
||||
{_t("Home server URL")}
|
||||
</label>
|
||||
<input className="mx_Login_field" id="hsurl" type="text"
|
||||
placeholder={this.props.defaultHsUrl}
|
||||
|
@ -163,7 +164,7 @@ module.exports = React.createClass({
|
|||
value={this.state.hs_url}
|
||||
onChange={this.onHomeserverChanged} />
|
||||
<label className="mx_Login_label mx_ServerConfig_islabel" htmlFor="isurl">
|
||||
Identity server URL
|
||||
{_t("Identity server URL")}
|
||||
</label>
|
||||
<input className="mx_Login_field" id="isurl" type="text"
|
||||
placeholder={this.props.defaultIsUrl}
|
||||
|
@ -171,7 +172,7 @@ module.exports = React.createClass({
|
|||
value={this.state.is_url}
|
||||
onChange={this.onIdentityServerChanged} />
|
||||
<a className="mx_ServerConfig_help" href="#" onClick={this.showHelpPopup}>
|
||||
What does this mean?
|
||||
{_t("What does this mean?")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -22,6 +22,7 @@ import MFileBody from './MFileBody';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default class MAudioBody extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -77,7 +78,7 @@ export default class MAudioBody extends React.Component {
|
|||
return (
|
||||
<span className="mx_MAudioBody" ref="body">
|
||||
<img src="img/warning.svg" width="16" height="16"/>
|
||||
Error decrypting audio
|
||||
{_t("Error decrypting audio")}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import React from 'react';
|
|||
import filesize from 'filesize';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {decryptFile} from '../../../utils/DecryptFile';
|
||||
import Tinter from '../../../Tinter';
|
||||
import request from 'browser-request';
|
||||
|
@ -202,7 +203,7 @@ module.exports = React.createClass({
|
|||
* @return {string} the human readable link text for the attachment.
|
||||
*/
|
||||
presentableTextForFile: function(content) {
|
||||
var linkText = 'Attachment';
|
||||
var linkText = _t("Attachment");
|
||||
if (content.body && content.body.length > 0) {
|
||||
// The content body should be the name of the file including a
|
||||
// file extension.
|
||||
|
@ -261,7 +262,7 @@ module.exports = React.createClass({
|
|||
const content = this.props.mxEvent.getContent();
|
||||
const text = this.presentableTextForFile(content);
|
||||
const isEncrypted = content.file !== undefined;
|
||||
const fileName = content.body && content.body.length > 0 ? content.body : "Attachment";
|
||||
const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment");
|
||||
const contentUrl = this._getContentUrl();
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
||||
|
@ -283,7 +284,8 @@ module.exports = React.createClass({
|
|||
}).catch((err) => {
|
||||
console.warn("Unable to decrypt attachment: ", err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
description: "Error decrypting attachment"
|
||||
title: _t("Error"),
|
||||
description: _t("Error decrypting attachment"),
|
||||
});
|
||||
}).finally(() => {
|
||||
decrypting = false;
|
||||
|
@ -295,7 +297,7 @@ module.exports = React.createClass({
|
|||
<span className="mx_MFileBody" ref="body">
|
||||
<div className="mx_MImageBody_download">
|
||||
<a href="javascript:void(0)" onClick={decrypt}>
|
||||
Decrypt {text}
|
||||
{ _t("Decrypt %(text)s", { text: text }) }
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
|
@ -314,7 +316,7 @@ module.exports = React.createClass({
|
|||
// We can't provide a Content-Disposition header like we would for HTTP.
|
||||
download: fileName,
|
||||
target: "_blank",
|
||||
textContent: "Download " + text,
|
||||
textContent: _t("Download %(text)s", { text: text }),
|
||||
}, "*");
|
||||
};
|
||||
|
||||
|
@ -362,7 +364,7 @@ module.exports = React.createClass({
|
|||
<div className="mx_MImageBody_download">
|
||||
<a href={contentUrl} download={fileName} target="_blank" rel="noopener">
|
||||
<img src={tintedDownloadImageURL} width="12" height="14" ref="downloadImage"/>
|
||||
Download {text}
|
||||
{ _t("Download %(text)s", { text: text }) }
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
|
@ -371,7 +373,7 @@ module.exports = React.createClass({
|
|||
} else {
|
||||
var extra = text ? (': ' + text) : '';
|
||||
return <span className="mx_MFileBody">
|
||||
Invalid file{extra}
|
||||
{ _t("Invalid file%(extra)s", { extra: extra }) }
|
||||
</span>;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -26,6 +26,7 @@ import dis from '../../../dispatcher';
|
|||
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
|
||||
import q from 'q';
|
||||
import UserSettingsStore from '../../../UserSettingsStore';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MImageBody',
|
||||
|
@ -56,7 +57,7 @@ module.exports = React.createClass({
|
|||
const ImageView = sdk.getComponent("elements.ImageView");
|
||||
const params = {
|
||||
src: httpUrl,
|
||||
name: content.body && content.body.length > 0 ? content.body : 'Attachment',
|
||||
name: content.body && content.body.length > 0 ? content.body : _t('Attachment'),
|
||||
mxEvent: this.props.mxEvent,
|
||||
};
|
||||
|
||||
|
@ -191,7 +192,7 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<span className="mx_MImageBody" ref="body">
|
||||
<img src="img/warning.svg" width="16" height="16"/>
|
||||
Error decrypting image
|
||||
{_t("Error decrypting image")}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -238,13 +239,13 @@ module.exports = React.createClass({
|
|||
} else if (content.body) {
|
||||
return (
|
||||
<span className="mx_MImageBody">
|
||||
Image '{content.body}' cannot be displayed.
|
||||
{_t("Image '%(Body)s' cannot be displayed.", {Body: content.body})}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span className="mx_MImageBody">
|
||||
This image cannot be displayed.
|
||||
{_t("This image cannot be displayed.")}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import sdk from '../../../index';
|
|||
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
|
||||
import q from 'q';
|
||||
import UserSettingsStore from '../../../UserSettingsStore';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MVideoBody',
|
||||
|
@ -128,7 +129,7 @@ module.exports = React.createClass({
|
|||
return (
|
||||
<span className="mx_MVideoBody" ref="body">
|
||||
<img src="img/warning.svg" width="16" height="16"/>
|
||||
Error decrypting video
|
||||
{_t("Error decrypting video")}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
92
src/components/views/messages/RoomAvatarEvent.js
Normal file
92
src/components/views/messages/RoomAvatarEvent.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import { ContentRepo } from 'matrix-js-sdk';
|
||||
import { _t, _tJsx } from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
import Modal from '../../../Modal';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomAvatarEvent',
|
||||
|
||||
propTypes: {
|
||||
/* the MatrixEvent to show */
|
||||
mxEvent: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
onAvatarClick: function(name) {
|
||||
var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(this.props.mxEvent.getContent().url);
|
||||
var ImageView = sdk.getComponent("elements.ImageView");
|
||||
var params = {
|
||||
src: httpUrl,
|
||||
name: name,
|
||||
};
|
||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var ev = this.props.mxEvent;
|
||||
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
var name = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {
|
||||
senderDisplayName: senderDisplayName,
|
||||
roomName: room ? room.name : '',
|
||||
});
|
||||
|
||||
if (!ev.getContent().url || ev.getContent().url.trim().length === 0) {
|
||||
return (
|
||||
<div className="mx_TextualEvent">
|
||||
{ _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName: senderDisplayName}) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var url = ContentRepo.getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
ev.getContent().url,
|
||||
14 * window.devicePixelRatio,
|
||||
14 * window.devicePixelRatio,
|
||||
'crop'
|
||||
);
|
||||
|
||||
// it sucks that _tJsx doesn't support normal _t substitutions :((
|
||||
return (
|
||||
<div className="mx_RoomAvatarEvent">
|
||||
{ _tJsx('$senderDisplayName changed the room avatar to <img/>',
|
||||
[
|
||||
/\$senderDisplayName/,
|
||||
/<img\/>/,
|
||||
],
|
||||
[
|
||||
(sub) => senderDisplayName,
|
||||
(sub) =>
|
||||
<AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
|
||||
onClick={ this.onAvatarClick.bind(this, name) }>
|
||||
<BaseAvatar width={14} height={14} url={ url }
|
||||
name={ name } />
|
||||
</AccessibleButton>,
|
||||
]
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -28,6 +28,7 @@ import ScalarAuthClient from '../../../ScalarAuthClient';
|
|||
import Modal from '../../../Modal';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import dis from '../../../dispatcher';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
|
@ -230,14 +231,14 @@ module.exports = React.createClass({
|
|||
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
let integrationsUrl = SdkConfig.get().integrations_ui_url;
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Add an Integration",
|
||||
title: _t("Add an Integration"),
|
||||
description:
|
||||
<div>
|
||||
You are about to be taken to a third-party site so you can
|
||||
authenticate your account for use with {integrationsUrl}.<br/>
|
||||
Do you wish to continue?
|
||||
{_t("You are about to be taken to a third-party site so you can " +
|
||||
"authenticate your account for use with %(integrationsUrl)s. " +
|
||||
"Do you wish to continue?", { integrationsUrl: integrationsUrl })}
|
||||
</div>,
|
||||
button: "Continue",
|
||||
button: _t("Continue"),
|
||||
onFinished: function(confirmed) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
|
|
|
@ -24,6 +24,11 @@ import sdk from '../../../index';
|
|||
module.exports = React.createClass({
|
||||
displayName: 'TextualEvent',
|
||||
|
||||
propTypes: {
|
||||
/* the MatrixEvent to show */
|
||||
mxEvent: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
var text = TextForEvent.textForEvent(this.props.mxEvent);
|
||||
|
|
|
@ -16,7 +16,8 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'UnknownBody',
|
||||
|
@ -24,7 +25,7 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
const text = this.props.mxEvent.getContent().body;
|
||||
return (
|
||||
<span className="mx_UnknownBody" title="Redacted or unknown message type">
|
||||
<span className="mx_UnknownBody" title={_t("Removed or unknown message type")}>
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -19,6 +19,7 @@ var React = require('react');
|
|||
var ObjectUtils = require("../../../ObjectUtils");
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
var sdk = require("../../../index");
|
||||
import { _t } from '../../../languageHandler';
|
||||
var Modal = require("../../../Modal");
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -154,8 +155,8 @@ module.exports = React.createClass({
|
|||
else {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Invalid alias format",
|
||||
description: "'" + alias + "' is not a valid format for an alias",
|
||||
title: _t('Invalid alias format'),
|
||||
description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -170,8 +171,8 @@ module.exports = React.createClass({
|
|||
else {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Invalid address format",
|
||||
description: "'" + alias + "' is not a valid format for an address",
|
||||
title: _t('Invalid address format'),
|
||||
description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -203,7 +204,7 @@ module.exports = React.createClass({
|
|||
if (this.props.canSetCanonicalAlias) {
|
||||
canonical_alias_section = (
|
||||
<select onChange={this.onCanonicalAliasChange} defaultValue={ this.state.canonicalAlias }>
|
||||
<option value="" key="unset">not specified</option>
|
||||
<option value="" key="unset">{ _t('not specified') }</option>
|
||||
{
|
||||
Object.keys(self.state.domainToAliases).map(function(domain, i) {
|
||||
return self.state.domainToAliases[domain].map(function(alias, j) {
|
||||
|
@ -220,7 +221,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
else {
|
||||
canonical_alias_section = (
|
||||
<b>{ this.state.canonicalAlias || "not set" }</b>
|
||||
<b>{ this.state.canonicalAlias || _t('not set') }</b>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -254,13 +255,13 @@ module.exports = React.createClass({
|
|||
<div>
|
||||
<h3>Addresses</h3>
|
||||
<div className="mx_RoomSettings_aliasLabel">
|
||||
The main address for this room is: { canonical_alias_section }
|
||||
{ _t('The main address for this room is') }: { canonical_alias_section }
|
||||
</div>
|
||||
<div className="mx_RoomSettings_aliasLabel">
|
||||
{ (this.state.domainToAliases[localDomain] &&
|
||||
this.state.domainToAliases[localDomain].length > 0)
|
||||
? "Local addresses for this room:"
|
||||
: "This room has no local addresses" }
|
||||
? _t('Local addresses for this room:')
|
||||
: _t('This room has no local addresses') }
|
||||
</div>
|
||||
<div className="mx_RoomSettings_aliasesTable">
|
||||
{ (this.state.domainToAliases[localDomain] || []).map((alias, i) => {
|
||||
|
@ -268,7 +269,7 @@ module.exports = React.createClass({
|
|||
if (this.props.canSetAliases) {
|
||||
deleteButton = (
|
||||
<img src="img/cancel-small.svg" width="14" height="14"
|
||||
alt="Delete" onClick={ self.onAliasDeleted.bind(self, localDomain, i) } />
|
||||
alt={ _t('Delete') } onClick={ self.onAliasDeleted.bind(self, localDomain, i) } />
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
@ -276,7 +277,7 @@ module.exports = React.createClass({
|
|||
<EditableText
|
||||
className="mx_RoomSettings_alias mx_RoomSettings_editable"
|
||||
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
|
||||
placeholder={ "New address (e.g. #foo:" + localDomain + ")" }
|
||||
placeholder={ _t('New address (e.g. #foo:%(localDomain)s)', { localDomain: localDomain}) }
|
||||
blurToCancel={ false }
|
||||
onValueChanged={ self.onAliasChanged.bind(self, localDomain, i) }
|
||||
editable={ self.props.canSetAliases }
|
||||
|
@ -294,7 +295,7 @@ module.exports = React.createClass({
|
|||
ref="add_alias"
|
||||
className="mx_RoomSettings_alias mx_RoomSettings_editable"
|
||||
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
|
||||
placeholder={ "New address (e.g. #foo:" + localDomain + ")" }
|
||||
placeholder={ _t('New address (e.g. #foo:%(localDomain)s)', { localDomain: localDomain}) }
|
||||
blurToCancel={ false }
|
||||
onValueChanged={ self.onAliasAdded } />
|
||||
<div className="mx_RoomSettings_addAlias mx_filterFlipColor">
|
||||
|
|
|
@ -20,6 +20,7 @@ var MatrixClientPeg = require('../../../MatrixClientPeg');
|
|||
var sdk = require("../../../index");
|
||||
var Modal = require("../../../Modal");
|
||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -120,19 +121,19 @@ module.exports = React.createClass({
|
|||
<input type="checkbox" ref="globalDisableUrlPreview"
|
||||
onChange={ this.onGlobalDisableUrlPreviewChange }
|
||||
checked={ this.state.globalDisableUrlPreview } />
|
||||
Disable URL previews by default for participants in this room
|
||||
{_t("Disable URL previews by default for participants in this room")}
|
||||
</label>;
|
||||
}
|
||||
else {
|
||||
disableRoomPreviewUrls =
|
||||
<label>
|
||||
URL previews are { this.state.globalDisableUrlPreview ? "disabled" : "enabled" } by default for participants in this room.
|
||||
{_t("URL previews are %(globalDisableUrlPreview)s by default for participants in this room.", {globalDisableUrlPreview: this.state.globalDisableUrlPreview ? _t("disabled") : _t("enabled")})}
|
||||
</label>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomSettings_toggles">
|
||||
<h3>URL Previews</h3>
|
||||
<h3>{_t("URL Previews")}</h3>
|
||||
|
||||
<label>
|
||||
You have <a href="#/settings">{ UserSettingsStore.getUrlPreviewsDisabled() ? 'disabled' : 'enabled' }</a> URL previews by default.
|
||||
|
@ -142,13 +143,13 @@ module.exports = React.createClass({
|
|||
<input type="checkbox" ref="userEnableUrlPreview"
|
||||
onChange={ this.onUserEnableUrlPreviewChange }
|
||||
checked={ this.state.userEnableUrlPreview } />
|
||||
Enable URL previews for this room (affects only you)
|
||||
{_t("Enable URL previews for this room (affects only you)")}
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" ref="userDisableUrlPreview"
|
||||
onChange={ this.onUserDisableUrlPreviewChange }
|
||||
checked={ this.state.userDisableUrlPreview } />
|
||||
Disable URL previews for this room (affects only you)
|
||||
{_t("Disable URL previews for this room (affects only you)")}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
var React = require('react');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
var sdk = require('../../../index');
|
||||
var dis = require("../../../dispatcher");
|
||||
var ObjectUtils = require('../../../ObjectUtils');
|
||||
import React from 'react';
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import sdk from '../../../index';
|
||||
import dis from "../../../dispatcher";
|
||||
import ObjectUtils from '../../../ObjectUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'AuxPanel',
|
||||
|
@ -79,7 +81,7 @@ module.exports = React.createClass({
|
|||
title="Drop File Here">
|
||||
<TintableSvg src="img/upload-big.svg" width="45" height="59"/>
|
||||
<br/>
|
||||
Drop file here to upload
|
||||
{_t("Drop file here to upload")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -89,7 +91,7 @@ module.exports = React.createClass({
|
|||
if (this.props.displayConfCallNotification) {
|
||||
var supportedText, joinText;
|
||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||
supportedText = " (unsupported)";
|
||||
supportedText = _t(" (unsupported)");
|
||||
}
|
||||
else {
|
||||
joinText = (<span>
|
||||
|
@ -101,7 +103,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
conferenceCallNotification = (
|
||||
<div className="mx_RoomView_ongoingConfCallNotification">
|
||||
Ongoing conference call{ supportedText }. { joinText }
|
||||
{_t("Ongoing conference call%(supportedText)s. %(joinText)s", {supportedText: supportedText, joinText: joinText})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,8 +16,10 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
|
||||
var React = require('react');
|
||||
var classNames = require("classnames");
|
||||
import { _t } from '../../../languageHandler';
|
||||
var Modal = require('../../../Modal');
|
||||
|
||||
var sdk = require('../../../index');
|
||||
|
@ -36,6 +38,7 @@ var eventTileTypes = {
|
|||
'm.call.answer' : 'messages.TextualEvent',
|
||||
'm.call.hangup' : 'messages.TextualEvent',
|
||||
'm.room.name' : 'messages.TextualEvent',
|
||||
'm.room.avatar' : 'messages.RoomAvatarEvent',
|
||||
'm.room.topic' : 'messages.TextualEvent',
|
||||
'm.room.third_party_invite' : 'messages.TextualEvent',
|
||||
'm.room.history_visibility' : 'messages.TextualEvent',
|
||||
|
@ -129,6 +132,9 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
* for now.
|
||||
*/
|
||||
tileShape: React.PropTypes.string,
|
||||
|
||||
// show twelve hour timestamps
|
||||
isTwelveHour: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -404,9 +410,10 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
|
||||
const isRedacted = (eventType === 'm.room.message') && this.props.isRedacted;
|
||||
|
||||
var classes = classNames({
|
||||
const classes = classNames({
|
||||
mx_EventTile: true,
|
||||
mx_EventTile_info: isInfoMessage,
|
||||
mx_EventTile_12hr: this.props.isTwelveHour,
|
||||
mx_EventTile_encrypting: this.props.eventSendStatus == 'encrypting',
|
||||
mx_EventTile_sending: isSending,
|
||||
mx_EventTile_notSent: this.props.eventSendStatus == 'not_sent',
|
||||
|
@ -464,9 +471,9 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
if (needsSenderProfile) {
|
||||
let aux = null;
|
||||
if (!this.props.tileShape) {
|
||||
if (msgtype === 'm.image') aux = "sent an image";
|
||||
else if (msgtype === 'm.video') aux = "sent a video";
|
||||
else if (msgtype === 'm.file') aux = "uploaded a file";
|
||||
if (msgtype === 'm.image') aux = _t('sent an image');
|
||||
else if (msgtype === 'm.video') aux = _t('sent a video');
|
||||
else if (msgtype === 'm.file') aux = _t('uploaded a file');
|
||||
sender = <SenderProfile onClick={ this.onSenderProfileClick } mxEvent={this.props.mxEvent} aux={aux} />;
|
||||
}
|
||||
else {
|
||||
|
@ -474,11 +481,10 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
var editButton = (
|
||||
<span className="mx_EventTile_editButton" title="Options" onClick={this.onEditClicked} />
|
||||
const editButton = (
|
||||
<span className="mx_EventTile_editButton" title={ _t("Options") } onClick={this.onEditClicked} />
|
||||
);
|
||||
|
||||
var e2e;
|
||||
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" />;
|
||||
|
@ -489,7 +495,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="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="Encrypted by 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" }}/>;
|
||||
|
@ -499,11 +505,10 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Unencrypted message" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
||||
}
|
||||
const timestamp = this.props.mxEvent.getTs() ?
|
||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
|
||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||
|
||||
if (this.props.tileShape === "notif") {
|
||||
var room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
||||
|
||||
const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_roomName">
|
||||
|
|
96
src/components/views/rooms/ForwardMessage.js
Normal file
96
src/components/views/rooms/ForwardMessage.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2017 Michael Telatynski
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import dis from '../../../dispatcher';
|
||||
import KeyCode from '../../../KeyCode';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ForwardMessage',
|
||||
|
||||
propTypes: {
|
||||
currentRoomId: React.PropTypes.string.isRequired,
|
||||
|
||||
/* the MatrixEvent to be forwarded */
|
||||
mxEvent: React.PropTypes.object.isRequired,
|
||||
|
||||
onCancelClick: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
dis.dispatch({
|
||||
action: 'ui_opacity',
|
||||
leftOpacity: 1.0,
|
||||
rightOpacity: 0.3,
|
||||
middleOpacity: 0.5,
|
||||
});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
document.addEventListener('keydown', this._onKeyDown);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.dispatch({
|
||||
action: 'ui_opacity',
|
||||
sideOpacity: 1.0,
|
||||
middleOpacity: 1.0,
|
||||
});
|
||||
dis.unregister(this.dispatcherRef);
|
||||
document.removeEventListener('keydown', this._onKeyDown);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
if (payload.action === 'view_room') {
|
||||
const event = this.props.mxEvent;
|
||||
const Client = MatrixClientPeg.get();
|
||||
Client.sendEvent(payload.room_id, event.getType(), event.getContent()).done(() => {
|
||||
dis.dispatch({action: 'message_sent'});
|
||||
}, (err) => {
|
||||
if (err.name === "UnknownDeviceError") {
|
||||
dis.dispatch({
|
||||
action: 'unknown_device_error',
|
||||
err: err,
|
||||
room: Client.getRoom(payload.room_id),
|
||||
});
|
||||
}
|
||||
dis.dispatch({action: 'message_send_failed'});
|
||||
});
|
||||
if (this.props.currentRoomId === payload.room_id) this.props.onCancelClick();
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyDown: function(ev) {
|
||||
switch (ev.keyCode) {
|
||||
case KeyCode.ESCAPE:
|
||||
this.props.onCancelClick();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_ForwardMessage">
|
||||
<h1>{_t('Please select the destination room for this message')}</h1>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -31,6 +31,7 @@ import classNames from 'classnames';
|
|||
import dis from '../../../dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import createRoom from '../../../createRoom';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import Unread from '../../../Unread';
|
||||
|
@ -219,7 +220,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
|
||||
onKick: function() {
|
||||
const membership = this.props.member.membership;
|
||||
const kickLabel = membership === "invite" ? "Disinvite" : "Kick";
|
||||
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
|
||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createDialog(ConfirmUserActionDialog, {
|
||||
member: this.props.member,
|
||||
|
@ -241,7 +242,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Kick error: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to kick",
|
||||
title: _t("Failed to kick"),
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
});
|
||||
}
|
||||
|
@ -256,7 +257,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createDialog(ConfirmUserActionDialog, {
|
||||
member: this.props.member,
|
||||
action: this.props.member.membership == 'ban' ? 'Unban' : 'Ban',
|
||||
action: this.props.member.membership == 'ban' ? _t("Unban") : _t("Ban"),
|
||||
askReason: this.props.member.membership != 'ban',
|
||||
danger: this.props.member.membership != 'ban',
|
||||
onFinished: (proceed, reason) => {
|
||||
|
@ -283,8 +284,8 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Ban error: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
description: "Failed to ban user",
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to ban user"),
|
||||
});
|
||||
}
|
||||
).finally(()=>{
|
||||
|
@ -333,8 +334,8 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
}, function(err) {
|
||||
console.error("Mute error: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
description: "Failed to mute user",
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to mute user"),
|
||||
});
|
||||
}
|
||||
).finally(()=>{
|
||||
|
@ -376,14 +377,14 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
|
||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Please Register",
|
||||
description: "This action cannot be performed by a guest user. Please register to be able to do this."
|
||||
title: _t("Please Register"),
|
||||
description: _t("This action cannot be performed by a guest user. Please register to be able to do this") + ".",
|
||||
});
|
||||
} else {
|
||||
console.error("Toggle moderator error:" + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
description: "Failed to toggle moderator status",
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to toggle moderator status"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -403,8 +404,8 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to change power level " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
description: "Failed to change power level",
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to change power level"),
|
||||
});
|
||||
}
|
||||
).finally(()=>{
|
||||
|
@ -432,13 +433,13 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
if (parseInt(myPower) === parseInt(powerLevel)) {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Warning",
|
||||
title: _t("Warning!"),
|
||||
description:
|
||||
<div>
|
||||
You will not be able to undo this change as you are promoting the user to have the same power level as yourself.<br/>
|
||||
Are you sure?
|
||||
{ _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: "Continue",
|
||||
button: _t("Continue"),
|
||||
onFinished: function(confirmed) {
|
||||
if (confirmed) {
|
||||
self._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
||||
|
@ -581,9 +582,9 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
// still loading
|
||||
devComponents = <Spinner />;
|
||||
} else if (devices === null) {
|
||||
devComponents = "Unable to load device list";
|
||||
devComponents = _t("Unable to load device list");
|
||||
} else if (devices.length === 0) {
|
||||
devComponents = "No devices with registered encryption keys";
|
||||
devComponents = _t("No devices with registered encryption keys");
|
||||
} else {
|
||||
devComponents = [];
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
|
@ -595,7 +596,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<h3>Devices</h3>
|
||||
<h3>{ _t("Devices") }</h3>
|
||||
<div className="mx_MemberInfo_devices">
|
||||
{devComponents}
|
||||
</div>
|
||||
|
@ -644,11 +645,11 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
<div className="mx_RoomTile_avatar">
|
||||
<img src="img/create-big.svg" width="26" height="26" />
|
||||
</div>
|
||||
<div className={labelClasses}><i>Start new chat</i></div>
|
||||
<div className={labelClasses}><i>{ _t("Start a chat") }</i></div>
|
||||
</AccessibleButton>;
|
||||
|
||||
startChat = <div>
|
||||
<h3>Direct chats</h3>
|
||||
<h3>{ _t("Direct chats") }</h3>
|
||||
{tiles}
|
||||
{startNewChat}
|
||||
</div>;
|
||||
|
@ -661,7 +662,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
|
||||
if (this.state.can.kick) {
|
||||
const membership = this.props.member.membership;
|
||||
const kickLabel = membership === "invite" ? "Disinvite" : "Kick";
|
||||
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
|
||||
kickButton = (
|
||||
<AccessibleButton className="mx_MemberInfo_field"
|
||||
onClick={this.onKick}>
|
||||
|
@ -670,9 +671,9 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
);
|
||||
}
|
||||
if (this.state.can.ban) {
|
||||
let label = 'Ban';
|
||||
let label = _t("Ban");
|
||||
if (this.props.member.membership == 'ban') {
|
||||
label = 'Unban';
|
||||
label = _t("Unban");
|
||||
}
|
||||
banButton = (
|
||||
<AccessibleButton className="mx_MemberInfo_field"
|
||||
|
@ -682,7 +683,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
);
|
||||
}
|
||||
if (this.state.can.mute) {
|
||||
const muteLabel = this.state.muted ? "Unmute" : "Mute";
|
||||
const muteLabel = this.state.muted ? _t("Unmute") : _t("Mute");
|
||||
muteButton = (
|
||||
<AccessibleButton className="mx_MemberInfo_field"
|
||||
onClick={this.onMuteToggle}>
|
||||
|
@ -691,7 +692,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
);
|
||||
}
|
||||
if (this.state.can.toggleMod) {
|
||||
var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator";
|
||||
var giveOpLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator");
|
||||
giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
|
||||
{giveOpLabel}
|
||||
</AccessibleButton>;
|
||||
|
@ -742,7 +743,7 @@ module.exports = WithMatrixClient(React.createClass({
|
|||
{ this.props.member.userId }
|
||||
</div>
|
||||
<div className="mx_MemberInfo_profileField">
|
||||
Level: <b><PowerSelector controlled={true} value={ parseInt(this.props.member.powerLevel) } disabled={ !this.state.can.modifyLevel } onChange={ this.onPowerChange }/></b>
|
||||
{ _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 }
|
||||
|
|
|
@ -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';
|
||||
var classNames = require('classnames');
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var q = require('q');
|
||||
|
@ -27,12 +28,6 @@ var CallHandler = require("../../../CallHandler");
|
|||
var Invite = require("../../../Invite");
|
||||
|
||||
var INITIAL_LOAD_NUM_MEMBERS = 30;
|
||||
var SHARE_HISTORY_WARNING =
|
||||
<span>
|
||||
Newly invited users will see the history of this room. <br/>
|
||||
If you'd prefer invited users not to see messages that were sent before they joined, <br/>
|
||||
turn off, 'Share message history with new users' in the settings for this room.
|
||||
</span>;
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MemberList',
|
||||
|
@ -207,7 +202,9 @@ module.exports = React.createClass({
|
|||
// For now we'll pretend this is any entity. It should probably be a separate tile.
|
||||
var EntityTile = sdk.getComponent("rooms.EntityTile");
|
||||
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||
var text = "and " + overflowCount + " other" + (overflowCount > 1 ? "s" : "") + "...";
|
||||
var text = (overflowCount > 1)
|
||||
? _t("and %(overflowCount)s others...", { overflowCount: overflowCount })
|
||||
: _t("and one other...");
|
||||
return (
|
||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
||||
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
|
||||
|
@ -352,7 +349,7 @@ module.exports = React.createClass({
|
|||
if (invitedMemberTiles.length > 0) {
|
||||
invitedSection = (
|
||||
<div className="mx_MemberList_invited">
|
||||
<h2>Invited</h2>
|
||||
<h2>{ _t("Invited") }</h2>
|
||||
<div className="mx_MemberList_wrapper">
|
||||
{invitedMemberTiles}
|
||||
</div>
|
||||
|
@ -363,8 +360,8 @@ module.exports = React.createClass({
|
|||
var inputBox = (
|
||||
<form autoComplete="off">
|
||||
<input className="mx_MemberList_query" id="mx_MemberList_query" type="text"
|
||||
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
|
||||
placeholder="Filter room members" />
|
||||
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
|
||||
placeholder={ _t('Filter room members') } />
|
||||
</form>
|
||||
);
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
var React = require('react');
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
var CallHandler = require('../../../CallHandler');
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
var Modal = require('../../../Modal');
|
||||
|
@ -93,8 +93,8 @@ export default class MessageComposer extends React.Component {
|
|||
if (MatrixClientPeg.get().isGuest()) {
|
||||
let NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Please Register",
|
||||
description: "Guest users can't upload files. Please register to upload.",
|
||||
title: _t('Please Register'),
|
||||
description: _t('Guest users can\'t upload files. Please register to upload') + '.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -118,10 +118,10 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Upload Files",
|
||||
title: _t('Upload Files'),
|
||||
description: (
|
||||
<div>
|
||||
<p>Are you sure you want upload the following files?</p>
|
||||
<p>{ _t('Are you sure you want to upload the following files?') }</p>
|
||||
<ul style={{listStyle: 'none', textAlign: 'left'}}>
|
||||
{fileList}
|
||||
</ul>
|
||||
|
@ -240,11 +240,11 @@ export default class MessageComposer extends React.Component {
|
|||
if (roomIsEncrypted) {
|
||||
// FIXME: show a /!\ if there are untrusted devices in the room...
|
||||
e2eImg = 'img/e2e-verified.svg';
|
||||
e2eTitle = 'Encrypted room';
|
||||
e2eTitle = _t('Encrypted room');
|
||||
e2eClass = 'mx_MessageComposer_e2eIcon';
|
||||
} else {
|
||||
e2eImg = 'img/e2e-unencrypted.svg';
|
||||
e2eTitle = 'Unencrypted room';
|
||||
e2eTitle = _t('Unencrypted room');
|
||||
e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor';
|
||||
}
|
||||
|
||||
|
@ -257,16 +257,16 @@ export default class MessageComposer extends React.Component {
|
|||
if (this.props.callState && this.props.callState !== 'ended') {
|
||||
hangupButton =
|
||||
<div key="controls_hangup" className="mx_MessageComposer_hangup" onClick={this.onHangupClick}>
|
||||
<img src="img/hangup.svg" alt="Hangup" title="Hangup" width="25" height="26"/>
|
||||
<img src="img/hangup.svg" alt={ _t('Hangup') } title={ _t('Hangup') } width="25" height="26"/>
|
||||
</div>;
|
||||
}
|
||||
else {
|
||||
callButton =
|
||||
<div key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title="Voice call">
|
||||
<div key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title={ _t('Voice call') }>
|
||||
<TintableSvg src="img/icon-call.svg" width="35" height="35"/>
|
||||
</div>;
|
||||
videoCallButton =
|
||||
<div key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title="Video call">
|
||||
<div key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title={ _t('Video call') }>
|
||||
<TintableSvg src="img/icons-video.svg" width="35" height="35"/>
|
||||
</div>;
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ export default class MessageComposer extends React.Component {
|
|||
// complex because of conference calls.
|
||||
var uploadButton = (
|
||||
<div key="controls_upload" className="mx_MessageComposer_upload"
|
||||
onClick={this.onUploadClick} title="Upload file">
|
||||
onClick={this.onUploadClick} title={ _t('Upload file') }>
|
||||
<TintableSvg src="img/icons-upload.svg" width="35" height="35"/>
|
||||
<input ref="uploadInput" type="file"
|
||||
style={uploadInputStyle}
|
||||
|
@ -300,7 +300,7 @@ export default class MessageComposer extends React.Component {
|
|||
);
|
||||
|
||||
const placeholderText = roomIsEncrypted ?
|
||||
"Send an encrypted message…" : "Send a message (unencrypted)…";
|
||||
_t('Send an encrypted message') + '…' : _t('Send a message (unencrypted)') + '…';
|
||||
|
||||
controls.push(
|
||||
<MessageComposerInput
|
||||
|
@ -325,7 +325,7 @@ export default class MessageComposer extends React.Component {
|
|||
} else {
|
||||
controls.push(
|
||||
<div key="controls_error" className="mx_MessageComposer_noperm_error">
|
||||
You do not have permission to post to this room
|
||||
{ _t('You do not have permission to post to this room') }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -354,7 +354,7 @@ export default class MessageComposer extends React.Component {
|
|||
mx_filterFlipColor: true,
|
||||
});
|
||||
return <img className={className}
|
||||
title={name}
|
||||
title={ _t(name) }
|
||||
onMouseDown={disabled ? null : onFormatButtonClicked}
|
||||
key={name}
|
||||
src={`img/button-text-${name}${suffix}.svg`}
|
||||
|
@ -374,11 +374,11 @@ export default class MessageComposer extends React.Component {
|
|||
<div className="mx_MessageComposer_formatbar" style={this.state.showFormatting ? {} : {display: 'none'}}>
|
||||
{formatButtons}
|
||||
<div style={{flex: 1}}></div>
|
||||
<img title={`Turn Markdown ${this.state.inputState.isRichtextEnabled ? 'on' : 'off'}`}
|
||||
<img title={ this.state.inputState.isRichtextEnabled ? _t("Turn Markdown on") : _t("Turn Markdown off") }
|
||||
onMouseDown={this.onToggleMarkdownClicked}
|
||||
className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
|
||||
src={`img/button-md-${!this.state.inputState.isRichtextEnabled}.png`} />
|
||||
<img title="Hide Text Formatting Toolbar"
|
||||
<img title={ _t("Hide Text Formatting Toolbar") }
|
||||
onClick={this.onToggleFormattingClicked}
|
||||
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
|
||||
src="img/icon-text-cancel.svg" />
|
||||
|
|
|
@ -30,6 +30,7 @@ import type {MatrixClient} from 'matrix-js-sdk/lib/matrix';
|
|||
import SlashCommands from '../../../SlashCommands';
|
||||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
import dis from '../../../dispatcher';
|
||||
import KeyCode from '../../../KeyCode';
|
||||
|
@ -504,8 +505,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
console.error("Command failure: %s", err);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Server error",
|
||||
description: ((err && err.message) ? err.message : "Server unavailable, overloaded, or something else went wrong."),
|
||||
title: _t("Server error"),
|
||||
description: ((err && err.message) ? err.message : _t("Server unavailable, overloaded, or something else went wrong.")),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -513,8 +514,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
console.error(cmd.error);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Command error",
|
||||
description: cmd.error
|
||||
title: _t("Command error"),
|
||||
description: cmd.error,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
|
@ -719,7 +720,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
<div className={className}>
|
||||
<img className="mx_MessageComposer_input_markdownIndicator mx_filterFlipColor"
|
||||
onMouseDown={this.onMarkdownToggleClicked}
|
||||
title={`Markdown is ${this.state.isRichtextEnabled ? 'disabled' : 'enabled'}`}
|
||||
title={ this.state.isRichtextEnabled ? _t("Markdown is disabled") : _t("Markdown is enabled")}
|
||||
src={`img/button-md-${!this.state.isRichtextEnabled}.png`} />
|
||||
<Editor ref="editor"
|
||||
placeholder={this.props.placeholder}
|
||||
|
|
|
@ -20,6 +20,7 @@ var SlashCommands = require("../../../SlashCommands");
|
|||
var Modal = require("../../../Modal");
|
||||
var MemberEntry = require("../../../TabCompleteEntries").MemberEntry;
|
||||
var sdk = require('../../../index');
|
||||
import { _t } from '../../../languageHandler';
|
||||
import UserSettingsStore from "../../../UserSettingsStore";
|
||||
|
||||
var dis = require("../../../dispatcher");
|
||||
|
@ -294,8 +295,8 @@ export default React.createClass({
|
|||
else {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unknown command",
|
||||
description: "Usage: /markdown on|off"
|
||||
title: _t("Unknown command"),
|
||||
description: _t("Usage") + ": /markdown on|off",
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
@ -314,8 +315,8 @@ export default React.createClass({
|
|||
console.error("Command failure: %s", err);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Server error",
|
||||
description: ((err && err.message) ? err.message : "Server unavailable, overloaded, or something else went wrong."),
|
||||
title: _t("Server error"),
|
||||
description: ((err && err.message) ? err.message : _t("Server unavailable, overloaded, or something else went wrong.")),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -323,8 +324,8 @@ export default React.createClass({
|
|||
console.error(cmd.error);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Command error",
|
||||
description: cmd.error
|
||||
title: _t("Command error"),
|
||||
description: cmd.error,
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue