diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..de4d313 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,47 @@ +{ + "env": { + "node": true, + "es6": true + }, + + "globals": { + "fetch": false + }, + + "parserOptions": { + "ecmaVersion": 8, + "ecmaFeatures": { + "arrowFunctions": true, + "blockBindings": true, + "classes": true, + "defaultParameters": true, + "destructuring": true, + "forOf": true, + "generators": true, + "modules": true, + "objectLiteralComputedProperties": true, + "objectLiteralDuplicateProperties": true, + "objectLiteralShorthandMethods": true, + "objectLiteralShorthandProperties": true, + "regexUFlag": true, + "regexYFlag": true, + "restParams": true, + "spread": true, + "superInFunctions": true, + "templateStrings": true, + "unicodeCodePointEscapes": true, + "globalReturn": true + }, + "sourceType": "module" + }, + + "extends": "airbnb", + + "rules": { + "arrow-parens": 0, + "no-param-reassign": 0, + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }] + } +} + +// https://eslint.org/docs/rules/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..8cb6831 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +__tests__/data \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..99ed93f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +tabWidth: 2 +useTabs: false +semi: true +singleQuote: true +trailingComma: all +bracketSpacing: true +jsxBracketSameLine: false +arrowParens: avoid +parser: flow +printWidth: 80 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3a2ecd9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +To contribute to hackd, clone/fork the repository, make changes and open a PR with your changes. Any type of change is welcomed, including bug fixes, new features, documentation or tests. + +Opening issues for bugs, features or anything else is also welcomed. + +If you are implementing a new feature, or modifying styles, please replace/add screenshot(s) to the [README](README.md). + +### Prettier + +[Prettier](https://github.com/prettier/prettier) is used to keep a consistent code style across the project. Before committing, run `npm run prettier` which will transform your code to fit the projects style. + +### ESLint + +[ESLint](https://github.com/eslint/eslint) is used for code linting inside your IDE/text editor. If you don't have ESLint installed in your editor, you should install it - [https://eslint.org/docs/user-guide/integrations](https://eslint.org/docs/user-guide/integrations). + +To find any errors or warnings, either view them in your editor once you've installed the right package, or run `npm run eslint`. Not all errors/warnings can be fixed, and some will be picked up by prettier, so don't worry if you can't fix them. + +### Lock Files + +All lock files (`package-lock.json`, `yarn.lock`) should be committed. + +### Dependencies + +Try to keep dependencies to a minimum where possible. If you are pulling a whole library for just one function, try to implement that function as a helper method. + +### Testing + +[Jest](https://github.com/facebook/jest) is used for testing. Once you've made changes, writing a test case helps to catch any bugs. Feel free to open a PR that just includes more tests. + +### Setup + +Follow the instructions in the [README](README.md) to get setup. Once hackd is running in the iOS simulator, you can enable debugging and view redux-logger logs in the console. \ No newline at end of file diff --git a/__tests__/tests/actions/auth.js b/__tests__/tests/actions/auth.js index 9e4a9a6..45bc38b 100644 --- a/__tests__/tests/actions/auth.js +++ b/__tests__/tests/actions/auth.js @@ -7,19 +7,18 @@ import { post } from '../../data/api/post'; const dispatch = jest.fn(); describe('Auth Actions', () => { - it('should login a user', () => { const getState = () => ({}); actions.login(appUser)(dispatch, getState); expect(dispatch).toHaveBeenCalledWith({ - type: 'UPDATE_USER', + type: 'UPDATE_USER', user: appUser, }); }); it('should add a post to the upvoted section of a new users account', () => { - const getState = () => ({ + const getState = () => ({ user: appUser, accounts: {}, }); @@ -32,13 +31,13 @@ describe('Auth Actions', () => { actions.addIdToUserAccount(post.id, 'upvoted')(dispatch, getState); expect(dispatch).toHaveBeenCalledWith({ - type: 'SET_ACCOUNTS_DETAILS', + type: 'SET_ACCOUNTS_DETAILS', accounts: expectedAccounts, }); }); it('should add a post to the saved section of a new users account', () => { - const getState = () => ({ + const getState = () => ({ user: appUser, accounts: {}, }); @@ -51,13 +50,13 @@ describe('Auth Actions', () => { actions.addIdToUserAccount(post.id, 'saved')(dispatch, getState); expect(dispatch).toHaveBeenCalledWith({ - type: 'SET_ACCOUNTS_DETAILS', + type: 'SET_ACCOUNTS_DETAILS', accounts: expectedAccounts, }); }); it('should remove a post from the saved section of an existing users account', () => { - const getState = () => ({ + const getState = () => ({ user: appUser, accounts: { [appUser.username]: { @@ -74,13 +73,13 @@ describe('Auth Actions', () => { actions.removeIdFromUserAccount(post.id, 'saved')(dispatch, getState); expect(dispatch).toHaveBeenCalledWith({ - type: 'SET_ACCOUNTS_DETAILS', + type: 'SET_ACCOUNTS_DETAILS', accounts: expectedAccounts, }); }); it('should add a post to the upvotedComments section of a new users account', () => { - const getState = () => ({ + const getState = () => ({ user: appUser, accounts: {}, }); @@ -93,13 +92,13 @@ describe('Auth Actions', () => { actions.addIdToUserAccount(post.id, 'upvotedComments')(dispatch, getState); expect(dispatch).toHaveBeenCalledWith({ - type: 'SET_ACCOUNTS_DETAILS', + type: 'SET_ACCOUNTS_DETAILS', accounts: expectedAccounts, }); }); it('should remove a post from the upvotedComments section of an existing users account', () => { - const getState = () => ({ + const getState = () => ({ user: appUser, accounts: { [appUser.username]: { @@ -114,11 +113,13 @@ describe('Auth Actions', () => { }, }; - actions.removeIdFromUserAccount(post.id, 'upvotedComments')(dispatch, getState); + actions.removeIdFromUserAccount(post.id, 'upvotedComments')( + dispatch, + getState, + ); expect(dispatch).toHaveBeenCalledWith({ - type: 'SET_ACCOUNTS_DETAILS', + type: 'SET_ACCOUNTS_DETAILS', accounts: expectedAccounts, }); }); - -}); \ No newline at end of file +}); diff --git a/__tests__/tests/actions/items.js b/__tests__/tests/actions/items.js index cfce9ed..7cce771 100644 --- a/__tests__/tests/actions/items.js +++ b/__tests__/tests/actions/items.js @@ -4,7 +4,6 @@ import * as actions from '../../../app/actions/items'; import { posts } from '../../data/api/post'; describe('Items Actions', () => { - it('should set first page posts', () => { const getState = () => ({}); @@ -15,7 +14,7 @@ describe('Items Actions', () => { actions.setPosts(1, posts)(dispatch, getState); expect(dispatch).toHaveBeenCalledWith({ - type: 'SET_POSTS', + type: 'SET_POSTS', posts, }); }); @@ -24,9 +23,8 @@ describe('Items Actions', () => { const storyType = 'top'; expect(actions.setStoryType(storyType)).toEqual({ - type: 'SET_STORY_TYPE', + type: 'SET_STORY_TYPE', storyType, }); }); - -}); \ No newline at end of file +}); diff --git a/__tests__/tests/utilities/utils.js b/__tests__/tests/utilities/utils.js index 528c388..9b278c1 100644 --- a/__tests__/tests/utilities/utils.js +++ b/__tests__/tests/utilities/utils.js @@ -1,4 +1,4 @@ -import { +import { capitalize, truncate, addToUserAccount, @@ -29,7 +29,7 @@ describe('App Utils', () => { expect(addToUserAccount(account, appUser, id, type)).toEqual({ [appUser.username]: { upvoted: [id], - } + }, }); }); @@ -45,8 +45,7 @@ describe('App Utils', () => { expect(removeFromUserAccount(account, appUser, id, type)).toEqual({ [appUser.username]: { upvotedComments: [id2], - } + }, }); }); - }); diff --git a/app/actions/auth.js b/app/actions/auth.js index 9c7e3ba..5c8cd13 100644 --- a/app/actions/auth.js +++ b/app/actions/auth.js @@ -1,9 +1,6 @@ import * as types from './types'; -import { - addToUserAccount, - removeFromUserAccount -} from '../helpers/utils'; -import { logout, upvote, } from '../helpers/api'; +import { addToUserAccount, removeFromUserAccount } from '../helpers/utils'; +import { logout, upvote } from '../helpers/api'; const setUser = user => { return { @@ -37,12 +34,11 @@ export const removeIdFromUserAccount = (id, type) => { }; }; -export const upvotePost = (id) => { +export const upvotePost = id => { return (dispatch, getState) => { - // Add upvote initially for immediate feedback dispatch(addIdToUserAccount(id, 'upvoted')); - + upvote(id).then(upvoted => { if (!upvoted) { dispatch(removeIdFromUserAccount(id, 'upvoted')); @@ -51,7 +47,7 @@ export const upvotePost = (id) => { }; }; -export const savePost = (id) => { +export const savePost = id => { return (dispatch, getState) => { dispatch(addIdToUserAccount(id, 'saved')); }; diff --git a/app/actions/index.js b/app/actions/index.js index ee2508c..67c1088 100644 --- a/app/actions/index.js +++ b/app/actions/index.js @@ -2,7 +2,8 @@ import * as ItemActions from './items'; import * as AuthActions from './auth'; import * as SettingActions from './settings'; -export const ActionCreators = Object.assign({}, +export const ActionCreators = Object.assign( + {}, ItemActions, AuthActions, SettingActions, diff --git a/app/actions/types.js b/app/actions/types.js index fb7ac2c..54af1fa 100644 --- a/app/actions/types.js +++ b/app/actions/types.js @@ -10,4 +10,4 @@ export const ADD_UPVOTED_POST = 'ADD_UPVOTED_POST'; export const CHANGE_SAVED_POSTS = 'CHANGE_SAVED_POSTS'; // Settings -export const SET_SETTINGS = 'SET_SETTINGS'; \ No newline at end of file +export const SET_SETTINGS = 'SET_SETTINGS'; diff --git a/app/components/CustomText.js b/app/components/CustomText.js index bb38af3..2ac5d15 100644 --- a/app/components/CustomText.js +++ b/app/components/CustomText.js @@ -1,7 +1,5 @@ import React from 'react'; -import { - Text, -} from 'react-native'; +import { Text } from 'react-native'; import commonStyles from '../styles/common'; diff --git a/app/components/Form.js b/app/components/Form.js index 72888f8..02084ce 100644 --- a/app/components/Form.js +++ b/app/components/Form.js @@ -36,47 +36,60 @@ export default class Form extends React.PureComponent { render() { return ( - - { Object.keys(this.props.inputs).map(key => { + {Object.keys(this.props.inputs).map(key => { const input = this.props.inputs[key]; return ( - this.textChanged(input, text)} + onChangeText={text => this.textChanged(input, text)} /> ); })} - {this.props.submitText ? this.props.submitText : 'Submit'} + onPress={this.submit} + > + + {this.props.submitText ? this.props.submitText : 'Submit'} + - Back to {this.props.backText} + onPress={this.back} + > + + Back to {this.props.backText} + - {this.props.error} + + {this.props.error} + diff --git a/app/components/ListItem.js b/app/components/ListItem.js index 469383e..6a537e2 100644 --- a/app/components/ListItem.js +++ b/app/components/ListItem.js @@ -1,12 +1,7 @@ import React from 'react'; import config from '../config/default'; import commonStyles from '../styles/common'; -import { - StyleSheet, - Text, - View, - TouchableOpacity, -} from 'react-native'; +import { StyleSheet, Text, View, TouchableOpacity } from 'react-native'; import CustomText from './CustomText'; import Score from './PostItem/Score'; import Comments from './PostItem/Comments'; @@ -29,7 +24,11 @@ export default class ListItem extends React.PureComponent { render() { return ( - + @@ -38,24 +37,13 @@ export default class ListItem extends React.PureComponent { - - - - @@ -75,7 +63,7 @@ const styles = StyleSheet.create({ backgroundColor: '#FFF', }, listItemSection: { - paddingBottom: 5 + paddingBottom: 5, }, listItemTitle: { fontSize: 16, @@ -89,7 +77,7 @@ const styles = StyleSheet.create({ paddingBottom: 5, }, listItemAction: { - flexGrow: 1 + flexGrow: 1, }, listItemActionText: { alignSelf: 'flex-end', diff --git a/app/components/Post/AllComments.js b/app/components/Post/AllComments.js index e2f98b5..a1c53fa 100644 --- a/app/components/Post/AllComments.js +++ b/app/components/Post/AllComments.js @@ -1,18 +1,10 @@ import React from 'react'; -import { - View, - StyleSheet, - FlatList, - ActivityIndicator, -} from 'react-native'; +import { View, StyleSheet, FlatList, ActivityIndicator } from 'react-native'; import Comment from './Comment'; import CustomText from '../CustomText'; import commonStyles from '../../styles/common'; -import { - getComments, - toggleComments, -} from '../../helpers/api'; +import { getComments, toggleComments } from '../../helpers/api'; export default class AllComments extends React.Component { constructor(props) { @@ -54,52 +46,60 @@ export default class AllComments extends React.Component { } for (let i = 0, p = Promise.resolve(); i < kids.length; i++) { - p = p.then(_ => new Promise(resolve => { - - // Get replies for current top level comment - getComments([kids[i]]) - .then(comments => { - let newComments = this.state.comments || []; - newComments.push(...comments) - - let loadingMoreComments = true; - - // Last comment finished loading - if (i == kids.length - 1) { - loadingMoreComments = false; - } - - if (this._mounted) { - this.setState({ - comments: newComments, - loading: false, - loadingMoreComments, - }, () => { - resolve(); + p = p.then( + _ => + new Promise(resolve => { + // Get replies for current top level comment + getComments([kids[i]]) + .then(comments => { + let newComments = this.state.comments || []; + newComments.push(...comments); + + let loadingMoreComments = true; + + // Last comment finished loading + if (i == kids.length - 1) { + loadingMoreComments = false; + } + + if (this._mounted) { + this.setState( + { + comments: newComments, + loading: false, + loadingMoreComments, + }, + () => { + resolve(); + }, + ); + } else { + resolve(); + } + }) + .catch(error => { + if (this._mounted) { + this.setState( + { + error, + loading: false, + }, + () => { + resolve(); + }, + ); + } }); - } else { - resolve(); - } - }) - .catch(error => { - if (this._mounted) { - this.setState({ - error, - loading: false, - }, () => { - resolve(); - }); - } - }); - })); + }), + ); } }; toggle = (id, level) => { if (!this.props.settings.tapToCollapse) { return; - }; - + } + let { comments } = this.state; comments = toggleComments(comments, id, level); @@ -124,7 +124,7 @@ export default class AllComments extends React.Component { extraData={[this.state.refreshComments, this.state.comments]} keyExtractor={(item, index) => index.toString()} renderItem={({ item }) => ( - )} /> - {this.state.loadingMoreComments && + {this.state.loadingMoreComments && ( - } + )} ); } diff --git a/app/components/Post/Comment.js b/app/components/Post/Comment.js index cf26f12..f2cc3e0 100644 --- a/app/components/Post/Comment.js +++ b/app/components/Post/Comment.js @@ -29,7 +29,7 @@ class Comment extends React.Component { super(props); this.state = { upvoted: false, - } + }; this.toggle = this.toggle.bind(this); } @@ -40,10 +40,10 @@ class Comment extends React.Component { } openUrl = url => { - const readerMode = this.props.settings.useSafariReaderMode + const readerMode = this.props.settings.useSafariReaderMode; SafariView.show({ url, - readerMode + readerMode, }); }; @@ -55,13 +55,17 @@ class Comment extends React.Component { if (this.props.user.loggedIn) { if (this.props.accounts[this.props.user.username]) { if (this.props.accounts[this.props.user.username].upvotedComments) { - if (this.props.accounts[this.props.user.username].upvotedComments.indexOf(this.props.id) !== -1) { + if ( + this.props.accounts[ + this.props.user.username + ].upvotedComments.indexOf(this.props.id) !== -1 + ) { return true; } } } } - + return false; }; @@ -84,10 +88,10 @@ class Comment extends React.Component { this.props.addIdToUserAccount(id, 'upvotedComments'); this.upvoteComment(id); } - ReactNativeHaptic.generate('impact') + ReactNativeHaptic.generate('impact'); }; - unvoteComment = (id) => { + unvoteComment = id => { unvote(id).then(unvoted => { if (!unvoted) { this.props.addIdToUserAccount(id, 'upvotedComments'); @@ -96,13 +100,13 @@ class Comment extends React.Component { }); AlertIOS.alert( 'Cannot unvote', - 'There was an error, please try again later.' + 'There was an error, please try again later.', ); } }); }; - upvoteComment = (id) => { + upvoteComment = id => { upvote(id).then(upvoted => { if (!upvoted) { this.props.removeIdFromUserAccount(id, 'upvotedComments'); @@ -111,7 +115,7 @@ class Comment extends React.Component { }); AlertIOS.alert( 'Cannot upvote', - 'There was an error, please try again later.' + 'There was an error, please try again later.', ); } }); @@ -120,45 +124,68 @@ class Comment extends React.Component { render() { const isUpvoted = this.checkIfUpvoted(); return ( - this.toggle(this.props.id, this.props.level)} activeOpacity={0.5}> - { !this.props.hidden && + this.toggle(this.props.id, this.props.level)} + activeOpacity={0.5} + > + {!this.props.hidden && ( - - 0 ? COMMENT_BORDER_WIDTH : 0, - borderLeftColor: this.props.level > 0 - ? config.commentThemes[this.props.settings.commentTheme][this.props.level - 1 % NUM_COLORS] - : 'transparent', - }]}> + + 0 ? COMMENT_BORDER_WIDTH : 0, + borderLeftColor: + this.props.level > 0 + ? config.commentThemes[ + this.props.settings.commentTheme + ][this.props.level - 1 % NUM_COLORS] + : 'transparent', + }, + ]} + > - {this.props.open && + {this.props.open && ( ${this.props.content}`} stylesheet={htmlStyles} - onLinkPress={(url) => this.openUrl(url)} + onLinkPress={url => this.openUrl(url)} /> - } + )} - } + )} ); } @@ -189,10 +216,10 @@ const styles = StyleSheet.create({ fontWeight: 'bold', }, iconArrowContainer: { - height: 20, + height: 20, width: 20, marginTop: 1, - padding: 2, + padding: 2, borderRadius: 5, marginLeft: 3, }, @@ -202,7 +229,8 @@ const styles = StyleSheet.create({ }, }); -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ settings: state.settings, @@ -210,7 +238,4 @@ const mapStateToProps = state => ({ accounts: state.accounts, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(Comment); +export default connect(mapStateToProps, mapDispatchToProps)(Comment); diff --git a/app/components/PostItem/Actions.js b/app/components/PostItem/Actions.js index 1e41ee0..2241c8d 100644 --- a/app/components/PostItem/Actions.js +++ b/app/components/PostItem/Actions.js @@ -15,9 +15,7 @@ import { bindActionCreators } from 'redux'; import { ActionCreators } from '../../actions'; import config from '../../config/default'; -import { - validateUserLoggedIn -} from '../../helpers/utils'; +import { validateUserLoggedIn } from '../../helpers/utils'; import { upvote } from '../../helpers/api'; import CustomText from '../CustomText'; @@ -46,13 +44,17 @@ class Actions extends React.Component { if (this.props.user.loggedIn) { if (this.props.accounts[this.props.user.username]) { if (this.props.accounts[this.props.user.username].upvoted) { - if (this.props.accounts[this.props.user.username].upvoted.indexOf(this.props.item.id) !== -1) { + if ( + this.props.accounts[this.props.user.username].upvoted.indexOf( + this.props.item.id, + ) !== -1 + ) { return true; } } } } - + return false; }; @@ -62,52 +64,54 @@ class Actions extends React.Component { if (this.props.user.loggedIn) { if (this.props.accounts[this.props.user.username]) { if (this.props.accounts[this.props.user.username].saved) { - if (this.props.accounts[this.props.user.username].saved.indexOf(this.props.item.id) !== -1) { + if ( + this.props.accounts[this.props.user.username].saved.indexOf( + this.props.item.id, + ) !== -1 + ) { saveOption = 'Unsave'; } } } } - const OPTIONS = [ - 'Cancel', - 'Reply', - 'Share', - saveOption, - ]; - - ActionSheetIOS.showActionSheetWithOptions({ - title: 'Post actions', - options: OPTIONS, - tintColor: this.props.settings.appColor, - cancelButtonIndex: 0, - }, (buttonIndex) => { - const selectedAction = OPTIONS[buttonIndex].toLowerCase(); - const { loggedIn } = this.props.user; - - // If 'Cancel' not pressed - if (buttonIndex !== 0) { - if (buttonIndex === 1) { - if (!validateUserLoggedIn(loggedIn, 'reply')) { - return; - } - } else if (buttonIndex === 2) { - this.share(); - } else if (buttonIndex === 3) { - if (!validateUserLoggedIn(loggedIn, 'save')) { - return; + const OPTIONS = ['Cancel', 'Reply', 'Share', saveOption]; + + ActionSheetIOS.showActionSheetWithOptions( + { + title: 'Post actions', + options: OPTIONS, + tintColor: this.props.settings.appColor, + cancelButtonIndex: 0, + }, + buttonIndex => { + const selectedAction = OPTIONS[buttonIndex].toLowerCase(); + const { loggedIn } = this.props.user; + + // If 'Cancel' not pressed + if (buttonIndex !== 0) { + if (buttonIndex === 1) { + if (!validateUserLoggedIn(loggedIn, 'reply')) { + return; + } + } else if (buttonIndex === 2) { + this.share(); + } else if (buttonIndex === 3) { + if (!validateUserLoggedIn(loggedIn, 'save')) { + return; + } + this.save(); } - this.save(); - }; - } - }); + } + }, + ); }; upvote = () => { if (!validateUserLoggedIn(this.props.user.loggedIn, 'upvote')) { return; } - ReactNativeHaptic.generate('impact') + ReactNativeHaptic.generate('impact'); this.props.upvotePost(this.props.item.id); }; @@ -120,7 +124,7 @@ class Actions extends React.Component { }; save = () => { - ReactNativeHaptic.generate('impact') + ReactNativeHaptic.generate('impact'); this.props.savePost(this.props.item.id); }; @@ -129,18 +133,33 @@ class Actions extends React.Component { return ( - + - + @@ -152,9 +171,9 @@ class Actions extends React.Component { const styles = StyleSheet.create({ iconArrowContainer: { - height: 28, - width: 28, - padding: 2, + height: 28, + width: 28, + padding: 2, borderRadius: 5, marginLeft: 3, }, @@ -185,7 +204,8 @@ const styles = StyleSheet.create({ }, }); -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ user: state.user, @@ -193,7 +213,4 @@ const mapStateToProps = state => ({ settings: state.settings, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(Actions); +export default connect(mapStateToProps, mapDispatchToProps)(Actions); diff --git a/app/components/PostItem/Comments.js b/app/components/PostItem/Comments.js index 611df1a..855c8ba 100644 --- a/app/components/PostItem/Comments.js +++ b/app/components/PostItem/Comments.js @@ -1,9 +1,5 @@ import React from 'react'; -import { - View, - StyleSheet, - Image, -} from 'react-native'; +import { View, StyleSheet, Image } from 'react-native'; import CustomText from '../CustomText'; @@ -21,9 +17,7 @@ export default class Comments extends React.PureComponent { source={require('../../img/comment.png')} /> - - {this.props.count > -1 ? this.props.count : 0} - + {this.props.count > -1 ? this.props.count : 0} ); } diff --git a/app/components/PostItem/Score.js b/app/components/PostItem/Score.js index fe28723..0deb40e 100644 --- a/app/components/PostItem/Score.js +++ b/app/components/PostItem/Score.js @@ -1,9 +1,5 @@ import React from 'react'; -import { - View, - StyleSheet, - Image, -} from 'react-native'; +import { View, StyleSheet, Image } from 'react-native'; import CustomText from '../CustomText'; @@ -16,14 +12,9 @@ export default class Score extends React.PureComponent { return ( - + - - {this.props.score} - + {this.props.score} ); } diff --git a/app/components/PostItem/SwipeContent.js b/app/components/PostItem/SwipeContent.js index 60175aa..42fd707 100644 --- a/app/components/PostItem/SwipeContent.js +++ b/app/components/PostItem/SwipeContent.js @@ -1,8 +1,5 @@ import React from 'react'; -import { - View, - Image, -} from 'react-native'; +import { View, Image } from 'react-native'; export default class SwipeContent extends React.PureComponent { constructor(props) { @@ -12,16 +9,18 @@ export default class SwipeContent extends React.PureComponent { render() { const { alignment, backgroundColor, image, size } = this.props; return ( - + diff --git a/app/components/PostItem/Time.js b/app/components/PostItem/Time.js index 9fc7524..2c2f014 100644 --- a/app/components/PostItem/Time.js +++ b/app/components/PostItem/Time.js @@ -1,9 +1,5 @@ import React from 'react'; -import { - View, - StyleSheet, - Image, -} from 'react-native'; +import { View, StyleSheet, Image } from 'react-native'; import TimeAgo from 'javascript-time-ago'; import en from 'javascript-time-ago/locale/en'; @@ -21,13 +17,12 @@ export default class Time extends React.PureComponent { return ( - + - {timeAgo.format(new Date(this.props.time * 1000), { flavour: 'tiny' })} + {timeAgo.format(new Date(this.props.time * 1000), { + flavour: 'tiny', + })} ); diff --git a/app/components/PostItem/User.js b/app/components/PostItem/User.js index 3fe0a38..16aaf6b 100644 --- a/app/components/PostItem/User.js +++ b/app/components/PostItem/User.js @@ -1,8 +1,5 @@ import React from 'react'; -import { - View, - StyleSheet, -} from 'react-native'; +import { View, StyleSheet } from 'react-native'; import CustomText from '../CustomText'; @@ -14,11 +11,8 @@ export default class User extends React.PureComponent { render() { return ( - - - - {this.props.by} - + + {this.props.by} ); } diff --git a/app/components/PostList.js b/app/components/PostList.js index 86be80f..e01eef9 100644 --- a/app/components/PostList.js +++ b/app/components/PostList.js @@ -11,10 +11,7 @@ import Swipeable from 'react-native-swipeable'; import ReactNativeHaptic from 'react-native-haptic'; import config from '../config/default'; -import { - truncate, - validateUserLoggedIn -} from '../helpers/utils'; +import { truncate, validateUserLoggedIn } from '../helpers/utils'; import ListItem from './ListItem'; import SwipeContent from './PostItem/SwipeContent'; @@ -29,11 +26,12 @@ export default class Posts extends React.Component { this.doSave = this.doSave.bind(this); } - showPost = (post) => { - const title = post.descendants > -1 - ? `${post.descendants} comments` - : truncate(post.title, 20); - + showPost = post => { + const title = + post.descendants > -1 + ? `${post.descendants} comments` + : truncate(post.title, 20); + this.props.navigator.push({ screen: 'hackd.Post', title, @@ -43,14 +41,15 @@ export default class Posts extends React.Component { }); }; - showPostPreview = (post) => { + showPostPreview = post => { if (this.state.isSwiping) { return; - }; + } - const title = post.descendants > -1 - ? `${post.descendants} comments` - : truncate(post.title, 20); + const title = + post.descendants > -1 + ? `${post.descendants} comments` + : truncate(post.title, 20); this.props.navigator.push({ screen: 'hackd.Post', @@ -61,17 +60,20 @@ export default class Posts extends React.Component { previewCommit: true, previewHeight: 400, previewView: this.previewRefs[post.id.toString()], - previewActions: [{ - id: 'action-upvote', - title: 'Upvote', - }, { - id: 'action-save', - title: 'Save', - }] + previewActions: [ + { + id: 'action-upvote', + title: 'Upvote', + }, + { + id: 'action-save', + title: 'Save', + }, + ], }); }; - renderSeparator = () => (); + renderSeparator = () => ; renderFooter = () => { if (!this.props.loadingMore) { @@ -115,20 +117,20 @@ export default class Posts extends React.Component { this.props.onEndReached(); }; - setActivated = (activated) => { + setActivated = activated => { if (activated) { - ReactNativeHaptic.generate('impact') + ReactNativeHaptic.generate('impact'); } }; - doUpvote = (id) => { + doUpvote = id => { if (!validateUserLoggedIn(this.props.user.loggedIn, 'upvote')) { return; } this.props.upvotePost(id); }; - doSave = (id) => { + doSave = id => { if (!validateUserLoggedIn(this.props.user.loggedIn, 'save')) { return; } @@ -152,7 +154,7 @@ export default class Posts extends React.Component { data={this.props.data} extraData={this.props.loadingMore} scrollEnabled={!this.state.isSwiping} - keyboardShouldPersistTaps='always' + keyboardShouldPersistTaps="always" keyExtractor={(item, index) => index.toString()} ItemSeparatorComponent={this.renderSeparator} ListFooterComponent={this.renderFooter} @@ -161,21 +163,21 @@ export default class Posts extends React.Component { onEndReached={this.onEndReached} onEndReachedThreshold={0.5} renderItem={({ item }) => ( - this.setActivated(true)} onRightActionRelease={() => this.doUpvote(item.id)} onLeftActionActivate={() => this.setActivated(true)} onLeftActionRelease={() => this.doSave(item.id)} - onSwipeStart={() => this.setState({isSwiping: true})} - onSwipeRelease={() => this.setState({isSwiping: false})} + onSwipeStart={() => this.setState({ isSwiping: true })} + onSwipeRelease={() => this.setState({ isSwiping: false })} > (this.previewRefs[item.id.toString()] = ref)} + ref={ref => (this.previewRefs[item.id.toString()] = ref)} onPress={() => this.showPost(item)} onPressIn={() => this.showPostPreview(item)} navigator={this.props.navigator} diff --git a/app/components/Settings/Circles.js b/app/components/Settings/Circles.js index 3b4185e..a8ca4e9 100644 --- a/app/components/Settings/Circles.js +++ b/app/components/Settings/Circles.js @@ -1,8 +1,5 @@ import React from 'react'; -import { - View, - StyleSheet, -} from 'react-native'; +import { View, StyleSheet } from 'react-native'; export default class Circles extends React.Component { constructor(props) { @@ -12,16 +9,19 @@ export default class Circles extends React.Component { render() { return ( - {Object.keys(this.props.data).map(key => - ( + - )} + ))} ); } diff --git a/app/config/router.js b/app/config/router.js index 9835d85..4e5c893 100644 --- a/app/config/router.js +++ b/app/config/router.js @@ -22,14 +22,34 @@ function registerScreens(store, Provider) { Navigation.registerComponent('hackd.Account', () => Account, store, Provider); Navigation.registerComponent('hackd.Search', () => Search, store, Provider); Navigation.registerComponent('hackd.Submit', () => Submit, store, Provider); - Navigation.registerComponent('hackd.Settings', () => Settings, store, Provider); + Navigation.registerComponent( + 'hackd.Settings', + () => Settings, + store, + Provider, + ); Navigation.registerComponent('hackd.Saved', () => Saved, store, Provider); Navigation.registerComponent('hackd.Login', () => Login, store, Provider); - Navigation.registerComponent('hackd.AccountDetails', () => AccountDetails, store, Provider); + Navigation.registerComponent( + 'hackd.AccountDetails', + () => AccountDetails, + store, + Provider, + ); - Navigation.registerComponent('hackd.CommentThemes', () => CommentThemes, store, Provider); - Navigation.registerComponent('hackd.AppColors', () => AppColors, store, Provider); + Navigation.registerComponent( + 'hackd.CommentThemes', + () => CommentThemes, + store, + Provider, + ); + Navigation.registerComponent( + 'hackd.AppColors', + () => AppColors, + store, + Provider, + ); } const hackd = [ @@ -70,7 +90,4 @@ const hackd = [ }, ]; -export { - registerScreens, - hackd, -}; +export { registerScreens, hackd }; diff --git a/app/config/store.js b/app/config/store.js index 0c6e953..6a566b3 100644 --- a/app/config/store.js +++ b/app/config/store.js @@ -1,8 +1,4 @@ -import { - createStore, - applyMiddleware, - compose, -} from 'redux'; +import { createStore, applyMiddleware, compose } from 'redux'; import { createLogger } from 'redux-logger'; import thunkMiddleware from 'redux-thunk'; import { persistStore, persistReducer } from 'redux-persist'; @@ -15,31 +11,20 @@ const persistConfig = { key: 'root', storage, stateReconciler: autoMergeLevel2, - whitelist: [ - 'user', - 'accounts', - 'settings', - ], -} + whitelist: ['user', 'accounts', 'settings'], +}; const persistedReducer = persistReducer(persistConfig, reducer); const loggerMiddleware = createLogger({ - predicate: (getState, action) => __DEV__ + predicate: (getState, action) => __DEV__, }); -const configureStore = (initialState) => { - const enhancer = compose( - applyMiddleware( - thunkMiddleware, - loggerMiddleware, - ), - ); +const configureStore = initialState => { + const enhancer = compose(applyMiddleware(thunkMiddleware, loggerMiddleware)); return createStore(persistedReducer, initialState, enhancer); }; const store = configureStore({}); -export { - store, -}; +export { store }; diff --git a/app/helpers/api.js b/app/helpers/api.js index ea122bb..dae3725 100644 --- a/app/helpers/api.js +++ b/app/helpers/api.js @@ -59,8 +59,7 @@ const getItems = (page, limit, itemIds) => { // Map each itemId to an Array of Promises const posts = slicedItems.map(item => { - return getItem(item) - .then(post => post); + return getItem(item).then(post => post); }); return posts; @@ -88,7 +87,8 @@ const getUpvoteUrl = itemId => { return fetch(`${config.base}/item?id=${itemId}`, { mode: 'no-cors', credentials: 'include', - }).then(response => response.text()) + }) + .then(response => response.text()) .then(responseText => { const document = cheerio.load(responseText); return document(`#up_${itemId}`).attr('href'); @@ -105,7 +105,8 @@ const getUnvoteUrl = itemId => { return fetch(`${config.base}/item?id=${itemId}`, { mode: 'no-cors', credentials: 'include', - }).then(response => response.text()) + }) + .then(response => response.text()) .then(responseText => { const document = cheerio.load(responseText); return document(`#un_${itemId}`).attr('href'); @@ -120,10 +121,12 @@ const getUnvoteUrl = itemId => { */ const upvote = itemId => { return getUpvoteUrl(itemId) - .then(upvoteUrl => fetch(`${config.base}/${upvoteUrl}`, { - mode: 'no-cors', - credentials: 'include', - })) + .then(upvoteUrl => + fetch(`${config.base}/${upvoteUrl}`, { + mode: 'no-cors', + credentials: 'include', + }), + ) .then(response => response.text()) .then(responseText => true) .catch(error => false); @@ -137,10 +140,12 @@ const upvote = itemId => { */ const unvote = itemId => { return getUnvoteUrl(itemId) - .then(upvoteUrl => fetch(`${config.base}/${upvoteUrl}`, { - mode: 'no-cors', - credentials: 'include', - })) + .then(upvoteUrl => + fetch(`${config.base}/${upvoteUrl}`, { + mode: 'no-cors', + credentials: 'include', + }), + ) .then(response => response.text()) .then(responseText => true) .catch(error => false); @@ -165,7 +170,8 @@ const login = (username, password) => { body: `acct=${username}&pw=${password}&goto=news`, mode: 'no-cors', credentials: 'include', - }).then(response => response.text()) + }) + .then(response => response.text()) .then(responseText => { if (responseText.match(/Bad Login/i)) { return false; @@ -183,7 +189,8 @@ const getLogoutUrl = () => { return fetch(`${config.base}/news`, { mode: 'no-cors', credentials: 'include', - }).then(response => response.text()) + }) + .then(response => response.text()) .then(responseText => { const document = cheerio.load(responseText); return document('#logout').attr('href'); @@ -197,12 +204,15 @@ const getLogoutUrl = () => { */ const logout = () => { return getLogoutUrl() - .then(logoutUrl => fetch(`${config.base}/${logoutUrl}`, { - mode: 'no-cors', - credentials: 'include', - })).then(response => response.text()) - .then(responseText => true) - .catch(error => false); + .then(logoutUrl => + fetch(`${config.base}/${logoutUrl}`, { + mode: 'no-cors', + credentials: 'include', + }), + ) + .then(response => response.text()) + .then(responseText => true) + .catch(error => false); }; /** @@ -215,7 +225,8 @@ const getCommentUrl = itemId => { return fetch(`${config.base}/item?id=${itemId}`, { mode: 'no-cors', credentials: 'include', - }).then(response => response.text()) + }) + .then(response => response.text()) .then(responseText => { const document = cheerio.load(responseText); return document('input[name=hmac]').attr('value'); @@ -230,20 +241,22 @@ const getCommentUrl = itemId => { * resolves true if commented, else false */ const comment = (itemId, reply) => { - return getCommentUrl(itemId).then(commentUrl => { - const headers = new Headers({ - 'Content-Type': 'application/x-www-form-urlencoded', - 'Access-Control-Allow-Origin': '*', - }); + return getCommentUrl(itemId) + .then(commentUrl => { + const headers = new Headers({ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Access-Control-Allow-Origin': '*', + }); - return fetch(`${config.base}/comment`, { - method: 'POST', - headers, - body: `parent=${itemId}&goto=item?id=${itemId}&hmac=${commentUrl}&text=${reply}`, - mode: 'no-cors', - credentials: 'include', - }); - }).then(response => response.text()) + return fetch(`${config.base}/comment`, { + method: 'POST', + headers, + body: `parent=${itemId}&goto=item?id=${itemId}&hmac=${commentUrl}&text=${reply}`, + mode: 'no-cors', + credentials: 'include', + }); + }) + .then(response => response.text()) .then(responseText => true) .catch(error => false); }; @@ -260,7 +273,11 @@ const flatten = (comments, commentsArray) => { for (var key in comments) { const currentComment = comments[key]; - if (comments.hasOwnProperty(key) && !currentComment.dead && !currentComment.deleted) { + if ( + comments.hasOwnProperty(key) && + !currentComment.dead && + !currentComment.deleted + ) { commentsArray.push(currentComment); if (currentComment.kids && currentComment.kids.length > 0) { flatten(currentComment.kids, commentsArray); @@ -279,10 +296,9 @@ const flatten = (comments, commentsArray) => { const getComments = commentIds => { return new Promise((resolve, reject) => { const comments = commentIds.map(id => { - return getChildComment(id, 0) - .then(comment => { - return comment; - }); + return getChildComment(id, 0).then(comment => { + return comment; + }); }); Promise.all(comments) @@ -312,16 +328,14 @@ const getChildComment = (parentId, level) => { if (comment.kids && comment.kids.length > 0) { const results = comment.kids.map(id => { - return getChildComment(id, level + 1) - .then(c => { - return c; - }); + return getChildComment(id, level + 1).then(c => { + return c; + }); }); - Promise.all(results) - .then(kids => { - resolve({ ...comment, kids }); - }); + Promise.all(results).then(kids => { + resolve({ ...comment, kids }); + }); } else { resolve(comment); } @@ -383,16 +397,16 @@ const toggleComments = (comments, id, level) => { return comments; }; -export { +export { getItem, getItems, upvote, unvote, login, - comment, + comment, getUser, logout, getComments, toggleComments, searchPost, -}; \ No newline at end of file +}; diff --git a/app/helpers/createReducer.js b/app/helpers/createReducer.js index b1d3eed..45453a6 100644 --- a/app/helpers/createReducer.js +++ b/app/helpers/createReducer.js @@ -1,10 +1,10 @@ // https://github.com/reactjs/redux/blob/master/docs/recipes/ReducingBoilerplate.md -export default createReducer = (initialState, handlers) => { - return reducer = (state = initialState, action) => { +export default (createReducer = (initialState, handlers) => { + return (reducer = (state = initialState, action) => { if (handlers.hasOwnProperty(action.type)) { return handlers[action.type](state, action); } else { return state; } - }; -}; \ No newline at end of file + }); +}); diff --git a/app/helpers/utils.js b/app/helpers/utils.js index 8d394ae..6a92975 100644 --- a/app/helpers/utils.js +++ b/app/helpers/utils.js @@ -1,13 +1,8 @@ -import { - AlertIOS, -} from 'react-native'; +import { AlertIOS } from 'react-native'; const validateUserLoggedIn = (user, action) => { if (!user) { - AlertIOS.alert( - `Cannot ${action}`, - 'Please login and try again.', - ); + AlertIOS.alert(`Cannot ${action}`, 'Please login and try again.'); return false; } return true; diff --git a/app/reducers/auth.js b/app/reducers/auth.js index c3e33d9..7133144 100644 --- a/app/reducers/auth.js +++ b/app/reducers/auth.js @@ -1,14 +1,20 @@ import createReducer from '../helpers/createReducer'; import * as types from '../actions/types'; -export const user = createReducer({}, { - [types.UPDATE_USER](state, action) { - return action.user; +export const user = createReducer( + {}, + { + [types.UPDATE_USER](state, action) { + return action.user; + }, }, -}); +); -export const accounts = createReducer({}, { - [types.SET_ACCOUNTS_DETAILS](state, action) { - return action.accounts; +export const accounts = createReducer( + {}, + { + [types.SET_ACCOUNTS_DETAILS](state, action) { + return action.accounts; + }, }, -}); +); diff --git a/app/reducers/index.js b/app/reducers/index.js index defde61..913a0d7 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -3,8 +3,6 @@ import * as itemsReducer from './items'; import * as authReducer from './auth'; import * as settingsReducer from './settings'; -export default combineReducers(Object.assign( - itemsReducer, - authReducer, - settingsReducer, -)); +export default combineReducers( + Object.assign(itemsReducer, authReducer, settingsReducer), +); diff --git a/app/reducers/items.js b/app/reducers/items.js index 33b4e3c..b08ae12 100644 --- a/app/reducers/items.js +++ b/app/reducers/items.js @@ -7,15 +7,18 @@ export const storyType = createReducer('top', { }, }); -export const posts = createReducer({}, { - [types.SET_POSTS](state, action) { - return action.posts; - }, - // When storyType is set, clear out posts - [types.SET_STORY_TYPE](state, action) { - return {}; +export const posts = createReducer( + {}, + { + [types.SET_POSTS](state, action) { + return action.posts; + }, + // When storyType is set, clear out posts + [types.SET_STORY_TYPE](state, action) { + return {}; + }, }, -}); +); export const isLoadingPosts = createReducer(true, { // When storyType is set, posts are loading diff --git a/app/reducers/settings.js b/app/reducers/settings.js index 6bfd24d..9728fa1 100644 --- a/app/reducers/settings.js +++ b/app/reducers/settings.js @@ -4,13 +4,16 @@ import * as types from '../actions/types'; import config from '../config/default'; // Default settings -export const settings = createReducer({ - tapToCollapse: true, - useSafariReaderMode: false, - commentTheme: 'raw', - appColor: config.colors.blue, -}, { - [types.SET_SETTINGS](state, action) { - return action.settings; +export const settings = createReducer( + { + tapToCollapse: true, + useSafariReaderMode: false, + commentTheme: 'raw', + appColor: config.colors.blue, }, -}); + { + [types.SET_SETTINGS](state, action) { + return action.settings; + }, + }, +); diff --git a/app/screens/Account.js b/app/screens/Account.js index c372727..b607231 100644 --- a/app/screens/Account.js +++ b/app/screens/Account.js @@ -21,21 +21,15 @@ class Account extends React.Component { /> ); } - return ( - - ); + return ; } } -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ user: state.user, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(Account); +export default connect(mapStateToProps, mapDispatchToProps)(Account); diff --git a/app/screens/Auth/AccountDetails.js b/app/screens/Auth/AccountDetails.js index 6395f40..36c395e 100644 --- a/app/screens/Auth/AccountDetails.js +++ b/app/screens/Auth/AccountDetails.js @@ -1,10 +1,6 @@ import React from 'react'; import commonStyles from '../../styles/common'; -import { - Text, - View, - StyleSheet, -} from 'react-native'; +import { Text, View, StyleSheet } from 'react-native'; import TableView from 'react-native-tableview'; import TimeAgo from 'javascript-time-ago'; import en from 'javascript-time-ago/locale/en'; @@ -30,7 +26,7 @@ class AccountDetails extends React.Component { karma: null, about: null, submitted: null, - userExists: false + userExists: false, }; } @@ -58,63 +54,84 @@ class AccountDetails extends React.Component { }; navigateToSaved = () => { - const saved = this.props.accounts[this.props.user.username] + const saved = this.props.accounts[this.props.user.username] ? this.props.accounts[this.props.user.username].saved - : [] + : []; this.props.navigator.push({ screen: 'hackd.Saved', title: 'Saved', passProps: { saved, - } + }, }); }; render() { return ( - {this.state.userExists && + {this.state.userExists && ( - + {this.state.karma} - + KARMA - - {timeAgo.format(new Date(this.state.created * 1000), { flavour: 'tiny' })} + + {timeAgo.format(new Date(this.state.created * 1000), { + flavour: 'tiny', + })} - + AGE - } + )} - -
+ tableViewStyle={TableView.Consts.Style.Grouped} + tableViewCellStyle={TableView.Consts.CellStyle.Value1} + > +
Username - {this.state.username} + + {this.state.username} + - this.navigateToSaved()}> + this.navigateToSaved()} + > Saved
- this.props.logOut()}> - Logout + this.props.logOut()} + > + + Logout +
@@ -143,21 +160,19 @@ const styles = StyleSheet.create({ fontSize: 12, }, tableFlex: { - flex: 0, + flex: 0, }, logout: { color: 'red', }, }); -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ user: state.user, accounts: state.accounts, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(AccountDetails); +export default connect(mapStateToProps, mapDispatchToProps)(AccountDetails); diff --git a/app/screens/Auth/Login.js b/app/screens/Auth/Login.js index 35963f6..845acd9 100644 --- a/app/screens/Auth/Login.js +++ b/app/screens/Auth/Login.js @@ -1,9 +1,6 @@ import React from 'react'; import commonStyles from '../../styles/common'; -import { - View, - StyleSheet, -} from 'react-native'; +import { View, StyleSheet } from 'react-native'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; @@ -24,16 +21,16 @@ class Login extends React.Component { }; } - handleUsername = (text) => { - this.setState({ username: text, error: null, }); + handleUsername = text => { + this.setState({ username: text, error: null }); }; - handlePassword = (text) => { - this.setState({ password: text, error: null, }); + handlePassword = text => { + this.setState({ password: text, error: null }); }; logIn = () => { - this.setState({ loading: true, error: null, }); + this.setState({ loading: true, error: null }); const { username, password } = this.state; login(username, password).then(loggedIn => { @@ -49,9 +46,9 @@ class Login extends React.Component { const user = { loggedIn: false, }; - this.setState({ + this.setState({ loading: false, - error: 'Invalid username or password. Please try again.' + error: 'Invalid username or password. Please try again.', }); this.props.login(user); } @@ -67,22 +64,28 @@ class Login extends React.Component { render() { return ( - Login to Hacker News + + Login to Hacker News +
- + ); } } @@ -100,14 +103,12 @@ const styles = StyleSheet.create({ }, }); -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ user: state.user, settings: state.settings, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(Login); +export default connect(mapStateToProps, mapDispatchToProps)(Login); diff --git a/app/screens/Auth/Saved.js b/app/screens/Auth/Saved.js index 83d8c44..9229344 100644 --- a/app/screens/Auth/Saved.js +++ b/app/screens/Auth/Saved.js @@ -19,7 +19,7 @@ export default class Saved extends React.Component { componentWillMount() { this.setState({ - savedIds: this.props.saved + savedIds: this.props.saved, }); } @@ -45,11 +45,14 @@ export default class Saved extends React.Component { }; handleRefresh = () => { - this.setState({ - refreshing: true, - }, () => { - this.fetchSavedPosts(); - }); + this.setState( + { + refreshing: true, + }, + () => { + this.fetchSavedPosts(); + }, + ); }; render() { @@ -67,8 +70,10 @@ export default class Saved extends React.Component { navigator={this.props.navigator} refreshing={this.state.refreshing} onRefresh={() => this.handleRefresh()} - onEndReached={() => {return}} + onEndReached={() => { + return; + }} /> - ) + ); } } diff --git a/app/screens/Post.js b/app/screens/Post.js index e869271..830311a 100644 --- a/app/screens/Post.js +++ b/app/screens/Post.js @@ -44,11 +44,11 @@ class Post extends React.Component { } } - openUrl = (url) => { - const readerMode = this.props.settings.useSafariReaderMode + openUrl = url => { + const readerMode = this.props.settings.useSafariReaderMode; SafariView.show({ url, - readerMode + readerMode, }); }; @@ -57,12 +57,19 @@ class Post extends React.Component { - {this.props.post.title} + + {this.props.post.title} + - {this.props.post.url && - this.openUrl(this.props.post.url)} activeOpacity={0.8}> + {this.props.post.url && ( + this.openUrl(this.props.post.url)} + activeOpacity={0.8} + > - {truncate(this.props.post.url, 35)} + + {truncate(this.props.post.url, 35)} + - } - {this.props.post.text && + )} + {this.props.post.text && ( ${this.props.post.text}`} stylesheet={htmlStyles} - onLinkPress={(url) => this.openUrl(url)} + onLinkPress={url => this.openUrl(url)} /> - } + )} - - - - ); @@ -183,15 +178,11 @@ const styles = StyleSheet.create({ }, }); - -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ settings: state.settings, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(Post); - +export default connect(mapStateToProps, mapDispatchToProps)(Post); diff --git a/app/screens/Posts.js b/app/screens/Posts.js index bb6e8e1..ef88ad8 100644 --- a/app/screens/Posts.js +++ b/app/screens/Posts.js @@ -1,9 +1,7 @@ import React from 'react'; import config from '../config/default'; import commonStyles from '../styles/common'; -import { - ActionSheetIOS, -} from 'react-native'; +import { ActionSheetIOS } from 'react-native'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; @@ -31,14 +29,16 @@ class Posts extends React.Component { } static navigatorButtons = { - rightButtons: [{ - icon: require('../img/list.png'), - id: 'selectFeed' - }], + rightButtons: [ + { + icon: require('../img/list.png'), + id: 'selectFeed', + }, + ], }; onNavigatorEvent(event) { - if (event.type == 'NavBarButtonPress') { + if (event.type == 'NavBarButtonPress') { if (event.id == 'selectFeed') { this.selectFeed(); } @@ -56,32 +56,27 @@ class Posts extends React.Component { // The storyType is updated in the store // And the posts are reloaded selectFeed = () => { - const OPTIONS = [ - 'Cancel', - 'Top', - 'New', - 'Best', - 'Ask', - 'Show', - 'Jobs', - ]; - ActionSheetIOS.showActionSheetWithOptions({ - title: 'Select a feed', - options: OPTIONS, - tintColor: this.props.settings.appColor, - cancelButtonIndex: 0, - }, (buttonIndex) => { - const selectedFeed = OPTIONS[buttonIndex].toLowerCase(); - - // If 'Cancel' not pressed and selectedFeed isnt current storyType - if (buttonIndex !== 0 && selectedFeed !== this.props.storyType) { - this.props.navigator.setTitle({ - title: capitalize(selectedFeed), - }); - this.props.setStoryType(selectedFeed); - this.makeRequest(); - } - }); + const OPTIONS = ['Cancel', 'Top', 'New', 'Best', 'Ask', 'Show', 'Jobs']; + ActionSheetIOS.showActionSheetWithOptions( + { + title: 'Select a feed', + options: OPTIONS, + tintColor: this.props.settings.appColor, + cancelButtonIndex: 0, + }, + buttonIndex => { + const selectedFeed = OPTIONS[buttonIndex].toLowerCase(); + + // If 'Cancel' not pressed and selectedFeed isnt current storyType + if (buttonIndex !== 0 && selectedFeed !== this.props.storyType) { + this.props.navigator.setTitle({ + title: capitalize(selectedFeed), + }); + this.props.setStoryType(selectedFeed); + this.makeRequest(); + } + }, + ); }; // Get posts based on storyType @@ -94,8 +89,8 @@ class Posts extends React.Component { } return fetch(`${config.api}/${storyType}stories.json`) - .then((response) => response.json()) - .then((responseJson) => { + .then(response => response.json()) + .then(responseJson => { const posts = getItems(page, limit, responseJson); // Wait for all Promises to complete @@ -111,18 +106,21 @@ class Posts extends React.Component { return; }); }) - .catch((error) => { + .catch(error => { return; }); }; handleRefresh = () => { - this.setState({ - page: 1, - refreshing: true, - }, () => { - this.makeRequest(); - }); + this.setState( + { + page: 1, + refreshing: true, + }, + () => { + this.makeRequest(); + }, + ); }; handleEndReached = () => { @@ -132,19 +130,22 @@ class Posts extends React.Component { return; } - this.setState({ - page: page + 1, - loadingMorePosts: true, - }, () => { - this.makeRequest(); - }); + this.setState( + { + page: page + 1, + loadingMorePosts: true, + }, + () => { + this.makeRequest(); + }, + ); }; - upvotePost = (id) => { + upvotePost = id => { this.props.upvotePost(id); }; - savePost = (id) => { + savePost = id => { this.props.savePost(id); }; @@ -162,11 +163,12 @@ class Posts extends React.Component { upvotePost={this.upvotePost} savePost={this.savePost} /> - ) + ); } } -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ storyType: state.storyType, @@ -176,7 +178,4 @@ const mapStateToProps = state => ({ user: state.user, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(Posts); +export default connect(mapStateToProps, mapDispatchToProps)(Posts); diff --git a/app/screens/Search.js b/app/screens/Search.js index e5bd0aa..7086024 100644 --- a/app/screens/Search.js +++ b/app/screens/Search.js @@ -107,4 +107,4 @@ const styles = StyleSheet.create({ marginRight: 10, alignSelf: 'flex-end', }, -}); \ No newline at end of file +}); diff --git a/app/screens/Settings.js b/app/screens/Settings.js index 6ca3132..7a58e57 100644 --- a/app/screens/Settings.js +++ b/app/screens/Settings.js @@ -1,13 +1,7 @@ import React from 'react'; import config from '../config/default'; import commonStyles from '../styles/common'; -import { - Text, - View, - StyleSheet, - Switch, - ActionSheetIOS, -} from 'react-native'; +import { Text, View, StyleSheet, Switch, ActionSheetIOS } from 'react-native'; import TableView from 'react-native-tableview'; import { connect } from 'react-redux'; @@ -25,7 +19,7 @@ class Settings extends React.Component { constructor(props) { super(props); } - + navigateTo = (screen, title) => { this.props.navigator.push({ screen, @@ -34,74 +28,95 @@ class Settings extends React.Component { }; keyValueChanged = (key, value) => { - this.props.changeSetting( - key, - value - ); + this.props.changeSetting(key, value); }; booleanChanged = (key, boolean) => { - this.props.changeSetting( - key, - !boolean - ); + this.props.changeSetting(key, !boolean); }; multiChange = (key, options, title) => { - ActionSheetIOS.showActionSheetWithOptions({ - title, - options, - tintColor: this.props.settings.appColor, - cancelButtonIndex: 0, - }, (buttonIndex) => { - const selectedAction = options[buttonIndex].toLowerCase(); + ActionSheetIOS.showActionSheetWithOptions( + { + title, + options, + tintColor: this.props.settings.appColor, + cancelButtonIndex: 0, + }, + buttonIndex => { + const selectedAction = options[buttonIndex].toLowerCase(); - if (buttonIndex !== 0) { - this.props.changeSetting( - key, - selectedAction, - ); - } - }); + if (buttonIndex !== 0) { + this.props.changeSetting(key, selectedAction); + } + }, + ); }; render() { return ( - -
+ tableViewStyle={TableView.Consts.Style.Grouped} + tableViewCellStyle={TableView.Consts.CellStyle.Value1} + > +
- Tap to collapse + + Tap to collapse + this.booleanChanged('tapToCollapse', this.props.settings.tapToCollapse)} /> + onValueChange={() => + this.booleanChanged( + 'tapToCollapse', + this.props.settings.tapToCollapse, + ) + } + /> - this.navigateTo('hackd.CommentThemes', 'Comment Themes')}> - Comment themes + onPress={() => + this.navigateTo('hackd.CommentThemes', 'Comment Themes') + } + > + + Comment themes +
-
+
- Use Safari Reader Mode + + Use Safari Reader Mode + this.booleanChanged('useSafariReaderMode', this.props.settings.useSafariReaderMode)} /> + onValueChange={() => + this.booleanChanged( + 'useSafariReaderMode', + this.props.settings.useSafariReaderMode, + ) + } + />
-
- + this.navigateTo('hackd.AppColors', 'Navigator Themes')}> - Navigator themes + onPress={() => + this.navigateTo('hackd.AppColors', 'Navigator Themes') + } + > + + Navigator themes +
@@ -110,13 +125,11 @@ class Settings extends React.Component { } } -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ settings: state.settings, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(Settings); +export default connect(mapStateToProps, mapDispatchToProps)(Settings); diff --git a/app/screens/Settings/AppColors.js b/app/screens/Settings/AppColors.js index be6a76a..5411662 100644 --- a/app/screens/Settings/AppColors.js +++ b/app/screens/Settings/AppColors.js @@ -1,8 +1,5 @@ import React from 'react'; -import { - View, - StyleSheet, -} from 'react-native'; +import { View, StyleSheet } from 'react-native'; import TableView from 'react-native-tableview'; import ReactNativeHaptic from 'react-native-haptic'; @@ -23,34 +20,39 @@ class AppColors extends React.Component { super(props); } - appColorThemeChanged = (color) => { - ReactNativeHaptic.generate('selection') - this.props.changeSetting( - 'appColor', - color - ); + appColorThemeChanged = color => { + ReactNativeHaptic.generate('selection'); + this.props.changeSetting('appColor', color); }; render() { - const appColor = this.props.settings.appColor + const appColor = this.props.settings.appColor; return ( - -
- {Object.keys(config.appColors).map(key => - +
+ {Object.keys(config.appColors).map(key => ( + this.appColorThemeChanged(config.appColors[key])}> - {capitalize(key)} + style={styles.cell} + accessoryType={ + appColor == config.appColors[key] + ? TableView.Consts.AccessoryType.Checkmark + : '' + } + onPress={() => this.appColorThemeChanged(config.appColors[key])} + > + + {capitalize(key)} + - )} + ))}
@@ -69,13 +71,11 @@ const styles = StyleSheet.create({ }, }); -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ settings: state.settings, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(AppColors); +export default connect(mapStateToProps, mapDispatchToProps)(AppColors); diff --git a/app/screens/Settings/CommentThemes.js b/app/screens/Settings/CommentThemes.js index ccacf97..51ffa86 100644 --- a/app/screens/Settings/CommentThemes.js +++ b/app/screens/Settings/CommentThemes.js @@ -1,8 +1,5 @@ import React from 'react'; -import { - View, - StyleSheet, -} from 'react-native'; +import { View, StyleSheet } from 'react-native'; import TableView from 'react-native-tableview'; import ReactNativeHaptic from 'react-native-haptic'; @@ -23,34 +20,39 @@ class CommentThemes extends React.Component { super(props); } - commentThemeChanged = (theme) => { - ReactNativeHaptic.generate('selection') - this.props.changeSetting( - 'commentTheme', - theme - ); + commentThemeChanged = theme => { + ReactNativeHaptic.generate('selection'); + this.props.changeSetting('commentTheme', theme); }; render() { - const selectedTheme = this.props.settings.commentTheme + const selectedTheme = this.props.settings.commentTheme; return ( - -
- {Object.keys(config.commentThemes).map(key => - +
+ {Object.keys(config.commentThemes).map(key => ( + this.commentThemeChanged(key)}> - {capitalize(key)} + style={styles.cell} + accessoryType={ + selectedTheme == key + ? TableView.Consts.AccessoryType.Checkmark + : '' + } + onPress={() => this.commentThemeChanged(key)} + > + + {capitalize(key)} + - )} + ))}
@@ -69,13 +71,11 @@ const styles = StyleSheet.create({ }, }); -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ settings: state.settings, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(CommentThemes); +export default connect(mapStateToProps, mapDispatchToProps)(CommentThemes); diff --git a/app/screens/Submit.js b/app/screens/Submit.js index 6c0d7ca..ea080d1 100644 --- a/app/screens/Submit.js +++ b/app/screens/Submit.js @@ -1,9 +1,5 @@ import React from 'react'; -import { - View, - StyleSheet, - SegmentedControlIOS, -} from 'react-native'; +import { View, StyleSheet, SegmentedControlIOS } from 'react-native'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; @@ -32,16 +28,16 @@ class Submit extends React.Component { navBarNoBorder: true, }; - handleTitle = (title) => { - this.setState({ title, error: null, }); + handleTitle = title => { + this.setState({ title, error: null }); }; - handleUrl = (url) => { - this.setState({ url, error: null, }); + handleUrl = url => { + this.setState({ url, error: null }); }; - handleText = (text) => { - this.setState({ text, error: null, }); + handleText = text => { + this.setState({ text, error: null }); }; submitUrl = () => { @@ -63,20 +59,21 @@ class Submit extends React.Component { render() { return ( - { - this.setState({selectedIndex: event.nativeEvent.selectedSegmentIndex}); + onChange={event => { + this.setState({ + selectedIndex: event.nativeEvent.selectedSegmentIndex, + }); }} /> - {this.state.selectedIndex === 0 && + {this.state.selectedIndex === 0 && ( - } + )} - {this.state.selectedIndex === 1 && + {this.state.selectedIndex === 1 && ( - } - + )} ); } @@ -125,13 +121,11 @@ const styles = StyleSheet.create({ }, }); -const mapDispatchToProps = dispatch => bindActionCreators(ActionCreators, dispatch); +const mapDispatchToProps = dispatch => + bindActionCreators(ActionCreators, dispatch); const mapStateToProps = state => ({ settings: state.settings, }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(Submit); +export default connect(mapStateToProps, mapDispatchToProps)(Submit); diff --git a/app/styles/common.js b/app/styles/common.js index 6c405e2..5ac42ef 100644 --- a/app/styles/common.js +++ b/app/styles/common.js @@ -42,7 +42,7 @@ export default StyleSheet.create({ fontSize: 17, }, cellValue: { - fontSize: 17, + fontSize: 17, marginRight: 16, color: '#8E8E93', }, diff --git a/package.json b/package.json index f1aa3a6..882d5b4 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "start": "react-native run-ios", "start:x": "react-native run-ios --simulator='iPhone X'", "link": "react-native link", + "prettier": "prettier --write \"**/*.js\"", + "eslint": "eslint .", "test": "jest" }, "dependencies": { @@ -30,7 +32,13 @@ "devDependencies": { "babel-jest": "22.1.0", "babel-preset-react-native": "4.0.0", + "eslint": "^4.18.1", + "eslint-config-airbnb": "^16.1.0", + "eslint-plugin-import": "^2.9.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-react": "^7.7.0", "jest": "22.1.4", + "prettier": "1.10.2", "react-test-renderer": "16.2.0", "redux-logger": "^3.0.6" },