BufferContainer.tsx (6398B)
1 import * as React from 'react'; 2 import { 3 StyleSheet, 4 Linking, 5 ActionSheetIOS, 6 KeyboardAvoidingView, 7 Image, 8 View, 9 Text, 10 TouchableOpacity, 11 LayoutAnimation 12 } from 'react-native'; 13 14 import { connect, ConnectedProps } from 'react-redux'; 15 import ParsedText from 'react-native-parsed-text'; 16 17 import Buffer from './Buffer'; 18 import { getParseArgs } from '../../../lib/helpers/parse-text-args'; 19 import { formatUrl } from '../../../lib/helpers/url-formatter'; 20 import { renderWeechatFormat } from '../../../lib/weechat/color-formatter'; 21 import { StoreState } from '../../../store'; 22 import UndoTextInput from './UndoTextInput'; 23 24 const connector = connect( 25 (state: StoreState, { bufferId }: { bufferId: string }) => ({ 26 lines: state.lines[bufferId] || [], 27 nicklist: state.nicklists[bufferId] || [], 28 buffer: state.buffers[bufferId] 29 }) 30 ); 31 32 type PropsFromRedux = ConnectedProps<typeof connector>; 33 34 type Props = PropsFromRedux & { 35 bufferId: string; 36 showTopic: boolean; 37 sendMessage: (message: string) => void; 38 }; 39 40 interface State { 41 showTabButton: boolean; 42 textValue: string; 43 selection: { start: number; end: number }; 44 } 45 46 class BufferContainer extends React.Component<Props, State> { 47 state = { 48 showTabButton: false, 49 textValue: '', 50 selection: { 51 start: 0, 52 end: 0 53 } 54 }; 55 56 tabCompleteInProgress = false; 57 tabCompleteMatches: WeechatNicklist[] = []; 58 tabCompleteIndex = 0; 59 tabCompleteWordStart = 0; 60 tabCompleteWordEnd = 0; 61 62 parseArgs = getParseArgs( 63 styles.link, 64 this.handleOnPress, 65 this.handleOnLongPress 66 ); 67 68 handleOnFocus() { 69 this.setState({ 70 showTabButton: true 71 }); 72 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); 73 } 74 75 handleOnBlur() { 76 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); 77 this.setState({ 78 showTabButton: false 79 }); 80 } 81 82 handleOnLongPress(type: string, text: string) { 83 ActionSheetIOS.showShareActionSheetWithOptions( 84 { 85 url: formatUrl(type, text), 86 message: text 87 }, 88 () => null, 89 () => null 90 ); 91 } 92 93 handleOnPress(type: string, text: string) { 94 console.log(type, text); 95 if (type === 'channel') { 96 // this.props.dispatch(changeCurrentBuffer(text)); 97 } else { 98 Linking.openURL(formatUrl(type, text)); 99 } 100 } 101 102 handleChangeText = (textValue: string) => { 103 this.tabCompleteInProgress = false; 104 this.setState({ textValue }); 105 }; 106 107 handleSubmit = () => { 108 const { textValue } = this.state; 109 textValue.split('\n').forEach((line) => { 110 this.props.sendMessage(line); 111 }); 112 this.handleChangeText(''); 113 }; 114 115 tabCompleteNick = () => { 116 const { textValue, selection } = this.state; 117 const { nicklist } = this.props; 118 119 if (!this.tabCompleteInProgress) { 120 this.tabCompleteWordEnd = selection.start; 121 122 this.tabCompleteWordStart = 123 textValue.lastIndexOf(' ', this.tabCompleteWordEnd - 1) + 1; 124 125 if (this.tabCompleteWordStart == this.tabCompleteWordEnd) return; 126 127 const prefix = textValue 128 .substring(this.tabCompleteWordStart, this.tabCompleteWordEnd) 129 .toLowerCase(); 130 131 this.tabCompleteMatches = nicklist.filter((nick) => 132 nick.name.toLowerCase().startsWith(prefix) 133 ); 134 if (this.tabCompleteMatches.length == 0) { 135 return; 136 } 137 138 this.tabCompleteIndex = 0; 139 } else { 140 this.tabCompleteIndex = 141 (this.tabCompleteIndex + 1) % this.tabCompleteMatches.length; 142 } 143 144 let nick = this.tabCompleteMatches[this.tabCompleteIndex].name; 145 if (this.tabCompleteWordStart == 0) { 146 nick += ': '; 147 } 148 149 this.setState({ 150 textValue: 151 textValue.substring(0, this.tabCompleteWordStart) + 152 nick + 153 textValue.substring(this.tabCompleteWordEnd) 154 }); 155 this.tabCompleteWordEnd = this.tabCompleteWordStart + nick.length; 156 this.tabCompleteInProgress = true; 157 }; 158 159 handleSelectionChange = ({ 160 nativeEvent: { selection } 161 }: { 162 nativeEvent: { selection: { start: number; end: number } }; 163 }) => { 164 this.setState({ selection }); 165 }; 166 167 onLongPress = () => { 168 // not implemented 169 }; 170 171 render() { 172 const { bufferId, buffer, showTopic, lines } = this.props; 173 const { textValue, showTabButton } = this.state; 174 175 return ( 176 <KeyboardAvoidingView style={styles.container} behavior="padding"> 177 {showTopic && ( 178 <View> 179 <Text> 180 {renderWeechatFormat(buffer.title).map((props, index) => ( 181 <ParsedText {...props} key={index} parse={this.parseArgs} /> 182 ))} 183 </Text> 184 </View> 185 )} 186 <Buffer 187 bufferId={bufferId} 188 lines={lines} 189 onLongPress={this.onLongPress} 190 parseArgs={this.parseArgs} 191 /> 192 <View style={styles.bottomBox}> 193 <UndoTextInput 194 style={styles.inputBox} 195 value={textValue} 196 onChangeText={this.handleChangeText} 197 onFocus={() => this.handleOnFocus()} 198 onBlur={() => this.handleOnBlur()} 199 onSelectionChange={this.handleSelectionChange} 200 returnKeyType="send" 201 blurOnSubmit={false} 202 onSubmitEditing={this.handleSubmit} 203 enablesReturnKeyAutomatically={true} 204 /> 205 {showTabButton && ( 206 <TouchableOpacity 207 style={{ alignItems: 'center', width: 40 }} 208 onPress={this.tabCompleteNick} 209 > 210 <Image source={require('../../icons/long-arrow-right.png')} /> 211 </TouchableOpacity> 212 )} 213 </View> 214 </KeyboardAvoidingView> 215 ); 216 } 217 } 218 219 export default connector(BufferContainer); 220 221 export const styles = StyleSheet.create({ 222 topbar: { 223 height: 20, 224 paddingHorizontal: 5, 225 backgroundColor: '#001' 226 }, 227 link: { 228 textDecorationLine: 'underline' 229 }, 230 text: { 231 color: '#eee' 232 }, 233 container: { 234 flex: 1, 235 backgroundColor: '#222' 236 }, 237 main: { 238 paddingVertical: 20 239 }, 240 bottomBox: { 241 paddingHorizontal: 10, 242 paddingVertical: 7.5, 243 flexDirection: 'row', 244 alignItems: 'center', 245 justifyContent: 'space-between', 246 backgroundColor: '#333' 247 }, 248 inputBox: { 249 maxHeight: 60.5, 250 padding: 5, 251 justifyContent: 'center', 252 borderColor: 'gray', 253 backgroundColor: '#fff', 254 flex: 1 255 } 256 });