Merge pull request #1148 from matrix-org/erikj/group_server

Add group view page
This commit is contained in:
David Baker 2017-07-07 10:31:14 +01:00 committed by GitHub
commit 28c0daf51d
6 changed files with 223 additions and 1 deletions

View file

@ -118,7 +118,17 @@ export function processHtmlForSending(html: string): string {
return contentHTML; return contentHTML;
} }
var sanitizeHtmlParams = { /*
* Given an untrusted HTML string, return a React node with an sanitized version
* of that HTML.
*/
export function sanitizedHtmlNode(insaneHtml) {
const saneHtml = sanitizeHtml(insaneHtml, sanitizeHtmlParams);
return <div dangerouslySetInnerHTML={{ __html: saneHtml }} dir="auto" />;
}
const sanitizeHtmlParams = {
allowedTags: [ allowedTags: [
'font', // custom to matrix for IRC-style font coloring 'font', // custom to matrix for IRC-style font coloring
'del', // for markdown 'del', // for markdown

View file

@ -22,4 +22,5 @@ export default {
CreateRoom: "create_room", CreateRoom: "create_room",
RoomDirectory: "room_directory", RoomDirectory: "room_directory",
UserView: "user_view", UserView: "user_view",
GroupView: "group_view",
}; };

View file

@ -0,0 +1,131 @@
/*
Copyright 2017 Vector Creations 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.
*/
import React from 'react';
import MatrixClientPeg from '../../MatrixClientPeg';
import sdk from '../../index';
import { sanitizedHtmlNode } from '../../HtmlUtils';
import { _t } from '../../languageHandler';
module.exports = React.createClass({
displayName: 'GroupView',
propTypes: {
groupId: React.PropTypes.string.isRequired,
},
getInitialState: function() {
return {
summary: null,
error: null,
};
},
componentWillMount: function() {
this._loadGroupFromServer(this.props.groupId);
},
componentWillReceiveProps: function(newProps) {
if (this.props.groupId != newProps.groupId) {
this.setState({
summary: null,
error: null,
}, () => {
this._loadGroupFromServer(newProps.groupId);
});
}
},
_loadGroupFromServer: function(groupId) {
MatrixClientPeg.get().getGroupSummary(groupId).done((res) => {
this.setState({
summary: res,
error: null,
});
}, (err) => {
this.setState({
summary: null,
error: err,
});
});
},
render: function() {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const Loader = sdk.getComponent("elements.Spinner");
if (this.state.summary === null && this.state.error === null) {
return <Loader />;
} else if (this.state.summary) {
const summary = this.state.summary;
let avatarUrl = null;
if (summary.profile && summary.profile.avatar_url) {
avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(summary.profile.avatar_url);
}
let description = null;
if (summary.profile && summary.profile.long_description) {
description = sanitizedHtmlNode(summary.profile.long_description);
}
return (
<div className="mx_GroupView">
<div className="mx_RoomHeader">
<div className="mx_RoomHeader_wrapper">
<div className="mx_RoomHeader_avatar">
<BaseAvatar url={avatarUrl} name={summary.profile.name}
width={48} height={48} />
</div>
<div className="mx_RoomHeader_info">
<div className="mx_RoomHeader_name">
<span>{summary.profile.name}</span>
<span className="mx_GroupView_header_groupid">
({this.props.groupId})
</span>
</div>
<div className="mx_RoomHeader_topic">
{summary.profile.short_description}
</div>
</div>
</div>
</div>
{description}
</div>
);
} else if (this.state.error) {
if (this.state.error.httpStatus === 404) {
return (
<div className="mx_GroupView_error">
Group {this.props.groupId} not found
</div>
);
} else {
let extraText;
if (this.state.error.errcode === 'M_UNRECOGNIZED') {
extraText = <div>{_t('This Home server does not support groups')}</div>;
}
return (
<div className="mx_GroupView_error">
Failed to load {this.props.groupId}
{extraText}
</div>
);
}
} else {
console.error("Invalid state for GroupView");
return <div />;
}
},
});

View file

@ -210,6 +210,7 @@ export default React.createClass({
const CreateRoom = sdk.getComponent('structures.CreateRoom'); const CreateRoom = sdk.getComponent('structures.CreateRoom');
const RoomDirectory = sdk.getComponent('structures.RoomDirectory'); const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
const HomePage = sdk.getComponent('structures.HomePage'); const HomePage = sdk.getComponent('structures.HomePage');
const GroupView = sdk.getComponent('structures.GroupView');
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar'); const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar'); const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar'); const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
@ -280,6 +281,11 @@ export default React.createClass({
page_element = null; // deliberately null for now page_element = null; // deliberately null for now
right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.rightOpacity} />; right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.rightOpacity} />;
break; break;
case PageTypes.GroupView:
page_element = <GroupView
groupId={this.props.currentGroupId}
/>;
break;
} }
let topBar; let topBar;

View file

@ -290,6 +290,9 @@ module.exports = React.createClass({
if (this.onUserClick) { if (this.onUserClick) {
linkifyMatrix.onUserClick = this.onUserClick; linkifyMatrix.onUserClick = this.onUserClick;
} }
if (this.onGroupClick) {
linkifyMatrix.onGroupClick = this.onGroupClick;
}
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
this.handleResize(); this.handleResize();
@ -483,6 +486,14 @@ module.exports = React.createClass({
this._setPage(PageTypes.RoomDirectory); this._setPage(PageTypes.RoomDirectory);
this.notifyNewScreen('directory'); this.notifyNewScreen('directory');
break; break;
case 'view_group':
{
const groupId = payload.group_id;
this.setState({currentGroupId: groupId});
this._setPage(PageTypes.GroupView);
this.notifyNewScreen('group/' + groupId);
}
break;
case 'view_home_page': case 'view_home_page':
this._setPage(PageTypes.HomePage); this._setPage(PageTypes.HomePage);
this.notifyNewScreen('home'); this.notifyNewScreen('home');
@ -1199,6 +1210,15 @@ module.exports = React.createClass({
member: member, member: member,
}); });
} }
} else if (screen.indexOf('group/') == 0) {
const groupId = screen.substring(6);
// TODO: Check valid group ID
dis.dispatch({
action: 'view_group',
group_id: groupId,
});
} else { } else {
console.info("Ignoring showScreen for '%s'", screen); console.info("Ignoring showScreen for '%s'", screen);
} }
@ -1227,6 +1247,11 @@ module.exports = React.createClass({
}); });
}, },
onGroupClick: function(event, groupId) {
event.preventDefault();
dis.dispatch({action: 'view_group', group_id: groupId});
},
onLogoutClick: function(event) { onLogoutClick: function(event) {
dis.dispatch({ dis.dispatch({
action: 'logout', action: 'logout',

View file

@ -108,11 +108,53 @@ function matrixLinkify(linkify) {
S_AT_NAME_COLON_DOMAIN.on(TT.DOT, S_AT_NAME_COLON_DOMAIN_DOT); S_AT_NAME_COLON_DOMAIN.on(TT.DOT, S_AT_NAME_COLON_DOMAIN_DOT);
S_AT_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_AT_NAME_COLON_DOMAIN); S_AT_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_AT_NAME_COLON_DOMAIN);
S_AT_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_USERID); S_AT_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_USERID);
var GROUPID = function(value) {
MultiToken.call(this, value);
this.type = 'groupid';
this.isLink = true;
};
GROUPID.prototype = new MultiToken();
var S_PLUS = new linkify.parser.State();
var S_PLUS_NAME = new linkify.parser.State();
var S_PLUS_NAME_COLON = new linkify.parser.State();
var S_PLUS_NAME_COLON_DOMAIN = new linkify.parser.State();
var S_PLUS_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
var S_GROUPID = new linkify.parser.State(GROUPID);
var groupid_tokens = [
TT.DOT,
TT.UNDERSCORE,
TT.PLUS,
TT.NUM,
TT.DOMAIN,
TT.TLD,
// as in roomname_tokens
TT.LOCALHOST,
];
S_START.on(TT.PLUS, S_PLUS);
S_PLUS.on(groupid_tokens, S_PLUS_NAME);
S_PLUS_NAME.on(groupid_tokens, S_PLUS_NAME);
S_PLUS_NAME.on(TT.DOMAIN, S_PLUS_NAME);
S_PLUS_NAME.on(TT.COLON, S_PLUS_NAME_COLON);
S_PLUS_NAME_COLON.on(TT.DOMAIN, S_PLUS_NAME_COLON_DOMAIN);
S_PLUS_NAME_COLON.on(TT.LOCALHOST, S_GROUPID); // accept +foo:localhost
S_PLUS_NAME_COLON_DOMAIN.on(TT.DOT, S_PLUS_NAME_COLON_DOMAIN_DOT);
S_PLUS_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_PLUS_NAME_COLON_DOMAIN);
S_PLUS_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_GROUPID);
} }
// stubs, overwritten in MatrixChat's componentDidMount // stubs, overwritten in MatrixChat's componentDidMount
matrixLinkify.onUserClick = function(e, userId) { e.preventDefault(); }; matrixLinkify.onUserClick = function(e, userId) { e.preventDefault(); };
matrixLinkify.onAliasClick = function(e, roomAlias) { e.preventDefault(); }; matrixLinkify.onAliasClick = function(e, roomAlias) { e.preventDefault(); };
matrixLinkify.onGroupClick = function(e, groupId) { e.preventDefault(); };
var escapeRegExp = function(string) { var escapeRegExp = function(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@ -143,6 +185,12 @@ matrixLinkify.options = {
matrixLinkify.onAliasClick(e, href); matrixLinkify.onAliasClick(e, href);
} }
}; };
case "groupid":
return {
click: function(e) {
matrixLinkify.onGroupClick(e, href);
}
};
} }
}, },
@ -150,6 +198,7 @@ matrixLinkify.options = {
switch (type) { switch (type) {
case 'roomalias': case 'roomalias':
case 'userid': case 'userid':
case 'groupid':
return matrixLinkify.MATRIXTO_BASE_URL + '/#/' + href; return matrixLinkify.MATRIXTO_BASE_URL + '/#/' + href;
default: default:
var m; var m;