Merge branch 'develop' into wmwragg/direct-chat-sublist

This commit is contained in:
wmwragg 2016-08-23 10:52:50 +01:00
commit 6d1f9003e2
24 changed files with 593 additions and 195 deletions

View File

@ -1,3 +1,224 @@
Changes in [0.6.4-r1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.4-r1) (2016-08-12)
=========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.4...v0.6.4-r1)
* Fix inviting multiple people
Changes in [0.6.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.4) (2016-08-11)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.3...v0.6.4)
* Only show Autocomplete if autocomplete is enabled
[\#411](https://github.com/matrix-org/matrix-react-sdk/pull/411)
* Wmwragg/room tag menu
[\#402](https://github.com/matrix-org/matrix-react-sdk/pull/402)
* Move guest registration into the login logic
[\#407](https://github.com/matrix-org/matrix-react-sdk/pull/407)
* Better support for inviting multiple people
[\#403](https://github.com/matrix-org/matrix-react-sdk/pull/403)
* Refactor login token
[\#406](https://github.com/matrix-org/matrix-react-sdk/pull/406)
* Use the current HS for guest login
[\#405](https://github.com/matrix-org/matrix-react-sdk/pull/405)
* Various fixes and improvements to emojification.
[\#395](https://github.com/matrix-org/matrix-react-sdk/pull/395)
* Fix settings resetting on refresh
[\#404](https://github.com/matrix-org/matrix-react-sdk/pull/404)
* Avoid flashing up login screen during guest registration
[\#401](https://github.com/matrix-org/matrix-react-sdk/pull/401)
* Cancel calls to rate-limited funcs on unmount
[\#400](https://github.com/matrix-org/matrix-react-sdk/pull/400)
* Move rehydration of MatrixClients from MatrixClientPeg to SessionLoader
[\#399](https://github.com/matrix-org/matrix-react-sdk/pull/399)
* Don't show integrations header if setting not on
[\#398](https://github.com/matrix-org/matrix-react-sdk/pull/398)
* Start to factor out session-loading magic
[\#397](https://github.com/matrix-org/matrix-react-sdk/pull/397)
* Hack around a react warning
[\#396](https://github.com/matrix-org/matrix-react-sdk/pull/396)
* Add config to hide the labs section
[\#393](https://github.com/matrix-org/matrix-react-sdk/pull/393)
* Dbkr/scalar
[\#392](https://github.com/matrix-org/matrix-react-sdk/pull/392)
* Wmwragg/mute mention state fix
[\#390](https://github.com/matrix-org/matrix-react-sdk/pull/390)
* Fix long freeze when opening 'historical' section
[\#391](https://github.com/matrix-org/matrix-react-sdk/pull/391)
* Refactor UI error effects
[\#388](https://github.com/matrix-org/matrix-react-sdk/pull/388)
* Implement account deactivation
[\#381](https://github.com/matrix-org/matrix-react-sdk/pull/381)
* Don't leave isRoomPublished as undefined
[\#389](https://github.com/matrix-org/matrix-react-sdk/pull/389)
* Call the logout API when we log out
[\#377](https://github.com/matrix-org/matrix-react-sdk/pull/377)
* feat: code cleanup & emoji replacement in composer
[\#335](https://github.com/matrix-org/matrix-react-sdk/pull/335)
* Add more logging to TimelinePanel-test
[\#387](https://github.com/matrix-org/matrix-react-sdk/pull/387)
* DevicesPanel: use device_id as a placeholder
[\#386](https://github.com/matrix-org/matrix-react-sdk/pull/386)
* MemberDeviceInfo: Use the device name, where available
[\#385](https://github.com/matrix-org/matrix-react-sdk/pull/385)
* Wmwragg/mention state menu
[\#369](https://github.com/matrix-org/matrix-react-sdk/pull/369)
* fix upload for video or image files where sniffing fails
[\#383](https://github.com/matrix-org/matrix-react-sdk/pull/383)
* fix: allow up/down normally for no completions
[\#384](https://github.com/matrix-org/matrix-react-sdk/pull/384)
* fix: autocomplete to use tab instead of return
[\#382](https://github.com/matrix-org/matrix-react-sdk/pull/382)
* strip (IRC) displayname suffix from autocomplete
[\#375](https://github.com/matrix-org/matrix-react-sdk/pull/375)
* Include rooms with 1 person invited
[\#379](https://github.com/matrix-org/matrix-react-sdk/pull/379)
* Fix 'start new direct chat'
[\#378](https://github.com/matrix-org/matrix-react-sdk/pull/378)
* Fix warnings from MessageComposer
[\#376](https://github.com/matrix-org/matrix-react-sdk/pull/376)
* New voice and video call buttons
[\#371](https://github.com/matrix-org/matrix-react-sdk/pull/371)
* Silence some more react warnings
[\#373](https://github.com/matrix-org/matrix-react-sdk/pull/373)
* Fix warnings emanating from Velociraptor elements
[\#372](https://github.com/matrix-org/matrix-react-sdk/pull/372)
* Wmwragg/button updates
[\#353](https://github.com/matrix-org/matrix-react-sdk/pull/353)
* Implement device management UI
[\#370](https://github.com/matrix-org/matrix-react-sdk/pull/370)
* Factor EditableTextContainer out of ChangeDisplayName
[\#368](https://github.com/matrix-org/matrix-react-sdk/pull/368)
* Stop the Avatar classes setting properties on <span>s
[\#367](https://github.com/matrix-org/matrix-react-sdk/pull/367)
* Remove relayoutOnUpdate prop on gemini-scrollbar
[\#366](https://github.com/matrix-org/matrix-react-sdk/pull/366)
* Fix bug where vector freezes on power level event
[\#364](https://github.com/matrix-org/matrix-react-sdk/pull/364)
* Refactor MatrixClientPeg
[\#361](https://github.com/matrix-org/matrix-react-sdk/pull/361)
* Fix 'start chat' button on MemberInfo
[\#363](https://github.com/matrix-org/matrix-react-sdk/pull/363)
* Bump dependency versions
[\#362](https://github.com/matrix-org/matrix-react-sdk/pull/362)
* Fix tab complete order properly
[\#360](https://github.com/matrix-org/matrix-react-sdk/pull/360)
* Add removeListener for account data listener
[\#359](https://github.com/matrix-org/matrix-react-sdk/pull/359)
* Set the device_id on pre-login MatrixClient
[\#358](https://github.com/matrix-org/matrix-react-sdk/pull/358)
* Wmwragg/mention state indicator round 2
[\#357](https://github.com/matrix-org/matrix-react-sdk/pull/357)
* Support for disabling/enabling URL previews per-user, per-room and per-user-
per-room
[\#356](https://github.com/matrix-org/matrix-react-sdk/pull/356)
* Use HS proxy API for requestToken on adding email
[\#336](https://github.com/matrix-org/matrix-react-sdk/pull/336)
* Error if email already in use when resetting pw
[\#337](https://github.com/matrix-org/matrix-react-sdk/pull/337)
* Fix enourmous video bug
[\#355](https://github.com/matrix-org/matrix-react-sdk/pull/355)
* Add support for sending uploaded content as m.video
[\#354](https://github.com/matrix-org/matrix-react-sdk/pull/354)
* Order tab complete by most recently spoke
[\#341](https://github.com/matrix-org/matrix-react-sdk/pull/341)
* Wmwragg/spinner fix
[\#350](https://github.com/matrix-org/matrix-react-sdk/pull/350)
* Now showing three dots when hovering over the badge
[\#352](https://github.com/matrix-org/matrix-react-sdk/pull/352)
* Fix unpublishing room in room settings
[\#351](https://github.com/matrix-org/matrix-react-sdk/pull/351)
* Fix race when creating rooms where invite list can be blank
[\#347](https://github.com/matrix-org/matrix-react-sdk/pull/347)
* improve wording of MemberInfo's start chat button.
[\#348](https://github.com/matrix-org/matrix-react-sdk/pull/348)
* Revert "Amends react template and removes opening image in lightbox"
[\#346](https://github.com/matrix-org/matrix-react-sdk/pull/346)
* Wmwragg/modal restyle
[\#345](https://github.com/matrix-org/matrix-react-sdk/pull/345)
* Amends react template and removes opening image in lightbox
[\#343](https://github.com/matrix-org/matrix-react-sdk/pull/343)
* Remove the member list loading hack
[\#344](https://github.com/matrix-org/matrix-react-sdk/pull/344)
* CSS classes to colour offline users differently
[\#342](https://github.com/matrix-org/matrix-react-sdk/pull/342)
* Listen for the new lastPreseceTs event
[\#340](https://github.com/matrix-org/matrix-react-sdk/pull/340)
* Fix filtering user list by ID
[\#339](https://github.com/matrix-org/matrix-react-sdk/pull/339)
* Update tab completion list when we have a room
[\#338](https://github.com/matrix-org/matrix-react-sdk/pull/338)
* JS code style guide
[\#330](https://github.com/matrix-org/matrix-react-sdk/pull/330)
* Error on registration if email taken
[\#334](https://github.com/matrix-org/matrix-react-sdk/pull/334)
* feat: render unicode emoji as emojione images
[\#332](https://github.com/matrix-org/matrix-react-sdk/pull/332)
* feat: unblacklist img tags with data URIs
[\#333](https://github.com/matrix-org/matrix-react-sdk/pull/333)
* Autocomplete fixes
[\#331](https://github.com/matrix-org/matrix-react-sdk/pull/331)
* Better autocomplete
[\#296](https://github.com/matrix-org/matrix-react-sdk/pull/296)
* feat: add and configure eslint
[\#329](https://github.com/matrix-org/matrix-react-sdk/pull/329)
* Fix user links
[\#326](https://github.com/matrix-org/matrix-react-sdk/pull/326)
* Fix ordering of Memberlist
[\#327](https://github.com/matrix-org/matrix-react-sdk/pull/327)
* Display an error message if room not found
[\#325](https://github.com/matrix-org/matrix-react-sdk/pull/325)
* Implement device blocking
[\#324](https://github.com/matrix-org/matrix-react-sdk/pull/324)
* Remove /encrypt command
[\#322](https://github.com/matrix-org/matrix-react-sdk/pull/322)
* RoomSettings: add encryption setting
[\#321](https://github.com/matrix-org/matrix-react-sdk/pull/321)
* Fix a pair of warnings from RoomSettings
[\#320](https://github.com/matrix-org/matrix-react-sdk/pull/320)
* RoomSettings: refactor permissions calculations
[\#319](https://github.com/matrix-org/matrix-react-sdk/pull/319)
* Fix https://github.com/vector-im/vector-web/issues/1679
[\#318](https://github.com/matrix-org/matrix-react-sdk/pull/318)
* Fix /join to be consistent with the other code
[\#317](https://github.com/matrix-org/matrix-react-sdk/pull/317)
* UserSettings: fix the displayed version of the react-sdk
[\#316](https://github.com/matrix-org/matrix-react-sdk/pull/316)
* Show canonical alias in URL bar
[\#314](https://github.com/matrix-org/matrix-react-sdk/pull/314)
* Some basic tests for RoomView
[\#313](https://github.com/matrix-org/matrix-react-sdk/pull/313)
* Support for making devices unverified
[\#315](https://github.com/matrix-org/matrix-react-sdk/pull/315)
* Fix eventListener warning
[\#312](https://github.com/matrix-org/matrix-react-sdk/pull/312)
* Fix peeking and member list vanishing
[\#307](https://github.com/matrix-org/matrix-react-sdk/pull/307)
* Use different keys for new MessageComposerInput
[\#311](https://github.com/matrix-org/matrix-react-sdk/pull/311)
* Fix RTE escaping, HTML output with breaks
[\#310](https://github.com/matrix-org/matrix-react-sdk/pull/310)
* Fix cursor bug, persist editor mode & rte default
[\#308](https://github.com/matrix-org/matrix-react-sdk/pull/308)
* Rich Text Editor
[\#292](https://github.com/matrix-org/matrix-react-sdk/pull/292)
* Hide e2e features if not enabled
[\#306](https://github.com/matrix-org/matrix-react-sdk/pull/306)
* Add experimental "Labs" section to settings
[\#305](https://github.com/matrix-org/matrix-react-sdk/pull/305)
* Make the room directory join rooms by alias
[\#304](https://github.com/matrix-org/matrix-react-sdk/pull/304)
* Factor out common parts of room creation
[\#303](https://github.com/matrix-org/matrix-react-sdk/pull/303)
* Fix spinner-of-doom in member info for guests
[\#302](https://github.com/matrix-org/matrix-react-sdk/pull/302)
* Support for marking devices as verified
[\#300](https://github.com/matrix-org/matrix-react-sdk/pull/300)
* Make the config optional
[\#301](https://github.com/matrix-org/matrix-react-sdk/pull/301)
* Pass brand parameter down to Notifications
[\#299](https://github.com/matrix-org/matrix-react-sdk/pull/299)
* Second attempt at fixing the Velocity memory leak
[\#298](https://github.com/matrix-org/matrix-react-sdk/pull/298)
Changes in [0.6.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.3) (2016-06-03)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.2...v0.6.3)

View File

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "0.6.3",
"version": "0.6.4-r1",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@ -40,7 +40,7 @@
"linkifyjs": "^2.0.0-beta.4",
"lodash": "^4.13.1",
"marked": "^0.3.5",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"matrix-js-sdk": "0.5.5",
"optimist": "^0.6.1",
"q": "^1.4.1",
"react": "^15.2.1",

View File

@ -273,8 +273,11 @@ function _onAction(payload) {
break;
case 'incoming_call':
if (module.exports.getAnyActiveCall()) {
payload.call.hangup("busy");
return; // don't allow >1 call to be received, hangup newer one.
// ignore multiple incoming calls. in future, we may want a line-1/line-2 setup.
// we avoid rejecting with "busy" in case the user wants to answer it on a different device.
// in future we could signal a "local busy" as a warning to the caller.
// see https://github.com/vector-im/vector-web/issues/1964
return;
}
// if the runtime env doesn't do VoIP, stop here.

View File

@ -69,7 +69,7 @@ var sanitizeHtmlParams = {
allowedAttributes: {
// custom ones first:
font: [ 'color' ], // custom to matrix
a: [ 'href', 'name', 'target' ], // remote target: custom to matrix
a: [ 'href', 'name', 'target', 'rel' ], // remote target: custom to matrix
// We don't currently allow img itself by default, but this
// would make sense if we did
img: [ 'src' ],
@ -92,6 +92,7 @@ var sanitizeHtmlParams = {
else {
attribs.target = '_blank';
}
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
return { tagName: tagName, attribs : attribs };
},
},

View File

@ -69,6 +69,7 @@ export function loadSession(opts) {
let enableGuest = opts.enableGuest || false;
const guestHsUrl = opts.guestHsUrl;
const guestIsUrl = opts.guestIsUrl;
const defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
if (fragmentQueryParams.client_secret && fragmentQueryParams.sid) {
// this happens during email validation: the email contains a link to the
@ -87,7 +88,7 @@ export function loadSession(opts) {
if (!realQueryParams.homeserver) {
console.warn("Cannot log in with token: can't determine HS URL to use");
} else {
return _loginWithToken(realQueryParams);
return _loginWithToken(realQueryParams, defaultDeviceDisplayName);
}
}
@ -111,23 +112,29 @@ export function loadSession(opts) {
}
if (enableGuest) {
return _registerAsGuest(guestHsUrl, guestIsUrl);
return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
}
// fall back to login screen
return q();
}
function _loginWithToken(queryParams) {
function _loginWithToken(queryParams, defaultDeviceDisplayName) {
// create a temporary MatrixClient to do the login
var client = Matrix.createClient({
baseUrl: queryParams.homeserver,
});
return client.loginWithToken(queryParams.loginToken).then(function(data) {
return client.login(
"m.login.token", {
token: queryParams.loginToken,
initial_device_display_name: defaultDeviceDisplayName,
},
).then(function(data) {
console.log("Logged in with token");
setLoggedIn({
userId: data.user_id,
deviceId: data.device_id,
accessToken: data.access_token,
homeserverUrl: queryParams.homeserver,
identityServerUrl: queryParams.identityServer,
@ -139,14 +146,26 @@ function _loginWithToken(queryParams) {
});
}
function _registerAsGuest(hsUrl, isUrl) {
function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
console.log("Doing guest login on %s", hsUrl);
MatrixClientPeg.replaceUsingUrls(hsUrl, isUrl);
return MatrixClientPeg.get().registerGuest().then((creds) => {
// TODO: we should probably de-duplicate this and Signup.Login.loginAsGuest.
// Not really sure where the right home for it is.
// create a temporary MatrixClient to do the login
var client = Matrix.createClient({
baseUrl: hsUrl,
});
return client.registerGuest({
body: {
initial_device_display_name: defaultDeviceDisplayName,
},
}).then((creds) => {
console.log("Registered as guest: %s", creds.user_id);
setLoggedIn({
userId: creds.user_id,
deviceId: creds.device_id,
accessToken: creds.access_token,
homeserverUrl: hsUrl,
identityServerUrl: isUrl,
@ -166,6 +185,7 @@ function _restoreFromLocalStorage() {
const is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
const access_token = localStorage.getItem("mx_access_token");
const user_id = localStorage.getItem("mx_user_id");
const device_id = localStorage.getItem("mx_device_id");
let is_guest;
if (localStorage.getItem("mx_is_guest") !== null) {
@ -179,6 +199,7 @@ function _restoreFromLocalStorage() {
console.log("Restoring session for %s", user_id);
setLoggedIn({
userId: user_id,
deviceId: device_id,
accessToken: access_token,
homeserverUrl: hs_url,
identityServerUrl: is_url,
@ -206,10 +227,19 @@ export function setLoggedIn(credentials) {
try {
localStorage.setItem("mx_hs_url", credentials.homeserverUrl);
localStorage.setItem("mx_is_url", credentials.identityServerUrl);
localStorage.setItem("mx_user_id", credentials.userId);
localStorage.setItem("mx_access_token", credentials.accessToken);
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
// if we didn't get a deviceId from the login, leave mx_device_id unset,
// rather than setting it to "undefined".
//
// (in this case MatrixClient doesn't bother with the crypto stuff
// - that's fine for us).
if (credentials.deviceId) {
localStorage.setItem("mx_device_id", credentials.deviceId);
}
console.log("Session persisted for %s", credentials.userId);
} catch (e) {
console.warn("Error using local storage: can't persist session!", e);
@ -286,7 +316,7 @@ export function onLoggedOut() {
if (hsUrl) window.localStorage.setItem("mx_hs_url", hsUrl);
if (isUrl) window.localStorage.setItem("mx_is_url", isUrl);
}
_stopMatrixClient();
stopMatrixClient();
dis.dispatch({action: 'on_logged_out'});
}
@ -294,11 +324,14 @@ export function onLoggedOut() {
/**
* Stop all the background processes related to the current client
*/
function _stopMatrixClient() {
export function stopMatrixClient() {
Notifier.stop();
UserActivity.stop();
Presence.stop();
MatrixClientPeg.get().stopClient();
MatrixClientPeg.get().removeAllListeners();
MatrixClientPeg.unset();
var cli = MatrixClientPeg.get();
if (cli) {
cli.stopClient();
cli.removeAllListeners();
MatrixClientPeg.unset();
}
}

View File

@ -21,21 +21,11 @@ import utils from 'matrix-js-sdk/lib/utils';
const localStorage = window.localStorage;
function deviceId() {
// XXX: is Math.random()'s deterministicity a problem here?
var id = Math.floor(Math.random()*16777215).toString(16);
id = "W" + "000000".substring(id.length) + id;
if (localStorage) {
id = localStorage.getItem("mx_device_id") || id;
localStorage.setItem("mx_device_id", id);
}
return id;
}
interface MatrixClientCreds {
homeserverUrl: string,
identityServerUrl: string,
userId: string,
deviceId: string,
accessToken: string,
guest: boolean,
}
@ -67,26 +57,12 @@ class MatrixClientPeg {
this.matrixClient = null;
}
/**
* Replace this MatrixClientPeg's client with a client instance that has
* Home Server / Identity Server URLs but no credentials
*/
replaceUsingUrls(hs_url, is_url) {
this._replaceClient(hs_url, is_url);
}
/**
* Replace this MatrixClientPeg's client with a client instance that has
* Home Server / Identity Server URLs and active credentials
*/
replaceUsingCreds(creds: MatrixClientCreds) {
this._replaceClient(
creds.homeserverUrl,
creds.identityServerUrl,
creds.userId,
creds.accessToken,
creds.guest,
);
this._createClient(creds);
}
start() {
@ -96,32 +72,29 @@ class MatrixClientPeg {
this.get().startClient(opts);
}
_replaceClient(hs_url, is_url, user_id, access_token, isGuest) {
this._createClient(hs_url, is_url, user_id, access_token, isGuest);
}
getCredentials(): MatrixClientCreds {
return {
homeserverUrl: this.matrixClient.baseUrl,
identityServerUrl: this.matrixClient.idBaseUrl,
userId: this.matrixClient.credentials.userId,
deviceId: this.matrixClient.getDeviceId(),
accessToken: this.matrixClient.getAccessToken(),
guest: this.matrixClient.isGuest(),
};
}
_createClient(hs_url, is_url, user_id, access_token, isGuest) {
_createClient(creds: MatrixClientCreds) {
var opts = {
baseUrl: hs_url,
idBaseUrl: is_url,
accessToken: access_token,
userId: user_id,
baseUrl: creds.homeserverUrl,
idBaseUrl: creds.identityServerUrl,
accessToken: creds.accessToken,
userId: creds.userId,
deviceId: creds.deviceId,
timelineSupport: true,
};
if (localStorage) {
opts.sessionStore = new Matrix.WebStorageSessionStore(localStorage);
opts.deviceId = deviceId();
}
this.matrixClient = Matrix.createClient(opts);
@ -130,7 +103,7 @@ class MatrixClientPeg {
// potential number of event listeners is quite high.
this.matrixClient.setMaxListeners(500);
this.matrixClient.setGuest(Boolean(isGuest));
this.matrixClient.setGuest(Boolean(creds.guest));
}
}

164
src/RoomNotifs.js Normal file
View File

@ -0,0 +1,164 @@
/*
Copyright 2016 OpenMarket 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 MatrixClientPeg from './MatrixClientPeg';
import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
import q from 'q';
export const ALL_MESSAGES_LOUD = 'all_messages_loud';
export const ALL_MESSAGES = 'all_messages';
export const MENTIONS_ONLY = 'mentions_only';
export const MUTE = 'mute';
export function getRoomNotifsState(roomId) {
if (MatrixClientPeg.get().isGuest()) return RoomNotifs.ALL_MESSAGES;
// look through the override rules for a rule affecting this room:
// if one exists, it will take precedence.
const muteRule = findOverrideMuteRule(roomId);
if (muteRule) {
return MUTE;
}
// for everything else, look at the room rule.
const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId);
// XXX: We have to assume the default is to notify for all messages
// (in particular this will be 'wrong' for one to one rooms because
// they will notify loudly for all messages)
if (!roomRule || !roomRule.enabled) return ALL_MESSAGES;
// a mute at the room level will still allow mentions
// to notify
if (isMuteRule(roomRule)) return MENTIONS_ONLY;
const actionsObject = PushProcessor.actionListToActionsObject(roomRule.actions);
if (actionsObject.tweaks.sound) return ALL_MESSAGES_LOUD;
return null;
}
export function setRoomNotifsState(roomId, newState) {
if (newState == MUTE) {
return setRoomNotifsStateMuted(roomId);
} else {
return setRoomNotifsStateUnmuted(roomId, newState);
}
}
function setRoomNotifsStateMuted(roomId) {
const cli = MatrixClientPeg.get();
const promises = [];
// delete the room rule
const roomRule = cli.getRoomPushRule('global', roomId);
if (roomRule) {
promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id));
}
// add/replace an override rule to squelch everything in this room
// NB. We use the room ID as the name of this rule too, although this
// is an override rule, not a room rule: it still pertains to this room
// though, so using the room ID as the rule ID is logical and prevents
// duplicate copies of the rule.
promises.push(cli.addPushRule('global', 'override', roomId, {
conditions: [
{
kind: 'event_match',
key: 'room_id',
pattern: roomId,
}
],
actions: [
'dont_notify',
]
}));
return q.all(promises);
}
function setRoomNotifsStateUnmuted(roomId, newState) {
const cli = MatrixClientPeg.get();
const promises = [];
const overrideMuteRule = findOverrideMuteRule(roomId);
if (overrideMuteRule) {
promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id));
}
if (newState == 'all_messages') {
const roomRule = cli.getRoomPushRule('global', roomId);
if (roomRule) {
promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id));
}
} else if (newState == 'mentions_only') {
promises.push(cli.addPushRule('global', 'room', roomId, {
actions: [
'dont_notify',
]
}));
// https://matrix.org/jira/browse/SPEC-400
promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true));
} else if ('all_messages_loud') {
promises.push(cli.addPushRule('global', 'room', roomId, {
actions: [
'notify',
{
set_tweak: 'sound',
value: 'default',
}
]
}));
// https://matrix.org/jira/browse/SPEC-400
promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true));
}
return q.all(promises);
}
function findOverrideMuteRule(roomId) {
for (const rule of MatrixClientPeg.get().pushRules['global'].override) {
if (isRuleForRoom(roomId, rule)) {
if (isMuteRule(rule) && rule.enabled) {
return rule;
}
}
}
return null;
}
function isRuleForRoom(roomId, rule) {
if (rule.conditions.length !== 1) {
return false;
}
const cond = rule.conditions[0];
if (
cond.kind == 'event_match' &&
cond.key == 'room_id' &&
cond.pattern == roomId
) {
return true;
}
return false;
}
function isMuteRule(rule) {
return (
rule.actions.length == 1 &&
rule.actions[0] == 'dont_notify'
);
}

View File

@ -34,8 +34,10 @@ class ScalarAuthClient {
defer.reject(err);
} else if (response.statusCode / 100 !== 2) {
defer.reject({statusCode: response.statusCode});
} else if (!body || !body.scalar_token) {
defer.reject(new Error("Missing scalar_token in response"));
} else {
defer.resolve(body.access_token);
defer.resolve(body.scalar_token);
}
});

View File

@ -1,4 +1,7 @@
"use strict";
import Matrix from "matrix-js-sdk";
var MatrixClientPeg = require("./MatrixClientPeg");
var SignupStages = require("./SignupStages");
var dis = require("./dispatcher");
@ -11,9 +14,10 @@ const EMAIL_STAGE_TYPE = "m.login.email.identity";
* storage of HS/IS URLs.
*/
class Signup {
constructor(hsUrl, isUrl) {
constructor(hsUrl, isUrl, opts) {
this._hsUrl = hsUrl;
this._isUrl = isUrl;
this._defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
}
getHomeserverUrl() {
@ -31,14 +35,25 @@ class Signup {
setIdentityServerUrl(isUrl) {
this._isUrl = isUrl;
}
/**
* Get a temporary MatrixClient, which can be used for login or register
* requests.
*/
_createTemporaryClient() {
return Matrix.createClient({
baseUrl: this._hsUrl,
idBaseUrl: this._isUrl,
});
}
}
/**
* Registration logic class
*/
class Register extends Signup {
constructor(hsUrl, isUrl) {
super(hsUrl, isUrl);
constructor(hsUrl, isUrl, opts) {
super(hsUrl, isUrl, opts);
this.setStep("START");
this.data = null; // from the server
// random other stuff (e.g. query params, NOT params from the server)
@ -106,19 +121,11 @@ class Register extends Signup {
this.email = email;
this.username = username;
this.password = password;
// feels a bit wrong to be clobbering the global client for something we
// don't even know if it'll work, but we'll leave this here for now to
// not complicate matters further. It would be nicer to isolate this
// logic entirely from the rest of the app though.
MatrixClientPeg.replaceUsingUrls(
this._hsUrl,
this._isUrl
);
return this._tryRegister();
const client = this._createTemporaryClient();
return this._tryRegister(client);
}
_tryRegister(authDict, poll_for_success) {
_tryRegister(client, authDict, poll_for_success) {
var self = this;
var bindEmail;
@ -129,7 +136,8 @@ class Register extends Signup {
bindEmail = true;
}
return MatrixClientPeg.get().register(
// TODO need to figure out how to send the device display name to /register.
return client.register(
this.username, this.password, this.params.sessionId, authDict, bindEmail,
this.guestAccessToken
).then(function(result) {
@ -152,7 +160,7 @@ class Register extends Signup {
console.log("Active flow => %s", JSON.stringify(flow));
var flowStage = self.firstUncompletedStage(flow);
if (flowStage != self.activeStage) {
return self.startStage(flowStage).catch(function(err) {
return self._startStage(client, flowStage).catch(function(err) {
self.setStep('START');
throw err;
});
@ -161,7 +169,7 @@ class Register extends Signup {
}
if (poll_for_success) {
return q.delay(5000).then(function() {
return self._tryRegister(authDict, poll_for_success);
return self._tryRegister(client, authDict, poll_for_success);
});
} else {
throw new Error("Authorisation failed!");
@ -201,7 +209,7 @@ class Register extends Signup {
return completed.indexOf(stageType) !== -1;
}
startStage(stageName) {
_startStage(client, stageName) {
var self = this;
this.setStep(`STEP_${stageName}`);
var StageClass = SignupStages[stageName];
@ -210,12 +218,12 @@ class Register extends Signup {
throw new Error("Unknown stage: " + stageName);
}
var stage = new StageClass(MatrixClientPeg.get(), this);
var stage = new StageClass(client, this);
this.activeStage = stage;
return stage.complete().then(function(request) {
if (request.auth) {
console.log("Stage %s is returning an auth dict", stageName);
return self._tryRegister(request.auth, request.poll_for_success);
return self._tryRegister(client, request.auth, request.poll_for_success);
}
else {
// never resolve the promise chain. This is for things like email auth
@ -263,14 +271,6 @@ class Register extends Signup {
}
recheckState() {
// feels a bit wrong to be clobbering the global client for something we
// don't even know if it'll work, but we'll leave this here for now to
// not complicate matters further. It would be nicer to isolate this
// logic entirely from the rest of the app though.
MatrixClientPeg.replaceUsingUrls(
this._hsUrl,
this._isUrl
);
// We've been given a bunch of data from a previous register step,
// this only happens for email auth currently. It's kinda ming we need
// to know this though. A better solution would be to ask the stages if
@ -281,7 +281,8 @@ class Register extends Signup {
);
if (this.params.hasEmailInfo) {
this.registrationPromise = this.startStage(EMAIL_STAGE_TYPE);
const client = this._createTemporaryClient();
this.registrationPromise = this._startStage(client, EMAIL_STAGE_TYPE);
}
return this.registrationPromise;
}
@ -296,8 +297,8 @@ class Register extends Signup {
class Login extends Signup {
constructor(hsUrl, isUrl, fallbackHsUrl) {
super(hsUrl, isUrl);
constructor(hsUrl, isUrl, fallbackHsUrl, opts) {
super(hsUrl, isUrl, opts);
this._fallbackHsUrl = fallbackHsUrl;
this._currentFlowIndex = 0;
this._flows = [];
@ -305,15 +306,8 @@ class Login extends Signup {
getFlows() {
var self = this;
// feels a bit wrong to be clobbering the global client for something we
// don't even know if it'll work, but we'll leave this here for now to
// not complicate matters further. It would be nicer to isolate this
// logic entirely from the rest of the app though.
MatrixClientPeg.replaceUsingUrls(
this._hsUrl,
this._isUrl
);
return MatrixClientPeg.get().loginFlows().then(function(result) {
var client = this._createTemporaryClient();
return client.loginFlows().then(function(result) {
self._flows = result.flows;
self._currentFlowIndex = 0;
// technically the UI should display options for all flows for the
@ -334,10 +328,15 @@ class Login extends Signup {
}
loginAsGuest() {
MatrixClientPeg.replaceUsingUrls(this._hsUrl, this._isUrl);
return MatrixClientPeg.get().registerGuest().then((creds) => {
var client = this._createTemporaryClient();
return client.registerGuest({
body: {
initial_device_display_name: this._defaultDeviceDisplayName,
},
}).then((creds) => {
return {
userId: creds.user_id,
deviceId: creds.device_id,
accessToken: creds.access_token,
homeserverUrl: this._hsUrl,
identityServerUrl: this._isUrl,
@ -357,7 +356,8 @@ class Login extends Signup {
var self = this;
var isEmail = username.indexOf("@") > 0;
var loginParams = {
password: pass
password: pass,
initial_device_display_name: this._defaultDeviceDisplayName,
};
if (isEmail) {
loginParams.medium = 'email';
@ -366,11 +366,13 @@ class Login extends Signup {
loginParams.user = username;
}
return MatrixClientPeg.get().login('m.login.password', loginParams).then(function(data) {
var client = this._createTemporaryClient();
return client.login('m.login.password', loginParams).then(function(data) {
return q({
homeserverUrl: self._hsUrl,
identityServerUrl: self._isUrl,
userId: data.user_id,
deviceId: data.device_id,
accessToken: data.access_token
});
}, function(error) {
@ -384,25 +386,20 @@ class Login extends Signup {
'Incorrect username and/or password.'
);
if (self._fallbackHsUrl) {
// as per elsewhere, it would be much nicer to not replace the global
// client just to try an alternate HS
MatrixClientPeg.replaceUsingUrls(
self._fallbackHsUrl,
self._isUrl
);
return MatrixClientPeg.get().login('m.login.password', loginParams).then(function(data) {
var fbClient = Matrix.createClient({
baseUrl: self._fallbackHsUrl,
idBaseUrl: this._isUrl,
});
return fbClient.login('m.login.password', loginParams).then(function(data) {
return q({
homeserverUrl: self._fallbackHsUrl,
identityServerUrl: self._isUrl,
userId: data.user_id,
deviceId: data.device_id,
accessToken: data.access_token
});
}, function(fallback_error) {
// We also have to put the default back again if it fails...
MatrixClientPeg.replaceUsingUrls(
this._hsUrl,
this._isUrl
);
// throw the original error
throw error;
});

View File

@ -94,7 +94,7 @@ CommandEntry.fromCommands = function(commandArray) {
class MemberEntry extends Entry {
constructor(member) {
super(member.name || member.userId);
super((member.name || member.userId).replace(' (IRC)', ''));
this.member = member;
this.kind = 'member';
}

View File

@ -58,6 +58,10 @@ module.exports = React.createClass({
// called when the session load completes
onLoadCompleted: React.PropTypes.func,
// displayname, if any, to set on the device when logging
// in/registering.
defaultDeviceDisplayName: React.PropTypes.string,
},
PageTypes: {
@ -185,6 +189,7 @@ module.exports = React.createClass({
enableGuest: this.props.enableGuest,
guestHsUrl: this.getCurrentHsUrl(),
guestIsUrl: this.getCurrentIsUrl(),
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
}).done(()=>{
// stuff this through the dispatcher so that it happens
// after the on_logged_in action.
@ -193,7 +198,7 @@ module.exports = React.createClass({
},
componentWillUnmount: function() {
this._stopMatrixClient();
Lifecycle.stopMatrixClient();
dis.unregister(this.dispatcherRef);
document.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("focus", this.onFocus);
@ -601,16 +606,6 @@ module.exports = React.createClass({
});
},
// stop all the background processes related to the current client
_stopMatrixClient: function() {
Notifier.stop();
UserActivity.stop();
Presence.stop();
MatrixClientPeg.get().stopClient();
MatrixClientPeg.get().removeAllListeners();
MatrixClientPeg.unset();
},
onKeyDown: function(ev) {
/*
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
@ -935,10 +930,8 @@ module.exports = React.createClass({
var NewVersionBar = sdk.getComponent('globals.NewVersionBar');
var ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
// work out the HS URL prompts we should show for
console.log("rendering; loading="+this.state.loading+"; screen="+this.state.screen +
"; logged_in="+this.state.logged_in+"; ready="+this.state.ready);
// console.log("rendering; loading="+this.state.loading+"; screen="+this.state.screen +
// "; logged_in="+this.state.logged_in+"; ready="+this.state.ready);
if (this.state.loading) {
var Spinner = sdk.getComponent('elements.Spinner');
@ -1051,6 +1044,7 @@ module.exports = React.createClass({
customHsUrl={this.getCurrentHsUrl()}
customIsUrl={this.getCurrentIsUrl()}
registrationUrl={this.props.registrationUrl}
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
onLoggedIn={this.onRegistered}
onLoginClick={this.onLoginClick}
onRegisterClick={this.onRegisterClick}
@ -1077,6 +1071,7 @@ module.exports = React.createClass({
customHsUrl={this.getCurrentHsUrl()}
customIsUrl={this.getCurrentIsUrl()}
fallbackHsUrl={this.getFallbackHsUrl()}
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
onForgotPasswordClick={this.onForgotPasswordClick}
enableGuest={this.props.enableGuest}
onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}

View File

@ -44,6 +44,8 @@ module.exports = React.createClass({
// different home server without confusing users.
fallbackHsUrl: React.PropTypes.string,
defaultDeviceDisplayName: React.PropTypes.string,
// login shouldn't know or care how registration is done.
onRegisterClick: React.PropTypes.func.isRequired,
@ -136,7 +138,9 @@ module.exports = React.createClass({
var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;
var loginLogic = new Signup.Login(hsUrl, isUrl, fallbackHsUrl);
var loginLogic = new Signup.Login(hsUrl, isUrl, fallbackHsUrl, {
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
});
this._loginLogic = loginLogic;
loginLogic.getFlows().then(function(flows) {

View File

@ -45,6 +45,9 @@ module.exports = React.createClass({
email: React.PropTypes.string,
username: React.PropTypes.string,
guestAccessToken: React.PropTypes.string,
defaultDeviceDisplayName: React.PropTypes.string,
// registration shouldn't know or care how login is done.
onLoginClick: React.PropTypes.func.isRequired,
onCancelClick: React.PropTypes.func
@ -71,7 +74,9 @@ module.exports = React.createClass({
this.dispatcherRef = dis.register(this.onAction);
// attach this to the instance rather than this.state since it isn't UI
this.registerLogic = new Signup.Register(
this.props.customHsUrl, this.props.customIsUrl
this.props.customHsUrl, this.props.customIsUrl, {
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
}
);
this.registerLogic.setClientSecret(this.props.clientSecret);
this.registerLogic.setSessionId(this.props.sessionId);
@ -154,6 +159,7 @@ module.exports = React.createClass({
}
self.props.onLoggedIn({
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: self.registerLogic.getHomeserverUrl(),
identityServerUrl: self.registerLogic.getIdentityServerUrl(),
accessToken: response.access_token

View File

@ -60,7 +60,7 @@ module.exports = React.createClass({
return (
<span className="mx_MFileBody">
<div className="mx_MImageBody_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
<TintableSvg src="img/download.svg" width="12" height="14"/>
Download {text}
</a>

View File

@ -134,7 +134,7 @@ module.exports = React.createClass({
onMouseLeave={this.onImageLeave} />
</a>
<div className="mx_MImageBody_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
<TintableSvg src="img/download.svg" width="12" height="14"/>
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
</a>

View File

@ -123,7 +123,7 @@ module.exports = React.createClass({
<div className="mx_LinkPreviewWidget" >
{ img }
<div className="mx_LinkPreviewWidget_caption">
<div className="mx_LinkPreviewWidget_title"><a href={ this.props.link } target="_blank">{ p["og:title"] }</a></div>
<div className="mx_LinkPreviewWidget_title"><a href={ this.props.link } target="_blank" rel="noopener">{ p["og:title"] }</a></div>
<div className="mx_LinkPreviewWidget_siteName">{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }</div>
<div className="mx_LinkPreviewWidget_description" ref="description">
{ p["og:description"] }

View File

@ -67,6 +67,11 @@ module.exports = React.createClass({
componentWillMount: function() {
this._cancelDeviceList = null;
// only display the devices list if our client supports E2E *and* the
// feature is enabled in the user settings
this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() &&
UserSettingsStore.isFeatureEnabled("e2e_encryption");
this.setState({
existingOneToOneRoomId: this.getExistingOneToOneRoomId()
});
@ -147,6 +152,10 @@ module.exports = React.createClass({
},
onDeviceVerificationChanged: function(userId, device) {
if (!this._enableDevices) {
return;
}
if (userId == this.props.member.userId) {
// no need to re-download the whole thing; just update our copy of
// the list.
@ -170,6 +179,10 @@ module.exports = React.createClass({
},
_downloadDeviceList: function(member) {
if (!this._enableDevices) {
return;
}
var cancelled = false;
this._cancelDeviceList = function() { cancelled = true; }
@ -532,7 +545,7 @@ module.exports = React.createClass({
},
_renderDevices: function() {
if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) {
if (!this._enableDevices) {
return null;
}

View File

@ -180,7 +180,7 @@ module.exports = React.createClass({
},
_doInvite(address) {
Invite.inviteToRoom(self.props.roomId, address).catch((err) => {
Invite.inviteToRoom(this.props.roomId, address).catch((err) => {
if (err !== null) {
console.error("Failed to invite: %s", JSON.stringify(err));
if (err.errcode == 'M_FORBIDDEN') {
@ -196,7 +196,7 @@ module.exports = React.createClass({
}
}
}).finally(() => {
self.setState({
this.setState({
inviting: false
});
// XXX: hacky focus on the invite box
@ -207,7 +207,7 @@ module.exports = React.createClass({
}
}, 0);
}).done();
self.setState({
this.setState({
inviting: true
});
},
@ -283,7 +283,7 @@ module.exports = React.createClass({
if (inputs.length == 1) {
// for a single address, we just send the invite
promise.done(() => {
this.doInvite(inputs[0]);
this._doInvite(inputs[0]);
});
} else {
// if there are several, display the confirmation/progress dialog

View File

@ -47,16 +47,6 @@ module.exports = React.createClass({
tags[tagName] = ['yep'];
});
var areNotifsMuted = false;
if (!MatrixClientPeg.get().isGuest()) {
var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId);
if (roomPushRule) {
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
areNotifsMuted = true;
}
}
}
return {
name: this._yankValueFromEvent("m.room.name", "name"),
topic: this._yankValueFromEvent("m.room.topic", "topic"),
@ -66,7 +56,6 @@ module.exports = React.createClass({
power_levels_changed: false,
tags_changed: false,
tags: tags,
areNotifsMuted: areNotifsMuted,
// isRoomPublished is loaded async in componentWillMount so when the component
// inits, the saved value will always be undefined, however getInitialState()
// is also called from the saving code so we must return the correct value here
@ -188,12 +177,6 @@ module.exports = React.createClass({
}
if (this.state.areNotifsMuted !== originalState.areNotifsMuted) {
promises.push(MatrixClientPeg.get().setRoomMutePushRule(
"global", roomId, this.state.areNotifsMuted
));
}
// power levels
var powerLevels = this._getPowerLevels();
if (powerLevels) {
@ -647,12 +630,6 @@ module.exports = React.createClass({
{ tagsSection }
<div className="mx_RoomSettings_toggles">
<label>
<input type="checkbox" disabled={ cli.isGuest() }
onChange={this._onToggle.bind(this, "areNotifsMuted", true, false)}
defaultChecked={this.state.areNotifsMuted}/>
'Mention only' notifications for this room
</label>
<div className="mx_RoomSettings_settings">
<h3>Who can access this room?</h3>
{ inviteGuestWarning }

View File

@ -22,6 +22,7 @@ var dis = require("../../../dispatcher");
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
var ContextualMenu = require('../../structures/ContextualMenu');
var RoomNotifs = require('../../../RoomNotifs');
module.exports = React.createClass({
displayName: 'RoomTile',
@ -43,43 +44,41 @@ module.exports = React.createClass({
},
getInitialState: function() {
var areNotifsMuted = false;
var cli = MatrixClientPeg.get();
if (!cli.isGuest()) {
var roomPushRule = cli.getRoomPushRule("global", this.props.room.roomId);
if (roomPushRule) {
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
areNotifsMuted = true;
}
}
}
return({
hover : false,
badgeHover : false,
notificationTagMenu: false,
roomTagMenu: false,
areNotifsMuted: areNotifsMuted,
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
});
},
onAction: function(payload) {
switch (payload.action) {
case 'notification_change':
// Is the notification about this room?
if (payload.roomId === this.props.room.roomId) {
this.setState( { areNotifsMuted : payload.areNotifsMuted });
}
break;
_shouldShowNotifBadge: function() {
const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD];
return showBadgeInStates.indexOf(this.state.notifState) > -1;
},
_shouldShowMentionBadge: function() {
return this.state.notifState != RoomNotifs.MUTE;
},
onAccountData: function(accountDataEvent) {
if (accountDataEvent.getType() == 'm.push_rules') {
this.setState({
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
});
}
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
componentWillMount: function() {
MatrixClientPeg.get().on("accountData", this.onAccountData);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
var cli = MatrixClientPeg.get();
if (cli) {
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
}
},
onClick: function() {
@ -179,15 +178,19 @@ module.exports = React.createClass({
var notificationCount = this.props.room.getUnreadNotificationCount();
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
const badges = notifBadges || mentionBadges;
var classes = classNames({
'mx_RoomTile': true,
'mx_RoomTile_selected': this.props.selected,
'mx_RoomTile_unread': this.props.unread,
'mx_RoomTile_unreadNotify': notificationCount > 0 && !this.state.areNotifsMuted,
'mx_RoomTile_highlight': this.props.highlight,
'mx_RoomTile_unreadNotify': notifBadges,
'mx_RoomTile_highlight': mentionBadges,
'mx_RoomTile_invited': (me && me.membership == 'invite'),
'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu,
'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted))
'mx_RoomTile_noBadges': !badges,
});
var avatarClasses = classNames({
@ -214,7 +217,7 @@ module.exports = React.createClass({
if (this.state.badgeHover || this.state.notificationTagMenu) {
badgeContent = "\u00B7\u00B7\u00B7";
} else if (this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted)) {
} else if (badges) {
var limitedCount = (notificationCount > 99) ? '99+' : notificationCount;
badgeContent = notificationCount ? limitedCount : '!';
} else {
@ -230,7 +233,7 @@ module.exports = React.createClass({
var nameClasses = classNames({
'mx_RoomTile_name': true,
'mx_RoomTile_invite': this.props.isInvite,
'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted) || this.state.badgeHover || this.state.notificationTagMenu,
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.notificationTagMenu,
});
if (this.props.selected) {

View File

@ -52,7 +52,7 @@ export default class DevicesPanel extends React.Component {
(error) => {
if (this._unmounted) { return; }
var errtxt;
if (err.httpStatus == 404) {
if (error.httpStatus == 404) {
// 404 probably means the HS doesn't yet support the API.
errtxt = "Your home server does not support device management.";
} else {
@ -127,6 +127,7 @@ export default class DevicesPanel extends React.Component {
return (
<div className={classes}>
<div className="mx_DevicesPanel_header">
<div className="mx_DevicesPanel_deviceId">ID</div>
<div className="mx_DevicesPanel_deviceName">Name</div>
<div className="mx_DevicesPanel_deviceLastSeen">Last seen</div>
<div className="mx_DevicesPanel_deviceButtons"></div>

View File

@ -109,6 +109,9 @@ export default class DevicesPanelEntry extends React.Component {
return (
<div className="mx_DevicesPanel_device">
<div className="mx_DevicesPanel_deviceId">
{device.device_id}
</div>
<div className="mx_DevicesPanel_deviceName">
<EditableTextContainer initialValue={device.display_name}
onSubmit={this._onDisplayNameChanged}

View File

@ -137,6 +137,10 @@ matrixLinkify.options = {
}
},
linkAttributes: {
rel: 'noopener',
},
target: function(href, type) {
if (type === 'url') {
if (href.match(matrixLinkify.VECTOR_URL_PATTERN)) {

View File

@ -50,8 +50,7 @@ module.exports.stubClient = function() {
//
// 'sandbox.restore()' doesn't work correctly on inherited methods,
// so we do this for each method
var methods = ['get', 'unset', 'replaceUsingUrls',
'replaceUsingCreds'];
var methods = ['get', 'unset', 'replaceUsingCreds'];
for (var i = 0; i < methods.length; i++) {
sandbox.stub(peg, methods[i]);
}
@ -184,4 +183,3 @@ module.exports.mkStubRoom = function() {
},
};
};