Merge branch 'develop' into luke/feature-read-marker

This commit is contained in:
Luke Barnard 2017-04-18 14:45:18 +01:00
commit 4b5364e9ae
17 changed files with 465 additions and 44 deletions

View file

@ -82,4 +82,12 @@ export default class BasePlatform {
screenCaptureErrorString() {
return "Not implemented";
}
/**
* Restarts the application, without neccessarily reloading
* any application code
*/
reload() {
throw new Error("reload not implemented!");
}
}

View file

@ -50,6 +50,18 @@ class MatrixClientPeg {
this.opts = {
initialSyncLimit: 20,
};
this.indexedDbWorkerScript = null;
}
/**
* Sets the script href passed to the IndexedDB web worker
* If set, a separate web worker will be started to run the IndexedDB
* queries on.
*
* @param {string} script href to the script to be passed to the web worker
*/
setIndexedDbWorkerScript(script) {
this.indexedDbWorkerScript = script;
}
get(): MatrixClient {
@ -129,6 +141,7 @@ class MatrixClientPeg {
indexedDB: window.indexedDB,
dbName: "riot-web-sync",
localStorage: localStorage,
workerScript: this.indexedDbWorkerScript,
});
}

29
src/Roles.js Normal file
View file

@ -0,0 +1,29 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export const LEVEL_ROLE_MAP = {
undefined: 'Default',
0: 'User',
50: 'Moderator',
100: 'Admin',
};
export function textualPowerLevel(level, userDefault) {
if (LEVEL_ROLE_MAP[level]) {
return LEVEL_ROLE_MAP[level] + (level !== undefined ? ` (${level})` : ` (${userDefault})`);
} else {
return level;
}
}

View file

@ -17,6 +17,8 @@ limitations under the License.
var MatrixClientPeg = require("./MatrixClientPeg");
var CallHandler = require("./CallHandler");
import * as Roles from './Roles';
function textForMemberEvent(ev) {
// XXX: SYJS-16 "sender is sometimes null for join messages"
var senderName = ev.sender ? ev.sender.name : ev.getSender();
@ -182,6 +184,45 @@ function textForEncryptionEvent(event) {
return senderName + " turned on end-to-end encryption (algorithm " + event.getContent().algorithm + ")";
}
// Currently will only display a change if a user's power level is changed
function textForPowerEvent(event) {
const senderName = event.sender ? event.sender.name : event.getSender();
if (!event.getPrevContent() || !event.getPrevContent().users) {
return '';
}
const userDefault = event.getContent().users_default || 0;
// Construct set of userIds
let users = [];
Object.keys(event.getContent().users).forEach(
(userId) => {
if (users.indexOf(userId) === -1) users.push(userId);
}
);
Object.keys(event.getPrevContent().users).forEach(
(userId) => {
if (users.indexOf(userId) === -1) users.push(userId);
}
);
let diff = [];
users.forEach((userId) => {
// Previous power level
const from = event.getPrevContent().users[userId];
// Current power level
const to = event.getContent().users[userId];
if (to !== from) {
diff.push(
userId +
' from ' + Roles.textualPowerLevel(from, userDefault) +
' to ' + Roles.textualPowerLevel(to, userDefault)
);
}
});
if (!diff.length) {
return '';
}
return senderName + ' changed the power level of ' + diff.join(', ');
}
var handlers = {
'm.room.message': textForMessageEvent,
'm.room.name': textForRoomNameEvent,
@ -193,6 +234,7 @@ var handlers = {
'm.room.third_party_invite': textForThreePidInviteEvent,
'm.room.history_visibility': textForHistoryVisibilityEvent,
'm.room.encryption': textForEncryptionEvent,
'm.room.power_levels': textForPowerEvent,
};
module.exports = {

View file

@ -106,6 +106,17 @@ export default React.createClass({
var handled = false;
switch (ev.keyCode) {
case KeyCode.ESCAPE:
// Implemented this way so possible handling for other pages is neater
switch (this.props.page_type) {
case PageTypes.UserSettings:
this.props.onUserSettingsClose();
handled = true;
break;
}
break;
case KeyCode.UP:
case KeyCode.DOWN:
if (ev.altKey) {

View file

@ -763,9 +763,11 @@ module.exports = React.createClass({
});
if (teamToken) {
// A team member has logged in, not a guest
this._teamToken = teamToken;
dis.dispatch({action: 'view_home_page'});
} else if (this._is_registered) {
// The user has just logged in after registering
dis.dispatch({action: 'view_user_settings'});
} else {
this._showScreenAfterLogin();
@ -788,6 +790,10 @@ module.exports = React.createClass({
action: 'view_room',
room_id: localStorage.getItem('mx_last_room_id'),
});
} else if (this._teamToken) {
// Team token might be set if we're a guest.
// Guests do not call _onLoggedIn with a teamToken
dis.dispatch({action: 'view_home_page'});
} else {
dis.dispatch({action: 'view_room_directory'});
}

View file

@ -345,7 +345,7 @@ module.exports = React.createClass({
//
// If backwards is true, we unpaginate (remove) tiles from the back (top).
for (let i = 0; i < tiles.length; i++) {
const tile = tiles[backwards ? tiles.length - 1 - i : i];
const tile = tiles[backwards ? i : tiles.length - 1 - i];
// Subtract height of tile as if it were unpaginated
excessHeight -= tile.clientHeight;
// The tile may not have a scroll token, so guard it

View file

@ -264,10 +264,10 @@ module.exports = React.createClass({
title: "Sign out?",
description:
<div>
For security, logging out will delete any end-to-end encryption keys from this browser,
making previous encrypted chat history unreadable if you log back in.
In future this <a href="https://github.com/vector-im/riot-web/issues/2108">will be improved</a>,
but for now be warned.
For security, logging out will delete any end-to-end encryption keys from this browser.
If you want to be able to decrypt your conversation history from future Riot sessions,
please export your room keys for safe-keeping.
</div>,
button: "Sign out",
extraButtons: [
@ -441,10 +441,11 @@ module.exports = React.createClass({
},
_onClearCacheClicked: function() {
if (!PlatformPeg.get()) return;
MatrixClientPeg.get().stopClient();
MatrixClientPeg.get().store.deleteAllData().done(() => {
// forceReload=false since we don't really need new HTML/JS files
// we just need to restart the JS runtime.
window.location.reload(false);
PlatformPeg.get().reload();
});
},

View file

@ -93,11 +93,17 @@ module.exports = React.createClass({
description:
<div>
Resetting password will currently reset any end-to-end encryption keys on all devices,
making encrypted chat history unreadable.
In future this <a href="https://github.com/vector-im/riot-web/issues/2671">may be improved</a>,
but for now be warned.
making encrypted chat history unreadable, unless you first export your room keys
and re-import them afterwards.
In future this <a href="https://github.com/vector-im/riot-web/issues/2671">will be improved</a>.
</div>,
button: "Continue",
extraButtons: [
<button className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}>
Export E2E room keys
</button>
],
onFinished: (confirmed) => {
if (confirmed) {
this.submitPasswordReset(
@ -110,6 +116,18 @@ module.exports = React.createClass({
}
},
_onExportE2eKeysClicked: function() {
Modal.createDialogAsync(
(cb) => {
require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog'));
}, "e2e-export");
}, {
matrixClient: MatrixClientPeg.get(),
}
);
},
onInputChanged: function(stateKey, ev) {
this.setState({
[stateKey]: ev.target.value

View file

@ -32,6 +32,8 @@ export default function AccessibleButton(props) {
};
restProps.tabIndex = restProps.tabIndex || "0";
restProps.role = "button";
restProps.className = (restProps.className ? restProps.className + " " : "") +
"mx_AccessibleButton";
return React.createElement(element, restProps, children);
}

View file

@ -16,17 +16,12 @@ limitations under the License.
'use strict';
var React = require('react');
var roles = {
0: 'User',
50: 'Moderator',
100: 'Admin',
};
import React from 'react';
import * as Roles from '../../../Roles';
var reverseRoles = {};
Object.keys(roles).forEach(function(key) {
reverseRoles[roles[key]] = key;
Object.keys(Roles.LEVEL_ROLE_MAP).forEach(function(key) {
reverseRoles[Roles.LEVEL_ROLE_MAP[key]] = key;
});
module.exports = React.createClass({
@ -49,7 +44,7 @@ module.exports = React.createClass({
getInitialState: function() {
return {
custom: (roles[this.props.value] === undefined),
custom: (Roles.LEVEL_ROLE_MAP[this.props.value] === undefined),
};
},
@ -99,22 +94,34 @@ module.exports = React.createClass({
selectValue = "Custom";
}
else {
selectValue = roles[this.props.value] || "Custom";
selectValue = Roles.LEVEL_ROLE_MAP[this.props.value] || "Custom";
}
var select;
if (this.props.disabled) {
select = <span>{ selectValue }</span>;
}
else {
// Each level must have a definition in LEVEL_ROLE_MAP
const levels = [0, 50, 100];
let options = levels.map((level) => {
return {
value: Roles.LEVEL_ROLE_MAP[level],
// Give a userDefault (users_default in the power event) of 0 but
// because level !== undefined, this should never be used.
text: Roles.textualPowerLevel(level, 0),
}
});
options.push({ value: "Custom", text: "Custom level" });
options = options.map((op) => {
return <option value={op.value}>{op.text}</option>;
});
select =
<select ref="select"
value={ this.props.controlled ? selectValue : undefined }
defaultValue={ !this.props.controlled ? selectValue : undefined }
onChange={ this.onSelectChange }>
<option value="User">User (0)</option>
<option value="Moderator">Moderator (50)</option>
<option value="Admin">Admin (100)</option>
<option value="Custom">Custom level</option>
{ options }
</select>;
}

View file

@ -16,17 +16,18 @@ limitations under the License.
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var highlight = require('highlight.js');
var HtmlUtils = require('../../../HtmlUtils');
var linkify = require('linkifyjs');
var linkifyElement = require('linkifyjs/element');
var linkifyMatrix = require('../../../linkify-matrix');
var sdk = require('../../../index');
var ScalarAuthClient = require("../../../ScalarAuthClient");
var Modal = require("../../../Modal");
var SdkConfig = require('../../../SdkConfig');
import React from 'react';
import ReactDOM from 'react-dom';
import highlight from 'highlight.js';
import * as HtmlUtils from '../../../HtmlUtils';
import * as linkify from 'linkifyjs';
import linkifyElement from 'linkifyjs/element';
import linkifyMatrix from '../../../linkify-matrix';
import sdk from '../../../index';
import ScalarAuthClient from '../../../ScalarAuthClient';
import Modal from '../../../Modal';
import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher';
linkifyMatrix(linkify);
@ -131,7 +132,8 @@ module.exports = React.createClass({
links.push(node);
}
}
else if (node.tagName === "PRE" || node.tagName === "CODE") {
else if (node.tagName === "PRE" || node.tagName === "CODE" ||
node.tagName === "BLOCKQUOTE") {
continue;
}
else if (node.children && node.children.length) {
@ -187,6 +189,15 @@ module.exports = React.createClass({
this.forceUpdate();
},
onEmoteSenderClick: function(event) {
const mxEvent = this.props.mxEvent;
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
dis.dispatch({
action: 'insert_displayname',
displayname: name.replace(' (IRC)', ''),
});
},
getEventTileOps: function() {
var self = this;
return {
@ -273,7 +284,15 @@ module.exports = React.createClass({
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
return (
<span ref="content" className="mx_MEmoteBody mx_EventTile_content">
* <EmojiText>{name}</EmojiText> { body }
*&nbsp;
<EmojiText
className="mx_MEmoteBody_sender"
onClick={this.onEmoteSenderClick}
>
{name}
</EmojiText>
&nbsp;
{ body }
{ widgets }
</span>
);

View file

@ -40,6 +40,7 @@ var eventTileTypes = {
'm.room.third_party_invite' : 'messages.TextualEvent',
'm.room.history_visibility' : 'messages.TextualEvent',
'm.room.encryption' : 'messages.TextualEvent',
'm.room.power_levels' : 'messages.TextualEvent',
};
var MAX_READ_AVATARS = 5;

View file

@ -73,11 +73,17 @@ module.exports = React.createClass({
description:
<div>
Changing password will currently reset any end-to-end encryption keys on all devices,
making encrypted chat history unreadable.
This will be <a href="https://github.com/vector-im/riot-web/issues/2671">improved shortly</a>,
but for now be warned.
making encrypted chat history unreadable, unless you first export your room keys
and re-import them afterwards.
In future this <a href="https://github.com/vector-im/riot-web/issues/2671">will be improved</a>.
</div>,
button: "Continue",
extraButtons: [
<button className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}>
Export E2E room keys
</button>
],
onFinished: (confirmed) => {
if (confirmed) {
var authDict = {
@ -105,6 +111,18 @@ module.exports = React.createClass({
});
},
_onExportE2eKeysClicked: function() {
Modal.createDialogAsync(
(cb) => {
require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog'));
}, "e2e-export");
}, {
matrixClient: MatrixClientPeg.get(),
}
);
},
onClickChange: function() {
var old_password = this.refs.old_input.value;
var new_password = this.refs.new_input.value;

View file

@ -122,7 +122,7 @@ var escapeRegExp = function(string) {
// anyone else really should be using matrix.to.
matrixLinkify.VECTOR_URL_PATTERN = "^(?:https?:\/\/)?(?:"
+ escapeRegExp(window.location.host + window.location.pathname) + "|"
+ "(?:www\\.)?vector\\.im/(?:beta|staging|develop)/"
+ "(?:www\\.)?(?:riot|vector)\\.im/(?:beta|staging|develop)/"
+ ")(#.*)";
matrixLinkify.MATRIXTO_URL_PATTERN = "^(?:https?:\/\/)?(?:www\\.)?matrix\\.to/#/((#|@|!).*)";