mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 13:14:58 +08:00
Merge remote-tracking branch 'origin/experimental' into travis/fix-memberlist-order
This commit is contained in:
commit
cc8fa7911b
@ -30,7 +30,7 @@ popd
|
||||
if [ "$TRAVIS_BRANCH" = "develop" ]
|
||||
then
|
||||
# run end to end tests
|
||||
git clone https://github.com/matrix-org/matrix-react-end-to-end-tests.git --branch master
|
||||
scripts/fetchdep.sh matrix-org matrix-react-end-to-end-tests master
|
||||
pushd matrix-react-end-to-end-tests
|
||||
ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web
|
||||
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
|
||||
|
@ -124,8 +124,9 @@
|
||||
"eslint-plugin-flowtype": "^2.30.0",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"estree-walker": "^0.5.0",
|
||||
"expect": "^1.16.0",
|
||||
"expect": "^23.6.0",
|
||||
"flow-parser": "^0.57.3",
|
||||
"jest-mock": "^23.2.0",
|
||||
"karma": "^3.0.0",
|
||||
"karma-chrome-launcher": "^0.2.3",
|
||||
"karma-cli": "^1.0.1",
|
||||
|
@ -25,8 +25,10 @@
|
||||
@import "./structures/_ViewSource.scss";
|
||||
@import "./structures/login/_Login.scss";
|
||||
@import "./views/avatars/_BaseAvatar.scss";
|
||||
@import "./views/avatars/_MemberStatusMessageAvatar.scss";
|
||||
@import "./views/context_menus/_MessageContextMenu.scss";
|
||||
@import "./views/context_menus/_RoomTileContextMenu.scss";
|
||||
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
||||
@import "./views/context_menus/_TagTileContextMenu.scss";
|
||||
@import "./views/context_menus/_TopLeftMenu.scss";
|
||||
@import "./views/dialogs/_BugReportDialog.scss";
|
||||
@ -35,7 +37,6 @@
|
||||
@import "./views/dialogs/_ChatInviteDialog.scss";
|
||||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||
@import "./views/dialogs/_CreateKeyBackupDialog.scss";
|
||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||
|
@ -14,12 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_CreateKeyBackupDialog {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_recoveryKey {
|
||||
padding: 20px;
|
||||
color: $info-plinth-fg-color;
|
||||
background-color: $info-plinth-bg-color;
|
||||
.mx_MemberStatusMessageAvatar_hasStatus {
|
||||
border: 2px solid $accent-color;
|
||||
border-radius: 40px;
|
||||
padding-right: 0 !important; /* Override AccessibleButton styling */
|
||||
}
|
55
res/css/views/context_menus/_StatusMessageContextMenu.scss
Normal file
55
res/css/views/context_menus/_StatusMessageContextMenu.scss
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_StatusMessageContextMenu_message {
|
||||
display: inline-block;
|
||||
border-radius: 3px 0 0 3px;
|
||||
border: 1px solid $input-border-color;
|
||||
font-size: 13px;
|
||||
padding: 7px 7px 7px 9px;
|
||||
width: 135px;
|
||||
background-color: $primary-bg-color !important;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_submit {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_submitFaded {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_submit img {
|
||||
vertical-align: middle;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu hr {
|
||||
border: 0.5px solid $menu-border-color;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_clearIcon {
|
||||
margin: 5px 15px 5px 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_clear {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_hasStatus .mx_StatusMessageContextMenu_clear {
|
||||
color: $warning-color;
|
||||
}
|
@ -13,7 +13,11 @@ 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.
|
||||
*/
|
||||
|
||||
|
||||
.mx_CreateKeyBackupDialog {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_primaryContainer {
|
||||
/*FIXME: plinth colour in new theme(s). background-color: $accent-color;*/
|
||||
padding: 20px
|
||||
@ -25,9 +29,13 @@ limitations under the License.
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_passPhraseContainer {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_passPhraseHelp {
|
||||
float: right;
|
||||
width: 230px;
|
||||
flex: 1;
|
||||
height: 85px;
|
||||
margin-left: 20px;
|
||||
font-size: 80%;
|
||||
@ -38,20 +46,36 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_passPhraseInput {
|
||||
flex: none;
|
||||
width: 250px;
|
||||
border: 1px solid $accent-color;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_passPhraseMatch {
|
||||
float: right;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_recoveryKeyButtons {
|
||||
float: right;
|
||||
.mx_CreateKeyBackupDialog_recoveryKeyHeader {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_recoveryKeyContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_recoveryKey {
|
||||
width: 300px;
|
||||
width: 262px;
|
||||
padding: 20px;
|
||||
color: $info-plinth-fg-color;
|
||||
background-color: $info-plinth-bg-color;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.mx_CreateKeyBackupDialog_recoveryKeyButtons {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -107,3 +107,10 @@ limitations under the License.
|
||||
}
|
||||
*/
|
||||
|
||||
.mx_EntityTile_subtext {
|
||||
font-size: 11px;
|
||||
opacity: 0.5;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
@ -132,6 +132,13 @@ limitations under the License.
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.mx_MemberInfo_statusMessage {
|
||||
font-size: 11px;
|
||||
opacity: 0.5;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: clip;
|
||||
}
|
||||
.mx_MemberInfo .mx_MemberInfo_scrollContainer {
|
||||
flex: 1;
|
||||
}
|
||||
|
@ -48,15 +48,48 @@ limitations under the License.
|
||||
left: -12px;
|
||||
}
|
||||
|
||||
.mx_RoomTile_avatar {
|
||||
flex: 0;
|
||||
padding: 4px;
|
||||
|
||||
.mx_RoomTile_nameContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_RoomTile_labelContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_RoomTile_subtext {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
padding: 0 0 0 7px;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: clip;
|
||||
position: relative;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
.mx_RoomTile_avatar_container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_RoomTile_avatar {
|
||||
flex: 0;
|
||||
padding: 4px;
|
||||
width: 24px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_RoomTile_hasSubtext .mx_RoomTile_avatar {
|
||||
padding-top: 0;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
.mx_RoomTile_dm {
|
||||
display: block;
|
||||
position: absolute;
|
||||
@ -69,7 +102,7 @@ limitations under the License.
|
||||
flex: 1 5 auto;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 6px;
|
||||
padding: 0 6px;
|
||||
color: $roomtile-name-color;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
|
17
res/img/icons-checkmark.svg
Normal file
17
res/img/icons-checkmark.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Tick</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Custom-Status-Copy" transform="translate(-529.000000, -917.000000)" fill-rule="nonzero">
|
||||
<g id="Tick" transform="translate(530.000000, 918.000000)">
|
||||
<circle id="Oval" stroke="#6AAC8C" fill="#75CFA6" cx="9" cy="9" r="9"></circle>
|
||||
<g id="Glyph" transform="translate(8.949747, 7.949747) rotate(-45.000000) translate(-8.949747, -7.949747) translate(4.449747, 5.449747)" fill="#FFFFFF">
|
||||
<rect id="Rectangle" x="0" y="0" width="2" height="5"></rect>
|
||||
<rect id="Rectangle" x="0" y="3" width="9" height="2"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -2,6 +2,9 @@
|
||||
|
||||
org="$1"
|
||||
repo="$2"
|
||||
defbranch="$3"
|
||||
|
||||
[ -z "$defbranch" ] && defbranch="develop"
|
||||
|
||||
rm -r "$repo" || true
|
||||
|
||||
@ -20,5 +23,5 @@ clone $TRAVIS_PULL_REQUEST_BRANCH
|
||||
clone $TRAVIS_BRANCH
|
||||
# Try the current branch from Jenkins.
|
||||
clone `"echo $GIT_BRANCH" | sed -e 's/^origin\///'`
|
||||
# Use develop as the last resort.
|
||||
clone develop
|
||||
# Use the default branch as the last resort.
|
||||
clone $defbranch
|
||||
|
13
src/Login.js
13
src/Login.js
@ -204,6 +204,19 @@ export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) {
|
||||
|
||||
const data = await client.login(loginType, loginParams);
|
||||
|
||||
const wellknown = data.well_known;
|
||||
if (wellknown) {
|
||||
if (wellknown["m.homeserver"] && wellknown["m.homeserver"]["base_url"]) {
|
||||
hsUrl = wellknown["m.homeserver"]["base_url"];
|
||||
console.log(`Overrode homeserver setting with ${hsUrl} from login response`);
|
||||
}
|
||||
if (wellknown["m.identity_server"] && wellknown["m.identity_server"]["base_url"]) {
|
||||
// TODO: should we prompt here?
|
||||
isUrl = wellknown["m.identity_server"]["base_url"];
|
||||
console.log(`Overrode IS setting with ${isUrl} from login response`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
homeserverUrl: hsUrl,
|
||||
identityServerUrl: isUrl,
|
||||
|
@ -289,11 +289,6 @@ const Notifier = {
|
||||
const room = MatrixClientPeg.get().getRoom(ev.getRoomId());
|
||||
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||
if (actions && actions.notify) {
|
||||
dis.dispatch({
|
||||
action: "event_notification",
|
||||
event: ev,
|
||||
room: room,
|
||||
});
|
||||
if (this.isEnabled()) {
|
||||
this._displayPopupNotification(ev, room);
|
||||
}
|
||||
|
@ -392,7 +392,7 @@ class Tinter {
|
||||
// XXX: we could just move this all into TintableSvg, but as it's so similar
|
||||
// to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg)
|
||||
// keeping it here for now.
|
||||
calcSvgFixups(svgs, forceColors) {
|
||||
calcSvgFixups(svgs) {
|
||||
// go through manually fixing up SVG colours.
|
||||
// we could do this by stylesheets, but keeping the stylesheets
|
||||
// updated would be a PITA, so just brute-force search for the
|
||||
@ -420,21 +420,13 @@ class Tinter {
|
||||
const tag = tags[j];
|
||||
for (let k = 0; k < this.svgAttrs.length; k++) {
|
||||
const attr = this.svgAttrs[k];
|
||||
for (let m = 0; m < this.keyHex.length; m++) { // dev note: don't use L please.
|
||||
// We use a different attribute from the one we're setting
|
||||
// because we may also be using forceColors. If we were to
|
||||
// check the keyHex against a forceColors value, it may not
|
||||
// match and therefore not change when we need it to.
|
||||
const valAttrName = "mx-val-" + attr;
|
||||
let attribute = tag.getAttribute(valAttrName);
|
||||
if (!attribute) attribute = tag.getAttribute(attr); // fall back to the original
|
||||
if (attribute && (attribute.toUpperCase() === this.keyHex[m] || attribute.toLowerCase() === this.keyRgb[m])) {
|
||||
for (let l = 0; l < this.keyHex.length; l++) {
|
||||
if (tag.getAttribute(attr) &&
|
||||
tag.getAttribute(attr).toUpperCase() === this.keyHex[l]) {
|
||||
fixups.push({
|
||||
node: tag,
|
||||
attr: attr,
|
||||
refAttr: valAttrName,
|
||||
index: m,
|
||||
forceColors: forceColors,
|
||||
index: l,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -450,9 +442,7 @@ class Tinter {
|
||||
if (DEBUG) console.log("applySvgFixups start for " + fixups);
|
||||
for (let i = 0; i < fixups.length; i++) {
|
||||
const svgFixup = fixups[i];
|
||||
const forcedColor = svgFixup.forceColors ? svgFixup.forceColors[svgFixup.index] : null;
|
||||
svgFixup.node.setAttribute(svgFixup.attr, forcedColor ? forcedColor : this.colors[svgFixup.index]);
|
||||
svgFixup.node.setAttribute(svgFixup.refAttr, this.colors[svgFixup.index]);
|
||||
svgFixup.node.setAttribute(svgFixup.attr, this.colors[svgFixup.index]);
|
||||
}
|
||||
if (DEBUG) console.log("applySvgFixups end");
|
||||
}
|
||||
|
@ -239,17 +239,19 @@ export default React.createClass({
|
||||
<p>{_t("You'll need it if you log out or lose access to this device.")}</p>
|
||||
|
||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||
<div className="mx_CreateKeyBackupDialog_passPhraseHelp">
|
||||
{strengthMeter}
|
||||
{helpText}
|
||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||
<input type="password"
|
||||
onChange={this._onPassPhraseChange}
|
||||
onKeyPress={this._onPassPhraseKeyPress}
|
||||
value={this.state.passPhrase}
|
||||
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||
placeholder={_t("Enter a passphrase...")}
|
||||
/>
|
||||
<div className="mx_CreateKeyBackupDialog_passPhraseHelp">
|
||||
{strengthMeter}
|
||||
{helpText}
|
||||
</div>
|
||||
</div>
|
||||
<input type="password"
|
||||
onChange={this._onPassPhraseChange}
|
||||
onKeyPress={this._onPassPhraseKeyPress}
|
||||
value={this.state.passPhrase}
|
||||
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||
placeholder={_t("Enter a passphrase...")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DialogButtons primaryButton={_t('Next')}
|
||||
@ -317,16 +319,18 @@ export default React.createClass({
|
||||
"somewhere safe.",
|
||||
)}</p>
|
||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||
{passPhraseMatch}
|
||||
<div>
|
||||
<input type="password"
|
||||
onChange={this._onPassPhraseConfirmChange}
|
||||
onKeyPress={this._onPassPhraseConfirmKeyPress}
|
||||
value={this.state.passPhraseConfirm}
|
||||
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||
placeholder={_t("Repeat your passphrase...")}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||
<div>
|
||||
<input type="password"
|
||||
onChange={this._onPassPhraseConfirmChange}
|
||||
onKeyPress={this._onPassPhraseConfirmKeyPress}
|
||||
value={this.state.passPhraseConfirm}
|
||||
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||
placeholder={_t("Repeat your passphrase...")}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
{passPhraseMatch}
|
||||
</div>
|
||||
</div>
|
||||
<DialogButtons primaryButton={_t('Next')}
|
||||
@ -351,21 +355,21 @@ export default React.createClass({
|
||||
<p>{_t("Make a copy of this Recovery Key and keep it safe.")}</p>
|
||||
<p>{bodyText}</p>
|
||||
<p className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||
<div>{_t("Your Recovery Key")}</div>
|
||||
<div className="mx_CreateKeyBackupDialog_recoveryKeyButtons">
|
||||
<button onClick={this._onCopyClick}>
|
||||
{_t("Copy to clipboard")}
|
||||
</button>
|
||||
{
|
||||
// FIXME REDESIGN: buttons should be adjacent but insufficient room in current design
|
||||
}
|
||||
<br /><br />
|
||||
<button onClick={this._onDownloadClick}>
|
||||
{_t("Download")}
|
||||
</button>
|
||||
<div className="mx_CreateKeyBackupDialog_recoveryKeyHeader">
|
||||
{_t("Your Recovery Key")}
|
||||
</div>
|
||||
<div className="mx_CreateKeyBackupDialog_recoveryKey">
|
||||
<code ref={this._collectRecoveryKeyNode}>{this._keyBackupInfo.recovery_key}</code>
|
||||
<div className="mx_CreateKeyBackupDialog_recoveryKeyContainer">
|
||||
<div className="mx_CreateKeyBackupDialog_recoveryKey">
|
||||
<code ref={this._collectRecoveryKeyNode}>{this._keyBackupInfo.recovery_key}</code>
|
||||
</div>
|
||||
<div className="mx_CreateKeyBackupDialog_recoveryKeyButtons">
|
||||
<button className="mx_Dialog_primary" onClick={this._onCopyClick}>
|
||||
{_t("Copy to clipboard")}
|
||||
</button>
|
||||
<button className="mx_Dialog_primary" onClick={this._onDownloadClick}>
|
||||
{_t("Download")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<br />
|
||||
|
@ -91,11 +91,15 @@ class HomePage extends React.Component {
|
||||
this._unmounted = true;
|
||||
}
|
||||
|
||||
onLoginClick() {
|
||||
onLoginClick(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
dis.dispatch({ action: 'start_login' });
|
||||
}
|
||||
|
||||
onRegisterClick() {
|
||||
onRegisterClick(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
dis.dispatch({ action: 'start_registration' });
|
||||
}
|
||||
|
||||
|
@ -927,6 +927,10 @@ export default React.createClass({
|
||||
},
|
||||
|
||||
_viewHome: function() {
|
||||
// The home page requires the "logged in" view, so we'll set that.
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.LOGGED_IN,
|
||||
});
|
||||
this._setPage(PageTypes.HomePage);
|
||||
this.notifyNewScreen('home');
|
||||
},
|
||||
@ -1183,10 +1187,7 @@ export default React.createClass({
|
||||
* @param {string} teamToken
|
||||
*/
|
||||
_onLoggedIn: async function(teamToken) {
|
||||
this.setState({
|
||||
view: VIEWS.LOGGED_IN,
|
||||
});
|
||||
|
||||
this.setStateForNewView({view: VIEWS.LOGGED_IN});
|
||||
if (teamToken) {
|
||||
// A team member has logged in, not a guest
|
||||
this._teamToken = teamToken;
|
||||
|
@ -163,6 +163,7 @@ module.exports = React.createClass({
|
||||
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
|
||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||
MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
||||
this._fetchMediaConfig();
|
||||
// Start listening for RoomViewStore updates
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
@ -451,6 +452,7 @@ module.exports = React.createClass({
|
||||
MatrixClientPeg.get().removeListener("Room.myMembership", this.onMyMembership);
|
||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
||||
}
|
||||
|
||||
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||
@ -620,6 +622,11 @@ module.exports = React.createClass({
|
||||
false,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
onKeyBackupStatus() {
|
||||
// Key backup status changes affect whether the in-room recovery
|
||||
// reminder is displayed.
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
|
@ -162,6 +162,18 @@ module.exports = React.createClass({
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
onLoginClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onLoginClick();
|
||||
},
|
||||
|
||||
onRegisterClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onRegisterClick();
|
||||
},
|
||||
|
||||
showErrorDialog: function(body, title) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
|
||||
@ -253,10 +265,10 @@ module.exports = React.createClass({
|
||||
</form>
|
||||
{ serverConfigSection }
|
||||
{ errorText }
|
||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||
<a className="mx_Login_create" onClick={this.onLoginClick} href="#">
|
||||
{ _t('Return to login screen') }
|
||||
</a>
|
||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||
<a className="mx_Login_create" onClick={this.onRegisterClick} href="#">
|
||||
{ _t('Create an account') }
|
||||
</a>
|
||||
<LanguageSelector />
|
||||
|
@ -214,7 +214,10 @@ module.exports = React.createClass({
|
||||
}).done();
|
||||
},
|
||||
|
||||
_onLoginAsGuestClick: function() {
|
||||
_onLoginAsGuestClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const self = this;
|
||||
self.setState({
|
||||
busy: true,
|
||||
@ -297,6 +300,12 @@ module.exports = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
onRegisterClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onRegisterClick();
|
||||
},
|
||||
|
||||
_tryWellKnownDiscovery: async function(serverName) {
|
||||
if (!serverName.trim()) {
|
||||
// Nothing to discover
|
||||
@ -567,7 +576,7 @@ module.exports = React.createClass({
|
||||
{ errorTextSection }
|
||||
{ this.componentForStep(this.state.currentFlow) }
|
||||
{ serverConfig }
|
||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||
<a className="mx_Login_create" onClick={this.onRegisterClick} href="#">
|
||||
{ _t('Create an account') }
|
||||
</a>
|
||||
{ loginAsGuestJsx }
|
||||
|
@ -363,6 +363,12 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
onLoginClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onLoginClick();
|
||||
},
|
||||
|
||||
_makeRegisterRequest: function(auth) {
|
||||
// Only send the bind params if we're sending username / pw params
|
||||
// (Since we need to send no params at all to use the ones saved in the
|
||||
@ -468,7 +474,7 @@ module.exports = React.createClass({
|
||||
let signIn;
|
||||
if (!this.state.doingUIAuth) {
|
||||
signIn = (
|
||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||
<a className="mx_Login_create" onClick={this.onLoginClick} href="#">
|
||||
{ theme === 'status' ? _t('Sign in') : _t('I already have an account') }
|
||||
</a>
|
||||
);
|
||||
|
120
src/components/views/avatars/MemberStatusMessageAvatar.js
Normal file
120
src/components/views/avatars/MemberStatusMessageAvatar.js
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import MemberAvatar from '../avatars/MemberAvatar';
|
||||
import classNames from 'classnames';
|
||||
import * as ContextualMenu from "../../structures/ContextualMenu";
|
||||
import StatusMessageContextMenu from "../context_menus/StatusMessageContextMenu";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
export default class MemberStatusMessageAvatar extends React.Component {
|
||||
static propTypes = {
|
||||
member: PropTypes.object.isRequired,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
resizeMethod: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
width: 40,
|
||||
height: 40,
|
||||
resizeMethod: 'crop',
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) {
|
||||
throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user");
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
|
||||
|
||||
if (this.props.member.user) {
|
||||
this.setState({message: this.props.member.user._unstable_statusMessage});
|
||||
} else {
|
||||
this.setState({message: ""});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
|
||||
}
|
||||
}
|
||||
|
||||
_onRoomStateEvents = (ev, state) => {
|
||||
if (ev.getStateKey() !== MatrixClientPeg.get().getUserId()) return;
|
||||
if (ev.getType() !== "im.vector.user_status") return;
|
||||
// TODO: We should be relying on `this.props.member.user._unstable_statusMessage`
|
||||
// We don't currently because the js-sdk doesn't emit a specific event for this
|
||||
// change, and we don't want to race it. This should be improved when we rip out
|
||||
// the im.vector.user_status stuff and replace it with a complete solution.
|
||||
this.setState({message: ev.getContent()["status"]});
|
||||
};
|
||||
|
||||
_onClick = (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const elementRect = e.target.getBoundingClientRect();
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const x = (elementRect.left + window.pageXOffset) - (elementRect.width / 2) + 3;
|
||||
const chevronOffset = 12;
|
||||
let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
|
||||
y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron
|
||||
|
||||
ContextualMenu.createMenu(StatusMessageContextMenu, {
|
||||
chevronOffset: chevronOffset,
|
||||
chevronFace: 'bottom',
|
||||
left: x,
|
||||
top: y,
|
||||
menuWidth: 190,
|
||||
user: this.props.member.user,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return <MemberAvatar member={this.props.member}
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
resizeMethod={this.props.resizeMethod} />;
|
||||
}
|
||||
|
||||
const hasStatus = this.props.member.user ? !!this.props.member.user._unstable_statusMessage : false;
|
||||
|
||||
const classes = classNames({
|
||||
"mx_MemberStatusMessageAvatar": true,
|
||||
"mx_MemberStatusMessageAvatar_hasStatus": hasStatus,
|
||||
});
|
||||
|
||||
return <AccessibleButton onClick={this._onClick} className={classes} element="div">
|
||||
<MemberAvatar member={this.props.member}
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
resizeMethod={this.props.resizeMethod} />
|
||||
</AccessibleButton>;
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class StatusMessageContextMenu extends React.Component {
|
||||
static propTypes = {
|
||||
// js-sdk User object. Not required because it might not exist.
|
||||
user: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
message: props.user ? props.user._unstable_statusMessage : "",
|
||||
};
|
||||
}
|
||||
|
||||
_onClearClick = async(e) => {
|
||||
await MatrixClientPeg.get()._unstable_setStatusMessage("");
|
||||
this.setState({message: ""});
|
||||
};
|
||||
|
||||
_onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
MatrixClientPeg.get()._unstable_setStatusMessage(this.state.message);
|
||||
};
|
||||
|
||||
_onStatusChange = (e) => {
|
||||
this.setState({message: e.target.value});
|
||||
};
|
||||
|
||||
render() {
|
||||
const formSubmitClasses = classNames({
|
||||
"mx_StatusMessageContextMenu_submit": true,
|
||||
"mx_StatusMessageContextMenu_submitFaded": !this.state.message, // no message == faded
|
||||
});
|
||||
|
||||
const form = <form className="mx_StatusMessageContextMenu_form" onSubmit={this._onSubmit} autoComplete="off">
|
||||
<input type="text" key="message" placeholder={_t("Set a new status...")} autoFocus={true}
|
||||
className="mx_StatusMessageContextMenu_message"
|
||||
value={this.state.message} onChange={this._onStatusChange} maxLength="60" />
|
||||
<AccessibleButton onClick={this._onSubmit} element="div" className={formSubmitClasses}>
|
||||
<img src="img/icons-checkmark.svg" width="22" height="22" />
|
||||
</AccessibleButton>
|
||||
</form>;
|
||||
|
||||
const clearIcon = this.state.message ? "img/cancel-red.svg" : "img/cancel.svg";
|
||||
const clearButton = <AccessibleButton onClick={this._onClearClick} disabled={!this.state.message}
|
||||
className="mx_StatusMessageContextMenu_clear">
|
||||
<img src={clearIcon} alt={_t('Clear status')} width="12" height="12"
|
||||
className="mx_filterFlipColor mx_StatusMessageContextMenu_clearIcon" />
|
||||
<span>{_t("Clear status")}</span>
|
||||
</AccessibleButton>;
|
||||
|
||||
const menuClasses = classNames({
|
||||
"mx_StatusMessageContextMenu": true,
|
||||
"mx_StatusMessageContextMenu_hasStatus": this.state.message,
|
||||
});
|
||||
|
||||
return <div className={menuClasses}>
|
||||
{ form }
|
||||
<hr />
|
||||
{ clearButton }
|
||||
</div>;
|
||||
}
|
||||
}
|
@ -36,8 +36,12 @@ export default class ChangelogDialog extends React.Component {
|
||||
for (let i=0; i<REPOS.length; i++) {
|
||||
const oldVersion = version2[2*i];
|
||||
const newVersion = version[2*i];
|
||||
request(`https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`, (a, b, body) => {
|
||||
if (body == null) return;
|
||||
const url = `https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`;
|
||||
request(url, (err, response, body) => {
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
this.setState({ [REPOS[i]]: response.statusText });
|
||||
return;
|
||||
}
|
||||
this.setState({[REPOS[i]]: JSON.parse(body).commits});
|
||||
});
|
||||
}
|
||||
@ -58,13 +62,20 @@ export default class ChangelogDialog extends React.Component {
|
||||
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||
|
||||
const logs = REPOS.map(repo => {
|
||||
if (this.state[repo] == null) return <Spinner key={repo} />;
|
||||
let content;
|
||||
if (this.state[repo] == null) {
|
||||
content = <Spinner key={repo} />;
|
||||
} else if (typeof this.state[repo] === "string") {
|
||||
content = _t("Unable to load commit detail: %(msg)s", {
|
||||
msg: this.state[repo],
|
||||
});
|
||||
} else {
|
||||
content = this.state[repo].map(this._elementsForCommit);
|
||||
}
|
||||
return (
|
||||
<div key={repo}>
|
||||
<h2>{repo}</h2>
|
||||
<ul>
|
||||
{this.state[repo].map(this._elementsForCommit)}
|
||||
</ul>
|
||||
<ul>{content}</ul>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -29,7 +29,6 @@ var TintableSvg = React.createClass({
|
||||
width: PropTypes.string.isRequired,
|
||||
height: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
forceColors: PropTypes.arrayOf(PropTypes.string),
|
||||
},
|
||||
|
||||
statics: {
|
||||
@ -51,12 +50,6 @@ var TintableSvg = React.createClass({
|
||||
delete TintableSvg.mounts[this.id];
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState) {
|
||||
if (prevProps.forceColors !== this.props.forceColors) {
|
||||
this.calcAndApplyFixups(this.refs.svgContainer);
|
||||
}
|
||||
},
|
||||
|
||||
tint: function() {
|
||||
// TODO: only bother running this if the global tint settings have changed
|
||||
// since we loaded!
|
||||
@ -64,13 +57,8 @@ var TintableSvg = React.createClass({
|
||||
},
|
||||
|
||||
onLoad: function(event) {
|
||||
this.calcAndApplyFixups(event.target);
|
||||
},
|
||||
|
||||
calcAndApplyFixups: function(target) {
|
||||
if (!target) return;
|
||||
// console.log("TintableSvg.calcAndApplyFixups for " + this.props.src);
|
||||
this.fixups = Tinter.calcSvgFixups([target], this.props.forceColors);
|
||||
// console.log("TintableSvg.onLoad for " + this.props.src);
|
||||
this.fixups = Tinter.calcSvgFixups([event.target]);
|
||||
Tinter.applySvgFixups(this.fixups);
|
||||
},
|
||||
|
||||
@ -83,7 +71,6 @@ var TintableSvg = React.createClass({
|
||||
height={this.props.height}
|
||||
onLoad={this.onLoad}
|
||||
tabIndex="-1"
|
||||
ref="svgContainer"
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
@ -85,8 +85,8 @@ export default React.createClass({
|
||||
_getDisplayedGroups(userGroups, relatedGroups) {
|
||||
let displayedGroups = userGroups || [];
|
||||
if (relatedGroups && relatedGroups.length > 0) {
|
||||
displayedGroups = displayedGroups.filter((groupId) => {
|
||||
return relatedGroups.includes(groupId);
|
||||
displayedGroups = relatedGroups.filter((groupId) => {
|
||||
return displayedGroups.includes(groupId);
|
||||
});
|
||||
} else {
|
||||
displayedGroups = [];
|
||||
|
@ -70,6 +70,7 @@ const EntityTile = React.createClass({
|
||||
onClick: PropTypes.func,
|
||||
suppressOnHover: PropTypes.bool,
|
||||
showPresence: PropTypes.bool,
|
||||
subtextLabel: PropTypes.string,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
@ -129,6 +130,9 @@ const EntityTile = React.createClass({
|
||||
presenceState={this.props.presenceState} />;
|
||||
nameClasses += ' mx_EntityTile_name_hover';
|
||||
}
|
||||
if (this.props.subtextLabel) {
|
||||
presenceLabel = <span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>;
|
||||
}
|
||||
nameEl = (
|
||||
<div className="mx_EntityTile_details">
|
||||
<EmojiText element="div" className={nameClasses} dir="auto">
|
||||
@ -137,6 +141,15 @@ const EntityTile = React.createClass({
|
||||
{presenceLabel}
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.subtextLabel) {
|
||||
nameEl = (
|
||||
<div className="mx_EntityTile_details">
|
||||
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">
|
||||
{name}
|
||||
</EmojiText>
|
||||
<span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
nameEl = (
|
||||
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">{ name }</EmojiText>
|
||||
|
@ -42,6 +42,7 @@ import AccessibleButton from '../elements/AccessibleButton';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import MultiInviter from "../../../utils/MultiInviter";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
module.exports = withMatrixClient(React.createClass({
|
||||
displayName: 'MemberInfo',
|
||||
@ -889,11 +890,16 @@ module.exports = withMatrixClient(React.createClass({
|
||||
let presenceState;
|
||||
let presenceLastActiveAgo;
|
||||
let presenceCurrentlyActive;
|
||||
let statusMessage;
|
||||
|
||||
if (this.props.member.user) {
|
||||
presenceState = this.props.member.user.presence;
|
||||
presenceLastActiveAgo = this.props.member.user.lastActiveAgo;
|
||||
presenceCurrentlyActive = this.props.member.user.currentlyActive;
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
statusMessage = this.props.member.user._unstable_statusMessage;
|
||||
}
|
||||
}
|
||||
|
||||
const room = this.props.matrixClient.getRoom(this.props.member.roomId);
|
||||
@ -915,6 +921,11 @@ module.exports = withMatrixClient(React.createClass({
|
||||
presenceState={presenceState} />;
|
||||
}
|
||||
|
||||
let statusLabel = null;
|
||||
if (statusMessage) {
|
||||
statusLabel = <span className="mx_MemberInfo_statusMessage">{ statusMessage }</span>;
|
||||
}
|
||||
|
||||
let roomMemberDetails = null;
|
||||
if (this.props.member.roomId) { // is in room
|
||||
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||
@ -931,6 +942,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||
</div>
|
||||
<div className="mx_MemberInfo_profileField">
|
||||
{presenceLabel}
|
||||
{statusLabel}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||
|
||||
'use strict';
|
||||
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@ -85,6 +87,11 @@ module.exports = React.createClass({
|
||||
const active = -1;
|
||||
const presenceState = member.user ? member.user.presence : null;
|
||||
|
||||
let statusMessage = null;
|
||||
if (member.user && SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
statusMessage = member.user._unstable_statusMessage;
|
||||
}
|
||||
|
||||
const av = (
|
||||
<MemberAvatar member={member} width={36} height={36} />
|
||||
);
|
||||
@ -106,7 +113,9 @@ module.exports = React.createClass({
|
||||
presenceLastTs={member.user ? member.user.lastPresenceTs : 0}
|
||||
presenceCurrentlyActive={member.user ? member.user.currentlyActive : false}
|
||||
avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick}
|
||||
name={name} powerStatus={powerStatus} showPresence={this.props.showPresence} />
|
||||
name={name} powerStatus={powerStatus} showPresence={this.props.showPresence}
|
||||
subtextLabel={statusMessage}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -291,7 +291,7 @@ export default class MessageComposer extends React.Component {
|
||||
|
||||
render() {
|
||||
const uploadInputStyle = {display: 'none'};
|
||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
||||
|
||||
@ -300,7 +300,7 @@ export default class MessageComposer extends React.Component {
|
||||
if (this.state.me) {
|
||||
controls.push(
|
||||
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
||||
<MemberAvatar member={this.state.me} width={24} height={24} />
|
||||
<MemberStatusMessageAvatar member={this.state.me} width={24} height={24} />
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
@ -349,6 +349,34 @@ export default class MessageComposer extends React.Component {
|
||||
const canSendMessages = !this.state.tombstone &&
|
||||
this.props.room.maySendMessage();
|
||||
|
||||
// TODO: Remove temporary logging for riot-web#7838
|
||||
// Note: we rip apart the power level event ourselves because we don't want to
|
||||
// log too much data about it - just the bits we care about. Many of the variables
|
||||
// logged here are to help figure out where in the stack the 'cannot post in room'
|
||||
// warning is coming from. This means logging various numbers from the PL event to
|
||||
// verify RoomState._maySendEventOfType is doing the right thing.
|
||||
const room = this.props.room;
|
||||
const plEvent = room.currentState.getStateEvents('m.room.power_levels', '');
|
||||
let plEventString = "<no power level event>";
|
||||
if (plEvent) {
|
||||
const content = plEvent.getContent();
|
||||
if (!content) {
|
||||
plEventString = "<no event content>";
|
||||
} else {
|
||||
const stringifyFalsey = (v) => v === null ? '<null>' : (v === undefined ? '<undefined>' : v);
|
||||
const actualUserPl = stringifyFalsey(content.users ? content.users[room.myUserId] : "<no users in content>");
|
||||
const usersPl = stringifyFalsey(content.users_default);
|
||||
const actualEventPl = stringifyFalsey(content.events ? content.events['m.room.message'] : "<no events in content>");
|
||||
const eventPl = stringifyFalsey(content.events_default);
|
||||
plEventString = `actualUserPl=${actualUserPl} defaultUserPl=${usersPl} actualEventPl=${actualEventPl} defaultEventPl=${eventPl}`;
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
`[riot-web#7838] renderComposer() hasTombstone=${!!this.state.tombstone} maySendMessage=${room.maySendMessage()}` +
|
||||
` myMembership=${room.getMyMembership()} maySendEvent=${room.currentState.maySendEvent('m.room.message', room.myUserId)}` +
|
||||
` myUserId=${room.myUserId} roomId=${room.roomId} hasPlEvent=${!!plEvent} powerLevels='${plEventString}'`
|
||||
);
|
||||
|
||||
if (canSendMessages) {
|
||||
// This also currently includes the call buttons. Really we should
|
||||
// check separately for whether we can call, but this is slightly
|
||||
@ -425,6 +453,8 @@ export default class MessageComposer extends React.Component {
|
||||
</div>
|
||||
</div>);
|
||||
} else {
|
||||
// TODO: Remove temporary logging for riot-web#7838
|
||||
console.log("[riot-web#7838] Falling back to showing cannot post in room error");
|
||||
controls.push(
|
||||
<div key="controls_error" className="mx_MessageComposer_noperm_error">
|
||||
{ _t('You do not have permission to post to this room') }
|
||||
|
@ -86,6 +86,7 @@ module.exports = React.createClass({
|
||||
incomingCallTag: null,
|
||||
incomingCall: null,
|
||||
selectedTags: [],
|
||||
hover: false,
|
||||
};
|
||||
},
|
||||
|
||||
@ -294,6 +295,17 @@ module.exports = React.createClass({
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
onMouseEnter: function(ev) {
|
||||
this.setState({hover: true});
|
||||
},
|
||||
|
||||
onMouseLeave: function(ev) {
|
||||
this.setState({hover: false});
|
||||
|
||||
// Refresh the room list just in case the user missed something.
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
_delayedRefreshRoomList: new rate_limited_func(function() {
|
||||
this.refreshRoomList();
|
||||
}, 500),
|
||||
@ -346,6 +358,11 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
refreshRoomList: function() {
|
||||
if (this.state.hover) {
|
||||
// Don't re-sort the list if we're hovering over the list
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: ideally we'd calculate this once at start, and then maintain
|
||||
// any changes to it incrementally, updating the appropriate sublists
|
||||
// as needed.
|
||||
@ -693,9 +710,10 @@ module.exports = React.createClass({
|
||||
const subListComponents = this._mapSubListProps(subLists);
|
||||
|
||||
return (
|
||||
<div ref={this._collectResizeContainer} className="mx_RoomList">
|
||||
<div ref={this._collectResizeContainer} className="mx_RoomList"
|
||||
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
{ subListComponents }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
@ -19,13 +19,76 @@ import PropTypes from "prop-types";
|
||||
import sdk from "../../../index";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Modal from "../../../Modal";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
|
||||
export default class RoomRecoveryReminder extends React.PureComponent {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
showKeyBackupDialog = () => {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
loading: true,
|
||||
error: null,
|
||||
unverifiedDevice: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._loadBackupStatus();
|
||||
}
|
||||
|
||||
async _loadBackupStatus() {
|
||||
let backupSigStatus;
|
||||
try {
|
||||
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||
backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo);
|
||||
} catch (e) {
|
||||
console.log("Unable to fetch key backup status", e);
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: e,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let unverifiedDevice;
|
||||
for (const sig of backupSigStatus.sigs) {
|
||||
if (!sig.device.isVerified()) {
|
||||
unverifiedDevice = sig.device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
loading: false,
|
||||
unverifiedDevice,
|
||||
});
|
||||
}
|
||||
|
||||
showSetupDialog = () => {
|
||||
if (this.state.unverifiedDevice) {
|
||||
// A key backup exists for this account, but the creating device is not
|
||||
// verified, so we'll show the device verify dialog.
|
||||
// TODO: Should change to a restore key backup flow that checks the recovery
|
||||
// passphrase while at the same time also cross-signing the device as well in
|
||||
// a single flow (for cases where a key backup exists but the backup creating
|
||||
// device is unverified). Since we don't have that yet, we'll look for an
|
||||
// unverified device and verify it. Note that this means we won't restore
|
||||
// keys yet; instead we'll only trust the backup for sending our own new keys
|
||||
// to it.
|
||||
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
|
||||
Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, {
|
||||
userId: MatrixClientPeg.get().credentials.userId,
|
||||
device: this.state.unverifiedDevice,
|
||||
onFinished: this.props.onFinished,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// The default case assumes that a key backup doesn't exist for this account, so
|
||||
// we'll show the create key backup flow.
|
||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||
import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
|
||||
{
|
||||
@ -46,29 +109,51 @@ export default class RoomRecoveryReminder extends React.PureComponent {
|
||||
this.props.onFinished(false);
|
||||
},
|
||||
onSetup: () => {
|
||||
this.showKeyBackupDialog();
|
||||
this.showSetupDialog();
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onSetupClick = () => {
|
||||
this.showKeyBackupDialog();
|
||||
this.showSetupDialog();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
||||
|
||||
let body;
|
||||
if (this.state.error) {
|
||||
body = <div className="error">
|
||||
{_t("Unable to load key backup status")}
|
||||
</div>;
|
||||
} else if (this.state.unverifiedDevice) {
|
||||
// A key backup exists for this account, but the creating device is not
|
||||
// verified.
|
||||
body = _t(
|
||||
"To view your secure message history and ensure you can view new " +
|
||||
"messages on future devices, set up Secure Message Recovery.",
|
||||
);
|
||||
} else {
|
||||
// The default case assumes that a key backup doesn't exist for this account.
|
||||
// (This component doesn't currently check that itself.)
|
||||
body = _t(
|
||||
"If you log out or use another device, you'll lose your " +
|
||||
"secure message history. To prevent this, set up Secure " +
|
||||
"Message Recovery.",
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomRecoveryReminder">
|
||||
<div className="mx_RoomRecoveryReminder_header">{_t(
|
||||
"Secure Message Recovery",
|
||||
)}</div>
|
||||
<div className="mx_RoomRecoveryReminder_body">{_t(
|
||||
"If you log out or use another device, you'll lose your " +
|
||||
"secure message history. To prevent this, set up Secure " +
|
||||
"Message Recovery.",
|
||||
)}</div>
|
||||
<div className="mx_RoomRecoveryReminder_body">{body}</div>
|
||||
<div className="mx_RoomRecoveryReminder_buttons">
|
||||
<AccessibleButton className="mx_RoomRecoveryReminder_button mx_RoomRecoveryReminder_secondary"
|
||||
onClick={this.onDontAskAgainClick}>
|
||||
|
@ -30,6 +30,7 @@ import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import ActiveRoomObserver from '../../../ActiveRoomObserver';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomTile',
|
||||
@ -251,6 +252,17 @@ module.exports = React.createClass({
|
||||
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
|
||||
const badges = notifBadges || mentionBadges;
|
||||
|
||||
const isJoined = this.props.room.getMyMembership() === "join";
|
||||
const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2;
|
||||
let subtext = null;
|
||||
if (!isInvite && isJoined && looksLikeDm && SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
const selfId = MatrixClientPeg.get().getUserId();
|
||||
const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0];
|
||||
if (otherMember && otherMember.user && otherMember.user._unstable_statusMessage) {
|
||||
subtext = otherMember.user._unstable_statusMessage;
|
||||
}
|
||||
}
|
||||
|
||||
const classes = classNames({
|
||||
'mx_RoomTile': true,
|
||||
'mx_RoomTile_selected': this.state.selected,
|
||||
@ -261,6 +273,7 @@ module.exports = React.createClass({
|
||||
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed,
|
||||
'mx_RoomTile_noBadges': !badges,
|
||||
'mx_RoomTile_transparent': this.props.transparent,
|
||||
'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
|
||||
});
|
||||
|
||||
const avatarClasses = classNames({
|
||||
@ -286,6 +299,7 @@ module.exports = React.createClass({
|
||||
|
||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
let label;
|
||||
let subtextLabel;
|
||||
let tooltip;
|
||||
if (!this.props.collapsed) {
|
||||
const nameClasses = classNames({
|
||||
@ -294,6 +308,8 @@ module.exports = React.createClass({
|
||||
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
||||
});
|
||||
|
||||
subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null;
|
||||
|
||||
if (this.state.selected) {
|
||||
const nameSelected = <EmojiText>{ name }</EmojiText>;
|
||||
|
||||
@ -337,9 +353,14 @@ module.exports = React.createClass({
|
||||
{ dmIndicator }
|
||||
</div>
|
||||
</div>
|
||||
{ label }
|
||||
{ contextMenuButton }
|
||||
{ badge }
|
||||
<div className="mx_RoomTile_nameContainer">
|
||||
<div className="mx_RoomTile_labelContainer">
|
||||
{ label }
|
||||
{ subtextLabel }
|
||||
</div>
|
||||
{ contextMenuButton }
|
||||
{ badge }
|
||||
</div>
|
||||
{ /* { incomingCallBox } */ }
|
||||
{ tooltip }
|
||||
</AccessibleButton>;
|
||||
|
@ -250,11 +250,14 @@
|
||||
"A word by itself is easy to guess": "A word by itself is easy to guess",
|
||||
"Names and surnames by themselves are easy to guess": "Names and surnames by themselves are easy to guess",
|
||||
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
|
||||
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
|
||||
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
|
||||
"There was an error joining the room": "There was an error joining the room",
|
||||
"Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.",
|
||||
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
|
||||
"Failed to join room": "Failed to join room",
|
||||
"Message Pinning": "Message Pinning",
|
||||
"Custom user status messages": "Custom user status messages",
|
||||
"Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view",
|
||||
"Backup of encryption keys to server": "Backup of encryption keys to server",
|
||||
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing",
|
||||
@ -563,8 +566,9 @@
|
||||
"You are trying to access a room.": "You are trying to access a room.",
|
||||
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
|
||||
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
|
||||
"Secure Message Recovery": "Secure Message Recovery",
|
||||
"To view your secure message history and ensure you can view new messages on future devices, set up Secure Message Recovery.": "To view your secure message history and ensure you can view new messages on future devices, set up Secure Message Recovery.",
|
||||
"If you log out or use another device, you'll lose your secure message history. To prevent this, set up Secure Message Recovery.": "If you log out or use another device, you'll lose your secure message history. To prevent this, set up Secure Message Recovery.",
|
||||
"Secure Message Recovery": "Secure Message Recovery",
|
||||
"Don't ask again": "Don't ask again",
|
||||
"Set up": "Set up",
|
||||
"To change the room's avatar, you must be a": "To change the room's avatar, you must be a",
|
||||
@ -888,6 +892,7 @@
|
||||
"What GitHub issue are these logs for?": "What GitHub issue are these logs for?",
|
||||
"Notes:": "Notes:",
|
||||
"Send logs": "Send logs",
|
||||
"Unable to load commit detail: %(msg)s": "Unable to load commit detail: %(msg)s",
|
||||
"Unavailable": "Unavailable",
|
||||
"Changelog": "Changelog",
|
||||
"Create a new chat or reuse an existing one": "Create a new chat or reuse an existing one",
|
||||
@ -1064,6 +1069,8 @@
|
||||
"Forget": "Forget",
|
||||
"Low Priority": "Low Priority",
|
||||
"Direct Chat": "Direct Chat",
|
||||
"Set a new status...": "Set a new status...",
|
||||
"Clear status": "Clear status",
|
||||
"View Community": "View Community",
|
||||
"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.",
|
||||
|
@ -83,6 +83,12 @@ export const SETTINGS = {
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_custom_status": {
|
||||
isFeature: true,
|
||||
displayName: _td("Custom user status messages"),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_lazyloading": {
|
||||
isFeature: true,
|
||||
displayName: _td("Increase performance by only loading room members on first view"),
|
||||
|
@ -52,6 +52,8 @@ _td("This is similar to a commonly used password");
|
||||
_td("A word by itself is easy to guess");
|
||||
_td("Names and surnames by themselves are easy to guess");
|
||||
_td("Common names and surnames are easy to guess");
|
||||
_td("Straight rows of keys are easy to guess");
|
||||
_td("Short keyboard patterns are easy to guess");
|
||||
|
||||
/**
|
||||
* Wrapper around zxcvbn password strength estimation
|
||||
|
@ -54,7 +54,7 @@ describe('DecryptionFailureTracker', function() {
|
||||
// Immediately track the newest failures
|
||||
tracker.trackFailures();
|
||||
|
||||
expect(count).toNotBe(0, 'should track a failure for an event that failed decryption');
|
||||
expect(count).not.toBe(0, 'should track a failure for an event that failed decryption');
|
||||
|
||||
done();
|
||||
});
|
||||
|
@ -185,21 +185,21 @@ describe('GroupView', function() {
|
||||
const avatar = ReactTestUtils.findRenderedComponentWithType(root, sdk.getComponent('avatars.GroupAvatar'));
|
||||
const img = ReactTestUtils.findRenderedDOMComponentWithTag(avatar, 'img');
|
||||
const avatarImgElement = ReactDOM.findDOMNode(img);
|
||||
expect(avatarImgElement).toExist();
|
||||
expect(avatarImgElement.src).toInclude(
|
||||
expect(avatarImgElement).toBeTruthy();
|
||||
expect(avatarImgElement.src).toContain(
|
||||
'https://my.home.server/_matrix/media/v1/thumbnail/' +
|
||||
'someavatarurl?width=48&height=48&method=crop',
|
||||
);
|
||||
|
||||
const name = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_header_name');
|
||||
const nameElement = ReactDOM.findDOMNode(name);
|
||||
expect(nameElement).toExist();
|
||||
expect(nameElement.innerText).toInclude('The name of a community');
|
||||
expect(nameElement.innerText).toInclude(groupId);
|
||||
expect(nameElement).toBeTruthy();
|
||||
expect(nameElement.innerText).toContain('The name of a community');
|
||||
expect(nameElement.innerText).toContain(groupId);
|
||||
|
||||
const shortDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_header_shortDesc');
|
||||
const shortDescElement = ReactDOM.findDOMNode(shortDesc);
|
||||
expect(shortDescElement).toExist();
|
||||
expect(shortDescElement).toBeTruthy();
|
||||
expect(shortDescElement.innerText).toBe('This is a community');
|
||||
});
|
||||
|
||||
@ -219,7 +219,7 @@ describe('GroupView', function() {
|
||||
|
||||
const longDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc');
|
||||
const longDescElement = ReactDOM.findDOMNode(longDesc);
|
||||
expect(longDescElement).toExist();
|
||||
expect(longDescElement).toBeTruthy();
|
||||
expect(longDescElement.innerText).toBe('This is a LONG description.');
|
||||
expect(longDescElement.innerHTML).toBe('<div dir="auto">This is a <b>LONG</b> description.</div>');
|
||||
});
|
||||
@ -239,7 +239,7 @@ describe('GroupView', function() {
|
||||
const placeholder = ReactTestUtils
|
||||
.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc_placeholder');
|
||||
const placeholderElement = ReactDOM.findDOMNode(placeholder);
|
||||
expect(placeholderElement).toExist();
|
||||
expect(placeholderElement).toBeTruthy();
|
||||
});
|
||||
|
||||
httpBackend
|
||||
@ -258,15 +258,15 @@ describe('GroupView', function() {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
const longDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc');
|
||||
const longDescElement = ReactDOM.findDOMNode(longDesc);
|
||||
expect(longDescElement).toExist();
|
||||
expect(longDescElement).toBeTruthy();
|
||||
|
||||
expect(longDescElement.innerHTML).toInclude('<h1>This is a more complicated group page</h1>');
|
||||
expect(longDescElement.innerHTML).toInclude('<p>With paragraphs</p>');
|
||||
expect(longDescElement.innerHTML).toInclude('<ul>');
|
||||
expect(longDescElement.innerHTML).toInclude('<li>And lists!</li>');
|
||||
expect(longDescElement.innerHTML).toContain('<h1>This is a more complicated group page</h1>');
|
||||
expect(longDescElement.innerHTML).toContain('<p>With paragraphs</p>');
|
||||
expect(longDescElement.innerHTML).toContain('<ul>');
|
||||
expect(longDescElement.innerHTML).toContain('<li>And lists!</li>');
|
||||
|
||||
const imgSrc = "https://my.home.server/_matrix/media/v1/thumbnail/someimageurl?width=800&height=600";
|
||||
expect(longDescElement.innerHTML).toInclude('<img src="' + imgSrc + '">');
|
||||
expect(longDescElement.innerHTML).toContain('<img src="' + imgSrc + '">');
|
||||
});
|
||||
|
||||
httpBackend
|
||||
@ -285,11 +285,11 @@ describe('GroupView', function() {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
const longDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc');
|
||||
const longDescElement = ReactDOM.findDOMNode(longDesc);
|
||||
expect(longDescElement).toExist();
|
||||
expect(longDescElement).toBeTruthy();
|
||||
|
||||
// If this fails, the URL could be in an img `src`, which is what we care about but
|
||||
// there's no harm in keeping this simple and checking the entire HTML string.
|
||||
expect(longDescElement.innerHTML).toExclude('evilimageurl');
|
||||
expect(longDescElement.innerHTML).not.toContain('evilimageurl');
|
||||
});
|
||||
|
||||
httpBackend
|
||||
@ -308,7 +308,7 @@ describe('GroupView', function() {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
const roomDetailList = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_RoomDetailList');
|
||||
const roomDetailListElement = ReactDOM.findDOMNode(roomDetailList);
|
||||
expect(roomDetailListElement).toExist();
|
||||
expect(roomDetailListElement).toBeTruthy();
|
||||
});
|
||||
|
||||
httpBackend.when('GET', '/groups/' + groupIdEncoded + '/summary').respond(200, summaryResponse);
|
||||
@ -325,7 +325,7 @@ describe('GroupView', function() {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
const roomDetailList = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_RoomDetailList');
|
||||
const roomDetailListElement = ReactDOM.findDOMNode(roomDetailList);
|
||||
expect(roomDetailListElement).toExist();
|
||||
expect(roomDetailListElement).toBeTruthy();
|
||||
|
||||
const roomDetailListRoomName = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||
root,
|
||||
@ -333,7 +333,7 @@ describe('GroupView', function() {
|
||||
);
|
||||
const roomDetailListRoomNameElement = ReactDOM.findDOMNode(roomDetailListRoomName);
|
||||
|
||||
expect(roomDetailListRoomNameElement).toExist();
|
||||
expect(roomDetailListRoomNameElement).toBeTruthy();
|
||||
expect(roomDetailListRoomNameElement.innerText).toEqual('Some room name');
|
||||
});
|
||||
|
||||
@ -364,7 +364,7 @@ describe('GroupView', function() {
|
||||
const prom = waitForUpdate(groupView, 3).then(() => {
|
||||
const shortDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_header_shortDesc');
|
||||
const shortDescElement = ReactDOM.findDOMNode(shortDesc);
|
||||
expect(shortDescElement).toExist();
|
||||
expect(shortDescElement).toBeTruthy();
|
||||
expect(shortDescElement.innerText).toBe('This is a community');
|
||||
});
|
||||
|
||||
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const jest = require('jest-mock');
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const ReactTestUtils = require('react-addons-test-utils');
|
||||
@ -87,8 +88,8 @@ describe('Registration', function() {
|
||||
});
|
||||
|
||||
it('should NOT track a referral following successful registration of a non-team member', function(done) {
|
||||
const onLoggedIn = expect.createSpy().andCall(function(creds, teamToken) {
|
||||
expect(teamToken).toNotExist();
|
||||
const onLoggedIn = jest.fn(function(creds, teamToken) {
|
||||
expect(teamToken).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -83,8 +83,8 @@ describe('InteractiveAuthDialog', function() {
|
||||
submitNode = node;
|
||||
}
|
||||
}
|
||||
expect(passwordNode).toExist();
|
||||
expect(submitNode).toExist();
|
||||
expect(passwordNode).toBeTruthy();
|
||||
expect(submitNode).toBeTruthy();
|
||||
|
||||
// submit should be disabled
|
||||
expect(submitNode.disabled).toBe(true);
|
||||
|
@ -114,7 +114,7 @@ describe("GroupMemberList", function() {
|
||||
|
||||
const memberList = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList_joined");
|
||||
const memberListElement = ReactDOM.findDOMNode(memberList);
|
||||
expect(memberListElement).toExist();
|
||||
expect(memberListElement).toBeTruthy();
|
||||
expect(memberListElement.innerText).toBe("Test");
|
||||
});
|
||||
|
||||
@ -134,7 +134,7 @@ describe("GroupMemberList", function() {
|
||||
|
||||
const memberList = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList_joined");
|
||||
const memberListElement = ReactDOM.findDOMNode(memberList);
|
||||
expect(memberListElement).toExist();
|
||||
expect(memberListElement).toBeTruthy();
|
||||
expect(memberListElement.innerText).toBe("Failed to load group members");
|
||||
});
|
||||
|
||||
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const jest = require('jest-mock');
|
||||
const React = require('react');
|
||||
const ReactDOM = require("react-dom");
|
||||
const ReactTestUtils = require('react-addons-test-utils');
|
||||
@ -55,14 +56,14 @@ function doInputEmail(inputEmail, onTeamSelected) {
|
||||
}
|
||||
|
||||
function expectTeamSelectedFromEmailInput(inputEmail, expectedTeam) {
|
||||
const onTeamSelected = expect.createSpy();
|
||||
const onTeamSelected = jest.fn();
|
||||
doInputEmail(inputEmail, onTeamSelected);
|
||||
|
||||
expect(onTeamSelected).toHaveBeenCalledWith(expectedTeam);
|
||||
}
|
||||
|
||||
function expectSupportFromEmailInput(inputEmail, isSupportShown) {
|
||||
const onTeamSelected = expect.createSpy();
|
||||
const onTeamSelected = jest.fn();
|
||||
const res = doInputEmail(inputEmail, onTeamSelected);
|
||||
|
||||
expect(res.state.showSupportEmail).toBe(isSupportShown);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactTestUtils from 'react-addons-test-utils';
|
||||
import ReactDOM from 'react-dom';
|
||||
import expect, {createSpy} from 'expect';
|
||||
import expect from 'expect';
|
||||
import sinon from 'sinon';
|
||||
import Promise from 'bluebird';
|
||||
import * as testUtils from '../../../test-utils';
|
||||
|
@ -69,7 +69,7 @@ describe('RoomList', () => {
|
||||
ReactTestUtils.findRenderedComponentWithType(root, RoomList);
|
||||
|
||||
movingRoom = createRoom({name: 'Moving room'});
|
||||
expect(movingRoom.roomId).toNotBe(null);
|
||||
expect(movingRoom.roomId).not.toBe(null);
|
||||
|
||||
// Mock joined member
|
||||
myMember = new RoomMember(movingRoomId, myUserId);
|
||||
@ -139,7 +139,7 @@ describe('RoomList', () => {
|
||||
throw err;
|
||||
}
|
||||
|
||||
expect(expectedRoomTile).toExist();
|
||||
expect(expectedRoomTile).toBeTruthy();
|
||||
expect(expectedRoomTile.props.room).toBe(room);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import expect, {createSpy} from 'expect';
|
||||
import expect from 'expect';
|
||||
import jest from 'jest-mock';
|
||||
import Promise from 'bluebird';
|
||||
import * as testUtils from '../../../test-utils';
|
||||
import sdk from 'matrix-react-sdk';
|
||||
@ -18,12 +19,12 @@ describe('RoomSettings', () => {
|
||||
|
||||
function expectSentStateEvent(roomId, eventType, expectedEventContent) {
|
||||
let found = false;
|
||||
for (const call of client.sendStateEvent.calls) {
|
||||
for (const call of client.sendStateEvent.mock.calls) {
|
||||
const [
|
||||
actualRoomId,
|
||||
actualEventType,
|
||||
actualEventContent,
|
||||
] = call.arguments.slice(0, 3);
|
||||
] = call.slice(0, 3);
|
||||
|
||||
if (roomId === actualRoomId && actualEventType === eventType) {
|
||||
expect(actualEventContent).toEqual(expectedEventContent);
|
||||
@ -40,20 +41,20 @@ describe('RoomSettings', () => {
|
||||
client = MatrixClientPeg.get();
|
||||
client.credentials = {userId: '@me:domain.com'};
|
||||
|
||||
client.setRoomName = createSpy().andReturn(Promise.resolve());
|
||||
client.setRoomTopic = createSpy().andReturn(Promise.resolve());
|
||||
client.setRoomDirectoryVisibility = createSpy().andReturn(Promise.resolve());
|
||||
client.setRoomName = jest.fn().mockReturnValue(Promise.resolve());
|
||||
client.setRoomTopic = jest.fn().mockReturnValue(Promise.resolve());
|
||||
client.setRoomDirectoryVisibility = jest.fn().mockReturnValue(Promise.resolve());
|
||||
|
||||
// Covers any room state event (e.g. name, avatar, topic)
|
||||
client.sendStateEvent = createSpy().andReturn(Promise.resolve());
|
||||
client.sendStateEvent = jest.fn().mockReturnValue(Promise.resolve());
|
||||
|
||||
// Covers room tagging
|
||||
client.setRoomTag = createSpy().andReturn(Promise.resolve());
|
||||
client.deleteRoomTag = createSpy().andReturn(Promise.resolve());
|
||||
client.setRoomTag = jest.fn().mockReturnValue(Promise.resolve());
|
||||
client.deleteRoomTag = jest.fn().mockReturnValue(Promise.resolve());
|
||||
|
||||
// Covers any setting in the SettingsStore
|
||||
// (including local client settings not stored via matrix)
|
||||
SettingsStore.setValue = createSpy().andReturn(Promise.resolve());
|
||||
SettingsStore.setValue = jest.fn().mockReturnValue(Promise.resolve());
|
||||
|
||||
parentDiv = document.createElement('div');
|
||||
document.body.appendChild(parentDiv);
|
||||
@ -83,9 +84,9 @@ describe('RoomSettings', () => {
|
||||
|
||||
it('should not set when no setting is changed', (done) => {
|
||||
roomSettings.save().then(() => {
|
||||
expect(client.sendStateEvent).toNotHaveBeenCalled();
|
||||
expect(client.setRoomTag).toNotHaveBeenCalled();
|
||||
expect(client.deleteRoomTag).toNotHaveBeenCalled();
|
||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||
expect(client.setRoomTag).not.toHaveBeenCalled();
|
||||
expect(client.deleteRoomTag).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -93,7 +94,7 @@ describe('RoomSettings', () => {
|
||||
// XXX: Apparently we do call SettingsStore.setValue
|
||||
xit('should not settings via the SettingsStore when no setting is changed', (done) => {
|
||||
roomSettings.save().then(() => {
|
||||
expect(SettingsStore.setValue).toNotHaveBeenCalled();
|
||||
expect(SettingsStore.setValue).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -103,7 +104,7 @@ describe('RoomSettings', () => {
|
||||
roomSettings.setName(name);
|
||||
|
||||
roomSettings.save().then(() => {
|
||||
expect(client.setRoomName.calls[0].arguments.slice(0, 2))
|
||||
expect(client.setRoomName.mock.calls[0].slice(0, 2))
|
||||
.toEqual(['!DdJkzRliezrwpNebLk:matrix.org', name]);
|
||||
|
||||
done();
|
||||
@ -115,7 +116,7 @@ describe('RoomSettings', () => {
|
||||
roomSettings.setTopic(topic);
|
||||
|
||||
roomSettings.save().then(() => {
|
||||
expect(client.setRoomTopic.calls[0].arguments.slice(0, 2))
|
||||
expect(client.setRoomTopic.mock.calls[0].slice(0, 2))
|
||||
.toEqual(['!DdJkzRliezrwpNebLk:matrix.org', topic]);
|
||||
|
||||
done();
|
||||
|
@ -39,7 +39,7 @@ describe('matrix-to', function() {
|
||||
it('should pick no candidate servers when the room is not found', function() {
|
||||
peg.get().getRoom = () => null;
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -50,7 +50,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -74,7 +74,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(3);
|
||||
expect(pickedServers[0]).toBe("pl_95");
|
||||
// we don't check the 2nd and 3rd servers because that is done by the next test
|
||||
@ -112,7 +112,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(3);
|
||||
expect(pickedServers[0]).toBe("first");
|
||||
expect(pickedServers[1]).toBe("second");
|
||||
@ -143,7 +143,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(3);
|
||||
expect(pickedServers[0]).toBe("first");
|
||||
expect(pickedServers[1]).toBe("second");
|
||||
@ -178,7 +178,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(3);
|
||||
});
|
||||
|
||||
@ -194,7 +194,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -210,7 +210,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -226,7 +226,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -242,7 +242,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -258,7 +258,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(1);
|
||||
expect(pickedServers[0]).toBe("example.org:8448");
|
||||
});
|
||||
@ -292,7 +292,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -325,7 +325,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(0);
|
||||
});
|
||||
|
||||
@ -358,7 +358,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(1);
|
||||
expect(pickedServers[0]).toEqual("evilcorp.com");
|
||||
});
|
||||
@ -392,7 +392,7 @@ describe('matrix-to', function() {
|
||||
};
|
||||
};
|
||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||
expect(pickedServers).toExist();
|
||||
expect(pickedServers).toBeTruthy();
|
||||
expect(pickedServers.length).toBe(1);
|
||||
expect(pickedServers[0]).toEqual("evilcorp.com");
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user