sup 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +12 -0
- data/CONTRIBUTORS +84 -0
- data/Gemfile +3 -0
- data/HACKING +42 -0
- data/History.txt +361 -0
- data/LICENSE +280 -0
- data/README.md +70 -0
- data/Rakefile +12 -0
- data/ReleaseNotes +231 -0
- data/bin/sup +434 -0
- data/bin/sup-add +118 -0
- data/bin/sup-config +243 -0
- data/bin/sup-dump +43 -0
- data/bin/sup-import-dump +101 -0
- data/bin/sup-psych-ify-config-files +21 -0
- data/bin/sup-recover-sources +87 -0
- data/bin/sup-sync +210 -0
- data/bin/sup-sync-back-maildir +127 -0
- data/bin/sup-tweak-labels +140 -0
- data/contrib/colorpicker.rb +100 -0
- data/contrib/completion/_sup.zsh +114 -0
- data/devel/console.sh +3 -0
- data/devel/count-loc.sh +3 -0
- data/devel/load-index.rb +9 -0
- data/devel/profile.rb +12 -0
- data/devel/start-console.rb +5 -0
- data/doc/FAQ.txt +119 -0
- data/doc/Hooks.txt +79 -0
- data/doc/Philosophy.txt +69 -0
- data/lib/sup.rb +467 -0
- data/lib/sup/account.rb +90 -0
- data/lib/sup/buffer.rb +768 -0
- data/lib/sup/colormap.rb +239 -0
- data/lib/sup/contact.rb +67 -0
- data/lib/sup/crypto.rb +461 -0
- data/lib/sup/draft.rb +119 -0
- data/lib/sup/hook.rb +159 -0
- data/lib/sup/horizontal_selector.rb +59 -0
- data/lib/sup/idle.rb +42 -0
- data/lib/sup/index.rb +882 -0
- data/lib/sup/interactive_lock.rb +89 -0
- data/lib/sup/keymap.rb +140 -0
- data/lib/sup/label.rb +87 -0
- data/lib/sup/logger.rb +77 -0
- data/lib/sup/logger/singleton.rb +10 -0
- data/lib/sup/maildir.rb +257 -0
- data/lib/sup/mbox.rb +187 -0
- data/lib/sup/message.rb +803 -0
- data/lib/sup/message_chunks.rb +328 -0
- data/lib/sup/mode.rb +140 -0
- data/lib/sup/modes/buffer_list_mode.rb +50 -0
- data/lib/sup/modes/completion_mode.rb +55 -0
- data/lib/sup/modes/compose_mode.rb +38 -0
- data/lib/sup/modes/console_mode.rb +125 -0
- data/lib/sup/modes/contact_list_mode.rb +148 -0
- data/lib/sup/modes/edit_message_async_mode.rb +110 -0
- data/lib/sup/modes/edit_message_mode.rb +728 -0
- data/lib/sup/modes/file_browser_mode.rb +109 -0
- data/lib/sup/modes/forward_mode.rb +82 -0
- data/lib/sup/modes/help_mode.rb +19 -0
- data/lib/sup/modes/inbox_mode.rb +85 -0
- data/lib/sup/modes/label_list_mode.rb +138 -0
- data/lib/sup/modes/label_search_results_mode.rb +38 -0
- data/lib/sup/modes/line_cursor_mode.rb +203 -0
- data/lib/sup/modes/log_mode.rb +57 -0
- data/lib/sup/modes/person_search_results_mode.rb +12 -0
- data/lib/sup/modes/poll_mode.rb +19 -0
- data/lib/sup/modes/reply_mode.rb +228 -0
- data/lib/sup/modes/resume_mode.rb +52 -0
- data/lib/sup/modes/scroll_mode.rb +252 -0
- data/lib/sup/modes/search_list_mode.rb +204 -0
- data/lib/sup/modes/search_results_mode.rb +59 -0
- data/lib/sup/modes/text_mode.rb +76 -0
- data/lib/sup/modes/thread_index_mode.rb +1033 -0
- data/lib/sup/modes/thread_view_mode.rb +941 -0
- data/lib/sup/person.rb +134 -0
- data/lib/sup/poll.rb +272 -0
- data/lib/sup/rfc2047.rb +56 -0
- data/lib/sup/search.rb +110 -0
- data/lib/sup/sent.rb +58 -0
- data/lib/sup/service/label_service.rb +45 -0
- data/lib/sup/source.rb +244 -0
- data/lib/sup/tagger.rb +50 -0
- data/lib/sup/textfield.rb +253 -0
- data/lib/sup/thread.rb +452 -0
- data/lib/sup/time.rb +93 -0
- data/lib/sup/undo.rb +38 -0
- data/lib/sup/update.rb +30 -0
- data/lib/sup/util.rb +747 -0
- data/lib/sup/util/ncurses.rb +274 -0
- data/lib/sup/util/path.rb +9 -0
- data/lib/sup/util/query.rb +17 -0
- data/lib/sup/util/uri.rb +15 -0
- data/lib/sup/version.rb +3 -0
- data/sup.gemspec +53 -0
- data/test/dummy_source.rb +61 -0
- data/test/gnupg_test_home/gpg.conf +1 -0
- data/test/gnupg_test_home/pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_secring.gpg +0 -0
- data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -0
- data/test/gnupg_test_home/trustdb.gpg +0 -0
- data/test/integration/test_label_service.rb +18 -0
- data/test/messages/bad-content-transfer-encoding-1.eml +8 -0
- data/test/messages/binary-content-transfer-encoding-2.eml +21 -0
- data/test/messages/missing-line.eml +9 -0
- data/test/test_crypto.rb +109 -0
- data/test/test_header_parsing.rb +168 -0
- data/test/test_helper.rb +7 -0
- data/test/test_message.rb +532 -0
- data/test/test_messages_dir.rb +147 -0
- data/test/test_yaml_migration.rb +85 -0
- data/test/test_yaml_regressions.rb +17 -0
- data/test/unit/service/test_label_service.rb +19 -0
- data/test/unit/test_horizontal_selector.rb +40 -0
- data/test/unit/util/test_query.rb +46 -0
- data/test/unit/util/test_string.rb +57 -0
- data/test/unit/util/test_uri.rb +19 -0
- metadata +423 -0
@@ -0,0 +1,203 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
## extends ScrollMode to have a line-based cursor.
|
4
|
+
class LineCursorMode < ScrollMode
|
5
|
+
register_keymap do |k|
|
6
|
+
## overwrite scrollmode binding on arrow keys for cursor movement
|
7
|
+
## but j and k still scroll!
|
8
|
+
k.add :cursor_down, "Move cursor down one line", :down, 'j'
|
9
|
+
k.add :cursor_up, "Move cursor up one line", :up, 'k'
|
10
|
+
k.add :select, "Select this item", :enter
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :curpos
|
14
|
+
|
15
|
+
def initialize opts={}
|
16
|
+
@cursor_top = @curpos = opts.delete(:skip_top_rows) || 0
|
17
|
+
@load_more_callbacks = []
|
18
|
+
@load_more_q = Queue.new
|
19
|
+
@load_more_thread = ::Thread.new do
|
20
|
+
while true
|
21
|
+
e = @load_more_q.pop
|
22
|
+
@load_more_callbacks.each { |c| c.call e }
|
23
|
+
sleep 0.5
|
24
|
+
@load_more_q.pop until @load_more_q.empty?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
super opts
|
29
|
+
end
|
30
|
+
|
31
|
+
def cleanup
|
32
|
+
@load_more_thread.kill
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def draw
|
37
|
+
super
|
38
|
+
set_status
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
## callbacks when the cursor is asked to go beyond the bottom
|
44
|
+
def to_load_more &b
|
45
|
+
@load_more_callbacks << b
|
46
|
+
end
|
47
|
+
|
48
|
+
def draw_line ln, opts={}
|
49
|
+
if ln == @curpos
|
50
|
+
super ln, :highlight => true, :debug => opts[:debug], :color => :text_color
|
51
|
+
else
|
52
|
+
super ln, :color => :text_color
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def ensure_mode_validity
|
57
|
+
super
|
58
|
+
raise @curpos.inspect unless @curpos.is_a?(Integer)
|
59
|
+
c = @curpos.clamp topline, botline - 1
|
60
|
+
c = @cursor_top if c < @cursor_top
|
61
|
+
buffer.mark_dirty unless c == @curpos
|
62
|
+
@curpos = c
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_cursor_pos p
|
66
|
+
return if @curpos == p
|
67
|
+
@curpos = p.clamp @cursor_top, lines
|
68
|
+
buffer.mark_dirty
|
69
|
+
set_status
|
70
|
+
end
|
71
|
+
|
72
|
+
## override search behavior to be cursor-based. this is a stupid
|
73
|
+
## implementation and should be made better. TODO: improve.
|
74
|
+
def search_goto_line line
|
75
|
+
page_down while line >= botline
|
76
|
+
page_up while line < topline
|
77
|
+
set_cursor_pos line
|
78
|
+
end
|
79
|
+
|
80
|
+
def search_start_line; @curpos end
|
81
|
+
|
82
|
+
def line_down # overwrite scrollmode
|
83
|
+
super
|
84
|
+
call_load_more_callbacks([topline + buffer.content_height - lines, 10].max) if topline + buffer.content_height > lines
|
85
|
+
set_cursor_pos topline if @curpos < topline
|
86
|
+
end
|
87
|
+
|
88
|
+
def line_up # overwrite scrollmode
|
89
|
+
super
|
90
|
+
set_cursor_pos botline - 1 if @curpos > botline - 1
|
91
|
+
end
|
92
|
+
|
93
|
+
def cursor_down
|
94
|
+
call_load_more_callbacks buffer.content_height if @curpos >= lines - [buffer.content_height/2,1].max
|
95
|
+
return false unless @curpos < lines - 1
|
96
|
+
|
97
|
+
if $config[:continuous_scroll] and (@curpos == botline - 3 and @curpos < lines - 3)
|
98
|
+
# load more lines, one at a time.
|
99
|
+
jump_to_line topline + 1
|
100
|
+
@curpos += 1
|
101
|
+
unless buffer.dirty?
|
102
|
+
draw_line @curpos - 1
|
103
|
+
draw_line @curpos
|
104
|
+
set_status
|
105
|
+
buffer.commit
|
106
|
+
end
|
107
|
+
elsif @curpos >= botline - 1
|
108
|
+
page_down
|
109
|
+
set_cursor_pos topline
|
110
|
+
else
|
111
|
+
@curpos += 1
|
112
|
+
unless buffer.dirty?
|
113
|
+
draw_line @curpos - 1
|
114
|
+
draw_line @curpos
|
115
|
+
set_status
|
116
|
+
buffer.commit
|
117
|
+
end
|
118
|
+
end
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
def cursor_up
|
123
|
+
return false unless @curpos > @cursor_top
|
124
|
+
|
125
|
+
if $config[:continuous_scroll] and (@curpos == topline + 2)
|
126
|
+
jump_to_line topline - 1
|
127
|
+
@curpos -= 1
|
128
|
+
unless buffer.dirty?
|
129
|
+
draw_line @curpos + 1
|
130
|
+
draw_line @curpos
|
131
|
+
set_status
|
132
|
+
buffer.commit
|
133
|
+
end
|
134
|
+
elsif @curpos == topline
|
135
|
+
old_topline = topline
|
136
|
+
page_up
|
137
|
+
set_cursor_pos [old_topline - 1, topline].max
|
138
|
+
else
|
139
|
+
@curpos -= 1
|
140
|
+
unless buffer.dirty?
|
141
|
+
draw_line @curpos + 1
|
142
|
+
draw_line @curpos
|
143
|
+
set_status
|
144
|
+
buffer.commit
|
145
|
+
end
|
146
|
+
end
|
147
|
+
true
|
148
|
+
end
|
149
|
+
|
150
|
+
def page_up # overwrite
|
151
|
+
if topline <= @cursor_top
|
152
|
+
set_cursor_pos @cursor_top
|
153
|
+
else
|
154
|
+
relpos = @curpos - topline
|
155
|
+
super
|
156
|
+
set_cursor_pos topline + relpos
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
## more complicated than one might think. three behaviors.
|
161
|
+
def page_down
|
162
|
+
## if we're on the last page, and it's not a full page, just move
|
163
|
+
## the cursor down to the bottom and assume we can't load anything
|
164
|
+
## else via the callbacks.
|
165
|
+
if topline > lines - buffer.content_height
|
166
|
+
set_cursor_pos(lines - 1)
|
167
|
+
|
168
|
+
## if we're on the last page, and it's a full page, try and load
|
169
|
+
## more lines via the callbacks and then shift the page down
|
170
|
+
elsif topline == lines - buffer.content_height
|
171
|
+
call_load_more_callbacks buffer.content_height
|
172
|
+
super
|
173
|
+
|
174
|
+
## otherwise, just move down
|
175
|
+
else
|
176
|
+
relpos = @curpos - topline
|
177
|
+
super
|
178
|
+
set_cursor_pos [topline + relpos, lines - 1].min
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def jump_to_start
|
183
|
+
super
|
184
|
+
set_cursor_pos @cursor_top
|
185
|
+
end
|
186
|
+
|
187
|
+
def jump_to_end
|
188
|
+
super if topline < (lines - buffer.content_height)
|
189
|
+
set_cursor_pos(lines - 1)
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def set_status
|
195
|
+
l = lines
|
196
|
+
@status = l > 0 ? "line #{@curpos + 1} of #{l}" : ""
|
197
|
+
end
|
198
|
+
|
199
|
+
def call_load_more_callbacks size
|
200
|
+
@load_more_q.push size if $config[:load_more_threads_when_scrolling]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
module Redwood
|
3
|
+
|
4
|
+
## a variant of text mode that allows the user to automatically follow text,
|
5
|
+
## and respawns when << is called if necessary.
|
6
|
+
|
7
|
+
class LogMode < TextMode
|
8
|
+
register_keymap do |k|
|
9
|
+
k.add :toggle_follow, "Toggle follow mode", 'f'
|
10
|
+
end
|
11
|
+
|
12
|
+
## if buffer_name is supplied, this mode will spawn a buffer
|
13
|
+
## upon receiving the << message. otherwise, it will act like
|
14
|
+
## a regular buffer.
|
15
|
+
def initialize autospawn_buffer_name=nil
|
16
|
+
@follow = true
|
17
|
+
@autospawn_buffer_name = autospawn_buffer_name
|
18
|
+
@on_kill = []
|
19
|
+
super()
|
20
|
+
end
|
21
|
+
|
22
|
+
## register callbacks for when the buffer is killed
|
23
|
+
def on_kill &b; @on_kill << b end
|
24
|
+
|
25
|
+
def toggle_follow
|
26
|
+
@follow = !@follow
|
27
|
+
if @follow
|
28
|
+
jump_to_line(lines - buffer.content_height + 1) # leave an empty line at bottom
|
29
|
+
end
|
30
|
+
buffer.mark_dirty
|
31
|
+
end
|
32
|
+
|
33
|
+
def << s
|
34
|
+
if buffer.nil? && @autospawn_buffer_name
|
35
|
+
BufferManager.spawn @autospawn_buffer_name, self, :hidden => true, :system => true
|
36
|
+
end
|
37
|
+
|
38
|
+
s.split("\n").each { |l| super(l + "\n") } # insane. different << semantics.
|
39
|
+
|
40
|
+
if @follow
|
41
|
+
follow_top = lines - buffer.content_height + 1
|
42
|
+
jump_to_line follow_top if topline < follow_top
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def status
|
47
|
+
super + " (follow: #@follow)"
|
48
|
+
end
|
49
|
+
|
50
|
+
def cleanup
|
51
|
+
@on_kill.each { |cb| cb.call self }
|
52
|
+
self.text = ""
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class PollMode < LogMode
|
4
|
+
def initialize
|
5
|
+
@new = true
|
6
|
+
super "poll for new messages"
|
7
|
+
end
|
8
|
+
|
9
|
+
def poll
|
10
|
+
unless @new
|
11
|
+
@new = false
|
12
|
+
self << "\n"
|
13
|
+
end
|
14
|
+
self << "Poll started at #{Time.now}\n"
|
15
|
+
PollManager.do_poll { |s| self << (s + "\n") }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class ReplyMode < EditMessageMode
|
4
|
+
REPLY_TYPES = [:sender, :recipient, :list, :all, :user]
|
5
|
+
TYPE_DESCRIPTIONS = {
|
6
|
+
:sender => "Sender",
|
7
|
+
:recipient => "Recipient",
|
8
|
+
:all => "All",
|
9
|
+
:list => "Mailing list",
|
10
|
+
:user => "Customized"
|
11
|
+
}
|
12
|
+
|
13
|
+
HookManager.register "attribution", <<EOS
|
14
|
+
Generates an attribution ("Excerpts from Joe Bloggs's message of Fri Jan 11 09:54:32 -0500 2008:").
|
15
|
+
Variables:
|
16
|
+
message: a message object representing the message being replied to
|
17
|
+
(useful values include message.from.name and message.date)
|
18
|
+
Return value:
|
19
|
+
A string containing the text of the quote line (can be multi-line)
|
20
|
+
EOS
|
21
|
+
|
22
|
+
HookManager.register "reply-from", <<EOS
|
23
|
+
Selects a default address for the From: header of a new reply.
|
24
|
+
Variables:
|
25
|
+
message: a message object representing the message being replied to
|
26
|
+
(useful values include message.recipient_email, message.to, and message.cc)
|
27
|
+
Return value:
|
28
|
+
A Person to be used as the default for the From: header, or nil to use the
|
29
|
+
default behavior.
|
30
|
+
EOS
|
31
|
+
|
32
|
+
HookManager.register "reply-to", <<EOS
|
33
|
+
Set the default reply-to mode.
|
34
|
+
Variables:
|
35
|
+
modes: array of valid modes to choose from, which will be a subset of
|
36
|
+
[:#{REPLY_TYPES * ', :'}]
|
37
|
+
The default behavior is equivalent to
|
38
|
+
([:list, :sender, :recipent] & modes)[0]
|
39
|
+
Return value:
|
40
|
+
The reply mode you desire, or nil to use the default behavior.
|
41
|
+
EOS
|
42
|
+
|
43
|
+
def initialize message, type_arg=nil
|
44
|
+
@m = message
|
45
|
+
@edited = false
|
46
|
+
|
47
|
+
## it's important to put this early because it forces a read of
|
48
|
+
## the full headers (most importantly the list-post header, if
|
49
|
+
## any)
|
50
|
+
body = reply_body_lines message
|
51
|
+
@body_orig = body
|
52
|
+
|
53
|
+
## first, determine the address at which we received this email. this will
|
54
|
+
## become our From: address in the reply.
|
55
|
+
hook_reply_from = HookManager.run "reply-from", :message => @m
|
56
|
+
|
57
|
+
## sanity check that selection is a Person (or we'll fail below)
|
58
|
+
## don't check that it's an Account, though; assume they know what they're
|
59
|
+
## doing.
|
60
|
+
if hook_reply_from && !(hook_reply_from.is_a? Person)
|
61
|
+
info "reply-from returned non-Person, using default from."
|
62
|
+
hook_reply_from = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
## determine the from address of a reply.
|
66
|
+
## if we have a value from a hook, use it.
|
67
|
+
from = if hook_reply_from
|
68
|
+
hook_reply_from
|
69
|
+
## otherwise, try and find an account somewhere in the list of to's
|
70
|
+
## and cc's and look up the corresponding name form the list of accounts.
|
71
|
+
## if this does not succeed use the recipient_email (=envelope-to) instead.
|
72
|
+
## this is for the case where mail is received from a mailing lists (so the
|
73
|
+
## To: is the list id itself). if the user subscribes via a particular
|
74
|
+
## alias, we want to use that alias in the reply.
|
75
|
+
elsif(b = (@m.to.collect {|t| t.email} + @m.cc.collect {|c| c.email} + [@m.recipient_email] ).find { |p| AccountManager.is_account_email? p })
|
76
|
+
a = AccountManager.account_for(b)
|
77
|
+
Person.new a.name, b
|
78
|
+
## if all else fails, use the default
|
79
|
+
else
|
80
|
+
AccountManager.default_account
|
81
|
+
end
|
82
|
+
|
83
|
+
## now, determine to: and cc: addressess. we ignore reply-to for list
|
84
|
+
## messages because it's typically set to the list address, which we
|
85
|
+
## explicitly treat with reply type :list
|
86
|
+
to = @m.is_list_message? ? @m.from : (@m.replyto || @m.from)
|
87
|
+
|
88
|
+
## next, cc:
|
89
|
+
cc = (@m.to + @m.cc - [from, to]).uniq
|
90
|
+
|
91
|
+
## one potential reply type is "reply to recipient". this only happens
|
92
|
+
## in certain cases:
|
93
|
+
## if there's no cc, then the sender is the person you want to reply
|
94
|
+
## to. if it's a list message, then the list address is. otherwise,
|
95
|
+
## the cc contains a recipient.
|
96
|
+
useful_recipient = !(cc.empty? || @m.is_list_message?)
|
97
|
+
|
98
|
+
@headers = {}
|
99
|
+
@headers[:recipient] = {
|
100
|
+
"To" => cc.map { |p| p.full_address },
|
101
|
+
"Cc" => [],
|
102
|
+
} if useful_recipient
|
103
|
+
|
104
|
+
## typically we don't want to have a reply-to-sender option if the sender
|
105
|
+
## is a user account. however, if the cc is empty, it's a message to
|
106
|
+
## ourselves, so for the lack of any other options, we'll add it.
|
107
|
+
@headers[:sender] = {
|
108
|
+
"To" => [to.full_address],
|
109
|
+
"Cc" => [],
|
110
|
+
} if !AccountManager.is_account?(to) || !useful_recipient
|
111
|
+
|
112
|
+
@headers[:user] = {
|
113
|
+
"To" => [],
|
114
|
+
"Cc" => [],
|
115
|
+
}
|
116
|
+
|
117
|
+
not_me_ccs = cc.select { |p| !AccountManager.is_account?(p) }
|
118
|
+
@headers[:all] = {
|
119
|
+
"To" => [to.full_address],
|
120
|
+
"Cc" => not_me_ccs.map { |p| p.full_address },
|
121
|
+
} unless not_me_ccs.empty?
|
122
|
+
|
123
|
+
@headers[:list] = {
|
124
|
+
"To" => [@m.list_address.full_address],
|
125
|
+
"Cc" => [],
|
126
|
+
} if @m.is_list_message?
|
127
|
+
|
128
|
+
refs = gen_references
|
129
|
+
|
130
|
+
types = REPLY_TYPES.select { |t| @headers.member?(t) }
|
131
|
+
@type_selector = HorizontalSelector.new "Reply to:", types, types.map { |x| TYPE_DESCRIPTIONS[x] }
|
132
|
+
|
133
|
+
hook_reply = HookManager.run "reply-to", :modes => types
|
134
|
+
|
135
|
+
@type_selector.set_to(
|
136
|
+
if types.include? type_arg
|
137
|
+
type_arg
|
138
|
+
elsif types.include? hook_reply
|
139
|
+
hook_reply
|
140
|
+
elsif @m.is_list_message?
|
141
|
+
:list
|
142
|
+
elsif @headers.member? :sender
|
143
|
+
:sender
|
144
|
+
else
|
145
|
+
:recipient
|
146
|
+
end)
|
147
|
+
|
148
|
+
headers_full = {
|
149
|
+
"From" => from.full_address,
|
150
|
+
"Bcc" => [],
|
151
|
+
"In-reply-to" => "<#{@m.id}>",
|
152
|
+
"Subject" => Message.reify_subj(@m.subj),
|
153
|
+
"References" => refs,
|
154
|
+
}.merge @headers[@type_selector.val]
|
155
|
+
|
156
|
+
HookManager.run "before-edit", :header => headers_full, :body => body
|
157
|
+
|
158
|
+
super :header => headers_full, :body => body, :twiddles => false
|
159
|
+
add_selector @type_selector
|
160
|
+
end
|
161
|
+
|
162
|
+
protected
|
163
|
+
|
164
|
+
def move_cursor_right
|
165
|
+
super
|
166
|
+
if @headers[@type_selector.val] != self.header
|
167
|
+
self.header = self.header.merge @headers[@type_selector.val]
|
168
|
+
rerun_crypto_selector_hook
|
169
|
+
update
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def move_cursor_left
|
174
|
+
super
|
175
|
+
if @headers[@type_selector.val] != self.header
|
176
|
+
self.header = self.header.merge @headers[@type_selector.val]
|
177
|
+
rerun_crypto_selector_hook
|
178
|
+
update
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def reply_body_lines m
|
183
|
+
attribution = HookManager.run("attribution", :message => m) || default_attribution(m)
|
184
|
+
lines = attribution.split("\n") + m.quotable_body_lines.map { |l| "> #{l}" }
|
185
|
+
lines.pop while lines.last =~ /^\s*$/
|
186
|
+
lines
|
187
|
+
end
|
188
|
+
|
189
|
+
def default_attribution m
|
190
|
+
"Excerpts from #{@m.from.name}'s message of #{@m.date}:"
|
191
|
+
end
|
192
|
+
|
193
|
+
def handle_new_text new_header, new_body
|
194
|
+
if new_body != @body_orig
|
195
|
+
@body_orig = new_body
|
196
|
+
@edited = true
|
197
|
+
end
|
198
|
+
old_header = @headers[@type_selector.val]
|
199
|
+
if old_header.any? { |k, v| new_header[k] != v }
|
200
|
+
@type_selector.set_to :user
|
201
|
+
self.header["To"] = @headers[:user]["To"] = new_header["To"]
|
202
|
+
self.header["Cc"] = @headers[:user]["Cc"] = new_header["Cc"]
|
203
|
+
update
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def gen_references
|
208
|
+
(@m.refs + [@m.id]).map { |x| "<#{x}>" }.join(" ")
|
209
|
+
end
|
210
|
+
|
211
|
+
def edit_field field
|
212
|
+
edited_field = super
|
213
|
+
if edited_field and (field == "To" or field == "Cc")
|
214
|
+
@type_selector.set_to :user
|
215
|
+
@headers[:user]["To"] = self.header["To"]
|
216
|
+
@headers[:user]["Cc"] = self.header["Cc"]
|
217
|
+
update
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def send_message
|
222
|
+
return unless super # super returns true if the mail has been sent
|
223
|
+
@m.add_label :replied
|
224
|
+
Index.save_message @m
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|