+ return
{completions}
;
}
diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js
index 4e0c0f5ea7..26ec15e124 100644
--- a/src/autocomplete/UserProvider.js
+++ b/src/autocomplete/UserProvider.js
@@ -37,10 +37,11 @@ export default class UserProvider extends AutocompleteProvider {
constructor() {
super(USER_REGEX, {
- keys: ['name', 'userId'],
+ keys: ['name'],
});
this.matcher = new FuzzyMatcher([], {
- keys: ['name', 'userId'],
+ keys: ['name'],
+ shouldMatchPrefix: true,
});
}
@@ -50,7 +51,7 @@ export default class UserProvider extends AutocompleteProvider {
let completions = [];
let {command, range} = this.getCurrentCommand(query, selection, force);
if (command) {
- completions = this.matcher.match(command[0]).map(user => {
+ completions = this.matcher.match(command[0]).slice(0, 4).map((user) => {
let displayName = (user.name || user.userId || '').replace(' (IRC)', ''); // FIXME when groups are done
let completion = displayName;
if (range.start === 0) {
@@ -68,7 +69,7 @@ export default class UserProvider extends AutocompleteProvider {
),
range,
};
- }).slice(0, 4);
+ });
}
return completions;
}
@@ -90,7 +91,9 @@ export default class UserProvider extends AutocompleteProvider {
if (member.userId !== currentUserId) return true;
});
- this.users = _sortBy(this.users, (user) => 1E20 - lastSpoken[user.userId] || 1E20);
+ this.users = _sortBy(this.users, (completion) =>
+ 1E20 - lastSpoken[completion.user.userId] || 1E20,
+ );
this.matcher.setObjects(this.users);
}
@@ -98,9 +101,10 @@ export default class UserProvider extends AutocompleteProvider {
onUserSpoke(user: RoomMember) {
if(user.userId === MatrixClientPeg.get().credentials.userId) return;
- // Probably unsafe to compare by reference here?
- _pull(this.users, user);
- this.users.splice(0, 0, user);
+ this.users = this.users.splice(
+ this.users.findIndex((user2) => user2.userId === user.userId), 1);
+ this.users = [user, ...this.users];
+
this.matcher.setObjects(this.users);
}
@@ -112,7 +116,7 @@ export default class UserProvider extends AutocompleteProvider {
}
renderCompletions(completions: [React.Component]): ?React.Component {
- return
+ return
{completions}
;
}
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 0a7674f4d8..18b9340b4a 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -103,8 +103,9 @@ export default React.createClass({
this.setState({
summary: null,
error: null,
+ }, () => {
+ this._loadGroupFromServer(newProps.groupId);
});
- this._loadGroupFromServer(newProps.groupId);
}
},
diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index 6f4c931ab7..9fed0e7d5b 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -18,8 +18,12 @@ limitations under the License.
import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
+import ScalarAuthClient from '../../../ScalarAuthClient';
+import SdkConfig from '../../../SdkConfig';
import { _t } from '../../../languageHandler';
+import url from 'url';
+
export default React.createClass({
displayName: 'AppTile',
@@ -36,6 +40,51 @@ export default React.createClass({
};
},
+ getInitialState: function() {
+ return {
+ loading: false,
+ widgetUrl: this.props.url,
+ error: null,
+ };
+ },
+
+ // Returns true if props.url is a scalar URL, typically https://scalar.vector.im/api
+ isScalarUrl: function() {
+ const scalarUrl = SdkConfig.get().integrations_rest_url;
+ return scalarUrl && this.props.url.startsWith(scalarUrl);
+ },
+
+ componentWillMount: function() {
+ if (!this.isScalarUrl()) {
+ return;
+ }
+ // Fetch the token before loading the iframe as we need to mangle the URL
+ this.setState({
+ loading: true,
+ });
+ this._scalarClient = new ScalarAuthClient();
+ this._scalarClient.getScalarToken().done((token) => {
+ // Append scalar_token as a query param
+ const u = url.parse(this.props.url);
+ if (!u.search) {
+ u.search = "?scalar_token=" + encodeURIComponent(token);
+ } else {
+ u.search += "&scalar_token=" + encodeURIComponent(token);
+ }
+
+ this.setState({
+ error: null,
+ widgetUrl: u.format(),
+ loading: false,
+ });
+ }, (err) => {
+ this.setState({
+ error: err.message,
+ loading: false,
+ });
+ });
+ },
+
_onEditClick: function() {
console.log("Edit widget %s", this.props.id);
},
@@ -72,6 +121,18 @@ export default React.createClass({
},
render: function() {
+ let appTileBody;
+ if (this.state.loading) {
+ appTileBody = (
+
Loading...
+ );
+ } else {
+ appTileBody = (
+
+
+
+ );
+ }
return (
@@ -93,9 +154,7 @@ export default React.createClass({
/>
-
-
-
+ {appTileBody}
);
},
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 190b1341c3..2c50a94a6a 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -143,9 +143,15 @@ module.exports = React.createClass({
if (this.props.showUrlPreview && !this.state.links.length) {
var links = this.findLinks(this.refs.content.children);
if (links.length) {
- this.setState({ links: links.map((link)=>{
- return link.getAttribute("href");
- })});
+ // de-dup the links (but preserve ordering)
+ const seen = new Set();
+ links = links.filter((link) => {
+ if (seen.has(link)) return false;
+ seen.add(link);
+ return true;
+ });
+
+ this.setState({ links: links });
// lazy-load the hidden state of the preview widget from localstorage
if (global.localStorage) {
@@ -158,12 +164,13 @@ module.exports = React.createClass({
findLinks: function(nodes) {
var links = [];
+
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.tagName === "A" && node.getAttribute("href"))
{
if (this.isLinkPreviewable(node)) {
- links.push(node);
+ links.push(node.getAttribute("href"));
}
}
else if (node.tagName === "PRE" || node.tagName === "CODE" ||
diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js
index b535b148ac..a12bd8ecac 100644
--- a/src/components/views/rooms/AppsDrawer.js
+++ b/src/components/views/rooms/AppsDrawer.js
@@ -176,7 +176,7 @@ module.exports = React.createClass({
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
- this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) :
+ this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') :
null;
Modal.createDialog(IntegrationsManager, {
src: src,
@@ -187,7 +187,7 @@ module.exports = React.createClass({
const apps = this.state.apps.map(
(app, index, arr) => {
return
0) {
+ if (this.state.completions.length > 0 || this.state.forceComplete) {
autocompleteDelay = 0;
}
@@ -177,7 +177,7 @@ export default class Autocomplete extends React.Component {
hide: false,
}, () => {
this.complete(this.props.query, this.props.selection).then(() => {
- done.resolve();
+ done.resolve(this.countCompletions());
});
});
return done.promise;
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js
index c83e32d9a8..27d5e11119 100644
--- a/src/components/views/rooms/MessageComposer.js
+++ b/src/components/views/rooms/MessageComposer.js
@@ -21,7 +21,6 @@ import Modal from '../../../Modal';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import Autocomplete from './Autocomplete';
-import classNames from 'classnames';
import UserSettingsStore from '../../../UserSettingsStore';
@@ -408,14 +407,10 @@ export default class MessageComposer extends React.Component {
const active = style.includes(name) || blockType === name;
const suffix = active ? '-o-n' : '';
const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
- const disabled = !this.state.inputState.isRichtextEnabled && 'underline' === name;
- const className = classNames("mx_MessageComposer_format_button", {
- mx_MessageComposer_format_button_disabled: disabled,
- mx_filterFlipColor: true,
- });
+ const className = 'mx_MessageComposer_format_button mx_filterFlipColor';
return ;
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js
index aae91620d8..3465b2ad14 100644
--- a/src/components/views/rooms/MessageComposerInput.js
+++ b/src/components/views/rooms/MessageComposerInput.js
@@ -43,6 +43,8 @@ import Markdown from '../../../Markdown';
import ComposerHistoryManager from '../../../ComposerHistoryManager';
import {onSendMessageFailed} from './MessageComposerInputOld';
+import MessageComposerStore from '../../../stores/MessageComposerStore';
+
const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
const ZWS_CODE = 8203;
@@ -130,7 +132,10 @@ export default class MessageComposerInput extends React.Component {
isRichtextEnabled,
// the currently displayed editor state (note: this is always what is modified on input)
- editorState: null,
+ editorState: this.createEditorState(
+ isRichtextEnabled,
+ MessageComposerStore.getContentState(this.props.room.roomId),
+ ),
// the original editor state, before we started tabbing through completions
originalEditorState: null,
@@ -138,11 +143,10 @@ export default class MessageComposerInput extends React.Component {
// the virtual state "above" the history stack, the message currently being composed that
// we want to persist whilst browsing history
currentlyComposedEditorState: null,
- };
- // bit of a hack, but we need to do this here since createEditorState needs isRichtextEnabled
- /* eslint react/no-direct-mutation-state:0 */
- this.state.editorState = this.createEditorState();
+ // whether there were any completions
+ someCompletions: null,
+ };
this.client = MatrixClientPeg.get();
}
@@ -336,6 +340,14 @@ export default class MessageComposerInput extends React.Component {
this.onFinishedTyping();
}
+ // Record the editor state for this room so that it can be retrieved after
+ // switching to another room and back
+ dis.dispatch({
+ action: 'content_state',
+ room_id: this.props.room.roomId,
+ content_state: state.editorState.getCurrentContent(),
+ });
+
if (!state.hasOwnProperty('originalEditorState')) {
state.originalEditorState = null;
}
@@ -403,26 +415,59 @@ export default class MessageComposerInput extends React.Component {
});
}
} else {
- let contentState = this.state.editorState.getCurrentContent(),
- selection = this.state.editorState.getSelection();
+ let contentState = this.state.editorState.getCurrentContent();
const modifyFn = {
'bold': (text) => `**${text}**`,
'italic': (text) => `*${text}*`,
- 'underline': (text) => `_${text}_`, // there's actually no valid underline in Markdown, but *shrug*
+ 'underline': (text) => `${text}`,
'strike': (text) => `${text}`,
- 'code-block': (text) => `\`\`\`\n${text}\n\`\`\``,
- 'blockquote': (text) => text.split('\n').map((line) => `> ${line}\n`).join(''),
+ 'code-block': (text) => `\`\`\`\n${text}\n\`\`\`\n`,
+ 'blockquote': (text) => text.split('\n').map((line) => `> ${line}\n`).join('') + '\n',
'unordered-list-item': (text) => text.split('\n').map((line) => `\n- ${line}`).join(''),
'ordered-list-item': (text) => text.split('\n').map((line, i) => `\n${i + 1}. ${line}`).join(''),
}[command];
+ const selectionAfterOffset = {
+ 'bold': -2,
+ 'italic': -1,
+ 'underline': -4,
+ 'strike': -6,
+ 'code-block': -5,
+ 'blockquote': -2,
+ }[command];
+
+ // Returns a function that collapses a selectionState to its end and moves it by offset
+ const collapseAndOffsetSelection = (selectionState, offset) => {
+ const key = selectionState.getEndKey();
+ return new SelectionState({
+ anchorKey: key, anchorOffset: offset,
+ focusKey: key, focusOffset: offset,
+ });
+ };
+
if (modifyFn) {
+ const previousSelection = this.state.editorState.getSelection();
+ const newContentState = RichText.modifyText(contentState, previousSelection, modifyFn);
newState = EditorState.push(
this.state.editorState,
- RichText.modifyText(contentState, selection, modifyFn),
+ newContentState,
'insert-characters',
);
+
+ let newSelection = newContentState.getSelectionAfter();
+ // If the selection range is 0, move the cursor inside the formatted body
+ if (previousSelection.getStartOffset() === previousSelection.getEndOffset() &&
+ previousSelection.getStartKey() === previousSelection.getEndKey() &&
+ selectionAfterOffset !== undefined
+ ) {
+ const selectedBlock = newContentState.getBlockForKey(previousSelection.getAnchorKey());
+ const blockLength = selectedBlock.getText().length;
+ const newOffset = blockLength + selectionAfterOffset;
+ newSelection = collapseAndOffsetSelection(newSelection, newOffset);
+ }
+
+ newState = EditorState.forceSelection(newState, newSelection);
}
}
@@ -443,8 +488,7 @@ export default class MessageComposerInput extends React.Component {
const currentContent = this.state.editorState.getCurrentContent();
let contentState = null;
-
- if (html) {
+ if (html && this.state.isRichtextEnabled) {
contentState = Modifier.replaceWithFragment(
currentContent,
currentSelection,
@@ -548,14 +592,6 @@ export default class MessageComposerInput extends React.Component {
let sendHtmlFn = this.client.sendHtmlMessage;
let sendTextFn = this.client.sendTextMessage;
- if (contentText.startsWith('/me')) {
- contentText = contentText.substring(4);
- // bit of a hack, but the alternative would be quite complicated
- if (contentHTML) contentHTML = contentHTML.replace(/\/me ?/, '');
- sendHtmlFn = this.client.sendHtmlEmote;
- sendTextFn = this.client.sendEmoteMessage;
- }
-
if (this.state.isRichtextEnabled) {
this.historyManager.addItem(
contentHTML ? contentHTML : contentText,
@@ -566,6 +602,14 @@ export default class MessageComposerInput extends React.Component {
this.historyManager.addItem(contentText, 'markdown');
}
+ if (contentText.startsWith('/me')) {
+ contentText = contentText.substring(4);
+ // bit of a hack, but the alternative would be quite complicated
+ if (contentHTML) contentHTML = contentHTML.replace(/\/me ?/, '');
+ sendHtmlFn = this.client.sendHtmlEmote;
+ sendTextFn = this.client.sendEmoteMessage;
+ }
+
let sendMessagePromise;
if (contentHTML) {
sendMessagePromise = sendHtmlFn.call(
@@ -599,6 +643,10 @@ export default class MessageComposerInput extends React.Component {
};
onVerticalArrow = (e, up) => {
+ if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) {
+ return;
+ }
+
// Select history only if we are not currently auto-completing
if (this.autocomplete.state.completionList.length === 0) {
// Don't go back in history if we're in the middle of a multi-line message
@@ -607,17 +655,16 @@ export default class MessageComposerInput extends React.Component {
const firstBlock = this.state.editorState.getCurrentContent().getFirstBlock();
const lastBlock = this.state.editorState.getCurrentContent().getLastBlock();
- const selectionOffset = selection.getAnchorOffset();
let canMoveUp = false;
let canMoveDown = false;
if (blockKey === firstBlock.getKey()) {
- const textBeforeCursor = firstBlock.getText().slice(0, selectionOffset);
- canMoveUp = textBeforeCursor.indexOf('\n') === -1;
+ canMoveUp = selection.getStartOffset() === selection.getEndOffset() &&
+ selection.getStartOffset() === 0;
}
if (blockKey === lastBlock.getKey()) {
- const textAfterCursor = lastBlock.getText().slice(selectionOffset);
- canMoveDown = textAfterCursor.indexOf('\n') === -1;
+ canMoveDown = selection.getStartOffset() === selection.getEndOffset() &&
+ selection.getStartOffset() === lastBlock.getText().length;
}
if ((up && !canMoveUp) || (!up && !canMoveDown)) return;
@@ -674,10 +721,16 @@ export default class MessageComposerInput extends React.Component {
};
onTab = async (e) => {
+ this.setState({
+ someCompletions: null,
+ });
e.preventDefault();
if (this.autocomplete.state.completionList.length === 0) {
// Force completions to show for the text currently entered
- await this.autocomplete.forceComplete();
+ const completionCount = await this.autocomplete.forceComplete();
+ this.setState({
+ someCompletions: completionCount > 0,
+ });
// Select the first item by moving "down"
await this.moveAutocompleteSelection(false);
} else {
@@ -798,6 +851,7 @@ export default class MessageComposerInput extends React.Component {
const className = classNames('mx_MessageComposer_input', {
mx_MessageComposer_input_empty: hidePlaceholder,
+ mx_MessageComposer_input_error: this.state.someCompletions === false,
});
const content = activeEditorState.getCurrentContent();
diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js
index 19010d8a10..85aedadf64 100644
--- a/src/components/views/rooms/RoomHeader.js
+++ b/src/components/views/rooms/RoomHeader.js
@@ -16,18 +16,18 @@ limitations under the License.
'use strict';
-var React = require('react');
-var classNames = require('classnames');
-var sdk = require('../../../index');
+import React from 'react';
+import classNames from 'classnames';
+import sdk from '../../../index';
import { _t } from '../../../languageHandler';
-var MatrixClientPeg = require('../../../MatrixClientPeg');
-var Modal = require("../../../Modal");
-var dis = require("../../../dispatcher");
-var rate_limited_func = require('../../../ratelimitedfunc');
+import MatrixClientPeg from '../../../MatrixClientPeg';
+import Modal from "../../../Modal";
+import dis from "../../../dispatcher";
+import RateLimitedFunc from '../../../ratelimitedfunc';
-var linkify = require('linkifyjs');
-var linkifyElement = require('linkifyjs/element');
-var linkifyMatrix = require('../../../linkify-matrix');
+import * as linkify from 'linkifyjs';
+import linkifyElement from 'linkifyjs/element';
+import linkifyMatrix from '../../../linkify-matrix';
import AccessibleButton from '../elements/AccessibleButton';
import {CancelButton} from './SimpleRoomHeader';
@@ -58,7 +58,7 @@ module.exports = React.createClass({
},
componentDidMount: function() {
- var cli = MatrixClientPeg.get();
+ const cli = MatrixClientPeg.get();
cli.on("RoomState.events", this._onRoomStateEvents);
// When a room name occurs, RoomState.events is fired *before*
@@ -79,14 +79,14 @@ module.exports = React.createClass({
if (this.props.room) {
this.props.room.removeListener("Room.name", this._onRoomNameChange);
}
- var cli = MatrixClientPeg.get();
+ const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener("RoomState.events", this._onRoomStateEvents);
}
},
_onRoomStateEvents: function(event, state) {
- if (!this.props.room || event.getRoomId() != this.props.room.roomId) {
+ if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
return;
}
@@ -94,7 +94,8 @@ module.exports = React.createClass({
this._rateLimitedUpdate();
},
- _rateLimitedUpdate: new rate_limited_func(function() {
+ _rateLimitedUpdate: new RateLimitedFunc(function() {
+ /* eslint-disable babel/no-invalid-this */
this.forceUpdate();
}, 500),
@@ -109,15 +110,14 @@ module.exports = React.createClass({
},
onAvatarSelected: function(ev) {
- var self = this;
- var changeAvatar = this.refs.changeAvatar;
+ const changeAvatar = this.refs.changeAvatar;
if (!changeAvatar) {
console.error("No ChangeAvatar found to upload image to!");
return;
}
changeAvatar.onFileSelected(ev).catch(function(err) {
- var errMsg = (typeof err === "string") ? err : (err.error || "");
- var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ const errMsg = (typeof err === "string") ? err : (err.error || "");
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to set avatar: " + errMsg);
Modal.createDialog(ErrorDialog, {
title: _t("Error"),
@@ -133,10 +133,10 @@ module.exports = React.createClass({
/**
* After editing the settings, get the new name for the room
*
- * Returns undefined if we didn't let the user edit the room name
+ * @return {?string} newName or undefined if we didn't let the user edit the room name
*/
getEditedName: function() {
- var newName;
+ let newName;
if (this.refs.nameEditor) {
newName = this.refs.nameEditor.getRoomName();
}
@@ -146,10 +146,10 @@ module.exports = React.createClass({
/**
* After editing the settings, get the new topic for the room
*
- * Returns undefined if we didn't let the user edit the room topic
+ * @return {?string} newTopic or undefined if we didn't let the user edit the room topic
*/
getEditedTopic: function() {
- var newTopic;
+ let newTopic;
if (this.refs.topicEditor) {
newTopic = this.refs.topicEditor.getTopic();
}
@@ -157,38 +157,31 @@ module.exports = React.createClass({
},
render: function() {
- var RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
- var ChangeAvatar = sdk.getComponent("settings.ChangeAvatar");
- var TintableSvg = sdk.getComponent("elements.TintableSvg");
+ const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
+ const ChangeAvatar = sdk.getComponent("settings.ChangeAvatar");
+ const TintableSvg = sdk.getComponent("elements.TintableSvg");
const EmojiText = sdk.getComponent('elements.EmojiText');
- var header;
- var name = null;
- var searchStatus = null;
- var topic_el = null;
- var cancel_button = null;
- var spinner = null;
- var save_button = null;
- var settings_button = null;
+ let name = null;
+ let searchStatus = null;
+ let topicElement = null;
+ let cancelButton = null;
+ let spinner = null;
+ let saveButton = null;
+ let settingsButton = null;
+
+ let canSetRoomName;
+ let canSetRoomAvatar;
+ let canSetRoomTopic;
if (this.props.editing) {
-
// calculate permissions. XXX: this should be done on mount or something
- var user_id = MatrixClientPeg.get().credentials.userId;
+ const userId = MatrixClientPeg.get().credentials.userId;
- var can_set_room_name = this.props.room.currentState.maySendStateEvent(
- 'm.room.name', user_id
- );
- var can_set_room_avatar = this.props.room.currentState.maySendStateEvent(
- 'm.room.avatar', user_id
- );
- var can_set_room_topic = this.props.room.currentState.maySendStateEvent(
- 'm.room.topic', user_id
- );
- var can_set_room_name = this.props.room.currentState.maySendStateEvent(
- 'm.room.name', user_id
- );
+ canSetRoomName = this.props.room.currentState.maySendStateEvent('m.room.name', userId);
+ canSetRoomAvatar = this.props.room.currentState.maySendStateEvent('m.room.avatar', userId);
+ canSetRoomTopic = this.props.room.currentState.maySendStateEvent('m.room.topic', userId);
- save_button = (
+ saveButton = (
{_t("Save")}
@@ -196,39 +189,41 @@ module.exports = React.createClass({
}
if (this.props.onCancelClick) {
- cancel_button = ;
+ cancelButton = ;
}
if (this.props.saving) {
- var Spinner = sdk.getComponent("elements.Spinner");
+ const Spinner = sdk.getComponent("elements.Spinner");
spinner =
;
}
- if (can_set_room_name) {
- var RoomNameEditor = sdk.getComponent("rooms.RoomNameEditor");
+ if (canSetRoomName) {
+ const RoomNameEditor = sdk.getComponent("rooms.RoomNameEditor");
name = ;
- }
- else {
- var searchStatus;
+ } else {
// don't display the search count until the search completes and
// gives us a valid (possibly zero) searchCount.
- if (this.props.searchInfo && this.props.searchInfo.searchCount !== undefined && this.props.searchInfo.searchCount !== null) {
- searchStatus = { _t("(~%(count)s results)", { count: this.props.searchInfo.searchCount }) }
;
+ if (this.props.searchInfo &&
+ this.props.searchInfo.searchCount !== undefined &&
+ this.props.searchInfo.searchCount !== null) {
+ searchStatus =
+ { _t("(~%(count)s results)", { count: this.props.searchInfo.searchCount }) }
+
;
}
// XXX: this is a bit inefficient - we could just compare room.name for 'Empty room'...
- var settingsHint = false;
- var members = this.props.room ? this.props.room.getJoinedMembers() : undefined;
+ let settingsHint = false;
+ const members = this.props.room ? this.props.room.getJoinedMembers() : undefined;
if (members) {
if (members.length === 1 && members[0].userId === MatrixClientPeg.get().credentials.userId) {
- var name = this.props.room.currentState.getStateEvents('m.room.name', '');
- if (!name || !name.getContent().name) {
+ const nameEvent = this.props.room.currentState.getStateEvents('m.room.name', '');
+ if (!nameEvent || !nameEvent.getContent().name) {
settingsHint = true;
}
}
}
- var roomName = _t("Join Room");
+ let roomName = _t("Join Room");
if (this.props.oobData && this.props.oobData.name) {
roomName = this.props.oobData.name;
} else if (this.props.room) {
@@ -243,24 +238,25 @@ module.exports = React.createClass({
;
}
- if (can_set_room_topic) {
- var RoomTopicEditor = sdk.getComponent("rooms.RoomTopicEditor");
- topic_el =
;
+ if (canSetRoomTopic) {
+ const RoomTopicEditor = sdk.getComponent("rooms.RoomTopicEditor");
+ topicElement =
;
} else {
- var topic;
+ let topic;
if (this.props.room) {
- var ev = this.props.room.currentState.getStateEvents('m.room.topic', '');
+ const ev = this.props.room.currentState.getStateEvents('m.room.topic', '');
if (ev) {
topic = ev.getContent().topic;
}
}
if (topic) {
- topic_el =
{ topic }
;
+ topicElement =
+
{ topic }
;
}
}
- var roomAvatar = null;
- if (can_set_room_avatar) {
+ let roomAvatar = null;
+ if (canSetRoomAvatar) {
roomAvatar = (
@@ -276,8 +272,7 @@ module.exports = React.createClass({
);
- }
- else if (this.props.room || (this.props.oobData && this.props.oobData.name)) {
+ } else if (this.props.room || (this.props.oobData && this.props.oobData.name)) {
roomAvatar = (
@@ -285,9 +280,8 @@ module.exports = React.createClass({
);
}
- var settings_button;
if (this.props.onSettingsClick) {
- settings_button =
+ settingsButton =
;
@@ -301,61 +295,58 @@ module.exports = React.createClass({
//
;
// }
- var forget_button;
+ let forgetButton;
if (this.props.onForgetClick) {
- forget_button =
+ forgetButton =
;
}
- let search_button;
+ let searchButton;
if (this.props.onSearchClick && this.props.inRoom) {
- search_button =
+ searchButton =
;
}
- var rightPanel_buttons;
+ let rightPanelButtons;
if (this.props.collapsedRhs) {
- rightPanel_buttons =
+ rightPanelButtons =
;
}
- var right_row;
+ let rightRow;
if (!this.props.editing) {
- right_row =
+ rightRow =
- { settings_button }
- { forget_button }
- { search_button }
- { rightPanel_buttons }
+ { settingsButton }
+ { forgetButton }
+ { searchButton }
+ { rightPanelButtons }
;
}
- header =
-
-
-
- { roomAvatar }
-
-
- { name }
- { topic_el }
-
-
- {spinner}
- {save_button}
- {cancel_button}
- {right_row}
-
;
-
return (
- { header }
+
+
+
+ { roomAvatar }
+
+
+ { name }
+ { topicElement }
+
+
+ {spinner}
+ {saveButton}
+ {cancelButton}
+ {rightRow}
+
);
},
diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js
index 171af4764b..d255670a52 100644
--- a/src/components/views/rooms/RoomSettings.js
+++ b/src/components/views/rooms/RoomSettings.js
@@ -39,6 +39,7 @@ function parseIntWithDefault(val, def) {
const BannedUser = React.createClass({
propTypes: {
+ canUnban: React.PropTypes.bool,
member: React.PropTypes.object.isRequired, // js-sdk RoomMember
reason: React.PropTypes.string,
},
@@ -67,13 +68,17 @@ const BannedUser = React.createClass({
},
render: function() {
+ let unbanButton;
+
+ if (this.props.canUnban) {
+ unbanButton =
+ { _t('Unban') }
+ ;
+ }
+
return (
-
- { _t('Unban') }
-
+ { unbanButton }
{this.props.member.name} {this.props.member.userId}
{this.props.reason ? " " +_t('Reason') + ": " + this.props.reason : ""}
@@ -667,6 +672,7 @@ module.exports = React.createClass({
const banned = this.props.room.getMembersWithMembership("ban");
let bannedUsersSection;
if (banned.length) {
+ const canBanUsers = current_user_level >= ban_level;
bannedUsersSection =
{ _t('Banned users') }
@@ -674,7 +680,7 @@ module.exports = React.createClass({
{banned.map(function(member) {
const banEvent = member.events.member.getContent();
return (
-
+
);
})}
diff --git a/src/components/views/voip/CallView.js b/src/components/views/voip/CallView.js
index b53794637f..e669f7e0a6 100644
--- a/src/components/views/voip/CallView.js
+++ b/src/components/views/voip/CallView.js
@@ -13,11 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
-var React = require("react");
-var dis = require("../../../dispatcher");
-var CallHandler = require("../../../CallHandler");
-var sdk = require('../../../index');
-var MatrixClientPeg = require("../../../MatrixClientPeg");
+import React from 'react';
+import dis from '../../../dispatcher';
+import CallHandler from '../../../CallHandler';
+import sdk from '../../../index';
+import MatrixClientPeg from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
@@ -73,10 +73,10 @@ module.exports = React.createClass({
},
showCall: function() {
- var call;
+ let call;
if (this.props.room) {
- var roomId = this.props.room.roomId;
+ const roomId = this.props.room.roomId;
call = CallHandler.getCallForRoom(roomId) ||
(this.props.ConferenceHandler ?
this.props.ConferenceHandler.getConferenceCallForRoom(roomId) :
@@ -86,9 +86,7 @@ module.exports = React.createClass({
if (this.call) {
this.setState({ call: call });
}
-
- }
- else {
+ } else {
call = CallHandler.getAnyActiveCall();
this.setState({ call: call });
}
@@ -109,8 +107,7 @@ module.exports = React.createClass({
call.confUserId ? "none" : "block"
);
this.getVideoView().getRemoteVideoElement().style.display = "block";
- }
- else {
+ } else {
this.getVideoView().getLocalVideoElement().style.display = "none";
this.getVideoView().getRemoteVideoElement().style.display = "none";
dis.dispatch({action: 'video_fullscreen', fullscreen: false});
@@ -126,11 +123,11 @@ module.exports = React.createClass({
},
render: function() {
- var VideoView = sdk.getComponent('voip.VideoView');
+ const VideoView = sdk.getComponent('voip.VideoView');
- var voice;
+ let voice;
if (this.state.call && this.state.call.type === "voice" && this.props.showVoice) {
- var callRoom = MatrixClientPeg.get().getRoom(this.state.call.roomId);
+ const callRoom = MatrixClientPeg.get().getRoom(this.state.call.roomId);
voice = (
{_t("Active call (%(roomName)s)", {roomName: callRoom.name})}
@@ -147,6 +144,6 @@ module.exports = React.createClass({
{ voice }
);
- }
+ },
});
diff --git a/src/components/views/voip/IncomingCallBox.js b/src/components/views/voip/IncomingCallBox.js
index 1b806fc5b3..c5934b74dc 100644
--- a/src/components/views/voip/IncomingCallBox.js
+++ b/src/components/views/voip/IncomingCallBox.js
@@ -13,10 +13,9 @@ 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 MatrixClientPeg = require('../../../MatrixClientPeg');
-var dis = require("../../../dispatcher");
-var CallHandler = require("../../../CallHandler");
+import React from 'react';
+import MatrixClientPeg from '../../../MatrixClientPeg';
+import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
@@ -29,34 +28,32 @@ module.exports = React.createClass({
onAnswerClick: function() {
dis.dispatch({
action: 'answer',
- room_id: this.props.incomingCall.roomId
+ room_id: this.props.incomingCall.roomId,
});
},
onRejectClick: function() {
dis.dispatch({
action: 'hangup',
- room_id: this.props.incomingCall.roomId
+ room_id: this.props.incomingCall.roomId,
});
},
render: function() {
- var room = null;
+ let room = null;
if (this.props.incomingCall) {
room = MatrixClientPeg.get().getRoom(this.props.incomingCall.roomId);
}
- var caller = room ? room.name : _t("unknown caller");
+ const caller = room ? room.name : _t("unknown caller");
let incomingCallText = null;
if (this.props.incomingCall) {
if (this.props.incomingCall.type === "voice") {
incomingCallText = _t("Incoming voice call from %(name)s", {name: caller});
- }
- else if (this.props.incomingCall.type === "video") {
+ } else if (this.props.incomingCall.type === "video") {
incomingCallText = _t("Incoming video call from %(name)s", {name: caller});
- }
- else {
+ } else {
incomingCallText = _t("Incoming call from %(name)s", {name: caller});
}
}
@@ -81,6 +78,6 @@ module.exports = React.createClass({
);
- }
+ },
});
diff --git a/src/components/views/voip/VideoFeed.js b/src/components/views/voip/VideoFeed.js
index 0b8d0b20fc..953dbc866f 100644
--- a/src/components/views/voip/VideoFeed.js
+++ b/src/components/views/voip/VideoFeed.js
@@ -16,7 +16,7 @@ limitations under the License.
'use strict';
-var React = require('react');
+import React from 'react';
module.exports = React.createClass({
displayName: 'VideoFeed',
diff --git a/src/components/views/voip/VideoView.js b/src/components/views/voip/VideoView.js
index ea37579237..6ebf2078c1 100644
--- a/src/components/views/voip/VideoView.js
+++ b/src/components/views/voip/VideoView.js
@@ -16,11 +16,11 @@ limitations under the License.
'use strict';
-var React = require('react');
-var ReactDOM = require('react-dom');
+import React from 'react';
+import ReactDOM from 'react-dom';
-var sdk = require('../../../index');
-var dis = require('../../../dispatcher');
+import sdk from '../../../index';
+import dis from '../../../dispatcher';
module.exports = React.createClass({
displayName: 'VideoView',
@@ -53,9 +53,10 @@ module.exports = React.createClass({
// this needs to be somewhere at the top of the DOM which
// always exists to avoid audio interruptions.
// Might as well just use DOM.
- var remoteAudioElement = document.getElementById("remoteAudio");
+ const remoteAudioElement = document.getElementById("remoteAudio");
if (!remoteAudioElement) {
- console.error("Failed to find remoteAudio element - cannot play audio! You need to add an