sup 0.3 → 0.4
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/HACKING +31 -9
- data/History.txt +7 -0
- data/Manifest.txt +2 -0
- data/Rakefile +9 -5
- data/bin/sup +81 -57
- data/bin/sup-config +1 -1
- data/bin/sup-sync +3 -0
- data/bin/sup-tweak-labels +127 -0
- data/doc/TODO +23 -12
- data/lib/sup.rb +13 -11
- data/lib/sup/account.rb +25 -12
- data/lib/sup/buffer.rb +61 -41
- data/lib/sup/colormap.rb +2 -0
- data/lib/sup/contact.rb +28 -18
- data/lib/sup/crypto.rb +86 -31
- data/lib/sup/draft.rb +12 -6
- data/lib/sup/horizontal-selector.rb +47 -0
- data/lib/sup/imap.rb +50 -37
- data/lib/sup/index.rb +76 -13
- data/lib/sup/keymap.rb +27 -8
- data/lib/sup/maildir.rb +1 -1
- data/lib/sup/mbox/loader.rb +1 -1
- data/lib/sup/message-chunks.rb +43 -15
- data/lib/sup/message.rb +67 -31
- data/lib/sup/mode.rb +40 -9
- data/lib/sup/modes/completion-mode.rb +1 -1
- data/lib/sup/modes/compose-mode.rb +3 -3
- data/lib/sup/modes/contact-list-mode.rb +12 -8
- data/lib/sup/modes/edit-message-mode.rb +100 -36
- data/lib/sup/modes/file-browser-mode.rb +1 -0
- data/lib/sup/modes/forward-mode.rb +43 -8
- data/lib/sup/modes/inbox-mode.rb +8 -5
- data/lib/sup/modes/label-search-results-mode.rb +12 -1
- data/lib/sup/modes/line-cursor-mode.rb +4 -7
- data/lib/sup/modes/reply-mode.rb +59 -54
- data/lib/sup/modes/resume-mode.rb +6 -6
- data/lib/sup/modes/scroll-mode.rb +4 -3
- data/lib/sup/modes/search-results-mode.rb +8 -5
- data/lib/sup/modes/text-mode.rb +19 -2
- data/lib/sup/modes/thread-index-mode.rb +109 -40
- data/lib/sup/modes/thread-view-mode.rb +180 -49
- data/lib/sup/person.rb +3 -3
- data/lib/sup/poll.rb +9 -8
- data/lib/sup/rfc2047.rb +7 -1
- data/lib/sup/sent.rb +1 -1
- data/lib/sup/tagger.rb +10 -4
- data/lib/sup/textfield.rb +7 -7
- data/lib/sup/thread.rb +86 -49
- data/lib/sup/update.rb +11 -0
- data/lib/sup/util.rb +74 -34
- data/test/test_message.rb +441 -0
- metadata +136 -117
data/lib/sup/keymap.rb
CHANGED
@@ -7,7 +7,7 @@ class Keymap
|
|
7
7
|
yield self if block_given?
|
8
8
|
end
|
9
9
|
|
10
|
-
def keysym_to_keycode k
|
10
|
+
def self.keysym_to_keycode k
|
11
11
|
case k
|
12
12
|
when :down: Curses::KEY_DOWN
|
13
13
|
when :up: Curses::KEY_UP
|
@@ -31,7 +31,7 @@ class Keymap
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
def keysym_to_string k
|
34
|
+
def self.keysym_to_string k
|
35
35
|
case k
|
36
36
|
when :down: "<down arrow>"
|
37
37
|
when :up: "<up arrow>"
|
@@ -60,25 +60,44 @@ class Keymap
|
|
60
60
|
entry = [action, help, keys]
|
61
61
|
@order << entry
|
62
62
|
keys.each do |k|
|
63
|
-
|
64
|
-
kc
|
63
|
+
kc = Keymap.keysym_to_keycode k
|
64
|
+
raise ArgumentError, "key '#{k}' already defined (as #{@map[kc].first})" if @map.include? kc
|
65
65
|
@map[kc] = entry
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
def add_multi prompt, key
|
70
|
+
submap = Keymap.new
|
71
|
+
add submap, prompt, key
|
72
|
+
yield submap
|
73
|
+
end
|
74
|
+
|
69
75
|
def action_for kc
|
70
76
|
action, help, keys = @map[kc]
|
71
|
-
action
|
77
|
+
[action, help]
|
72
78
|
end
|
73
79
|
|
80
|
+
def has_key? k; @map[k] end
|
81
|
+
|
74
82
|
def keysyms; @map.values.map { |action, help, keys| keys }.flatten; end
|
75
83
|
|
76
|
-
def
|
77
|
-
lines =
|
84
|
+
def help_lines except_for={}, prefix=""
|
85
|
+
lines = [] # :(
|
86
|
+
@order.each do |action, help, keys|
|
78
87
|
valid_keys = keys.select { |k| !except_for[k] }
|
79
88
|
next if valid_keys.empty?
|
80
|
-
|
89
|
+
case action
|
90
|
+
when Symbol
|
91
|
+
lines << [valid_keys.map { |k| prefix + Keymap.keysym_to_string(k) }.join(", "), help]
|
92
|
+
when Keymap
|
93
|
+
lines += action.help_lines({}, prefix + Keymap.keysym_to_string(keys.first))
|
94
|
+
end
|
81
95
|
end.compact
|
96
|
+
lines
|
97
|
+
end
|
98
|
+
|
99
|
+
def help_text except_for={}
|
100
|
+
lines = help_lines except_for
|
82
101
|
llen = lines.max_of { |a, b| a.length }
|
83
102
|
lines.map { |a, b| sprintf " %#{llen}s : %s", a, b }.join("\n")
|
84
103
|
end
|
data/lib/sup/maildir.rb
CHANGED
data/lib/sup/mbox/loader.rb
CHANGED
data/lib/sup/message-chunks.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
1
3
|
## Here we define all the "chunks" that a message is parsed
|
2
4
|
## into. Chunks are used by ThreadViewMode to render a message. Chunks
|
3
5
|
## are used for both MIME stuff like attachments, for Sup's parsing of
|
@@ -29,6 +31,14 @@
|
|
29
31
|
## included as quoted text during a reply. Text, Quotes, and mime-parsed
|
30
32
|
## attachments are quotable; Signatures are not.
|
31
33
|
|
34
|
+
## monkey-patch time: make temp files have the right extension
|
35
|
+
class Tempfile
|
36
|
+
def make_tmpname basename, n
|
37
|
+
sprintf '%d-%d-%s', $$, n, basename
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
32
42
|
module Redwood
|
33
43
|
module Chunk
|
34
44
|
class Attachment
|
@@ -36,14 +46,23 @@ module Chunk
|
|
36
46
|
Executes when decoding a MIME attachment.
|
37
47
|
Variables:
|
38
48
|
content_type: the content-type of the message
|
39
|
-
filename: the filename of the attachment as saved to disk
|
40
|
-
on the fly, so don't call more than once)
|
49
|
+
filename: the filename of the attachment as saved to disk
|
41
50
|
sibling_types: if this attachment is part of a multipart MIME attachment,
|
42
51
|
an array of content-types for all attachments. Otherwise,
|
43
52
|
the empty array.
|
44
53
|
Return value:
|
45
54
|
The decoded text of the attachment, or nil if not decoded.
|
46
55
|
EOS
|
56
|
+
|
57
|
+
HookManager.register "mime-view", <<EOS
|
58
|
+
Executes when viewing a MIME attachment, i.e., launching a separate
|
59
|
+
viewer program.
|
60
|
+
Variables:
|
61
|
+
content_type: the content-type of the attachment
|
62
|
+
filename: the filename of the attachment as saved to disk
|
63
|
+
Return value:
|
64
|
+
True if the viewing was successful, false otherwise.
|
65
|
+
EOS
|
47
66
|
#' stupid ruby-mode
|
48
67
|
|
49
68
|
## raw_content is the post-MIME-decode content. this is used for
|
@@ -54,7 +73,8 @@ EOS
|
|
54
73
|
def initialize content_type, filename, encoded_content, sibling_types
|
55
74
|
@content_type = content_type
|
56
75
|
@filename = filename
|
57
|
-
@quotable = false #
|
76
|
+
@quotable = false # changed to true if we can parse it through the
|
77
|
+
# mime-decode hook, or if it's plain text
|
58
78
|
@raw_content =
|
59
79
|
if encoded_content.body
|
60
80
|
encoded_content.decode
|
@@ -62,19 +82,21 @@ EOS
|
|
62
82
|
"For some bizarre reason, RubyMail was unable to parse this attachment.\n"
|
63
83
|
end
|
64
84
|
|
65
|
-
|
85
|
+
text =
|
66
86
|
case @content_type
|
67
87
|
when /^text\/plain\b/
|
68
|
-
Message.convert_from
|
88
|
+
Message.convert_from @raw_content, encoded_content.charset
|
69
89
|
else
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
if text
|
74
|
-
@quotable = true
|
75
|
-
text.split("\n")
|
76
|
-
end
|
90
|
+
HookManager.run "mime-decode", :content_type => content_type,
|
91
|
+
:filename => lambda { write_to_disk },
|
92
|
+
:sibling_types => sibling_types
|
77
93
|
end
|
94
|
+
|
95
|
+
@lines = nil
|
96
|
+
if text
|
97
|
+
@lines = text.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
|
98
|
+
@quotable = true
|
99
|
+
end
|
78
100
|
end
|
79
101
|
|
80
102
|
def color; :none end
|
@@ -93,14 +115,20 @@ EOS
|
|
93
115
|
def expandable?; !viewable? end
|
94
116
|
def initial_state; :open end
|
95
117
|
def viewable?; @lines.nil? end
|
96
|
-
def
|
97
|
-
path = write_to_disk
|
118
|
+
def view_default! path
|
98
119
|
system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path} > /dev/null 2> /dev/null"
|
99
120
|
$? == 0
|
100
121
|
end
|
101
122
|
|
123
|
+
def view!
|
124
|
+
path = write_to_disk
|
125
|
+
ret = HookManager.run "mime-view", :content_type => @content_type,
|
126
|
+
:filename => path
|
127
|
+
view_default! path unless ret
|
128
|
+
end
|
129
|
+
|
102
130
|
def write_to_disk
|
103
|
-
file = Tempfile.new "
|
131
|
+
file = Tempfile.new(@filename || "sup-attachment")
|
104
132
|
file.print @raw_content
|
105
133
|
file.close
|
106
134
|
file.path
|
data/lib/sup/message.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'tempfile'
|
2
1
|
require 'time'
|
3
2
|
require 'iconv'
|
4
3
|
|
@@ -30,7 +29,7 @@ class Message
|
|
30
29
|
|
31
30
|
QUOTE_PATTERN = /^\s{0,4}[>|\}]/
|
32
31
|
BLOCK_QUOTE_PATTERN = /^-----\s*Original Message\s*----+$/
|
33
|
-
QUOTE_START_PATTERN =
|
32
|
+
QUOTE_START_PATTERN = /\w.*:$/
|
34
33
|
SIG_PATTERN = /(^-- ?$)|(^\s*----------+\s*$)|(^\s*_________+\s*$)|(^\s*--~--~-)|(^\s*--\+\+\*\*==)/
|
35
34
|
|
36
35
|
MAX_SIG_DISTANCE = 15 # lines from the end
|
@@ -39,45 +38,54 @@ class Message
|
|
39
38
|
|
40
39
|
attr_reader :id, :date, :from, :subj, :refs, :replytos, :to, :source,
|
41
40
|
:cc, :bcc, :labels, :list_address, :recipient_email, :replyto,
|
42
|
-
:source_info, :
|
41
|
+
:source_info, :list_subscribe, :list_unsubscribe
|
43
42
|
|
44
|
-
bool_reader :dirty, :source_marked_read
|
43
|
+
bool_reader :dirty, :source_marked_read, :snippet_contains_encrypted_content
|
45
44
|
|
46
45
|
## if you specify a :header, will use values from that. otherwise,
|
47
46
|
## will try and load the header from the source.
|
48
47
|
def initialize opts
|
49
48
|
@source = opts[:source] or raise ArgumentError, "source can't be nil"
|
50
49
|
@source_info = opts[:source_info] or raise ArgumentError, "source_info can't be nil"
|
51
|
-
@snippet = opts[:snippet]
|
52
|
-
@
|
50
|
+
@snippet = opts[:snippet]
|
51
|
+
@snippet_contains_encrypted_content = false
|
52
|
+
@have_snippet = !(opts[:snippet].nil? || opts[:snippet].empty?)
|
53
53
|
@labels = [] + (opts[:labels] || [])
|
54
54
|
@dirty = false
|
55
|
+
@encrypted = false
|
55
56
|
@chunks = nil
|
56
57
|
|
58
|
+
## we need to initialize this. see comments in parse_header as to
|
59
|
+
## why.
|
60
|
+
@refs = []
|
61
|
+
|
57
62
|
parse_header(opts[:header] || @source.load_header(@source_info))
|
58
63
|
end
|
59
64
|
|
60
65
|
def parse_header header
|
61
66
|
header.each { |k, v| header[k.downcase] = v }
|
62
|
-
|
67
|
+
|
68
|
+
fakeid = nil
|
69
|
+
fakename = nil
|
70
|
+
|
63
71
|
@id =
|
64
72
|
if header["message-id"]
|
65
73
|
sanitize_message_id header["message-id"]
|
66
74
|
else
|
67
|
-
|
68
|
-
Redwood::log "faking message-id for message from #@from: #{id}"
|
69
|
-
end
|
75
|
+
fakeid = "sup-faked-" + Digest::MD5.hexdigest(raw_header)
|
70
76
|
end
|
71
77
|
|
72
78
|
@from =
|
73
79
|
if header["from"]
|
74
80
|
PersonManager.person_for header["from"]
|
75
81
|
else
|
76
|
-
|
77
|
-
|
78
|
-
PersonManager.person_for name
|
82
|
+
fakename = "Sup Auto-generated Fake Sender <sup@fake.sender.example.com>"
|
83
|
+
PersonManager.person_for fakename
|
79
84
|
end
|
80
85
|
|
86
|
+
Redwood::log "faking message-id for message from #@from: #{id}" if fakeid
|
87
|
+
Redwood::log "faking from for message #@id: #{fakename}" if fakename
|
88
|
+
|
81
89
|
date = header["date"]
|
82
90
|
@date =
|
83
91
|
case date
|
@@ -98,7 +106,13 @@ class Message
|
|
98
106
|
@to = PersonManager.people_for header["to"]
|
99
107
|
@cc = PersonManager.people_for header["cc"]
|
100
108
|
@bcc = PersonManager.people_for header["bcc"]
|
101
|
-
|
109
|
+
|
110
|
+
## before loading our full header from the source, we can actually
|
111
|
+
## have some extra refs set by the UI. (this happens when the user
|
112
|
+
## joins threads manually). so we will merge the current refs values
|
113
|
+
## in here.
|
114
|
+
refs = (header["references"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }
|
115
|
+
@refs = (@refs + refs).uniq
|
102
116
|
@replytos = (header["in-reply-to"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }
|
103
117
|
|
104
118
|
@replyto = PersonManager.person_for header["reply-to"]
|
@@ -116,7 +130,12 @@ class Message
|
|
116
130
|
end
|
117
131
|
private :parse_header
|
118
132
|
|
119
|
-
def
|
133
|
+
def add_ref ref
|
134
|
+
@refs << ref
|
135
|
+
@dirty = true
|
136
|
+
end
|
137
|
+
|
138
|
+
def snippet; @snippet || (chunks && @snippet); end
|
120
139
|
def is_list_message?; !@list_address.nil?; end
|
121
140
|
def is_draft?; @source.is_a? DraftLoader; end
|
122
141
|
def draft_filename
|
@@ -152,11 +171,16 @@ class Message
|
|
152
171
|
@dirty = true
|
153
172
|
end
|
154
173
|
|
174
|
+
def chunks
|
175
|
+
load_from_source!
|
176
|
+
@chunks
|
177
|
+
end
|
178
|
+
|
155
179
|
## this is called when the message body needs to actually be loaded.
|
156
180
|
def load_from_source!
|
157
181
|
@chunks ||=
|
158
182
|
if @source.has_errors?
|
159
|
-
[Chunk::Text.new(error_message(@source.error.message.split("\n"))
|
183
|
+
[Chunk::Text.new(error_message(@source.error.message).split("\n"))]
|
160
184
|
else
|
161
185
|
begin
|
162
186
|
## we need to re-read the header because it contains information
|
@@ -175,7 +199,7 @@ class Message
|
|
175
199
|
## up the error message one
|
176
200
|
@source.error ||= e
|
177
201
|
Redwood::report_broken_sources :force_to_top => true
|
178
|
-
[Chunk::Text.new(error_message(e.message))]
|
202
|
+
[Chunk::Text.new(error_message(e.message).split("\n"))]
|
179
203
|
end
|
180
204
|
end
|
181
205
|
end
|
@@ -275,7 +299,6 @@ private
|
|
275
299
|
## product.
|
276
300
|
|
277
301
|
def multipart_signed_to_chunks m
|
278
|
-
# Redwood::log ">> multipart SIGNED: #{m.header['Content-Type']}: #{m.body.size}"
|
279
302
|
if m.body.size != 2
|
280
303
|
Redwood::log "warning: multipart/signed with #{m.body.size} parts (expecting 2)"
|
281
304
|
return
|
@@ -287,13 +310,15 @@ private
|
|
287
310
|
return
|
288
311
|
end
|
289
312
|
|
313
|
+
## this probably will never happen
|
290
314
|
if payload.header.content_type == "application/pgp-signature"
|
291
315
|
Redwood::log "warning: multipart/signed with payload content type #{payload.header.content_type}"
|
292
316
|
return
|
293
317
|
end
|
294
318
|
|
295
319
|
if signature.header.content_type != "application/pgp-signature"
|
296
|
-
|
320
|
+
## unknown signature type; just ignore.
|
321
|
+
#Redwood::log "warning: multipart/signed with signature content type #{signature.header.content_type}"
|
297
322
|
return
|
298
323
|
end
|
299
324
|
|
@@ -301,7 +326,6 @@ private
|
|
301
326
|
end
|
302
327
|
|
303
328
|
def multipart_encrypted_to_chunks m
|
304
|
-
Redwood::log ">> multipart ENCRYPTED: #{m.header['Content-Type']}: #{m.body.size}"
|
305
329
|
if m.body.size != 2
|
306
330
|
Redwood::log "warning: multipart/encrypted with #{m.body.size} parts (expecting 2)"
|
307
331
|
return
|
@@ -324,11 +348,11 @@ private
|
|
324
348
|
end
|
325
349
|
|
326
350
|
decryptedm, sig, notice = CryptoManager.decrypt payload
|
327
|
-
children = message_to_chunks(decryptedm) if decryptedm
|
351
|
+
children = message_to_chunks(decryptedm, true) if decryptedm
|
328
352
|
[notice, sig, children].flatten.compact
|
329
353
|
end
|
330
354
|
|
331
|
-
def message_to_chunks m, sibling_types=[]
|
355
|
+
def message_to_chunks m, encrypted=false, sibling_types=[]
|
332
356
|
if m.multipart?
|
333
357
|
chunks =
|
334
358
|
case m.header.content_type
|
@@ -340,7 +364,7 @@ private
|
|
340
364
|
|
341
365
|
unless chunks
|
342
366
|
sibling_types = m.body.map { |p| p.header.content_type }
|
343
|
-
chunks = m.body.map { |p| message_to_chunks p, sibling_types }.flatten.compact
|
367
|
+
chunks = m.body.map { |p| message_to_chunks p, encrypted, sibling_types }.flatten.compact
|
344
368
|
end
|
345
369
|
|
346
370
|
chunks
|
@@ -359,8 +383,16 @@ private
|
|
359
383
|
|
360
384
|
## haven't found one, but it's a non-text message. fake
|
361
385
|
## it.
|
386
|
+
##
|
387
|
+
## TODO: make this less lame.
|
362
388
|
elsif m.header["Content-Type"] && m.header["Content-Type"] !~ /^text\/plain/
|
363
|
-
|
389
|
+
extension =
|
390
|
+
case m.header["Content-Type"]
|
391
|
+
when /text\/html/: "html"
|
392
|
+
when /image\/(.*)/: $1
|
393
|
+
end
|
394
|
+
|
395
|
+
["sup-attachment-#{Time.now.to_i}-#{rand 10000}", extension].join(".")
|
364
396
|
end
|
365
397
|
|
366
398
|
## if there's a filename, we'll treat it as an attachment.
|
@@ -369,17 +401,18 @@ private
|
|
369
401
|
|
370
402
|
## otherwise, it's body text
|
371
403
|
else
|
372
|
-
body = Message.convert_from m.decode, m.charset
|
373
|
-
text_to_chunks
|
404
|
+
body = Message.convert_from m.decode, m.charset if m.body
|
405
|
+
text_to_chunks((body || "").normalize_whitespace.split("\n"), encrypted)
|
374
406
|
end
|
375
407
|
end
|
376
408
|
end
|
377
409
|
|
378
410
|
def self.convert_from body, charset
|
411
|
+
charset = "utf-8" if charset =~ /UTF_?8/i
|
379
412
|
begin
|
380
413
|
raise MessageFormatError, "RubyMail decode returned a null body" unless body
|
381
414
|
return body unless charset
|
382
|
-
Iconv.iconv($encoding, charset, body).join
|
415
|
+
Iconv.iconv($encoding + "//IGNORE", charset, body + " ").join[0 .. -2]
|
383
416
|
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence, MessageFormatError => e
|
384
417
|
Redwood::log "warning: error (#{e.class.name}) decoding message body from #{charset}: #{e.message}"
|
385
418
|
File.open("sup-unable-to-decode.txt", "w") { |f| f.write body }
|
@@ -390,7 +423,7 @@ private
|
|
390
423
|
## parse the lines of text into chunk objects. the heuristics here
|
391
424
|
## need tweaking in some nice manner. TODO: move these heuristics
|
392
425
|
## into the classes themselves.
|
393
|
-
def text_to_chunks lines
|
426
|
+
def text_to_chunks lines, encrypted
|
394
427
|
state = :text # one of :text, :quote, or :sig
|
395
428
|
chunks = []
|
396
429
|
chunk_lines = []
|
@@ -402,7 +435,7 @@ private
|
|
402
435
|
when :text
|
403
436
|
newstate = nil
|
404
437
|
|
405
|
-
if line =~ QUOTE_PATTERN || (line =~ QUOTE_START_PATTERN &&
|
438
|
+
if line =~ QUOTE_PATTERN || (line =~ QUOTE_START_PATTERN && nextline =~ QUOTE_PATTERN)
|
406
439
|
newstate = :quote
|
407
440
|
elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE
|
408
441
|
newstate = :sig
|
@@ -421,7 +454,7 @@ private
|
|
421
454
|
when :quote
|
422
455
|
newstate = nil
|
423
456
|
|
424
|
-
if line =~ QUOTE_PATTERN || line =~
|
457
|
+
if line =~ QUOTE_PATTERN || (line =~ /^\s*$/ && nextline =~ QUOTE_PATTERN)
|
425
458
|
chunk_lines << line
|
426
459
|
elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE
|
427
460
|
newstate = :sig
|
@@ -442,11 +475,14 @@ private
|
|
442
475
|
when :block_quote, :sig
|
443
476
|
chunk_lines << line
|
444
477
|
end
|
445
|
-
|
478
|
+
|
446
479
|
if !@have_snippet && state == :text && (@snippet.nil? || @snippet.length < SNIPPET_LEN) && line !~ /[=\*#_-]{3,}/ && line !~ /^\s*$/
|
480
|
+
@snippet ||= ""
|
447
481
|
@snippet += " " unless @snippet.empty?
|
448
482
|
@snippet += line.gsub(/^\s+/, "").gsub(/[\r\n]/, "").gsub(/\s+/, " ")
|
449
483
|
@snippet = @snippet[0 ... SNIPPET_LEN].chomp
|
484
|
+
@dirty = true unless encrypted && $config[:discard_snippets_from_encrypted_messages]
|
485
|
+
@snippet_contains_encrypted_content = true if encrypted
|
450
486
|
end
|
451
487
|
end
|
452
488
|
|