sup 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/History.txt +10 -0
- data/bin/sup +50 -68
- data/doc/NewUserGuide.txt +11 -7
- data/doc/TODO +34 -22
- data/lib/sup.rb +30 -24
- data/lib/sup/buffer.rb +124 -39
- data/lib/sup/colormap.rb +4 -4
- data/lib/sup/draft.rb +1 -1
- data/lib/sup/hook.rb +18 -5
- data/lib/sup/imap.rb +11 -13
- data/lib/sup/index.rb +52 -14
- data/lib/sup/keymap.rb +1 -1
- data/lib/sup/logger.rb +1 -0
- data/lib/sup/maildir.rb +9 -0
- data/lib/sup/mbox.rb +3 -1
- data/lib/sup/message-chunks.rb +21 -7
- data/lib/sup/message.rb +31 -15
- data/lib/sup/mode.rb +2 -0
- data/lib/sup/modes/buffer-list-mode.rb +7 -3
- data/lib/sup/modes/compose-mode.rb +14 -16
- data/lib/sup/modes/contact-list-mode.rb +2 -2
- data/lib/sup/modes/edit-message-mode.rb +55 -23
- data/lib/sup/modes/forward-mode.rb +22 -5
- data/lib/sup/modes/inbox-mode.rb +3 -7
- data/lib/sup/modes/label-list-mode.rb +30 -10
- data/lib/sup/modes/label-search-results-mode.rb +12 -0
- data/lib/sup/modes/line-cursor-mode.rb +13 -0
- data/lib/sup/modes/log-mode.rb +0 -6
- data/lib/sup/modes/poll-mode.rb +0 -3
- data/lib/sup/modes/reply-mode.rb +19 -11
- data/lib/sup/modes/scroll-mode.rb +111 -20
- data/lib/sup/modes/search-results-mode.rb +21 -0
- data/lib/sup/modes/text-mode.rb +10 -2
- data/lib/sup/modes/thread-index-mode.rb +200 -90
- data/lib/sup/modes/thread-view-mode.rb +27 -10
- data/lib/sup/person.rb +1 -0
- data/lib/sup/poll.rb +15 -7
- data/lib/sup/source.rb +6 -1
- data/lib/sup/suicide.rb +1 -1
- data/lib/sup/textfield.rb +14 -14
- data/lib/sup/thread.rb +6 -2
- data/lib/sup/util.rb +111 -9
- metadata +13 -6
data/lib/sup/keymap.rb
CHANGED
data/lib/sup/logger.rb
CHANGED
data/lib/sup/maildir.rb
CHANGED
@@ -39,6 +39,15 @@ class Maildir < Source
|
|
39
39
|
|
40
40
|
start = @ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}." # couldn't find the most recent email
|
41
41
|
end
|
42
|
+
|
43
|
+
def each_raw_message_line id
|
44
|
+
scan_mailbox
|
45
|
+
with_file_for(id) do |f|
|
46
|
+
until f.eof?
|
47
|
+
yield f.gets
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
42
51
|
|
43
52
|
def load_header id
|
44
53
|
scan_mailbox
|
data/lib/sup/mbox.rb
CHANGED
@@ -30,6 +30,8 @@ module MBox
|
|
30
30
|
/^(In-Reply-To):\s+(.*?)\s*$/i,
|
31
31
|
/^(Reply-To):\s+(.*?)\s*$/i,
|
32
32
|
/^(List-Post):\s+(.*?)\s*$/i,
|
33
|
+
/^(List-Subscribe):\s+(.*?)\s*$/i,
|
34
|
+
/^(List-Unsubscribe):\s+(.*?)\s*$/i,
|
33
35
|
/^(Status):\s+(.*?)\s*$/i: header[last = $1] = $2
|
34
36
|
when /^(Message-Id):\s+(.*?)\s*$/i: header[mid_field = last = $1] = $2
|
35
37
|
|
@@ -40,7 +42,7 @@ module MBox
|
|
40
42
|
/^(Envelope-To):\s+(.*)$/i: header[last = $1] ||= $2
|
41
43
|
|
42
44
|
when /^$/: break
|
43
|
-
when
|
45
|
+
when /^\S+: /: last = nil # some other header we don't care about
|
44
46
|
else
|
45
47
|
header[last] += " " + line.chomp.gsub(/^\s+/, "") if last
|
46
48
|
end
|
data/lib/sup/message-chunks.rb
CHANGED
@@ -20,10 +20,14 @@
|
|
20
20
|
## :open if they want to start expanded (default is to start collapsed).
|
21
21
|
##
|
22
22
|
## If it's not expandable but is viewable, a patina is displayed using
|
23
|
-
|
24
|
-
##if #view! is defined, pressing enter on the widget calls view! and
|
25
|
-
##(if that returns false) #to_s. Otherwise, enter does nothing. This
|
26
|
-
##is how non-inlineable attachments work.
|
23
|
+
## #patina_color and #patina_text, but no toggling is allowed. Instead,
|
24
|
+
## if #view! is defined, pressing enter on the widget calls view! and
|
25
|
+
## (if that returns false) #to_s. Otherwise, enter does nothing. This
|
26
|
+
## is how non-inlineable attachments work.
|
27
|
+
##
|
28
|
+
## Independent of all that, a chunk can be quotable, in which case it's
|
29
|
+
## included as quoted text during a reply. Text, Quotes, and mime-parsed
|
30
|
+
## attachments are quotable; Signatures are not.
|
27
31
|
|
28
32
|
module Redwood
|
29
33
|
module Chunk
|
@@ -45,10 +49,12 @@ EOS
|
|
45
49
|
## raw_content is the post-MIME-decode content. this is used for
|
46
50
|
## saving the attachment to disk.
|
47
51
|
attr_reader :content_type, :filename, :lines, :raw_content
|
52
|
+
bool_reader :quotable
|
48
53
|
|
49
54
|
def initialize content_type, filename, encoded_content, sibling_types
|
50
55
|
@content_type = content_type
|
51
56
|
@filename = filename
|
57
|
+
@quotable = false # only quotable if we can parse it through the mime-decode hook
|
52
58
|
@raw_content =
|
53
59
|
if encoded_content.body
|
54
60
|
encoded_content.decode
|
@@ -64,7 +70,10 @@ EOS
|
|
64
70
|
text = HookManager.run "mime-decode", :content_type => content_type,
|
65
71
|
:filename => lambda { write_to_disk },
|
66
72
|
:sibling_types => sibling_types
|
67
|
-
|
73
|
+
if text
|
74
|
+
@quotable = true
|
75
|
+
text.split("\n")
|
76
|
+
end
|
68
77
|
end
|
69
78
|
end
|
70
79
|
|
@@ -86,7 +95,7 @@ EOS
|
|
86
95
|
def viewable?; @lines.nil? end
|
87
96
|
def view!
|
88
97
|
path = write_to_disk
|
89
|
-
system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path}
|
98
|
+
system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path} > /dev/null 2> /dev/null"
|
90
99
|
$? == 0
|
91
100
|
end
|
92
101
|
|
@@ -111,10 +120,11 @@ EOS
|
|
111
120
|
@lines = lines.map { |l| l.chomp.wrap WRAP_LEN }.flatten # wrap
|
112
121
|
|
113
122
|
## trim off all empty lines except one
|
114
|
-
lines.pop while lines.
|
123
|
+
@lines.pop while @lines.length > 1 && @lines[-1] =~ /^\s*$/ && @lines[-2] =~ /^\s*$/
|
115
124
|
end
|
116
125
|
|
117
126
|
def inlineable?; true end
|
127
|
+
def quotable?; true end
|
118
128
|
def expandable?; false end
|
119
129
|
def viewable?; false end
|
120
130
|
def color; :none end
|
@@ -127,6 +137,7 @@ EOS
|
|
127
137
|
end
|
128
138
|
|
129
139
|
def inlineable?; @lines.length == 1 end
|
140
|
+
def quotable?; true end
|
130
141
|
def expandable?; !inlineable? end
|
131
142
|
def viewable?; false end
|
132
143
|
|
@@ -142,6 +153,7 @@ EOS
|
|
142
153
|
end
|
143
154
|
|
144
155
|
def inlineable?; @lines.length == 1 end
|
156
|
+
def quotable?; false end
|
145
157
|
def expandable?; !inlineable? end
|
146
158
|
def viewable?; false end
|
147
159
|
|
@@ -162,6 +174,7 @@ EOS
|
|
162
174
|
end
|
163
175
|
|
164
176
|
def inlineable?; false end
|
177
|
+
def quotable?; false end
|
165
178
|
def expandable?; true end
|
166
179
|
def initial_state; :open end
|
167
180
|
def viewable?; false end
|
@@ -191,6 +204,7 @@ EOS
|
|
191
204
|
def color; patina_color end
|
192
205
|
|
193
206
|
def inlineable?; false end
|
207
|
+
def quotable?; false end
|
194
208
|
def expandable?; !@lines.empty? end
|
195
209
|
def viewable?; false end
|
196
210
|
end
|
data/lib/sup/message.rb
CHANGED
@@ -13,6 +13,10 @@ class MessageFormatError < StandardError; end
|
|
13
13
|
## i would like, for example, to be able to add in a ruby-talk
|
14
14
|
## specific module that would detect and link to /ruby-talk:\d+/
|
15
15
|
## sequences in the text of an email. (how sweet would that be?)
|
16
|
+
##
|
17
|
+
## this class cathces all source exceptions. if the underlying source throws
|
18
|
+
## an error, it is caught and handled.
|
19
|
+
|
16
20
|
class Message
|
17
21
|
SNIPPET_LEN = 80
|
18
22
|
RE_PATTERN = /^((re|re[\[\(]\d[\]\)]):\s*)+/i
|
@@ -35,7 +39,7 @@ class Message
|
|
35
39
|
|
36
40
|
attr_reader :id, :date, :from, :subj, :refs, :replytos, :to, :source,
|
37
41
|
:cc, :bcc, :labels, :list_address, :recipient_email, :replyto,
|
38
|
-
:source_info, :chunks
|
42
|
+
:source_info, :chunks, :list_subscribe, :list_unsubscribe
|
39
43
|
|
40
44
|
bool_reader :dirty, :source_marked_read
|
41
45
|
|
@@ -56,16 +60,24 @@ class Message
|
|
56
60
|
def parse_header header
|
57
61
|
header.each { |k, v| header[k.downcase] = v }
|
58
62
|
|
59
|
-
@from = PersonManager.person_for header["from"]
|
60
|
-
|
61
63
|
@id =
|
62
64
|
if header["message-id"]
|
63
65
|
sanitize_message_id header["message-id"]
|
64
66
|
else
|
65
|
-
"sup-faked-" + Digest::MD5.hexdigest(raw_header)
|
66
|
-
|
67
|
+
returning("sup-faked-" + Digest::MD5.hexdigest(raw_header)) do |id|
|
68
|
+
Redwood::log "faking message-id for message from #@from: #{id}"
|
69
|
+
end
|
67
70
|
end
|
68
71
|
|
72
|
+
@from =
|
73
|
+
if header["from"]
|
74
|
+
PersonManager.person_for header["from"]
|
75
|
+
else
|
76
|
+
name = "Sup Auto-generated Fake Sender <sup@fake.sender.example.com>"
|
77
|
+
Redwood::log "faking from for message #@id: #{name}"
|
78
|
+
PersonManager.person_for name
|
79
|
+
end
|
80
|
+
|
69
81
|
date = header["date"]
|
70
82
|
@date =
|
71
83
|
case date
|
@@ -99,6 +111,8 @@ class Message
|
|
99
111
|
|
100
112
|
@recipient_email = header["envelope-to"] || header["x-original-to"] || header["delivered-to"]
|
101
113
|
@source_marked_read = header["status"] == "RO"
|
114
|
+
@list_subscribe = header["list-subscribe"]
|
115
|
+
@list_unsubscribe = header["list-unsubscribe"]
|
102
116
|
end
|
103
117
|
private :parse_header
|
104
118
|
|
@@ -159,6 +173,7 @@ class Message
|
|
159
173
|
Redwood::log "problem getting messages from #{@source}: #{e.message}"
|
160
174
|
## we need force_to_top here otherwise this window will cover
|
161
175
|
## up the error message one
|
176
|
+
@source.error ||= e
|
162
177
|
Redwood::report_broken_sources :force_to_top => true
|
163
178
|
[Chunk::Text.new(error_message(e.message))]
|
164
179
|
end
|
@@ -184,11 +199,14 @@ The error message was:
|
|
184
199
|
EOS
|
185
200
|
end
|
186
201
|
|
202
|
+
## wrap any source methods that might throw sourceerrors
|
187
203
|
def with_source_errors_handled
|
188
204
|
begin
|
189
205
|
yield
|
190
206
|
rescue SourceError => e
|
191
207
|
Redwood::log "problem getting messages from #{@source}: #{e.message}"
|
208
|
+
@source.error ||= e
|
209
|
+
Redwood::report_broken_sources :force_to_top => true
|
192
210
|
error_message e.message
|
193
211
|
end
|
194
212
|
end
|
@@ -218,11 +236,11 @@ EOS
|
|
218
236
|
].flatten.compact.join " "
|
219
237
|
end
|
220
238
|
|
221
|
-
def
|
222
|
-
chunks.find_all { |c| c.
|
239
|
+
def quotable_body_lines
|
240
|
+
chunks.find_all { |c| c.quotable? }.map { |c| c.lines }.flatten
|
223
241
|
end
|
224
242
|
|
225
|
-
def
|
243
|
+
def quotable_header_lines
|
226
244
|
["From: #{@from.full_address}"] +
|
227
245
|
(@to.empty? ? [] : ["To: " + @to.map { |p| p.full_address }.join(", ")]) +
|
228
246
|
(@cc.empty? ? [] : ["Cc: " + @cc.map { |p| p.full_address }.join(", ")]) +
|
@@ -334,11 +352,9 @@ private
|
|
334
352
|
else
|
335
353
|
filename =
|
336
354
|
## first, paw through the headers looking for a filename
|
337
|
-
if m.header["Content-Disposition"] &&
|
338
|
-
m.header["Content-Disposition"] =~ /filename="?(.*?[^\\])("|;|$)/
|
355
|
+
if m.header["Content-Disposition"] && m.header["Content-Disposition"] =~ /filename="?(.*?[^\\])("|;|$)/
|
339
356
|
$1
|
340
|
-
elsif m.header["Content-Type"] &&
|
341
|
-
m.header["Content-Type"] =~ /name=(.*?)(;|$)/
|
357
|
+
elsif m.header["Content-Type"] && m.header["Content-Type"] =~ /name="?(.*?[^\\])("|;|$)/
|
342
358
|
$1
|
343
359
|
|
344
360
|
## haven't found one, but it's a non-text message. fake
|
@@ -360,11 +376,11 @@ private
|
|
360
376
|
end
|
361
377
|
|
362
378
|
def self.convert_from body, charset
|
363
|
-
return body unless charset
|
364
|
-
|
365
379
|
begin
|
380
|
+
raise MessageFormatError, "RubyMail decode returned a null body" unless body
|
381
|
+
return body unless charset
|
366
382
|
Iconv.iconv($encoding, charset, body).join
|
367
|
-
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
|
383
|
+
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence, MessageFormatError => e
|
368
384
|
Redwood::log "warning: error (#{e.class.name}) decoding message body from #{charset}: #{e.message}"
|
369
385
|
File.open("sup-unable-to-decode.txt", "w") { |f| f.write body }
|
370
386
|
body
|
data/lib/sup/mode.rb
CHANGED
@@ -3,7 +3,7 @@ module Redwood
|
|
3
3
|
class BufferListMode < LineCursorMode
|
4
4
|
register_keymap do |k|
|
5
5
|
k.add :jump_to_buffer, "Jump to selected buffer", :enter
|
6
|
-
k.add :reload, "Reload buffer list", "
|
6
|
+
k.add :reload, "Reload buffer list", "@"
|
7
7
|
end
|
8
8
|
|
9
9
|
def initialize
|
@@ -11,8 +11,12 @@ class BufferListMode < LineCursorMode
|
|
11
11
|
super
|
12
12
|
end
|
13
13
|
|
14
|
-
def lines; @text.length
|
15
|
-
def [] i; @text[i]
|
14
|
+
def lines; @text.length end
|
15
|
+
def [] i; @text[i] end
|
16
|
+
|
17
|
+
def focus
|
18
|
+
reload # buffers may have been killed or created since last view
|
19
|
+
end
|
16
20
|
|
17
21
|
protected
|
18
22
|
|
@@ -1,23 +1,10 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
|
-
module CanSpawnComposeMode
|
4
|
-
def spawn_compose_mode opts={}
|
5
|
-
to = opts[:to] || BufferManager.ask_for_contacts(:people, "To: ") or return
|
6
|
-
cc = opts[:cc] || BufferManager.ask_for_contacts(:people, "Cc: ") or return if $config[:ask_for_cc]
|
7
|
-
bcc = opts[:bcc] || BufferManager.ask_for_contacts(:people, "Bcc: ") or return if $config[:ask_for_bcc]
|
8
|
-
|
9
|
-
mode = ComposeMode.new :to => to, :cc => cc, :bcc => bcc
|
10
|
-
BufferManager.spawn "New Message", mode
|
11
|
-
mode.edit_message
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
3
|
class ComposeMode < EditMessageMode
|
16
4
|
def initialize opts={}
|
17
|
-
header = {
|
18
|
-
|
19
|
-
}
|
20
|
-
|
5
|
+
header = {}
|
6
|
+
header["From"] = (opts[:from] || AccountManager.default_account).full_address
|
7
|
+
header["To"] = opts[:to].map { |p| p.full_address }.join(", ") if opts[:to]
|
21
8
|
header["To"] = opts[:to].map { |p| p.full_address }.join(", ") if opts[:to]
|
22
9
|
header["Cc"] = opts[:cc].map { |p| p.full_address }.join(", ") if opts[:cc]
|
23
10
|
header["Bcc"] = opts[:bcc].map { |p| p.full_address }.join(", ") if opts[:bcc]
|
@@ -31,6 +18,17 @@ class ComposeMode < EditMessageMode
|
|
31
18
|
BufferManager.kill_buffer self.buffer unless edited
|
32
19
|
edited
|
33
20
|
end
|
21
|
+
|
22
|
+
def self.spawn_nicely opts={}
|
23
|
+
to = opts[:to] || BufferManager.ask_for_contacts(:people, "To: ") or return
|
24
|
+
cc = opts[:cc] || BufferManager.ask_for_contacts(:people, "Cc: ") or return if $config[:ask_for_cc]
|
25
|
+
bcc = opts[:bcc] || BufferManager.ask_for_contacts(:people, "Bcc: ") or return if $config[:ask_for_bcc]
|
26
|
+
subj = opts[:subj] || BufferManager.ask(:subject, "Subject: ") or return if $config[:ask_for_subject]
|
27
|
+
|
28
|
+
mode = ComposeMode.new :from => opts[:from], :to => to, :cc => cc, :bcc => bcc, :subj => subj
|
29
|
+
BufferManager.spawn "New Message", mode
|
30
|
+
mode.edit_message
|
31
|
+
end
|
34
32
|
end
|
35
33
|
|
36
34
|
end
|
@@ -59,7 +59,7 @@ class ContactListMode < LineCursorMode
|
|
59
59
|
@num += num
|
60
60
|
load
|
61
61
|
update
|
62
|
-
BufferManager.flash "Added #{num}
|
62
|
+
BufferManager.flash "Added #{num.pluralize 'contact'}."
|
63
63
|
end
|
64
64
|
|
65
65
|
def multi_select people
|
@@ -94,7 +94,7 @@ class ContactListMode < LineCursorMode
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def load_in_background
|
97
|
-
Redwood::reporting_thread do
|
97
|
+
Redwood::reporting_thread("contact manager load in bg") do
|
98
98
|
load
|
99
99
|
update
|
100
100
|
BufferManager.draw_screen
|
@@ -13,7 +13,7 @@ class EditMessageMode < LineCursorMode
|
|
13
13
|
NON_EDITABLE_HEADERS = %w(Message-Id Date)
|
14
14
|
|
15
15
|
HookManager.register "signature", <<EOS
|
16
|
-
Generates a signature
|
16
|
+
Generates a message signature.
|
17
17
|
Variables:
|
18
18
|
header: an object that supports string-to-string hashtable-style access
|
19
19
|
to the raw headers for the message. E.g., header["From"],
|
@@ -24,13 +24,26 @@ Return value:
|
|
24
24
|
use the default signature.
|
25
25
|
EOS
|
26
26
|
|
27
|
+
HookManager.register "before-edit", <<EOS
|
28
|
+
Modifies message body and headers before editing a new message. Variables
|
29
|
+
should be modified in place.
|
30
|
+
Variables:
|
31
|
+
header: a hash of headers. See 'signature' hook for documentation.
|
32
|
+
body: an array of lines of body text.
|
33
|
+
Return value:
|
34
|
+
none
|
35
|
+
EOS
|
36
|
+
|
27
37
|
attr_reader :status
|
28
38
|
attr_accessor :body, :header
|
29
39
|
bool_reader :edited
|
30
40
|
|
31
41
|
register_keymap do |k|
|
32
42
|
k.add :send_message, "Send message", 'y'
|
33
|
-
k.add :
|
43
|
+
k.add :edit_message_or_field, "Edit selected field", 'e'
|
44
|
+
k.add :edit_to, "Edit To:", 't'
|
45
|
+
k.add :edit_cc, "Edit Cc:", 'c'
|
46
|
+
k.add :edit_subject, "Edit Subject", 's'
|
34
47
|
k.add :edit_message, "Edit message", :enter
|
35
48
|
k.add :save_as_draft, "Save as draft", 'P'
|
36
49
|
k.add :attach_file, "Attach a file", 'a'
|
@@ -48,6 +61,8 @@ EOS
|
|
48
61
|
@message_id = "<#{Time.now.to_i}-sup-#{rand 10000}@#{Socket.gethostname}>"
|
49
62
|
@edited = false
|
50
63
|
@skip_top_rows = opts[:skip_top_rows] || 0
|
64
|
+
|
65
|
+
HookManager.run "before-edit", :header => @header, :body => @body
|
51
66
|
|
52
67
|
super opts
|
53
68
|
regen_text
|
@@ -59,33 +74,18 @@ EOS
|
|
59
74
|
## a hook
|
60
75
|
def handle_new_text header, body; end
|
61
76
|
|
62
|
-
def
|
77
|
+
def edit_message_or_field
|
63
78
|
if (curpos - @skip_top_rows) >= @header_lines.length
|
64
79
|
edit_message
|
65
80
|
else
|
66
|
-
|
67
|
-
when "Subject"
|
68
|
-
text = BufferManager.ask :subject, "Subject: ", @header[field]
|
69
|
-
@header[field] = parse_header field, text if text
|
70
|
-
else
|
71
|
-
default =
|
72
|
-
case field
|
73
|
-
when *MULTI_HEADERS
|
74
|
-
@header[field].join(", ")
|
75
|
-
else
|
76
|
-
@header[field]
|
77
|
-
end
|
78
|
-
|
79
|
-
contacts = BufferManager.ask_for_contacts :people, "#{field}: ", default
|
80
|
-
if contacts
|
81
|
-
text = contacts.map { |s| s.longname }.join(", ")
|
82
|
-
@header[field] = parse_header field, text
|
83
|
-
end
|
84
|
-
end
|
85
|
-
update
|
81
|
+
edit_field @header_lines[curpos - @skip_top_rows]
|
86
82
|
end
|
87
83
|
end
|
88
84
|
|
85
|
+
def edit_to; edit_field "To" end
|
86
|
+
def edit_cc; edit_field "Cc" end
|
87
|
+
def edit_subject; edit_field "Subject" end
|
88
|
+
|
89
89
|
def edit_message
|
90
90
|
@file = Tempfile.new "sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}"
|
91
91
|
@file.puts format_headers(@header - NON_EDITABLE_HEADERS).first
|
@@ -139,6 +139,8 @@ protected
|
|
139
139
|
header, @header_lines = format_headers(@header - NON_EDITABLE_HEADERS) + [""]
|
140
140
|
@text = header + [""] + @body
|
141
141
|
@text += sig_lines unless $config[:edit_signature]
|
142
|
+
|
143
|
+
@attachment_lines_offset = 0
|
142
144
|
|
143
145
|
unless @attachments.empty?
|
144
146
|
@text += [""]
|
@@ -298,6 +300,36 @@ EOS
|
|
298
300
|
f.puts sig_lines if full unless $config[:edit_signature]
|
299
301
|
end
|
300
302
|
|
303
|
+
protected
|
304
|
+
|
305
|
+
def edit_field field
|
306
|
+
case field
|
307
|
+
when "Subject"
|
308
|
+
text = BufferManager.ask :subject, "Subject: ", @header[field]
|
309
|
+
if text
|
310
|
+
@header[field] = parse_header field, text
|
311
|
+
update
|
312
|
+
field
|
313
|
+
end
|
314
|
+
else
|
315
|
+
default =
|
316
|
+
case field
|
317
|
+
when *MULTI_HEADERS
|
318
|
+
@header[field].join(", ")
|
319
|
+
else
|
320
|
+
@header[field]
|
321
|
+
end
|
322
|
+
|
323
|
+
contacts = BufferManager.ask_for_contacts :people, "#{field}: ", default
|
324
|
+
if contacts
|
325
|
+
text = contacts.map { |s| s.longname }.join(", ")
|
326
|
+
@header[field] = parse_header field, text
|
327
|
+
update
|
328
|
+
field
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
301
333
|
private
|
302
334
|
|
303
335
|
def sanitize_body body
|