Merge branch 'develop' into feature-composer-emoji
This commit is contained in:
commit
b7555f49ea
44 changed files with 1057 additions and 311 deletions
|
@ -390,7 +390,7 @@ module.exports = React.createClass({
|
|||
|
||||
// FIXME: controller shouldn't be loading a view :(
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
var modal = Modal.createDialog(Loader);
|
||||
var modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||
|
||||
d.then(function() {
|
||||
modal.close();
|
||||
|
|
|
@ -44,6 +44,9 @@ module.exports = React.createClass({
|
|||
// ID of an event to highlight. If undefined, no event will be highlighted.
|
||||
highlightedEventId: React.PropTypes.string,
|
||||
|
||||
// Should we show URL Previews
|
||||
showUrlPreview: React.PropTypes.bool,
|
||||
|
||||
// event after which we should show a read marker
|
||||
readMarkerEventId: React.PropTypes.string,
|
||||
|
||||
|
@ -365,6 +368,7 @@ module.exports = React.createClass({
|
|||
onWidgetLoad={this._onWidgetLoad}
|
||||
readReceipts={readReceipts}
|
||||
readReceiptMap={this._readReceiptMap}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
checkUnmounting={this._isUnmounting}
|
||||
eventSendStatus={mxEv.status}
|
||||
last={last} isSelectedEvent={highlight}/>
|
||||
|
|
|
@ -26,9 +26,9 @@ module.exports = React.createClass({
|
|||
propTypes: {
|
||||
// the room this statusbar is representing.
|
||||
room: React.PropTypes.object.isRequired,
|
||||
|
||||
// a list of TabCompleteEntries.Entry objects
|
||||
tabCompleteEntries: React.PropTypes.array,
|
||||
|
||||
// a TabComplete object
|
||||
tabComplete: React.PropTypes.object.isRequired,
|
||||
|
||||
// the number of messages which have arrived since we've been scrolled up
|
||||
numUnreadMessages: React.PropTypes.number,
|
||||
|
@ -208,11 +208,11 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.tabCompleteEntries) {
|
||||
if (this.props.tabComplete.isTabCompleting()) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar_tabCompleteBar">
|
||||
<div className="mx_RoomStatusBar_tabCompleteWrapper">
|
||||
<TabCompleteBar entries={this.props.tabCompleteEntries} />
|
||||
<TabCompleteBar tabComplete={this.props.tabComplete} />
|
||||
<div className="mx_RoomStatusBar_tabCompleteEol" title="->|">
|
||||
<TintableSvg src="img/eol.svg" width="22" height="16"/>
|
||||
Auto-complete
|
||||
|
@ -233,7 +233,7 @@ module.exports = React.createClass({
|
|||
<a className="mx_RoomStatusBar_resend_link"
|
||||
onClick={ this.props.onResendAllClick }>
|
||||
Resend all
|
||||
</a> or <a
|
||||
</a> or <a
|
||||
className="mx_RoomStatusBar_resend_link"
|
||||
onClick={ this.props.onCancelAllClick }>
|
||||
cancel all
|
||||
|
@ -247,7 +247,7 @@ module.exports = React.createClass({
|
|||
// unread count trumps who is typing since the unread count is only
|
||||
// set when you've scrolled up
|
||||
if (this.props.numUnreadMessages) {
|
||||
var unreadMsgs = this.props.numUnreadMessages + " new message" +
|
||||
var unreadMsgs = this.props.numUnreadMessages + " new message" +
|
||||
(this.props.numUnreadMessages > 1 ? "s" : "");
|
||||
|
||||
return (
|
||||
|
@ -291,5 +291,5 @@ module.exports = React.createClass({
|
|||
{content}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -31,10 +31,7 @@ var Modal = require("../../Modal");
|
|||
var sdk = require('../../index');
|
||||
var CallHandler = require('../../CallHandler');
|
||||
var TabComplete = require("../../TabComplete");
|
||||
var MemberEntry = require("../../TabCompleteEntries").MemberEntry;
|
||||
var CommandEntry = require("../../TabCompleteEntries").CommandEntry;
|
||||
var Resend = require("../../Resend");
|
||||
var SlashCommands = require("../../SlashCommands");
|
||||
var dis = require("../../dispatcher");
|
||||
var Tinter = require("../../Tinter");
|
||||
var rate_limited_func = require('../../ratelimitedfunc');
|
||||
|
@ -141,6 +138,7 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData);
|
||||
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||
|
||||
this.tabComplete = new TabComplete({
|
||||
allowLooping: false,
|
||||
|
@ -204,6 +202,9 @@ module.exports = React.createClass({
|
|||
user_is_in_room = this.state.room.hasMembershipState(
|
||||
MatrixClientPeg.get().credentials.userId, 'join'
|
||||
);
|
||||
|
||||
this._updateAutoComplete();
|
||||
this.tabComplete.loadEntries(this.state.room);
|
||||
}
|
||||
|
||||
if (!user_is_in_room && this.state.roomId) {
|
||||
|
@ -267,6 +268,7 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().removeListener("Room.accountData", this.onRoomAccountData);
|
||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
}
|
||||
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
|
@ -338,6 +340,10 @@ module.exports = React.createClass({
|
|||
// ignore events for other rooms
|
||||
if (!this.state.room || room.roomId != this.state.room.roomId) return;
|
||||
|
||||
if (ev.getType() === "org.matrix.room.preview_urls") {
|
||||
this._updatePreviewUrlVisibility(room);
|
||||
}
|
||||
|
||||
// ignore anything but real-time updates at the end of the room:
|
||||
// updates from pagination will happen when the paginate completes.
|
||||
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
||||
|
@ -357,12 +363,21 @@ module.exports = React.createClass({
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
// update the tab complete list as it depends on who most recently spoke,
|
||||
// and that has probably just changed
|
||||
if (ev.sender) {
|
||||
this.tabComplete.onMemberSpoke(ev.sender);
|
||||
// nb. we don't need to update the new autocomplete here since
|
||||
// its results are currently ordered purely by search score.
|
||||
}
|
||||
},
|
||||
|
||||
// called when state.room is first initialised (either at initial load,
|
||||
// after a successful peek, or after we join the room).
|
||||
_onRoomLoaded: function(room) {
|
||||
this._calculatePeekRules(room);
|
||||
this._updatePreviewUrlVisibility(room);
|
||||
},
|
||||
|
||||
_calculatePeekRules: function(room) {
|
||||
|
@ -381,6 +396,42 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_updatePreviewUrlVisibility: function(room) {
|
||||
// console.log("_updatePreviewUrlVisibility");
|
||||
|
||||
// check our per-room overrides
|
||||
var roomPreviewUrls = room.getAccountData("org.matrix.room.preview_urls");
|
||||
if (roomPreviewUrls && roomPreviewUrls.getContent().disable !== undefined) {
|
||||
this.setState({
|
||||
showUrlPreview: !roomPreviewUrls.getContent().disable
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// check our global disable override
|
||||
var userRoomPreviewUrls = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls");
|
||||
if (userRoomPreviewUrls && userRoomPreviewUrls.getContent().disable) {
|
||||
this.setState({
|
||||
showUrlPreview: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// check the room state event
|
||||
var roomStatePreviewUrls = room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
|
||||
if (roomStatePreviewUrls && roomStatePreviewUrls.getContent().disable) {
|
||||
this.setState({
|
||||
showUrlPreview: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, we assume they're on.
|
||||
this.setState({
|
||||
showUrlPreview: true
|
||||
});
|
||||
},
|
||||
|
||||
onRoom: function(room) {
|
||||
// This event is fired when the room is 'stored' by the JS SDK, which
|
||||
// means it's now a fully-fledged room object ready to be used, so
|
||||
|
@ -411,14 +462,23 @@ module.exports = React.createClass({
|
|||
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
||||
},
|
||||
|
||||
onRoomAccountData: function(room, event) {
|
||||
if (room.roomId == this.props.roomId) {
|
||||
if (event.getType === "org.matrix.room.color_scheme") {
|
||||
onAccountData: function(event) {
|
||||
if (event.getType() === "org.matrix.preview_urls" && this.state.room) {
|
||||
this._updatePreviewUrlVisibility(this.state.room);
|
||||
}
|
||||
},
|
||||
|
||||
onRoomAccountData: function(event, room) {
|
||||
if (room.roomId == this.state.roomId) {
|
||||
if (event.getType() === "org.matrix.room.color_scheme") {
|
||||
var color_scheme = event.getContent();
|
||||
// XXX: we should validate the event
|
||||
console.log("Tinter.tint from onRoomAccountData");
|
||||
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
||||
}
|
||||
else if (event.getType() === "org.matrix.room.preview_urls") {
|
||||
this._updatePreviewUrlVisibility(room);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -434,7 +494,8 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
// a member state changed in this room, refresh the tab complete list
|
||||
this._updateTabCompleteList();
|
||||
this.tabComplete.loadEntries(this.state.room);
|
||||
this._updateAutoComplete();
|
||||
|
||||
// if we are now a member of the room, where we were not before, that
|
||||
// means we have finished joining a room we were previously peeking
|
||||
|
@ -499,8 +560,6 @@ module.exports = React.createClass({
|
|||
window.addEventListener('resize', this.onResize);
|
||||
this.onResize();
|
||||
|
||||
this._updateTabCompleteList();
|
||||
|
||||
// XXX: EVIL HACK to autofocus inviting on empty rooms.
|
||||
// We use the setTimeout to avoid racing with focus_composer.
|
||||
if (this.state.room &&
|
||||
|
@ -518,24 +577,6 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_updateTabCompleteList: function() {
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
if (!this.state.room) {
|
||||
return;
|
||||
}
|
||||
var members = this.state.room.getJoinedMembers().filter(function(member) {
|
||||
if (member.userId !== cli.credentials.userId) return true;
|
||||
});
|
||||
|
||||
UserProvider.getInstance().setUserList(members);
|
||||
this.tabComplete.setCompletionList(
|
||||
MemberEntry.fromMemberList(members).concat(
|
||||
CommandEntry.fromCommands(SlashCommands.getCommandList())
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (this.refs.roomView) {
|
||||
var roomView = ReactDOM.findDOMNode(this.refs.roomView);
|
||||
|
@ -1260,6 +1301,14 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_updateAutoComplete: function() {
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
const members = this.state.room.getJoinedMembers().filter(function(member) {
|
||||
if (member.userId !== myUserId) return true;
|
||||
});
|
||||
UserProvider.getInstance().setUserList(members);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||
var MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||
|
@ -1373,12 +1422,10 @@ module.exports = React.createClass({
|
|||
statusBar = <UploadBar room={this.state.room} />
|
||||
} else if (!this.state.searchResults) {
|
||||
var RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
|
||||
var tabEntries = this.tabComplete.isTabCompleting() ?
|
||||
this.tabComplete.peek(6) : null;
|
||||
|
||||
statusBar = <RoomStatusBar
|
||||
room={this.state.room}
|
||||
tabCompleteEntries={tabEntries}
|
||||
tabComplete={this.tabComplete}
|
||||
numUnreadMessages={this.state.numUnreadMessages}
|
||||
hasUnsentMessages={this.state.hasUnsentMessages}
|
||||
atEndOfLiveTimeline={this.state.atEndOfLiveTimeline}
|
||||
|
@ -1511,6 +1558,8 @@ module.exports = React.createClass({
|
|||
hideMessagePanel = true;
|
||||
}
|
||||
|
||||
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
||||
|
||||
var messagePanel = (
|
||||
<TimelinePanel ref={this._gatherTimelinePanelRef}
|
||||
room={this.state.room}
|
||||
|
@ -1520,6 +1569,7 @@ module.exports = React.createClass({
|
|||
eventPixelOffset={this.props.eventPixelOffset}
|
||||
onScroll={ this.onMessageListScroll }
|
||||
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
|
||||
showUrlPreview = { this.state.showUrlPreview }
|
||||
opacity={ this.props.opacity }
|
||||
/>);
|
||||
|
||||
|
|
|
@ -71,6 +71,9 @@ var TimelinePanel = React.createClass({
|
|||
// half way down the viewport.
|
||||
eventPixelOffset: React.PropTypes.number,
|
||||
|
||||
// Should we show URL Previews
|
||||
showUrlPreview: React.PropTypes.bool,
|
||||
|
||||
// callback which is called when the panel is scrolled.
|
||||
onScroll: React.PropTypes.func,
|
||||
|
||||
|
@ -934,6 +937,7 @@ var TimelinePanel = React.createClass({
|
|||
readMarkerEventId={ this.state.readMarkerEventId }
|
||||
readMarkerVisible={ this.state.readMarkerVisible }
|
||||
suppressFirstDateSeparator={ this.state.canBackPaginate }
|
||||
showUrlPreview = { this.props.showUrlPreview }
|
||||
ourUserId={ MatrixClientPeg.get().credentials.userId }
|
||||
stickyBottom={ stickyBottom }
|
||||
onScroll={ this.onMessageListScroll }
|
||||
|
|
|
@ -214,9 +214,10 @@ module.exports = React.createClass({
|
|||
onFinished: this.onEmailDialogFinished,
|
||||
});
|
||||
}, (err) => {
|
||||
this.setState({email_add_pending: false});
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unable to add email address",
|
||||
description: err.toString()
|
||||
description: err.message
|
||||
});
|
||||
});
|
||||
ReactDOM.findDOMNode(this.refs.add_threepid_input).blur();
|
||||
|
@ -261,6 +262,63 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
_renderUserInterfaceSettings: function() {
|
||||
var client = MatrixClientPeg.get();
|
||||
|
||||
var settingsLabels = [
|
||||
/*
|
||||
{
|
||||
id: 'alwaysShowTimestamps',
|
||||
label: 'Always show message timestamps',
|
||||
},
|
||||
{
|
||||
id: 'showTwelveHourTimestamps',
|
||||
label: 'Show timestamps in 12 hour format (e.g. 2:30pm)',
|
||||
},
|
||||
{
|
||||
id: 'useCompactLayout',
|
||||
label: 'Use compact timeline layout',
|
||||
},
|
||||
{
|
||||
id: 'useFixedWidthFont',
|
||||
label: 'Use fixed width font',
|
||||
},
|
||||
*/
|
||||
];
|
||||
|
||||
var syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>User Interface</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_toggle">
|
||||
<input id="urlPreviewsDisabled"
|
||||
type="checkbox"
|
||||
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
||||
onChange={ e => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
||||
/>
|
||||
<label htmlFor="urlPreviewsDisabled">
|
||||
Disable inline URL previews by default
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{ settingsLabels.forEach( setting => {
|
||||
<div className="mx_UserSettings_toggle">
|
||||
<input id={ setting.id }
|
||||
type="checkbox"
|
||||
defaultChecked={ syncedSettings[setting.id] }
|
||||
onChange={ e => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
|
||||
/>
|
||||
<label htmlFor={ setting.id }>
|
||||
{ settings.label }
|
||||
</label>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_renderDeviceInfo: function() {
|
||||
if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) {
|
||||
return null;
|
||||
|
@ -378,7 +436,7 @@ module.exports = React.createClass({
|
|||
|
||||
this._renderLabs = function () {
|
||||
let features = LABS_FEATURES.map(feature => (
|
||||
<div key={feature.id}>
|
||||
<div key={feature.id} className="mx_UserSettings_toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={feature.id}
|
||||
|
@ -452,6 +510,8 @@ module.exports = React.createClass({
|
|||
|
||||
{notification_area}
|
||||
|
||||
{this._renderUserInterfaceSettings()}
|
||||
|
||||
{this._renderDeviceInfo()}
|
||||
|
||||
{this._renderLabs()}
|
||||
|
|
|
@ -54,6 +54,16 @@ module.exports = React.createClass({
|
|||
return {
|
||||
busy: false,
|
||||
errorText: null,
|
||||
// We remember the values entered by the user because
|
||||
// the registration form will be unmounted during the
|
||||
// course of registration, but if there's an error we
|
||||
// want to bring back the registration form with the
|
||||
// values the user entered still in it. We can keep
|
||||
// them in this component's state since this component
|
||||
// persist for the duration of the registration process.
|
||||
formVals: {
|
||||
email: this.props.email,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -108,7 +118,8 @@ module.exports = React.createClass({
|
|||
var self = this;
|
||||
this.setState({
|
||||
errorText: "",
|
||||
busy: true
|
||||
busy: true,
|
||||
formVals: formVals,
|
||||
});
|
||||
|
||||
if (formVals.username !== this.props.username) {
|
||||
|
@ -228,11 +239,15 @@ module.exports = React.createClass({
|
|||
break; // NOP
|
||||
case "Register.START":
|
||||
case "Register.STEP_m.login.dummy":
|
||||
// NB. Our 'username' prop is specifically for upgrading
|
||||
// a guest account
|
||||
registerStep = (
|
||||
<RegistrationForm
|
||||
showEmail={true}
|
||||
defaultUsername={this.props.username}
|
||||
defaultEmail={this.props.email}
|
||||
defaultUsername={this.state.formVals.username}
|
||||
defaultEmail={this.state.formVals.email}
|
||||
defaultPassword={this.state.formVals.password}
|
||||
guestUsername={this.props.username}
|
||||
minPasswordLength={MIN_PASSWORD_LENGTH}
|
||||
onError={this.onFormValidationFailed}
|
||||
onRegisterClick={this.onFormSubmit} />
|
||||
|
|
|
@ -59,7 +59,7 @@ module.exports = React.createClass({
|
|||
{this.props.description}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.props.onFinished} autoFocus={this.props.focus}>
|
||||
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={this.props.focus}>
|
||||
{this.props.button}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -46,7 +46,7 @@ module.exports = React.createClass({
|
|||
Sign out?
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons" onKeyDown={ this.onKeyDown }>
|
||||
<button autoFocus onClick={this.logOut}>Sign Out</button>
|
||||
<button className="mx_Dialog_primary" autoFocus onClick={this.logOut}>Sign Out</button>
|
||||
<button onClick={this.cancelPrompt}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -63,7 +63,7 @@ module.exports = React.createClass({
|
|||
{this.props.description}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.props.onFinished} autoFocus={true}>
|
||||
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={true}>
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={this.onRegisterClicked}>
|
||||
|
|
|
@ -56,7 +56,7 @@ module.exports = React.createClass({
|
|||
{this.props.description}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.onOk} autoFocus={this.props.focus}>
|
||||
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
|
||||
{this.props.button}
|
||||
</button>
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ module.exports = React.createClass({
|
|||
/>
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<input type="submit" value="Set" />
|
||||
<input className="mx_Dialog_primary" type="submit" value="Set" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -86,7 +86,7 @@ module.exports = React.createClass({
|
|||
<button onClick={this.onCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={this.onOk}>
|
||||
<button className="mx_Dialog_primary" onClick={this.onOk}>
|
||||
{this.props.button}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -35,8 +35,16 @@ module.exports = React.createClass({
|
|||
displayName: 'RegistrationForm',
|
||||
|
||||
propTypes: {
|
||||
// Values pre-filled in the input boxes when the component loads
|
||||
defaultEmail: React.PropTypes.string,
|
||||
defaultUsername: React.PropTypes.string,
|
||||
defaultPassword: React.PropTypes.string,
|
||||
|
||||
// A username that will be used if no username is entered.
|
||||
// Specifying this param will also warn the user that entering
|
||||
// a different username will cause a fresh account to be generated.
|
||||
guestUsername: React.PropTypes.string,
|
||||
|
||||
showEmail: React.PropTypes.bool,
|
||||
minPasswordLength: React.PropTypes.number,
|
||||
onError: React.PropTypes.func,
|
||||
|
@ -55,10 +63,6 @@ module.exports = React.createClass({
|
|||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
email: this.props.defaultEmail,
|
||||
username: null,
|
||||
password: null,
|
||||
passwordConfirm: null,
|
||||
fieldValid: {}
|
||||
};
|
||||
},
|
||||
|
@ -103,7 +107,7 @@ module.exports = React.createClass({
|
|||
|
||||
_doSubmit: function() {
|
||||
var promise = this.props.onRegisterClick({
|
||||
username: this.refs.username.value.trim() || this.props.defaultUsername,
|
||||
username: this.refs.username.value.trim() || this.props.guestUsername,
|
||||
password: this.refs.password.value.trim(),
|
||||
email: this.refs.email.value.trim()
|
||||
});
|
||||
|
@ -144,7 +148,7 @@ module.exports = React.createClass({
|
|||
break;
|
||||
case FIELD_USERNAME:
|
||||
// XXX: SPEC-1
|
||||
var username = this.refs.username.value.trim() || this.props.defaultUsername;
|
||||
var username = this.refs.username.value.trim() || this.props.guestUsername;
|
||||
if (encodeURIComponent(username) != username) {
|
||||
this.markFieldValid(
|
||||
field_id,
|
||||
|
@ -225,7 +229,7 @@ module.exports = React.createClass({
|
|||
emailSection = (
|
||||
<input className="mx_Login_field" type="text" ref="email"
|
||||
autoFocus={true} placeholder="Email address (optional)"
|
||||
defaultValue={this.state.email}
|
||||
defaultValue={this.props.defaultEmail}
|
||||
style={this._styleField(FIELD_EMAIL)}
|
||||
onBlur={function() {self.validateField(FIELD_EMAIL)}} />
|
||||
);
|
||||
|
@ -237,8 +241,8 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
var placeholderUserName = "User name";
|
||||
if (this.props.defaultUsername) {
|
||||
placeholderUserName += " (default: " + this.props.defaultUsername + ")"
|
||||
if (this.props.guestUsername) {
|
||||
placeholderUserName += " (default: " + this.props.guestUsername + ")"
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -247,23 +251,23 @@ module.exports = React.createClass({
|
|||
{emailSection}
|
||||
<br />
|
||||
<input className="mx_Login_field" type="text" ref="username"
|
||||
placeholder={ placeholderUserName } defaultValue={this.state.username}
|
||||
placeholder={ placeholderUserName } defaultValue={this.props.defaultUsername}
|
||||
style={this._styleField(FIELD_USERNAME)}
|
||||
onBlur={function() {self.validateField(FIELD_USERNAME)}} />
|
||||
<br />
|
||||
{ this.props.defaultUsername ?
|
||||
{ this.props.guestUsername ?
|
||||
<div className="mx_Login_fieldLabel">Setting a user name will create a fresh account</div> : null
|
||||
}
|
||||
<input className="mx_Login_field" type="password" ref="password"
|
||||
style={this._styleField(FIELD_PASSWORD)}
|
||||
onBlur={function() {self.validateField(FIELD_PASSWORD)}}
|
||||
placeholder="Password" defaultValue={this.state.password} />
|
||||
placeholder="Password" defaultValue={this.props.defaultPassword} />
|
||||
<br />
|
||||
<input className="mx_Login_field" type="password" ref="passwordConfirm"
|
||||
placeholder="Confirm password"
|
||||
style={this._styleField(FIELD_PASSWORD_CONFIRM)}
|
||||
onBlur={function() {self.validateField(FIELD_PASSWORD_CONFIRM)}}
|
||||
defaultValue={this.state.passwordConfirm} />
|
||||
defaultValue={this.props.defaultPassword} />
|
||||
<br />
|
||||
{registerButton}
|
||||
</form>
|
||||
|
|
|
@ -34,7 +34,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
if (fullWidth < thumbWidth && fullHeight < thumbHeight) {
|
||||
// no scaling needs to be applied
|
||||
return fullHeight;
|
||||
return 1;
|
||||
}
|
||||
var widthMulti = thumbWidth / fullWidth;
|
||||
var heightMulti = thumbHeight / fullHeight;
|
||||
|
|
|
@ -38,6 +38,9 @@ module.exports = React.createClass({
|
|||
/* link URL for the highlights */
|
||||
highlightLink: React.PropTypes.string,
|
||||
|
||||
/* should show URL previews for this event */
|
||||
showUrlPreview: React.PropTypes.bool,
|
||||
|
||||
/* callback called when dynamic content in events are loaded */
|
||||
onWidgetLoad: React.PropTypes.func,
|
||||
},
|
||||
|
@ -71,6 +74,7 @@ module.exports = React.createClass({
|
|||
|
||||
return <BodyType ref="body" mxEvent={this.props.mxEvent} highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -39,6 +39,9 @@ module.exports = React.createClass({
|
|||
/* link URL for the highlights */
|
||||
highlightLink: React.PropTypes.string,
|
||||
|
||||
/* should show URL previews for this event */
|
||||
showUrlPreview: React.PropTypes.bool,
|
||||
|
||||
/* callback for when our widget has loaded */
|
||||
onWidgetLoad: React.PropTypes.func,
|
||||
},
|
||||
|
@ -56,34 +59,47 @@ module.exports = React.createClass({
|
|||
|
||||
componentDidMount: function() {
|
||||
linkifyElement(this.refs.content, linkifyMatrix.options);
|
||||
|
||||
var links = this.findLinks(this.refs.content.children);
|
||||
if (links.length) {
|
||||
this.setState({ links: links.map((link)=>{
|
||||
return link.getAttribute("href");
|
||||
})});
|
||||
|
||||
// lazy-load the hidden state of the preview widget from localstorage
|
||||
if (global.localStorage) {
|
||||
var hidden = global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId());
|
||||
this.setState({ widgetHidden: hidden });
|
||||
}
|
||||
}
|
||||
this.calculateUrlPreview();
|
||||
|
||||
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
|
||||
HtmlUtils.highlightDom(ReactDOM.findDOMNode(this));
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
this.calculateUrlPreview();
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
//console.log("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
|
||||
|
||||
// exploit that events are immutable :)
|
||||
// ...and that .links is only ever set in componentDidMount and never changes
|
||||
return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
|
||||
nextProps.highlights !== this.props.highlights ||
|
||||
nextProps.highlightLink !== this.props.highlightLink ||
|
||||
nextProps.showUrlPreview !== this.props.showUrlPreview ||
|
||||
nextState.links !== this.state.links ||
|
||||
nextState.widgetHidden !== this.state.widgetHidden);
|
||||
},
|
||||
|
||||
calculateUrlPreview: function() {
|
||||
//console.log("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
|
||||
|
||||
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");
|
||||
})});
|
||||
|
||||
// lazy-load the hidden state of the preview widget from localstorage
|
||||
if (global.localStorage) {
|
||||
var hidden = global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId());
|
||||
this.setState({ widgetHidden: hidden });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
findLinks: function(nodes) {
|
||||
var links = [];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
|
@ -163,12 +179,14 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
var mxEvent = this.props.mxEvent;
|
||||
var content = mxEvent.getContent();
|
||||
var body = HtmlUtils.bodyToHtml(content, this.props.highlights,
|
||||
{highlightLink: this.props.highlightLink});
|
||||
var body = HtmlUtils.bodyToHtml(content, this.props.highlights, {});
|
||||
|
||||
if (this.props.highlightLink) {
|
||||
body = <a href={ this.props.highlightLink }>{ body }</a>;
|
||||
}
|
||||
|
||||
var widgets;
|
||||
if (this.state.links.length && !this.state.widgetHidden) {
|
||||
if (this.state.links.length && !this.state.widgetHidden && this.props.showUrlPreview) {
|
||||
var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget');
|
||||
widgets = this.state.links.map((link)=>{
|
||||
return <LinkPreviewWidget
|
||||
|
|
|
@ -83,13 +83,11 @@ module.exports = React.createClass({
|
|||
alias: this.state.canonicalAlias
|
||||
}, ""
|
||||
)
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// save new aliases for m.room.aliases
|
||||
var aliasOperations = this.getAliasOperations();
|
||||
var promises = [];
|
||||
for (var i = 0; i < aliasOperations.length; i++) {
|
||||
var alias_operation = aliasOperations[i];
|
||||
console.log("alias %s %s", alias_operation.place, alias_operation.val);
|
||||
|
@ -301,7 +299,7 @@ module.exports = React.createClass({
|
|||
<div className="mx_RoomSettings_addAlias">
|
||||
<img src="img/plus.svg" width="14" height="14" alt="Add"
|
||||
onClick={ self.onAliasAdded.bind(self, undefined) }/>
|
||||
</div>
|
||||
</div>
|
||||
</div> : ""
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -57,7 +57,7 @@ module.exports = React.createClass({
|
|||
data.primary_color = scheme.primary_color;
|
||||
data.secondary_color = scheme.secondary_color;
|
||||
data.index = this._getColorIndex(data);
|
||||
|
||||
|
||||
if (data.index === -1) {
|
||||
// append the unrecognised colours to our palette
|
||||
data.index = ROOM_COLORS.length;
|
||||
|
|
157
src/components/views/room_settings/UrlPreviewSettings.js
Normal file
157
src/components/views/room_settings/UrlPreviewSettings.js
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var q = require("q");
|
||||
var React = require('react');
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
var sdk = require("../../../index");
|
||||
var Modal = require("../../../Modal");
|
||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'UrlPreviewSettings',
|
||||
|
||||
propTypes: {
|
||||
room: React.PropTypes.object,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var cli = MatrixClientPeg.get();
|
||||
var roomState = this.props.room.currentState;
|
||||
|
||||
var roomPreviewUrls = this.props.room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
|
||||
var userPreviewUrls = this.props.room.getAccountData("org.matrix.room.preview_urls");
|
||||
|
||||
return {
|
||||
globalDisableUrlPreview: (roomPreviewUrls && roomPreviewUrls.getContent().disable) || false,
|
||||
userDisableUrlPreview: (userPreviewUrls && (userPreviewUrls.getContent().disable === true)) || false,
|
||||
userEnableUrlPreview: (userPreviewUrls && (userPreviewUrls.getContent().disable === false)) || false,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.originalState = Object.assign({}, this.state);
|
||||
},
|
||||
|
||||
saveSettings: function() {
|
||||
var promises = [];
|
||||
|
||||
if (this.state.globalDisableUrlPreview !== this.originalState.globalDisableUrlPreview) {
|
||||
console.log("UrlPreviewSettings: Updating room's preview_urls state event");
|
||||
promises.push(
|
||||
MatrixClientPeg.get().sendStateEvent(
|
||||
this.props.room.roomId, "org.matrix.room.preview_urls", {
|
||||
disable: this.state.globalDisableUrlPreview
|
||||
}, ""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var content = undefined;
|
||||
if (this.state.userDisableUrlPreview !== this.originalState.userDisableUrlPreview) {
|
||||
console.log("UrlPreviewSettings: Disabling user's per-room preview_urls");
|
||||
content = this.state.userDisableUrlPreview ? { disable : true } : {};
|
||||
}
|
||||
|
||||
if (this.state.userEnableUrlPreview !== this.originalState.userEnableUrlPreview) {
|
||||
console.log("UrlPreviewSettings: Enabling user's per-room preview_urls");
|
||||
if (!content || content.disable === undefined) {
|
||||
content = this.state.userEnableUrlPreview ? { disable : false } : {};
|
||||
}
|
||||
}
|
||||
|
||||
if (content) {
|
||||
promises.push(
|
||||
MatrixClientPeg.get().setRoomAccountData(
|
||||
this.props.room.roomId, "org.matrix.room.preview_urls", content
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
console.log("UrlPreviewSettings: saveSettings: " + JSON.stringify(promises));
|
||||
|
||||
return promises;
|
||||
},
|
||||
|
||||
onGlobalDisableUrlPreviewChange: function() {
|
||||
this.setState({
|
||||
globalDisableUrlPreview: this.refs.globalDisableUrlPreview.checked ? true : false,
|
||||
});
|
||||
},
|
||||
|
||||
onUserEnableUrlPreviewChange: function() {
|
||||
this.setState({
|
||||
userDisableUrlPreview: false,
|
||||
userEnableUrlPreview: this.refs.userEnableUrlPreview.checked ? true : false,
|
||||
});
|
||||
},
|
||||
|
||||
onUserDisableUrlPreviewChange: function() {
|
||||
this.setState({
|
||||
userDisableUrlPreview: this.refs.userDisableUrlPreview.checked ? true : false,
|
||||
userEnableUrlPreview: false,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
var roomState = this.props.room.currentState;
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
var maySetRoomPreviewUrls = roomState.mayClientSendStateEvent('org.matrix.room.preview_urls', cli);
|
||||
var disableRoomPreviewUrls;
|
||||
if (maySetRoomPreviewUrls) {
|
||||
disableRoomPreviewUrls =
|
||||
<label>
|
||||
<input type="checkbox" ref="globalDisableUrlPreview"
|
||||
onChange={ this.onGlobalDisableUrlPreviewChange }
|
||||
checked={ this.state.globalDisableUrlPreview } />
|
||||
Disable URL previews by default for participants in this room
|
||||
</label>
|
||||
}
|
||||
else {
|
||||
disableRoomPreviewUrls =
|
||||
<label>
|
||||
URL previews are { this.state.globalDisableUrlPreview ? "disabled" : "enabled" } by default for participants in this room.
|
||||
</label>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomSettings_toggles">
|
||||
<h3>URL Previews</h3>
|
||||
|
||||
<label>
|
||||
You have <a href="#/settings">{ UserSettingsStore.getUrlPreviewsDisabled() ? 'disabled' : 'enabled' }</a> URL previews by default.
|
||||
</label>
|
||||
{ disableRoomPreviewUrls }
|
||||
<label>
|
||||
<input type="checkbox" ref="userEnableUrlPreview"
|
||||
onChange={ this.onUserEnableUrlPreviewChange }
|
||||
checked={ this.state.userEnableUrlPreview } />
|
||||
Enable URL previews for this room (affects only you)
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" ref="userDisableUrlPreview"
|
||||
onChange={ this.onUserDisableUrlPreviewChange }
|
||||
checked={ this.state.userDisableUrlPreview } />
|
||||
Disable URL previews for this room (affects only you)
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
});
|
|
@ -29,6 +29,23 @@ var PRESENCE_CLASS = {
|
|||
"unavailable": "mx_EntityTile_unavailable"
|
||||
};
|
||||
|
||||
|
||||
function presenceClassForMember(presenceState, lastActiveAgo) {
|
||||
// offline is split into two categories depending on whether we have
|
||||
// a last_active_ago for them.
|
||||
if (presenceState == 'offline') {
|
||||
if (lastActiveAgo) {
|
||||
return PRESENCE_CLASS['offline'] + '_beenactive';
|
||||
} else {
|
||||
return PRESENCE_CLASS['offline'] + '_neveractive';
|
||||
}
|
||||
} else if (presenceState) {
|
||||
return PRESENCE_CLASS[presenceState];
|
||||
} else {
|
||||
return PRESENCE_CLASS['offline'] + '_neveractive';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'EntityTile',
|
||||
|
||||
|
@ -79,7 +96,10 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
var presenceClass = PRESENCE_CLASS[this.props.presenceState] || "mx_EntityTile_offline";
|
||||
const presenceClass = presenceClassForMember(
|
||||
this.props.presenceState, this.props.presenceLastActiveAgo
|
||||
);
|
||||
|
||||
var mainClassName = "mx_EntityTile ";
|
||||
mainClassName += presenceClass + (this.props.className ? (" " + this.props.className) : "");
|
||||
var nameEl;
|
||||
|
|
|
@ -101,6 +101,9 @@ module.exports = React.createClass({
|
|||
/* link URL for the highlights */
|
||||
highlightLink: React.PropTypes.string,
|
||||
|
||||
/* should show URL previews for this event */
|
||||
showUrlPreview: React.PropTypes.bool,
|
||||
|
||||
/* is this the focused event */
|
||||
isSelectedEvent: React.PropTypes.bool,
|
||||
|
||||
|
@ -359,6 +362,8 @@ module.exports = React.createClass({
|
|||
var SenderProfile = sdk.getComponent('messages.SenderProfile');
|
||||
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||
|
||||
//console.log("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
|
||||
|
||||
var content = this.props.mxEvent.getContent();
|
||||
var msgtype = content.msgtype;
|
||||
|
||||
|
@ -420,6 +425,7 @@ module.exports = React.createClass({
|
|||
<div className="mx_EventTile_line">
|
||||
<EventTileType ref="tile" mxEvent={this.props.mxEvent} highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,17 +37,14 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
var cli = MatrixClientPeg.get();
|
||||
cli.on("RoomState.members", this.onRoomStateMember);
|
||||
|
||||
this._emailEntity = null;
|
||||
// Load the complete user list for inviting new users
|
||||
// TODO: Keep this list bleeding-edge up-to-date. Practically speaking,
|
||||
// it will do for now not being updated as random new users join different
|
||||
// rooms as this list will be reloaded every room swap.
|
||||
if (this._room) {
|
||||
this._userList = MatrixClientPeg.get().getUsers().filter((u) => {
|
||||
return !this._room.hasMembershipState(u.userId, "join");
|
||||
});
|
||||
}
|
||||
|
||||
// we have to update the list whenever membership changes
|
||||
// particularly to avoid bug https://github.com/vector-im/vector-web/issues/1813
|
||||
this._updateList();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -55,6 +52,28 @@ module.exports = React.createClass({
|
|||
this.onSearchQueryChanged('');
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
var cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
cli.removeListener("RoomState.members", this.onRoomStateMember);
|
||||
}
|
||||
},
|
||||
|
||||
_updateList: function() {
|
||||
this._room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
// Load the complete user list for inviting new users
|
||||
if (this._room) {
|
||||
this._userList = MatrixClientPeg.get().getUsers().filter((u) => {
|
||||
return (!this._room.hasMembershipState(u.userId, "join") &&
|
||||
!this._room.hasMembershipState(u.userId, "invite"));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onRoomStateMember: function(ev, state, member) {
|
||||
this._updateList();
|
||||
},
|
||||
|
||||
onInvite: function(ev) {
|
||||
this.props.onInvite(this._input);
|
||||
},
|
||||
|
|
|
@ -61,12 +61,16 @@ module.exports = React.createClass({
|
|||
updating: 0,
|
||||
devicesLoading: true,
|
||||
devices: null,
|
||||
existingOneToOneRoomId: null,
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
componentWillMount: function() {
|
||||
this._cancelDeviceList = null;
|
||||
|
||||
this.setState({
|
||||
existingOneToOneRoomId: this.getExistingOneToOneRoomId()
|
||||
});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -90,6 +94,44 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
getExistingOneToOneRoomId: function() {
|
||||
var self = this;
|
||||
var rooms = MatrixClientPeg.get().getRooms();
|
||||
var userIds = [
|
||||
this.props.member.userId,
|
||||
MatrixClientPeg.get().credentials.userId
|
||||
];
|
||||
var existingRoomId;
|
||||
|
||||
// roomId can be null here because of a hack in MatrixChat.onUserClick where we
|
||||
// abuse this to view users rather than room members.
|
||||
var currentMembers;
|
||||
if (this.props.member.roomId) {
|
||||
var currentRoom = MatrixClientPeg.get().getRoom(this.props.member.roomId);
|
||||
currentMembers = currentRoom.getJoinedMembers();
|
||||
}
|
||||
|
||||
// reuse the first private 1:1 we find
|
||||
existingRoomId = null;
|
||||
|
||||
for (var i = 0; i < rooms.length; i++) {
|
||||
// don't try to reuse public 1:1 rooms
|
||||
var join_rules = rooms[i].currentState.getStateEvents("m.room.join_rules", '');
|
||||
if (join_rules && join_rules.getContent().join_rule === 'public') continue;
|
||||
|
||||
var members = rooms[i].getJoinedMembers();
|
||||
if (members.length === 2 &&
|
||||
userIds.indexOf(members[0].userId) !== -1 &&
|
||||
userIds.indexOf(members[1].userId) !== -1)
|
||||
{
|
||||
existingRoomId = rooms[i].roomId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return existingRoomId;
|
||||
},
|
||||
|
||||
onDeviceVerificationChanged: function(userId, device) {
|
||||
if (userId == this.props.member.userId) {
|
||||
// no need to re-download the whole thing; just update our copy of
|
||||
|
@ -349,66 +391,29 @@ module.exports = React.createClass({
|
|||
|
||||
onChatClick: function() {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
||||
// TODO: keep existingOneToOneRoomId updated if we see any room member changes anywhere
|
||||
|
||||
var useExistingOneToOneRoom = this.state.existingOneToOneRoomId && (this.state.existingOneToOneRoomId !== this.props.member.roomId);
|
||||
|
||||
// check if there are any existing rooms with just us and them (1:1)
|
||||
// If so, just view that room. If not, create a private room with them.
|
||||
var self = this;
|
||||
var rooms = MatrixClientPeg.get().getRooms();
|
||||
var userIds = [
|
||||
this.props.member.userId,
|
||||
MatrixClientPeg.get().credentials.userId
|
||||
];
|
||||
var existingRoomId;
|
||||
|
||||
// roomId can be null here because of a hack in MatrixChat.onUserClick where we
|
||||
// abuse this to view users rather than room members.
|
||||
var currentMembers;
|
||||
if (this.props.member.roomId) {
|
||||
var currentRoom = MatrixClientPeg.get().getRoom(this.props.member.roomId);
|
||||
currentMembers = currentRoom.getJoinedMembers();
|
||||
}
|
||||
// if we're currently in a 1:1 with this user, start a new chat
|
||||
if (currentMembers && currentMembers.length === 2 &&
|
||||
userIds.indexOf(currentMembers[0].userId) !== -1 &&
|
||||
userIds.indexOf(currentMembers[1].userId) !== -1)
|
||||
{
|
||||
existingRoomId = null;
|
||||
}
|
||||
// otherwise reuse the first private 1:1 we find
|
||||
else {
|
||||
existingRoomId = null;
|
||||
|
||||
for (var i = 0; i < rooms.length; i++) {
|
||||
// don't try to reuse public 1:1 rooms
|
||||
var join_rules = rooms[i].currentState.getStateEvents("m.room.join_rules", '');
|
||||
if (join_rules && join_rules.getContent().join_rule === 'public') continue;
|
||||
|
||||
var members = rooms[i].getJoinedMembers();
|
||||
if (members.length === 2 &&
|
||||
userIds.indexOf(members[0].userId) !== -1 &&
|
||||
userIds.indexOf(members[1].userId) !== -1)
|
||||
{
|
||||
existingRoomId = rooms[i].roomId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (existingRoomId) {
|
||||
if (this.state.existingOneToOneRoomId) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: existingRoomId
|
||||
room_id: this.state.existingOneToOneRoomId,
|
||||
});
|
||||
this.props.onFinished();
|
||||
}
|
||||
else {
|
||||
self.setState({ updating: self.state.updating + 1 });
|
||||
this.setState({ updating: this.state.updating + 1 });
|
||||
createRoom({
|
||||
createOpts: {
|
||||
invite: [this.props.member.userId],
|
||||
},
|
||||
}).finally(function() {
|
||||
self.props.onFinished();
|
||||
self.setState({ updating: self.state.updating - 1 });
|
||||
}).finally(() => {
|
||||
this.props.onFinished();
|
||||
this.setState({ updating: this.state.updating - 1 });
|
||||
}).done();
|
||||
}
|
||||
},
|
||||
|
@ -553,7 +558,22 @@ module.exports = React.createClass({
|
|||
if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) {
|
||||
// FIXME: we're referring to a vector component from react-sdk
|
||||
var BottomLeftMenuTile = sdk.getComponent('rooms.BottomLeftMenuTile');
|
||||
startChat = <BottomLeftMenuTile collapsed={ false } img="img/create-big.svg" label="Start chat" onClick={ this.onChatClick }/>
|
||||
|
||||
var label;
|
||||
if (this.state.existingOneToOneRoomId) {
|
||||
if (this.state.existingOneToOneRoomId == this.props.member.roomId) {
|
||||
label = "Start new direct chat";
|
||||
}
|
||||
else {
|
||||
label = "Go to direct chat";
|
||||
}
|
||||
}
|
||||
else {
|
||||
label = "Start direct chat";
|
||||
}
|
||||
|
||||
startChat = <BottomLeftMenuTile collapsed={ false } img="img/create-big.svg"
|
||||
label={ label } onClick={ this.onChatClick }/>
|
||||
}
|
||||
|
||||
if (this.state.updating) {
|
||||
|
|
|
@ -54,7 +54,7 @@ module.exports = React.createClass({
|
|||
|
||||
this.memberDict = this.getMemberDict();
|
||||
|
||||
state.members = this.roomMembers(INITIAL_LOAD_NUM_MEMBERS);
|
||||
state.members = this.roomMembers();
|
||||
return state;
|
||||
},
|
||||
|
||||
|
@ -64,7 +64,10 @@ module.exports = React.createClass({
|
|||
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||
cli.on("RoomState.events", this.onRoomStateEvent);
|
||||
cli.on("Room", this.onRoom); // invites
|
||||
cli.on("User.presence", this.onUserPresence);
|
||||
// We listen for changes to the lastPresenceTs which is essentially
|
||||
// listening for all presence events (we display most of not all of
|
||||
// the information contained in presence events).
|
||||
cli.on("User.lastPresenceTs", this.onUserLastPresenceTs);
|
||||
// cli.on("Room.timeline", this.onRoomTimeline);
|
||||
},
|
||||
|
||||
|
@ -75,24 +78,11 @@ module.exports = React.createClass({
|
|||
cli.removeListener("RoomMember.name", this.onRoomMemberName);
|
||||
cli.removeListener("RoomState.events", this.onRoomStateEvent);
|
||||
cli.removeListener("Room", this.onRoom);
|
||||
cli.removeListener("User.presence", this.onUserPresence);
|
||||
cli.removeListener("User.lastPresenceTs", this.onUserLastPresenceTs);
|
||||
// cli.removeListener("Room.timeline", this.onRoomTimeline);
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var self = this;
|
||||
|
||||
// Lazy-load in more than the first N members
|
||||
setTimeout(function() {
|
||||
if (!self.isMounted()) return;
|
||||
// lazy load to prevent it blocking the first render
|
||||
self.setState({
|
||||
members: self.roomMembers()
|
||||
});
|
||||
}, 50);
|
||||
},
|
||||
|
||||
/*
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) {
|
||||
// ignore anything but real-time updates at the end of the room:
|
||||
|
@ -121,7 +111,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
*/
|
||||
|
||||
onUserPresence(event, user) {
|
||||
onUserLastPresenceTs(event, user) {
|
||||
// Attach a SINGLE listener for global presence changes then locate the
|
||||
// member tile and re-render it. This is more efficient than every tile
|
||||
// evar attaching their own listener.
|
||||
|
@ -325,7 +315,7 @@ module.exports = React.createClass({
|
|||
return all_members;
|
||||
},
|
||||
|
||||
roomMembers: function(limit) {
|
||||
roomMembers: function() {
|
||||
var all_members = this.memberDict || {};
|
||||
var all_user_ids = Object.keys(all_members);
|
||||
var ConferenceHandler = CallHandler.getConferenceHandler();
|
||||
|
@ -334,7 +324,7 @@ module.exports = React.createClass({
|
|||
|
||||
var to_display = [];
|
||||
var count = 0;
|
||||
for (var i = 0; i < all_user_ids.length && (limit === undefined || count < limit); ++i) {
|
||||
for (var i = 0; i < all_user_ids.length; ++i) {
|
||||
var user_id = all_user_ids[i];
|
||||
var m = all_members[user_id];
|
||||
|
||||
|
@ -442,9 +432,16 @@ module.exports = React.createClass({
|
|||
|
||||
var memberList = self.state.members.filter(function(userId) {
|
||||
var m = self.memberDict[userId];
|
||||
if (query && m.name.toLowerCase().indexOf(query) === -1) {
|
||||
return false;
|
||||
|
||||
if (query) {
|
||||
const matchesName = m.name.toLowerCase().indexOf(query) !== -1;
|
||||
const matchesId = m.userId.toLowerCase().indexOf(query) !== -1;
|
||||
|
||||
if (!matchesName && !matchesId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return m.membership == membership;
|
||||
}).map(function(userId) {
|
||||
var m = self.memberDict[userId];
|
||||
|
|
|
@ -65,7 +65,12 @@ module.exports = React.createClass({
|
|||
tags_changed: false,
|
||||
tags: tags,
|
||||
areNotifsMuted: areNotifsMuted,
|
||||
isRoomPublished: false, // loaded async in componentWillMount
|
||||
// isRoomPublished is loaded async in componentWillMount so when the component
|
||||
// inits, the saved value will always be undefined, however getInitialState()
|
||||
// is also called from the saving code so we must return the correct value here
|
||||
// if we have it (although this could race if the user saves before we load whether
|
||||
// the room is published or not).
|
||||
isRoomPublished: this._originalIsRoomPublished,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -211,10 +216,13 @@ module.exports = React.createClass({
|
|||
// color scheme
|
||||
promises.push(this.saveColor());
|
||||
|
||||
// url preview settings
|
||||
promises.push(this.saveUrlPreviewSettings());
|
||||
|
||||
// encryption
|
||||
promises.push(this.saveEncryption());
|
||||
|
||||
console.log("Performing %s operations", promises.length);
|
||||
console.log("Performing %s operations: %s", promises.length, JSON.stringify(promises));
|
||||
return q.allSettled(promises);
|
||||
},
|
||||
|
||||
|
@ -228,6 +236,11 @@ module.exports = React.createClass({
|
|||
return this.refs.color_settings.saveSettings();
|
||||
},
|
||||
|
||||
saveUrlPreviewSettings: function() {
|
||||
if (!this.refs.url_preview_settings) { return q(); }
|
||||
return this.refs.url_preview_settings.saveSettings();
|
||||
},
|
||||
|
||||
saveEncryption: function () {
|
||||
if (!this.refs.encrypt) { return q(); }
|
||||
|
||||
|
@ -422,6 +435,7 @@ module.exports = React.createClass({
|
|||
|
||||
var AliasSettings = sdk.getComponent("room_settings.AliasSettings");
|
||||
var ColorSettings = sdk.getComponent("room_settings.ColorSettings");
|
||||
var UrlPreviewSettings = sdk.getComponent("room_settings.UrlPreviewSettings");
|
||||
var EditableText = sdk.getComponent('elements.EditableText');
|
||||
var PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||
|
||||
|
@ -654,6 +668,8 @@ module.exports = React.createClass({
|
|||
canonicalAliasEvent={this.props.room.currentState.getStateEvents('m.room.canonical_alias', '')}
|
||||
aliasEvents={this.props.room.currentState.getStateEvents('m.room.aliases')} />
|
||||
|
||||
<UrlPreviewSettings ref="url_preview_settings" room={this.props.room} />
|
||||
|
||||
<h3>Permissions</h3>
|
||||
<div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings">
|
||||
<div className="mx_RoomSettings_powerLevel">
|
||||
|
|
|
@ -43,7 +43,10 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return( { hover : false });
|
||||
return({
|
||||
hover : false,
|
||||
badgeHover : false,
|
||||
});
|
||||
},
|
||||
|
||||
onClick: function() {
|
||||
|
@ -61,6 +64,14 @@ module.exports = React.createClass({
|
|||
this.setState( { hover : false });
|
||||
},
|
||||
|
||||
badgeOnMouseEnter: function() {
|
||||
this.setState( { badgeHover : true } );
|
||||
},
|
||||
|
||||
badgeOnMouseLeave: function() {
|
||||
this.setState( { badgeHover : false } );
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
var me = this.props.room.currentState.members[myUserId];
|
||||
|
@ -83,9 +94,25 @@ module.exports = React.createClass({
|
|||
|
||||
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
||||
var badge;
|
||||
if (this.props.highlight || notificationCount > 0) {
|
||||
badge = <div className="mx_RoomTile_badge">{ notificationCount ? notificationCount : '!' }</div>;
|
||||
var badgeContent;
|
||||
var badgeClasses;
|
||||
|
||||
if (this.state.badgeHover) {
|
||||
badgeContent = "\u00B7\u00B7\u00B7";
|
||||
} else if (this.props.highlight || notificationCount > 0) {
|
||||
badgeContent = notificationCount ? notificationCount : '!';
|
||||
} else {
|
||||
badgeContent = '\u200B';
|
||||
}
|
||||
|
||||
if (this.props.highlight || notificationCount > 0) {
|
||||
badgeClasses = "mx_RoomTile_badge";
|
||||
} else {
|
||||
badgeClasses = "mx_RoomTile_badge mx_RoomTile_badge_no_unread";
|
||||
}
|
||||
|
||||
badge = <div className={ badgeClasses } onMouseEnter={this.badgeOnMouseEnter} onMouseLeave={this.badgeOnMouseLeave}>{ badgeContent }</div>;
|
||||
|
||||
/*
|
||||
if (this.props.highlight) {
|
||||
badge = <div className="mx_RoomTile_badge">!</div>;
|
||||
|
|
|
@ -24,17 +24,17 @@ module.exports = React.createClass({
|
|||
displayName: 'TabCompleteBar',
|
||||
|
||||
propTypes: {
|
||||
entries: React.PropTypes.array.isRequired
|
||||
tabComplete: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_TabCompleteBar">
|
||||
{this.props.entries.map(function(entry, i) {
|
||||
{this.props.tabComplete.peek(6).map((entry, i) => {
|
||||
return (
|
||||
<div key={entry.getKey() || i + ""}
|
||||
className={ "mx_TabCompleteBar_item " + (entry instanceof CommandEntry ? "mx_TabCompleteBar_command" : "") }
|
||||
onClick={entry.onClick.bind(entry)} >
|
||||
className={ "mx_TabCompleteBar_item " + (entry instanceof CommandEntry ? "mx_TabCompleteBar_command" : "") }
|
||||
onClick={this.props.tabComplete.onEntryClick.bind(this.props.tabComplete, entry)} >
|
||||
{entry.getImageJsx()}
|
||||
<span className="mx_TabCompleteBar_text">
|
||||
{entry.getText()}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue