twterm 2.6.0 → 2.10.1

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 (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