mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-17 22:14:58 +08:00
factor out tintable SVGs into their own component, and use plain DOM onload rather than react synthetic events
This commit is contained in:
parent
9e8daba8d7
commit
509ea7c4f3
@ -102,7 +102,7 @@ function calcCssFixups() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function calcSvgFixups(nodes) {
|
function calcSvgFixups(nodes) {
|
||||||
var svgs = nodes || document.getElementsByClassName("mx_Svg");
|
var svgs = nodes || document.getElementsByClassName("mx_TintableSvg");
|
||||||
var fixups = [];
|
var fixups = [];
|
||||||
for (var i = 0; i < svgs.length; i++) {
|
for (var i = 0; i < svgs.length; i++) {
|
||||||
|
|
||||||
|
@ -23,14 +23,14 @@ limitations under the License.
|
|||||||
|
|
||||||
module.exports.components = {};
|
module.exports.components = {};
|
||||||
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
|
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
|
||||||
module.exports.components['structures.login.Login'] = require('./components/structures/login/Login');
|
|
||||||
module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration');
|
|
||||||
module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration');
|
|
||||||
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
|
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
|
||||||
module.exports.components['structures.RoomView'] = require('./components/structures/RoomView');
|
module.exports.components['structures.RoomView'] = require('./components/structures/RoomView');
|
||||||
module.exports.components['structures.ScrollPanel'] = require('./components/structures/ScrollPanel');
|
module.exports.components['structures.ScrollPanel'] = require('./components/structures/ScrollPanel');
|
||||||
module.exports.components['structures.UploadBar'] = require('./components/structures/UploadBar');
|
module.exports.components['structures.UploadBar'] = require('./components/structures/UploadBar');
|
||||||
module.exports.components['structures.UserSettings'] = require('./components/structures/UserSettings');
|
module.exports.components['structures.UserSettings'] = require('./components/structures/UserSettings');
|
||||||
|
module.exports.components['structures.login.Login'] = require('./components/structures/login/Login');
|
||||||
|
module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration');
|
||||||
|
module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration');
|
||||||
module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar');
|
module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar');
|
||||||
module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar');
|
module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar');
|
||||||
module.exports.components['views.create_room.CreateRoomButton'] = require('./components/views/create_room/CreateRoomButton');
|
module.exports.components['views.create_room.CreateRoomButton'] = require('./components/views/create_room/CreateRoomButton');
|
||||||
@ -41,6 +41,7 @@ module.exports.components['views.dialogs.LogoutPrompt'] = require('./components/
|
|||||||
module.exports.components['views.dialogs.QuestionDialog'] = require('./components/views/dialogs/QuestionDialog');
|
module.exports.components['views.dialogs.QuestionDialog'] = require('./components/views/dialogs/QuestionDialog');
|
||||||
module.exports.components['views.elements.EditableText'] = require('./components/views/elements/EditableText');
|
module.exports.components['views.elements.EditableText'] = require('./components/views/elements/EditableText');
|
||||||
module.exports.components['views.elements.ProgressBar'] = require('./components/views/elements/ProgressBar');
|
module.exports.components['views.elements.ProgressBar'] = require('./components/views/elements/ProgressBar');
|
||||||
|
module.exports.components['views.elements.TintableSvg'] = require('./components/views/elements/TintableSvg');
|
||||||
module.exports.components['views.elements.UserSelector'] = require('./components/views/elements/UserSelector');
|
module.exports.components['views.elements.UserSelector'] = require('./components/views/elements/UserSelector');
|
||||||
module.exports.components['views.login.CaptchaForm'] = require('./components/views/login/CaptchaForm');
|
module.exports.components['views.login.CaptchaForm'] = require('./components/views/login/CaptchaForm');
|
||||||
module.exports.components['views.login.CasLogin'] = require('./components/views/login/CasLogin');
|
module.exports.components['views.login.CasLogin'] = require('./components/views/login/CasLogin');
|
||||||
@ -50,10 +51,10 @@ module.exports.components['views.login.LoginHeader'] = require('./components/vie
|
|||||||
module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin');
|
module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin');
|
||||||
module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm');
|
module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm');
|
||||||
module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig');
|
module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig');
|
||||||
module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent');
|
|
||||||
module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody');
|
module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody');
|
||||||
module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody');
|
module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody');
|
||||||
module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody');
|
module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody');
|
||||||
|
module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent');
|
||||||
module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody');
|
module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody');
|
||||||
module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent');
|
module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent');
|
||||||
module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody');
|
module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody');
|
||||||
|
@ -1104,6 +1104,7 @@ module.exports = React.createClass({
|
|||||||
var RoomSettings = sdk.getComponent("rooms.RoomSettings");
|
var RoomSettings = sdk.getComponent("rooms.RoomSettings");
|
||||||
var SearchBar = sdk.getComponent("rooms.SearchBar");
|
var SearchBar = sdk.getComponent("rooms.SearchBar");
|
||||||
var ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
var ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||||
|
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
if (!this.state.room) {
|
if (!this.state.room) {
|
||||||
if (this.props.roomId) {
|
if (this.props.roomId) {
|
||||||
@ -1198,7 +1199,7 @@ module.exports = React.createClass({
|
|||||||
<div className="mx_RoomView_tabCompleteWrapper">
|
<div className="mx_RoomView_tabCompleteWrapper">
|
||||||
<TabCompleteBar entries={this.tabComplete.peek(6)} />
|
<TabCompleteBar entries={this.tabComplete.peek(6)} />
|
||||||
<div className="mx_RoomView_tabCompleteEol" title="->|">
|
<div className="mx_RoomView_tabCompleteEol" title="->|">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/eol.svg" width="22" height="16"/>
|
<TintableSvg src="img/eol.svg" width="22" height="16"/>
|
||||||
Auto-complete
|
Auto-complete
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1272,7 +1273,7 @@ module.exports = React.createClass({
|
|||||||
if (this.state.draggingFile) {
|
if (this.state.draggingFile) {
|
||||||
fileDropTarget = <div className="mx_RoomView_fileDropTarget">
|
fileDropTarget = <div className="mx_RoomView_fileDropTarget">
|
||||||
<div className="mx_RoomView_fileDropTargetLabel" title="Drop File Here">
|
<div className="mx_RoomView_fileDropTargetLabel" title="Drop File Here">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/upload-big.svg" width="45" height="59"/><br/>
|
<TintableSvg src="img/upload-big.svg" width="45" height="59"/><br/>
|
||||||
Drop File Here
|
Drop File Here
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
@ -1310,7 +1311,7 @@ module.exports = React.createClass({
|
|||||||
if (call.type === "video") {
|
if (call.type === "video") {
|
||||||
zoomButton = (
|
zoomButton = (
|
||||||
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title="Fill screen">
|
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title="Fill screen">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/fullscreen.svg" width="29" height="22" style={{ marginTop: 1, marginRight: 4 }}/>
|
<TintableSvg src="img/fullscreen.svg" width="29" height="22" style={{ marginTop: 1, marginRight: 4 }}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1342,7 +1343,7 @@ module.exports = React.createClass({
|
|||||||
{ videoMuteButton }
|
{ videoMuteButton }
|
||||||
{ zoomButton }
|
{ zoomButton }
|
||||||
{ statusBar }
|
{ statusBar }
|
||||||
<object onLoad={ this.onSvgLoad } type="image/svg+xml" className="mx_RoomView_voipChevron mx_Svg" data="img/voip-chevron.svg" width="22" height="17"/>
|
<TintableSvg className="mx_RoomView_voipChevron" src="img/voip-chevron.svg" width="22" height="17"/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
src/components/views/elements/TintableSvg.js
Normal file
56
src/components/views/elements/TintableSvg.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react');
|
||||||
|
var ReactDOM = require("react-dom");
|
||||||
|
var dis = require("../../../dispatcher");
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'TintableSvg',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
src: React.PropTypes.string.isRequired,
|
||||||
|
width: React.PropTypes.string.isRequired,
|
||||||
|
height: React.PropTypes.string.isRequired,
|
||||||
|
className: React.PropTypes.string,
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
// we can't use onLoad on object due to https://github.com/facebook/react/pull/5781
|
||||||
|
// so handle it with pure DOM instead
|
||||||
|
ReactDOM.findDOMNode(this).addEventListener('load', this.onLoad);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
ReactDOM.findDOMNode(this).removeEventListener('load', this.onLoad);
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad: function(event) {
|
||||||
|
dis.dispatch({ action: "svg_onload", svg: event.target });
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<object className={ "mx_TintableSvg " + this.props.className }
|
||||||
|
type="image/svg+xml"
|
||||||
|
data={ this.props.src }
|
||||||
|
width={ this.props.width }
|
||||||
|
height={ this.props.height }/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
var filesize = require('filesize');
|
var filesize = require('filesize');
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
var sdk = require('../../../index');
|
||||||
var dis = require("../../../dispatcher");
|
var dis = require("../../../dispatcher");
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
@ -57,12 +58,14 @@ module.exports = React.createClass({
|
|||||||
var httpUrl = cli.mxcUrlToHttp(content.url);
|
var httpUrl = cli.mxcUrlToHttp(content.url);
|
||||||
var text = this.presentableTextForFile(content);
|
var text = this.presentableTextForFile(content);
|
||||||
|
|
||||||
|
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
if (httpUrl) {
|
if (httpUrl) {
|
||||||
return (
|
return (
|
||||||
<span className="mx_MFileBody">
|
<span className="mx_MFileBody">
|
||||||
<div className="mx_MImageBody_download">
|
<div className="mx_MImageBody_download">
|
||||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
|
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/download.svg" width="12" height="14"/>
|
<TintableSvg src="img/download.svg" width="12" height="14"/>
|
||||||
Download {text}
|
Download {text}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -102,6 +102,7 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
var content = this.props.mxEvent.getContent();
|
var content = this.props.mxEvent.getContent();
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
@ -123,7 +124,7 @@ module.exports = React.createClass({
|
|||||||
</a>
|
</a>
|
||||||
<div className="mx_MImageBody_download">
|
<div className="mx_MImageBody_download">
|
||||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
|
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/download.svg" width="12" height="14"/>
|
<TintableSvg src="img/download.svg" width="12" height="14"/>
|
||||||
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
|
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -465,6 +465,7 @@ module.exports = React.createClass({
|
|||||||
var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
var uploadInputStyle = {display: 'none'};
|
var uploadInputStyle = {display: 'none'};
|
||||||
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||||
|
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
var callButton, videoCallButton, hangupButton;
|
var callButton, videoCallButton, hangupButton;
|
||||||
var call = CallHandler.getCallForRoom(this.props.room.roomId);
|
var call = CallHandler.getCallForRoom(this.props.room.roomId);
|
||||||
@ -478,11 +479,11 @@ module.exports = React.createClass({
|
|||||||
else {
|
else {
|
||||||
callButton =
|
callButton =
|
||||||
<div className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title="Voice call">
|
<div className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title="Voice call">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/voice.svg" width="16" height="26"/>
|
<TintableSvg src="img/voice.svg" width="16" height="26"/>
|
||||||
</div>
|
</div>
|
||||||
videoCallButton =
|
videoCallButton =
|
||||||
<div className="mx_MessageComposer_videocall" onClick={this.onCallClick} title="Video call">
|
<div className="mx_MessageComposer_videocall" onClick={this.onCallClick} title="Video call">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/call.svg" width="30" height="22"/>
|
<TintableSvg src="img/call.svg" width="30" height="22"/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,7 +498,7 @@ module.exports = React.createClass({
|
|||||||
<textarea ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder="Type a message..." />
|
<textarea ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder="Type a message..." />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_MessageComposer_upload" onClick={this.onUploadClick} title="Upload file">
|
<div className="mx_MessageComposer_upload" onClick={this.onUploadClick} title="Upload file">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/upload.svg" width="19" height="24"/>
|
<TintableSvg src="img/upload.svg" width="19" height="24"/>
|
||||||
<input type="file" style={uploadInputStyle} ref="uploadInput" onChange={this.onUploadFileSelected} />
|
<input type="file" style={uploadInputStyle} ref="uploadInput" onChange={this.onUploadFileSelected} />
|
||||||
</div>
|
</div>
|
||||||
{ hangupButton }
|
{ hangupButton }
|
||||||
|
@ -74,6 +74,7 @@ module.exports = React.createClass({
|
|||||||
render: function() {
|
render: function() {
|
||||||
var EditableText = sdk.getComponent("elements.EditableText");
|
var EditableText = sdk.getComponent("elements.EditableText");
|
||||||
var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
||||||
|
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
var header;
|
var header;
|
||||||
if (this.props.simpleHeader) {
|
if (this.props.simpleHeader) {
|
||||||
@ -123,7 +124,7 @@ module.exports = React.createClass({
|
|||||||
<div className="mx_RoomHeader_nametext" title={ this.props.room.name }>{ this.props.room.name }</div>
|
<div className="mx_RoomHeader_nametext" title={ this.props.room.name }>{ this.props.room.name }</div>
|
||||||
{ searchStatus }
|
{ searchStatus }
|
||||||
<div className="mx_RoomHeader_settingsButton" title="Settings">
|
<div className="mx_RoomHeader_settingsButton" title="Settings">
|
||||||
<object className="mx_Svg" type="image/svg+xml" data="img/settings.svg" width="12" height="12"/>
|
<TintableSvg src="img/settings.svg" width="12" height="12"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
if (topic) topic_el = <div className="mx_RoomHeader_topic" title={topic.getContent().topic}>{ topic.getContent().topic }</div>;
|
if (topic) topic_el = <div className="mx_RoomHeader_topic" title={topic.getContent().topic}>{ topic.getContent().topic }</div>;
|
||||||
@ -140,8 +141,7 @@ module.exports = React.createClass({
|
|||||||
if (this.props.onLeaveClick) {
|
if (this.props.onLeaveClick) {
|
||||||
leave_button =
|
leave_button =
|
||||||
<div className="mx_RoomHeader_button mx_RoomHeader_leaveButton" onClick={this.props.onLeaveClick} title="Leave room">
|
<div className="mx_RoomHeader_button mx_RoomHeader_leaveButton" onClick={this.props.onLeaveClick} title="Leave room">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/leave.svg"
|
<TintableSvg src="img/leave.svg" width="26" height="20"/>
|
||||||
width="26" height="20"/>
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,8 +149,7 @@ module.exports = React.createClass({
|
|||||||
if (this.props.onForgetClick) {
|
if (this.props.onForgetClick) {
|
||||||
forget_button =
|
forget_button =
|
||||||
<div className="mx_RoomHeader_button mx_RoomHeader_leaveButton" onClick={this.props.onForgetClick} title="Forget room">
|
<div className="mx_RoomHeader_button mx_RoomHeader_leaveButton" onClick={this.props.onForgetClick} title="Forget room">
|
||||||
<object onLoad={ this.onSvgLoad } className="mx_Svg" type="image/svg+xml" data="img/leave.svg"
|
<TintableSvg src="img/leave.svg" width="26" height="20"/>
|
||||||
width="26" height="20"/>
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +170,7 @@ module.exports = React.createClass({
|
|||||||
{ forget_button }
|
{ forget_button }
|
||||||
{ leave_button }
|
{ leave_button }
|
||||||
<div className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title="Search">
|
<div className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title="Search">
|
||||||
<object className="mx_Svg" type="image/svg+xml" data="img/search.svg" width="21" height="19"/>
|
<TintableSvg src="img/search.svg" width="21" height="19"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user