Merge remote-tracking branch 'origin/develop' into dbkr/add_finnish

This commit is contained in:
David Baker 2017-09-22 09:50:56 +01:00
commit 133e17c1db
20 changed files with 773 additions and 130 deletions

View File

@ -1,3 +1,50 @@
Changes in [0.12.6](https://github.com/vector-im/riot-web/releases/tag/v0.12.6) (2017-09-21)
============================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.12.5...v0.12.6)
* Use matrix-js-sdk v0.8.4 to fix build
Changes in [0.12.5](https://github.com/vector-im/riot-web/releases/tag/v0.12.5) (2017-09-21)
============================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.12.4...v0.12.5)
* Use react-sdk v0.10.5 to fix build
Changes in [0.12.4](https://github.com/vector-im/riot-web/releases/tag/v0.12.4) (2017-09-20)
============================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.12.4-rc.1...v0.12.4)
* No changes
Changes in [0.12.4-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.12.4-rc.1) (2017-09-19)
======================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.12.3...v0.12.4-rc.1)
* Fix test for new behaviour of 'joining' flag
[\#5053](https://github.com/vector-im/riot-web/pull/5053)
* fix really dumb blunder/typo preventing system from going to sleep.
[\#5080](https://github.com/vector-im/riot-web/pull/5080)
* T3chguy/devtools
[\#4735](https://github.com/vector-im/riot-web/pull/4735)
* CSS for unignore button in UserSettings
[\#5042](https://github.com/vector-im/riot-web/pull/5042)
* Fix alias on home page for identity room
[\#5044](https://github.com/vector-im/riot-web/pull/5044)
* generic contextual menu for tooltip/responses
[\#4989](https://github.com/vector-im/riot-web/pull/4989)
* Update from Weblate.
[\#5018](https://github.com/vector-im/riot-web/pull/5018)
* Avoid re-rendering RoomList on room switch
[\#5015](https://github.com/vector-im/riot-web/pull/5015)
* Fix menu on change keyboard language issue #4345
[\#4623](https://github.com/vector-im/riot-web/pull/4623)
* Make isInvite default to false
[\#4999](https://github.com/vector-im/riot-web/pull/4999)
* Revert "Implement sticky date separators"
[\#4991](https://github.com/vector-im/riot-web/pull/4991)
* Implement sticky date separators
[\#4939](https://github.com/vector-im/riot-web/pull/4939)
Changes in [0.12.3](https://github.com/vector-im/riot-web/releases/tag/v0.12.3) (2017-09-06) Changes in [0.12.3](https://github.com/vector-im/riot-web/releases/tag/v0.12.3) (2017-09-06)
============================================================================================ ============================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.12.3-rc.3...v0.12.3) [Full Changelog](https://github.com/vector-im/riot-web/compare/v0.12.3-rc.3...v0.12.3)

View File

@ -2,7 +2,7 @@
"name": "riot-web", "name": "riot-web",
"productName": "Riot", "productName": "Riot",
"main": "src/electron-main.js", "main": "src/electron-main.js",
"version": "0.12.3", "version": "0.12.6",
"description": "A feature-rich client for Matrix.org", "description": "A feature-rich client for Matrix.org",
"author": "Vector Creations Ltd.", "author": "Vector Creations Ltd.",
"dependencies": { "dependencies": {

View File

@ -84,7 +84,7 @@ let powerSaveBlockerId;
electron.ipcMain.on('app_onAction', function(ev, payload) { electron.ipcMain.on('app_onAction', function(ev, payload) {
switch (payload.action) { switch (payload.action) {
case 'call_state': case 'call_state':
if (powerSaveBlockerId && powerSaveBlockerId.isStarted(powerSaveBlockerId)) { if (powerSaveBlockerId && electron.powerSaveBlocker.isStarted(powerSaveBlockerId)) {
if (payload.state === 'ended') { if (payload.state === 'ended') {
electron.powerSaveBlocker.stop(powerSaveBlockerId); electron.powerSaveBlocker.stop(powerSaveBlockerId);
} }

View File

@ -2,7 +2,7 @@
"name": "riot-web", "name": "riot-web",
"productName": "Riot", "productName": "Riot",
"main": "electron_app/src/electron-main.js", "main": "electron_app/src/electron-main.js",
"version": "0.12.3", "version": "0.12.6",
"description": "A feature-rich client for Matrix.org", "description": "A feature-rich client for Matrix.org",
"author": "Vector Creations Ltd.", "author": "Vector Creations Ltd.",
"repository": { "repository": {
@ -47,7 +47,7 @@
"lint": "eslint src/", "lint": "eslint src/",
"lintall": "eslint src/ test/", "lintall": "eslint src/ test/",
"clean": "rimraf lib webapp electron_app/dist", "clean": "rimraf lib webapp electron_app/dist",
"prepublish": "npm run build:compile", "prepublish": "npm run clean && npm run build:compile",
"test": "karma start --single-run=true --autoWatch=false --browsers ChromeHeadless", "test": "karma start --single-run=true --autoWatch=false --browsers ChromeHeadless",
"test-multi": "karma start" "test-multi": "karma start"
}, },
@ -66,8 +66,8 @@
"gfm.css": "^1.1.1", "gfm.css": "^1.1.1",
"highlight.js": "^9.0.0", "highlight.js": "^9.0.0",
"linkifyjs": "^2.1.3", "linkifyjs": "^2.1.3",
"matrix-js-sdk": "0.8.2", "matrix-js-sdk": "0.8.4",
"matrix-react-sdk": "0.10.3", "matrix-react-sdk": "0.10.6",
"modernizr": "^3.1.0", "modernizr": "^3.1.0",
"pako": "^1.0.5", "pako": "^1.0.5",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",

View File

@ -249,7 +249,7 @@
<span class="mx_HomePage_desc">_t("Implementing VoIP services with Matrix")</span> <span class="mx_HomePage_desc">_t("Implementing VoIP services with Matrix")</span>
</div> </div>
<div class="mx_HomePage_room"> <div class="mx_HomePage_room">
<a href="#/room/#identity:matrix.org"> <a href="#/room/#matrix-identity:matrix.org">
<img class="mx_HomePage_icon" src="home/rooms/identity.jpg"> <img class="mx_HomePage_icon" src="home/rooms/identity.jpg">
<span class="mx_HomePage_name">Matrix Identity</span> <span class="mx_HomePage_name">Matrix Identity</span>
</a> </a>

View File

@ -14,6 +14,7 @@ import subprocess
import sys import sys
import tarfile import tarfile
import shutil import shutil
import glob
try: try:
# python3 # python3
@ -66,7 +67,7 @@ class Deployer:
self.bundles_path = None self.bundles_path = None
self.should_clean = False self.should_clean = False
# filename -> symlink path e.g 'config.localhost.json' => '../localhost/config.json' # filename -> symlink path e.g 'config.localhost.json' => '../localhost/config.json'
self.config_locations = {} self.symlink_paths = {}
self.verify_signature = True self.verify_signature = True
def deploy(self, tarball, extract_path): def deploy(self, tarball, extract_path):
@ -98,11 +99,11 @@ class Deployer:
print ("Extracted into: %s" % extracted_dir) print ("Extracted into: %s" % extracted_dir)
if self.config_locations: if self.symlink_paths:
for config_filename, config_loc in self.config_locations.iteritems(): for link_path, file_path in self.symlink_paths.iteritems():
create_relative_symlink( create_relative_symlink(
target=config_loc, target=file_path,
linkname=os.path.join(extracted_dir, config_filename) linkname=os.path.join(extracted_dir, link_path)
) )
if self.bundles_path: if self.bundles_path:
@ -165,9 +166,10 @@ if __name__ == "__main__":
) )
) )
parser.add_argument( parser.add_argument(
"--config", nargs='?', default='./config.json', help=( "--include", nargs='*', default='./config*.json', help=(
"Write a symlink at config.json in the extracted tarball to this \ "Symlink these files into the root of the deployed tarball. \
location. (Default: '%(default)s')" Useful for config files and home pages. Supports glob syntax. \
(Default: '%(default)s')"
) )
) )
parser.add_argument( parser.add_argument(
@ -182,8 +184,8 @@ if __name__ == "__main__":
deployer.packages_path = args.packages_dir deployer.packages_path = args.packages_dir
deployer.bundles_path = args.bundles_dir deployer.bundles_path = args.bundles_dir
deployer.should_clean = args.clean deployer.should_clean = args.clean
deployer.config_locations = {
"config.json": args.config, for include in args.include:
} deployer.symlink_paths.update({ os.path.basename(pth): pth for pth in glob.iglob(include) })
deployer.deploy(args.tarball, args.extract_path) deployer.deploy(args.tarball, args.extract_path)

View File

@ -15,6 +15,7 @@ import json, requests, tarfile, argparse, os, errno
import time import time
import traceback import traceback
from urlparse import urljoin from urlparse import urljoin
import glob
from flask import Flask, jsonify, request, abort from flask import Flask, jsonify, request, abort
@ -188,15 +189,12 @@ if __name__ == "__main__":
) )
) )
def _raise(ex): # --include ../../config.json ./localhost.json homepages/*
raise ex
# --config config.json=../../config.json --config config.localhost.json=./localhost.json
parser.add_argument( parser.add_argument(
"--config", action="append", dest="configs", "--include", nargs='*', default='./config*.json', help=(
type=lambda kv: kv.split("=", 1) if "=" in kv else _raise(Exception("Missing =")), help=( "Symlink these files into the root of the deployed tarball. \
"A list of configs to symlink into the extracted tarball. \ Useful for config files and home pages. Supports glob syntax. \
For example, --config config.json=../config.json config2.json=../test/config.json" (Default: '%(default)s')"
) )
) )
parser.add_argument( parser.add_argument(
@ -220,7 +218,9 @@ if __name__ == "__main__":
deployer = Deployer() deployer = Deployer()
deployer.bundles_path = args.bundles_dir deployer.bundles_path = args.bundles_dir
deployer.should_clean = args.clean deployer.should_clean = args.clean
deployer.config_locations = dict(args.configs) if args.configs else {}
for include in args.include:
deployer.symlink_paths.update({ os.path.basename(pth): pth for pth in glob.iglob(include) })
# we don't pgp-sign jenkins artifacts; instead we rely on HTTPS access to # we don't pgp-sign jenkins artifacts; instead we rely on HTTPS access to
@ -234,13 +234,13 @@ if __name__ == "__main__":
deploy_tarball(args.tarball_uri, build_dir) deploy_tarball(args.tarball_uri, build_dir)
else: else:
print( print(
"Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s. Config locations: %s" % "Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s. Include files: %s" %
(args.port, (args.port,
arg_extract_path, arg_extract_path,
" (clean after)" if deployer.should_clean else "", " (clean after)" if deployer.should_clean else "",
arg_symlink, arg_symlink,
arg_jenkins_url, arg_jenkins_url,
deployer.config_locations, deployer.symlink_paths,
) )
) )
app.run(host="0.0.0.0", port=args.port, debug=True) app.run(host="0.0.0.0", port=args.port, debug=True)

View File

@ -1,5 +1,7 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,8 +16,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
'use strict';
import React from 'react'; import React from 'react';
import { _t } from 'matrix-react-sdk/lib/languageHandler'; import { _t } from 'matrix-react-sdk/lib/languageHandler';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
@ -26,26 +26,31 @@ import Analytics from 'matrix-react-sdk/lib/Analytics';
import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc'; import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';
import Modal from 'matrix-react-sdk/lib/Modal'; import Modal from 'matrix-react-sdk/lib/Modal';
import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton'; import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
import { showGroupInviteDialog } from 'matrix-react-sdk/lib/GroupInvite';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'RightPanel', displayName: 'RightPanel',
propTypes: { propTypes: {
// TODO: This should not be a prop, it should be received from the RoomViewStore // TODO: We're trying to move away from these being props, but we need to know
// whether we should be displaying a room or group member list
roomId: React.PropTypes.string, // if showing panels for a given room, this is set roomId: React.PropTypes.string, // if showing panels for a given room, this is set
groupId: React.PropTypes.string, // if showing panels for a given group, this is set
collapsed: React.PropTypes.bool, // currently unused property to request for a minimized view of the panel collapsed: React.PropTypes.bool, // currently unused property to request for a minimized view of the panel
}, },
Phase: { Phase: {
MemberList: 'MemberList', RoomMemberList: 'RoomMemberList',
GroupMemberList: 'GroupMemberList',
FilePanel: 'FilePanel', FilePanel: 'FilePanel',
NotificationPanel: 'NotificationPanel', NotificationPanel: 'NotificationPanel',
MemberInfo: 'MemberInfo', RoomMemberInfo: 'RoomMemberInfo',
GroupMemberInfo: 'GroupMemberInfo',
}, },
componentWillMount: function() { componentWillMount: function() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
var cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
cli.on("RoomState.members", this.onRoomStateMember); cli.on("RoomState.members", this.onRoomStateMember);
}, },
@ -57,14 +62,20 @@ module.exports = React.createClass({
}, },
getInitialState: function() { getInitialState: function() {
if (this.props.groupId) {
return { return {
phase: this.Phase.MemberList phase: this.Phase.GroupMemberList,
}; };
} else {
return {
phase: this.Phase.RoomMemberList,
};
}
}, },
onMemberListButtonClick: function() { onMemberListButtonClick: function() {
Analytics.trackEvent('Right Panel', 'Member List Button', 'click'); Analytics.trackEvent('Right Panel', 'Member List Button', 'click');
this.setState({ phase: this.Phase.MemberList }); this.setState({ phase: this.Phase.RoomMemberList });
}, },
onFileListButtonClick: function() { onFileListButtonClick: function() {
@ -89,19 +100,23 @@ module.exports = React.createClass({
return; return;
} }
// call ChatInviteDialog if (this.state.phase === this.Phase.GroupMemberList) {
showGroupInviteDialog(this.props.groupId);
} else {
// call AddressPickerDialog
dis.dispatch({ dis.dispatch({
action: 'view_invite', action: 'view_invite',
roomId: this.props.roomId, roomId: this.props.roomId,
}); });
}
}, },
onRoomStateMember: function(ev, state, member) { onRoomStateMember: function(ev, state, member) {
// redraw the badge on the membership list // redraw the badge on the membership list
if (this.state.phase == this.Phase.MemberList && member.roomId === this.props.roomId) { if (this.state.phase == this.Phase.RoomMemberList && member.roomId === this.props.roomId) {
this._delayedUpdate(); this._delayedUpdate();
} }
else if (this.state.phase === this.Phase.MemberInfo && member.roomId === this.props.roomId && else if (this.state.phase === this.Phase.RoomMemberInfo && member.roomId === this.props.roomId &&
member.userId === this.state.member.userId) { member.userId === this.state.member.userId) {
// refresh the member info (e.g. new power level) // refresh the member info (e.g. new power level)
this._delayedUpdate(); this._delayedUpdate();
@ -119,39 +134,55 @@ module.exports = React.createClass({
}); });
if (payload.member) { if (payload.member) {
this.setState({ this.setState({
phase: this.Phase.MemberInfo, phase: this.Phase.RoomMemberInfo,
member: payload.member,
});
} else {
if (this.props.roomId) {
this.setState({
phase: this.Phase.RoomMemberList
});
} else if (this.props.groupId) {
this.setState({
phase: this.Phase.GroupMemberList,
groupId: payload.groupId,
member: payload.member, member: payload.member,
}); });
} }
else { }
} else if (payload.action === "view_group") {
this.setState({ this.setState({
phase: this.Phase.MemberList phase: this.Phase.GroupMemberList,
groupId: payload.groupId,
member: null,
}); });
} } else if (payload.action === "view_group_user") {
}
else if (payload.action === "view_room") {
if (this.state.phase === this.Phase.MemberInfo) {
this.setState({ this.setState({
phase: this.Phase.MemberList phase: this.Phase.GroupMemberInfo,
groupId: payload.groupId,
member: payload.member,
});
} else if (payload.action === "view_room") {
this.setState({
phase: this.Phase.RoomMemberList
}); });
}
} }
}, },
render: function() { render: function() {
var MemberList = sdk.getComponent('rooms.MemberList'); const MemberList = sdk.getComponent('rooms.MemberList');
var NotificationPanel = sdk.getComponent('structures.NotificationPanel'); const GroupMemberList = sdk.getComponent('groups.GroupMemberList');
var FilePanel = sdk.getComponent('structures.FilePanel'); const NotificationPanel = sdk.getComponent('structures.NotificationPanel');
var TintableSvg = sdk.getComponent("elements.TintableSvg"); const FilePanel = sdk.getComponent('structures.FilePanel');
var buttonGroup; const TintableSvg = sdk.getComponent("elements.TintableSvg");
var inviteGroup; let inviteGroup;
var panel; let panel;
var filesHighlight; let filesHighlight;
var membersHighlight; let membersHighlight;
var notificationsHighlight; let notificationsHighlight;
if (!this.props.collapsed) { if (!this.props.collapsed) {
if (this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) { if (this.state.phase == this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo) {
membersHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>; membersHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>;
} }
else if (this.state.phase == this.Phase.FilePanel) { else if (this.state.phase == this.Phase.FilePanel) {
@ -162,11 +193,11 @@ module.exports = React.createClass({
} }
} }
var membersBadge; let membersBadge;
if ((this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) && this.props.roomId) { if ((this.state.phase == this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo) && this.props.roomId) {
var cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
var room = cli.getRoom(this.props.roomId); const room = cli.getRoom(this.props.roomId);
var user_is_in_room; let user_is_in_room;
if (room) { if (room) {
membersBadge = room.getJoinedMembers().length; membersBadge = room.getJoinedMembers().length;
user_is_in_room = room.hasMembershipState( user_is_in_room = room.hasMembershipState(
@ -186,48 +217,72 @@ module.exports = React.createClass({
} }
let headerButtons = [];
if (this.props.roomId) { if (this.props.roomId) {
buttonGroup = headerButtons.push(
<div className="mx_RightPanel_headerButtonGroup"> <AccessibleButton className="mx_RightPanel_headerButton" key="_membersButton"
<AccessibleButton className="mx_RightPanel_headerButton"
title={ _t('Members') } onClick={ this.onMemberListButtonClick }> title={ _t('Members') } onClick={ this.onMemberListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">{ membersBadge ? membersBadge : <span>&nbsp;</span>}</div> <div className="mx_RightPanel_headerButton_badge">{ membersBadge ? membersBadge : <span>&nbsp;</span>}</div>
<TintableSvg src="img/icons-people.svg" width="25" height="25"/> <TintableSvg src="img/icons-people.svg" width="25" height="25"/>
{ membersHighlight } { membersHighlight }
</AccessibleButton> </AccessibleButton>
);
headerButtons.push(
<AccessibleButton <AccessibleButton
className="mx_RightPanel_headerButton mx_RightPanel_filebutton" className="mx_RightPanel_headerButton mx_RightPanel_filebutton" key="_filesButton"
title={ _t('Files') } onClick={ this.onFileListButtonClick }> title={ _t('Files') } onClick={ this.onFileListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div> <div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-files.svg" width="25" height="25"/> <TintableSvg src="img/icons-files.svg" width="25" height="25"/>
{ filesHighlight } { filesHighlight }
</AccessibleButton> </AccessibleButton>
);
headerButtons.push(
<AccessibleButton <AccessibleButton
className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton" className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton" key="_notifsButton"
title={ _t('Notifications') } onClick={ this.onNotificationListButtonClick }> title={ _t('Notifications') } onClick={ this.onNotificationListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div> <div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-notifications.svg" width="25" height="25"/> <TintableSvg src="img/icons-notifications.svg" width="25" height="25"/>
{ notificationsHighlight } { notificationsHighlight }
</AccessibleButton> </AccessibleButton>
<div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" title={ _t("Hide panel") } onClick={ this.onCollapseClick }> );
}
if (this.props.roomId || this.props.groupId) {
// Hiding the right panel hides it completely and relies on an 'expand' button
// being put in the RoomHeader or GroupView header, so only show the minimise
// button on these 2 screens or you won't be able to re-expand the panel.
headerButtons.push(
<div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" key="_minimizeButton"
title={ _t("Hide panel") } onClick={ this.onCollapseClick }
>
<TintableSvg src="img/minimise.svg" width="10" height="16"/> <TintableSvg src="img/minimise.svg" width="10" height="16"/>
</div> </div>
</div>; );
} }
if (!this.props.collapsed) { if (!this.props.collapsed) {
if(this.props.roomId && this.state.phase == this.Phase.MemberList) { if (this.props.roomId && this.state.phase == this.Phase.RoomMemberList) {
panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} /> panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
} } else if (this.props.groupId && this.state.phase == this.Phase.GroupMemberList) {
else if(this.state.phase == this.Phase.MemberInfo) { panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />;
var MemberInfo = sdk.getComponent('rooms.MemberInfo'); inviteGroup = (
<AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
<div className="mx_RightPanel_icon" >
<TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />
</div>
<div className="mx_RightPanel_message">{ _t('Invite to this group') }</div>
</AccessibleButton>
);
} else if (this.state.phase == this.Phase.RoomMemberInfo) {
const MemberInfo = sdk.getComponent('rooms.MemberInfo');
panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.state.member.userId} /> panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.state.member.userId} />
} } else if (this.state.phase == this.Phase.GroupMemberInfo) {
else if (this.state.phase == this.Phase.NotificationPanel) { const GroupMemberInfo = sdk.getComponent('groups.GroupMemberInfo');
panel = <NotificationPanel /> panel = <GroupMemberInfo groupMember={this.state.member} groupId={this.props.groupId} key={this.state.member.user_id} />;
} } else if (this.state.phase == this.Phase.NotificationPanel) {
else if (this.state.phase == this.Phase.FilePanel) { panel = <NotificationPanel />;
panel = <FilePanel roomId={this.props.roomId} /> } else if (this.state.phase == this.Phase.FilePanel) {
panel = <FilePanel roomId={this.props.roomId} />;
} }
} }
@ -235,7 +290,7 @@ module.exports = React.createClass({
panel = <div className="mx_RightPanel_blank"></div>; panel = <div className="mx_RightPanel_blank"></div>;
} }
var classes = "mx_RightPanel mx_fadable"; let classes = "mx_RightPanel mx_fadable";
if (this.props.collapsed) { if (this.props.collapsed) {
classes += " collapsed"; classes += " collapsed";
} }
@ -243,7 +298,9 @@ module.exports = React.createClass({
return ( return (
<aside className={classes} style={{ opacity: this.props.opacity }}> <aside className={classes} style={{ opacity: this.props.opacity }}>
<div className="mx_RightPanel_header"> <div className="mx_RightPanel_header">
{ buttonGroup } <div className="mx_RightPanel_headerButtonGroup">
{headerButtons}
</div>
</div> </div>
{ panel } { panel }
<div className="mx_RightPanel_footer"> <div className="mx_RightPanel_footer">

View File

@ -88,6 +88,7 @@ var RoomSubList = React.createClass({
searchFilter: React.PropTypes.string, searchFilter: React.PropTypes.string,
emptyContent: React.PropTypes.node, // content shown if the list is empty emptyContent: React.PropTypes.node, // content shown if the list is empty
headerItems: React.PropTypes.node, // content shown in the sublist header headerItems: React.PropTypes.node, // content shown in the sublist header
extraTiles: React.PropTypes.arrayOf(React.PropTypes.node), // extra elements added beneath tiles
}, },
getInitialState: function() { getInitialState: function() {
@ -102,6 +103,7 @@ var RoomSubList = React.createClass({
return { return {
onHeaderClick: function() {}, // NOP onHeaderClick: function() {}, // NOP
onShowMoreRooms: function() {}, // NOP onShowMoreRooms: function() {}, // NOP
extraTiles: [],
isInvite: false, isInvite: false,
}; };
}, },
@ -534,13 +536,14 @@ var RoomSubList = React.createClass({
var label = this.props.collapsed ? null : this.props.label; var label = this.props.collapsed ? null : this.props.label;
let content; let content;
if (this.state.sortedList.length == 0 && !this.props.searchFilter) { if (this.state.sortedList.length == 0 && !this.props.searchFilter && !this.props.extraTiles) {
content = this.props.emptyContent; content = this.props.emptyContent;
} else { } else {
content = this.makeRoomTiles(); content = this.makeRoomTiles();
content.push(...this.props.extraTiles);
} }
if (this.state.sortedList.length > 0 || this.props.editable) { if (this.state.sortedList.length > 0 || this.props.extraTiles.length > 0 || this.props.editable) {
var subList; var subList;
var classes = "mx_RoomSubList"; var classes = "mx_RoomSubList";

View File

@ -0,0 +1,350 @@
/*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from 'matrix-react-sdk';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
class SendCustomEvent extends React.Component {
static propTypes = {
roomId: React.PropTypes.string.isRequired,
onBack: React.PropTypes.func.isRequired,
eventType: React.PropTypes.string.isRequired,
evContent: React.PropTypes.string.isRequired,
};
static defaultProps = {
eventType: '',
evContent: '{\n\n}',
};
constructor(props, context) {
super(props, context);
this._send = this._send.bind(this);
this.onBack = this.onBack.bind(this);
this._onChange = this._onChange.bind(this);
this.state = {
message: null,
input_eventType: this.props.eventType,
input_evContent: this.props.evContent,
};
}
onBack() {
if (this.state.message) {
this.setState({ message: null });
} else {
this.props.onBack();
}
}
_buttons() {
return <div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button>
{!this.state.message && <button onClick={this._send}>{ _t('Send') }</button>}
</div>;
}
send(content) {
return MatrixClientPeg.get().sendEvent(this.props.roomId, this.state.input_eventType, content);
}
async _send() {
if (this.state.input_eventType === '') {
this.setState({ message: _t('You must specify an event type!') });
return;
}
let message;
try {
const content = JSON.parse(this.state.input_evContent);
await this.send(content);
message = _t('Event sent!');
} catch (e) {
message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
}
this.setState({ message });
}
_additionalFields() {
return <div/>;
}
_onChange(e) {
this.setState({[`input_${e.target.id}`]: e.target.value});
}
render() {
if (this.state.message) {
return <div>
<div className="mx_Dialog_content">
{this.state.message}
</div>
{this._buttons()}
</div>;
}
return <div>
<div className="mx_Dialog_content">
{this._additionalFields()}
<div className="mx_TextInputDialog_label">
<label htmlFor="eventType"> { _t('Event Type') } </label>
</div>
<div>
<input id="eventType" onChange={this._onChange} value={this.state.input_eventType} className="mx_TextInputDialog_input" size="64" />
</div>
<div className="mx_TextInputDialog_label">
<label htmlFor="evContent"> { _t('Event Content') } </label>
</div>
<div>
<textarea id="evContent" onChange={this._onChange} value={this.state.input_evContent} className="mx_TextInputDialog_input" cols="63" rows="5" />
</div>
</div>
{this._buttons()}
</div>;
}
}
class SendCustomStateEvent extends SendCustomEvent {
static propTypes = {
roomId: React.PropTypes.string.isRequired,
onBack: React.PropTypes.func.isRequired,
eventType: React.PropTypes.string.isRequired,
evContent: React.PropTypes.string.isRequired,
stateKey: React.PropTypes.string.isRequired,
};
static defaultProps = {
eventType: '',
evContent: '{\n\n}',
stateKey: '',
};
constructor(props, context) {
super(props, context);
this.state['input_stateKey'] = this.props.stateKey;
}
send(content) {
const cli = MatrixClientPeg.get();
return cli.sendStateEvent(this.props.roomId, this.state.input_eventType, content, this.state.input_stateKey);
}
_additionalFields() {
return <div>
<div className="mx_TextInputDialog_label">
<label htmlFor="stateKey"> { _t('State Key') } </label>
</div>
<div>
<input id="stateKey" onChange={this._onChange} value={this.state.input_stateKey} className="mx_TextInputDialog_input" size="64" />
</div>
</div>;
}
}
class RoomStateExplorer extends React.Component {
static propTypes = {
setMode: React.PropTypes.func.isRequired,
roomId: React.PropTypes.string.isRequired,
onBack: React.PropTypes.func.isRequired,
};
constructor(props, context) {
super(props, context);
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
this.roomStateEvents = room.currentState.events;
this.onBack = this.onBack.bind(this);
this.editEv = this.editEv.bind(this);
this.onQuery = this.onQuery.bind(this);
}
state = {
query: '',
eventType: null,
event: null,
};
browseEventType(eventType) {
return () => {
this.setState({ eventType });
};
}
onViewSourceClick(event) {
return () => {
this.setState({ event });
};
}
onBack() {
if (this.state.event) {
this.setState({ event: null });
} else if (this.state.eventType) {
this.setState({ eventType: null });
} else {
this.props.onBack();
}
}
editEv() {
const ev = this.state.event;
this.props.setMode(SendCustomStateEvent, {
eventType: ev.getType(),
evContent: JSON.stringify(ev.getContent(), null, '\t'),
stateKey: ev.getStateKey(),
});
}
onQuery(ev) {
this.setState({ query: ev.target.value });
}
render() {
if (this.state.event) {
return <div className="mx_ViewSource">
<div className="mx_Dialog_content">
<pre>{JSON.stringify(this.state.event.event, null, 2)}</pre>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button>
<button onClick={this.editEv}>{ _t('Edit') }</button>
</div>
</div>;
}
const rows = [];
if (this.state.eventType === null) {
Object.keys(this.roomStateEvents).forEach((evType) => {
// Skip this entry if does not contain search query
if (this.state.query && !evType.includes(this.state.query)) return;
const stateGroup = this.roomStateEvents[evType];
const stateKeys = Object.keys(stateGroup);
let onClickFn;
if (stateKeys.length > 1) {
onClickFn = this.browseEventType(evType);
} else if (stateKeys.length === 1) {
onClickFn = this.onViewSourceClick(stateGroup[stateKeys[0]]);
}
rows.push(<button className="mx_DevTools_RoomStateExplorer_button" key={evType} onClick={onClickFn}>
{ evType }
</button>);
});
} else {
const evType = this.state.eventType;
const stateGroup = this.roomStateEvents[evType];
Object.keys(stateGroup).forEach((stateKey) => {
// Skip this entry if does not contain search query
if (this.state.query && !stateKey.includes(this.state.query)) return;
const ev = stateGroup[stateKey];
rows.push(<button className="mx_DevTools_RoomStateExplorer_button" key={stateKey}
onClick={this.onViewSourceClick(ev)}>
{ stateKey }
</button>);
});
}
return <div>
<div className="mx_Dialog_content">
<input onChange={this.onQuery} placeholder={_t('Filter results')} size="64" className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" value={this.state.query} />
{rows}
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button>
</div>
</div>;
}
}
export default class DevtoolsDialog extends React.Component {
static propTypes = {
roomId: React.PropTypes.string.isRequired,
onFinished: React.PropTypes.func.isRequired,
};
state = {
mode: null,
modeArgs: {},
};
constructor(props, context) {
super(props, context);
this.onBack = this.onBack.bind(this);
this.setMode = this.setMode.bind(this);
this.onCancel = this.onCancel.bind(this);
}
componentWillUnmount() {
this._unmounted = true;
}
_setMode(mode) {
return () => {
this.setMode(mode);
};
}
setMode(mode, modeArgs={}) {
this.setState({ mode, modeArgs });
}
onBack() {
this.setState({ mode: null });
}
onCancel() {
this.props.onFinished(false);
}
render() {
let body;
if (this.state.mode) {
body =
<this.state.mode {...this.props} {...this.state.modeArgs} onBack={this.onBack} setMode={this.setMode} />;
} else {
body = <div>
<div className="mx_Dialog_content">
<button onClick={this._setMode(SendCustomEvent)}>{ _t('Send Custom Event') }</button>
<button onClick={this._setMode(SendCustomStateEvent)}>{ _t('Send Custom State Event') }</button>
<button onClick={this._setMode(RoomStateExplorer)}>{ _t('Explore Room State') }</button>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onCancel}>{ _t('Cancel') }</button>
</div>
</div>;
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished} title={_t('Developer Tools')}>
<div>Room ID: { this.props.roomId }</div>
{ body }
</BaseDialog>
);
}
}

View File

@ -9,6 +9,7 @@
"All Rooms": "All Rooms", "All Rooms": "All Rooms",
"All notifications are currently disabled for all targets.": "All notifications are currently disabled for all targets.", "All notifications are currently disabled for all targets.": "All notifications are currently disabled for all targets.",
"An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.", "An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.",
"Back": "Back",
"Bug report sent": "Bug report sent", "Bug report sent": "Bug report sent",
"Call invitation": "Call invitation", "Call invitation": "Call invitation",
"Cancel": "Cancel", "Cancel": "Cancel",
@ -26,6 +27,7 @@
"delete the alias.": "delete the alias.", "delete the alias.": "delete the alias.",
"Delete the room alias %(alias)s and remove %(name)s from the directory?": "Delete the room alias %(alias)s and remove %(name)s from the directory?", "Delete the room alias %(alias)s and remove %(name)s from the directory?": "Delete the room alias %(alias)s and remove %(name)s from the directory?",
"Describe your problem here.": "Describe your problem here.", "Describe your problem here.": "Describe your problem here.",
"Developer Tools": "Developer Tools",
"Direct Chat": "Direct Chat", "Direct Chat": "Direct Chat",
"Directory": "Directory", "Directory": "Directory",
"Dismiss": "Dismiss", "Dismiss": "Dismiss",
@ -50,12 +52,14 @@
"Failed to get public room list": "Failed to get public room list", "Failed to get public room list": "Failed to get public room list",
"Failed to join the room": "Failed to join the room", "Failed to join the room": "Failed to join the room",
"Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room",
"Failed to send custom event.": "Failed to send custom event.",
"Failed to send report: ": "Failed to send report: ", "Failed to send report: ": "Failed to send report: ",
"Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to set direct chat tag": "Failed to set direct chat tag",
"Failed to set Direct Message status of room": "Failed to set Direct Message status of room", "Failed to set Direct Message status of room": "Failed to set Direct Message status of room",
"Favourite": "Favourite", "Favourite": "Favourite",
"Fetching third party location failed": "Fetching third party location failed", "Fetching third party location failed": "Fetching third party location failed",
"Files": "Files", "Files": "Files",
"Filter results": "Filter results",
"Filter room names": "Filter room names", "Filter room names": "Filter room names",
"Forget": "Forget", "Forget": "Forget",
"Forward Message": "Forward Message", "Forward Message": "Forward Message",
@ -117,6 +121,9 @@
"Search for a room": "Search for a room", "Search for a room": "Search for a room",
"Send": "Send", "Send": "Send",
"Send logs": "Send logs", "Send logs": "Send logs",
"Send Custom Event": "Send Custom Event",
"Send Custom State Event": "Send Custom State Event",
"Explore Room State": "Explore Room State",
"Settings": "Settings", "Settings": "Settings",
"Source URL": "Source URL", "Source URL": "Source URL",
"Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.", "Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.",
@ -150,6 +157,7 @@
"You are not receiving desktop notifications": "You are not receiving desktop notifications", "You are not receiving desktop notifications": "You are not receiving desktop notifications",
"You are Rioting as a guest. <a>Register</a> or <a>sign in</a> to access more rooms and features!": "You are Rioting as a guest. <a>Register</a> or <a>sign in</a> to access more rooms and features!", "You are Rioting as a guest. <a>Register</a> or <a>sign in</a> to access more rooms and features!": "You are Rioting as a guest. <a>Register</a> or <a>sign in</a> to access more rooms and features!",
"You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply": "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply", "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply": "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply",
"You must specify an event type!": "You must specify an event type!",
"Thank you!": "Thank you!", "Thank you!": "Thank you!",
"Sunday": "Sunday", "Sunday": "Sunday",
"Monday": "Monday", "Monday": "Monday",
@ -164,6 +172,10 @@
"Warning": "Warning", "Warning": "Warning",
"Checking for an update...": "Checking for an update...", "Checking for an update...": "Checking for an update...",
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
"Event sent!": "Event sent!",
"Event Type": "Event Type",
"Event Content": "Event Content",
"State Key": "State Key",
"No update available.": "No update available.", "No update available.": "No update available.",
"Downloading update...": "Downloading update...", "Downloading update...": "Downloading update...",
"You need to be using HTTPS to place a screen-sharing call.": "You need to be using HTTPS to place a screen-sharing call.", "You need to be using HTTPS to place a screen-sharing call.": "You need to be using HTTPS to place a screen-sharing call.",
@ -207,5 +219,6 @@
"Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.", "Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.",
"To return to your account in future you need to <u>set a password</u>": "To return to your account in future you need to <u>set a password</u>", "To return to your account in future you need to <u>set a password</u>": "To return to your account in future you need to <u>set a password</u>",
"Set Password": "Set Password", "Set Password": "Set Password",
"Couldn't load home page": "Couldn't load home page" "Couldn't load home page": "Couldn't load home page",
"Invite to this group": "Invite to this group"
} }

View File

@ -32,6 +32,7 @@
@import "./matrix-react-sdk/views/elements/_ProgressBar.scss"; @import "./matrix-react-sdk/views/elements/_ProgressBar.scss";
@import "./matrix-react-sdk/views/elements/_RichText.scss"; @import "./matrix-react-sdk/views/elements/_RichText.scss";
@import "./matrix-react-sdk/views/elements/_RoleButton.scss"; @import "./matrix-react-sdk/views/elements/_RoleButton.scss";
@import "./matrix-react-sdk/views/groups/_GroupInviteTile.scss";
@import "./matrix-react-sdk/views/login/_InteractiveAuthEntryComponents.scss"; @import "./matrix-react-sdk/views/login/_InteractiveAuthEntryComponents.scss";
@import "./matrix-react-sdk/views/login/_ServerConfig.scss"; @import "./matrix-react-sdk/views/login/_ServerConfig.scss";
@import "./matrix-react-sdk/views/messages/_MEmoteBody.scss"; @import "./matrix-react-sdk/views/messages/_MEmoteBody.scss";
@ -74,6 +75,7 @@
@import "./vector-web/views/context_menus/_MessageContextMenu.scss"; @import "./vector-web/views/context_menus/_MessageContextMenu.scss";
@import "./vector-web/views/context_menus/_RoomTileContextMenu.scss"; @import "./vector-web/views/context_menus/_RoomTileContextMenu.scss";
@import "./vector-web/views/dialogs/_ChangelogDialog.scss"; @import "./vector-web/views/dialogs/_ChangelogDialog.scss";
@import "./vector-web/views/dialogs/_DevtoolsDialog.scss";
@import "./vector-web/views/dialogs/_SetEmailDialog.scss"; @import "./vector-web/views/dialogs/_SetEmailDialog.scss";
@import "./vector-web/views/dialogs/_SetPasswordDialog.scss"; @import "./vector-web/views/dialogs/_SetPasswordDialog.scss";
@import "./vector-web/views/directory/_NetworkDropdown.scss"; @import "./vector-web/views/directory/_NetworkDropdown.scss";

View File

@ -70,8 +70,12 @@ limitations under the License.
flex: 1; flex: 1;
} }
.mx_GroupView_saveButton, .mx_GroupView_cancelButton { .mx_GroupView_header_rightCol {
display: table-cell; display: flex;
}
.mx_GroupView_textButton {
display: inline-block;
} }
.mx_GroupView_header_groupid { .mx_GroupView_header_groupid {
@ -126,6 +130,26 @@ limitations under the License.
top: 5px; top: 5px;
} }
.mx_GroupView_membershipSection {
margin-left: auto;
margin-right: auto;
margin-bottom: 11px;
justify-content: space-between;
display: flex;
color: $greyed-fg-color;
}
.mx_GroupView_membershipSection_description {
/* To match textButton */
line-height: 34px;
}
.mx_GroupView_membershipSection .mx_GroupView_textButton {
margin-right: 0px;
margin-top: 0px;
margin-left: 8px;
}
.mx_GroupView_featuredThings { .mx_GroupView_featuredThings {
margin-top: 20px; margin-top: 20px;
} }
@ -142,9 +166,30 @@ limitations under the License.
margin-top: 10px; margin-top: 10px;
} }
.mx_GroupView_featuredThings_container {
display: flex;
}
.mx_GroupView_featuredThings_addButton,
.mx_GroupView_featuredThing { .mx_GroupView_featuredThing {
cursor: pointer;
display: table-cell; display: table-cell;
text-align: center;
width: 100px;
margin: 0px 20px;
}
.mx_GroupView_featuredThing .mx_BaseAvatar {
/* To prevent misalignment with mx_TintableSvg (in addButton) */
vertical-align: initial;
}
.mx_GroupView_featuredThings_addButton object {
pointer-events: none;
}
.mx_GroupView_featuredThing_name {
word-wrap: break-word;
} }
.mx_GroupView_uploadInput { .mx_GroupView_uploadInput {

View File

@ -80,6 +80,16 @@ limitations under the License.
cursor: pointer; cursor: pointer;
} }
.mx_UserSettings_button.mx_UserSettings_buttonSmall {
height: 36px;
padding: 4px;
padding-left: 7px;
padding-right: 7px;
font-size: 12px;
margin-right: 5px;
line-height: 12px;
}
.mx_UserSettings_button.danger { .mx_UserSettings_button.danger {
background-color: $warning-color; background-color: $warning-color;
} }

View File

@ -0,0 +1,74 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_GroupInviteTile {
position: relative;
cursor: pointer;
font-size: 13px;
display: block;
height: 34px;
}
.mx_GroupInviteTile_nameContainer {
display: inline-block;
width: 180px;
height: 24px;
}
.mx_GroupInviteTile_avatarContainer {
display: inline-block;
padding-top: 5px;
padding-bottom: 5px;
padding-left: 16px;
padding-right: 6px;
width: 24px;
height: 24px;
vertical-align: middle;
}
.mx_GroupInviteTile_name {
display: inline-block;
position: relative;
width: 165px;
vertical-align: middle;
padding-left: 6px;
padding-right: 6px;
padding-top: 2px;
padding-bottom: 3px;
color: $roomtile-name-color;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_GroupInviteTile_badge {
display: inline-block;
min-width: 15px;
height: 15px;
position: absolute;
right: 8px; /*gutter */
top: 9px;
border-radius: 8px;
color: $accent-fg-color;
background-color: $group-alert-color;
font-weight: 600;
font-size: 10px;
text-align: center;
padding-top: 1px;
padding-left: 4px;
padding-right: 4px;
}

View File

@ -45,7 +45,6 @@ limitations under the License.
.mx_EventTile .mx_SenderProfile { .mx_EventTile .mx_SenderProfile {
color: $primary-fg-color; color: $primary-fg-color;
opacity: 0.5;
font-size: 14px; font-size: 14px;
display: block; /* anti-zalgo, with overflow hidden */ display: block; /* anti-zalgo, with overflow hidden */
overflow-y: hidden; overflow-y: hidden;
@ -57,6 +56,15 @@ limitations under the License.
line-height: 22px; line-height: 22px;
} }
.mx_EventTile .mx_SenderProfile .mx_SenderProfile_name,
.mx_EventTile .mx_SenderProfile .mx_SenderProfile_aux {
opacity: 0.5;
}
.mx_EventTile .mx_SenderProfile .mx_Flair {
opacity: 0.7;
}
.mx_EventTile .mx_MessageTimestamp { .mx_EventTile .mx_MessageTimestamp {
display: block; display: block;
visibility: hidden; visibility: hidden;

View File

@ -21,9 +21,7 @@ limitations under the License.
} }
.mx_RoomSettings_leaveButton, .mx_RoomSettings_leaveButton,
.mx_RoomSettings_unbanButton, .mx_RoomSettings_unbanButton {
.mx_RoomSettings_integrationsButton,
.mx_RoomSettings_integrationsButton_error {
position: relative; position: relative;
height: 36px; height: 36px;
background-color: $accent-color; background-color: $accent-color;
@ -36,25 +34,37 @@ limitations under the License.
padding-left: 12px; padding-left: 12px;
padding-right: 12px; padding-right: 12px;
} }
.mx_RoomSettings_integrationsButton_error {
position: relative;
cursor: not-allowed;
}
.mx_RoomSettings_integrationsButton_error img {
position: absolute;
right: -5px;
top: -5px;
}
.mx_RoomSettings_leaveButton, .mx_RoomSettings_leaveButton,
.mx_RoomSettings_integrationsButton,
.mx_RoomSettings_integrationsButton_error { .mx_RoomSettings_integrationsButton_error {
float: right; float: right;
} }
.mx_RoomSettings_integrationsButton_error { .mx_RoomSettings_integrationsButton_error .mx_RoomSettings_integrationsButton_errorPopup {
pointer: not-allowed; display: none;
}
.mx_RoomSettings_integrationsButton_error:hover .mx_RoomSettings_integrationsButton_errorPopup {
display: inline;
} }
.mx_RoomSettings_integrationsButton_errorPopup { .mx_RoomSettings_integrationsButton_errorPopup {
position: absolute; position: absolute;
top: 110%; top: 110%;
left: -26%; left: -125%;
width: 150%; width: 348%;
padding: 2%; padding: 2%;
font-size: 10pt; font-size: 10pt;
line-height: 1.5em; line-height: 1.5em;
border-radius: 5px; border-radius: 5px;
background-color: $accent-color; background-color: $accent-color;
color: $accent-fg-color; color: $accent-fg-color;
text-align: center;
} }
.mx_RoomSettings_unbanButton { .mx_RoomSettings_unbanButton {
display: inline; display: inline;

View File

@ -22,6 +22,8 @@ $warning-color: #ff0064;
$mention-user-pill-bg-color: #ff0064; $mention-user-pill-bg-color: #ff0064;
$other-user-pill-bg-color: rgba(0, 0, 0, 0.1); $other-user-pill-bg-color: rgba(0, 0, 0, 0.1);
$group-alert-color: #774f7e;
$preview-bar-bg-color: #f7f7f7; $preview-bar-bg-color: #f7f7f7;
// left-panel style muted accent color // left-panel style muted accent color

View File

@ -0,0 +1,19 @@
/*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_DevTools_RoomStateExplorer_button, .mx_DevTools_RoomStateExplorer_query {
margin-bottom: 10px;
}

View File

@ -176,8 +176,9 @@ describe('joining a room', function () {
return Promise.delay(1); return Promise.delay(1);
}).then(() => { }).then(() => {
// We've joined, expect this to false // NB. we don't expect the 'joining' flag to reset at any point:
expect(roomView.state.joining).toBe(false); // it will stay set and we observe whether we have Room object for
// the room and whether our member event shows we're joined.
// now send the room down the /sync pipe // now send the room down the /sync pipe
httpBackend.when('GET', '/sync'). httpBackend.when('GET', '/sync').