Merge remote-tracking branch 'origin/develop' into dbkr/scalar

This commit is contained in:
David Baker 2016-06-06 17:19:38 +01:00
commit fdcebe1e56
21 changed files with 243 additions and 54 deletions

View file

@ -39,11 +39,11 @@ module.exports = React.createClass({
focus: true
};
},
componentDidMount: function() {
if (this.props.focus) {
// Set the cursor at the end of the text input
this.refs.textinput.value = this.props.value;
// Set the cursor at the end of the text input
this.refs.textinput.value = this.props.value;
}
},
@ -83,13 +83,12 @@ module.exports = React.createClass({
</div>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onOk}>
{this.props.button}
</button>
<button onClick={this.onCancel}>
Cancel
</button>
<button onClick={this.onOk}>
{this.props.button}
</button>
</div>
</div>
);

View file

@ -45,9 +45,9 @@ module.exports = React.createClass({
getInitialState: function() {
return {
// the URL (if any) to be previewed with a LinkPreviewWidget
// the URLs (if any) to be previewed with a LinkPreviewWidget
// inside this TextualBody.
link: null,
links: [],
// track whether the preview widget is hidden
widgetHidden: false,
@ -57,9 +57,11 @@ module.exports = React.createClass({
componentDidMount: function() {
linkifyElement(this.refs.content, linkifyMatrix.options);
var link = this.findLink(this.refs.content.children);
if (link) {
this.setState({ link: link.getAttribute("href") });
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) {
@ -74,27 +76,32 @@ module.exports = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
// 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 ||
nextState.link !== this.state.link ||
nextState.links !== this.state.links ||
nextState.widgetHidden !== this.state.widgetHidden);
},
findLink: function(nodes) {
findLinks: function(nodes) {
var links = [];
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.tagName === "A" && node.getAttribute("href"))
{
return this.isLinkPreviewable(node) ? node : undefined;
if (this.isLinkPreviewable(node)) {
links.push(node);
}
}
else if (node.tagName === "PRE" || node.tagName === "CODE") {
return;
continue;
}
else if (node.children && node.children.length) {
return this.findLink(node.children)
links = links.concat(this.findLinks(node.children));
}
}
return links;
},
isLinkPreviewable: function(node) {
@ -117,7 +124,7 @@ module.exports = React.createClass({
else {
var url = node.getAttribute("href");
var host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1];
if (node.textContent.trim().startsWith(host)) {
if (node.textContent.toLowerCase().trim().startsWith(host.toLowerCase())) {
// it's a "foo.pl" style link
return;
}
@ -160,14 +167,17 @@ module.exports = React.createClass({
{highlightLink: this.props.highlightLink});
var widget;
if (this.state.link && !this.state.widgetHidden) {
var widgets;
if (this.state.links.length && !this.state.widgetHidden) {
var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget');
widget = <LinkPreviewWidget
link={ this.state.link }
mxEvent={ this.props.mxEvent }
onCancelClick={ this.onCancelClick }
onWidgetLoad={ this.props.onWidgetLoad }/>;
widgets = this.state.links.map((link)=>{
return <LinkPreviewWidget
key={ link }
link={ link }
mxEvent={ this.props.mxEvent }
onCancelClick={ this.onCancelClick }
onWidgetLoad={ this.props.onWidgetLoad }/>;
});
}
switch (content.msgtype) {
@ -176,21 +186,21 @@ module.exports = React.createClass({
return (
<span ref="content" className="mx_MEmoteBody mx_EventTile_content">
* { name } { body }
{ widget }
{ widgets }
</span>
);
case "m.notice":
return (
<span ref="content" className="mx_MNoticeBody mx_EventTile_content">
{ body }
{ widget }
{ widgets }
</span>
);
default: // including "m.text"
return (
<span ref="content" className="mx_MTextBody mx_EventTile_content">
{ body }
{ widget }
{ widgets }
</span>
);
}

View file

@ -26,6 +26,7 @@ module.exports = React.createClass({
propTypes: {
roomId: React.PropTypes.string.isRequired,
onInvite: React.PropTypes.func.isRequired, // fn(inputText)
onThirdPartyInvite: React.PropTypes.func.isRequired, // fn(inputText)
onSearchQueryChanged: React.PropTypes.func // fn(inputText)
},
@ -49,10 +50,19 @@ module.exports = React.createClass({
}
},
componentDidMount: function() {
// initialise the email tile
this.onSearchQueryChanged('');
},
onInvite: function(ev) {
this.props.onInvite(this._input);
},
onThirdPartyInvite: function(ev) {
this.props.onThirdPartyInvite(this._input);
},
onSearchQueryChanged: function(input) {
this._input = input;
var EntityTile = sdk.getComponent("rooms.EntityTile");
@ -68,9 +78,10 @@ module.exports = React.createClass({
this._emailEntity = new Entities.newEntity(
<EntityTile key="dynamic_invite_tile" suppressOnHover={true} showInviteButton={true}
avatarJsx={ <BaseAvatar name="@" width={36} height={36} /> }
className="mx_EntityTile_invitePlaceholder"
presenceState="online" onClick={this.onInvite} name={label} />,
avatarJsx={ <BaseAvatar name="@" width={36} height={36} /> }
className="mx_EntityTile_invitePlaceholder"
presenceState="online" onClick={this.onThirdPartyInvite} name={"Invite by email"}
/>,
function(query) {
return true; // always show this
}
@ -89,7 +100,7 @@ module.exports = React.createClass({
}
return (
<SearchableEntityList searchPlaceholderText={"Invite/search by name, email, id"}
<SearchableEntityList searchPlaceholderText={"Search/invite by name, email, id"}
onSubmit={this.props.onInvite}
onQueryChanged={this.onSearchQueryChanged}
entities={entities}

View file

@ -340,6 +340,7 @@ module.exports = React.createClass({
},
type: 'm.room.guest_access',
state_key: '',
visibility: 'private',
}
],
}).then(
@ -367,7 +368,7 @@ module.exports = React.createClass({
action: 'leave_room',
room_id: this.props.member.roomId,
});
this.props.onFinished();
this.props.onFinished();
},
getInitialState: function() {

View file

@ -166,6 +166,25 @@ module.exports = React.createClass({
});
}, 500),
onThirdPartyInvite: function(inputText) {
var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
Modal.createDialog(TextInputDialog, {
title: "Invite members by email",
description: "Please enter one or more email addresses",
value: inputText,
button: "Invite",
onFinished: (should_invite, addresses)=>{
if (should_invite) {
// defer the actual invite to the next event loop to give this
// Modal a chance to unmount in case onInvite() triggers a new one
setTimeout(()=>{
this.onInvite(addresses);
}, 0);
}
}
});
},
onInvite: function(inputText) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
@ -514,6 +533,7 @@ module.exports = React.createClass({
inviteMemberListSection = (
<InviteMemberList roomId={this.props.roomId}
onSearchQueryChanged={this.onSearchQueryChanged}
onThirdPartyInvite={this.onThirdPartyInvite}
onInvite={this.onInvite} />
);
}

View file

@ -46,6 +46,15 @@ module.exports = React.createClass({
},
onUploadClick: function(ev) {
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't upload files. Please register to upload."
});
return;
}
this.refs.uploadInput.click();
},

View file

@ -36,7 +36,7 @@ module.exports = React.createClass({
getInitialState: function() {
var tags = {};
Object.keys(this.props.room.tags).forEach(function(tagName) {
tags[tagName] = {};
tags[tagName] = ['yep'];
});
var areNotifsMuted = false;
@ -186,7 +186,7 @@ module.exports = React.createClass({
// tags
if (this.state.tags_changed) {
var tagDiffs = ObjectUtils.getKeyValueArrayDiffs(originalState.tags, this.state.tags);
// [ {place: add, key: "m.favourite", val: "yep"} ]
// [ {place: add, key: "m.favourite", val: ["yep"]} ]
tagDiffs.forEach(function(diff) {
switch (diff.place) {
case "add":

View file

@ -48,6 +48,7 @@ var SearchableEntityList = React.createClass({
getInitialState: function() {
return {
query: "",
focused: false,
truncateAt: this.props.truncateAt,
results: this.getSearchResults("", this.props.entities)
};
@ -101,7 +102,7 @@ var SearchableEntityList = React.createClass({
getSearchResults: function(query, entities) {
if (!query || query.length === 0) {
return this.props.emptyQueryShowsAll ? entities : []
return this.props.emptyQueryShowsAll ? entities : [ entities[0] ]
}
return entities.filter(function(e) {
return e.matches(query);
@ -134,13 +135,27 @@ var SearchableEntityList = React.createClass({
<form onSubmit={this.onQuerySubmit} autoComplete="off">
<input className="mx_SearchableEntityList_query" id="mx_SearchableEntityList_query" type="text"
onChange={this.onQueryChanged} value={this.state.query}
onFocus={ ()=>{
if (this._blurTimeout) {
clearTimeout(this.blurTimeout);
}
this.setState({ focused: true });
} }
onBlur={ ()=>{
// nasty setTimeout heuristic to avoid the 'invite by email' prompt disappearing
// due to the onBlur before we can click on it
this._blurTimeout = setTimeout(
()=>{ this.setState({ focused: false }) },
300
);
} }
placeholder={this.props.searchPlaceholderText} />
</form>
);
}
var list;
if (this.state.results.length) {
if (this.state.results.length > 1 || this.state.focused) {
if (this.props.truncateAt) { // caller wants list truncated
var TruncatedList = sdk.getComponent("elements.TruncatedList");
list = (
@ -172,10 +187,10 @@ var SearchableEntityList = React.createClass({
}
return (
<div className={ "mx_SearchableEntityList " + (this.state.query.length ? "mx_SearchableEntityList_expanded" : "") }>
<div className={ "mx_SearchableEntityList " + (list ? "mx_SearchableEntityList_expanded" : "") }>
{ inputBox }
{ list }
{ this.state.query.length ? <div className="mx_SearchableEntityList_hrWrapper"><hr/></div> : '' }
{ list ? <div className="mx_SearchableEntityList_hrWrapper"><hr/></div> : '' }
</div>
);
}