thread: The message thread to be formatted.
EOS
+ HookManager.register "mark-as-spam", <<EOS
+This hook is run when a thread is marked as spam
+Variables:
+ thread: The message thread being marked as spam.
+EOS
+
register_keymap do |k|
k.add :load_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
k.add_multi "Load all threads (! to confirm) :", '!' do |kk|
k.add :toggle_tagged, "Tag/untag selected thread", 't'
k.add :toggle_tagged_all, "Tag/untag all threads", 'T'
k.add :tag_matching, "Tag matching threads", 'g'
- k.add :apply_to_tagged, "Apply next command to all tagged threads", ';'
+ k.add :apply_to_tagged, "Apply next command to all tagged threads", '+', '='
k.add :join_threads, "Force tagged threads to be joined into the same thread", '#'
end
UpdateManager.register self
+ @save_thread_mutex = Mutex.new
+
@last_load_more_size = nil
to_load_more do |size|
next if @last_load_more_size == 0
end
end
+ def unsaved?; dirty? end
def lines; @text.length; end
def [] i; @text[i]; end
def contains_thread? t; @threads.include?(t) end
threads.each { |t| select t }
end
- ## this is called by thread-view-modes when the user wants to view
- ## the next thread without going to index-mode. we update the cursor
- ## as a convenience.
+ ## these two methods are called by thread-view-modes when the user
+ ## wants to view the previous/next thread without going back to
+ ## index-mode. we update the cursor as a convenience.
def launch_next_thread_after thread, &b
+ launch_another_thread thread, 1, &b
+ end
+
+ def launch_prev_thread_before thread, &b
+ launch_another_thread thread, -1, &b
+ end
+
+ def launch_another_thread thread, direction, &b
l = @lines[thread] or return
+ target_l = l + direction
t = @mutex.synchronize do
- if l < @threads.length - 1
- set_cursor_pos l + 1 # move out of mutex?
- @threads[l + 1]
+ if target_l >= 0 && target_l < @threads.length
+ @threads[target_l]
end
end
if t # there's a next thread
+ set_cursor_pos target_l # move out of mutex?
select t, b
elsif b # no next thread. call the block anyways
b.call
def toggle_spam
t = cursor_thread or return
multi_toggle_spam [t]
+ HookManager.run("mark-as-spam", :thread => t)
end
## both spam and deleted have the curious characteristic that you
BufferManager.flash "#{threads.size.pluralize 'Thread'} killed."
end
- def save
- dirty_threads = @mutex.synchronize { (@threads + @hidden_threads.keys).select { |t| t.dirty? } }
- return if dirty_threads.empty?
+ def save background=true
+ if background
+ Redwood::reporting_thread("saving thread") { actually_save }
+ else
+ actually_save
+ end
+ end
+
+ def actually_save
+ @save_thread_mutex.synchronize do
+ BufferManager.say("Saving contacts...") { ContactManager.instance.save }
+ dirty_threads = @mutex.synchronize { (@threads + @hidden_threads.keys).select { |t| t.dirty? } }
+ next if dirty_threads.empty?
- BufferManager.say("Saving threads...") do |say_id|
- dirty_threads.each_with_index do |t, i|
- BufferManager.say "Saving modified thread #{i + 1} of #{dirty_threads.length}...", say_id
- t.save Index
+ BufferManager.say("Saving threads...") do |say_id|
+ dirty_threads.each_with_index do |t, i|
+ BufferManager.say "Saving modified thread #{i + 1} of #{dirty_threads.length}...", say_id
+ t.save Index
+ end
end
end
end
sleep 0.1 # TODO: necessary?
BufferManager.erase_flash
end
- save
+ save false
super
end
end
def tag_matching
- query = BufferManager.ask :search, "tag threads matching: "
+ query = BufferManager.ask :search, "tag threads matching (regex): "
return if query.nil? || query.empty?
- query = /#{query}/i
+ query = begin
+ /#{query}/i
+ rescue RegexpError => e
+ BufferManager.flash "error interpreting '#{query}': #{e.message}"
+ return
+ end
@mutex.synchronize { @threads.each { |t| @tags.tag t if thread_matches?(t, query) } }
regen_text
end
end
def multi_edit_labels threads
- answer = BufferManager.ask :add_labels, "add labels: "
- return unless answer
- user_labels = answer.split(/\s+/).map { |l| l.intern }
-
- hl = user_labels.select { |l| @hidden_labels.member? l }
+ user_labels = BufferManager.ask_for_labels :labels, "Add/remove labels (use -label to remove): ", [], @hidden_labels
+ return unless user_labels
+
+ user_labels.map! { |l| (l.to_s =~ /^-/)? [l.to_s.gsub(/^-?/, '').to_sym, true] : [l, false] }
+ hl = user_labels.select { |(l,_)| @hidden_labels.member? l }
if hl.empty?
- threads.each { |t| user_labels.each { |l| t.apply_label l } }
- user_labels.each { |l| LabelManager << l }
+ threads.each do |t|
+ user_labels.each do |(l, to_remove)|
+ if to_remove
+ t.remove_label l
+ else
+ t.apply_label l
+ end
+ end
+ end
+ user_labels.each { |(l,_)| LabelManager << l }
else
BufferManager.flash "'#{hl}' is a reserved label!"
end
def add_or_unhide m
@ts_mutex.synchronize do
if (is_relevant?(m) || @ts.is_relevant?(m)) && !@ts.contains?(m)
- @ts.load_thread_for_message m
+ @ts.load_thread_for_message m, @load_thread_opts
end
@hidden_threads.delete @ts.thread_for(m)
date = t.date.to_nice_s
- new = t.has_label?(:unread)
starred = t.has_label?(:starred)
## format the from column
p = dp || t.participants.any? { |p| AccountManager.is_account? p }
subj_color =
- if new
+ if t.has_label?(:draft)
+ :index_draft_color
+ elsif t.has_label?(:unread)
:index_new_color
elsif starred
:index_starred_color
from +
[
[subj_color, size_widget_text],
- [:to_me_color, dp ? " >" : (p ? ' +' : " ")],
+ [:to_me_color, t.labels.member?(:attachment) ? "@" : " "],
+ [:to_me_color, dp ? ">" : (p ? '+' : " ")],
[subj_color, t.subj + (t.subj.empty? ? "" : " ")],
] +
(t.labels - @hidden_labels).map { |label| [:label_color, "+#{label} "] } +
[[:snippet_color, snippet]
]
-
end
def dirty?; @mutex.synchronize { (@hidden_threads.keys + @threads).any? { |t| t.dirty? } } end