sup 0.0.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.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/History.txt +5 -0
- data/LICENSE +280 -0
- data/Manifest.txt +52 -0
- data/README.txt +119 -0
- data/Rakefile +45 -0
- data/bin/sup +229 -0
- data/bin/sup-import +162 -0
- data/doc/FAQ.txt +38 -0
- data/doc/Philosophy.txt +59 -0
- data/doc/TODO +31 -0
- data/lib/sup.rb +141 -0
- data/lib/sup/account.rb +53 -0
- data/lib/sup/buffer.rb +391 -0
- data/lib/sup/colormap.rb +118 -0
- data/lib/sup/contact.rb +40 -0
- data/lib/sup/draft.rb +105 -0
- data/lib/sup/index.rb +353 -0
- data/lib/sup/keymap.rb +89 -0
- data/lib/sup/label.rb +41 -0
- data/lib/sup/logger.rb +42 -0
- data/lib/sup/mbox.rb +51 -0
- data/lib/sup/mbox/loader.rb +116 -0
- data/lib/sup/message.rb +302 -0
- data/lib/sup/mode.rb +79 -0
- data/lib/sup/modes/buffer-list-mode.rb +37 -0
- data/lib/sup/modes/compose-mode.rb +33 -0
- data/lib/sup/modes/contact-list-mode.rb +121 -0
- data/lib/sup/modes/edit-message-mode.rb +162 -0
- data/lib/sup/modes/forward-mode.rb +38 -0
- data/lib/sup/modes/help-mode.rb +19 -0
- data/lib/sup/modes/inbox-mode.rb +45 -0
- data/lib/sup/modes/label-list-mode.rb +89 -0
- data/lib/sup/modes/label-search-results-mode.rb +29 -0
- data/lib/sup/modes/line-cursor-mode.rb +133 -0
- data/lib/sup/modes/log-mode.rb +44 -0
- data/lib/sup/modes/person-search-results-mode.rb +29 -0
- data/lib/sup/modes/poll-mode.rb +24 -0
- data/lib/sup/modes/reply-mode.rb +136 -0
- data/lib/sup/modes/resume-mode.rb +18 -0
- data/lib/sup/modes/scroll-mode.rb +106 -0
- data/lib/sup/modes/search-results-mode.rb +31 -0
- data/lib/sup/modes/text-mode.rb +51 -0
- data/lib/sup/modes/thread-index-mode.rb +389 -0
- data/lib/sup/modes/thread-view-mode.rb +338 -0
- data/lib/sup/person.rb +120 -0
- data/lib/sup/poll.rb +80 -0
- data/lib/sup/sent.rb +46 -0
- data/lib/sup/tagger.rb +40 -0
- data/lib/sup/textfield.rb +83 -0
- data/lib/sup/thread.rb +358 -0
- data/lib/sup/update.rb +21 -0
- data/lib/sup/util.rb +260 -0
- metadata +123 -0
    
        data/lib/sup/mode.rb
    ADDED
    
    | @@ -0,0 +1,79 @@ | |
| 1 | 
            +
            module Redwood
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Mode
         | 
| 4 | 
            +
              attr_accessor :buffer
         | 
| 5 | 
            +
              @@keymaps = {}
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def self.register_keymap keymap=nil, &b
         | 
| 8 | 
            +
                keymap = Keymap.new(&b) if keymap.nil?
         | 
| 9 | 
            +
                @@keymaps[self] = keymap
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def initialize
         | 
| 13 | 
            +
                @buffer = nil
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def self.make_name s; s.gsub(/.*::/, "").camel_to_hyphy; end
         | 
| 17 | 
            +
              def name; Mode.make_name self.class.name; end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def self.load_all_modes dir
         | 
| 20 | 
            +
                Dir[File.join(dir, "*.rb")].each do |f|
         | 
| 21 | 
            +
                  $stderr.puts "## loading mode #{f}"
         | 
| 22 | 
            +
                  require f
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              def draw; end
         | 
| 27 | 
            +
              def focus; end
         | 
| 28 | 
            +
              def blur; end
         | 
| 29 | 
            +
              def status; ""; end
         | 
| 30 | 
            +
              def resize rows, cols; end
         | 
| 31 | 
            +
              def cleanup
         | 
| 32 | 
            +
                @buffer = nil
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              ## turns an input keystroke into an action symbol
         | 
| 36 | 
            +
              def resolve_input c
         | 
| 37 | 
            +
                ## try all keymaps in order of age
         | 
| 38 | 
            +
                action = nil
         | 
| 39 | 
            +
                klass = self.class
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                ancestors.each do |klass|
         | 
| 42 | 
            +
                  action = @@keymaps.member?(klass) && @@keymaps[klass].action_for(c)
         | 
| 43 | 
            +
                  return action if action
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                nil
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def handle_input c
         | 
| 50 | 
            +
                if(action = resolve_input c)
         | 
| 51 | 
            +
                  send action
         | 
| 52 | 
            +
                  true
         | 
| 53 | 
            +
                else
         | 
| 54 | 
            +
                  false
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              def help_text
         | 
| 59 | 
            +
                used_keys = {}
         | 
| 60 | 
            +
                ancestors.map do |klass|
         | 
| 61 | 
            +
                  km = @@keymaps[klass] or next
         | 
| 62 | 
            +
                  title = "Keybindings from #{Mode.make_name klass.name}"
         | 
| 63 | 
            +
                  s = <<EOS
         | 
| 64 | 
            +
            #{title}
         | 
| 65 | 
            +
            #{'-' * title.length}
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            #{km.help_text used_keys}
         | 
| 68 | 
            +
            EOS
         | 
| 69 | 
            +
                  begin
         | 
| 70 | 
            +
                    used_keys.merge! km.keysyms.to_boolean_h
         | 
| 71 | 
            +
                  rescue ArgumentError
         | 
| 72 | 
            +
                    raise km.keysyms.inspect
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                  s
         | 
| 75 | 
            +
                end.compact.join "\n"
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            module Redwood
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class BufferListMode < LineCursorMode
         | 
| 4 | 
            +
              register_keymap do |k|
         | 
| 5 | 
            +
                k.add :jump_to_buffer, "Jump to that buffer", :enter
         | 
| 6 | 
            +
                k.add :reload, "Reload", "R"
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              def initialize
         | 
| 10 | 
            +
                regen_text
         | 
| 11 | 
            +
                super
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def lines; @text.length; end
         | 
| 15 | 
            +
              def [] i; @text[i]; end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            protected
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def reload
         | 
| 20 | 
            +
                regen_text
         | 
| 21 | 
            +
                buffer.mark_dirty
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def regen_text
         | 
| 25 | 
            +
                @bufs = BufferManager.buffers.sort_by { |name, buf| name }
         | 
| 26 | 
            +
                width = @bufs.map { |name, buf| name.length }.max
         | 
| 27 | 
            +
                @text = @bufs.map do |name, buf|
         | 
| 28 | 
            +
                  sprintf "%#{width}s  %s", name, buf.mode.name
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def jump_to_buffer
         | 
| 33 | 
            +
                BufferManager.raise_to_front @bufs[curpos][1]
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            module Redwood
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class ComposeMode < EditMessageMode
         | 
| 4 | 
            +
              attr_reader :body, :header
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize h={}
         | 
| 7 | 
            +
                super()
         | 
| 8 | 
            +
                @header = {
         | 
| 9 | 
            +
                  "From" => AccountManager.default_account.full_address,
         | 
| 10 | 
            +
                  "Message-Id" => gen_message_id,
         | 
| 11 | 
            +
                }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                @header["To"] = [h[:to]].flatten.compact.map { |p| p.full_address }
         | 
| 14 | 
            +
                @body = sig_lines
         | 
| 15 | 
            +
                regen_text
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def lines; @text.length; end
         | 
| 19 | 
            +
              def [] i; @text[i]; end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            protected
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def handle_new_text new_header, new_body
         | 
| 24 | 
            +
                @header = new_header
         | 
| 25 | 
            +
                @body = new_body
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def regen_text
         | 
| 29 | 
            +
                @text = header_lines(@header - EditMessageMode::NON_EDITABLE_HEADERS) + [""] + @body
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,121 @@ | |
| 1 | 
            +
            module Redwood
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class ContactListMode < LineCursorMode
         | 
| 4 | 
            +
              LOAD_MORE_CONTACTS_NUM = 10
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              register_keymap do |k|
         | 
| 7 | 
            +
                k.add :load_more, "Load #{LOAD_MORE_CONTACTS_NUM} more contacts", 'M'
         | 
| 8 | 
            +
                k.add :reload, "Reload contacts", 'R'
         | 
| 9 | 
            +
                k.add :alias, "Edit alias for contact", 'a'
         | 
| 10 | 
            +
                k.add :toggle_tagged, "Tag/untag current line", 't'
         | 
| 11 | 
            +
                k.add :apply_to_tagged, "Apply next command to all tagged items", ';'
         | 
| 12 | 
            +
                k.add :search, "Search for messages from particular people", 'S'
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def initialize mode = :regular
         | 
| 16 | 
            +
                @mode = mode
         | 
| 17 | 
            +
                @tags = Tagger.new self
         | 
| 18 | 
            +
                reload
         | 
| 19 | 
            +
                super()
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def lines; @text.length; end
         | 
| 23 | 
            +
              def [] i; @text[i]; end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def toggle_tagged
         | 
| 26 | 
            +
                p = @contacts[curpos] or return
         | 
| 27 | 
            +
                @tags.toggle_tag_for p
         | 
| 28 | 
            +
                update_text_for_line curpos
         | 
| 29 | 
            +
                cursor_down
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def multi_toggle_tagged threads
         | 
| 33 | 
            +
                @tags.drop_all_tags
         | 
| 34 | 
            +
                regen_text
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              def apply_to_tagged; @tags.apply_to_tagged; end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def load; regen_text; end
         | 
| 40 | 
            +
              def load_more
         | 
| 41 | 
            +
                @num += LOAD_MORE_CONTACTS_NUM
         | 
| 42 | 
            +
                regen_text
         | 
| 43 | 
            +
                BufferManager.flash "Loaded #{LOAD_MORE_CONTACTS_NUM} more contacts."
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              def multi_select people
         | 
| 47 | 
            +
                case @mode
         | 
| 48 | 
            +
                when :regular
         | 
| 49 | 
            +
                  mode = ComposeMode.new :to => people
         | 
| 50 | 
            +
                  BufferManager.spawn "new message", mode
         | 
| 51 | 
            +
                  mode.edit
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              def select
         | 
| 56 | 
            +
                p = @contacts[curpos] or return
         | 
| 57 | 
            +
                multi_select [p]
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              def multi_search people
         | 
| 61 | 
            +
                mode = PersonSearchResultsMode.new people
         | 
| 62 | 
            +
                BufferManager.spawn "personal search results", mode
         | 
| 63 | 
            +
                mode.load_more_threads mode.buffer.content_height
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              def search
         | 
| 67 | 
            +
                p = @contacts[curpos] or return
         | 
| 68 | 
            +
                multi_search [p]
         | 
| 69 | 
            +
              end    
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              def reload
         | 
| 72 | 
            +
                @tags.drop_all_tags
         | 
| 73 | 
            +
                @num = LOAD_MORE_CONTACTS_NUM
         | 
| 74 | 
            +
                load
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              def alias
         | 
| 78 | 
            +
                p = @contacts[curpos] or return
         | 
| 79 | 
            +
                a = BufferManager.ask(:alias, "alias for #{p.longname}: ", @user_contacts[p]) or return
         | 
| 80 | 
            +
                if a.empty?
         | 
| 81 | 
            +
                  ContactManager.drop_contact p
         | 
| 82 | 
            +
                else
         | 
| 83 | 
            +
                  ContactManager.set_contact p, a
         | 
| 84 | 
            +
                  @user_contacts[p] = a
         | 
| 85 | 
            +
                  update_text_for_line curpos
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            protected
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              def update_text_for_line line
         | 
| 92 | 
            +
                @text[line] = text_for_contact @contacts[line]
         | 
| 93 | 
            +
                buffer.mark_dirty
         | 
| 94 | 
            +
              end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              def text_for_contact p
         | 
| 97 | 
            +
                aalias = @user_contacts[p] || ""
         | 
| 98 | 
            +
                [[:tagged_color, @tags.tagged?(p) ? ">" : " "],
         | 
| 99 | 
            +
                 [:none, sprintf("%-#{@awidth}s %-#{@nwidth}s %s", aalias, p.name, p.email)]]
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
              def regen_text
         | 
| 103 | 
            +
                @user_contacts = ContactManager.contacts.invert
         | 
| 104 | 
            +
                recent = Index.load_contacts AccountManager.user_emails,
         | 
| 105 | 
            +
                                             :num => @num
         | 
| 106 | 
            +
                
         | 
| 107 | 
            +
                @contacts = (@user_contacts.keys + recent.select { |p| !@user_contacts[p] }).sort_by { |p| p.sort_by_me + (p.name || "") + p.email }.remove_successive_dupes
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                @awidth, @nwidth = 0, 0
         | 
| 110 | 
            +
                @contacts.each do |p|
         | 
| 111 | 
            +
                  aalias = @user_contacts[p]
         | 
| 112 | 
            +
                  @awidth = aalias.length if aalias && aalias.length > @awidth
         | 
| 113 | 
            +
                  @nwidth = p.name.length if p.name && p.name.length > @nwidth
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                @text = @contacts.map { |p| text_for_contact p }
         | 
| 117 | 
            +
                buffer.mark_dirty if buffer
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
            end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            end
         | 
| @@ -0,0 +1,162 @@ | |
| 1 | 
            +
            require 'tempfile'
         | 
| 2 | 
            +
            require 'socket' # just for gethostname!
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Redwood
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class EditMessageMode < LineCursorMode
         | 
| 7 | 
            +
              FORCE_HEADERS = %w(From To Cc Bcc Subject)
         | 
| 8 | 
            +
              MULTI_HEADERS = %w(To Cc Bcc)
         | 
| 9 | 
            +
              NON_EDITABLE_HEADERS = %w(Message-Id Date)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              attr_reader :status
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              register_keymap do |k|
         | 
| 14 | 
            +
                k.add :send_message, "Send message", 'y'
         | 
| 15 | 
            +
                k.add :edit, "Edit message", 'e', :enter
         | 
| 16 | 
            +
                k.add :save_as_draft, "Save as draft", 'P'
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def initialize *a
         | 
| 20 | 
            +
                super
         | 
| 21 | 
            +
                @attachments = []
         | 
| 22 | 
            +
                @edited = false
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def edit
         | 
| 26 | 
            +
                @file = Tempfile.new "redwood.#{self.class.name.camel_to_hyphy}"
         | 
| 27 | 
            +
                @file.puts header_lines(header - NON_EDITABLE_HEADERS)
         | 
| 28 | 
            +
                @file.puts
         | 
| 29 | 
            +
                @file.puts body
         | 
| 30 | 
            +
                @file.close
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                editor = $config[:editor] || ENV['EDITOR'] || "/usr/bin/vi"
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                mtime = File.mtime @file.path
         | 
| 35 | 
            +
                BufferManager.shell_out "#{editor} #{@file.path}"
         | 
| 36 | 
            +
                @edited = true if File.mtime(@file.path) > mtime
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                new_header, new_body = parse_file(@file.path)
         | 
| 39 | 
            +
                NON_EDITABLE_HEADERS.each { |h| new_header[h] = header[h] if header[h] }
         | 
| 40 | 
            +
                handle_new_text new_header, new_body
         | 
| 41 | 
            +
                update
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            protected
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              def gen_message_id
         | 
| 47 | 
            +
                "<#{Time.now.to_i}-redwood-#{rand 10000}@#{Socket.gethostname}>"
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def update
         | 
| 51 | 
            +
                regen_text
         | 
| 52 | 
            +
                buffer.mark_dirty
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              def parse_file fn
         | 
| 56 | 
            +
                File.open(fn) do |f|
         | 
| 57 | 
            +
                  header = MBox::read_header f
         | 
| 58 | 
            +
                  body = MBox::read_body f
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  header.delete_if { |k, v| NON_EDITABLE_HEADERS.member? k }
         | 
| 61 | 
            +
                  header.each do |k, v|
         | 
| 62 | 
            +
                    next unless MULTI_HEADERS.include?(k) && !v.empty?
         | 
| 63 | 
            +
                    header[k] = v.split_on_commas.map do |name|
         | 
| 64 | 
            +
                      (p = ContactManager.resolve(name)) && p.full_address || name
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  [header, body]
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              def header_lines header
         | 
| 73 | 
            +
                force_headers = FORCE_HEADERS.map { |h| make_lines "#{h}:", header[h] }
         | 
| 74 | 
            +
                other_headers = (header.keys - FORCE_HEADERS).map do |h|
         | 
| 75 | 
            +
                  make_lines "#{h}:", header[h]
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                (force_headers + other_headers).flatten.compact
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              def make_lines header, things
         | 
| 82 | 
            +
                case things
         | 
| 83 | 
            +
                when nil, []
         | 
| 84 | 
            +
                  [header + " "]
         | 
| 85 | 
            +
                when String
         | 
| 86 | 
            +
                  [header + " " + things]
         | 
| 87 | 
            +
                else
         | 
| 88 | 
            +
                  if things.empty?
         | 
| 89 | 
            +
                    [header]
         | 
| 90 | 
            +
                  else
         | 
| 91 | 
            +
                    things.map_with_index do |name, i|
         | 
| 92 | 
            +
                      raise "an array: #{name.inspect} (things #{things.inspect})" if Array === name
         | 
| 93 | 
            +
                      if i == 0
         | 
| 94 | 
            +
                        header + " " + name
         | 
| 95 | 
            +
                      else
         | 
| 96 | 
            +
                        (" " * (header.length + 1)) + name
         | 
| 97 | 
            +
                      end + (i == things.length - 1 ? "" : ",")
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              def send_message
         | 
| 104 | 
            +
                return false unless @edited || BufferManager.ask_yes_or_no("message unedited---really send?")
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                raise "no message id!" unless header["Message-Id"]
         | 
| 107 | 
            +
                date = Time.now
         | 
| 108 | 
            +
                from_email = 
         | 
| 109 | 
            +
                  if header["From"] =~ /<?(\S+@(\S+?))>?$/
         | 
| 110 | 
            +
                    $1
         | 
| 111 | 
            +
                  else
         | 
| 112 | 
            +
                    AccountManager.default_account.email
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                sendmail = AccountManager.account_for(from_email).sendmail
         | 
| 116 | 
            +
                raise "nil sendmail" unless sendmail
         | 
| 117 | 
            +
                SentManager.write_sent_message(date, from_email) { |f| write_message f, true, date }
         | 
| 118 | 
            +
                BufferManager.flash "sending..."
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                IO.popen(sendmail, "w") { |p| write_message p, true, date }
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                BufferManager.kill_buffer buffer
         | 
| 123 | 
            +
                BufferManager.flash "Message sent!"
         | 
| 124 | 
            +
                true
         | 
| 125 | 
            +
              end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
              def save_as_draft
         | 
| 128 | 
            +
                DraftManager.write_draft { |f| write_message f, false }
         | 
| 129 | 
            +
                BufferManager.kill_buffer buffer
         | 
| 130 | 
            +
                BufferManager.flash "Saved for later editing."
         | 
| 131 | 
            +
              end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
              def sig_lines
         | 
| 134 | 
            +
                sigfn = (AccountManager.account_for(header["From"]) || 
         | 
| 135 | 
            +
                         AccountManager.default_account).sig_file
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                if sigfn && File.exists?(sigfn)
         | 
| 138 | 
            +
                  ["", "-- "] + File.readlines(sigfn).map { |l| l.chomp }
         | 
| 139 | 
            +
                else
         | 
| 140 | 
            +
                  []
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
              end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
              def write_message f, full_header=true, date=Time.now
         | 
| 145 | 
            +
                raise ArgumentError, "no pre-defined date: header allowed" if header["Date"]
         | 
| 146 | 
            +
                f.puts header_lines(header)
         | 
| 147 | 
            +
                f.puts "Date: #{date.rfc2822}"
         | 
| 148 | 
            +
                if full_header
         | 
| 149 | 
            +
                  f.puts <<EOS
         | 
| 150 | 
            +
            Mime-Version: 1.0
         | 
| 151 | 
            +
            Content-Type: text/plain; charset=us-ascii
         | 
| 152 | 
            +
            Content-Disposition: inline
         | 
| 153 | 
            +
            User-Agent: Redwood/#{Redwood::VERSION}
         | 
| 154 | 
            +
            EOS
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                f.puts
         | 
| 158 | 
            +
                f.puts @body
         | 
| 159 | 
            +
              end  
         | 
| 160 | 
            +
            end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            module Redwood
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class ForwardMode < EditMessageMode
         | 
| 4 | 
            +
              attr_reader :body, :header
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize m
         | 
| 7 | 
            +
                super()
         | 
| 8 | 
            +
                @header = {
         | 
| 9 | 
            +
                  "From" => AccountManager.default_account.full_address,
         | 
| 10 | 
            +
                  "Subject" => "Fwd: #{m.subj}",
         | 
| 11 | 
            +
                  "Message-Id" => gen_message_id,
         | 
| 12 | 
            +
                }
         | 
| 13 | 
            +
                @body = forward_body_lines(m) + sig_lines
         | 
| 14 | 
            +
                regen_text
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def lines; @text.length; end
         | 
| 18 | 
            +
              def [] i; @text[i]; end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            protected
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def forward_body_lines m
         | 
| 23 | 
            +
                ["--- Begin forwarded message from #{m.from.mediumname} ---"] + 
         | 
| 24 | 
            +
                  m.basic_header_lines + [""] + m.basic_body_lines +
         | 
| 25 | 
            +
                  ["--- End forwarded message ---"]
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def handle_new_text new_header, new_body
         | 
| 29 | 
            +
                @header = new_header
         | 
| 30 | 
            +
                @body = new_body
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def regen_text
         | 
| 34 | 
            +
                @text = header_lines(@header - NON_EDITABLE_HEADERS) + [""] + @body
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            end
         |