mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' into webrtc_settings
commit
110ca22c4c
|
@ -64,7 +64,7 @@ module.exports = {
|
|||
// to JSX.
|
||||
ignorePattern: '^\\s*<',
|
||||
ignoreComments: true,
|
||||
code: 90,
|
||||
code: 120,
|
||||
}],
|
||||
"valid-jsdoc": ["warn"],
|
||||
"new-cap": ["warn"],
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
},
|
||||
"scripts": {
|
||||
"reskindex": "scripts/reskindex.js -h header",
|
||||
"build": "node scripts/babelcheck.js && babel src -d lib --source-maps",
|
||||
"start": "node scripts/babelcheck.js && babel src -w -d lib --source-maps",
|
||||
"build": "babel src -d lib --source-maps",
|
||||
"start": "babel src -w -d lib --source-maps",
|
||||
"lint": "eslint src/",
|
||||
"lintall": "eslint src/ test/",
|
||||
"clean": "rimraf lib",
|
||||
|
@ -53,7 +53,7 @@
|
|||
"draft-js-export-markdown": "^0.2.0",
|
||||
"emojione": "2.2.3",
|
||||
"file-saver": "^1.3.3",
|
||||
"filesize": "^3.1.2",
|
||||
"filesize": "3.5.6",
|
||||
"flux": "^2.0.3",
|
||||
"fuse.js": "^2.2.0",
|
||||
"glob": "^5.0.14",
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
// Makes sure the babel executable in the path is babel 6 (or greater), not
|
||||
// babel 5, which it is if you upgrade from an older version of react-sdk and
|
||||
// run 'npm install' since the package has changed to babel-cli, so 'babel'
|
||||
// remains installed and the executable in node_modules/.bin remains as babel
|
||||
// 5.
|
||||
|
||||
exec("babel -V", function (error, stdout, stderr) {
|
||||
if ((error && error.code) || parseInt(stdout.substr(0,1), 10) < 6) {
|
||||
console.log("\033[31m\033[1m"+
|
||||
'*****************************************\n'+
|
||||
'* matrix-react-sdk has moved to babel 6 *\n'+
|
||||
'* Please "rm -rf node_modules && npm i" *\n'+
|
||||
'* then restore links as appropriate *\n'+
|
||||
'*****************************************\n'+
|
||||
"\033[91m");
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
|
@ -22,8 +22,8 @@ module.exports = {
|
|||
avatarUrlForMember: function(member, width, height, resizeMethod) {
|
||||
var url = member.getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
width,
|
||||
height,
|
||||
Math.floor(width * window.devicePixelRatio),
|
||||
Math.floor(height * window.devicePixelRatio),
|
||||
resizeMethod,
|
||||
false,
|
||||
false
|
||||
|
@ -40,7 +40,9 @@ module.exports = {
|
|||
avatarUrlForUser: function(user, width, height, resizeMethod) {
|
||||
var url = ContentRepo.getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
|
||||
width, height, resizeMethod
|
||||
Math.floor(width * window.devicePixelRatio),
|
||||
Math.floor(height * window.devicePixelRatio),
|
||||
resizeMethod
|
||||
);
|
||||
if (!url || url.length === 0) {
|
||||
return null;
|
||||
|
@ -57,4 +59,3 @@ module.exports = {
|
|||
return 'img/' + images[total % images.length] + '.png';
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class ConstantTimeDispatcher {
|
|||
|
||||
dispatch(type, arg, params) {
|
||||
if (!this.listeners[type] || !this.listeners[type][arg]) {
|
||||
console.warn("No registered listeners for dispatch (type=" + type + ", arg=" + arg + ")");
|
||||
//console.warn("No registered listeners for dispatch (type=" + type + ", arg=" + arg + ")");
|
||||
return;
|
||||
}
|
||||
this.listeners[type][arg].forEach(listener=>{
|
||||
|
|
|
@ -49,7 +49,7 @@ import sdk from './index';
|
|||
* If any of steps 1-4 are successful, it will call {setLoggedIn}, which in
|
||||
* turn will raise on_logged_in and will_start_client events.
|
||||
*
|
||||
* It returns a promise which resolves when the above process completes.
|
||||
* @param {object} opts
|
||||
*
|
||||
* @param {object} opts.realQueryParams: string->string map of the
|
||||
* query-parameters extracted from the real query-string of the starting
|
||||
|
@ -67,6 +67,7 @@ import sdk from './index';
|
|||
* @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is
|
||||
* true; defines the IS to use.
|
||||
*
|
||||
* @returns {Promise} a promise which resolves when the above process completes.
|
||||
*/
|
||||
export function loadSession(opts) {
|
||||
const realQueryParams = opts.realQueryParams || {};
|
||||
|
@ -127,7 +128,7 @@ export function loadSession(opts) {
|
|||
|
||||
function _loginWithToken(queryParams, defaultDeviceDisplayName) {
|
||||
// create a temporary MatrixClient to do the login
|
||||
var client = Matrix.createClient({
|
||||
const client = Matrix.createClient({
|
||||
baseUrl: queryParams.homeserver,
|
||||
});
|
||||
|
||||
|
@ -159,7 +160,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
|||
// Not really sure where the right home for it is.
|
||||
|
||||
// create a temporary MatrixClient to do the login
|
||||
var client = Matrix.createClient({
|
||||
const client = Matrix.createClient({
|
||||
baseUrl: hsUrl,
|
||||
});
|
||||
|
||||
|
@ -188,30 +189,30 @@ function _restoreFromLocalStorage() {
|
|||
if (!localStorage) {
|
||||
return q(false);
|
||||
}
|
||||
const hs_url = localStorage.getItem("mx_hs_url");
|
||||
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");
|
||||
const hsUrl = localStorage.getItem("mx_hs_url");
|
||||
const isUrl = localStorage.getItem("mx_is_url") || 'https://matrix.org';
|
||||
const accessToken = localStorage.getItem("mx_access_token");
|
||||
const userId = localStorage.getItem("mx_user_id");
|
||||
const deviceId = localStorage.getItem("mx_device_id");
|
||||
|
||||
let is_guest;
|
||||
let isGuest;
|
||||
if (localStorage.getItem("mx_is_guest") !== null) {
|
||||
is_guest = localStorage.getItem("mx_is_guest") === "true";
|
||||
isGuest = localStorage.getItem("mx_is_guest") === "true";
|
||||
} else {
|
||||
// legacy key name
|
||||
is_guest = localStorage.getItem("matrix-is-guest") === "true";
|
||||
isGuest = localStorage.getItem("matrix-is-guest") === "true";
|
||||
}
|
||||
|
||||
if (access_token && user_id && hs_url) {
|
||||
console.log("Restoring session for %s", user_id);
|
||||
if (accessToken && userId && hsUrl) {
|
||||
console.log("Restoring session for %s", userId);
|
||||
try {
|
||||
setLoggedIn({
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
accessToken: access_token,
|
||||
homeserverUrl: hs_url,
|
||||
identityServerUrl: is_url,
|
||||
guest: is_guest,
|
||||
userId: userId,
|
||||
deviceId: deviceId,
|
||||
accessToken: accessToken,
|
||||
homeserverUrl: hsUrl,
|
||||
identityServerUrl: isUrl,
|
||||
guest: isGuest,
|
||||
});
|
||||
return q(true);
|
||||
} catch (e) {
|
||||
|
@ -273,9 +274,13 @@ export function initRtsClient(url) {
|
|||
*/
|
||||
export function setLoggedIn(credentials) {
|
||||
credentials.guest = Boolean(credentials.guest);
|
||||
console.log("setLoggedIn => %s (guest=%s) hs=%s",
|
||||
credentials.userId, credentials.guest,
|
||||
credentials.homeserverUrl);
|
||||
|
||||
console.log(
|
||||
"setLoggedIn: mxid:", credentials.userId,
|
||||
"deviceId:", credentials.deviceId,
|
||||
"guest:", credentials.guest,
|
||||
"hs:", credentials.homeserverUrl,
|
||||
);
|
||||
// This is dispatched to indicate that the user is still in the process of logging in
|
||||
// because `teamPromise` may take some time to resolve, breaking the assumption that
|
||||
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
|
||||
|
@ -352,7 +357,7 @@ export function logout() {
|
|||
return;
|
||||
}
|
||||
|
||||
return MatrixClientPeg.get().logout().then(onLoggedOut,
|
||||
MatrixClientPeg.get().logout().then(onLoggedOut,
|
||||
(err) => {
|
||||
// Just throwing an error here is going to be very unhelpful
|
||||
// if you're trying to log out because your server's down and
|
||||
|
@ -363,8 +368,8 @@ export function logout() {
|
|||
// change your password).
|
||||
console.log("Failed to call logout API: token will not be invalidated");
|
||||
onLoggedOut();
|
||||
}
|
||||
);
|
||||
},
|
||||
).done();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -420,7 +425,7 @@ export function stopMatrixClient() {
|
|||
UserActivity.stop();
|
||||
Presence.stop();
|
||||
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
||||
var cli = MatrixClientPeg.get();
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
cli.stopClient();
|
||||
cli.removeAllListeners();
|
||||
|
|
|
@ -15,11 +15,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||
var PlatformPeg = require("./PlatformPeg");
|
||||
var TextForEvent = require('./TextForEvent');
|
||||
var Avatar = require('./Avatar');
|
||||
var dis = require("./dispatcher");
|
||||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import PlatformPeg from './PlatformPeg';
|
||||
import TextForEvent from './TextForEvent';
|
||||
import Avatar from './Avatar';
|
||||
import dis from './dispatcher';
|
||||
import sdk from './index';
|
||||
import Modal from './Modal';
|
||||
|
||||
/*
|
||||
* Dispatches:
|
||||
|
@ -29,7 +31,7 @@ var dis = require("./dispatcher");
|
|||
* }
|
||||
*/
|
||||
|
||||
var Notifier = {
|
||||
const Notifier = {
|
||||
notifsByRoom: {},
|
||||
|
||||
notificationMessageForEvent: function(ev) {
|
||||
|
@ -48,16 +50,16 @@ var Notifier = {
|
|||
return;
|
||||
}
|
||||
|
||||
var msg = this.notificationMessageForEvent(ev);
|
||||
let msg = this.notificationMessageForEvent(ev);
|
||||
if (!msg) return;
|
||||
|
||||
var title;
|
||||
if (!ev.sender || room.name == ev.sender.name) {
|
||||
let title;
|
||||
if (!ev.sender || room.name === ev.sender.name) {
|
||||
title = room.name;
|
||||
// notificationMessageForEvent includes sender,
|
||||
// but we already have the sender here
|
||||
if (ev.getContent().body) msg = ev.getContent().body;
|
||||
} else if (ev.getType() == 'm.room.member') {
|
||||
} else if (ev.getType() === 'm.room.member') {
|
||||
// context is all in the message here, we don't need
|
||||
// to display sender info
|
||||
title = room.name;
|
||||
|
@ -68,7 +70,7 @@ var Notifier = {
|
|||
if (ev.getContent().body) msg = ev.getContent().body;
|
||||
}
|
||||
|
||||
var avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
|
||||
const avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
|
||||
ev.sender, 40, 40, 'crop'
|
||||
) : null;
|
||||
|
||||
|
@ -83,7 +85,7 @@ var Notifier = {
|
|||
},
|
||||
|
||||
_playAudioNotification: function(ev, room) {
|
||||
var e = document.getElementById("messageAudio");
|
||||
const e = document.getElementById("messageAudio");
|
||||
if (e) {
|
||||
e.load();
|
||||
e.play();
|
||||
|
@ -95,7 +97,7 @@ var Notifier = {
|
|||
this.boundOnSyncStateChange = this.onSyncStateChange.bind(this);
|
||||
this.boundOnRoomReceipt = this.onRoomReceipt.bind(this);
|
||||
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
|
||||
MatrixClientPeg.get().on("Room.receipt", this.boundOnRoomReceipt);
|
||||
MatrixClientPeg.get().on('Room.receipt', this.boundOnRoomReceipt);
|
||||
MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
|
||||
this.toolbarHidden = false;
|
||||
this.isSyncing = false;
|
||||
|
@ -104,7 +106,7 @@ var Notifier = {
|
|||
stop: function() {
|
||||
if (MatrixClientPeg.get() && this.boundOnRoomTimeline) {
|
||||
MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline);
|
||||
MatrixClientPeg.get().removeListener("Room.receipt", this.boundOnRoomReceipt);
|
||||
MatrixClientPeg.get().removeListener('Room.receipt', this.boundOnRoomReceipt);
|
||||
MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange);
|
||||
}
|
||||
this.isSyncing = false;
|
||||
|
@ -121,7 +123,7 @@ var Notifier = {
|
|||
// make sure that we persist the current setting audio_enabled setting
|
||||
// before changing anything
|
||||
if (global.localStorage) {
|
||||
if(global.localStorage.getItem('audio_notifications_enabled') == null) {
|
||||
if (global.localStorage.getItem('audio_notifications_enabled') === null) {
|
||||
this.setAudioEnabled(this.isEnabled());
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +133,16 @@ var Notifier = {
|
|||
plaf.requestNotificationPermission().done((result) => {
|
||||
if (result !== 'granted') {
|
||||
// The permission request was dismissed or denied
|
||||
const description = result === 'denied'
|
||||
? 'Riot does not have permission to send you notifications'
|
||||
+ ' - please check your browser settings'
|
||||
: 'Riot was not given permission to send notifications'
|
||||
+ ' - please try again';
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: 'Unable to enable Notifications',
|
||||
description,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -141,7 +153,7 @@ var Notifier = {
|
|||
if (callback) callback();
|
||||
dis.dispatch({
|
||||
action: "notifier_enabled",
|
||||
value: true
|
||||
value: true,
|
||||
});
|
||||
});
|
||||
// clear the notifications_hidden flag, so that if notifications are
|
||||
|
@ -152,7 +164,7 @@ var Notifier = {
|
|||
global.localStorage.setItem('notifications_enabled', 'false');
|
||||
dis.dispatch({
|
||||
action: "notifier_enabled",
|
||||
value: false
|
||||
value: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -165,7 +177,7 @@ var Notifier = {
|
|||
|
||||
if (!global.localStorage) return true;
|
||||
|
||||
var enabled = global.localStorage.getItem('notifications_enabled');
|
||||
const enabled = global.localStorage.getItem('notifications_enabled');
|
||||
if (enabled === null) return true;
|
||||
return enabled === 'true';
|
||||
},
|
||||
|
@ -178,7 +190,7 @@ var Notifier = {
|
|||
|
||||
isAudioEnabled: function(enable) {
|
||||
if (!global.localStorage) return true;
|
||||
var enabled = global.localStorage.getItem(
|
||||
const enabled = global.localStorage.getItem(
|
||||
'audio_notifications_enabled');
|
||||
// default to true if the popups are enabled
|
||||
if (enabled === null) return this.isEnabled();
|
||||
|
@ -192,7 +204,7 @@ var Notifier = {
|
|||
// this is nothing to do with notifier_enabled
|
||||
dis.dispatch({
|
||||
action: "notifier_enabled",
|
||||
value: this.isEnabled()
|
||||
value: this.isEnabled(),
|
||||
});
|
||||
|
||||
// update the info to localStorage for persistent settings
|
||||
|
@ -215,8 +227,7 @@ var Notifier = {
|
|||
onSyncStateChange: function(state) {
|
||||
if (state === "SYNCING") {
|
||||
this.isSyncing = true;
|
||||
}
|
||||
else if (state === "STOPPED" || state === "ERROR") {
|
||||
} else if (state === "STOPPED" || state === "ERROR") {
|
||||
this.isSyncing = false;
|
||||
}
|
||||
},
|
||||
|
@ -225,10 +236,10 @@ var Notifier = {
|
|||
if (toStartOfTimeline) return;
|
||||
if (!room) return;
|
||||
if (!this.isSyncing) return; // don't alert for any messages initially
|
||||
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;
|
||||
if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return;
|
||||
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||
|
||||
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||
if (actions && actions.notify) {
|
||||
if (this.isEnabled()) {
|
||||
this._displayPopupNotification(ev, room);
|
||||
|
@ -240,7 +251,7 @@ var Notifier = {
|
|||
},
|
||||
|
||||
onRoomReceipt: function(ev, room) {
|
||||
if (room.getUnreadNotificationCount() == 0) {
|
||||
if (room.getUnreadNotificationCount() === 0) {
|
||||
// ideally we would clear each notification when it was read,
|
||||
// but we have no way, given a read receipt, to know whether
|
||||
// the receipt comes before or after an event, so we can't
|
||||
|
@ -255,7 +266,7 @@ var Notifier = {
|
|||
}
|
||||
delete this.notifsByRoom[room.roomId];
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (!global.mxNotifier) {
|
||||
|
|
|
@ -32,7 +32,7 @@ class UserActivity {
|
|||
start() {
|
||||
document.onmousedown = this._onUserActivity.bind(this);
|
||||
document.onmousemove = this._onUserActivity.bind(this);
|
||||
document.onkeypress = this._onUserActivity.bind(this);
|
||||
document.onkeydown = this._onUserActivity.bind(this);
|
||||
// can't use document.scroll here because that's only the document
|
||||
// itself being scrolled. Need to use addEventListener's useCapture.
|
||||
// also this needs to be the wheel event, not scroll, as scroll is
|
||||
|
@ -50,7 +50,7 @@ class UserActivity {
|
|||
stop() {
|
||||
document.onmousedown = undefined;
|
||||
document.onmousemove = undefined;
|
||||
document.onkeypress = undefined;
|
||||
document.onkeydown = undefined;
|
||||
window.removeEventListener('wheel', this._onUserActivity.bind(this),
|
||||
{ passive: true, capture: true });
|
||||
}
|
||||
|
|
|
@ -226,10 +226,8 @@ export default React.createClass({
|
|||
case PageTypes.RoomDirectory:
|
||||
page_element = <RoomDirectory
|
||||
ref="roomDirectory"
|
||||
collapsedRhs={this.props.collapse_rhs}
|
||||
config={this.props.config.roomDirectory}
|
||||
/>;
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
|
||||
break;
|
||||
|
||||
case PageTypes.HomePage:
|
||||
|
|
|
@ -282,15 +282,16 @@ module.exports = React.createClass({
|
|||
var isMembershipChange = (e) => e.getType() === 'm.room.member';
|
||||
|
||||
for (i = 0; i < this.props.events.length; i++) {
|
||||
var mxEv = this.props.events[i];
|
||||
var wantTile = true;
|
||||
var eventId = mxEv.getId();
|
||||
let mxEv = this.props.events[i];
|
||||
let wantTile = true;
|
||||
let eventId = mxEv.getId();
|
||||
let readMarkerInMels = false;
|
||||
|
||||
if (!EventTile.haveTileForEvent(mxEv)) {
|
||||
wantTile = false;
|
||||
}
|
||||
|
||||
var last = (i == lastShownEventIndex);
|
||||
let last = (i == lastShownEventIndex);
|
||||
|
||||
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
||||
if (isMembershipChange(mxEv) &&
|
||||
|
@ -332,6 +333,9 @@ module.exports = React.createClass({
|
|||
|
||||
let eventTiles = summarisedEvents.map(
|
||||
(e) => {
|
||||
if (e.getId() === this.props.readMarkerEventId) {
|
||||
readMarkerInMels = true;
|
||||
}
|
||||
// In order to prevent DateSeparators from appearing in the expanded form
|
||||
// of MemberEventListSummary, render each member event as if the previous
|
||||
// one was itself. This way, the timestamp of the previous event === the
|
||||
|
@ -350,12 +354,16 @@ module.exports = React.createClass({
|
|||
<MemberEventListSummary
|
||||
key={key}
|
||||
events={summarisedEvents}
|
||||
data-scroll-token={eventId}
|
||||
onToggle={this._onWidgetLoad} // Update scroll state
|
||||
>
|
||||
{eventTiles}
|
||||
</MemberEventListSummary>
|
||||
);
|
||||
|
||||
if (readMarkerInMels) {
|
||||
ret.push(this._getReadMarkerTile(visible));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -464,7 +472,7 @@ module.exports = React.createClass({
|
|||
ret.push(
|
||||
<li key={eventId}
|
||||
ref={this._collectEventNode.bind(this, eventId)}
|
||||
data-scroll-token={scrollToken}>
|
||||
data-scroll-tokens={scrollToken}>
|
||||
<EventTile mxEvent={mxEv} continuation={continuation}
|
||||
isRedacted={mxEv.isRedacted()}
|
||||
onWidgetLoad={this._onWidgetLoad}
|
||||
|
|
|
@ -1276,13 +1276,7 @@ module.exports = React.createClass({
|
|||
return;
|
||||
}
|
||||
|
||||
var pos = this.refs.messagePanel.getReadMarkerPosition();
|
||||
|
||||
// we want to show the bar if the read-marker is off the top of the
|
||||
// screen.
|
||||
// If pos is null, the event might not be paginated, so show the unread bar!
|
||||
var showBar = pos < 0 || pos === null;
|
||||
|
||||
const showBar = this.refs.messagePanel.canJumpToReadMarker();
|
||||
if (this.state.showTopUnreadMessagesBar != showBar) {
|
||||
this.setState({showTopUnreadMessagesBar: showBar},
|
||||
this.onChildResize);
|
||||
|
|
|
@ -46,9 +46,13 @@ if (DEBUG_SCROLL) {
|
|||
* It also provides a hook which allows parents to provide more list elements
|
||||
* when we get close to the start or end of the list.
|
||||
*
|
||||
* Each child element should have a 'data-scroll-token'. This token is used to
|
||||
* serialise the scroll state, and returned as the 'trackedScrollToken'
|
||||
* attribute by getScrollState().
|
||||
* Each child element should have a 'data-scroll-tokens'. This string of
|
||||
* comma-separated tokens may contain a single token or many, where many indicates
|
||||
* that the element contains elements that have scroll tokens themselves. The first
|
||||
* token in 'data-scroll-tokens' is used to serialise the scroll state, and returned
|
||||
* as the 'trackedScrollToken' attribute by getScrollState().
|
||||
*
|
||||
* IMPORTANT: INDIVIDUAL TOKENS WITHIN 'data-scroll-tokens' MUST NOT CONTAIN COMMAS.
|
||||
*
|
||||
* Some notes about the implementation:
|
||||
*
|
||||
|
@ -349,8 +353,8 @@ module.exports = React.createClass({
|
|||
// Subtract height of tile as if it were unpaginated
|
||||
excessHeight -= tile.clientHeight;
|
||||
// The tile may not have a scroll token, so guard it
|
||||
if (tile.dataset.scrollToken) {
|
||||
markerScrollToken = tile.dataset.scrollToken;
|
||||
if (tile.dataset.scrollTokens) {
|
||||
markerScrollToken = tile.dataset.scrollTokens.split(',')[0];
|
||||
}
|
||||
if (tile.clientHeight > excessHeight) {
|
||||
break;
|
||||
|
@ -419,7 +423,8 @@ module.exports = React.createClass({
|
|||
* scroll. false if we are tracking a particular child.
|
||||
*
|
||||
* string trackedScrollToken: undefined if stuckAtBottom is true; if it is
|
||||
* false, the data-scroll-token of the child which we are tracking.
|
||||
* false, the first token in data-scroll-tokens of the child which we are
|
||||
* tracking.
|
||||
*
|
||||
* number pixelOffset: undefined if stuckAtBottom is true; if it is false,
|
||||
* the number of pixels the bottom of the tracked child is above the
|
||||
|
@ -551,8 +556,10 @@ module.exports = React.createClass({
|
|||
var messages = this.refs.itemlist.children;
|
||||
for (var i = messages.length-1; i >= 0; --i) {
|
||||
var m = messages[i];
|
||||
if (!m.dataset.scrollToken) continue;
|
||||
if (m.dataset.scrollToken == scrollToken) {
|
||||
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
|
||||
// There might only be one scroll token
|
||||
if (m.dataset.scrollTokens &&
|
||||
m.dataset.scrollTokens.split(',').indexOf(scrollToken) !== -1) {
|
||||
node = m;
|
||||
break;
|
||||
}
|
||||
|
@ -568,7 +575,7 @@ module.exports = React.createClass({
|
|||
var boundingRect = node.getBoundingClientRect();
|
||||
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
|
||||
|
||||
debuglog("ScrollPanel: scrolling to token '" + node.dataset.scrollToken + "'+" +
|
||||
debuglog("ScrollPanel: scrolling to token '" + scrollToken + "'+" +
|
||||
pixelOffset + " (delta: "+scrollDelta+")");
|
||||
|
||||
if(scrollDelta != 0) {
|
||||
|
@ -591,12 +598,12 @@ module.exports = React.createClass({
|
|||
|
||||
for (var i = messages.length-1; i >= 0; --i) {
|
||||
var node = messages[i];
|
||||
if (!node.dataset.scrollToken) continue;
|
||||
if (!node.dataset.scrollTokens) continue;
|
||||
|
||||
var boundingRect = node.getBoundingClientRect();
|
||||
newScrollState = {
|
||||
stuckAtBottom: false,
|
||||
trackedScrollToken: node.dataset.scrollToken,
|
||||
trackedScrollToken: node.dataset.scrollTokens.split(',')[0],
|
||||
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
|
||||
};
|
||||
// If the bottom of the panel intersects the ClientRect of node, use this node
|
||||
|
@ -608,7 +615,7 @@ module.exports = React.createClass({
|
|||
break;
|
||||
}
|
||||
}
|
||||
// This is only false if there were no nodes with `node.dataset.scrollToken` set.
|
||||
// This is only false if there were no nodes with `node.dataset.scrollTokens` set.
|
||||
if (newScrollState) {
|
||||
this.scrollState = newScrollState;
|
||||
debuglog("ScrollPanel: saved scroll state", this.scrollState);
|
||||
|
|
|
@ -170,7 +170,7 @@ var TimelinePanel = React.createClass({
|
|||
forwardPaginating: false,
|
||||
|
||||
// cache of matrixClient.getSyncState() (but from the 'sync' event)
|
||||
clientSyncState: null,
|
||||
clientSyncState: MatrixClientPeg.get().getSyncState(),
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -503,7 +503,9 @@ var TimelinePanel = React.createClass({
|
|||
// This happens on user_activity_end which is delayed, and it's
|
||||
// very possible have logged out within that timeframe, so check
|
||||
// we still have a client.
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
const cli = MatrixClientPeg.get();
|
||||
// if no client or client is guest don't send RR
|
||||
if (!cli || cli.isGuest()) return;
|
||||
|
||||
var currentReadUpToEventId = this._getCurrentReadReceipt(true);
|
||||
var currentReadUpToEventIndex = this._indexForEventId(currentReadUpToEventId);
|
||||
|
@ -766,6 +768,19 @@ var TimelinePanel = React.createClass({
|
|||
return null;
|
||||
},
|
||||
|
||||
canJumpToReadMarker: function() {
|
||||
// 1. Do not show jump bar if neither the RM nor the RR are set.
|
||||
// 2. Only show jump bar if RR !== RM. If they are the same, there are only fully
|
||||
// read messages and unread messages. We already have a badge count and the bottom
|
||||
// bar to jump to "live" when we have unread messages.
|
||||
// 3. We want to show the bar if the read-marker is off the top of the screen.
|
||||
// 4. Also, if pos === null, the event might not be paginated - show the unread bar
|
||||
const pos = this.getReadMarkerPosition();
|
||||
return this.state.readMarkerEventId !== null && // 1.
|
||||
this.state.readMarkerEventId !== this._getCurrentReadReceipt() && // 2.
|
||||
(pos < 0 || pos === null); // 3., 4.
|
||||
},
|
||||
|
||||
/**
|
||||
* called by the parent component when PageUp/Down/etc is pressed.
|
||||
*
|
||||
|
|
|
@ -14,32 +14,40 @@ 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.
|
||||
*/
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
var sdk = require('../../index');
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var PlatformPeg = require("../../PlatformPeg");
|
||||
var Modal = require('../../Modal');
|
||||
var dis = require("../../dispatcher");
|
||||
var q = require('q');
|
||||
var package_json = require('../../../package.json');
|
||||
var UserSettingsStore = require('../../UserSettingsStore');
|
||||
var CallMediaHandler = require('../../CallMediaHandler');
|
||||
var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
var Email = require('../../email');
|
||||
var AddThreepid = require('../../AddThreepid');
|
||||
var SdkConfig = require('../../SdkConfig');
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const sdk = require('../../index');
|
||||
const MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
const PlatformPeg = require("../../PlatformPeg");
|
||||
const Modal = require('../../Modal');
|
||||
const dis = require("../../dispatcher");
|
||||
const q = require('q');
|
||||
const packageJson = require('../../../package.json');
|
||||
const UserSettingsStore = require('../../UserSettingsStore');
|
||||
const GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
const Email = require('../../email');
|
||||
const AddThreepid = require('../../AddThreepid');
|
||||
const SdkConfig = require('../../SdkConfig');
|
||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
|
||||
// if this looks like a release, use the 'version' from package.json; else use
|
||||
// the git sha. Prepend version with v, to look like riot-web version
|
||||
const REACT_SDK_VERSION = 'dist' in package_json ? `v${package_json.version}` : package_json.gitHead || '<local>';
|
||||
const REACT_SDK_VERSION = 'dist' in packageJson ? packageJson.version : packageJson.gitHead || '<local>';
|
||||
|
||||
// Simple method to help prettify GH Release Tags and Commit Hashes.
|
||||
const GHVersionUrl = function(repo, token) {
|
||||
const uriTail = (token.startsWith('v') && token.includes('.')) ? `releases/tag/${token}` : `commit/${token}`;
|
||||
return `https://github.com/${repo}/${uriTail}`;
|
||||
const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i;
|
||||
const gHVersionLabel = function(repo, token) {
|
||||
const match = token.match(semVerRegex);
|
||||
let url;
|
||||
if (match && match[1]) { // basic semVer string possibly with commit hash
|
||||
url = (match.length > 1 && match[2])
|
||||
? `https://github.com/${repo}/commit/${match[2]}`
|
||||
: `https://github.com/${repo}/releases/tag/v${match[1]}`;
|
||||
} else {
|
||||
url = `https://github.com/${repo}/commit/${token.split('-')[0]}`;
|
||||
}
|
||||
return <a href={url}>{token}</a>;
|
||||
};
|
||||
|
||||
// Enumerate some simple 'flip a bit' UI settings (if any).
|
||||
// 'id' gives the key name in the im.vector.web.settings account data event
|
||||
|
@ -51,7 +59,7 @@ const SETTINGS_LABELS = [
|
|||
},
|
||||
{
|
||||
id: 'hideReadReceipts',
|
||||
label: 'Hide read receipts'
|
||||
label: 'Hide read receipts',
|
||||
},
|
||||
{
|
||||
id: 'dontSendTypingNotifications',
|
||||
|
@ -107,7 +115,7 @@ const THEMES = [
|
|||
id: 'theme',
|
||||
label: 'Dark theme',
|
||||
value: 'dark',
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -193,7 +201,7 @@ module.exports = React.createClass({
|
|||
});
|
||||
this._refreshFromServer();
|
||||
|
||||
var syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||
const syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||
if (!syncedSettings.theme) {
|
||||
syncedSettings.theme = 'light';
|
||||
}
|
||||
|
@ -217,16 +225,16 @@ module.exports = React.createClass({
|
|||
middleOpacity: 1.0,
|
||||
});
|
||||
dis.unregister(this.dispatcherRef);
|
||||
let cli = MatrixClientPeg.get();
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
cli.removeListener("RoomMember.membership", this._onInviteStateChange);
|
||||
}
|
||||
},
|
||||
|
||||
_refreshFromServer: function() {
|
||||
var self = this;
|
||||
const self = this;
|
||||
q.all([
|
||||
UserSettingsStore.loadProfileInfo(), UserSettingsStore.loadThreePids()
|
||||
UserSettingsStore.loadProfileInfo(), UserSettingsStore.loadThreePids(),
|
||||
]).done(function(resps) {
|
||||
self.setState({
|
||||
avatarUrl: resps[0].avatar_url,
|
||||
|
@ -234,7 +242,7 @@ module.exports = React.createClass({
|
|||
phase: "UserSettings.DISPLAY",
|
||||
});
|
||||
}, function(error) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to load user settings: " + error);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Can't load user settings",
|
||||
|
@ -251,7 +259,7 @@ module.exports = React.createClass({
|
|||
|
||||
onAvatarPickerClick: function(ev) {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Please Register",
|
||||
description: "Guests can't set avatars. Please register.",
|
||||
|
@ -265,8 +273,8 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onAvatarSelected: function(ev) {
|
||||
var self = this;
|
||||
var changeAvatar = this.refs.changeAvatar;
|
||||
const self = this;
|
||||
const changeAvatar = this.refs.changeAvatar;
|
||||
if (!changeAvatar) {
|
||||
console.error("No ChangeAvatar found to upload image to!");
|
||||
return;
|
||||
|
@ -275,9 +283,9 @@ module.exports = React.createClass({
|
|||
// dunno if the avatar changed, re-check it.
|
||||
self._refreshFromServer();
|
||||
}, function(err) {
|
||||
var errMsg = (typeof err === "string") ? err : (err.error || "");
|
||||
// const errMsg = (typeof err === "string") ? err : (err.error || "");
|
||||
console.error("Failed to set avatar: " + err);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to set avatar",
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
|
@ -286,7 +294,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onLogoutClicked: function(ev) {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Sign out?",
|
||||
description:
|
||||
|
@ -301,7 +309,7 @@ module.exports = React.createClass({
|
|||
<button key="export" className="mx_Dialog_primary"
|
||||
onClick={this._onExportE2eKeysClicked}>
|
||||
Export E2E room keys
|
||||
</button>
|
||||
</button>,
|
||||
],
|
||||
onFinished: (confirmed) => {
|
||||
if (confirmed) {
|
||||
|
@ -315,34 +323,33 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onPasswordChangeError: function(err) {
|
||||
var errMsg = err.error || "";
|
||||
let errMsg = err.error || "";
|
||||
if (err.httpStatus === 403) {
|
||||
errMsg = "Failed to change password. Is your password correct?";
|
||||
}
|
||||
else if (err.httpStatus) {
|
||||
} else if (err.httpStatus) {
|
||||
errMsg += ` (HTTP status ${err.httpStatus})`;
|
||||
}
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to change password: " + errMsg);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
description: errMsg
|
||||
description: errMsg,
|
||||
});
|
||||
},
|
||||
|
||||
onPasswordChanged: function() {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Success",
|
||||
description: `Your password was successfully changed. You will not
|
||||
receive push notifications on other devices until you
|
||||
log back in to them.`
|
||||
log back in to them.`,
|
||||
});
|
||||
},
|
||||
|
||||
onUpgradeClicked: function() {
|
||||
dis.dispatch({
|
||||
action: "start_upgrade_registration"
|
||||
action: "start_upgrade_registration",
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -356,11 +363,11 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_addEmail: function() {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
var email_address = this.refs.add_email_input.value;
|
||||
if (!Email.looksValid(email_address)) {
|
||||
const emailAddress = this.refs.add_email_input.value;
|
||||
if (!Email.looksValid(emailAddress)) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Invalid Email Address",
|
||||
description: "This doesn't appear to be a valid email address",
|
||||
|
@ -370,7 +377,7 @@ module.exports = React.createClass({
|
|||
this._addThreepid = new AddThreepid();
|
||||
// we always bind emails when registering, so let's do the
|
||||
// same here.
|
||||
this._addThreepid.addEmailAddress(email_address, true).done(() => {
|
||||
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Verification Pending",
|
||||
description: "Please check your email and click on the link it contains. Once this is done, click continue.",
|
||||
|
@ -379,7 +386,7 @@ module.exports = React.createClass({
|
|||
});
|
||||
}, (err) => {
|
||||
this.setState({email_add_pending: false});
|
||||
console.error("Unable to add email address " + email_address + " " + err);
|
||||
console.error("Unable to add email address " + emailAddress + " " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unable to add email address",
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
|
@ -433,9 +440,9 @@ module.exports = React.createClass({
|
|||
this.setState({email_add_pending: false});
|
||||
}, (err) => {
|
||||
this.setState({email_add_pending: false});
|
||||
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
var message = "Unable to verify email address. ";
|
||||
if (err.errcode === 'M_THREEPID_AUTH_FAILED') {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
let message = "Unable to verify email address. ";
|
||||
message += "Please check your email and click on the link it contains. Once this is done, click continue.";
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Verification Pending",
|
||||
|
@ -444,7 +451,7 @@ module.exports = React.createClass({
|
|||
onFinished: this.onEmailDialogFinished,
|
||||
});
|
||||
} else {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Unable to verify email address: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unable to verify email address",
|
||||
|
@ -484,17 +491,17 @@ module.exports = React.createClass({
|
|||
|
||||
_onRejectAllInvitesClicked: function(rooms, ev) {
|
||||
this.setState({
|
||||
rejectingInvites: true
|
||||
rejectingInvites: true,
|
||||
});
|
||||
// reject the invites
|
||||
let promises = rooms.map((room) => {
|
||||
const promises = rooms.map((room) => {
|
||||
return MatrixClientPeg.get().leave(room.roomId);
|
||||
});
|
||||
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
|
||||
// after trying to reject all the invites.
|
||||
q.allSettled(promises).then(() => {
|
||||
this.setState({
|
||||
rejectingInvites: false
|
||||
rejectingInvites: false,
|
||||
});
|
||||
}).done();
|
||||
},
|
||||
|
@ -507,7 +514,7 @@ module.exports = React.createClass({
|
|||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -519,7 +526,7 @@ module.exports = React.createClass({
|
|||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -545,8 +552,6 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_renderUserInterfaceSettings: function() {
|
||||
var client = MatrixClientPeg.get();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>User Interface</h3>
|
||||
|
@ -564,7 +569,7 @@ module.exports = React.createClass({
|
|||
<input id="urlPreviewsDisabled"
|
||||
type="checkbox"
|
||||
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
||||
onChange={ e => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
||||
onChange={ (e) => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
||||
/>
|
||||
<label htmlFor="urlPreviewsDisabled">
|
||||
Disable inline URL previews by default
|
||||
|
@ -577,7 +582,7 @@ module.exports = React.createClass({
|
|||
<input id={ setting.id }
|
||||
type="checkbox"
|
||||
defaultChecked={ this._syncedSettings[setting.id] }
|
||||
onChange={ e => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
|
||||
onChange={ (e) => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
|
||||
/>
|
||||
<label htmlFor={ setting.id }>
|
||||
{ setting.label }
|
||||
|
@ -592,7 +597,7 @@ module.exports = React.createClass({
|
|||
name={ setting.id }
|
||||
value={ setting.value }
|
||||
defaultChecked={ this._syncedSettings[setting.id] === setting.value }
|
||||
onChange={ e => {
|
||||
onChange={ (e) => {
|
||||
if (e.target.checked) {
|
||||
UserSettingsStore.setSyncedSetting(setting.id, setting.value);
|
||||
}
|
||||
|
@ -654,8 +659,8 @@ module.exports = React.createClass({
|
|||
type="checkbox"
|
||||
defaultChecked={ this._localSettings[setting.id] }
|
||||
onChange={
|
||||
e => {
|
||||
UserSettingsStore.setLocalSetting(setting.id, e.target.checked)
|
||||
(e) => {
|
||||
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
|
||||
if (setting.id === 'blacklistUnverifiedDevices') { // XXX: this is a bit ugly
|
||||
client.setGlobalBlacklistUnverifiedDevices(e.target.checked);
|
||||
}
|
||||
|
@ -669,7 +674,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_renderDevicesPanel: function() {
|
||||
var DevicesPanel = sdk.getComponent('settings.DevicesPanel');
|
||||
const DevicesPanel = sdk.getComponent('settings.DevicesPanel');
|
||||
return (
|
||||
<div>
|
||||
<h3>Devices</h3>
|
||||
|
@ -680,7 +685,7 @@ module.exports = React.createClass({
|
|||
|
||||
_renderBugReport: function() {
|
||||
if (!SdkConfig.get().bug_report_endpoint_url) {
|
||||
return <div />
|
||||
return <div />;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
|
@ -699,17 +704,17 @@ module.exports = React.createClass({
|
|||
// default to enabled if undefined
|
||||
if (this.props.enableLabs === false) return null;
|
||||
|
||||
let features = UserSettingsStore.LABS_FEATURES.map(feature => (
|
||||
const features = UserSettingsStore.LABS_FEATURES.map((feature) => (
|
||||
<div key={feature.id} className="mx_UserSettings_toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={feature.id}
|
||||
name={feature.id}
|
||||
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
e.target.checked = false;
|
||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||
Modal.createDialog(NeedToRegisterDialog, {
|
||||
title: "Please Register",
|
||||
description: "Guests can't use labs features. Please register.",
|
||||
|
@ -761,14 +766,14 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_renderBulkOptions: function() {
|
||||
let invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => {
|
||||
const invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => {
|
||||
return r.hasMembershipState(this._me, "invite");
|
||||
});
|
||||
if (invitedRooms.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let Spinner = sdk.getComponent("elements.Spinner");
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
|
||||
let reject = <Spinner />;
|
||||
if (!this.state.rejectingInvites) {
|
||||
|
@ -852,9 +857,7 @@ module.exports = React.createClass({
|
|||
|
||||
_showSpoiler: function(event) {
|
||||
const target = event.target;
|
||||
const hidden = target.getAttribute('data-spoiler');
|
||||
|
||||
target.innerHTML = hidden;
|
||||
target.innerHTML = target.getAttribute('data-spoiler');
|
||||
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(target);
|
||||
|
@ -865,12 +868,12 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
nameForMedium: function(medium) {
|
||||
if (medium == 'msisdn') return 'Phone';
|
||||
if (medium === 'msisdn') return 'Phone';
|
||||
return medium[0].toUpperCase() + medium.slice(1);
|
||||
},
|
||||
|
||||
presentableTextForThreepid: function(threepid) {
|
||||
if (threepid.medium == 'msisdn') {
|
||||
if (threepid.medium === 'msisdn') {
|
||||
return '+' + threepid.address;
|
||||
} else {
|
||||
return threepid.address;
|
||||
|
@ -878,7 +881,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
switch (this.state.phase) {
|
||||
case "UserSettings.LOADING":
|
||||
return (
|
||||
|
@ -890,18 +893,18 @@ module.exports = React.createClass({
|
|||
throw new Error("Unknown state.phase => " + this.state.phase);
|
||||
}
|
||||
// can only get here if phase is UserSettings.DISPLAY
|
||||
var SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||
var ChangeDisplayName = sdk.getComponent("views.settings.ChangeDisplayName");
|
||||
var ChangePassword = sdk.getComponent("views.settings.ChangePassword");
|
||||
var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
|
||||
var Notifications = sdk.getComponent("settings.Notifications");
|
||||
var EditableText = sdk.getComponent('elements.EditableText');
|
||||
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||
const ChangeDisplayName = sdk.getComponent("views.settings.ChangeDisplayName");
|
||||
const ChangePassword = sdk.getComponent("views.settings.ChangePassword");
|
||||
const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
|
||||
const Notifications = sdk.getComponent("settings.Notifications");
|
||||
const EditableText = sdk.getComponent('elements.EditableText');
|
||||
|
||||
var avatarUrl = (
|
||||
const avatarUrl = (
|
||||
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
|
||||
);
|
||||
|
||||
var threepidsSection = this.state.threepids.map((val, pidIndex) => {
|
||||
const threepidsSection = this.state.threepids.map((val, pidIndex) => {
|
||||
const id = "3pid-" + val.address;
|
||||
return (
|
||||
<div className="mx_UserSettings_profileTableRow" key={pidIndex}>
|
||||
|
@ -949,7 +952,7 @@ module.exports = React.createClass({
|
|||
threepidsSection.push(addEmailSection);
|
||||
threepidsSection.push(addMsisdnSection);
|
||||
|
||||
var accountJsx;
|
||||
let accountJsx;
|
||||
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
accountJsx = (
|
||||
|
@ -957,8 +960,7 @@ module.exports = React.createClass({
|
|||
Create an account
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
accountJsx = (
|
||||
<ChangePassword
|
||||
className="mx_UserSettings_accountTable"
|
||||
|
@ -970,9 +972,9 @@ module.exports = React.createClass({
|
|||
onFinished={this.onPasswordChanged} />
|
||||
);
|
||||
}
|
||||
var notification_area;
|
||||
let notificationArea;
|
||||
if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) {
|
||||
notification_area = (<div>
|
||||
notificationArea = (<div>
|
||||
<h3>Notifications</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
|
@ -986,7 +988,7 @@ module.exports = React.createClass({
|
|||
// we are using a version old version of olm. We assume the former.
|
||||
let olmVersionString = "<not-enabled>";
|
||||
if (olmVersion !== undefined) {
|
||||
olmVersionString = `v${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
|
||||
olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1044,7 +1046,7 @@ module.exports = React.createClass({
|
|||
|
||||
{this._renderReferral()}
|
||||
|
||||
{notification_area}
|
||||
{notificationArea}
|
||||
|
||||
{this._renderUserInterfaceSettings()}
|
||||
{this._renderLabs()}
|
||||
|
@ -1061,7 +1063,10 @@ module.exports = React.createClass({
|
|||
Logged in as {this._me}
|
||||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
Access Token: <span className="mx_UserSettings_advanced_spoiler" onClick={this._showSpoiler} data-spoiler={ MatrixClientPeg.get().getAccessToken() }><click to reveal></span>
|
||||
Access Token: <span className="mx_UserSettings_advanced_spoiler"
|
||||
onClick={this._showSpoiler}
|
||||
data-spoiler={ MatrixClientPeg.get().getAccessToken() }
|
||||
><click to reveal></span>
|
||||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
Homeserver is { MatrixClientPeg.get().getHomeserverUrl() }
|
||||
|
@ -1071,11 +1076,11 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
matrix-react-sdk version: {(REACT_SDK_VERSION !== '<local>')
|
||||
? <a href={ GHVersionUrl('matrix-org/matrix-react-sdk', REACT_SDK_VERSION) }>{REACT_SDK_VERSION}</a>
|
||||
? gHVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION)
|
||||
: REACT_SDK_VERSION
|
||||
}<br/>
|
||||
riot-web version: {(this.state.vectorVersion !== null)
|
||||
? <a href={ GHVersionUrl('vector-im/riot-web', this.state.vectorVersion.split('-')[0]) }>{this.state.vectorVersion}</a>
|
||||
? gHVersionLabel('vector-im/riot-web', this.state.vectorVersion)
|
||||
: 'unknown'
|
||||
}<br/>
|
||||
olm version: {olmVersionString}<br/>
|
||||
|
@ -1089,5 +1094,5 @@ module.exports = React.createClass({
|
|||
</GeminiScrollbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -23,6 +23,9 @@ import url from 'url';
|
|||
import sdk from '../../../index';
|
||||
import Login from '../../../Login';
|
||||
|
||||
// For validating phone numbers without country codes
|
||||
const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/;
|
||||
|
||||
/**
|
||||
* A wire component which glues together login UI components and Login logic
|
||||
*/
|
||||
|
@ -125,7 +128,16 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onPhoneNumberChanged: function(phoneNumber) {
|
||||
this.setState({ phoneNumber: phoneNumber });
|
||||
// Validate the phone number entered
|
||||
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
|
||||
this.setState({ errorText: 'The phone number entered looks invalid' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
phoneNumber: phoneNumber,
|
||||
errorText: null,
|
||||
});
|
||||
},
|
||||
|
||||
onServerConfigChange: function(config) {
|
||||
|
|
|
@ -59,7 +59,9 @@ module.exports = React.createClass({
|
|||
ContentRepo.getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
props.oobData.avatarUrl,
|
||||
props.width, props.height, props.resizeMethod
|
||||
Math.floor(props.width * window.devicePixelRatio),
|
||||
Math.floor(props.height * window.devicePixelRatio),
|
||||
props.resizeMethod
|
||||
), // highest priority
|
||||
this.getRoomAvatarUrl(props),
|
||||
this.getOneToOneAvatar(props),
|
||||
|
@ -74,7 +76,9 @@ module.exports = React.createClass({
|
|||
|
||||
return props.room.getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
props.width, props.height, props.resizeMethod,
|
||||
Math.floor(props.width * window.devicePixelRatio),
|
||||
Math.floor(props.height * window.devicePixelRatio),
|
||||
props.resizeMethod,
|
||||
false
|
||||
);
|
||||
},
|
||||
|
@ -103,13 +107,17 @@ module.exports = React.createClass({
|
|||
}
|
||||
return theOtherGuy.getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
props.width, props.height, props.resizeMethod,
|
||||
Math.floor(props.width * window.devicePixelRatio),
|
||||
Math.floor(props.height * window.devicePixelRatio),
|
||||
props.resizeMethod,
|
||||
false
|
||||
);
|
||||
} else if (userIds.length == 1) {
|
||||
return mlist[userIds[0]].getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
props.width, props.height, props.resizeMethod,
|
||||
Math.floor(props.width * window.devicePixelRatio),
|
||||
Math.floor(props.height * window.devicePixelRatio),
|
||||
props.resizeMethod,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -369,6 +369,7 @@ module.exports = React.createClass({
|
|||
|
||||
render: function() {
|
||||
const eventsToRender = this.props.events;
|
||||
const eventIds = eventsToRender.map(e => e.getId()).join(',');
|
||||
const fewEvents = eventsToRender.length < this.props.threshold;
|
||||
const expanded = this.state.expanded || fewEvents;
|
||||
|
||||
|
@ -379,7 +380,7 @@ module.exports = React.createClass({
|
|||
|
||||
if (fewEvents) {
|
||||
return (
|
||||
<div className="mx_MemberEventListSummary">
|
||||
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
|
||||
{expandedEvents}
|
||||
</div>
|
||||
);
|
||||
|
@ -437,7 +438,7 @@ module.exports = React.createClass({
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="mx_MemberEventListSummary">
|
||||
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
|
||||
{toggleButton}
|
||||
{summaryContainer}
|
||||
{expanded ? <div className="mx_MemberEventListSummary_line"> </div> : null}
|
||||
|
|
|
@ -391,47 +391,6 @@ module.exports = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
if (s.lists["im.vector.fake.direct"].length == 0 &&
|
||||
MatrixClientPeg.get().getAccountData('m.direct') === undefined &&
|
||||
!MatrixClientPeg.get().isGuest())
|
||||
{
|
||||
// scan through the 'recents' list for any rooms which look like DM rooms
|
||||
// and make them DM rooms
|
||||
const oldRecents = s.lists["im.vector.fake.recent"];
|
||||
s.lists["im.vector.fake.recent"] = [];
|
||||
|
||||
for (const room of oldRecents) {
|
||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
|
||||
if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
|
||||
self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
|
||||
s.lists["im.vector.fake.direct"].push(room);
|
||||
} else {
|
||||
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
|
||||
s.lists["im.vector.fake.recent"].push(room);
|
||||
}
|
||||
}
|
||||
|
||||
// save these new guessed DM rooms into the account data
|
||||
const newMDirectEvent = {};
|
||||
for (const room of s.lists["im.vector.fake.direct"]) {
|
||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
const otherPerson = Rooms.getOnlyOtherMember(room, me);
|
||||
if (!otherPerson) continue;
|
||||
|
||||
const roomList = newMDirectEvent[otherPerson.userId] || [];
|
||||
roomList.push(room.roomId);
|
||||
newMDirectEvent[otherPerson.userId] = roomList;
|
||||
}
|
||||
|
||||
console.warn("Resetting room DM state to be " + JSON.stringify(newMDirectEvent));
|
||||
|
||||
// if this fails, fine, we'll just do the same thing next time we get the room lists
|
||||
MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
|
||||
}
|
||||
|
||||
//console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]);
|
||||
|
||||
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
||||
|
||||
// we'll need this when we get to iterating through lists programatically - e.g. ctrl-shift-up/down
|
||||
|
|
|
@ -60,7 +60,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
return (
|
||||
<li data-scroll-token={eventId+"+"+j}>
|
||||
<li data-scroll-tokens={eventId+"+"+j}>
|
||||
{ret}
|
||||
</li>);
|
||||
},
|
||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import dis from '../../../dispatcher';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import sdk from '../../../index';
|
||||
|
||||
// cancel button which is shared between room header and simple room header
|
||||
export function CancelButton(props) {
|
||||
|
@ -45,6 +46,9 @@ export default React.createClass({
|
|||
|
||||
// is the RightPanel collapsed?
|
||||
collapsedRhs: React.PropTypes.bool,
|
||||
|
||||
// `src` to a TintableSvg. Optional.
|
||||
icon: React.PropTypes.string,
|
||||
},
|
||||
|
||||
onShowRhsClick: function(ev) {
|
||||
|
@ -53,9 +57,17 @@ export default React.createClass({
|
|||
|
||||
render: function() {
|
||||
let cancelButton;
|
||||
let icon;
|
||||
if (this.props.onCancelClick) {
|
||||
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
|
||||
}
|
||||
if (this.props.icon) {
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
icon = <TintableSvg
|
||||
className="mx_RoomHeader_icon" src={this.props.icon}
|
||||
width="25" height="25"
|
||||
/>;
|
||||
}
|
||||
|
||||
let showRhsButton;
|
||||
/* // don't bother cluttering things up with this for now.
|
||||
|
@ -73,6 +85,7 @@ export default React.createClass({
|
|||
<div className="mx_RoomHeader" >
|
||||
<div className="mx_RoomHeader_wrapper">
|
||||
<div className="mx_RoomHeader_simpleHeader">
|
||||
{ icon }
|
||||
{ this.props.title }
|
||||
{ showRhsButton }
|
||||
{ cancelButton }
|
||||
|
|
|
@ -115,7 +115,7 @@ var Tester = React.createClass({
|
|||
//
|
||||
// there is an extra 50 pixels of margin at the bottom.
|
||||
return (
|
||||
<li key={key} data-scroll-token={key}>
|
||||
<li key={key} data-scroll-tokens={key}>
|
||||
<div style={{height: '98px', margin: '50px', border: '1px solid black',
|
||||
backgroundColor: '#fff8dc' }}>
|
||||
{key}
|
||||
|
|
Loading…
Reference in New Issue