twterm 1.0.12 → 1.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/twterm.rb +15 -8
  3. data/lib/twterm/app.rb +2 -3
  4. data/lib/twterm/client.rb +218 -68
  5. data/lib/twterm/filterable_list.rb +2 -1
  6. data/lib/twterm/friendship.rb +124 -0
  7. data/lib/twterm/list.rb +5 -3
  8. data/lib/twterm/promise.rb +143 -0
  9. data/lib/twterm/screen.rb +0 -1
  10. data/lib/twterm/status.rb +15 -14
  11. data/lib/twterm/tab/base.rb +15 -1
  12. data/lib/twterm/tab/key_assignments_cheatsheet.rb +7 -19
  13. data/lib/twterm/tab/new/list.rb +9 -15
  14. data/lib/twterm/tab/new/search.rb +9 -17
  15. data/lib/twterm/tab/new/start.rb +90 -29
  16. data/lib/twterm/tab/new/user.rb +5 -7
  17. data/lib/twterm/tab/scrollable.rb +36 -4
  18. data/lib/twterm/tab/statuses/base.rb +240 -0
  19. data/lib/twterm/tab/statuses/conversation.rb +54 -0
  20. data/lib/twterm/tab/statuses/favorites.rb +45 -0
  21. data/lib/twterm/tab/statuses/home.rb +36 -0
  22. data/lib/twterm/tab/statuses/list_timeline.rb +47 -0
  23. data/lib/twterm/tab/statuses/mentions.rb +40 -0
  24. data/lib/twterm/tab/statuses/search.rb +42 -0
  25. data/lib/twterm/tab/statuses/user_timeline.rb +51 -0
  26. data/lib/twterm/tab/user_tab.rb +288 -19
  27. data/lib/twterm/tab/users/base.rb +90 -0
  28. data/lib/twterm/tab/users/followers.rb +41 -0
  29. data/lib/twterm/tab/users/friends.rb +41 -0
  30. data/lib/twterm/tab_manager.rb +13 -5
  31. data/lib/twterm/user.rb +67 -8
  32. data/lib/twterm/version.rb +1 -1
  33. data/spec/twterm/friendship_spec.rb +104 -0
  34. metadata +18 -11
  35. data/lib/twterm/tab/conversation_tab.rb +0 -49
  36. data/lib/twterm/tab/list_tab.rb +0 -44
  37. data/lib/twterm/tab/mentions_tab.rb +0 -36
  38. data/lib/twterm/tab/search_tab.rb +0 -40
  39. data/lib/twterm/tab/statuses_tab.rb +0 -251
  40. data/lib/twterm/tab/timeline_tab.rb +0 -31
  41. data/lib/twterm/user_window.rb +0 -71
@@ -19,7 +19,6 @@ module Twterm
19
19
  def initialize
20
20
  super
21
21
 
22
- @title = 'New tab'
23
22
  refresh
24
23
  end
25
24
 
@@ -28,33 +27,28 @@ module Twterm
28
27
  end
29
28
 
30
29
  def respond_to_key(key)
30
+ return true if scroller.respond_to_key(key)
31
+
31
32
  case key
32
- when ?d, 4
33
- 10.times { scroller.move_down }
34
- when ?g
35
- scroller.move_to_top
36
- when ?G
37
- scroller.move_to_bottom
38
- when ?j, 14, Curses::Key::DOWN
39
- scroller.move_down
40
33
  when 10
41
34
  return true if current_list.nil?
42
- list_tab = Tab::ListTab.new(current_list.id)
35
+ list_tab = Tab::Statuses::ListTimeline.new(current_list.id)
43
36
  TabManager.instance.switch(list_tab)
44
- when ?k, 16, Curses::Key::UP
45
- scroller.move_up
46
37
  when ?q
47
38
  reset_filter
48
- when ?u, 21
49
- 10.times { scroller.move_up }
50
39
  when ?/
51
40
  filter
52
41
  else
53
42
  return false
54
43
  end
44
+
55
45
  true
56
46
  end
57
47
 
48
+ def title
49
+ 'New tab'.freeze
50
+ end
51
+
58
52
  def total_item_count
59
53
  items.count
60
54
  end
@@ -91,7 +85,7 @@ module Twterm
91
85
 
92
86
  Thread.new do
93
87
  Notifier.instance.show_message('Loading lists ...')
94
- Client.current.lists do |lists|
88
+ Client.current.lists.then do |lists|
95
89
  @@lists = lists.sort_by(&:full_name)
96
90
  show_lists
97
91
  window.refresh if TabManager.instance.current_tab == self
@@ -20,8 +20,6 @@ module Twterm
20
20
  def initialize
21
21
  super
22
22
 
23
- @title = 'New tab'
24
-
25
23
  update_saved_search
26
24
  end
27
25
 
@@ -39,7 +37,7 @@ module Twterm
39
37
  query = (readline('> ') || '').strip
40
38
  resetter.call
41
39
 
42
- tab = query.nil? || query.empty? ? Tab::New::Search.new : Tab::SearchTab.new(query)
40
+ tab = query.nil? || query.empty? ? Tab::New::Search.new : Tab::Statuses::Search.new(query)
43
41
  TabManager.instance.switch(tab)
44
42
  end
45
43
 
@@ -62,21 +60,11 @@ module Twterm
62
60
  end
63
61
 
64
62
  def respond_to_key(key)
63
+ return true if scroller.respond_to_key(key)
64
+
65
65
  case key
66
- when ?d, 4
67
- 10.times { scroller.move_down }
68
- when ?g
69
- scroller.move_to_top
70
- when ?G
71
- scroller.move_to_bottom
72
66
  when 10
73
67
  open_search_tab_with_current_query
74
- when ?j, 14, Curses::Key::DOWN
75
- scroller.move_down
76
- when ?k, 16, Curses::Key::UP
77
- scroller.move_up
78
- when ?u, 21
79
- 10.times { scroller.move_up }
80
68
  when ?q
81
69
  reset_filter
82
70
  when ?/
@@ -88,6 +76,10 @@ module Twterm
88
76
  true
89
77
  end
90
78
 
79
+ def title
80
+ 'New tab'.freeze
81
+ end
82
+
91
83
  def total_item_count
92
84
  items.count
93
85
  end
@@ -103,7 +95,7 @@ module Twterm
103
95
  invoke_input
104
96
  else
105
97
  query = items[index]
106
- tab = Tab::SearchTab.new(query)
98
+ tab = Tab::Statuses::Search.new(query)
107
99
  TabManager.instance.switch(tab)
108
100
  end
109
101
  end
@@ -132,7 +124,7 @@ module Twterm
132
124
  def update_saved_search
133
125
  return unless @@queries.empty?
134
126
 
135
- Client.current.saved_search do |searches|
127
+ Client.current.saved_search.then do |searches|
136
128
  @@queries = searches.map(&:query)
137
129
  refresh
138
130
  end
@@ -3,60 +3,121 @@ module Twterm
3
3
  module New
4
4
  class Start
5
5
  include Base
6
+ include Scrollable
6
7
 
7
8
  def ==(other)
8
9
  other.is_a?(self.class)
9
10
  end
10
11
 
12
+ def drawable_item_count
13
+ (window.maxy - 1).div(2)
14
+ end
15
+
16
+ def items
17
+ %i(
18
+ list_tab
19
+ search_tab
20
+ user_tab
21
+ key_assignments_cheatsheet
22
+ ).freeze
23
+ end
24
+
11
25
  def initialize
12
26
  super
13
- @title = 'New tab'
14
27
  refresh
15
28
  end
16
29
 
17
30
  def respond_to_key(key)
31
+ return true if scroller.respond_to_key(key)
32
+
18
33
  case key
34
+ when 10
35
+ perform_selected_action
19
36
  when 'L'
20
- tab = Tab::New::List.new
21
- TabManager.instance.switch(tab)
37
+ open_list_tab
22
38
  when 'S'
23
- tab = Tab::New::Search.new
24
- TabManager.instance.switch(tab)
39
+ open_search_tab
25
40
  when 'U'
26
- tab = Tab::New::User.new
27
- TabManager.instance.switch(tab)
28
- tab.invoke_input
41
+ open_user_tab
29
42
  else
30
43
  return false
31
44
  end
32
45
  true
33
46
  end
34
47
 
48
+ def title
49
+ 'New tab'.freeze
50
+ end
51
+
35
52
  private
36
53
 
54
+ def open_list_tab
55
+ switch(Tab::New::List.new)
56
+ end
57
+
58
+ def open_search_tab
59
+ switch(Tab::New::Search.new)
60
+ end
61
+
62
+ def open_user_tab
63
+ tab = Tab::New::User.new
64
+ switch(tab)
65
+ tab.invoke_input
66
+ end
67
+
68
+ def open_key_assignments_cheatsheet
69
+ switch(Tab::KeyAssignmentsCheatsheet.new)
70
+ end
71
+
72
+ def perform_selected_action
73
+ case current_item
74
+ when :list_tab
75
+ open_list_tab
76
+ when :search_tab
77
+ open_search_tab
78
+ when :user_tab
79
+ open_user_tab
80
+ when :key_assignments_cheatsheet
81
+ open_key_assignments_cheatsheet
82
+ end
83
+ end
84
+
85
+ def switch(tab)
86
+ TabManager.instance.switch(tab)
87
+ end
88
+
37
89
  def update
38
90
  window.setpos(2, 3)
39
- window.bold { window.addstr("You've opened a new tab") }
40
-
41
- window.setpos(4, 5)
42
- window.addstr('- [L] Open list tab')
43
- window.setpos(4, 7)
44
- window.bold { window.addstr('[L]') }
45
-
46
- window.setpos(6, 5)
47
- window.addstr('- [S] Open search tab')
48
- window.setpos(6, 7)
49
- window.bold { window.addstr('[S]') }
50
-
51
- window.setpos(8, 5)
52
- window.addstr('- [U] Open user tab')
53
- window.setpos(8, 7)
54
- window.bold { window.addstr('[U]') }
55
-
56
- window.setpos(11, 3)
57
- window.addstr('To cancel opening a new tab, just press [w] to close this tab.')
58
- window.setpos(11, 43)
59
- window.bold { window.addstr('[w]') }
91
+ window.bold { window.addstr('Open new tab') }
92
+
93
+ drawable_items.each.with_index(0) do |item, i|
94
+ line = 4 + i * 2
95
+ window.setpos(line, 5)
96
+
97
+ case item
98
+ when :list_tab
99
+ window.addstr('[L] List tab')
100
+ window.setpos(line, 6)
101
+ window.bold { window.addch(?L) }
102
+ when :search_tab
103
+ window.addstr('[S] Search tab')
104
+ window.setpos(line, 6)
105
+ window.bold { window.addch(?S) }
106
+ when :user_tab
107
+ window.addstr('[U] User tab')
108
+ window.setpos(line, 6)
109
+ window.bold { window.addch(?U) }
110
+ when :key_assignments_cheatsheet
111
+ window.addstr('[?] Key assignments cheatsheet')
112
+ window.setpos(line, 6)
113
+ window.bold { window.addch(??) }
114
+ end
115
+
116
+ window.with_color(:black, :magenta) do
117
+ window.setpos(line, 3)
118
+ window.addch(' ')
119
+ end if scroller.current_item?(i)
120
+ end
60
121
  end
61
122
  end
62
123
  end
@@ -9,12 +9,6 @@ module Twterm
9
9
  other.is_a?(self.class)
10
10
  end
11
11
 
12
- def initialize
13
- super
14
-
15
- @title = 'New tab'
16
- end
17
-
18
12
  def invoke_input
19
13
  resetter = proc do
20
14
  reset_prog_mode
@@ -33,7 +27,7 @@ module Twterm
33
27
  if screen_name.nil? || screen_name.empty?
34
28
  TabManager.instance.switch(Tab::New::Start.new)
35
29
  else
36
- Client.current.show_user(screen_name) do |user|
30
+ Client.current.show_user(screen_name).then do |user|
37
31
  if user.nil?
38
32
  Notifier.instance.show_error 'User not found'
39
33
  tab = Tab::New::Start.new
@@ -59,6 +53,10 @@ module Twterm
59
53
  false
60
54
  end
61
55
 
56
+ def title
57
+ 'New tab'.freeze
58
+ end
59
+
62
60
  private
63
61
 
64
62
  def update; end
@@ -4,7 +4,7 @@ module Twterm
4
4
  extend Forwardable
5
5
 
6
6
  attr_reader :scroller
7
- def_delegators :scroller, :drawable_items
7
+ def_delegators :scroller, :current_item, :drawable_items
8
8
 
9
9
  def scroller
10
10
  return @scroller unless @scroller.nil?
@@ -15,6 +15,12 @@ module Twterm
15
15
  @scroller
16
16
  end
17
17
 
18
+ # define default behaviour
19
+ # overwrite if necessary
20
+ def total_item_count
21
+ items.count
22
+ end
23
+
18
24
  private
19
25
 
20
26
  class Scroller
@@ -29,6 +35,10 @@ module Twterm
29
35
  add_hook(:after_move, &block)
30
36
  end
31
37
 
38
+ def current_item
39
+ items[index]
40
+ end
41
+
32
42
  def current_item?(i)
33
43
  index == offset + i
34
44
  end
@@ -54,7 +64,8 @@ module Twterm
54
64
 
55
65
  def item_prepended!
56
66
  @index += 1
57
- @offset += 1
67
+ @offset += 1 unless @index < 4
68
+ # keep cursor position unless it is on the top
58
69
  end
59
70
 
60
71
  def move_down
@@ -100,14 +111,33 @@ module Twterm
100
111
  n.between?(offset, offset + drawable_item_count)
101
112
  end
102
113
 
114
+ def respond_to_key(key)
115
+ case key
116
+ when ?d, 4
117
+ 10.times { move_down }
118
+ when ?g
119
+ move_to_top
120
+ when ?G
121
+ move_to_bottom
122
+ when ?j, 14, Curses::Key::DOWN
123
+ move_down
124
+ when ?k, 16, Curses::Key::UP
125
+ move_up
126
+ when ?u, 21
127
+ 10.times { move_up }
128
+ else
129
+ return false
130
+ end
131
+
132
+ true
133
+ end
134
+
103
135
  def set_cursor_free!
104
136
  @cursor_free = true
105
137
  end
106
138
 
107
139
  private
108
140
 
109
- alias_method :count, :total_item_count
110
-
111
141
  def add_hook(name, &block)
112
142
  @hooks ||= {}
113
143
  @hooks[name] = block
@@ -132,6 +162,8 @@ module Twterm
132
162
  def hook(name)
133
163
  @hooks[name].call unless @hooks[name].nil?
134
164
  end
165
+
166
+ alias_method :count, :total_item_count
135
167
  end
136
168
  end
137
169
  end
@@ -0,0 +1,240 @@
1
+ module Twterm
2
+ module Tab
3
+ module Statuses
4
+ module Base
5
+ include Tab::Base
6
+ include FilterableList
7
+ include Scrollable
8
+
9
+ def append(status)
10
+ fail ArgumentError,
11
+ 'argument must be an instance of Status class' unless status.is_a? Status
12
+
13
+ return if @status_ids.include?(status.id)
14
+
15
+ @status_ids.unshift(status.id)
16
+ status.split(window.maxx - 4)
17
+ status.touch!
18
+ scroller.item_appended!
19
+ refresh
20
+ end
21
+
22
+ def delete(status_id)
23
+ @status_ids.delete(status_id)
24
+ refresh
25
+ end
26
+
27
+ def destroy_status
28
+ status = highlighted_status
29
+
30
+ Client.current.destroy_status(status).then do
31
+ delete(status.id)
32
+ refresh
33
+ end
34
+ end
35
+
36
+ def drawable_item_count
37
+ statuses.reverse.drop(scroller.offset).lazy
38
+ .map { |s| s.split(window.maxx - 4).count + 2 }
39
+ .scan(0, :+)
40
+ .select { |l| l < window.maxy }
41
+ .count
42
+ end
43
+
44
+ def favorite
45
+ return if highlighted_status.nil?
46
+
47
+ method_name = highlighted_status.favorited ? :unfavorite : :favorite
48
+ Client.current.method(method_name).call(highlighted_status)
49
+ .then { refresh }
50
+ end
51
+
52
+ def fetch
53
+ fail NotImplementedError, 'fetch method must be implemented'
54
+ end
55
+
56
+ def initialize
57
+ super
58
+
59
+ @status_ids = []
60
+ end
61
+
62
+ def items
63
+ statuses.reverse
64
+ end
65
+
66
+ def open_link
67
+ return if highlighted_status.nil?
68
+
69
+ status = highlighted_status
70
+ urls = status.urls.map(&:expanded_url) + status.media.map(&:expanded_url)
71
+ urls.each(&Launchy.method(:open))
72
+ end
73
+
74
+ def prepend(status)
75
+ fail unless status.is_a? Status
76
+
77
+ return if @status_ids.include?(status.id)
78
+
79
+ @status_ids << status.id
80
+ status.split(window.maxx - 4)
81
+ status.touch!
82
+ scroller.item_prepended!
83
+ refresh
84
+ end
85
+
86
+ def reply
87
+ return if highlighted_status.nil?
88
+ Tweetbox.instance.compose(highlighted_status)
89
+ end
90
+
91
+ def respond_to_key(key)
92
+ return true if scroller.respond_to_key(key)
93
+
94
+ case key
95
+ when ?c
96
+ show_conversation
97
+ when ?D
98
+ destroy_status
99
+ when ?F
100
+ favorite
101
+ when ?o
102
+ open_link
103
+ when ?r
104
+ reply
105
+ when ?R
106
+ retweet
107
+ when 18
108
+ fetch
109
+ when ?U
110
+ show_user
111
+ when ?/
112
+ filter
113
+ when ?q
114
+ reset_filter
115
+ else
116
+ return false
117
+ end
118
+ true
119
+ end
120
+
121
+ def retweet
122
+ return if highlighted_status.nil?
123
+ Client.current.retweet(highlighted_status).then { refresh }
124
+ end
125
+
126
+ def show_conversation
127
+ return if highlighted_status.nil?
128
+ tab = Tab::Statuses::Conversation.new(highlighted_status.id)
129
+ TabManager.instance.add_and_show(tab)
130
+ end
131
+
132
+ def show_user
133
+ return if highlighted_status.nil?
134
+ user = highlighted_status.user
135
+ user_tab = Tab::UserTab.new(user.id)
136
+ TabManager.instance.add_and_show(user_tab)
137
+ end
138
+
139
+ def statuses
140
+ statuses = @status_ids.map { |id| Status.find(id) }.reject(&:nil?)
141
+ @status_ids = statuses.map(&:id)
142
+
143
+ if filter_query.empty?
144
+ statuses
145
+ else
146
+ statuses.select { |s| s.matches?(filter_query) }
147
+ end
148
+ end
149
+
150
+ def touch_statuses
151
+ statuses.reverse.take(100).each(&:touch!)
152
+ end
153
+
154
+ def total_item_count
155
+ filter_query.empty? ? @status_ids.count : statuses.count
156
+ end
157
+
158
+ def update
159
+ line = 0
160
+
161
+ scroller.drawable_items.each.with_index(0) do |status, i|
162
+ formatted_lines = status.split(window.maxx - 4).count
163
+ window.with_color(:black, :magenta) do
164
+ (formatted_lines + 1).times do |j|
165
+ window.setpos(line + j, 0)
166
+ window.addch(' ')
167
+ end
168
+ end if scroller.current_item?(i)
169
+
170
+ window.setpos(line, 2)
171
+
172
+ window.bold do
173
+ window.with_color(status.user.color) do
174
+ window.addstr(status.user.name)
175
+ end
176
+ end
177
+
178
+ window.addstr(" (@#{status.user.screen_name}) [#{status.date}] ")
179
+
180
+ unless status.retweeted_by.nil?
181
+ window.addstr('(retweeted by ')
182
+ window.bold do
183
+ window.addstr("@#{status.retweeted_by.screen_name}")
184
+ end
185
+ window.addstr(') ')
186
+ end
187
+
188
+ if status.favorited?
189
+ window.with_color(:black, :yellow) do
190
+ window.addch(' ')
191
+ end
192
+
193
+ window.addch(' ')
194
+ end
195
+
196
+ if status.retweeted?
197
+ window.with_color(:black, :green) do
198
+ window.addch(' ')
199
+ end
200
+ window.addch(' ')
201
+ end
202
+
203
+ if status.favorite_count > 0
204
+ window.with_color(:yellow) do
205
+ window.addstr("#{status.favorite_count}fav#{status.favorite_count > 1 ? 's' : ''}")
206
+ end
207
+ window.addch(' ')
208
+ end
209
+
210
+ if status.retweet_count > 0
211
+ window.with_color(:green) do
212
+ window.addstr("#{status.retweet_count}RT#{status.retweet_count > 1 ? 's' : ''}")
213
+ end
214
+ window.addch(' ')
215
+ end
216
+
217
+ status.split(window.maxx - 4).each do |str|
218
+ line += 1
219
+ window.setpos(line, 2)
220
+ window.addstr(str)
221
+ end
222
+
223
+ line += 2
224
+ end
225
+ end
226
+
227
+ private
228
+
229
+ def highlighted_status
230
+ statuses[scroller.count - scroller.index - 1]
231
+ end
232
+
233
+ def sort
234
+ @status_ids &= Status.all.map(&:id)
235
+ @status_ids.sort_by! { |id| Status.find(id).appeared_at }
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end