Merge branch 'develop' into feature-composer-emoji

This commit is contained in:
Aviral Dasgupta 2016-07-23 19:15:06 +05:30
commit b7555f49ea
44 changed files with 1057 additions and 311 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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}>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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;

View file

@ -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} />;
},
});

View file

@ -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

View file

@ -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>

View file

@ -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;

View 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>
);
}
});

View file

@ -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;

View file

@ -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>

View file

@ -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);
},

View file

@ -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) {

View file

@ -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];

View file

@ -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">

View file

@ -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>;

View file

@ -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()}