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.
- checksums.yaml +4 -4
- data/lib/twterm.rb +15 -8
- data/lib/twterm/app.rb +2 -3
- data/lib/twterm/client.rb +218 -68
- data/lib/twterm/filterable_list.rb +2 -1
- data/lib/twterm/friendship.rb +124 -0
- data/lib/twterm/list.rb +5 -3
- data/lib/twterm/promise.rb +143 -0
- data/lib/twterm/screen.rb +0 -1
- data/lib/twterm/status.rb +15 -14
- data/lib/twterm/tab/base.rb +15 -1
- data/lib/twterm/tab/key_assignments_cheatsheet.rb +7 -19
- data/lib/twterm/tab/new/list.rb +9 -15
- data/lib/twterm/tab/new/search.rb +9 -17
- data/lib/twterm/tab/new/start.rb +90 -29
- data/lib/twterm/tab/new/user.rb +5 -7
- data/lib/twterm/tab/scrollable.rb +36 -4
- data/lib/twterm/tab/statuses/base.rb +240 -0
- data/lib/twterm/tab/statuses/conversation.rb +54 -0
- data/lib/twterm/tab/statuses/favorites.rb +45 -0
- data/lib/twterm/tab/statuses/home.rb +36 -0
- data/lib/twterm/tab/statuses/list_timeline.rb +47 -0
- data/lib/twterm/tab/statuses/mentions.rb +40 -0
- data/lib/twterm/tab/statuses/search.rb +42 -0
- data/lib/twterm/tab/statuses/user_timeline.rb +51 -0
- data/lib/twterm/tab/user_tab.rb +288 -19
- data/lib/twterm/tab/users/base.rb +90 -0
- data/lib/twterm/tab/users/followers.rb +41 -0
- data/lib/twterm/tab/users/friends.rb +41 -0
- data/lib/twterm/tab_manager.rb +13 -5
- data/lib/twterm/user.rb +67 -8
- data/lib/twterm/version.rb +1 -1
- data/spec/twterm/friendship_spec.rb +104 -0
- metadata +18 -11
- data/lib/twterm/tab/conversation_tab.rb +0 -49
- data/lib/twterm/tab/list_tab.rb +0 -44
- data/lib/twterm/tab/mentions_tab.rb +0 -36
- data/lib/twterm/tab/search_tab.rb +0 -40
- data/lib/twterm/tab/statuses_tab.rb +0 -251
- data/lib/twterm/tab/timeline_tab.rb +0 -31
- data/lib/twterm/user_window.rb +0 -71
@@ -12,7 +12,7 @@ module Twterm
|
|
12
12
|
reset_filter
|
13
13
|
Notifier.instance.show_error "No matches found: \"#{query}\""
|
14
14
|
else
|
15
|
-
Notifier.instance.show_message "#{total_item_count}
|
15
|
+
Notifier.instance.show_message "#{total_item_count} items found: \"#{filter_query}\""
|
16
16
|
scroller.move_to_top
|
17
17
|
end
|
18
18
|
|
@@ -30,6 +30,7 @@ module Twterm
|
|
30
30
|
def reset_filter
|
31
31
|
FilterQueryWindow.instance.clear
|
32
32
|
@filter_query = ''
|
33
|
+
refresh
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Twterm
|
2
|
+
class Friendship
|
3
|
+
STATUSES = %i(
|
4
|
+
blocking
|
5
|
+
following
|
6
|
+
following_requested
|
7
|
+
muting
|
8
|
+
).freeze
|
9
|
+
|
10
|
+
attr_reader :status, :from, :to
|
11
|
+
|
12
|
+
@@instances = []
|
13
|
+
@@user_ids = Set.new
|
14
|
+
|
15
|
+
def initialize(status, from, to)
|
16
|
+
fail ArgumentError,
|
17
|
+
'' unless STATUSES.include? status
|
18
|
+
|
19
|
+
@status, @from, @to = status, from, to
|
20
|
+
@@instances << self
|
21
|
+
end
|
22
|
+
|
23
|
+
def blocking?
|
24
|
+
status?(:blocking)
|
25
|
+
end
|
26
|
+
|
27
|
+
def following?
|
28
|
+
status?(:following)
|
29
|
+
end
|
30
|
+
|
31
|
+
def following_requested?
|
32
|
+
status?(:following_requested)
|
33
|
+
end
|
34
|
+
|
35
|
+
def muting?
|
36
|
+
status?(:muting)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.already_looked_up?(user_id)
|
40
|
+
@@user_ids.include?(user_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.block(from, to)
|
44
|
+
new(:blocking, from, to)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.blocking?(from, to)
|
48
|
+
!find(:blocking, from, to).nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.cancel_follow_request(from, to)
|
52
|
+
new(:following_requested, from, to)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.delete(status, from, to)
|
56
|
+
cond = -> f { f.status == status && f.from == from && f.to == to }
|
57
|
+
@@instances.delete_if(&cond)
|
58
|
+
end
|
59
|
+
private_class_method :delete
|
60
|
+
|
61
|
+
def self.find(status, from, to)
|
62
|
+
cond = -> f { f.status == status && f.from == from && f.to == to }
|
63
|
+
@@instances.find(&cond)
|
64
|
+
end
|
65
|
+
private_class_method :find
|
66
|
+
|
67
|
+
def self.follow(from, to)
|
68
|
+
new(:following, from, to)
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.following?(from, to)
|
72
|
+
!find(:following, from, to).nil?
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.following_not_requested(from, to)
|
76
|
+
delete(:following_requested, from, to)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.following_requested(from, to)
|
80
|
+
new(:following_requested, from, to)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.following_requested?(from, to)
|
84
|
+
!find(:following_requested, from, to).nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.looked_up!(user_id)
|
88
|
+
@@user_ids << user_id
|
89
|
+
user_id
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.mute(from, to)
|
93
|
+
new(:muting, from, to)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.muting?(from, to)
|
97
|
+
!find(:muting, from, to).nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.new(status, from, to)
|
101
|
+
instance = find(status, from, to)
|
102
|
+
instance.nil? ? super : instance
|
103
|
+
end
|
104
|
+
private_class_method :new
|
105
|
+
|
106
|
+
def self.unblock(from, to)
|
107
|
+
delete(:blocking, from, to)
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.unfollow(from, to)
|
111
|
+
delete(:following, from, to)
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.unmute(from, to)
|
115
|
+
delete(:muting, from, to)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def status?(status)
|
121
|
+
self.status == status
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/twterm/list.rb
CHANGED
@@ -41,10 +41,12 @@ module Twterm
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def self.find_or_fetch(id)
|
44
|
-
|
45
|
-
|
44
|
+
Promise.new do |resolve, reject|
|
45
|
+
instance = find(id)
|
46
|
+
(resolve.(instance) && next) if instance
|
46
47
|
|
47
|
-
|
48
|
+
Client.current.list(id).then { |list| resolve.(list) }
|
49
|
+
end
|
48
50
|
end
|
49
51
|
|
50
52
|
def self.new(list)
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Twterm
|
2
|
+
class Promise
|
3
|
+
def initialize
|
4
|
+
@state = :pending
|
5
|
+
@callbacks = []
|
6
|
+
|
7
|
+
Thread.new do
|
8
|
+
yield method(:resolve), method(:reject)
|
9
|
+
end if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
def catch(on_rejected = nil, &block)
|
13
|
+
if on_rejected.is_a?(Proc) && block_given?
|
14
|
+
fail ArgumentError, 'both proc and block cannot be passed at the same time'
|
15
|
+
end
|
16
|
+
|
17
|
+
on_rejected ||= block
|
18
|
+
self.then(nil, on_rejected)
|
19
|
+
end
|
20
|
+
|
21
|
+
def reject(reason)
|
22
|
+
return if settled?
|
23
|
+
|
24
|
+
@state = :rejected
|
25
|
+
@reason = reason
|
26
|
+
|
27
|
+
callbacks.each do |cb|
|
28
|
+
cb.invoke_on_rejected(reason)
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def rejected?
|
34
|
+
state.eql? :rejected
|
35
|
+
end
|
36
|
+
|
37
|
+
def resolve(value)
|
38
|
+
return if settled?
|
39
|
+
|
40
|
+
@state = :fulfilled
|
41
|
+
@value = value
|
42
|
+
|
43
|
+
callbacks.each do |cb|
|
44
|
+
cb.invoke_on_fulfilled(value)
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def then(on_fulfilled = nil, on_rejected = nil, &block)
|
50
|
+
if (on_fulfilled.is_a?(Proc) || on_rejected.is_a?(Proc)) && block_given?
|
51
|
+
fail ArgumentError, 'both procs and block cannot be passed at the same time'
|
52
|
+
end
|
53
|
+
|
54
|
+
on_fulfilled ||= block
|
55
|
+
next_promise = Promise.new
|
56
|
+
callback = Callback.new(
|
57
|
+
self,
|
58
|
+
on_fulfilled.is_a?(Proc) ? on_fulfilled : nil,
|
59
|
+
on_rejected.is_a?(Proc) ? on_rejected : nil,
|
60
|
+
next_promise
|
61
|
+
)
|
62
|
+
@callbacks << callback
|
63
|
+
|
64
|
+
callback.invoke_on_fulfilled(@value) if fulfilled?
|
65
|
+
callback.invoke_on_rejected(@reason) if rejected?
|
66
|
+
|
67
|
+
next_promise
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.resolve(value)
|
71
|
+
Promise.new { |resolve, _| resolve.(value) }
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
attr_reader :callbacks, :errorbacks, :next_promises, :state
|
77
|
+
|
78
|
+
def fulfilled?
|
79
|
+
state.eql? :fulfilled
|
80
|
+
end
|
81
|
+
|
82
|
+
def pending?
|
83
|
+
state.eql? :pending
|
84
|
+
end
|
85
|
+
|
86
|
+
def rejected?
|
87
|
+
state.eql? :rejected
|
88
|
+
end
|
89
|
+
|
90
|
+
def settled?
|
91
|
+
!pending?
|
92
|
+
end
|
93
|
+
|
94
|
+
class Callback
|
95
|
+
def initialize(promise, on_fulfilled, on_rejected, next_promise)
|
96
|
+
@promise = promise
|
97
|
+
@on_fulfilled, @on_rejected = on_fulfilled, on_rejected
|
98
|
+
@next_promise = next_promise
|
99
|
+
end
|
100
|
+
|
101
|
+
def has_on_fulfilled?
|
102
|
+
!on_fulfilled.nil?
|
103
|
+
end
|
104
|
+
|
105
|
+
def has_on_rejected?
|
106
|
+
!on_rejected.nil?
|
107
|
+
end
|
108
|
+
|
109
|
+
def invoke_on_fulfilled(value)
|
110
|
+
if has_on_fulfilled?
|
111
|
+
new_value = on_fulfilled.(value)
|
112
|
+
next_promise.resolve(new_value)
|
113
|
+
else
|
114
|
+
next_promise.resolve(value)
|
115
|
+
end
|
116
|
+
rescue => reason
|
117
|
+
next_promise.reject(reason)
|
118
|
+
end
|
119
|
+
|
120
|
+
def invoke_on_rejected(reason)
|
121
|
+
unless has_on_rejected?
|
122
|
+
if has_on_fulfilled?
|
123
|
+
next_promise.reject(reason)
|
124
|
+
else
|
125
|
+
raise reason unless has_on_fulfilled?
|
126
|
+
end
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
begin
|
131
|
+
new_value = on_rejected.(reason)
|
132
|
+
next_promise.resolve(new_value)
|
133
|
+
rescue => reason
|
134
|
+
next_promise.reject(reason)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
attr_reader :promise, :on_fulfilled, :on_rejected, :next_promise
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/lib/twterm/screen.rb
CHANGED
data/lib/twterm/status.rb
CHANGED
@@ -35,18 +35,17 @@ module Twterm
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def in_reply_to_status(&block)
|
38
|
-
|
39
|
-
|
40
|
-
return
|
41
|
-
end
|
38
|
+
Promise.new do |resolve, reject|
|
39
|
+
(resolve.(nil) && next) if in_reply_to_status_id.nil?
|
42
40
|
|
43
|
-
|
44
|
-
|
45
|
-
block.call(status)
|
46
|
-
return
|
47
|
-
end
|
41
|
+
instance = Status.find(in_reply_to_status_id)
|
42
|
+
(resolve.(instance) && next) if instance
|
48
43
|
|
49
|
-
|
44
|
+
Client.current.show_status(in_reply_to_status_id)
|
45
|
+
.then do |status|
|
46
|
+
resolve.(status)
|
47
|
+
end
|
48
|
+
end
|
50
49
|
end
|
51
50
|
|
52
51
|
def initialize(tweet)
|
@@ -131,7 +130,7 @@ module Twterm
|
|
131
130
|
|
132
131
|
def self.cleanup
|
133
132
|
TabManager.instance.each_tab do |tab|
|
134
|
-
tab.touch_statuses if tab.is_a?(Tab::
|
133
|
+
tab.touch_statuses if tab.is_a?(Tab::Statuses::Base)
|
135
134
|
end
|
136
135
|
cond = -> (status) { status.touched_at > Time.now - MAX_CACHED_TIME }
|
137
136
|
statuses = all.select(&cond)
|
@@ -144,10 +143,12 @@ module Twterm
|
|
144
143
|
end
|
145
144
|
|
146
145
|
def self.find_or_fetch(id)
|
147
|
-
|
148
|
-
|
146
|
+
Promise.new do |resolve, reject|
|
147
|
+
instance = find(id)
|
148
|
+
(resolve.(instance) && next) if instance
|
149
149
|
|
150
|
-
|
150
|
+
Client.current.show_status(id) { |status| resolve.(status) }
|
151
|
+
end
|
151
152
|
end
|
152
153
|
|
153
154
|
def self.new(tweet)
|
data/lib/twterm/tab/base.rb
CHANGED
@@ -15,13 +15,22 @@ module Twterm
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def initialize
|
18
|
-
@window = stdscr.subwin(stdscr.maxy - 5, stdscr.maxx
|
18
|
+
@window = stdscr.subwin(stdscr.maxy - 5, stdscr.maxx, 3, 0)
|
19
19
|
end
|
20
20
|
|
21
21
|
def refresh
|
22
22
|
Thread.new do
|
23
23
|
refresh_mutex.synchronize do
|
24
24
|
window.clear
|
25
|
+
|
26
|
+
# avoid misalignment caused by some multibyte-characters
|
27
|
+
window.with_color(:black, :transparent) do
|
28
|
+
(0...window.maxy).each do |i|
|
29
|
+
window.setpos(i, 0)
|
30
|
+
window.addch(' ')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
25
34
|
update
|
26
35
|
window.refresh
|
27
36
|
end if refreshable?
|
@@ -32,6 +41,11 @@ module Twterm
|
|
32
41
|
fail NotImplementedError, 'respond_to_key method must be implemented'
|
33
42
|
end
|
34
43
|
|
44
|
+
def title=(title)
|
45
|
+
@title = title
|
46
|
+
TabManager.instance.refresh_window
|
47
|
+
end
|
48
|
+
|
35
49
|
private
|
36
50
|
|
37
51
|
def refresh_mutex
|
@@ -16,8 +16,7 @@ module Twterm
|
|
16
16
|
'[j] [C-p] [DOWN]' => 'Move down',
|
17
17
|
'[k] [C-n] [UP]' => 'Move up',
|
18
18
|
'[u] [C-u]' => 'Scroll up',
|
19
|
-
'[Q]' => 'Quit twterm'
|
20
|
-
'[?]' => 'Open key assignments cheatsheet'
|
19
|
+
'[Q]' => 'Quit twterm'
|
21
20
|
},
|
22
21
|
'Tabs' => {
|
23
22
|
'[h] [C-b] [LEFT]' => 'Show previous tab',
|
@@ -36,6 +35,10 @@ module Twterm
|
|
36
35
|
'[r]' => 'Reply',
|
37
36
|
'[R]' => 'Retweet',
|
38
37
|
'[U]' => 'Show user'
|
38
|
+
},
|
39
|
+
'Others' => {
|
40
|
+
'[P]' => 'View my profile',
|
41
|
+
'[?]' => 'Open key assignments cheatsheet'
|
39
42
|
}
|
40
43
|
}
|
41
44
|
|
@@ -49,24 +52,9 @@ module Twterm
|
|
49
52
|
end
|
50
53
|
|
51
54
|
def respond_to_key(key)
|
52
|
-
|
53
|
-
when ?d, 4
|
54
|
-
10.times { scroller.move_down }
|
55
|
-
when ?g
|
56
|
-
scroller.move_to_top
|
57
|
-
when ?G
|
58
|
-
scroller.move_to_bottom
|
59
|
-
when ?j, 14, Curses::Key::DOWN
|
60
|
-
scroller.move_down
|
61
|
-
when ?k, 16, Curses::Key::UP
|
62
|
-
scroller.move_up
|
63
|
-
when ?u, 21
|
64
|
-
10.times { scroller.move_up }
|
65
|
-
else
|
66
|
-
return false
|
67
|
-
end
|
55
|
+
return true if scroller.respond_to_key(key)
|
68
56
|
|
69
|
-
|
57
|
+
false
|
70
58
|
end
|
71
59
|
|
72
60
|
def title
|