twterm 2.6.0 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +62 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +7 -0
  5. data/Makefile +21 -0
  6. data/README.md +52 -6
  7. data/bin/twterm +1 -3
  8. data/default.nix +21 -0
  9. data/gemset.nix +5130 -0
  10. data/lib/twterm/app.rb +13 -24
  11. data/lib/twterm/client.rb +1 -4
  12. data/lib/twterm/color_manager.rb +8 -9
  13. data/lib/twterm/image.rb +31 -0
  14. data/lib/twterm/image/attr.rb +42 -0
  15. data/lib/twterm/image/bold.rb +7 -21
  16. data/lib/twterm/image/color.rb +9 -15
  17. data/lib/twterm/image/dim.rb +13 -0
  18. data/lib/twterm/image/underlined.rb +17 -0
  19. data/lib/twterm/image_builder/user_name_image_builder.rb +1 -0
  20. data/lib/twterm/key_mapper.rb +6 -8
  21. data/lib/twterm/key_mapper/abstract_key_mapper.rb +2 -2
  22. data/lib/twterm/list.rb +36 -1
  23. data/lib/twterm/message_window.rb +4 -13
  24. data/lib/twterm/persistable_configuration_proxy.rb +6 -6
  25. data/lib/twterm/preferences.rb +8 -1
  26. data/lib/twterm/rest_client.rb +0 -33
  27. data/lib/twterm/screen.rb +103 -25
  28. data/lib/twterm/search_query_window.rb +5 -13
  29. data/lib/twterm/status.rb +10 -0
  30. data/lib/twterm/tab/abstract_tab.rb +30 -16
  31. data/lib/twterm/tab/new/index.rb +0 -10
  32. data/lib/twterm/tab/new/search.rb +2 -2
  33. data/lib/twterm/tab/new/user.rb +2 -2
  34. data/lib/twterm/tab/preferences/control.rb +77 -0
  35. data/lib/twterm/tab/preferences/index.rb +6 -0
  36. data/lib/twterm/tab/scrollable.rb +1 -1
  37. data/lib/twterm/tab/searchable.rb +6 -4
  38. data/lib/twterm/tab/status_tab.rb +10 -0
  39. data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +11 -6
  40. data/lib/twterm/tab/user_tab.rb +0 -9
  41. data/lib/twterm/tab_manager.rb +77 -10
  42. data/lib/twterm/tweetbox.rb +2 -3
  43. data/lib/twterm/user.rb +1 -0
  44. data/lib/twterm/version.rb +1 -1
  45. data/nix/Gemfile +3 -0
  46. data/nix/Gemfile.lock +77 -0
  47. data/nix/gemset.nix +325 -0
  48. data/shell.nix +40 -0
  49. data/spec/twterm/image/bold_spec.rb +30 -0
  50. data/spec/twterm/image/color_spec.rb +16 -0
  51. data/spec/twterm/image/dim_spec.rb +30 -0
  52. data/twterm.gemspec +7 -14
  53. metadata +31 -113
  54. data/.travis.yml +0 -12
  55. data/lib/twterm/direct_message.rb +0 -60
  56. data/lib/twterm/direct_message_composer.rb +0 -80
  57. data/lib/twterm/direct_message_manager.rb +0 -51
  58. data/lib/twterm/event/direct_message/fetched.rb +0 -10
  59. data/lib/twterm/event/notification/direct_message.rb +0 -30
  60. data/lib/twterm/event/screen/resize.rb +0 -13
  61. data/lib/twterm/repository/direct_message_repository.rb +0 -14
  62. data/lib/twterm/tab/direct_message/conversation.rb +0 -104
  63. data/lib/twterm/tab/direct_message/conversation_list.rb +0 -100
  64. data/spec/twterm/event/screen/resize_spec.rb +0 -11
data/.travis.yml DELETED
@@ -1,12 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 2.1
5
- - 2.2
6
- - 2.3.0
7
- - 2.4.0
8
- - 2.5.0
9
-
10
- before_install: gem update --system
11
-
12
- script: bundle exec rubocop --parallel --fail-level warning && bundle exec rspec
@@ -1,60 +0,0 @@
1
- require 'twterm/user'
2
- require 'twterm/utils'
3
-
4
- module Twterm
5
- class DirectMessage
6
- attr_reader :id, :created_at, :recipient_id, :sender_id, :text
7
-
8
- def initialize(message)
9
- @id = message.id
10
- update!(message)
11
- end
12
-
13
- def ==(other)
14
- other.is_a?(self.class) && id == other.id
15
- end
16
-
17
- def date
18
- format = Time.now - @created_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
19
- @created_at.strftime(format)
20
- end
21
-
22
- def update!(message)
23
- @created_at = message.created_at.dup.localtime
24
- @recipient_id = message.recipient.id
25
- @sender_id = message.sender.id
26
- @text = message.text
27
-
28
- self
29
- end
30
-
31
- class Conversation
32
- include Utils
33
-
34
- attr_reader :collocutor_id, :messages
35
-
36
- def initialize(collocutor_id)
37
- @collocutor_id = collocutor_id
38
- @messages = []
39
- end
40
-
41
- def <<(message)
42
- @messages << message if messages.find { |m| m == message }.nil?
43
- @messages.sort_by!(&:created_at).reverse!
44
-
45
- self
46
- end
47
-
48
- def preview
49
- messages.sort_by(&:created_at).last.text.gsub("\n", ' ')
50
- end
51
-
52
- def updated_at
53
- updated_at = @messages.map(&:created_at).max
54
-
55
- format = Time.now - updated_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
56
- updated_at.strftime(format)
57
- end
58
- end
59
- end
60
- end
@@ -1,80 +0,0 @@
1
- require 'twterm/event/screen/refresh'
2
- require 'twterm/publisher'
3
- require 'twterm/user'
4
- require 'twterm/utils'
5
-
6
- module Twterm
7
- class DirectMessageComposer
8
- include Readline
9
- include Curses
10
- include Publisher
11
- include Utils
12
-
13
- def initialize(app, client)
14
- @app, @client = app, client
15
- end
16
-
17
- def compose(recipient)
18
- check_type User, recipient
19
-
20
- clear
21
-
22
- resetter = proc do
23
- reset_prog_mode
24
- sleep 0.1
25
- publish(Event::Screen::Refresh.new)
26
- end
27
-
28
- thread = Thread.new do
29
- close_screen
30
-
31
- puts "\nCompose new message to @%s:" % recipient.screen_name
32
-
33
- app.completion_manager.set_default_mode!
34
-
35
- loop do
36
- line = (readline('> ', true) || '').strip
37
- break if line.empty?
38
-
39
- if line.end_with?('\\')
40
- @text << line.chop.rstrip + "\n"
41
- else
42
- @text << line
43
- break
44
- end
45
- end
46
-
47
- puts "\n"
48
-
49
- resetter.call
50
- send(recipient) unless text.empty?
51
- end
52
-
53
- app.register_interruption_handler do
54
- thread.kill
55
- clear
56
- puts "\nCanceled"
57
- resetter.call
58
- end
59
-
60
- thread.join
61
- end
62
-
63
- private
64
-
65
- attr_reader :app, :client
66
-
67
- def clear
68
- @text = ''
69
- end
70
-
71
- def send(recipient)
72
- client.create_direct_message(recipient, text)
73
- clear
74
- end
75
-
76
- def text
77
- @text || ''
78
- end
79
- end
80
- end
@@ -1,51 +0,0 @@
1
- require 'twterm/client'
2
- require 'twterm/direct_message'
3
- require 'twterm/event/direct_message/fetched'
4
- require 'twterm/publisher'
5
- require 'twterm/scheduler'
6
- require 'twterm/user'
7
- require 'twterm/utils'
8
-
9
- module Twterm
10
- class DirectMessageManager
11
- include Publisher, Utils
12
-
13
- def initialize(client)
14
- check_type Client, client
15
-
16
- @client = client
17
- @conversations = {}
18
-
19
- fetch
20
-
21
- Scheduler.new(300) { fetch }
22
- end
23
-
24
- def add(collocutor_id, message)
25
- check_type DirectMessage, message
26
-
27
- @conversations[collocutor_id] ||= DirectMessage::Conversation.new(collocutor_id)
28
- @conversations[collocutor_id] << message
29
- end
30
-
31
- def fetch
32
- client.direct_messages_received.then do |messages|
33
- messages.each { |m| add(m.sender_id, m) }
34
- publish(Event::DirectMessage::Fetched.new)
35
- end
36
-
37
- client.direct_messages_sent.then do |messages|
38
- messages.each { |m| add(m.recipient_id, m) }
39
- publish(Event::DirectMessage::Fetched.new)
40
- end
41
- end
42
-
43
- def conversations
44
- @conversations.values
45
- end
46
-
47
- private
48
-
49
- attr_reader :client
50
- end
51
- end
@@ -1,10 +0,0 @@
1
- require 'twterm/event/abstract_event'
2
-
3
- module Twterm
4
- module Event
5
- module DirectMessage
6
- class Fetched < AbstractEvent
7
- end
8
- end
9
- end
10
- end
@@ -1,30 +0,0 @@
1
- require 'twterm/event/notification/abstract_notification'
2
-
3
- module Twterm
4
- module Event
5
- module Notification
6
- class DirectMessage < AbstractNotification
7
- # @param [Twterm::DirectMessage] message
8
- # @param [Twterm::User] user
9
- def initialize(message, user)
10
- @message = message
11
- @user = user
12
- end
13
-
14
- # @return [String] notification body
15
- def body
16
- message.text
17
- end
18
-
19
- # @return [String] notification title
20
- def title
21
- "@#{user.screen_name} has sent you a message"
22
- end
23
-
24
- private
25
-
26
- attr_reader :message, :user
27
- end
28
- end
29
- end
30
- end
@@ -1,13 +0,0 @@
1
- require 'twterm/event/abstract_event'
2
-
3
- module Twterm
4
- module Event
5
- module Screen
6
- class Resize < AbstractEvent
7
- def fields
8
- { lines: Integer, cols: Integer }
9
- end
10
- end
11
- end
12
- end
13
- end
@@ -1,14 +0,0 @@
1
- require 'twterm/direct_message'
2
- require 'twterm/repository/abstract_entity_repository'
3
-
4
- module Twterm
5
- module Repository
6
- class DirectMessageRepository < AbstractEntityRepository
7
- private
8
-
9
- def type
10
- DirectMessage
11
- end
12
- end
13
- end
14
- end
@@ -1,104 +0,0 @@
1
- require 'twterm/direct_message_composer'
2
- require 'twterm/event/direct_message/fetched'
3
- require 'twterm/image_builder/user_name_image_builder'
4
- require 'twterm/subscriber'
5
- require 'twterm/tab/abstract_tab'
6
- require 'twterm/tab/loadable'
7
- require 'twterm/tab/searchable'
8
-
9
- module Twterm
10
- module Tab
11
- module DirectMessage
12
- class Conversation < AbstractTab
13
- include Searchable
14
- include Subscriber
15
- include Loadable
16
-
17
- def drawable_item_count
18
- messages.drop(scroller.offset).lazy
19
- .map { |m| m.text.split_by_width(window.maxx - 4).count + 2 }
20
- .scan(0, :+)
21
- .each_cons(2)
22
- .select { |_, l| l < window.maxy }
23
- .count
24
- end
25
-
26
- def image
27
- return Image.string(initially_loaded? ? 'No results found' : 'Loading...') if items.empty?
28
-
29
- scroller.drawable_items.map.with_index(0) do |message, i|
30
- sender = app.user_repository.find(message.sender_id)
31
-
32
- header = [
33
- ImageBuilder::UserNameImageBuilder.new(sender).build,
34
- Image.string(message.date.to_s).brackets,
35
- ].intersperse(Image.whitespace).reduce(Image.empty, :-)
36
-
37
- body = message.text.split_by_width(window.maxx - 4)
38
- .map { |x| Image.string(x) }
39
- .reduce(Image.empty, :|)
40
-
41
- m = header | body
42
-
43
- cursor = Image.cursor(m.height, scroller.current_index?(i))
44
-
45
- cursor - Image.whitespace - m
46
- end
47
- .intersperse(Image.blank_line)
48
- .reduce(Image.empty, :|)
49
- end
50
-
51
- def initialize(app, client, conversation)
52
- super(app, client)
53
-
54
- @conversation = conversation
55
-
56
- subscribe(Event::DirectMessage::Fetched) { initially_loaded! }
57
- end
58
-
59
- def items
60
- messages
61
- end
62
-
63
- def matches?(message, query)
64
- sender = app.user_repository.find(message.sender_id)
65
-
66
- [
67
- message.text,
68
- sender.screen_name,
69
- sender.name
70
- ].any? { |x| x.downcase.include?(query.downcase) }
71
- end
72
-
73
- def respond_to_key(key)
74
- return true if scroller.respond_to_key(key)
75
-
76
- k = KeyMapper.instance
77
-
78
- case key
79
- when k[:status, :compose], k[:status, :reply]
80
- collocutor = app.user_repository.find(conversation.collocutor_id)
81
- app.direct_message_composer.compose(collocutor)
82
- else
83
- return false
84
- end
85
-
86
- true
87
- end
88
-
89
- def title
90
- collocutor = app.user_repository.find(conversation.collocutor_id)
91
- '@%s messages' % collocutor.screen_name
92
- end
93
-
94
- private
95
-
96
- attr_reader :conversation
97
-
98
- def messages
99
- @conversation.messages
100
- end
101
- end
102
- end
103
- end
104
- end
@@ -1,100 +0,0 @@
1
- require 'twterm/direct_message_composer'
2
- require 'twterm/event/direct_message/fetched'
3
- require 'twterm/image_builder/user_name_image_builder'
4
- require 'twterm/subscriber'
5
- require 'twterm/tab/abstract_tab'
6
- require 'twterm/tab/loadable'
7
- require 'twterm/tab/direct_message/conversation'
8
-
9
- module Twterm
10
- module Tab
11
- module DirectMessage
12
- class ConversationList < AbstractTab
13
- include Loadable
14
- include Searchable
15
- include Subscriber
16
-
17
- def drawable_item_count
18
- window.maxy.-(2).div(3)
19
- end
20
-
21
- def image
22
- return Image.string(initially_loaded? ? 'No results found' : 'Loading...') if items.empty?
23
-
24
- scroller.drawable_items.map.with_index(0) do |conversation, i|
25
- cursor = Image.cursor(2, scroller.current_index?(i))
26
-
27
- collocutor = app.user_repository.find(conversation.collocutor_id)
28
-
29
- header = [
30
- ImageBuilder::UserNameImageBuilder.new(collocutor).build,
31
- Image.string(conversation.updated_at.to_s).brackets,
32
- ].intersperse(Image.whitespace).reduce(Image.empty, :-)
33
-
34
- body = Image.string(conversation.preview.split_by_width(window.maxx - 4).first)
35
-
36
- cursor - Image.whitespace - (header | body)
37
- end
38
- .intersperse(Image.blank_line)
39
- .reduce(Image.empty, :|)
40
- end
41
-
42
- def initialize(app, client)
43
- super(app, client)
44
-
45
- subscribe(Event::DirectMessage::Fetched) { initially_loaded! }
46
- end
47
-
48
- def ==(other)
49
- other.is_a?(self.class)
50
- end
51
-
52
- def items
53
- client.direct_message_conversations
54
- end
55
-
56
- def matches?(conversation, query)
57
- collocutor = app.user_repository.find(conversation.collocutor_id)
58
-
59
- [
60
- collocutor.name,
61
- collocutor.screen_name,
62
- conversation.preview
63
- ].any? { |x| x.downcase.include?(query.downcase) }
64
- end
65
-
66
- def respond_to_key(key)
67
- return true if scroller.respond_to_key(key)
68
-
69
- k = KeyMapper.instance
70
-
71
- case key
72
- when 10
73
- open_conversation
74
- when k[:status, :compose], k[:status, :reply]
75
- conversation = current_item
76
- collocutor = app.user_repository.find(conversation.collocutor_id)
77
- app.direct_message_composer.compose(collocutor)
78
- else
79
- return false
80
- end
81
-
82
- true
83
- end
84
-
85
- def title
86
- 'Direct Messages'
87
- end
88
-
89
- private
90
-
91
- def open_conversation
92
- conversation = scroller.current_item
93
-
94
- tab = Tab::DirectMessage::Conversation.new(app, client, conversation)
95
- app.tab_manager.add_and_show(tab)
96
- end
97
- end
98
- end
99
- end
100
- end