11import fetch from 'isomorphic-fetch'
22import { getAccessToken } from '../utils/auth'
3- import { getTopTerms } from '../utils/terms'
3+
4+ import common from 'common-words'
5+ import twitter from 'twitter-text'
46
57/* @flow */
68// ------------------------------------
@@ -12,6 +14,9 @@ export const CREDENTIALS_ERROR = 'CREDENTIALS_ERROR'
1214export const TWEETS_START = 'TWEETS_START'
1315export const TWEETS_COMPLETE = 'TWEETS_COMPLETE'
1416export const TWEETS_ERROR = 'TWEETS_ERROR'
17+ export const TOP_TERMS_START = 'TOP_TERMS_START'
18+ export const TOP_TERMS_COMPLETE = 'TOP_TERMS_COMPLETE'
19+ export const TOP_TERMS_ERROR = 'TOP_TERMS_ERROR'
1520
1621// ------------------------------------
1722// Actions
@@ -63,14 +68,17 @@ export const getCredentials = (): Function => {
6368}
6469
6570export const tweetsStart = ( ) : Action => ( {
66- type : TWEETS_START
71+ type : TWEETS_START ,
72+ payload : {
73+ tweetsLoading : true
74+ }
6775} )
6876
69- export const tweetsComplete = ( tweets : Array , topTerms : Array ) : Action => ( {
77+ export const tweetsComplete = ( tweets : Array ) : Action => ( {
7078 type : TWEETS_COMPLETE ,
7179 payload : {
72- tweets : tweets . map ( ( item ) => item . id_str ) ,
73- topTerms : topTerms
80+ tweetsLoading : false ,
81+ tweets
7482 }
7583} )
7684
@@ -99,7 +107,7 @@ export const getTweets = (): Function => {
99107 . then ( ( json ) => {
100108 console . log ( '@-->response json' , json )
101109
102- dispatch ( tweetsComplete ( json , getTopTerms ( json ) ) )
110+ dispatch ( tweetsComplete ( json . map ( ( tweet ) => tweet . id_str ) ) )
103111
104112 return json
105113 } )
@@ -110,9 +118,75 @@ export const getTweets = (): Function => {
110118 }
111119}
112120
121+ export const topTermsStart = ( ) : Action => ( {
122+ type : TOP_TERMS_START ,
123+ payload : {
124+ topTermsLoading : true
125+ }
126+ } )
127+
128+ export const topTermsComplete = ( topTerms : Array ) : Action => ( {
129+ type : TOP_TERMS_COMPLETE ,
130+ payload : {
131+ topTermsLoading : false ,
132+ topTerms
133+ }
134+ } )
135+
136+ export const topTermsError = ( error : string ) : Action => ( {
137+ type : TOP_TERMS_ERROR ,
138+ error
139+ } )
140+
141+ export const getTopTerms = ( tweets : Array ) : Function => {
142+ return ( dispatch : Function , getState : Function ) : Promise => {
143+ dispatch ( topTermsStart ( ) )
144+ // Ignore common words
145+ const ignoreWords = common . map ( ( item ) => item . word )
146+ ignoreWords . push ( 'is' , 're' , 'are' , 'where' , 'https' , 'don' , 'sure' )
147+
148+ // Map of word counts
149+ const counts = new Map ( )
150+
151+ tweets
152+ . map ( ( tweet ) => tweet . text )
153+ // Remove Twitter Entities from Tweet i.e. hashtags, mentions, URLs
154+ . map ( ( text ) => {
155+ const entities = twitter . extractEntitiesWithIndices ( text )
156+
157+ // Iterate the list of entities in reverse
158+ // in order to correctly splice out the indices
159+ for ( let i = entities . length - 1 ; i >= 0 ; i -- ) {
160+ let [ start , end ] = entities [ i ] . indices
161+ // Splice out text
162+ text = text . slice ( 0 , start ) + '' + text . slice ( end )
163+ }
164+ return text
165+ } )
166+ // Concat text
167+ . reduce ( ( a , b ) => a + ' ' + b )
168+ // Match whole words
169+ . match ( / [ a - z ] + | \d + / igm)
170+ . filter ( ( word ) => word . length > 1 && word !== 'RT' && ! ignoreWords . includes ( word . toLowerCase ( ) ) )
171+ // Do word count
172+ . forEach ( ( word ) => {
173+ let count = counts . get ( word ) || 0
174+ counts . set ( word , count + 1 )
175+ } )
176+ // Sort words by counts, descending
177+ let ret = Array . from ( counts . entries ( ) ) . map ( ( [ word , count ] ) => ( { word, count} ) )
178+ ret . sort ( ( a , b ) => b . count - a . count )
179+ // Return only top 10 terms
180+ ret = ret . length > 10 ? ret . slice ( 0 , 10 ) : ret
181+ dispatch ( topTermsComplete ( ret ) )
182+ return Promise . resolve ( ret )
183+ }
184+ }
185+
113186export const actions = {
114187 getCredentials,
115- getTweets
188+ getTweets,
189+ getTopTerms
116190}
117191
118192// ------------------------------------
@@ -121,8 +195,12 @@ export const actions = {
121195const ACTION_HANDLERS = {
122196 [ CREDENTIALS_COMPLETE ] : ( state : Object , action : { payload : Object } ) : Object => Object . assign ( { } , state , action . payload ) ,
123197 [ CREDENTIALS_ERROR ] : ( state : Object , action : { error : string } ) : Object => Object . assign ( { } , state , action . error ) ,
198+ [ TWEETS_START ] : ( state : Object , action : { payload : Object } ) : Object => Object . assign ( { } , state , action . payload ) ,
124199 [ TWEETS_COMPLETE ] : ( state : Object , action : { payload : Object } ) : Object => Object . assign ( { } , state , action . payload ) ,
125- [ TWEETS_ERROR ] : ( state : Object , action : { error : string } ) : Object => Object . assign ( { } , state , action . error )
200+ [ TWEETS_ERROR ] : ( state : Object , action : { error : string } ) : Object => Object . assign ( { } , state , action . error ) ,
201+ [ TOP_TERMS_START ] : ( state : Object , action : { payload : Object } ) : Object => Object . assign ( { } , state , action . payload ) ,
202+ [ TOP_TERMS_COMPLETE ] : ( state : Object , action : { payload : Object } ) : Object => Object . assign ( { } , state , action . payload ) ,
203+ [ TOP_TERMS_ERROR ] : ( state : Object , action : { error : string } ) : Object => Object . assign ( { } , state , action . error )
126204}
127205
128206// ------------------------------------
@@ -132,7 +210,9 @@ const initialState = {
132210 id : null ,
133211 screenName : null ,
134212 tweets : [ ] ,
213+ tweetsLoading : true ,
135214 topTerms : [ ] ,
215+ topTermsLoading : true ,
136216 error : null
137217}
138218export default function twitterReducer ( state : Object = initialState , action : Action ) : Object {
0 commit comments