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"}