weechatRN

Weechat relay client for iOS using websockets https://github.com/mhoran/weechatRN
git clone http://git.hanabi.in/repos/weechatRN.git
Log | Files | Refs | README | LICENSE

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 });