mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-24 04:16:00 +08:00
The Great File Renaming in RCTText
Summary: The previous file/class name convention seemed cool... but now it drives me BANANAS! It makes all this code really hard to maintain. So, evething were renamed following common modern RN convention. Reviewed By: mmmulani Differential Revision: D6605090 fbshipit-source-id: 88ca13d793a5d2adaac2b7922ec6bd4654aacec5
This commit is contained in:
committed by
Facebook Github Bot
parent
5c8481e836
commit
19a9c5e41d
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <React/RCTShadowView.h>
|
||||
|
||||
@interface RCTSinglelineTextInputShadowView : RCTShadowView
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTSinglelineTextInputShadowView.h"
|
||||
|
||||
@implementation RCTSinglelineTextInputShadowView
|
||||
|
||||
- (BOOL)isYogaLeafNode
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <React/RCTComponent.h>
|
||||
#import <React/RCTView.h>
|
||||
|
||||
#import "RCTBaseTextInputView.h"
|
||||
|
||||
@class RCTUITextField;
|
||||
|
||||
@interface RCTSinglelineTextInputView : RCTBaseTextInputView
|
||||
|
||||
@property (nonatomic, assign) BOOL caretHidden;
|
||||
@property (nonatomic, strong) NSNumber *maxLength;
|
||||
|
||||
@end
|
||||
142
Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.m
Normal file
142
Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputView.m
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTSinglelineTextInputView.h"
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
#import <React/RCTFont.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
#import "RCTBackedTextInputDelegate.h"
|
||||
#import "RCTTextSelection.h"
|
||||
#import "RCTUITextField.h"
|
||||
|
||||
@interface RCTSinglelineTextInputView () <RCTBackedTextInputDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTSinglelineTextInputView
|
||||
{
|
||||
RCTUITextField *_backedTextInput;
|
||||
BOOL _submitted;
|
||||
CGSize _previousContentSize;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if (self = [super initWithBridge:bridge]) {
|
||||
// `blurOnSubmit` defaults to `true` for <TextInput multiline={false}> by design.
|
||||
_blurOnSubmit = YES;
|
||||
|
||||
_backedTextInput = [[RCTUITextField alloc] initWithFrame:self.bounds];
|
||||
_backedTextInput.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_backedTextInput.textInputDelegate = self;
|
||||
_backedTextInput.font = self.fontAttributes.font;
|
||||
|
||||
[self addSubview:_backedTextInput];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
|
||||
- (id<RCTBackedTextInputViewProtocol>)backedTextInputView
|
||||
{
|
||||
return _backedTextInput;
|
||||
}
|
||||
|
||||
- (void)sendKeyValueForString:(NSString *)string
|
||||
{
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
|
||||
reactTag:self.reactTag
|
||||
text:nil
|
||||
key:string
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (NSString *)text
|
||||
{
|
||||
return _backedTextInput.text;
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
|
||||
if (eventLag == 0 && ![text isEqualToString:self.text]) {
|
||||
UITextRange *selection = _backedTextInput.selectedTextRange;
|
||||
NSInteger oldTextLength = _backedTextInput.text.length;
|
||||
|
||||
_backedTextInput.text = text;
|
||||
|
||||
if (selection.empty) {
|
||||
// maintain cursor position relative to the end of the old text
|
||||
NSInteger offsetStart = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument toPosition:selection.start];
|
||||
NSInteger offsetFromEnd = oldTextLength - offsetStart;
|
||||
NSInteger newOffset = text.length - offsetFromEnd;
|
||||
UITextPosition *position = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument offset:newOffset];
|
||||
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:position toPosition:position]
|
||||
notifyDelegate:YES];
|
||||
}
|
||||
} else if (eventLag > RCTTextUpdateLagWarningThreshold) {
|
||||
RCTLogWarn(@"Native TextInput(%@) is %lld events ahead of JS - try to make your JS faster.", _backedTextInput.text, (long long)eventLag);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RCTBackedTextInputDelegate
|
||||
|
||||
- (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string
|
||||
{
|
||||
// Only allow single keypresses for `onKeyPress`, pasted text will not be sent.
|
||||
if (!_backedTextInput.textWasPasted) {
|
||||
[self sendKeyValueForString:string];
|
||||
}
|
||||
|
||||
if (_maxLength != nil && ![string isEqualToString:@"\n"]) { // Make sure forms can be submitted via return.
|
||||
NSUInteger allowedLength = _maxLength.integerValue - MIN(_maxLength.integerValue, _backedTextInput.text.length) + range.length;
|
||||
if (string.length > allowedLength) {
|
||||
if (string.length > 1) {
|
||||
// Truncate the input string so the result is exactly `maxLength`.
|
||||
NSString *limitedString = [string substringToIndex:allowedLength];
|
||||
NSMutableString *newString = _backedTextInput.text.mutableCopy;
|
||||
[newString replaceCharactersInRange:range withString:limitedString];
|
||||
_backedTextInput.text = newString;
|
||||
|
||||
// Collapse selection at end of insert to match normal paste behavior.
|
||||
UITextPosition *insertEnd = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument
|
||||
offset:(range.location + allowedLength)];
|
||||
[_backedTextInput setSelectedTextRange:[_backedTextInput textRangeFromPosition:insertEnd toPosition:insertEnd]
|
||||
notifyDelegate:YES];
|
||||
[self textInputDidChange];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textInputDidChange
|
||||
{
|
||||
_nativeEventCount++;
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
||||
reactTag:self.reactTag
|
||||
text:_backedTextInput.text
|
||||
key:nil
|
||||
eventCount:_nativeEventCount];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@interface RCTSinglelineTextInputViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTSinglelineTextInputViewManager.h"
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTFont.h>
|
||||
#import <React/RCTShadowView+Layout.h>
|
||||
#import <React/RCTShadowView.h>
|
||||
|
||||
#import "RCTConvert+Text.h"
|
||||
#import "RCTSinglelineTextInputShadowView.h"
|
||||
#import "RCTSinglelineTextInputView.h"
|
||||
#import "RCTUITextField.h"
|
||||
|
||||
@implementation RCTSinglelineTextInputViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return [RCTSinglelineTextInputShadowView new];
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTSinglelineTextInputView alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
|
||||
#pragma mark - Unified <TextInput> properties
|
||||
|
||||
RCT_REMAP_VIEW_PROPERTY(allowFontScaling, fontAttributes.allowFontScaling, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, backedTextInputView.autocapitalizationType, UITextAutocapitalizationType)
|
||||
RCT_REMAP_VIEW_PROPERTY(autoCorrect, backedTextInputView.autocorrectionType, UITextAutocorrectionType)
|
||||
RCT_REMAP_VIEW_PROPERTY(color, backedTextInputView.textColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(editable, backedTextInputView.editable, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, backedTextInputView.enablesReturnKeyAutomatically, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(fontSize, fontAttributes.fontSize, NSNumber)
|
||||
RCT_REMAP_VIEW_PROPERTY(fontWeight, fontAttributes.fontWeight, NSString)
|
||||
RCT_REMAP_VIEW_PROPERTY(fontStyle, fontAttributes.fontStyle, NSString)
|
||||
RCT_REMAP_VIEW_PROPERTY(fontFamily, fontAttributes.fontFamily, NSString)
|
||||
RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, backedTextInputView.keyboardAppearance, UIKeyboardAppearance)
|
||||
RCT_REMAP_VIEW_PROPERTY(keyboardType, backedTextInputView.keyboardType, UIKeyboardType)
|
||||
RCT_REMAP_VIEW_PROPERTY(placeholder, backedTextInputView.placeholder, NSString)
|
||||
RCT_REMAP_VIEW_PROPERTY(placeholderTextColor, backedTextInputView.placeholderColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(returnKeyType, backedTextInputView.returnKeyType, UIReturnKeyType)
|
||||
RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(selectionColor, backedTextInputView.tintColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(spellCheck, backedTextInputView.spellCheckingType, UITextSpellCheckingType)
|
||||
RCT_REMAP_VIEW_PROPERTY(textAlign, backedTextInputView.textAlignment, NSTextAlignment)
|
||||
RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
|
||||
#pragma mark - Singleline <TextInput> (aka TextField) specific properties
|
||||
|
||||
RCT_REMAP_VIEW_PROPERTY(caretHidden, backedTextInputView.caretHidden, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
|
||||
|
||||
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
|
||||
{
|
||||
NSNumber *reactTag = shadowView.reactTag;
|
||||
UIEdgeInsets borderAsInsets = shadowView.borderAsInsets;
|
||||
UIEdgeInsets paddingAsInsets = shadowView.paddingAsInsets;
|
||||
return ^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTBaseTextInputView *> *viewRegistry) {
|
||||
RCTBaseTextInputView *view = viewRegistry[reactTag];
|
||||
view.reactBorderInsets = borderAsInsets;
|
||||
view.reactPaddingInsets = paddingAsInsets;
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
33
Libraries/Text/TextInput/Singleline/RCTUITextField.h
Normal file
33
Libraries/Text/TextInput/Singleline/RCTUITextField.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBackedTextInputViewProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*
|
||||
* Just regular UITextField... but much better!
|
||||
*/
|
||||
@interface RCTUITextField : UITextField <RCTBackedTextInputViewProtocol>
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
|
||||
|
||||
@property (nonatomic, weak) id<RCTBackedTextInputDelegate> textInputDelegate;
|
||||
|
||||
@property (nonatomic, assign) BOOL caretHidden;
|
||||
@property (nonatomic, assign, readonly) BOOL textWasPasted;
|
||||
@property (nonatomic, strong, nullable) UIColor *placeholderColor;
|
||||
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
|
||||
@property (nonatomic, assign, getter=isEditable) BOOL editable;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
165
Libraries/Text/TextInput/Singleline/RCTUITextField.m
Normal file
165
Libraries/Text/TextInput/Singleline/RCTUITextField.m
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTUITextField.h"
|
||||
|
||||
#import <React/RCTUtils.h>
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
#import "RCTBackedTextInputDelegateAdapter.h"
|
||||
|
||||
@implementation RCTUITextField {
|
||||
RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(_textDidChange)
|
||||
name:UITextFieldTextDidChangeNotification
|
||||
object:self];
|
||||
|
||||
_textInputDelegateAdapter = [[RCTBackedTextFieldDelegateAdapter alloc] initWithTextField:self];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)_textDidChange
|
||||
{
|
||||
_textWasPasted = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
|
||||
{
|
||||
_textContainerInset = textContainerInset;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setPlaceholder:(NSString *)placeholder
|
||||
{
|
||||
[super setPlaceholder:placeholder];
|
||||
[self _updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderColor:(UIColor *)placeholderColor
|
||||
{
|
||||
_placeholderColor = placeholderColor;
|
||||
[self _updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholder
|
||||
{
|
||||
if (self.placeholder == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *attributes = [NSMutableDictionary new];
|
||||
if (_placeholderColor) {
|
||||
[attributes setObject:_placeholderColor forKey:NSForegroundColorAttributeName];
|
||||
}
|
||||
|
||||
self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder
|
||||
attributes:attributes];
|
||||
}
|
||||
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return self.isEnabled;
|
||||
}
|
||||
|
||||
- (void)setEditable:(BOOL)editable
|
||||
{
|
||||
self.enabled = editable;
|
||||
}
|
||||
|
||||
#pragma mark - Caret Manipulation
|
||||
|
||||
- (CGRect)caretRectForPosition:(UITextPosition *)position
|
||||
{
|
||||
if (_caretHidden) {
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
return [super caretRectForPosition:position];
|
||||
}
|
||||
|
||||
#pragma mark - Positioning Overrides
|
||||
|
||||
- (CGRect)textRectForBounds:(CGRect)bounds
|
||||
{
|
||||
return UIEdgeInsetsInsetRect([super textRectForBounds:bounds], _textContainerInset);
|
||||
}
|
||||
|
||||
- (CGRect)editingRectForBounds:(CGRect)bounds
|
||||
{
|
||||
return [self textRectForBounds:bounds];
|
||||
}
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
- (void)setSelectedTextRange:(UITextRange *)selectedTextRange
|
||||
{
|
||||
[super setSelectedTextRange:selectedTextRange];
|
||||
[_textInputDelegateAdapter selectedTextRangeWasSet];
|
||||
}
|
||||
|
||||
- (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate
|
||||
{
|
||||
if (!notifyDelegate) {
|
||||
// We have to notify an adapter that following selection change was initiated programmatically,
|
||||
// so the adapter must not generate a notification for it.
|
||||
[_textInputDelegateAdapter skipNextTextInputDidChangeSelectionEventWithTextRange:selectedTextRange];
|
||||
}
|
||||
|
||||
[super setSelectedTextRange:selectedTextRange];
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender
|
||||
{
|
||||
[super paste:sender];
|
||||
_textWasPasted = YES;
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (CGSize)contentSize
|
||||
{
|
||||
// Returning size DOES contain `textContainerInset` (aka `padding`).
|
||||
return self.intrinsicContentSize;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize
|
||||
{
|
||||
// Note: `placeholder` defines intrinsic size for `<TextInput>`.
|
||||
NSString *text = self.placeholder ?: @"";
|
||||
CGSize size = [text sizeWithAttributes:@{NSFontAttributeName: self.font}];
|
||||
size = CGSizeMake(RCTCeilPixelValue(size.width), RCTCeilPixelValue(size.height));
|
||||
size.width += _textContainerInset.left + _textContainerInset.right;
|
||||
size.height += _textContainerInset.top + _textContainerInset.bottom;
|
||||
// Returning size DOES contain `textContainerInset` (aka `padding`).
|
||||
return size;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size
|
||||
{
|
||||
// All size values here contain `textContainerInset` (aka `padding`).
|
||||
CGSize intrinsicSize = self.intrinsicContentSize;
|
||||
return CGSizeMake(MIN(size.width, intrinsicSize.width), MIN(size.height, intrinsicSize.height));
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user