diff --git a/src/components/views/context_menus/GroupInviteTileContextMenu.js b/src/components/views/context_menus/GroupInviteTileContextMenu.js
new file mode 100644
index 0000000000..e30acca16d
--- /dev/null
+++ b/src/components/views/context_menus/GroupInviteTileContextMenu.js
@@ -0,0 +1,87 @@
+/*
+Copyright 2018 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 PropTypes from 'prop-types';
+import sdk from '../../../index';
+import { _t } from '../../../languageHandler';
+import Modal from '../../../Modal';
+import {Group} from 'matrix-js-sdk';
+import GroupStore from "../../../stores/GroupStore";
+
+export default class GroupInviteTileContextMenu extends React.Component {
+ static propTypes = {
+ group: PropTypes.instanceOf(Group).isRequired,
+ /* callback called when the menu is dismissed */
+ onFinished: PropTypes.func,
+ };
+
+ constructor(props, context) {
+ super(props, context);
+
+ this._onClickReject = this._onClickReject.bind(this);
+ }
+
+ componentWillMount() {
+ this._unmounted = false;
+ }
+
+ componentWillUnmount() {
+ this._unmounted = true;
+ }
+
+ _onClickReject() {
+ const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
+ Modal.createTrackedDialog('Reject community invite', '', QuestionDialog, {
+ title: _t('Reject invitation'),
+ description: _t('Are you sure you want to reject the invitation?'),
+ onFinished: async (shouldLeave) => {
+ if (!shouldLeave) return;
+
+ // FIXME: controller shouldn't be loading a view :(
+ const Loader = sdk.getComponent("elements.Spinner");
+ const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
+
+ try {
+ await GroupStore.leaveGroup(this.props.group.groupId);
+ } catch (e) {
+ console.error("Error rejecting community invite: ", e);
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ Modal.createTrackedDialog('Error rejecting invite', '', ErrorDialog, {
+ title: _t("Error"),
+ description: _t("Unable to reject invite"),
+ });
+ } finally {
+ modal.close();
+ }
+ },
+ });
+
+ // Close the context menu
+ if (this.props.onFinished) {
+ this.props.onFinished();
+ }
+ }
+
+ render() {
+ return
+
+
+ { _t('Reject') }
+
+
;
+ }
+}
diff --git a/src/components/views/groups/GroupInviteTile.js b/src/components/views/groups/GroupInviteTile.js
index d97464e8ca..4d5f3c6f3a 100644
--- a/src/components/views/groups/GroupInviteTile.js
+++ b/src/components/views/groups/GroupInviteTile.js
@@ -1,5 +1,5 @@
/*
-Copyright 2017 New Vector Ltd
+Copyright 2017, 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ import { MatrixClient } from 'matrix-js-sdk';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import AccessibleButton from '../elements/AccessibleButton';
+import * as ContextualMenu from "../../structures/ContextualMenu";
+import classNames from 'classnames';
export default React.createClass({
displayName: 'GroupInviteTile',
@@ -32,6 +34,15 @@ export default React.createClass({
matrixClient: PropTypes.instanceOf(MatrixClient),
},
+ getInitialState: function() {
+ return ({
+ hover: false,
+ badgeHover: false,
+ menuDisplayed: false,
+ selected: this.props.group.groupId === null, // XXX: this needs linking to LoggedInView/GroupView state
+ });
+ },
+
onClick: function(e) {
dis.dispatch({
action: 'view_group',
@@ -39,6 +50,55 @@ export default React.createClass({
});
},
+ onMouseEnter: function() {
+ const state = {hover: true};
+ // Only allow non-guests to access the context menu
+ if (!this.context.matrixClient.isGuest()) {
+ state.badgeHover = true;
+ }
+ this.setState(state);
+ },
+
+ onMouseLeave: function() {
+ this.setState({
+ badgeHover: false,
+ hover: false,
+ });
+ },
+
+ onBadgeClicked: function(e) {
+ // Prevent the RoomTile onClick event firing as well
+ e.stopPropagation();
+
+ // Only allow none guests to access the context menu
+ if (this.context.matrixClient.isGuest()) return;
+
+ // If the badge is clicked, then no longer show tooltip
+ if (this.props.collapsed) {
+ this.setState({ hover: false });
+ }
+
+ const RoomTileContextMenu = sdk.getComponent('context_menus.GroupInviteTileContextMenu');
+ const elementRect = e.target.getBoundingClientRect();
+
+ // The window X and Y offsets are to adjust position when zoomed in to page
+ const x = elementRect.right + window.pageXOffset + 3;
+ const chevronOffset = 12;
+ let y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset);
+ y = y - (chevronOffset + 8); // where 8 is half the height of the chevron
+
+ ContextualMenu.createMenu(RoomTileContextMenu, {
+ chevronOffset: chevronOffset,
+ left: x,
+ top: y,
+ group: this.props.group,
+ onFinished: () => {
+ this.setState({ menuDisplayed: false });
+ },
+ });
+ this.setState({ menuDisplayed: true });
+ },
+
render: function() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const EmojiText = sdk.getComponent('elements.EmojiText');
@@ -49,19 +109,37 @@ export default React.createClass({
const av = ;
- const label =
+ const nameClasses = classNames({
+ 'mx_RoomTile_name': true,
+ 'mx_RoomTile_invite': this.props.isInvite,
+ 'mx_RoomTile_badgeShown': this.state.badgeHover || this.state.menuDisplayed,
+ });
+
+ const label =
{ groupName }
;
- const badge = !
;
+ const badgeEllipsis = this.state.badgeHover || this.state.menuDisplayed;
+ const badgeClasses = classNames('mx_RoomSubList_badge mx_RoomSubList_badgeHighlight', {
+ 'mx_RoomTile_badgeButton': badgeEllipsis,
+ });
+
+ const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!';
+ const badge = { badgeContent }
;
+
+ let tooltip;
+ if (this.props.collapsed && this.state.hover) {
+ const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
+ tooltip = ;
+ }
+
+ const classes = classNames('mx_RoomTile mx_RoomTile_highlight', {
+ 'mx_RoomTile_menuDisplayed': this.state.menuDisplayed,
+ 'mx_RoomTile_selected': this.state.selected,
+ });
return (
-
+
{ av }
@@ -69,6 +147,7 @@ export default React.createClass({
{ label }
{ badge }
+ { tooltip }
);
},
diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js
index fc1872249f..167603ecfb 100644
--- a/src/components/views/rooms/RoomList.js
+++ b/src/components/views/rooms/RoomList.js
@@ -583,14 +583,18 @@ module.exports = React.createClass({
}
},
- _makeGroupInviteTiles() {
+ _makeGroupInviteTiles(filter) {
const ret = [];
+ const lcFilter = filter && filter.toLowerCase();
const GroupInviteTile = sdk.getComponent('groups.GroupInviteTile');
for (const group of MatrixClientPeg.get().getGroups()) {
- if (group.myMembership !== 'invite') continue;
-
- ret.push();
+ const {groupId, name, myMembership} = group;
+ // filter to only groups in invite state and group_id starts with filter or group name includes it
+ if (myMembership !== 'invite') continue;
+ if (lcFilter && !groupId.toLowerCase().startsWith(lcFilter) &&
+ !(name && name.toLowerCase().includes(lcFilter))) continue;
+ ret.push();
}
return ret;
@@ -610,7 +614,7 @@ module.exports = React.createClass({
autoshow={true} onScroll={self._whenScrolling} wrappedRef={this._collectGemini}>
;
+ tooltip =
;
}
//var incomingCallBox;
@@ -314,7 +314,7 @@ module.exports = React.createClass({
let directMessageIndicator;
if (this._isDirectMessageRoom(this.props.room.roomId)) {
- directMessageIndicator =
;
+ directMessageIndicator =
;
}
return
diff --git a/src/components/views/rooms/RoomTooltip.js b/src/components/views/rooms/RoomTooltip.js
index b17f54ef3c..bce0922637 100644
--- a/src/components/views/rooms/RoomTooltip.js
+++ b/src/components/views/rooms/RoomTooltip.js
@@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-'use strict';
-var React = require('react');
-var ReactDOM = require('react-dom');
-var dis = require('../../../dispatcher');
+import React from 'react';
+import ReactDOM from 'react-dom';
+import dis from '../../../dispatcher';
import classNames from 'classnames';
const MIN_TOOLTIP_HEIGHT = 25;
@@ -77,25 +76,21 @@ module.exports = React.createClass({
},
_renderTooltip: function() {
- var label = this.props.room ? this.props.room.name : this.props.label;
-
// Add the parent's position to the tooltips, so it's correctly
// positioned, also taking into account any window zoom
// NOTE: The additional 6 pixels for the left position, is to take account of the
// tooltips chevron
- var parent = ReactDOM.findDOMNode(this).parentNode;
- var style = {};
+ const parent = ReactDOM.findDOMNode(this).parentNode;
+ let style = {};
style = this._updatePosition(style);
style.display = "block";
- const tooltipClasses = classNames(
- "mx_RoomTooltip", this.props.tooltipClassName,
- );
+ const tooltipClasses = classNames("mx_RoomTooltip", this.props.tooltipClassName);
- var tooltip = (
-
-
- { label }
+ const tooltip = (
+
);