Merge branch 'develop' into travis/openid-widget

pull/21833/head
Travis Ralston 2019-03-15 20:51:02 -06:00
commit 44d8f9ee9f
58 changed files with 7993 additions and 358 deletions

60
.buildkite/pipeline.yaml Normal file
View File

@ -0,0 +1,60 @@
steps:
- label: ":eslint: Lint"
command:
- "yarn install"
- "yarn lintwithexclusions"
plugins:
- docker#v3.0.1:
image: "node:10"
# - label: ":chains: End-to-End Tests"
# command:
# # TODO: Remove hacky chmod for BuildKite
# - "chmod +x ./scripts/ci/*.sh"
# - "chmod +x ./scripts/*"
# - "sudo apt-get install build-essential python2.7-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev"
# - "./scripts/ci/install-deps.sh"
# - "./scripts/ci/end-to-end-tests.sh"
# plugins:
# - docker#v3.0.1:
# image: "node:10"
- label: ":karma: Tests"
command:
# Install chrome
- "wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -"
- "sh -c 'echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list'"
- "apt-get update"
- "apt-get install -y google-chrome-stable"
# Run tests
# TODO: Remove hacky chmod for BuildKite
- "chmod +x ./scripts/ci/*.sh"
- "chmod +x ./scripts/*"
- "./scripts/ci/install-deps.sh"
- "./scripts/ci/unit-tests.sh"
env:
CHROME_BIN: "/usr/bin/google-chrome-stable"
plugins:
- docker#v3.0.1:
image: "node:10"
propagate-environment: true
- label: "🔧 Riot Tests"
command:
# Install chrome
- "wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -"
- "sh -c 'echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list'"
- "apt-get update"
- "apt-get install -y google-chrome-stable"
# Run tests
# TODO: Remove hacky chmod for BuildKite
- "chmod +x ./scripts/ci/*.sh"
- "chmod +x ./scripts/*"
- "./scripts/ci/install-deps.sh"
- "./scripts/ci/riot-unit-tests.sh"
env:
CHROME_BIN: "/usr/bin/google-chrome-stable"
plugins:
- docker#v3.0.1:
image: "node:10"
propagate-environment: true

8
.gitignore vendored
View File

@ -1,10 +1,11 @@
npm-debug.log
/.npmrc
/*.log
package-lock.json
/node_modules
/lib
# version file and tarball created by 'npm pack'
# version file and tarball created by `npm pack` / `yarn pack`
/git-revision.txt
/matrix-react-sdk-*.tgz
@ -15,6 +16,3 @@ npm-debug.log
/src/component-index.js
.DS_Store
# https://github.com/vector-im/riot-web/issues/7083
package-lock.json

View File

@ -1,35 +0,0 @@
# we need trusty for the chrome addon
dist: trusty
# we don't need sudo, so can run in a container, which makes startup much
# quicker.
#
# unfortunately we do temporarily require sudo as a workaround for
# https://github.com/travis-ci/travis-ci/issues/8836
sudo: required
language: node_js
node_js:
- node # Latest stable version of nodejs.
addons:
chrome: stable
install:
- ./scripts/travis/install-deps.sh
matrix:
include:
- name: Linting Checks
script:
# run the linter, but exclude any files known to have errors or warnings.
- npm run lintwithexclusions
# - name: End-to-End Tests
# if: branch = develop
# install:
# - sudo apt-get install build-essential python2.7-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev
# script:
# - ./scripts/travis/end-to-end-tests.sh
- name: Unit Tests
script:
- ./scripts/travis/unit-tests.sh
- name: Riot-web Unit Tests
script:
- ./scripts/travis/riot-unit-tests.sh

View File

@ -66,7 +66,7 @@ practices that anyone working with the SDK needs to be be aware of and uphold:
component is a view or a structure, and then a broad functional grouping
(e.g. 'rooms' here)
* After creating a new component you must run `npm run reskindex` to regenerate
* After creating a new component you must run `yarn reskindex` to regenerate
the `component-index.js` for the SDK (used in future for skinning)
* The view's CSS file MUST have the same name (e.g. view/rooms/MessageTile.css).
@ -131,26 +131,35 @@ for now.
Development
===========
Ensure you have the latest stable Node JS runtime installed (v8.x is the best choice). Then check out
the code and pull in dependencies:
Ensure you have the latest LTS version of Node.js installed.
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install
guide](https://yarnpkg.com/docs/install/) if you do not have it already.
`matrix-react-sdk` depends on `matrix-js-sdk`. To make use of changes in the
latter and to ensure tests run against the develop branch of `matrix-js-sdk`,
you should set up `matrix-js-sdk`:
```bash
git clone https://github.com/matrix-org/matrix-react-sdk.git
git clone https://github.com/matrix-org/matrix-js-sdk
cd matrix-js-sdk
git checkout develop
yarn link
yarn install
```
Then check out `matrix-react-sdk` and pull in dependencies:
```bash
git clone https://github.com/matrix-org/matrix-react-sdk
cd matrix-react-sdk
git checkout develop
npm install
yarn link matrix-js-sdk
yarn install
```
`matrix-react-sdk` depends on `matrix-js-sdk`. To make use of changes in the
latter and to ensure tests run against the develop branch of `matrix-js-sdk`,
you should run the following which will sync changes from the JS sdk here.
```bash
npm link ../matrix-js-sdk
```
Command assumes a checked out and installed `matrix-js-sdk` folder in parent
folder.
See the [help for `yarn link`](https://yarnpkg.com/docs/cli/link) for more
details about this.
Running tests
=============
@ -158,5 +167,5 @@ Running tests
Ensure you've followed the above development instructions and then:
```bash
npm run test
yarn test
```

View File

@ -8,25 +8,29 @@ nvm use 10
set -x
# install the other dependencies
npm install
scripts/fetchdep.sh matrix-org matrix-js-sdk
rm -r node_modules/matrix-js-sdk || true
ln -s ../matrix-js-sdk node_modules/matrix-js-sdk
(cd matrix-js-sdk && npm install)
pushd matrix-js-sdk
yarn link
yarn install
popd
yarn link matrix-js-sdk
# install the other dependencies
yarn install
# run the mocha tests
npm run test -- --no-colors
yarn test --no-colors
# run eslint
npm run lintall -- -f checkstyle -o eslint.xml || true
yarn lintall -f checkstyle -o eslint.xml || true
# re-run the linter, excluding any files known to have errors or warnings.
npm run lintwithexclusions
yarn lintwithexclusions
# delete the old tarball, if it exists
rm -f matrix-react-sdk-*.tgz
# build our tarball
npm pack
yarn pack

View File

@ -8,7 +8,7 @@ var fs = require('fs');
* to build everything; however it's the easiest way to load our dependencies
* from node_modules.
*
* If you run karma in multi-run mode (with `npm run test-multi`), it will watch
* If you run karma in multi-run mode (with `yarn test-multi`), it will watch
* the tests for changes, and webpack will rebuild using a cache. This is much quicker
* than a clean rebuild.
*/
@ -35,7 +35,7 @@ function fileExists(name) {
}
}
// try find the gemini-scrollbar css in an npm-version-agnostic way
// try find the gemini-scrollbar css in an version-agnostic way
var gsCss = 'node_modules/gemini-scrollbar/gemini-scrollbar.css';
if (!fileExists(gsCss)) {
gsCss = 'node_modules/react-gemini-scrollbar/'+gsCss;
@ -135,9 +135,10 @@ module.exports = function (config) {
],
customLaunchers: {
'ChromeHeadless': {
'VectorChromeHeadless': {
base: 'Chrome',
flags: [
'--no-sandbox',
// See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
'--headless',
'--disable-gpu',
@ -198,7 +199,7 @@ module.exports = function (config) {
alias: {
// alias any requires to the react module to the one in our
// path, otherwise we tend to get the react source included
// twice when using npm link.
// twice when using `npm link` / `yarn link`.
react: path.resolve('./node_modules/react'),
'matrix-react-sdk': path.resolve('test/skinned-sdk.js'),

View File

@ -40,18 +40,18 @@
"rethemendex": "res/css/rethemendex.sh",
"i18n": "matrix-gen-i18n",
"prunei18n": "matrix-prune-i18n",
"build": "npm run reskindex && npm run start:init",
"build": "yarn reskindex && yarn start:init",
"build:watch": "babel src -w --skip-initial-build -d lib --source-maps --copy-files",
"emoji-data-strip": "node scripts/emoji-data-strip.js",
"start": "npm run start:init && npm run start:all",
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"npm run build:watch\" \"npm run reskindex:watch\"",
"start": "yarn start:init && yarn start:all",
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn build:watch\" \"yarn reskindex:watch\"",
"start:init": "babel src -d lib --source-maps --copy-files",
"lint": "eslint src/",
"lintall": "eslint src/ test/",
"lintwithexclusions": "eslint --max-warnings 0 --ignore-path .eslintignore.errorfiles src test",
"clean": "rimraf lib",
"prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt",
"test": "karma start --single-run=true --browsers ChromeHeadless",
"prepare": "yarn clean && yarn build && git rev-parse HEAD > git-revision.txt",
"test": "karma start --single-run=true --browsers VectorChromeHeadless",
"test-multi": "karma start"
},
"dependencies": {
@ -131,7 +131,7 @@
"flow-parser": "^0.57.3",
"jest-mock": "^23.2.0",
"karma": "^4.0.1",
"karma-chrome-launcher": "^0.2.3",
"karma-chrome-launcher": "^2.2.0",
"karma-cli": "^1.0.1",
"karma-junit-reporter": "^2.0.0",
"karma-logcapture-reporter": "0.0.1",

View File

@ -95,6 +95,7 @@
@import "./views/elements/_SyntaxHighlight.scss";
@import "./views/elements/_ToggleSwitch.scss";
@import "./views/elements/_ToolTipButton.scss";
@import "./views/elements/_Tooltip.scss";
@import "./views/globals/_MatrixToolbar.scss";
@import "./views/groups/_GroupPublicityToggle.scss";
@import "./views/groups/_GroupRoomList.scss";
@ -137,7 +138,6 @@
@import "./views/rooms/_RoomPreviewBar.scss";
@import "./views/rooms/_RoomRecoveryReminder.scss";
@import "./views/rooms/_RoomTile.scss";
@import "./views/rooms/_RoomTooltip.scss";
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
@import "./views/rooms/_SearchBar.scss";
@import "./views/rooms/_SearchableEntityList.scss";

View File

@ -16,6 +16,16 @@ limitations under the License.
.mx_BaseAvatar {
position: relative;
// In at least Firefox, the case of relative positioned inline elements
// (such as mx_BaseAvatar) with absolute positioned children (such as
// mx_BaseAvatar_initial) is a dark corner full of spider webs. It will give
// different results during full reflow of the page vs. incremental reflow
// of small portions. While that's surely a browser bug, we can avoid it by
// using `inline-block` instead of the default `inline`.
// https://github.com/vector-im/riot-web/issues/5594
// https://bugzilla.mozilla.org/show_bug.cgi?id=1535053
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139
display: inline-block;
}
.mx_BaseAvatar_initial {

View File

@ -141,6 +141,39 @@ limitations under the License.
color: $greyed-fg-color;
}
.mx_Field_valid {
&.mx_Field,
&.mx_Field:focus-within {
border-color: $input-valid-border-color;
}
&.mx_Field label,
&.mx_Field:focus-within label {
color: $input-valid-border-color;
}
}
.mx_Field_invalid {
&.mx_Field,
&.mx_Field:focus-within {
border-color: $input-invalid-border-color;
}
&.mx_Field label,
&.mx_Field:focus-within label {
color: $input-invalid-border-color;
}
}
.mx_Field_tooltip {
margin-top: -12px;
margin-left: 4px;
}
.mx_Field_tooltip.mx_Field_valid {
animation: mx_fadeout 1s 2s forwards;
}
// Customise other components when placed inside a Field
.mx_Field .mx_Dropdown_input {

View File

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,41 +15,55 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_RoomTooltip_chevron {
position: absolute;
left: -8px;
top: 4px;
width: 0;
height: 0;
border-top: 8px solid transparent;
border-right: 8px solid $menu-bg-color;
border-bottom: 8px solid transparent;
@keyframes mx_fadein {
from { opacity: 0; }
to { opacity: 1; }
}
.mx_RoomTooltip_chevron:after {
content:'';
@keyframes mx_fadeout {
from { opacity: 1; }
to { opacity: 0; }
}
.mx_Tooltip_chevron {
position: absolute;
left: -7px;
top: 10px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-right: 7px solid $primary-bg-color;
border-right: 7px solid $menu-border-color;
border-bottom: 7px solid transparent;
position:absolute;
top: -7px;
}
.mx_Tooltip_chevron:after {
content:'';
width: 0;
height: 0;
border-top: 6px solid transparent;
border-right: 6px solid $menu-bg-color;
border-bottom: 6px solid transparent;
position: absolute;
top: -6px;
left: 1px;
}
.mx_RoomTooltip {
.mx_Tooltip {
display: none;
animation: mx_fadein 0.2s;
position: fixed;
border-radius: 5px;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.21);
background-color: $primary-bg-color;
border: 1px solid $menu-border-color;
border-radius: 4px;
box-shadow: 4px 4px 12px 0 rgba(118, 131, 156, 0.6);
background-color: $menu-bg-color;
z-index: 2000;
padding: 5px;
padding: 10px;
pointer-events: none;
line-height: 14px;
font-size: 13px;
font-size: 12px;
font-weight: 600;
color: $primary-fg-color;
max-width: 600px;
max-width: 200px;
word-break: break-word;
margin-right: 50px;
}

View File

@ -22,10 +22,20 @@ limitations under the License.
}
.mx_EntityTile:hover {
background-image: url('$(res)/img/member_chevron.png');
background-position: center right 10px;
background-repeat: no-repeat;
padding-right: 30px;
position: relative; // to keep the chevron aligned
}
.mx_EntityTile:hover::before {
content: "";
position: absolute;
top: calc(50% - 8px); // center
right: 10px;
mask: url('$(res)/img/member_chevron.png');
mask-repeat: no-repeat;
width: 16px;
height: 16px;
background-color: $rightpanel-button-color;
}
.mx_EntityTile .mx_PresenceLabel {
@ -92,19 +102,19 @@ limitations under the License.
.mx_EntityTile_unavailable .mx_EntityTile_avatar,
.mx_EntityTile_unavailable .mx_EntityTile_name,
.mx_EntityTile_offline_beenactive .mx_EntityTile_avatar,
.mx_EntityTile_offline_beenactive .mx_EntityTile_name,
.mx_EntityTile_offline_beenactive .mx_EntityTile_name
{
opacity: 0.5;
}
.mx_EntityTile_offline_neveractive .mx_EntityTile_avatar,
.mx_EntityTile_offline_neveractive .mx_EntityTile_name,
.mx_EntityTile_offline_neveractive .mx_EntityTile_name
{
opacity: 0.25;
}
.mx_EntityTile_unknown .mx_EntityTile_avatar,
.mx_EntityTile_unknown .mx_EntityTile_name,
.mx_EntityTile_unknown .mx_EntityTile_name
{
opacity: 0.25;
}

View File

@ -53,6 +53,7 @@ $input-lighter-bg-color: #f2f5f8;
$input-lighter-fg-color: $input-darker-fg-color;
$input-focused-border-color: #238cf5;
$input-valid-border-color: $accent-color;
$input-invalid-border-color: $warning-color;
$field-focused-label-bg-color: $bg-color;

View File

@ -79,6 +79,7 @@ $input-lighter-bg-color: #f2f5f8;
$input-lighter-fg-color: $input-darker-fg-color;
$input-focused-border-color: #238cf5;
$input-valid-border-color: $accent-color;
$input-invalid-border-color: $warning-color;
$field-focused-label-bg-color: #ffffff;
@ -95,7 +96,7 @@ $input-fg-color: rgba(74, 74, 74, 0.9);
$scrollbar-thumb-color: rgba(0, 0, 0, 0.2);
$scrollbar-track-color: transparent;
// context menus
$menu-border-color: #ebedf8;
$menu-border-color: #e7e7e7;
$menu-bg-color: #fff;
$menu-box-shadow-color: rgba(118, 131, 156, 0.6);
$menu-selected-color: #f5f8fa;

25
scripts/ci/build.sh Normal file
View File

@ -0,0 +1,25 @@
#!/bin/bash
#
# script which is run by the CI build (after `yarn test`).
#
# clones riot-web develop and runs the tests against our version of react-sdk.
set -ev
RIOT_WEB_DIR=riot-web
REACT_SDK_DIR=`pwd`
yarn link
scripts/fetchdep.sh vector-im riot-web
pushd "$RIOT_WEB_DIR"
yarn link matrix-js-sdk
yarn link matrix-react-sdk
yarn install
yarn build
popd

View File

@ -1,6 +1,6 @@
#!/bin/bash
#
# script which is run by the travis build (after `npm run test`).
# script which is run by the CI build (after `yarn test`).
#
# clones riot-web develop and runs the tests against our version of react-sdk.
@ -9,7 +9,7 @@ set -ev
RIOT_WEB_DIR=riot-web
REACT_SDK_DIR=`pwd`
scripts/travis/build.sh
scripts/ci/build.sh
# run end to end tests
scripts/fetchdep.sh matrix-org matrix-react-end-to-end-tests master
pushd matrix-react-end-to-end-tests

View File

@ -0,0 +1,13 @@
#!/bin/bash
set -ex
scripts/fetchdep.sh matrix-org matrix-js-sdk
pushd matrix-js-sdk
yarn link
yarn install
popd
yarn link matrix-js-sdk
yarn install

View File

@ -1,6 +1,6 @@
#!/bin/bash
#
# script which is run by the travis build (after `npm run test`).
# script which is run by the CI build (after `yarn test`).
#
# clones riot-web develop and runs the tests against our version of react-sdk.
@ -8,7 +8,7 @@ set -ev
RIOT_WEB_DIR=riot-web
scripts/travis/build.sh
scripts/ci/build.sh
pushd "$RIOT_WEB_DIR"
npm run test
yarn test
popd

10
scripts/ci/unit-tests.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/bash
#
# script which is run by the CI build (after `yarn test`).
#
# clones riot-web develop and runs the tests against our version of react-sdk.
set -ev
scripts/ci/build.sh
yarn test

View File

@ -17,10 +17,11 @@ clone() {
fi
}
# Try the PR author's branch in case it exists on the deps as well.
clone $TRAVIS_PULL_REQUEST_BRANCH
clone $BUILDKITE_BRANCH
# Try the target branch of the push or PR.
clone $TRAVIS_BRANCH
clone $BUILDKITE_PULL_REQUEST_BASE_BRANCH
# Try the current branch from Jenkins.
clone `"echo $GIT_BRANCH" | sed -e 's/^origin\///'`
# Use the default branch as the last resort.

View File

@ -1,27 +0,0 @@
#!/bin/bash
#
# script which is run by the travis build (after `npm run test`).
#
# clones riot-web develop and runs the tests against our version of react-sdk.
set -ev
RIOT_WEB_DIR=riot-web
REACT_SDK_DIR=`pwd`
scripts/fetchdep.sh vector-im riot-web
pushd "$RIOT_WEB_DIR"
mkdir node_modules
npm install
# use the version of js-sdk we just used in the react-sdk tests
rm -r node_modules/matrix-js-sdk
ln -s "$REACT_SDK_DIR/node_modules/matrix-js-sdk" node_modules/matrix-js-sdk
# ... and, of course, the version of react-sdk we just built
rm -r node_modules/matrix-react-sdk
ln -s "$REACT_SDK_DIR" node_modules/matrix-react-sdk
npm run build
popd

View File

@ -1,11 +0,0 @@
#!/bin/sh
set -ex
npm install
scripts/fetchdep.sh matrix-org matrix-js-sdk
rm -r node_modules/matrix-js-sdk || true
ln -s ../matrix-js-sdk node_modules/matrix-js-sdk
cd matrix-js-sdk
npm install
cd ..

View File

@ -1,10 +0,0 @@
#!/bin/bash
#
# script which is run by the travis build (after `npm run test`).
#
# clones riot-web develop and runs the tests against our version of react-sdk.
set -ev
scripts/travis/build.sh
CHROME_BIN='/usr/bin/google-chrome-stable' npm run test

View File

@ -51,6 +51,11 @@ export default class IntegrationManager {
*/
static async open(integName, integId, onFinished) {
await IntegrationManager._init();
if (global.mxIntegrationManager.client) {
await global.mxIntegrationManager.client.connect();
} else {
return;
}
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
if (global.mxIntegrationManager.error ||
!(global.mxIntegrationManager.client && global.mxIntegrationManager.client.hasCredentials())) {

View File

@ -21,6 +21,9 @@ const request = require('browser-request');
const SdkConfig = require('./SdkConfig');
const MatrixClientPeg = require('./MatrixClientPeg');
// The version of the integration manager API we're intending to work with
const imApiVersion = "1.1";
class ScalarAuthClient {
constructor() {
this.scalarToken = null;
@ -66,7 +69,7 @@ class ScalarAuthClient {
request({
method: "GET",
uri: url,
qs: {scalar_token: token},
qs: {scalar_token: token, v: imApiVersion},
json: true,
}, (err, response, body) => {
if (err) {
@ -100,6 +103,7 @@ class ScalarAuthClient {
request({
method: 'POST',
uri: scalar_rest_url+'/register',
qs: {v: imApiVersion},
body: openid_token_object,
json: true,
}, (err, response, body) => {

View File

@ -145,8 +145,8 @@ module.exports = React.createClass({
// Get the label/tooltip to show
getLabel: function(label, show) {
if (show) {
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
const Tooltip = sdk.getComponent("elements.Tooltip");
return <Tooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
}
},

View File

@ -48,19 +48,13 @@ module.exports = React.createClass({
</p>
<p>
{ _t(
'Please install <chromeLink>Chrome</chromeLink> or <firefoxLink>Firefox</firefoxLink> ' +
'for the best experience.',
'Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, ' +
'or <safariLink>Safari</safariLink> for the best experience.',
{},
{
'chromeLink': (sub) => <a href="https://www.google.com/chrome">{sub}</a>,
'firefoxLink': (sub) => <a href="https://getfirefox.com">{sub}</a>,
},
)}
{ _t('<safariLink>Safari</safariLink> and <operaLink>Opera</operaLink> work too.',
{},
{
'safariLink': (sub) => <a href="http://apple.com/safari">{sub}</a>,
'operaLink': (sub) => <a href="http://opera.com">{sub}</a>,
'firefoxLink': (sub) => <a href="https://firefox.com">{sub}</a>,
'safariLink': (sub) => <a href="https://apple.com/safari">{sub}</a>,
},
)}
</p>

View File

@ -84,7 +84,7 @@ class CustomRoomTagTile extends React.Component {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
const Tooltip = sdk.getComponent('rooms.Tooltip');
const tag = this.props.tag;
const avatarHeight = 40;
@ -103,7 +103,7 @@ class CustomRoomTagTile extends React.Component {
}
const tip = (this.state.hover ?
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
<Tooltip className="mx_TagTile_tooltip" label={name} /> :
<div />);
return (
<AccessibleButton className={className} onClick={this.onClick}>

View File

@ -786,7 +786,7 @@ module.exports = React.createClass({
return;
}
this._updateRoomMembers();
this._updateRoomMembers(member);
},
onMyMembership: function(room, membership, oldMembership) {
@ -798,16 +798,24 @@ module.exports = React.createClass({
// rate limited because a power level change will emit an event for every
// member in the room.
_updateRoomMembers: new rate_limited_func(function() {
_updateRoomMembers: new rate_limited_func(function(dueToMember) {
// a member state changed in this room
// refresh the conf call notification state
this._updateConfCallNotification();
this._updateDMState();
this._checkIfAlone(this.state.room);
let memberCountInfluence = 0;
if (dueToMember && dueToMember.membership === "invite" && this.state.room.getInvitedMemberCount() === 0) {
// A member got invited, but the room hasn't detected that change yet. Influence the member
// count by 1 to counteract this.
memberCountInfluence = 1;
}
this._checkIfAlone(this.state.room, memberCountInfluence);
this._updateE2EStatus(this.state.room);
}, 500),
_checkIfAlone: function(room) {
_checkIfAlone: function(room, countInfluence) {
let warnedAboutLonelyRoom = false;
if (localStorage) {
warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId);
@ -817,7 +825,8 @@ module.exports = React.createClass({
return;
}
const joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount();
let joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount();
if (countInfluence) joinedOrInvitedMemberCount += countInfluence;
this.setState({isAlone: joinedOrInvitedMemberCount === 1});
},

View File

@ -20,7 +20,6 @@ import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import Modal from "../../../Modal";
import MatrixClientPeg from "../../../MatrixClientPeg";
import SdkConfig from "../../../SdkConfig";
import PasswordReset from "../../../PasswordReset";
@ -124,20 +123,13 @@ module.exports = React.createClass({
description:
<div>
{ _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.',
"Changing your password will reset any end-to-end encryption keys " +
"on all of your devices, making encrypted chat history unreadable. Set up " +
"Key Backup or export your room keys from another device before resetting your " +
"password.",
) }
</div>,
button: _t('Continue'),
extraButtons: [
<button key="export_keys" className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}>
{ _t('Export E2E room keys') }
</button>,
],
onFinished: (confirmed) => {
if (confirmed) {
this.submitPasswordReset(
@ -150,15 +142,6 @@ module.exports = React.createClass({
}
},
_onExportE2eKeysClicked: function() {
Modal.createTrackedDialogAsync('Export E2E Keys', 'Forgot Password',
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
{
matrixClient: MatrixClientPeg.get(),
},
);
},
onInputChanged: function(stateKey, ev) {
this.setState({
[stateKey]: ev.target.value,

View File

@ -70,11 +70,6 @@ class PasswordLogin extends React.Component {
this.isLoginEmpty = this.isLoginEmpty.bind(this);
}
componentWillMount() {
this._passwordField = null;
this._loginField = null;
}
onForgotPasswordClick(ev) {
ev.preventDefault();
ev.stopPropagation();
@ -180,7 +175,6 @@ class PasswordLogin extends React.Component {
return <Field
className={classNames(classes)}
id="mx_PasswordLogin_email"
ref={(e) => { this._loginField = e; }}
name="username" // make it a little easier for browser's remember-password
key="email_input"
type="text"
@ -196,7 +190,6 @@ class PasswordLogin extends React.Component {
return <Field
className={classNames(classes)}
id="mx_PasswordLogin_username"
ref={(e) => { this._loginField = e; }}
name="username" // make it a little easier for browser's remember-password
key="username_input"
type="text"
@ -223,7 +216,6 @@ class PasswordLogin extends React.Component {
return <Field
className={classNames(classes)}
id="mx_PasswordLogin_phoneNumber"
ref={(e) => { this._loginField = e; }}
name="phoneNumber"
key="phone_input"
type="text"
@ -344,7 +336,6 @@ class PasswordLogin extends React.Component {
<Field
className={pwFieldClass}
id="mx_PasswordLogin_password"
ref={(e) => { this._passwordField = e; }}
type="password"
name="password"
label={_t('Password')}

View File

@ -70,6 +70,11 @@ module.exports = React.createClass({
fieldErrors: {},
// The ISO2 country code selected in the phone number entry
phoneCountry: this.props.defaultPhoneCountry,
username: "",
email: "",
phoneNumber: "",
password: "",
passwordConfirm: "",
};
},
@ -89,7 +94,7 @@ module.exports = React.createClass({
const self = this;
if (this.allFieldsValid()) {
if (this.refs.email.value == '') {
if (this.state.email == '') {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('If you don\'t specify an email address...', '', QuestionDialog, {
title: _t("Warning!"),
@ -112,13 +117,13 @@ module.exports = React.createClass({
},
_doSubmit: function(ev) {
const email = this.refs.email.value.trim();
const email = this.state.email.trim();
const promise = this.props.onRegisterClick({
username: this.refs.username.value.trim(),
password: this.refs.password.value.trim(),
username: this.state.username.trim(),
password: this.state.password.trim(),
email: email,
phoneCountry: this.state.phoneCountry,
phoneNumber: this.refs.phoneNumber ? this.refs.phoneNumber.value.trim() : '',
phoneNumber: this.state.phoneNumber,
});
if (promise) {
@ -143,13 +148,13 @@ module.exports = React.createClass({
},
validateField: function(fieldID, eventType) {
const pwd1 = this.refs.password.value.trim();
const pwd2 = this.refs.passwordConfirm.value.trim();
const pwd1 = this.state.password.trim();
const pwd2 = this.state.passwordConfirm.trim();
const allowEmpty = eventType === "blur";
switch (fieldID) {
case FIELD_EMAIL: {
const email = this.refs.email.value;
const email = this.state.email;
const emailValid = email === '' || Email.looksValid(email);
if (this._authStepIsRequired('m.login.email.identity') && (!emailValid || email === '')) {
this.markFieldValid(fieldID, false, "RegistrationForm.ERR_MISSING_EMAIL");
@ -157,7 +162,7 @@ module.exports = React.createClass({
break;
}
case FIELD_PHONE_NUMBER: {
const phoneNumber = this.refs.phoneNumber ? this.refs.phoneNumber.value : '';
const phoneNumber = this.state.phoneNumber;
const phoneNumberValid = phoneNumber === '' || phoneNumberLooksValid(phoneNumber);
if (this._authStepIsRequired('m.login.msisdn') && (!phoneNumberValid || phoneNumber === '')) {
this.markFieldValid(fieldID, false, "RegistrationForm.ERR_MISSING_PHONE_NUMBER");
@ -165,7 +170,7 @@ module.exports = React.createClass({
break;
}
case FIELD_USERNAME: {
const username = this.refs.username.value.trim();
const username = this.state.username;
if (allowEmpty && username === '') {
this.markFieldValid(fieldID, true);
} else if (!SAFE_LOCALPART_REGEX.test(username)) {
@ -230,21 +235,6 @@ module.exports = React.createClass({
this.props.onValidationChange(fieldErrors);
},
fieldElementById(fieldID) {
switch (fieldID) {
case FIELD_EMAIL:
return this.refs.email;
case FIELD_PHONE_NUMBER:
return this.refs.phoneNumber;
case FIELD_USERNAME:
return this.refs.username;
case FIELD_PASSWORD:
return this.refs.password;
case FIELD_PASSWORD_CONFIRM:
return this.refs.passwordConfirm;
}
},
_classForField: function(fieldID, ...baseClasses) {
let cls = baseClasses.join(' ');
if (this.state.fieldErrors[fieldID]) {
@ -258,14 +248,32 @@ module.exports = React.createClass({
this.validateField(FIELD_EMAIL, ev.type);
},
onEmailChange(ev) {
this.setState({
email: ev.target.value,
});
},
onPasswordBlur(ev) {
this.validateField(FIELD_PASSWORD, ev.type);
},
onPasswordChange(ev) {
this.setState({
password: ev.target.value,
});
},
onPasswordConfirmBlur(ev) {
this.validateField(FIELD_PASSWORD_CONFIRM, ev.type);
},
onPasswordConfirmChange(ev) {
this.setState({
passwordConfirm: ev.target.value,
});
},
onPhoneCountryChange(newVal) {
this.setState({
phoneCountry: newVal.iso2,
@ -277,10 +285,22 @@ module.exports = React.createClass({
this.validateField(FIELD_PHONE_NUMBER, ev.type);
},
onPhoneNumberChange(ev) {
this.setState({
phoneNumber: ev.target.value,
});
},
onUsernameBlur(ev) {
this.validateField(FIELD_USERNAME, ev.type);
},
onUsernameChange(ev) {
this.setState({
username: ev.target.value,
});
},
/**
* A step is required if all flows include that step.
*
@ -343,12 +363,12 @@ module.exports = React.createClass({
<Field
className={this._classForField(FIELD_EMAIL)}
id="mx_RegistrationForm_email"
ref="email"
type="text"
label={emailPlaceholder}
defaultValue={this.props.defaultEmail}
value={this.state.email}
onBlur={this.onEmailBlur}
onChange={this.onEmailChange}
/>
);
}
@ -370,13 +390,13 @@ module.exports = React.createClass({
phoneSection = <Field
className={this._classForField(FIELD_PHONE_NUMBER)}
id="mx_RegistrationForm_phoneNumber"
ref="phoneNumber"
type="text"
label={phoneLabel}
defaultValue={this.props.defaultPhoneNumber}
value={this.state.phoneNumber}
prefix={phoneCountry}
onBlur={this.onPhoneNumberBlur}
onChange={this.onPhoneNumberChange}
/>;
}
@ -395,32 +415,35 @@ module.exports = React.createClass({
<Field
className={this._classForField(FIELD_USERNAME)}
id="mx_RegistrationForm_username"
ref="username"
type="text"
autoFocus={true}
label={_t("Username")}
defaultValue={this.props.defaultUsername}
value={this.state.username}
onBlur={this.onUsernameBlur}
onChange={this.onUsernameChange}
/>
</div>
<div className="mx_AuthBody_fieldRow">
<Field
className={this._classForField(FIELD_PASSWORD)}
id="mx_RegistrationForm_password"
ref="password"
type="password"
label={_t("Password")}
defaultValue={this.props.defaultPassword}
value={this.state.password}
onBlur={this.onPasswordBlur}
onChange={this.onPasswordChange}
/>
<Field
className={this._classForField(FIELD_PASSWORD_CONFIRM)}
id="mx_RegistrationForm_passwordConfirm"
ref="passwordConfirm"
type="password"
label={_t("Confirm")}
defaultValue={this.props.defaultPassword}
value={this.state.passwordConfirm}
onBlur={this.onPasswordConfirmBlur}
onChange={this.onPasswordConfirmChange}
/>
</div>
<div className="mx_AuthBody_fieldRow">

View File

@ -69,8 +69,8 @@ export default React.createClass({
let tooltip;
if (this.state.showTooltip) {
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
tooltip = <RoomTooltip className="mx_RoleButton_tooltip" label={this.props.label} />;
const Tooltip = sdk.getComponent("elements.Tooltip");
tooltip = <Tooltip className="mx_RoleButton_tooltip" label={this.props.label} />;
}
const icon = this.props.iconPath ?

View File

@ -241,11 +241,18 @@ export default class AppTile extends React.Component {
this.props.onEditClick();
} else {
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
const src = this._scalarClient.getScalarInterfaceUrlForRoom(
this.props.room, 'type_' + this.props.type, this.props.id);
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
src: src,
}, "mx_IntegrationsManager");
this._scalarClient.connect().done(() => {
const src = this._scalarClient.getScalarInterfaceUrlForRoom(
this.props.room, 'type_' + this.props.type, this.props.id);
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
src: src,
}, "mx_IntegrationsManager");
}, (err) => {
this.setState({
error: err.message,
});
console.error('Error ensuring a valid scalar_token exists', err);
});
}
}

View File

@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import sdk from '../../../index';
export default class Field extends React.PureComponent {
static propTypes = {
@ -31,25 +32,43 @@ export default class Field extends React.PureComponent {
label: PropTypes.string,
// The field's placeholder string. Defaults to the label.
placeholder: PropTypes.string,
// The field's value.
// This is a controlled component, so the value is required.
value: PropTypes.string.isRequired,
// Optional component to include inside the field before the input.
prefix: PropTypes.node,
// The callback called whenever the contents of the field
// changes. Returns an object with `valid` boolean field
// and a `feedback` react component field to provide feedback
// to the user.
onValidate: PropTypes.func,
// All other props pass through to the <input>.
};
get value() {
if (!this.refs.fieldInput) return null;
return this.refs.fieldInput.value;
constructor() {
super();
this.state = {
valid: undefined,
feedback: undefined,
};
}
set value(newValue) {
if (!this.refs.fieldInput) {
throw new Error("No field input reference");
onChange = (ev) => {
if (this.props.onValidate) {
const result = this.props.onValidate(ev.target.value);
this.setState({
valid: result.valid,
feedback: result.feedback,
});
}
this.refs.fieldInput.value = newValue;
}
// Parent component may have supplied its own `onChange` as well
if (this.props.onChange) {
this.props.onChange(ev);
}
};
render() {
const { element, prefix, children, ...inputProps } = this.props;
const { element, prefix, onValidate, children, ...inputProps } = this.props;
const inputElement = element || "input";
@ -58,6 +77,8 @@ export default class Field extends React.PureComponent {
inputProps.ref = "fieldInput";
inputProps.placeholder = inputProps.placeholder || inputProps.label;
inputProps.onChange = this.onChange;
const fieldInput = React.createElement(inputElement, inputProps, children);
let prefixContainer = null;
@ -65,17 +86,37 @@ export default class Field extends React.PureComponent {
prefixContainer = <span className="mx_Field_prefix">{prefix}</span>;
}
const classes = classNames("mx_Field", `mx_Field_${inputElement}`, {
let validClass;
if (onValidate) {
validClass = classNames({
mx_Field_valid: this.state.valid === true,
mx_Field_invalid: this.state.valid === false,
});
}
const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, {
// If we have a prefix element, leave the label always at the top left and
// don't animate it, as it looks a bit clunky and would add complexity to do
// properly.
mx_Field_labelAlwaysTopLeft: prefix,
[validClass]: true,
});
return <div className={classes}>
// handle displaying feedback on validity
const Tooltip = sdk.getComponent("elements.Tooltip");
let feedback;
if (this.state.feedback) {
feedback = <Tooltip
tooltipClassName="mx_Field_tooltip"
label={this.state.feedback}
/>;
}
return <div className={fieldClasses}>
{prefixContainer}
{fieldInput}
<label htmlFor={this.props.id}>{this.props.label}</label>
{feedback}
</div>;
}
}

View File

@ -45,7 +45,7 @@ export default class ManageIntegsButton extends React.Component {
this.scalarClient.connect().done(() => {
this.forceUpdate();
}, (err) => {
this.setState({ scalarError: err});
this.setState({scalarError: err});
console.error('Error whilst initialising scalarClient for ManageIntegsButton', err);
});
}
@ -61,11 +61,16 @@ export default class ManageIntegsButton extends React.Component {
return;
}
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
Modal.createDialog(IntegrationsManager, {
src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room) :
null,
}, "mx_IntegrationsManager");
this.scalarClient.connect().done(() => {
Modal.createDialog(IntegrationsManager, {
src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room) :
null,
}, "mx_IntegrationsManager");
}, (err) => {
this.setState({scalarError: err});
console.error('Error ensuring a valid scalar_token exists', err);
});
}
render() {

View File

@ -156,7 +156,7 @@ export default React.createClass({
render: function() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
const Tooltip = sdk.getComponent('elements.Tooltip');
const profile = this.state.profile || {};
const name = profile.name || this.props.tag;
const avatarHeight = 40;
@ -181,7 +181,7 @@ export default React.createClass({
}
const tip = this.state.hover ?
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
<Tooltip className="mx_TagTile_tooltip" label={name} /> :
<div />;
const contextButton = this.state.hover || this.state.menuDisplayed ?
<div className="mx_TagTile_context_button" onClick={this.onContextButtonClick}>

View File

@ -39,8 +39,8 @@ module.exports = React.createClass({
},
render: function() {
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
const tip = this.state.hover ? <RoomTooltip
const Tooltip = sdk.getComponent("elements.Tooltip");
const tip = this.state.hover ? <Tooltip
className="mx_ToolTipButton_container"
tooltipClassName="mx_ToolTipButton_helpText"
label={this.props.helpText}

View File

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -23,22 +24,21 @@ import classNames from 'classnames';
const MIN_TOOLTIP_HEIGHT = 25;
module.exports = React.createClass({
displayName: 'RoomTooltip',
displayName: 'Tooltip',
propTypes: {
// Class applied to the element used to position the tooltip
className: React.PropTypes.string.isRequired,
className: React.PropTypes.string,
// Class applied to the tooltip itself
tooltipClassName: React.PropTypes.string,
// The tooltip is derived from either the room name or a label
room: React.PropTypes.object,
// the react element to put into the tooltip
label: React.PropTypes.node,
},
// Create a wrapper for the tooltip outside the parent and attach it to the body element
componentDidMount: function() {
this.tooltipContainer = document.createElement("div");
this.tooltipContainer.className = "mx_RoomTileTooltip_wrapper";
this.tooltipContainer.className = "mx_Tooltip_wrapper";
document.body.appendChild(this.tooltipContainer);
window.addEventListener('scroll', this._renderTooltip, true);
@ -85,11 +85,11 @@ module.exports = React.createClass({
style = this._updatePosition(style);
style.display = "block";
const tooltipClasses = classNames("mx_RoomTooltip", this.props.tooltipClassName);
const tooltipClasses = classNames("mx_Tooltip", this.props.tooltipClassName);
const tooltip = (
<div className={tooltipClasses} style={style}>
<div className="mx_RoomTooltip_chevron" />
<div className="mx_Tooltip_chevron" />
{ this.props.label }
</div>
);

View File

@ -143,8 +143,8 @@ export default React.createClass({
let tooltip;
if (this.props.collapsed && this.state.hover) {
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" label={groupName} dir="auto" />;
const Tooltip = sdk.getComponent("elements.Tooltip");
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={groupName} dir="auto" />;
}
const classes = classNames('mx_RoomTile mx_RoomTile_highlight', {

View File

@ -44,9 +44,9 @@ export default class MStickerBody extends MImageBody {
if (!content || !content.body || !content.info || !content.info.w) return null;
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
const Tooltip = sdk.getComponent('elements.Tooltip');
return <div style={{left: content.info.w + 'px'}} className="mx_MStickerBody_tooltip">
<RoomTooltip label={content.body} />
<Tooltip label={content.body} />
</div>;
}

View File

@ -144,12 +144,16 @@ module.exports = React.createClass({
_launchManageIntegrations: function() {
const IntegrationsManager = sdk.getComponent('views.settings.IntegrationsManager');
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this._scalarClient.connect().done(() => {
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room, 'add_integ') :
null;
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
src: src,
}, 'mx_IntegrationsManager');
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
src: src,
}, 'mx_IntegrationsManager');
}, (err) => {
console.error('Error ensuring a valid scalar_token exists', err);
});
},
onClickAddWidget: function(e) {

View File

@ -32,7 +32,6 @@ import {CancelButton} from './SimpleRoomHeader';
import SettingsStore from "../../../settings/SettingsStore";
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
import E2EIcon from './E2EIcon';
import * as cryptodevices from '../../../cryptodevices';
module.exports = React.createClass({
displayName: 'RoomHeader',
@ -146,12 +145,6 @@ module.exports = React.createClass({
return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0);
},
_onShowDevicesClick: function() {
if (this.props.e2eStatus === "warning") {
cryptodevices.showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
}
},
render: function() {
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
const EmojiText = sdk.getComponent('elements.EmojiText');
@ -162,7 +155,7 @@ module.exports = React.createClass({
let pinnedEventsButton = null;
const e2eIcon = this.props.e2eStatus ?
<E2EIcon status={this.props.e2eStatus} onClick={this._onShowDevicesClick} /> :
<E2EIcon status={this.props.e2eStatus} /> :
undefined;
if (this.props.onCancelClick) {

View File

@ -364,8 +364,8 @@ module.exports = React.createClass({
label = <EmojiText element="div" title={name} className={nameClasses} dir="auto">{ name }</EmojiText>;
}
} else if (this.state.hover) {
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;
const Tooltip = sdk.getComponent("elements.Tooltip");
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;
}
//var incomingCallBox;

View File

@ -310,18 +310,22 @@ export default class Stickerpicker extends React.Component {
*/
_launchManageIntegrations() {
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.connect().done(() => {
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
this.scalarClient.getScalarInterfaceUrlForRoom(
this.props.room,
'type_' + widgetType,
this.state.widgetId,
) :
null;
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
src: src,
}, "mx_IntegrationsManager");
this.setState({showStickers: false});
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
src: src,
}, "mx_IntegrationsManager");
this.setState({showStickers: false});
}, (err) => {
this.setState({imError: err});
console.error('Error ensuring a valid scalar_token exists', err);
});
}
render() {

View File

@ -32,6 +32,7 @@ import sessionStore from '../../../stores/SessionStore';
module.exports = React.createClass({
displayName: 'ChangePassword',
propTypes: {
onFinished: PropTypes.func,
onError: PropTypes.func,
@ -73,6 +74,9 @@ module.exports = React.createClass({
return {
phase: this.Phases.Edit,
cachedPassword: null,
oldPassword: "",
newPassword: "",
newPasswordConfirm: "",
};
},
@ -165,6 +169,9 @@ module.exports = React.createClass({
}).finally(() => {
this.setState({
phase: this.Phases.Edit,
oldPassword: "",
newPassword: "",
newPasswordConfirm: "",
});
}).done();
},
@ -192,11 +199,29 @@ module.exports = React.createClass({
);
},
onChangeOldPassword(ev) {
this.setState({
oldPassword: ev.target.value,
});
},
onChangeNewPassword(ev) {
this.setState({
newPassword: ev.target.value,
});
},
onChangeNewPasswordConfirm(ev) {
this.setState({
newPasswordConfirm: ev.target.value,
});
},
onClickChange: function(ev) {
ev.preventDefault();
const oldPassword = this.state.cachedPassword || this.refs.old_input.value;
const newPassword = this.refs.new_input.value;
const confirmPassword = this.refs.confirm_input.value;
const oldPassword = this.state.cachedPassword || this.state.oldPassword;
const newPassword = this.state.newPassword;
const confirmPassword = this.state.newPasswordConfirm;
const err = this.props.onCheckPassword(
oldPassword, newPassword, confirmPassword,
);
@ -217,7 +242,12 @@ module.exports = React.createClass({
if (!this.state.cachedPassword) {
currentPassword = (
<div className={rowClassName}>
<Field id="passwordold" type="password" ref="old_input" label={_t('Current password')} />
<Field id="mx_ChangePassword_oldPassword"
type="password"
label={_t('Current password')}
value={this.state.oldPassword}
onChange={this.onChangeOldPassword}
/>
</div>
);
}
@ -230,11 +260,21 @@ module.exports = React.createClass({
<form className={this.props.className} onSubmit={this.onClickChange}>
{ currentPassword }
<div className={rowClassName}>
<Field id="password1" type="password" ref="new_input" label={passwordLabel}
autoFocus={this.props.autoFocusNewPasswordInput} />
<Field id="mx_ChangePassword_newPassword"
type="password"
label={passwordLabel}
value={this.state.newPassword}
autoFocus={this.props.autoFocusNewPasswordInput}
onChange={this.onChangeNewPassword}
/>
</div>
<div className={rowClassName}>
<Field id="password2" type="password" ref="confirm_input" label={_t("Confirm password")} />
<Field id="mx_ChangePassword_newPasswordConfirm"
type="password"
label={_t("Confirm password")}
value={this.state.newPasswordConfirm}
onChange={this.onChangeNewPasswordConfirm}
/>
</div>
<AccessibleButton className={buttonClassName} kind={this.props.buttonKind} onClick={this.onClickChange}>
{ _t('Change Password') }

View File

@ -119,6 +119,7 @@ export default class EmailAddresses extends React.Component {
verifying: false,
addTask: null,
continueDisabled: false,
newEmailAddress: "",
};
}
@ -134,14 +135,20 @@ export default class EmailAddresses extends React.Component {
this.setState({emails: this.state.emails.filter((e) => e !== address)});
};
_onChangeNewEmailAddress = (e) => {
this.setState({
newEmailAddress: e.target.value,
});
};
_onAddClick = (e) => {
e.stopPropagation();
e.preventDefault();
if (!this.refs.newEmailAddress) return;
if (!this.state.newEmailAddress) return;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const email = this.refs.newEmailAddress.value;
const email = this.state.newEmailAddress;
// TODO: Inline field validation
if (!Email.looksValid(email)) {
@ -173,14 +180,14 @@ export default class EmailAddresses extends React.Component {
this.setState({continueDisabled: true});
this.state.addTask.checkEmailLinkClicked().then(() => {
const email = this.refs.newEmailAddress.value;
const email = this.state.newEmailAddress;
this.setState({
emails: [...this.state.emails, {address: email, medium: "email"}],
addTask: null,
continueDisabled: false,
verifying: false,
newEmailAddress: "",
});
this.refs.newEmailAddress.value = "";
}).catch((err) => {
this.setState({continueDisabled: false});
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
@ -221,8 +228,14 @@ export default class EmailAddresses extends React.Component {
{existingEmailElements}
<form onSubmit={this._onAddClick} autoComplete={false}
noValidate={true} className="mx_EmailAddresses_new">
<Field id="newEmailAddress" ref="newEmailAddress" label={_t("Email Address")}
type="text" autoComplete="off" disabled={this.state.verifying} />
<Field id="mx_EmailAddressses_newEmailAddress"
type="text"
label={_t("Email Address")}
autoComplete="off"
disabled={this.state.verifying}
value={this.state.newEmailAddress}
onChange={this._onChangeNewEmailAddress}
/>
{addButton}
</form>
</div>

View File

@ -117,6 +117,8 @@ export default class PhoneNumbers extends React.Component {
addTask: null,
continueDisabled: false,
phoneCountry: "",
newPhoneNumber: "",
newPhoneNumberCode: "",
};
}
@ -132,14 +134,26 @@ export default class PhoneNumbers extends React.Component {
this.setState({msisdns: this.state.msisdns.filter((e) => e !== address)});
};
_onChangeNewPhoneNumber = (e) => {
this.setState({
newPhoneNumber: e.target.value,
});
};
_onChangeNewPhoneNumberCode = (e) => {
this.setState({
newPhoneNumberCode: e.target.value,
});
};
_onAddClick = (e) => {
e.stopPropagation();
e.preventDefault();
if (!this.refs.newPhoneNumber) return;
if (!this.state.newPhoneNumber) return;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const phoneNumber = this.refs.newPhoneNumber.value;
const phoneNumber = this.state.newPhoneNumber;
const phoneCountry = this.state.phoneCountry;
const task = new AddThreepid();
@ -162,7 +176,7 @@ export default class PhoneNumbers extends React.Component {
e.preventDefault();
this.setState({continueDisabled: true});
const token = this.refs.newPhoneNumberCode.value;
const token = this.state.newPhoneNumberCode;
this.state.addTask.haveMsisdnToken(token).then(() => {
this.setState({
msisdns: [...this.state.msisdns, {address: this.state.verifyMsisdn, medium: "msisdn"}],
@ -171,8 +185,9 @@ export default class PhoneNumbers extends React.Component {
verifying: false,
verifyMsisdn: "",
verifyError: null,
newPhoneNumber: "",
newPhoneNumberCode: "",
});
this.refs.newPhoneNumber.value = "";
}).catch((err) => {
this.setState({continueDisabled: false});
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
@ -205,22 +220,28 @@ export default class PhoneNumbers extends React.Component {
if (this.state.verifying) {
const msisdn = this.state.verifyMsisdn;
addVerifySection = (
<div>
<div>
{_t("A text message has been sent to +%(msisdn)s. " +
"Please enter the verification code it contains", {msisdn: msisdn})}
<br />
{this.state.verifyError}
</div>
<form onSubmit={this._onContinueClick} autoComplete={false} noValidate={true}>
<Field id="newPhoneNumberCode" ref="newPhoneNumberCode" label={_t("Verification code")}
type="text" autoComplete="off" disabled={this.state.continueDisabled} />
<AccessibleButton onClick={this._onContinueClick} kind="primary"
disabled={this.state.continueDisabled}>
{_t("Continue")}
</AccessibleButton>
</form>
</div>
<div>
<div>
{_t("A text message has been sent to +%(msisdn)s. " +
"Please enter the verification code it contains", { msisdn: msisdn })}
<br />
{this.state.verifyError}
</div>
<form onSubmit={this._onContinueClick} autoComplete={false} noValidate={true}>
<Field id="mx_PhoneNumbers_newPhoneNumberCode"
type="text"
label={_t("Verification code")}
autoComplete="off"
disabled={this.state.continueDisabled}
value={this.state.newPhoneNumberCode}
onChange={this._onChangeNewPhoneNumberCode}
/>
<AccessibleButton onClick={this._onContinueClick} kind="primary"
disabled={this.state.continueDisabled}>
{_t("Continue")}
</AccessibleButton>
</form>
</div>
);
}
@ -238,9 +259,15 @@ export default class PhoneNumbers extends React.Component {
<form onSubmit={this._onAddClick} autoComplete={false}
noValidate={true} className="mx_PhoneNumbers_new">
<div className="mx_PhoneNumbers_input">
<Field id="newPhoneNumber" ref="newPhoneNumber" label={_t("Phone Number")}
type="text" autoComplete="off" disabled={this.state.verifying}
prefix={phoneCountry} />
<Field id="mx_PhoneNumbers_newPhoneNumber"
type="text"
label={_t("Phone Number")}
autoComplete="off"
disabled={this.state.verifying}
prefix={phoneCountry}
value={this.state.newPhoneNumber}
onChange={this._onChangeNewPhoneNumber}
/>
</div>
{addVerifySection}
</form>

View File

@ -266,12 +266,12 @@
"Passwords can't be empty": "Hesla nemohou být prázdná",
"Permissions": "Oprávnění",
"Phone": "Telefon",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s změnil/a úroveň moci o %(powerLevelDiffText)s.",
"Define the power level of a user": "Stanovte úroveň moci uživatele",
"Failed to change power level": "Nepodařilo se změnit úroveň moci",
"Power level must be positive integer.": "Úroveň moci musí být kladné celé číslo.",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (moc %(powerLevelNumber)s)",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Tuto změnu nepůjde vrátit zpět, protože tomuto uživateli nastavujete stejnou úroveň moci, jakou máte vy.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s změnil/a úroveň oprávnění o %(powerLevelDiffText)s.",
"Define the power level of a user": "Stanovte úroveň oprávnění uživatele",
"Failed to change power level": "Nepodařilo se změnit úroveň oprávnění",
"Power level must be positive integer.": "Úroveň oprávnění musí být kladné celé číslo.",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (oprávnění %(powerLevelNumber)s)",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Tuto změnu nepůjde vrátit zpět, protože tomuto uživateli nastavujete stejnou úroveň oprávnění, jakou máte vy.",
"Alias (optional)": "Alias (nepovinný)",
"Room name (optional)": "Název místnosti (nepovinný)",
"Results from DuckDuckGo": "Výsledky z DuckDuckGo",
@ -1692,5 +1692,50 @@
"Sign in with single sign-on": "Přihlásit se přes jednotné přihlašování",
"Create account": "Vytvořit účet",
"Unable to query for supported registration methods.": "Nepovedlo se načíst podporované způsoby přihlášení.",
"You need to enter a username.": "Je potřeba zadat uživatelské jméno."
"You need to enter a username.": "Je potřeba zadat uživatelské jméno.",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Vloží ¯\\_(ツ)_/¯ na začátek zprávy",
"Changes your display nickname in the current room only": "Pro tuto místnost změní vaší přezdívku",
"User %(userId)s is already in the room": "Uživatel %(userId)s už je v této místnosti",
"The user must be unbanned before they can be invited.": "Uživatel je vykázán, nelze jej pozvat.",
"Show read receipts sent by other users": "Zobrazovat potvrzení o přijetí",
"Order rooms in the room list by most important first instead of most recent": "Seřadit místosti v seznamu podle důležitosti místo podle posledního použití",
"Scissors": "Nůžky",
"Close button should minimize window to tray": "Zavírací tlačítko má jen minimalizovat okno",
"Accept all %(invitedRooms)s invites": "Přijmout všechny pozvánky: %(invitedRooms)s",
"Change room avatar": "Změnit ikonu místnosti",
"Change room name": "Změnit jméno místnosti",
"Change main address for the room": "Změnit hlavní adresu místnosti",
"Change history visibility": "Změnit viditelnost historie",
"Change permissions": "Změnit oprávnění",
"Change topic": "Změnit téma",
"Modify widgets": "Pozměnit widgety",
"Send messages": "Poslat zprávy",
"Invite users": "Pozvat uživatele",
"Change settings": "Změnit nastavení",
"Kick users": "Vykopnout uživatele",
"Ban users": "Vykázat uživatele",
"Remove messages": "Odstranit zprávy",
"Notify everyone": "Upozornění pro celou místnost",
"Enable encryption?": "Povolit šifrování?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Po zapnutí už nelze šifrování v této místnosti zakázat. Zprávy v šifrovaných místostech můžou číst jenom členové místnosti, server se k obsahu nedostane. Šifrování místností nepodporuje většina botů a bridgů. <a>Více informací o šifrování.</a>",
"Error updating main address": "Nepovedlo se změnit hlavní adresu",
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Nastala chyba při pokusu o nastavení hlavní adresy místnosti. Mohl to zakázat server, nebo to může být dočasná chyba.",
"Error creating alias": "Nepovedlo se vyrobit alias",
"There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "Nastala chyba při pokusu o nastavení aliasu místnosti. Mohl to zakázat server, nebo to může být dočasná chyba.",
"Error removing alias": "Nepovedlo se odebrat alias",
"There was an error removing that alias. It may no longer exist or a temporary error occurred.": "Nastala chyba při pokusu o odstranění aliasu místnosti. Je možné, že už neexistuje, nebo to může být dočasná chyba.",
"Power level": "Úroveň oprávnění",
"To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of Riot to do this": "Abyste po odhlášení nepřišli o přístup k historii šifrovaných konverzací, měli byste si před odhlášením exportovat šifrovací klíče místností. Prosím vraťte se k novější verzi Riotu a exportujte si klíče",
"Room Settings - %(roomName)s": "Nastavení místnosti - %(roomName)s",
"A username can only contain lower case letters, numbers and '=_-./'": "Uživatelské jméno může obsahovat malá písmena, čísla a znaky '=_-./'",
"Share Permalink": "Sdílet odkaz",
"Sign in to your Matrix account": "Přihlašte se k účtu Matrix",
"Sign in to your Matrix account on %(serverName)s": "Přihlašte se k účtu Matrix na %(serverName)s",
"Create your Matrix account": "Vytvořte si účet Matrix",
"Create your Matrix account on %(serverName)s": "Vytvořte si účet Matrix na %(serverName)s",
"You are logged in to another account": "Jste přihlášeni k jinému účtu",
"Thank you for verifying your email! The account you're logged into here (%(sessionUserId)s) appears to be different from the account you've verified an email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, please log out first.": "Děkujeme za ověření emailu! Nicméně účet kterým jste zde přihlášeni (%(sessionUserId)s) je jiný než účet pro který jste verifikovali email (%(verifiedUserId)s). Pokud se chcete přihlásit jako %(verifiedUserId2)s, tak se nejdřív odhlašte.",
"Could not load user profile": "Nepovedlo se načíst profil uživatele",
"Your Matrix account": "Váš účet Matrix",
"Your Matrix account on %(serverName)s": "Váš účet Matrix na %(serverName)s"
}

View File

@ -1143,7 +1143,7 @@
"Picture": "Bild",
"Every page you use in the app": "Jede Seite, die du in der App benutzt",
"e.g. <CurrentPageURL>": "z.B. <CurrentPageURL>",
"Your User Agent": "Deine Browser-Kennung",
"Your User Agent": "Dein User-Agent",
"Your device resolution": "Deine Bildschirmauflösung",
"Popout widget": "Widget ausklinken",
"Always show encryption icons": "Immer Verschlüsselungssymbole zeigen",
@ -1301,7 +1301,7 @@
"Show developer tools": "Zeige Entwickler-Werkzeuge",
"If you would like to create a Matrix account you can <a>register</a> now.": "Wenn du ein Matrix-Konto erstellen möchtest, kannst du dich jetzt <a>registrieren</a>.",
"You are currently using Riot anonymously as a guest.": "Du benutzt aktuell Riot anonym als Gast.",
"Unable to load! Check your network connectivity and try again.": "Konnte nicht geladen werden! Überprüfe deine Netzwerkverbindung und versuche es erneut.",
"Unable to load! Check your network connectivity and try again.": "Konnte nicht geladen werden! Überprüfe die Netzwerkverbindung und versuche es erneut.",
"Backup of encryption keys to server": "Sichern der Verschlüsselungs-Schlüssel auf dem Server",
"Delete Backup": "Sicherung löschen",
"Delete backup": "Sicherung löschen",
@ -1361,7 +1361,7 @@
"No need for symbols, digits, or uppercase letters": "Kein Bedarf an Symbolen, Zahlen oder Großbuchstaben",
"Avoid repeated words and characters": "Vermeide wiederholte Worte und Zeichen",
"Avoid sequences": "Vermeide Sätze",
"Avoid recent years": "Vermeide nahe Jahre",
"Avoid recent years": "Vermeide die letzten Jahre",
"Avoid years that are associated with you": "Vermeide Jahre, die mit dir zusammenhängen",
"Avoid dates and years that are associated with you": "Vermeide Daten und Jahre, die mit dir in Verbindung stehen",
"Capitalization doesn't help very much": "Großschreibung hilft nicht viel",
@ -1531,12 +1531,12 @@
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Die Datei '%(fileName)s' überschreitet die maximale Größe für Uploads auf diesem Heimserver",
"This room has no topic.": "Dieser Raum hat kein Thema.",
"%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s machte den Raum für jeden, der den Link kennt öffentlich.",
"%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s machte den Raum nur für Eingeladene zugreifbar.",
"%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s hat den Raum auf eingeladene User beschränkt.",
"%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s änderte die Zutrittsregel auf '%(rule)s'",
"%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s erlaubte Gäste diesem Raum beizutreten.",
"%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s verbot Gästen diesem Raum beizutreten.",
"%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s änderte den Gastzugriff auf '%(rule)s'",
"Group & filter rooms by custom tags (refresh to apply changes)": "Gruppiere & filtere Räume nach angepassten Tags (neu laden um Änderungen zu übernehmen)",
"Group & filter rooms by custom tags (refresh to apply changes)": "Gruppiere & filtere Räume nach eigenen Tags (neu laden um Änderungen zu übernehmen)",
"Unable to find a supported verification method.": "Konnte kein unterstützte Verifikationsmethode finden.",
"Dog": "Hund",
"Cat": "Katze",
@ -1752,5 +1752,12 @@
"Failed to get protocol list from homeserver": "Konnte Protokollliste vom Heimserver nicht abrufen",
"Unable to query for supported registration methods.": "Konnte unterstützte Registrierungsmethoden nicht abrufen.",
"Bulk options": "Sammeloptionen",
"Join millions for free on the largest public server": "Tritt wie Millionen andere kostenlos dem größten öffentlichen Server bei"
"Join millions for free on the largest public server": "Tritt wie Millionen andere kostenlos dem größten öffentlichen Server bei",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Fügt ¯\\_(ツ)_/¯ vor einer Klartextnachricht ein",
"Changes your display nickname in the current room only": "Ändert den Anzeigenamen ausschließlich für den aktuellen Raum",
"%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s aktivierte Abzeichen der Gruppen %(groups)s für diesen Raum.",
"%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s deaktivierte Abzeichen der Gruppen %(groups)s in diesem Raum.",
"%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s aktivierte Abzeichen von %(newGroups)s und deaktivierte die Abzeichen von %(oldGroups)s in diesem Raum.",
"User %(userId)s is already in the room": "Nutzer %(userId)s ist bereits im Raum",
"The user must be unbanned before they can be invited.": "Nutzer müssen entbannt werden, bevor sie eingeladen werden können."
}

View File

@ -1290,8 +1290,7 @@
"Find other public servers or use a custom server": "Find other public servers or use a custom server",
"Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.",
"Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.",
"Please install <chromeLink>Chrome</chromeLink> or <firefoxLink>Firefox</firefoxLink> for the best experience.": "Please install <chromeLink>Chrome</chromeLink> or <firefoxLink>Firefox</firefoxLink> for the best experience.",
"<safariLink>Safari</safariLink> and <operaLink>Opera</operaLink> work too.": "<safariLink>Safari</safariLink> and <operaLink>Opera</operaLink> work too.",
"Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.": "Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.",
"With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!",
"I understand the risks and wish to continue": "I understand the risks and wish to continue",
"Couldn't load page": "Couldn't load page",
@ -1425,7 +1424,7 @@
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
"A new password must be entered.": "A new password must be entered.",
"New passwords must match each other.": "New passwords must match each other.",
"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.": "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.",
"Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.": "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.",
"Your Matrix account": "Your Matrix account",
"Your Matrix account on %(serverName)s": "Your Matrix account on %(serverName)s",
"The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please enter a valid URL including the protocol prefix.": "The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please enter a valid URL including the protocol prefix.",

View File

@ -1529,5 +1529,33 @@
"An error ocurred whilst trying to remove the widget from the room": "Sovelman poistossa huoneesta tapahtui virhe",
"Minimize apps": "Pienennä sovellukset",
"Reload widget": "Lataa sovelma uudelleen",
"Popout widget": "Avaa sovelma omassa ikkunassaan"
"Popout widget": "Avaa sovelma omassa ikkunassaan",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Lisää ”¯\\_(ツ)_/¯” viestin alkuun",
"User %(userId)s is already in the room": "Käyttäjä %(userId)s on jo tässä huoneessa",
"The user must be unbanned before they can be invited.": "Käyttäjän esto täytyy purkaa ennen kyseinen käyttäjä voidaan kutsua huoneeseen.",
"<a>Upgrade</a> to your own domain": "<a>Päivitä</a> osoitteeksesi oma domainisi",
"Accept all %(invitedRooms)s invites": "Hyväksy kaikki %(invitedRooms)s kutsut",
"Change room avatar": "Vaihda huoneen kuva",
"Change room name": "Vaihda huoneen nimi",
"Change main address for the room": "Vaihda huoneen pääosoite",
"Change history visibility": "Vaihda keskusteluhistorian näkyvyys",
"Change permissions": "Vaihda oikeuksia",
"Change topic": "Vaihda otsikko",
"Modify widgets": "Muokkaa sovelmia",
"Default role": "Oletusrooli",
"Send messages": "Lähetä viestejä",
"Invite users": "Kutsu käyttäjiä",
"Change settings": "Vaihda asetuksia",
"Kick users": "Potki käyttäjiä",
"Ban users": "Estä käyttäjiä",
"Remove messages": "Poista viestejä",
"Notify everyone": "Kiinnitä kaikkien huomio",
"Send %(eventType)s events": "Lähetä %(eventType)s tapahtumaa",
"Select the roles required to change various parts of the room": "Valitse roolit, jotka vaaditaan huoneen eri osioiden vaihtamiseen",
"Enable encryption?": "Ota salaus käyttöön?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Salausta ei voi ottaa pois käytöstä käyttöönoton jälkeen. Viestejä, jotka on lähetetty salattuun huoneeseen, voidaan lukea vain huoneen jäsenten, ei palvelimen, toimesta. Salauksen käyttöönotto saattaa haitata bottien ja siltojen toimivuutta. <a>Lisää tietoa salauksesta.</a>",
"Power level": "Oikeuksien taso",
"Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Tapahtuman, johon oli vastattu, lataaminen epäonnistui. Se joko ei ole olemassa tai sinulla ei ole oikeutta katsoa sitä.",
"The following users may not exist": "Seuraavat käyttäjät eivät välttämättä ole olemassa",
"Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Alla luetelluille Matrix ID:ille ei löytynyt profiileja. Haluaisitko kutsua ne siitä huolimatta?"
}

View File

@ -1476,7 +1476,7 @@
"Tabbed settings": "Réglages en onglets",
"Two-way device verification using short text": "Vérification d'appareils bidirectionnelle utilisant un texte court",
"Enable Emoji suggestions while typing": "Activer la suggestion démojis lors de la saisie",
"Render simple counters in room header": "Afficher des compteurs simples dans l'en-tête des salons",
"Render simple counters in room header": "Afficher des compteurs simples dans len-tête des salons",
"Show a placeholder for removed messages": "Afficher les messages supprimés",
"Show join/leave messages (invites/kicks/bans unaffected)": "Afficher les messages d'arrivée et de départ (les invitations/expulsions/bannissements ne sont pas concernés)",
"Show avatar changes": "Afficher les changements d'avatar",
@ -1803,5 +1803,33 @@
"Create your Matrix account on %(serverName)s": "Créez votre compte Matrix sur %(serverName)s",
"Could not load user profile": "Impossible de charger le profil de lutilisateur",
"Your Matrix account": "Votre compte Matrix",
"Your Matrix account on %(serverName)s": "Votre compte Matrix sur %(serverName)s"
"Your Matrix account on %(serverName)s": "Votre compte Matrix sur %(serverName)s",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Ajoute ¯\\_(ツ)_/¯ devant un message en texte brut",
"User %(userId)s is already in the room": "Lutilisateur %(userId)s est déjà membre du salon",
"The user must be unbanned before they can be invited.": "Le bannissement de lutilisateur doit être révoqué avant de pouvoir linviter.",
"<a>Upgrade</a> to your own domain": "<a>Mettre à niveau</a> vers votre propre domaine",
"Accept all %(invitedRooms)s invites": "Accepter les %(invitedRooms)s invitations",
"Change room avatar": "Changer lavatar du salon",
"Change room name": "Changer le nom du salon",
"Change main address for the room": "Changer ladresse principale du salon",
"Change history visibility": "Changer la visibilité de lhistorique",
"Change permissions": "Changer les permissions",
"Change topic": "Changer le sujet",
"Modify widgets": "Modifier les widgets",
"Default role": "Rôle par défaut",
"Send messages": "Envoyer des messages",
"Invite users": "Inviter des utilisateurs",
"Change settings": "Changer les paramètres",
"Kick users": "Expulser des utilisateurs",
"Ban users": "Bannir des utilisateurs",
"Remove messages": "Supprimer des messages",
"Notify everyone": "Avertir tout le monde",
"Send %(eventType)s events": "Envoyer %(eventType)s évènements",
"Select the roles required to change various parts of the room": "Sélectionner les rôles nécessaires pour modifier les différentes parties du salon",
"Enable encryption?": "Activer le chiffrement ?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Le chiffrement du salon ne peut pas être désactivé après son activation. Les messages dun salon chiffré ne peuvent pas être vus par le serveur, seulement par les membres du salon. Activer le chiffrement peut empêcher certains robots et certaines passerelles de fonctionner correctement. <a>En savoir plus sur le chiffrement.</a>",
"Power level": "Rang",
"Want more than a community? <a>Get your own server</a>": "Vous voulez plus quune communauté ? <a>Obtenez votre propre serveur</a>",
"You are logged in to another account": "Vous êtes connecté(e) avec un autre compte",
"Thank you for verifying your email! The account you're logged into here (%(sessionUserId)s) appears to be different from the account you've verified an email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, please log out first.": "Merci de vérifier votre adresse e-mail ! Le compte avec lequel vous êtes connecté (%(sessionUserId)s) est différent du compte que vous avez vérifié avec cette adresse e-mail (%(verifiedUserId)s). Si vous souhaitez vous connecter avec %(verifiedUserId2)s, veuillez vous déconnecter dabord."
}

View File

@ -1803,5 +1803,33 @@
"Create your Matrix account on %(serverName)s": "Készíts egy Matrix fiókot itt: %(serverName)s",
"Could not load user profile": "A felhasználói profil nem tölthető be",
"Your Matrix account": "A Matrix fiókod",
"Your Matrix account on %(serverName)s": "A Matrix fiókod itt: %(serverName)s"
"Your Matrix account on %(serverName)s": "A Matrix fiókod itt: %(serverName)s",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Hozzáteszi a sima szöveges üzenethez ezt: ¯\\_(ツ)_/¯",
"User %(userId)s is already in the room": "%(userId)s felhasználó már a szobában van",
"The user must be unbanned before they can be invited.": "A felhasználó kitiltását először vissza kell vonni mielőtt újra meghívható lesz.",
"<a>Upgrade</a> to your own domain": "<a>Frissíts</a> a saját domain-re",
"Accept all %(invitedRooms)s invites": "Minden meghívást elfogad: %(invitedRooms)s",
"Change room avatar": "Szoba profilképének megváltoztatása",
"Change room name": "Szoba nevének megváltoztatása",
"Change main address for the room": "A szoba elsődleges címének megváltoztatása",
"Change history visibility": "Régi üzenetek láthatóságának megváltoztatása",
"Change permissions": "Jogosultságok megváltoztatása",
"Change topic": "Téma megváltoztatása",
"Modify widgets": "Kisalkalmazások megváltoztatása",
"Default role": "Alapértelmezett szerep",
"Send messages": "Üzenetek küldése",
"Invite users": "Felhasználók meghívása",
"Change settings": "Beállítások megváltoztatása",
"Kick users": "Felhasználók kirúgása",
"Ban users": "Felhasználók kitiltása",
"Remove messages": "Üzenetek törlése",
"Notify everyone": "Mindenki értesítése",
"Send %(eventType)s events": "%(eventType)s esemény küldése",
"Select the roles required to change various parts of the room": "A szoba bizonyos beállításainak megváltoztatásához szükséges szerep kiválasztása",
"Enable encryption?": "Titkosítás engedélyezése?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Ha egyszer engedélyezve lett, a szoba titkosítását nem lehet kikapcsolni. A titkosított szobákban küldött üzenetek a szerver számára nem, csak a szoba tagjai számára láthatók. A titkosítás bekapcsolása megakadályoz sok botot és hidat a megfelelő működésben. <a>Tudj meg többet a titkosításról.</a>",
"Power level": "Hozzáférési szint",
"Want more than a community? <a>Get your own server</a>": "Többet szeretnél, mint egy közösség? <a>Szerezz saját szervert</a>",
"You are logged in to another account": "Másik felhasználói fiókba vagy bejelentkezve",
"Thank you for verifying your email! The account you're logged into here (%(sessionUserId)s) appears to be different from the account you've verified an email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, please log out first.": "Köszönjük, hogy ellenőrizted az e-mailed! A fiók ahova be vagy jelentkezve itt (%(sessionUserId)s) különbözőnek tűnik attól a fióktól amihez az e-mailedet ellenőrizted (%(verifiedUserId)s). Ha be szeretnél jelentkezni ide: %(verifiedUserId2)s, ahhoz először ki kell jelentkezned."
}

View File

@ -1523,5 +1523,82 @@
"Hat": "Chapéu",
"Glasses": "Óculos",
"Spanner": "Chave",
"Santa": "Papai-noel"
"Santa": "Papai-noel",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Adiciona ¯ \\ _ (ツ) _ / ¯ no início de uma mensagem de texto simples",
"User %(userId)s is already in the room": "O usuário %(userId)s já está na sala",
"The user must be unbanned before they can be invited.": "O usuário não deve estar banido para poder ser convidado.",
"Show display name changes": "Mostrar alterações no nome de exibição",
"Verify this user by confirming the following emoji appear on their screen.": "Verifique este usuário confirmando o emoticon a seguir exibido em sua tela.",
"Verify this user by confirming the following number appears on their screen.": "Confirme este usuário confirmando se o número a seguir aparece na tela.",
"Thumbs up": "Joinha",
"Umbrella": "Guarda-chuva",
"Hourglass": "Ampulheta",
"Clock": "Relógio",
"Gift": "Presente",
"Light bulb": "Lâmpada",
"Book": "Livro",
"Pencil": "Lápis",
"Paperclip": "Clipe de papel",
"Scissors": "Tesouras",
"Padlock": "Cadeado",
"Key": "Chave",
"Hammer": "Martelo",
"Telephone": "Telefone",
"Flag": "Bandeira",
"Train": "Trem",
"Bicycle": "Bicicleta",
"Aeroplane": "Avião",
"Rocket": "Foguete",
"Trophy": "Troféu",
"Ball": "Bola",
"Guitar": "Violão",
"Trumpet": "Trombeta",
"Bell": "Sino",
"Anchor": "Âncora",
"Headphones": "Fones de ouvido",
"Folder": "Pasta",
"Pin": "Alfinete",
"Your homeserver does not support device management.": "Seu homeserver não suporta gerenciamento de dispositivos.",
"Yes": "Sim",
"No": "Não",
"We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Enviamos um e-mail para você confirmar seu endereço. Por favor, siga as instruções e clique no botão abaixo.",
"Email Address": "Endereço de e-mail",
"Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Você tem certeza? Você perderá suas mensagens criptografadas se não for feito o backup correto de suas chaves.",
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "As mensagens criptografadas são protegidas com criptografia de ponta a ponta. Somente você e o(s) destinatário(s) têm as chaves para ler essas mensagens.",
"Restore from Backup": "Restaurar do Backup",
"This device is backing up your keys. ": "Este dispositivo está fazendo backup de suas chaves. ",
"This device is <b>not backing up your keys</b>.": "Este dispositivo <b>não está fazendo backup de suas chaves</ b>.",
"Back up your keys before signing out to avoid losing them.": "Faça o backup das suas chaves antes de sair, para evitar perdê-las.",
"Use key backup": "Usar backup de chave",
"Backing up %(sessionsRemaining)s keys...": "Fazendo o backup das chaves de %(sessionsRemaining)s...",
"All keys backed up": "O Backup de todas as chaves foi realizado",
"Backup has a signature from <verify>unknown</verify> device with ID %(deviceId)s.": "O Backup tem uma assinatura do dispositivo <verify>unknown</ verify> com ID %(deviceId)s.",
"This backup is trusted because it has been restored on this device": "Este backup é confiável porque foi restaurado neste dispositivo",
"Your keys are <b>not being backed up from this device</b>.": "Suas chaves <b>não estão sendo copiadas para este dispositivo</ b>.",
"Start using Key Backup": "Comece a usar o Backup de chave",
"Add an email address to configure email notifications": "Adicione um endereço de e-mail para configurar notificações por e-mail",
"Unable to verify phone number.": "Não é possível verificar o número de telefone.",
"Verification code": "Código de verificação",
"Phone Number": "Número de telefone",
"Profile picture": "Foto de Perfil",
"Upload profile picture": "Carregar foto do perfil",
"<a>Upgrade</a> to your own domain": "<a>Atualize</a> para seu próprio domínio",
"Set a new account password...": "Definir uma nova senha da conta...",
"Email addresses": "Endereços de e-mail",
"Phone numbers": "Números de Telefone",
"Language and region": "Idioma e região",
"Theme": "Tema",
"Account management": "Gerenciamento da Conta",
"Deactivating your account is a permanent action - be careful!": "Desativar sua conta é uma ação permanente - tenha cuidado!",
"General": "Geral",
"Credits": "Créditos",
"For help with using Riot, click <a>here</a>.": "Para ajuda com o uso do Riot, clique <a>aqui</a>.",
"For help with using Riot, click <a>here</a> or start a chat with our bot using the button below.": "Para obter ajuda com o uso do Riot, clique <a>aqui</a> ou inicie um bate-papo com nosso bot usando o botão abaixo.",
"Chat with Riot Bot": "Converse com o bot do Riot",
"Help & About": "Ajuda & Sobre",
"Bug reporting": "Relato de Erros",
"FAQ": "FAQ",
"Versions": "Versões",
"Close button should minimize window to tray": "O botão Fechar deve minimizar a janela para a bandeja",
"Preferences": "Preferências"
}

View File

@ -1722,5 +1722,33 @@
"Recovery Method Removed": "恢复方式已移除",
"This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "此设置已检测到您的安全消息的恢复密码和密钥被移除。",
"If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "如果您意外执行了此操作,您可以在此设备上设置安全消息来使用新恢复方式重新加密此设备上的消息历史记录。",
"If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "如果您没有移除该恢复方式,可能有攻击者正试图侵入您的账号。请立即更改您的账号密码并在设置中设定一个新的恢复方式。"
"If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "如果您没有移除该恢复方式,可能有攻击者正试图侵入您的账号。请立即更改您的账号密码并在设置中设定一个新的恢复方式。",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "将 ¯\\_(ツ)_/¯ 添加到纯文本消息中",
"User %(userId)s is already in the room": "用户 %(userId)s 已在聊天室中",
"The user must be unbanned before they can be invited.": "用户必须先解封才能被邀请。",
"<a>Upgrade</a> to your own domain": "<a>升级</a> 到您自己的域名",
"Accept all %(invitedRooms)s invites": "接受所有 %(invitedRooms)s 邀请",
"Change room avatar": "更改聊天室头像",
"Change room name": "更改聊天室名称",
"Change main address for the room": "更改聊天室主地址",
"Change history visibility": "更改历史记录可见性",
"Change permissions": "更改权限",
"Change topic": "更改话题",
"Modify widgets": "修改小部件",
"Default role": "默认角色",
"Send messages": "发送消息",
"Invite users": "邀请用户",
"Change settings": "更改设置",
"Kick users": "移除用户",
"Ban users": "封禁用户",
"Remove messages": "移除消息",
"Notify everyone": "通知每个人",
"Send %(eventType)s events": "发送 %(eventType)s 事件",
"Select the roles required to change various parts of the room": "选择更改聊天室各个部分所需的角色",
"Enable encryption?": "启用加密?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "聊天室加密一旦启用就无法再被停用。在加密聊天室内传送的消息不会被服务器看到,而只能被聊天室的参与者看到。启用加密可能会使许多机器人和桥接工作不正常。 <a>详细了解加密。</a>",
"Power level": "权限级别",
"Want more than a community? <a>Get your own server</a>": "想要的不只是社区? <a>架设您自己的服务器</a>",
"You are logged in to another account": "您已以另一个账号登入",
"Thank you for verifying your email! The account you're logged into here (%(sessionUserId)s) appears to be different from the account you've verified an email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, please log out first.": "感谢您验证电子邮件地址!您在此登入的账号(%(sessionUserId)s似乎不同于您验证的电子邮件地址%(verifiedUserId)s。如果您想要登入的账号是 %(verifiedUserId2)s ,请先登出。"
}

View File

@ -1798,5 +1798,33 @@
"Create your Matrix account on %(serverName)s": "在 %(serverName)s 上建立您的 Matrix 帳號",
"Could not load user profile": "無法載入使用者簡介",
"Your Matrix account": "您的 Matrix 帳號",
"Your Matrix account on %(serverName)s": "您在 %(serverName)s 上的 Matrix 帳號"
"Your Matrix account on %(serverName)s": "您在 %(serverName)s 上的 Matrix 帳號",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "將 ¯\\_(ツ)_/¯ 附加到純文字訊息中",
"User %(userId)s is already in the room": "使用者 %(userId)s 已經在此聊天室了",
"The user must be unbanned before they can be invited.": "使用者必須在被邀請前先解除封鎖。",
"<a>Upgrade</a> to your own domain": "<a>升級</a>到您自己的網域",
"Accept all %(invitedRooms)s invites": "接受所有 %(invitedRooms)s 邀請",
"Change room avatar": "變更聊天室大頭貼",
"Change room name": "變更聊天室名稱",
"Change main address for the room": "變更聊天室主要位置",
"Change history visibility": "變更歷史紀錄能見度",
"Change permissions": "變更權限",
"Change topic": "變更主題",
"Modify widgets": "修改小工具",
"Default role": "預設角色",
"Send messages": "傳送訊息",
"Invite users": "邀請使用者",
"Change settings": "變更設定",
"Kick users": "踢除使用者",
"Ban users": "封鎖使用者",
"Remove messages": "移除訊息",
"Notify everyone": "通知每個人",
"Send %(eventType)s events": "傳送 %(eventType)s 活動",
"Select the roles required to change various parts of the room": "選取更改聊天室各部份的所需的角色",
"Enable encryption?": "啟用加密?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "一旦啟用,聊天室的加密就不能停用了。在已加密的聊天室裡傳送的訊息無法被伺服器看見,僅能被聊天室的參與者看到。啟用加密可能會讓許多機器人與橋接運作不正常。<a>取得更多關於加密的資訊。</a>",
"Power level": "權力等級",
"Want more than a community? <a>Get your own server</a>": "想要的不只是社群?<a>架設您自己的伺服器</a>",
"You are logged in to another account": "您已登入到另一個帳號",
"Thank you for verifying your email! The account you're logged into here (%(sessionUserId)s) appears to be different from the account you've verified an email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, please log out first.": "感謝驗證您的電子郵件!您在此登入的帳號 (%(sessionUserId)s) 似乎與您驗證電子郵件的 (%(verifiedUserId)s) 不同。如果您想要登入 %(verifiedUserId2)s請先登出。"
}

7116
yarn.lock Normal file

File diff suppressed because it is too large Load Diff