mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-17 05:55:00 +08:00
Merge pull request #369 from matrix-org/wmwragg/mention-state-menu
Wmwragg/mention state menu
This commit is contained in:
commit
f95a11a9bf
@ -25,6 +25,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports.components = {};
|
module.exports.components = {};
|
||||||
|
module.exports.components['structures.ContextualMenu'] = require('./components/structures/ContextualMenu');
|
||||||
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
|
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
|
||||||
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
|
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
|
||||||
module.exports.components['structures.MessagePanel'] = require('./components/structures/MessagePanel');
|
module.exports.components['structures.MessagePanel'] = require('./components/structures/MessagePanel');
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var classNames = require('classnames');
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var ReactDOM = require('react-dom');
|
var ReactDOM = require('react-dom');
|
||||||
|
|
||||||
@ -27,6 +28,12 @@ var ReactDOM = require('react-dom');
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
ContextualMenuContainerId: "mx_ContextualMenu_Container",
|
ContextualMenuContainerId: "mx_ContextualMenu_Container",
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
menuWidth: React.PropTypes.number,
|
||||||
|
menuHeight: React.PropTypes.number,
|
||||||
|
chevronOffset: React.PropTypes.number,
|
||||||
|
},
|
||||||
|
|
||||||
getOrCreateContainer: function() {
|
getOrCreateContainer: function() {
|
||||||
var container = document.getElementById(this.ContextualMenuContainerId);
|
var container = document.getElementById(this.ContextualMenuContainerId);
|
||||||
|
|
||||||
@ -45,29 +52,50 @@ module.exports = {
|
|||||||
var closeMenu = function() {
|
var closeMenu = function() {
|
||||||
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
|
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
|
||||||
|
|
||||||
if (props && props.onFinished) props.onFinished.apply(null, arguments);
|
if (props && props.onFinished) {
|
||||||
|
props.onFinished.apply(null, arguments);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var position = {
|
var position = {
|
||||||
top: props.top - 20,
|
top: props.top,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var chevronOffset = {
|
||||||
|
top: props.chevronOffset,
|
||||||
|
}
|
||||||
|
|
||||||
var chevron = null;
|
var chevron = null;
|
||||||
if (props.left) {
|
if (props.left) {
|
||||||
chevron = <img className="mx_ContextualMenu_chevron_left" src="img/chevron-left.png" width="9" height="16" />
|
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_left"></div>
|
||||||
position.left = props.left + 8;
|
position.left = props.left;
|
||||||
} else {
|
} else {
|
||||||
chevron = <img className="mx_ContextualMenu_chevron_right" src="img/chevron-right.png" width="9" height="16" />
|
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_right"></div>
|
||||||
position.right = props.right + 8;
|
position.right = props.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
var className = 'mx_ContextualMenu_wrapper';
|
var className = 'mx_ContextualMenu_wrapper';
|
||||||
|
|
||||||
|
var menuClasses = classNames({
|
||||||
|
'mx_ContextualMenu': true,
|
||||||
|
'mx_ContextualMenu_left': props.left,
|
||||||
|
'mx_ContextualMenu_right': !props.left,
|
||||||
|
});
|
||||||
|
|
||||||
|
var menuSize = {};
|
||||||
|
if (props.menuWidth) {
|
||||||
|
menuSize.width = props.menuWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.menuHeight) {
|
||||||
|
menuSize.height = props.menuHeight;
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
|
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
|
||||||
// property set here so you can't close the menu from a button click!
|
// property set here so you can't close the menu from a button click!
|
||||||
var menu = (
|
var menu = (
|
||||||
<div className={className}>
|
<div className={className} style={position}>
|
||||||
<div className="mx_ContextualMenu" style={position}>
|
<div className={menuClasses} style={menuSize}>
|
||||||
{chevron}
|
{chevron}
|
||||||
<Element {...props} onFinished={closeMenu}/>
|
<Element {...props} onFinished={closeMenu}/>
|
||||||
</div>
|
</div>
|
@ -20,7 +20,7 @@ var Favico = require('favico.js');
|
|||||||
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
var Notifier = require("../../Notifier");
|
var Notifier = require("../../Notifier");
|
||||||
var ContextualMenu = require("../../ContextualMenu");
|
var ContextualMenu = require("./ContextualMenu");
|
||||||
var RoomListSorter = require("../../RoomListSorter");
|
var RoomListSorter = require("../../RoomListSorter");
|
||||||
var UserActivity = require("../../UserActivity");
|
var UserActivity = require("../../UserActivity");
|
||||||
var Presence = require("../../Presence");
|
var Presence = require("../../Presence");
|
||||||
|
@ -23,7 +23,7 @@ var sdk = require('../../../index');
|
|||||||
var MatrixClientPeg = require('../../../MatrixClientPeg')
|
var MatrixClientPeg = require('../../../MatrixClientPeg')
|
||||||
var TextForEvent = require('../../../TextForEvent');
|
var TextForEvent = require('../../../TextForEvent');
|
||||||
|
|
||||||
var ContextualMenu = require('../../../ContextualMenu');
|
var ContextualMenu = require('../../structures/ContextualMenu');
|
||||||
var dispatcher = require("../../../dispatcher");
|
var dispatcher = require("../../../dispatcher");
|
||||||
|
|
||||||
var ObjectUtils = require('../../../ObjectUtils');
|
var ObjectUtils = require('../../../ObjectUtils');
|
||||||
@ -249,12 +249,15 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onEditClicked: function(e) {
|
onEditClicked: function(e) {
|
||||||
var MessageContextMenu = sdk.getComponent('rooms.MessageContextMenu');
|
var MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
|
||||||
var buttonRect = e.target.getBoundingClientRect()
|
var buttonRect = e.target.getBoundingClientRect()
|
||||||
var x = buttonRect.right;
|
|
||||||
var y = buttonRect.top + (e.target.height / 2);
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||||
|
var x = buttonRect.right + window.pageXOffset;
|
||||||
|
var y = (buttonRect.top + (e.target.height / 2) + window.pageYOffset) - 19;
|
||||||
var self = this;
|
var self = this;
|
||||||
ContextualMenu.createMenu(MessageContextMenu, {
|
ContextualMenu.createMenu(MessageContextMenu, {
|
||||||
|
chevronOffset: 10,
|
||||||
mxEvent: this.props.mxEvent,
|
mxEvent: this.props.mxEvent,
|
||||||
left: x,
|
left: x,
|
||||||
top: y,
|
top: y,
|
||||||
|
@ -268,9 +268,11 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_repositionTooltip: function(e) {
|
_repositionTooltip: function(e) {
|
||||||
if (this.tooltip && this.tooltip.parentElement) {
|
// We access the parent of the parent, as the tooltip is inside a container
|
||||||
|
// Needs refactoring into a better multipurpose tooltip
|
||||||
|
if (this.tooltip && this.tooltip.parentElement && this.tooltip.parentElement.parentElement) {
|
||||||
var scroll = ReactDOM.findDOMNode(this);
|
var scroll = ReactDOM.findDOMNode(this);
|
||||||
this.tooltip.style.top = (70 + scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - this._getScrollNode().scrollTop) + "px";
|
this.tooltip.style.top = (3 + scroll.parentElement.offsetTop + this.tooltip.parentElement.parentElement.offsetTop - this._getScrollNode().scrollTop) + "px";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ var classNames = require('classnames');
|
|||||||
var dis = require("../../../dispatcher");
|
var dis = require("../../../dispatcher");
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
|
var ContextualMenu = require('../../structures/ContextualMenu');
|
||||||
import {emojifyText} from '../../../HtmlUtils';
|
import {emojifyText} from '../../../HtmlUtils';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
@ -43,16 +44,48 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
var areNotifsMuted = false;
|
||||||
|
var cli = MatrixClientPeg.get();
|
||||||
|
if (!cli.isGuest()) {
|
||||||
|
var roomPushRule = cli.getRoomPushRule("global", this.props.room.roomId);
|
||||||
|
if (roomPushRule) {
|
||||||
|
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
|
||||||
|
areNotifsMuted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return({
|
return({
|
||||||
hover : false,
|
hover : false,
|
||||||
badgeHover : false,
|
badgeHover : false,
|
||||||
|
menu: false,
|
||||||
|
areNotifsMuted: areNotifsMuted,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onAction: function(payload) {
|
||||||
|
switch (payload.action) {
|
||||||
|
case 'notification_change':
|
||||||
|
// Is the notification about this room?
|
||||||
|
if (payload.roomId === this.props.room.roomId) {
|
||||||
|
this.setState( { areNotifsMuted : payload.isMuted });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
dis.unregister(this.dispatcherRef);
|
||||||
|
},
|
||||||
|
|
||||||
onClick: function() {
|
onClick: function() {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
room_id: this.props.room.roomId
|
room_id: this.props.room.roomId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -65,13 +98,47 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
badgeOnMouseEnter: function() {
|
badgeOnMouseEnter: function() {
|
||||||
|
// Only allow none guests to access the context menu
|
||||||
|
// and only change it if it needs to change
|
||||||
|
if (!MatrixClientPeg.get().isGuest() && !this.state.badgeHover) {
|
||||||
this.setState( { badgeHover : true } );
|
this.setState( { badgeHover : true } );
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
badgeOnMouseLeave: function() {
|
badgeOnMouseLeave: function() {
|
||||||
this.setState( { badgeHover : false } );
|
this.setState( { badgeHover : false } );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onBadgeClicked: function(e) {
|
||||||
|
// Only allow none guests to access the context menu
|
||||||
|
if (!MatrixClientPeg.get().isGuest()) {
|
||||||
|
|
||||||
|
// If the badge is clicked, then no longer show tooltip
|
||||||
|
if (this.props.collapsed) {
|
||||||
|
this.setState({ hover: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
var Menu = sdk.getComponent('context_menus.NotificationStateContextMenu');
|
||||||
|
var elementRect = e.target.getBoundingClientRect();
|
||||||
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||||
|
var x = elementRect.right + window.pageXOffset + 3;
|
||||||
|
var y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset) - 53;
|
||||||
|
var self = this;
|
||||||
|
ContextualMenu.createMenu(Menu, {
|
||||||
|
menuWidth: 188,
|
||||||
|
menuHeight: 126,
|
||||||
|
chevronOffset: 45,
|
||||||
|
left: x,
|
||||||
|
top: y,
|
||||||
|
room: this.props.room,
|
||||||
|
onFinished: function() {
|
||||||
|
self.setState({ menu: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setState({ menu: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
var me = this.props.room.currentState.members[myUserId];
|
var me = this.props.room.currentState.members[myUserId];
|
||||||
@ -84,60 +151,63 @@ module.exports = React.createClass({
|
|||||||
'mx_RoomTile_selected': this.props.selected,
|
'mx_RoomTile_selected': this.props.selected,
|
||||||
'mx_RoomTile_unread': this.props.unread,
|
'mx_RoomTile_unread': this.props.unread,
|
||||||
'mx_RoomTile_unreadNotify': notificationCount > 0,
|
'mx_RoomTile_unreadNotify': notificationCount > 0,
|
||||||
|
'mx_RoomTile_read': !(this.props.highlight || notificationCount > 0),
|
||||||
'mx_RoomTile_highlight': this.props.highlight,
|
'mx_RoomTile_highlight': this.props.highlight,
|
||||||
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
||||||
|
'mx_RoomTile_menu': this.state.menu,
|
||||||
|
});
|
||||||
|
|
||||||
|
var avatarClasses = classNames({
|
||||||
|
'mx_RoomTile_avatar': true,
|
||||||
|
'mx_RoomTile_mute': this.state.areNotifsMuted,
|
||||||
|
});
|
||||||
|
|
||||||
|
var badgeClasses = classNames({
|
||||||
|
'mx_RoomTile_badge': true,
|
||||||
|
'mx_RoomTile_badgeButton': this.state.badgeHover || this.state.menu,
|
||||||
|
'mx_RoomTile_badgeMute': this.state.areNotifsMuted,
|
||||||
});
|
});
|
||||||
|
|
||||||
// XXX: We should never display raw room IDs, but sometimes the
|
// XXX: We should never display raw room IDs, but sometimes the
|
||||||
// room name js sdk gives is undefined (cannot repro this -- k)
|
// room name js sdk gives is undefined (cannot repro this -- k)
|
||||||
var name = this.props.room.name || this.props.room.roomId;
|
var name = this.props.room.name || this.props.room.roomId;
|
||||||
|
|
||||||
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
||||||
|
|
||||||
var badge;
|
var badge;
|
||||||
var badgeContent;
|
var badgeContent;
|
||||||
var badgeClasses;
|
|
||||||
|
|
||||||
if (this.state.badgeHover) {
|
if (this.state.badgeHover || this.state.menu) {
|
||||||
badgeContent = "\u00B7\u00B7\u00B7";
|
badgeContent = "\u00B7\u00B7\u00B7";
|
||||||
} else if (this.props.highlight || notificationCount > 0) {
|
} else if (this.props.highlight || notificationCount > 0) {
|
||||||
badgeContent = notificationCount ? notificationCount : '!';
|
var limitedCount = (notificationCount > 99) ? '99+' : notificationCount;
|
||||||
|
badgeContent = notificationCount ? limitedCount : '!';
|
||||||
} else {
|
} else {
|
||||||
badgeContent = '\u200B';
|
badgeContent = '\u200B';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.highlight || notificationCount > 0) {
|
if (this.state.areNotifsMuted && !(this.state.badgeHover || this.state.menu)) {
|
||||||
badgeClasses = "mx_RoomTile_badge";
|
badge = <div className={ badgeClasses } onClick={this.onBadgeClicked} onMouseEnter={this.badgeOnMouseEnter} onMouseLeave={this.badgeOnMouseLeave}><img className="mx_RoomTile_badgeIcon" src="img/icon-context-mute.svg" width="16" height="12" /></div>;
|
||||||
} else {
|
} else {
|
||||||
badgeClasses = "mx_RoomTile_badge mx_RoomTile_badge_no_unread";
|
badge = <div className={ badgeClasses } onClick={this.onBadgeClicked} onMouseEnter={this.badgeOnMouseEnter} onMouseLeave={this.badgeOnMouseLeave}>{ badgeContent }</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
badge = <div className={ badgeClasses } onMouseEnter={this.badgeOnMouseEnter} onMouseLeave={this.badgeOnMouseLeave}>{ badgeContent }</div>;
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (this.props.highlight) {
|
|
||||||
badge = <div className="mx_RoomTile_badge">!</div>;
|
|
||||||
}
|
|
||||||
else if (this.props.unread) {
|
|
||||||
badge = <div className="mx_RoomTile_badge">1</div>;
|
|
||||||
}
|
|
||||||
var nameCell;
|
|
||||||
if (badge) {
|
|
||||||
nameCell = <div className="mx_RoomTile_nameBadge"><div className="mx_RoomTile_name">{name}</div><div className="mx_RoomTile_badgeCell">{badge}</div></div>;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nameCell = <div className="mx_RoomTile_name">{name}</div>;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
var label;
|
var label;
|
||||||
|
var tooltip;
|
||||||
if (!this.props.collapsed) {
|
if (!this.props.collapsed) {
|
||||||
var className = 'mx_RoomTile_name' + (this.props.isInvite ? ' mx_RoomTile_invite' : '');
|
var nameClasses = classNames({
|
||||||
|
'mx_RoomTile_name': true,
|
||||||
|
'mx_RoomTile_invite': this.props.isInvite,
|
||||||
|
'mx_RoomTile_mute': this.state.areNotifsMuted,
|
||||||
|
'mx_RoomTile_badgeShown': this.props.highlight || notificationCount > 0 || this.state.badgeHover || this.state.menu || this.state.areNotifsMuted,
|
||||||
|
});
|
||||||
|
|
||||||
let nameHTML = emojifyText(name);
|
let nameHTML = emojifyText(name);
|
||||||
if (this.props.selected) {
|
if (this.props.selected) {
|
||||||
name = <span dangerouslySetInnerHTML={nameHTML}></span>;
|
let nameSelected = <span dangerouslySetInnerHTML={nameHTML}></span>;
|
||||||
label = <div className={ className }>{ name }</div>;
|
|
||||||
|
label = <div title={ name } onClick={this.onClick} className={ nameClasses }>{ nameSelected }</div>;
|
||||||
} else {
|
} else {
|
||||||
label = <div className={ className } dangerouslySetInnerHTML={nameHTML}></div>;
|
label = <div title={ name } onClick={this.onClick} className={ nameClasses } dangerouslySetInnerHTML={nameHTML}></div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (this.state.hover) {
|
else if (this.state.hover) {
|
||||||
@ -160,13 +230,16 @@ module.exports = React.createClass({
|
|||||||
var connectDropTarget = this.props.connectDropTarget;
|
var connectDropTarget = this.props.connectDropTarget;
|
||||||
|
|
||||||
return connectDragSource(connectDropTarget(
|
return connectDragSource(connectDropTarget(
|
||||||
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<div className={classes} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<div className="mx_RoomTile_avatar">
|
<div className={avatarClasses}>
|
||||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
<RoomAvatar onClick={this.onClick} room={this.props.room} width={24} height={24} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mx_RoomTile_nameContainer">
|
||||||
{ label }
|
{ label }
|
||||||
{ badge }
|
{ badge }
|
||||||
|
</div>
|
||||||
{ incomingCallBox }
|
{ incomingCallBox }
|
||||||
|
{ tooltip }
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user