diff --git a/lib/mail_catcher/mail.rb b/lib/mail_catcher/mail.rb index 7be0b5a..ee4ccd3 100644 --- a/lib/mail_catcher/mail.rb +++ b/lib/mail_catcher/mail.rb @@ -150,4 +150,11 @@ module MailCatcher::Mail extend self @delete_messages_query.execute and @delete_message_parts_query.execute end + + def delete_message!(message_id) + @delete_messages_query ||= db.prepare 'DELETE FROM message WHERE id = ?' + @delete_message_parts_query ||= db.prepare 'DELETE FROM message_part WHERE message_id = ?' + @delete_messages_query.execute(message_id) and + @delete_message_parts_query.execute(message_id) + end end diff --git a/lib/mail_catcher/web.rb b/lib/mail_catcher/web.rb index d84bd91..496f6a9 100644 --- a/lib/mail_catcher/web.rb +++ b/lib/mail_catcher/web.rb @@ -132,6 +132,16 @@ class MailCatcher::Web < Sinatra::Base end end + delete '/messages/:id' do + id = params[:id].to_i + if message = MailCatcher::Mail.message(id) + MailCatcher::Mail.delete_message!(id) + status 204 + else + not_found + end + end + not_found do "

No Dice

The message you were looking for does not exist, or doesn't have content of this type.

" end diff --git a/public/javascripts/application.coffee b/public/javascripts/application.coffee index 2ffb749..0d9355d 100644 --- a/public/javascripts/application.coffee +++ b/public/javascripts/application.coffee @@ -42,9 +42,7 @@ class MailCatcher url: '/messages' type: 'DELETE' success: -> - $('#messages tbody, #message .metadata dd').empty() - $('#message .metadata .attachments').hide() - $('#message iframe').attr 'src', 'about:blank' + @unselectMessage() error: -> alert 'Error while quitting.' @@ -59,26 +57,53 @@ class MailCatcher alert 'Error while quitting.' key 'up', => - id = @selectedMessage() || 1 - id -= 1 if id > 1 - @loadMessage(id) + if @selectedMessage() + @loadMessage $('#messages tr.selected').prev().data('message-id') + else + @loadMessage $('#messages tbody tr[data-message-id]:first').data('message-id') + false key 'down', => - id = @selectedMessage() || @messagesCount() - id += 1 if id < @messagesCount() - @loadMessage(id) + if @selectedMessage() + @loadMessage $('#messages tr.selected').next().data('message-id') + else + @loadMessage $('#messages tbody tr[data-message-id]:first').data('message-id') + false key '⌘+up, ctrl+up', => - @loadMessage(1) + @loadMessage $('#messages tbody tr[data-message-id]:first').data('message-id') + false key '⌘+down, ctrl+down', => - @loadMessage @messagesCount() + @loadMessage $('#messages tbody tr[data-message-id]:last').data('message-id') + false key 'left', => @openTab @previousTab() + false key 'right', => @openTab @nextTab() + false + + key 'backspace, delete', => + id = @selectedMessage() + if id? + $.ajax + url: '/messages/' + id + type: 'DELETE' + success: => + messageRow = $("#messages tbody tr[data-message-id='#{id}']") + switchTo = messageRow.next().data('message-id') || messageRow.prev().data('message-id') + messageRow.remove() + if switchTo + @loadMessage switchTo + else + @unselectMessage() + + error: -> + alert 'Error while removing message.' + false @refresh() @subscribe() @@ -155,15 +180,32 @@ class MailCatcher .append($('').text(message.subject or "No subject").toggleClass("blank", !message.subject)) .append($('').text @formatDate message.created_at) + scrollToRow: (row) -> + relativePosition = row.offset().top - $('#messages').offset().top + if relativePosition < 0 + $('#messages').scrollTop($('#messages').scrollTop() + relativePosition - 20) + else + overflow = relativePosition + row.height() - $('#messages').height() + if overflow > 0 + $('#messages').scrollTop($('#messages').scrollTop() + overflow + 20) + + unselectMessage: -> + $('#messages tbody, #message .metadata dd').empty() + $('#message .metadata .attachments').hide() + $('#message iframe').attr 'src', 'about:blank' + null + loadMessage: (id) -> id = id.id if id?.id? id ||= $('#messages tr.selected').attr 'data-message-id' if id? - $('#messages tbody tr:not([data-message-id="'+id+'"])').removeClass 'selected' - $('#messages tbody tr[data-message-id="'+id+'"]').addClass 'selected' + $("#messages tbody tr:not([data-message-id='#{id}'])").removeClass 'selected' + messageRow = $("#messages tbody tr[data-message-id='#{id}']") + messageRow.addClass 'selected' + @scrollToRow(messageRow) - $.getJSON '/messages/' + id + '.json', (message) => + $.getJSON "/messages/#{id}.json", (message) => $('#message .metadata dd.created_at').text @formatDate message.created_at $('#message .metadata dd.from').text message.sender $('#message .metadata dd.to').text (message.recipients || []).join(', ') @@ -172,7 +214,7 @@ class MailCatcher $el = $(el) format = $el.attr 'data-message-format' if $.inArray(format, message.formats) >= 0 - $el.find('a').attr('href', '/messages/' + id + '.' + format) + $el.find('a').attr('href', "/messages/#{id}.#{format}") $el.show() else $el.hide() diff --git a/public/javascripts/application.js b/public/javascripts/application.js index c2efaa3..11f157a 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -62,9 +62,7 @@ url: '/messages', type: 'DELETE', success: function() { - $('#messages tbody, #message .metadata dd').empty(); - $('#message .metadata .attachments').hide(); - return $('#message iframe').attr('src', 'about:blank'); + return this.unselectMessage(); }, error: function() { return alert('Error while quitting.'); @@ -87,28 +85,61 @@ } }); key('up', function() { - var id; - id = _this.selectedMessage() || 1; - if (id > 1) id -= 1; - return _this.loadMessage(id); + if (_this.selectedMessage()) { + _this.loadMessage($('#messages tr.selected').prev().data('message-id')); + } else { + _this.loadMessage($('#messages tbody tr[data-message-id]:first').data('message-id')); + } + return false; }); key('down', function() { - var id; - id = _this.selectedMessage() || _this.messagesCount(); - if (id < _this.messagesCount()) id += 1; - return _this.loadMessage(id); + if (_this.selectedMessage()) { + _this.loadMessage($('#messages tr.selected').next().data('message-id')); + } else { + _this.loadMessage($('#messages tbody tr[data-message-id]:first').data('message-id')); + } + return false; }); key('⌘+up, ctrl+up', function() { - return _this.loadMessage(1); + _this.loadMessage($('#messages tbody tr[data-message-id]:first').data('message-id')); + return false; }); key('⌘+down, ctrl+down', function() { - return _this.loadMessage(_this.messagesCount()); + _this.loadMessage($('#messages tbody tr[data-message-id]:last').data('message-id')); + return false; }); key('left', function() { - return _this.openTab(_this.previousTab()); + _this.openTab(_this.previousTab()); + return false; }); key('right', function() { - return _this.openTab(_this.nextTab()); + _this.openTab(_this.nextTab()); + return false; + }); + key('backspace, delete', function() { + var id; + id = _this.selectedMessage(); + if (id != null) { + $.ajax({ + url: '/messages/' + id, + type: 'DELETE', + success: function() { + var messageRow, switchTo; + messageRow = $("#messages tbody tr[data-message-id='" + id + "']"); + switchTo = messageRow.next().data('message-id') || messageRow.prev().data('message-id'); + messageRow.remove(); + if (switchTo) { + return _this.loadMessage(switchTo); + } else { + return _this.unselectMessage(); + } + }, + error: function() { + return alert('Error while removing message.'); + } + }); + } + return false; }); this.refresh(); this.subscribe(); @@ -212,14 +243,37 @@ return $('#messages tbody').append($('').attr('data-message-id', message.id.toString()).append($('').text(message.sender || "No sender").toggleClass("blank", !message.sender)).append($('').text((message.recipients || []).join(', ') || "No receipients").toggleClass("blank", !message.recipients.length)).append($('').text(message.subject || "No subject").toggleClass("blank", !message.subject)).append($('').text(this.formatDate(message.created_at)))); }; + MailCatcher.prototype.scrollToRow = function(row) { + var overflow, relativePosition; + relativePosition = row.offset().top - $('#messages').offset().top; + if (relativePosition < 0) { + return $('#messages').scrollTop($('#messages').scrollTop() + relativePosition - 20); + } else { + overflow = relativePosition + row.height() - $('#messages').height(); + if (overflow > 0) { + return $('#messages').scrollTop($('#messages').scrollTop() + overflow + 20); + } + } + }; + + MailCatcher.prototype.unselectMessage = function() { + $('#messages tbody, #message .metadata dd').empty(); + $('#message .metadata .attachments').hide(); + $('#message iframe').attr('src', 'about:blank'); + return null; + }; + MailCatcher.prototype.loadMessage = function(id) { - var _this = this; + var messageRow, + _this = this; if ((id != null ? id.id : void 0) != null) id = id.id; id || (id = $('#messages tr.selected').attr('data-message-id')); if (id != null) { - $('#messages tbody tr:not([data-message-id="' + id + '"])').removeClass('selected'); - $('#messages tbody tr[data-message-id="' + id + '"]').addClass('selected'); - return $.getJSON('/messages/' + id + '.json', function(message) { + $("#messages tbody tr:not([data-message-id='" + id + "'])").removeClass('selected'); + messageRow = $("#messages tbody tr[data-message-id='" + id + "']"); + messageRow.addClass('selected'); + this.scrollToRow(messageRow); + return $.getJSON("/messages/" + id + ".json", function(message) { var $ul; $('#message .metadata dd.created_at').text(_this.formatDate(message.created_at)); $('#message .metadata dd.from').text(message.sender); @@ -230,7 +284,7 @@ $el = $(el); format = $el.attr('data-message-format'); if ($.inArray(format, message.formats) >= 0) { - $el.find('a').attr('href', '/messages/' + id + '.' + format); + $el.find('a').attr('href', "/messages/" + id + "." + format); return $el.show(); } else { return $el.hide(); diff --git a/views/index.haml b/views/index.haml index f83f676..8880f20 100644 --- a/views/index.haml +++ b/views/index.haml @@ -3,6 +3,7 @@ %head %title MailCatcher %link{:rel => "stylesheet", :href => "/stylesheets/application.css"} + %link{:href => "/favicon.ico", :rel => "shortcut icon" } %script{:src => "/javascripts/modernizr.js"} %script{:src => "/javascripts/jquery.js"} %script{:src => "/javascripts/xslt-3.2.js"}