Merge remote-tracking branch 'origin/develop' into dbkr/my_groups
This commit is contained in:
commit
45986306cc
51 changed files with 665 additions and 436 deletions
|
@ -45,8 +45,9 @@ export default React.createClass({
|
|||
this.setState({
|
||||
summary: null,
|
||||
error: null,
|
||||
}, () => {
|
||||
this._loadGroupFromServer(newProps.groupId);
|
||||
});
|
||||
this._loadGroupFromServer(newProps.groupId);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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 = (
|
||||
<div> Loading... </div>
|
||||
);
|
||||
} else {
|
||||
appTileBody = (
|
||||
<div className="mx_AppTileBody">
|
||||
<iframe ref="appFrame" src={this.state.widgetUrl} allowFullScreen="true"></iframe>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
|
||||
<div className="mx_AppTileMenuBar">
|
||||
|
@ -93,9 +154,7 @@ export default React.createClass({
|
|||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="mx_AppTileBody">
|
||||
<iframe ref="appFrame" src={this.props.url} allowFullScreen="true"></iframe>
|
||||
</div>
|
||||
{appTileBody}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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" ||
|
||||
|
|
|
@ -68,7 +68,7 @@ export default class Autocomplete extends React.Component {
|
|||
let autocompleteDelay = UserSettingsStore.getLocalSetting('autocompleteDelay', 200);
|
||||
|
||||
// Don't debounce if we are already showing completions
|
||||
if (this.state.completions.length > 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;
|
||||
|
|
|
@ -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 <img className={className}
|
||||
title={ _t(name) }
|
||||
onMouseDown={disabled ? null : onFormatButtonClicked}
|
||||
onMouseDown={onFormatButtonClicked}
|
||||
key={name}
|
||||
src={`img/button-text-${name}${suffix}.svg`}
|
||||
height="17" />;
|
||||
|
|
|
@ -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) => `<u>${text}</u>`,
|
||||
'strike': (text) => `<del>${text}</del>`,
|
||||
'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();
|
||||
|
|
|
@ -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 = (
|
||||
<AccessibleButton className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>
|
||||
{_t("Save")}
|
||||
</AccessibleButton>
|
||||
|
@ -196,39 +189,41 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
if (this.props.onCancelClick) {
|
||||
cancel_button = <CancelButton onClick={this.props.onCancelClick}/>;
|
||||
cancelButton = <CancelButton onClick={this.props.onCancelClick}/>;
|
||||
}
|
||||
|
||||
if (this.props.saving) {
|
||||
var Spinner = sdk.getComponent("elements.Spinner");
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
spinner = <div className="mx_RoomHeader_spinner"><Spinner/></div>;
|
||||
}
|
||||
|
||||
if (can_set_room_name) {
|
||||
var RoomNameEditor = sdk.getComponent("rooms.RoomNameEditor");
|
||||
if (canSetRoomName) {
|
||||
const RoomNameEditor = sdk.getComponent("rooms.RoomNameEditor");
|
||||
name = <RoomNameEditor ref="nameEditor" room={this.props.room} />;
|
||||
}
|
||||
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 = <div className="mx_RoomHeader_searchStatus"> { _t("(~%(count)s results)", { count: this.props.searchInfo.searchCount }) }</div>;
|
||||
if (this.props.searchInfo &&
|
||||
this.props.searchInfo.searchCount !== undefined &&
|
||||
this.props.searchInfo.searchCount !== null) {
|
||||
searchStatus = <div className="mx_RoomHeader_searchStatus">
|
||||
{ _t("(~%(count)s results)", { count: this.props.searchInfo.searchCount }) }
|
||||
</div>;
|
||||
}
|
||||
|
||||
// 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({
|
|||
</div>;
|
||||
}
|
||||
|
||||
if (can_set_room_topic) {
|
||||
var RoomTopicEditor = sdk.getComponent("rooms.RoomTopicEditor");
|
||||
topic_el = <RoomTopicEditor ref="topicEditor" room={this.props.room} />;
|
||||
if (canSetRoomTopic) {
|
||||
const RoomTopicEditor = sdk.getComponent("rooms.RoomTopicEditor");
|
||||
topicElement = <RoomTopicEditor ref="topicEditor" room={this.props.room} />;
|
||||
} 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 = <div className="mx_RoomHeader_topic" ref="topic" title={ topic } dir="auto">{ topic }</div>;
|
||||
topicElement =
|
||||
<div className="mx_RoomHeader_topic" ref="topic" title={ topic } dir="auto">{ topic }</div>;
|
||||
}
|
||||
}
|
||||
|
||||
var roomAvatar = null;
|
||||
if (can_set_room_avatar) {
|
||||
let roomAvatar = null;
|
||||
if (canSetRoomAvatar) {
|
||||
roomAvatar = (
|
||||
<div className="mx_RoomHeader_avatarPicker">
|
||||
<div onClick={ this.onAvatarPickerClick }>
|
||||
|
@ -276,8 +272,7 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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 = (
|
||||
<div onClick={this.props.onSettingsClick}>
|
||||
<RoomAvatar room={this.props.room} width={48} height={48} oobData={this.props.oobData} />
|
||||
|
@ -285,9 +280,8 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
var settings_button;
|
||||
if (this.props.onSettingsClick) {
|
||||
settings_button =
|
||||
settingsButton =
|
||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title={_t("Settings")}>
|
||||
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
|
||||
</AccessibleButton>;
|
||||
|
@ -301,61 +295,58 @@ module.exports = React.createClass({
|
|||
// </div>;
|
||||
// }
|
||||
|
||||
var forget_button;
|
||||
let forgetButton;
|
||||
if (this.props.onForgetClick) {
|
||||
forget_button =
|
||||
forgetButton =
|
||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onForgetClick} title={ _t("Forget room") }>
|
||||
<TintableSvg src="img/leave.svg" width="26" height="20"/>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
let search_button;
|
||||
let searchButton;
|
||||
if (this.props.onSearchClick && this.props.inRoom) {
|
||||
search_button =
|
||||
searchButton =
|
||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title={ _t("Search") }>
|
||||
<TintableSvg src="img/icons-search.svg" width="35" height="35"/>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
var rightPanel_buttons;
|
||||
let rightPanelButtons;
|
||||
if (this.props.collapsedRhs) {
|
||||
rightPanel_buttons =
|
||||
rightPanelButtons =
|
||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.onShowRhsClick} title={ _t('Show panel') }>
|
||||
<TintableSvg src="img/maximise.svg" width="10" height="16"/>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
var right_row;
|
||||
let rightRow;
|
||||
if (!this.props.editing) {
|
||||
right_row =
|
||||
rightRow =
|
||||
<div className="mx_RoomHeader_rightRow">
|
||||
{ settings_button }
|
||||
{ forget_button }
|
||||
{ search_button }
|
||||
{ rightPanel_buttons }
|
||||
{ settingsButton }
|
||||
{ forgetButton }
|
||||
{ searchButton }
|
||||
{ rightPanelButtons }
|
||||
</div>;
|
||||
}
|
||||
|
||||
header =
|
||||
<div className="mx_RoomHeader_wrapper">
|
||||
<div className="mx_RoomHeader_leftRow">
|
||||
<div className="mx_RoomHeader_avatar">
|
||||
{ roomAvatar }
|
||||
</div>
|
||||
<div className="mx_RoomHeader_info">
|
||||
{ name }
|
||||
{ topic_el }
|
||||
</div>
|
||||
</div>
|
||||
{spinner}
|
||||
{save_button}
|
||||
{cancel_button}
|
||||
{right_row}
|
||||
</div>;
|
||||
|
||||
return (
|
||||
<div className={ "mx_RoomHeader " + (this.props.editing ? "mx_RoomHeader_editing" : "") }>
|
||||
{ header }
|
||||
<div className="mx_RoomHeader_wrapper">
|
||||
<div className="mx_RoomHeader_leftRow">
|
||||
<div className="mx_RoomHeader_avatar">
|
||||
{ roomAvatar }
|
||||
</div>
|
||||
<div className="mx_RoomHeader_info">
|
||||
{ name }
|
||||
{ topicElement }
|
||||
</div>
|
||||
</div>
|
||||
{spinner}
|
||||
{saveButton}
|
||||
{cancelButton}
|
||||
{rightRow}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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 = <AccessibleButton className="mx_RoomSettings_unbanButton" onClick={this._onUnbanClick}>
|
||||
{ _t('Unban') }
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
<AccessibleButton className="mx_RoomSettings_unbanButton"
|
||||
onClick={this._onUnbanClick}
|
||||
>
|
||||
{ _t('Unban') }
|
||||
</AccessibleButton>
|
||||
{ unbanButton }
|
||||
<strong>{this.props.member.name}</strong> {this.props.member.userId}
|
||||
{this.props.reason ? " " +_t('Reason') + ": " + this.props.reason : ""}
|
||||
</li>
|
||||
|
@ -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 =
|
||||
<div>
|
||||
<h3>{ _t('Banned users') }</h3>
|
||||
|
@ -674,7 +680,7 @@ module.exports = React.createClass({
|
|||
{banned.map(function(member) {
|
||||
const banEvent = member.events.member.getContent();
|
||||
return (
|
||||
<BannedUser key={member.userId} member={member} reason={banEvent.reason} />
|
||||
<BannedUser key={member.userId} canUnban={canBanUsers} member={member} reason={banEvent.reason} />
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
|
|
@ -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 = (
|
||||
<div className="mx_CallView_voice" onClick={ this.props.onClick }>
|
||||
{_t("Active call (%(roomName)s)", {roomName: callRoom.name})}
|
||||
|
@ -147,6 +144,6 @@ module.exports = React.createClass({
|
|||
{ voice }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
import React from 'react';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'VideoFeed',
|
||||
|
|
|
@ -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 <audio/> to the DOM.");
|
||||
console.error("Failed to find remoteAudio element - cannot play audio!"
|
||||
+ "You need to add an <audio/> to the DOM.");
|
||||
}
|
||||
return remoteAudioElement;
|
||||
},
|
||||
|
@ -70,22 +71,21 @@ module.exports = React.createClass({
|
|||
|
||||
onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
case 'video_fullscreen':
|
||||
case 'video_fullscreen': {
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
var element = this.container;
|
||||
const element = this.container;
|
||||
if (payload.fullscreen) {
|
||||
var requestMethod = (
|
||||
const requestMethod = (
|
||||
element.requestFullScreen ||
|
||||
element.webkitRequestFullScreen ||
|
||||
element.mozRequestFullScreen ||
|
||||
element.msRequestFullscreen
|
||||
);
|
||||
requestMethod.call(element);
|
||||
}
|
||||
else {
|
||||
var exitMethod = (
|
||||
} else {
|
||||
const exitMethod = (
|
||||
document.exitFullscreen ||
|
||||
document.mozCancelFullScreen ||
|
||||
document.webkitExitFullscreen ||
|
||||
|
@ -96,17 +96,18 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var VideoFeed = sdk.getComponent('voip.VideoFeed');
|
||||
const VideoFeed = sdk.getComponent('voip.VideoFeed');
|
||||
|
||||
// if we're fullscreen, we don't want to set a maxHeight on the video element.
|
||||
var fullscreenElement = (document.fullscreenElement ||
|
||||
const fullscreenElement = (document.fullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement);
|
||||
var maxVideoHeight = fullscreenElement ? null : this.props.maxHeight;
|
||||
const maxVideoHeight = fullscreenElement ? null : this.props.maxHeight;
|
||||
|
||||
return (
|
||||
<div className="mx_VideoView" ref={this.setContainer} onClick={ this.props.onClick }>
|
||||
|
@ -119,5 +120,5 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue