mailcatcher 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.md +32 -16
- data/lib/mail_catcher.rb +41 -9
- data/lib/mail_catcher/mail.rb +9 -1
- data/lib/mail_catcher/web.rb +11 -1
- data/public/javascripts/application.js +52 -23
- data/public/stylesheets/application.css +218 -157
- data/views/index.haml +31 -25
- metadata +15 -4
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -6,34 +6,44 @@ MailCatcher runs a super simple SMTP server which catches any message sent to it
|
|
6
6
|
|
7
7
|
![MailCatcher screenshot](http://puu.sh/2fZR)
|
8
8
|
|
9
|
-
## How
|
10
|
-
|
11
|
-
1. `gem install mailcatcher`
|
12
|
-
2. `mailcatcher`
|
13
|
-
3. Go to http://localhost:1080/
|
14
|
-
4. Send mail through smtp://localhost:1025
|
15
|
-
|
16
9
|
## Features
|
17
10
|
|
18
11
|
* Catches all mail and stores it for display.
|
19
12
|
* Shows HTML, Plain Text and Source version of messages, as applicable.
|
20
|
-
* Download original email to view in your native mail client(s).
|
21
13
|
* Rewrites HTML enabling display of embedded, inline images/etc and open links in a new window. (currently very basic)
|
22
14
|
* Lists attachments and allows separate downloading of parts.
|
23
|
-
*
|
15
|
+
* Download original email to view in your native mail client(s).
|
24
16
|
* Command line options to override the default SMTP/HTTP IP and port settings.
|
25
17
|
* Mail appears instantly if your browser supports [WebSockets][websockets].
|
26
|
-
*
|
18
|
+
* Runs as a daemon run in the background.
|
19
|
+
* Sendmail-analogue command, `catchmail`, makes [using mailcatcher from PHP][withphp] a lot easier.
|
20
|
+
* Written super-simply in EventMachine, easy to dig in and change.
|
27
21
|
|
28
|
-
##
|
22
|
+
## How
|
29
23
|
|
30
|
-
|
31
|
-
|
24
|
+
1. `gem install mailcatcher`
|
25
|
+
2. `mailcatcher`
|
26
|
+
3. Go to http://localhost:1080/
|
27
|
+
4. Send mail through smtp://localhost:1025
|
28
|
+
|
29
|
+
The brave can get the source from [the GitHub repository][mailcatcher-github].
|
30
|
+
|
31
|
+
## RVM
|
32
|
+
|
33
|
+
Under RVM your mailcatcher command may only available under the ruby you install mailcatcher into. To prevent this, and to prevent gem conflicts, install mailcatcher into a dedicated gemset and create wrapper scripts:
|
34
|
+
|
35
|
+
rvm default@mailcatcher --create gem install mailcatcher
|
36
|
+
rvm wrapper default@mailcatcher --no-prefix mailcatcher catchmail
|
32
37
|
|
33
38
|
## API
|
34
39
|
|
35
40
|
A fairly RESTful URL schema means you can download a list of messages in JSON from `/messages`, each message's metadata with `/messages/:id.json`, and then the pertinent parts with `/messages/:id.html` and `/messages/:id.plain` for the default HTML and plain text version, `/messages/:id/:cid` for individual attachments by CID, or the whole message with `/messages/:id.source`.
|
36
41
|
|
42
|
+
## Caveats
|
43
|
+
|
44
|
+
* Mail processing is fairly basic but easily modified. If something doesn't work for you, fork and fix it or file an issue and let me know. Include the whole message you're having problems with.
|
45
|
+
* The interface is very basic and has not been tested on many browsers yet.
|
46
|
+
|
37
47
|
## TODO
|
38
48
|
|
39
49
|
* Growl support.
|
@@ -46,12 +56,18 @@ A fairly RESTful URL schema means you can download a list of messages in JSON fr
|
|
46
56
|
|
47
57
|
MailCatcher is just a mishmash of other people's hard work. Thank you so much to the people who have built the wonderful guts on which this project relies.
|
48
58
|
|
59
|
+
Thanks also to [The Frontier Group][tfg] for giving me the idea, being great guinea pigs and letting me steal pieces of time to keep the project alive.
|
60
|
+
|
49
61
|
## Copyright
|
50
62
|
|
51
|
-
Copyright
|
63
|
+
Copyright © 2010-2011 Samuel Cochran. See [LICENSE][license] for details.
|
52
64
|
|
53
65
|
## Dreams
|
54
66
|
|
55
|
-
For dream catching, try [this](http://goo.gl/kgbh).
|
67
|
+
For dream catching, try [this](http://goo.gl/kgbh). OR [THIS](http://www.nyanicorn.com), OMG.
|
56
68
|
|
57
|
-
[
|
69
|
+
[license]: https://github.com/sj26/mailcatcher/blob/master/LICENSE
|
70
|
+
[mailcatcher-github]: https://github.com/sj26/mailcatcher
|
71
|
+
[tfg]: http://www.thefrontiergroup.com.au
|
72
|
+
[websockets]: http://www.whatwg.org/specs/web-socket-protocol/
|
73
|
+
[withphp]: http://webschuur.com/publications/blogs/2011-05-29-catchmail_for_drupal_and_other_phpapplications_the_simple_version
|
data/lib/mail_catcher.rb
CHANGED
@@ -17,7 +17,7 @@ module MailCatcher
|
|
17
17
|
:http_ip => '127.0.0.1',
|
18
18
|
:http_port => '1080',
|
19
19
|
:verbose => false,
|
20
|
-
:daemon =>
|
20
|
+
:daemon => true,
|
21
21
|
}
|
22
22
|
|
23
23
|
def self.parse! arguments=ARGV, defaults=@@defaults
|
@@ -45,8 +45,8 @@ module MailCatcher
|
|
45
45
|
options[:http_port] = port
|
46
46
|
end
|
47
47
|
|
48
|
-
parser.on('-
|
49
|
-
options[:daemon] =
|
48
|
+
parser.on('-f', '--foreground', 'Run in the foreground') do
|
49
|
+
options[:daemon] = false
|
50
50
|
end
|
51
51
|
|
52
52
|
parser.on('-v', '--verbose', 'Be more verbose') do
|
@@ -68,22 +68,54 @@ module MailCatcher
|
|
68
68
|
options ||= parse!
|
69
69
|
|
70
70
|
puts "Starting MailCatcher"
|
71
|
-
puts "==> smtp://#{options[:smtp_ip]}:#{options[:smtp_port]}"
|
72
|
-
puts "==> http://#{options[:http_ip]}:#{options[:http_port]}"
|
73
71
|
|
74
72
|
Thin::Logging.silent = true
|
75
73
|
|
74
|
+
# One EventMachine loop...
|
76
75
|
EventMachine.run do
|
77
|
-
|
76
|
+
# TODO: DRY this up
|
78
77
|
|
79
|
-
|
78
|
+
# Set up an SMTP server to run within EventMachine
|
79
|
+
rescue_port options[:smtp_port] do
|
80
|
+
EventMachine.start_server options[:smtp_ip], options[:smtp_port], Smtp
|
81
|
+
puts "==> smtp://#{options[:smtp_ip]}:#{options[:smtp_port]}"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Let Thin set itself up inside our EventMachine loop
|
85
|
+
# (Skinny/WebSockets just works on the inside)
|
86
|
+
rescue_port options[:http_port] do
|
87
|
+
Thin::Server.start options[:http_ip], options[:http_port], Web
|
88
|
+
puts "==> http://#{options[:http_ip]}:#{options[:http_port]}"
|
89
|
+
end
|
80
90
|
|
91
|
+
# Daemonize, if we should, but only after the servers have started.
|
81
92
|
if options[:daemon]
|
82
|
-
# Make sure the servers start before daemonizing.
|
83
93
|
EventMachine.next_tick do
|
84
|
-
|
94
|
+
puts "*** MailCatcher now runs as a daemon by default. Go to the web interface to quit."
|
95
|
+
Process.daemon
|
85
96
|
end
|
86
97
|
end
|
87
98
|
end
|
88
99
|
end
|
100
|
+
|
101
|
+
def self.quit!
|
102
|
+
EventMachine.next_tick { EventMachine.stop_event_loop }
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def self.rescue_port port
|
108
|
+
begin
|
109
|
+
yield
|
110
|
+
|
111
|
+
# XXX: EventMachine only spits out RuntimeError with a string description
|
112
|
+
rescue RuntimeError
|
113
|
+
if $!.to_s =~ /\bno acceptor\b/
|
114
|
+
puts "~~> ERROR: Something's using port #{port}. Are you already running MailCatcher?"
|
115
|
+
exit -1
|
116
|
+
else
|
117
|
+
raise
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
89
121
|
end
|
data/lib/mail_catcher/mail.rb
CHANGED
@@ -69,7 +69,7 @@ module MailCatcher::Mail
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def messages
|
72
|
-
@@messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at
|
72
|
+
@@messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at ASC"
|
73
73
|
@@messages_query.execute.map do |row|
|
74
74
|
Hash[row.fields.zip(row)].tap do |message|
|
75
75
|
message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
|
@@ -142,5 +142,13 @@ module MailCatcher::Mail
|
|
142
142
|
part["cid"] == cid
|
143
143
|
end
|
144
144
|
end
|
145
|
+
|
146
|
+
def delete!
|
147
|
+
@@delete_messages_query ||= db.prepare 'DELETE FROM message'
|
148
|
+
@@delete_message_parts_query ||= db.prepare 'DELETE FROM message_part'
|
149
|
+
|
150
|
+
@@delete_messages_query.execute and
|
151
|
+
@@delete_message_parts_query.execute
|
152
|
+
end
|
145
153
|
end
|
146
154
|
end
|
data/lib/mail_catcher/web.rb
CHANGED
@@ -17,6 +17,11 @@ module MailCatcher
|
|
17
17
|
haml :index
|
18
18
|
end
|
19
19
|
|
20
|
+
delete '/' do
|
21
|
+
MailCatcher.quit!
|
22
|
+
status 204
|
23
|
+
end
|
24
|
+
|
20
25
|
get '/messages' do
|
21
26
|
if request.websocket?
|
22
27
|
request.websocket!(
|
@@ -32,6 +37,11 @@ module MailCatcher
|
|
32
37
|
end
|
33
38
|
end
|
34
39
|
|
40
|
+
delete '/messages' do
|
41
|
+
MailCatcher::Mail.delete!
|
42
|
+
status 204
|
43
|
+
end
|
44
|
+
|
35
45
|
get '/messages/:id.json' do
|
36
46
|
id = params[:id].to_i
|
37
47
|
if message = MailCatcher::Mail.message(id)
|
@@ -109,7 +119,7 @@ module MailCatcher
|
|
109
119
|
not_found
|
110
120
|
end
|
111
121
|
end
|
112
|
-
|
122
|
+
|
113
123
|
not_found do
|
114
124
|
"<html><body><h1>No Dice</h1><p>The message you were looking for does not exist, or doesn't have content of this type.</p></body></html>"
|
115
125
|
end
|
@@ -3,11 +3,40 @@
|
|
3
3
|
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
4
4
|
MailCatcher = (function() {
|
5
5
|
function MailCatcher() {
|
6
|
-
$('#
|
6
|
+
$('#messages tr').live('click', __bind(function(e) {
|
7
7
|
return this.loadMessage($(e.currentTarget).attr('data-message-id'));
|
8
8
|
}, this));
|
9
|
-
$('#message .
|
10
|
-
return this.loadMessageBody($('#
|
9
|
+
$('#message .views .tab').live('click', __bind(function(e) {
|
10
|
+
return this.loadMessageBody($('#messages tr.selected').attr('data-message-id'), $(e.currentTarget).attr('data-message-format'));
|
11
|
+
}, this));
|
12
|
+
$('nav.app .clear a').live('click', __bind(function(e) {
|
13
|
+
if (confirm("You will lose all your received messages.\n\nAre you sure you want to clear all messages?")) {
|
14
|
+
return $.ajax({
|
15
|
+
url: '/messages',
|
16
|
+
type: 'DELETE',
|
17
|
+
success: function() {
|
18
|
+
$('#messages tbody, #message .metadata dd').empty();
|
19
|
+
$('#message .metadata .attachments').hide();
|
20
|
+
return $('#message iframe').attr('src', 'about:blank');
|
21
|
+
},
|
22
|
+
error: function() {
|
23
|
+
return alert('Error while quitting.');
|
24
|
+
}
|
25
|
+
});
|
26
|
+
}
|
27
|
+
}, this));
|
28
|
+
$('nav.app .quit a').live('click', __bind(function(e) {
|
29
|
+
if (confirm("You will lose all your received messages.\n\nAre you sure you want to quit?")) {
|
30
|
+
return $.ajax({
|
31
|
+
type: 'DELETE',
|
32
|
+
success: function() {
|
33
|
+
return location.replace($('body > header h1 a').attr('href'));
|
34
|
+
},
|
35
|
+
error: function() {
|
36
|
+
return alert('Error while quitting.');
|
37
|
+
}
|
38
|
+
});
|
39
|
+
}
|
11
40
|
}, this));
|
12
41
|
this.refresh();
|
13
42
|
this.subscribe();
|
@@ -29,25 +58,25 @@
|
|
29
58
|
if (message.id != null) {
|
30
59
|
message = message.id;
|
31
60
|
}
|
32
|
-
return $("#
|
61
|
+
return $("#messages tbody tr[data-message-id=\"" + message + "\"]").length > 0;
|
33
62
|
};
|
34
63
|
MailCatcher.prototype.addMessage = function(message) {
|
35
|
-
return $('#
|
64
|
+
return $('#messages tbody').append($('<tr />').attr('data-message-id', message.id.toString()).append($('<td/>').text(message.sender)).append($('<td/>').text((message.recipients || []).join(', '))).append($('<td/>').text(message.subject)).append($('<td/>').text(this.formatDate(message.created_at))));
|
36
65
|
};
|
37
66
|
MailCatcher.prototype.loadMessage = function(id) {
|
38
67
|
if ((id != null ? id.id : void 0) != null) {
|
39
68
|
id = id.id;
|
40
69
|
}
|
41
|
-
id || (id = $('#
|
70
|
+
id || (id = $('#messages tr.selected').attr('data-message-id'));
|
42
71
|
if (id != null) {
|
43
|
-
$('#
|
44
|
-
$('#
|
72
|
+
$('#messages tbody tr:not([data-message-id="' + id + '"])').removeClass('selected');
|
73
|
+
$('#messages tbody tr[data-message-id="' + id + '"]').addClass('selected');
|
45
74
|
return $.getJSON('/messages/' + id + '.json', __bind(function(message) {
|
46
|
-
$('#message .
|
47
|
-
$('#message .from
|
48
|
-
$('#message .to
|
49
|
-
$('#message .subject
|
50
|
-
$('#message .
|
75
|
+
$('#message .metadata dd.created_at').text(this.formatDate(message.created_at));
|
76
|
+
$('#message .metadata dd.from').text(message.sender);
|
77
|
+
$('#message .metadata dd.to').text((message.recipients || []).join(', '));
|
78
|
+
$('#message .metadata dd.subject').text(message.subject);
|
79
|
+
$('#message .views .tab.format').each(function(i, el) {
|
51
80
|
var $el, format;
|
52
81
|
$el = $(el);
|
53
82
|
format = $el.attr('data-message-format');
|
@@ -57,30 +86,30 @@
|
|
57
86
|
return $el.hide();
|
58
87
|
}
|
59
88
|
});
|
60
|
-
if ($("#message .
|
61
|
-
$("#message .
|
62
|
-
$("#message .
|
89
|
+
if ($("#message .views .tab.selected:not(:visible)").length) {
|
90
|
+
$("#message .views .tab.selected").removeClass("selected");
|
91
|
+
$("#message .views .tab:visible:first").addClass("selected");
|
63
92
|
}
|
64
93
|
if (message.attachments.length) {
|
65
|
-
$('#message .metadata .attachments ul').empty();
|
94
|
+
$('#message .metadata dd.attachments ul').empty();
|
66
95
|
$.each(message.attachments, function(i, attachment) {
|
67
|
-
return $('#message .metadata .attachments ul').append($('<li>').append($('<a>').attr('href', attachment['href']).addClass(attachment['type'].split('/', 1)[0]).addClass(attachment['type'].replace('/', '-')).text(attachment['filename'])));
|
96
|
+
return $('#message .metadata dd.attachments ul').append($('<li>').append($('<a>').attr('href', attachment['href']).addClass(attachment['type'].split('/', 1)[0]).addClass(attachment['type'].replace('/', '-')).text(attachment['filename'])));
|
68
97
|
});
|
69
98
|
$('#message .metadata .attachments').show();
|
70
99
|
} else {
|
71
100
|
$('#message .metadata .attachments').hide();
|
72
101
|
}
|
73
|
-
$('#message .actions
|
102
|
+
$('#message .actions .download a').attr('href', "/messages/" + id + ".eml");
|
74
103
|
return this.loadMessageBody();
|
75
104
|
}, this));
|
76
105
|
}
|
77
106
|
};
|
78
107
|
MailCatcher.prototype.loadMessageBody = function(id, format) {
|
79
|
-
id || (id = $('#
|
80
|
-
format || (format = $('#message .
|
108
|
+
id || (id = $('#messages tr.selected').attr('data-message-id'));
|
109
|
+
format || (format = $('#message .views .tab.format.selected').attr('data-message-format'));
|
81
110
|
format || (format = 'html');
|
82
|
-
$("#message .
|
83
|
-
$("#message .
|
111
|
+
$("#message .views .tab[data-message-format=\"" + format + "\"]:not(.selected)").addClass('selected');
|
112
|
+
$("#message .views .tab:not([data-message-format=\"" + format + "\"]).selected").removeClass('selected');
|
84
113
|
if (id != null) {
|
85
114
|
return $('#message iframe').attr("src", "/messages/" + id + "." + format);
|
86
115
|
}
|
@@ -1,186 +1,247 @@
|
|
1
|
-
html, body, div, span, applet, object, iframe,
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
html, body, div, span, applet, object, iframe,
|
2
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
3
|
+
a, abbr, acronym, address, big, cite, code,
|
4
|
+
del, dfn, em, img, ins, kbd, q, s, samp,
|
5
|
+
small, strike, strong, sub, sup, tt, var,
|
6
|
+
b, u, i, center,
|
7
|
+
dl, dt, dd, ol, ul, li,
|
8
|
+
fieldset, form, label, legend,
|
9
|
+
table, caption, tbody, tfoot, thead, tr, th, td,
|
10
|
+
article, aside, canvas, details, embed,
|
11
|
+
figure, figcaption, footer, header, hgroup,
|
12
|
+
menu, nav, output, ruby, section, summary,
|
13
|
+
time, mark, audio, video {
|
14
|
+
margin: 0;
|
15
|
+
padding: 0;
|
16
|
+
border: 0;
|
8
17
|
font-size: 100%;
|
9
|
-
font
|
18
|
+
font: inherit;
|
10
19
|
vertical-align: baseline; }
|
11
20
|
|
12
21
|
body {
|
13
|
-
line-height: 1;
|
14
|
-
background: #eee;
|
15
|
-
color: #000;
|
16
|
-
font-size: 12px;
|
17
|
-
font-family: Helvetica, Arial, sans-serif; }
|
22
|
+
line-height: 1; }
|
18
23
|
|
19
24
|
ol, ul {
|
20
25
|
list-style: none; }
|
21
26
|
|
22
27
|
table {
|
23
|
-
border-collapse:
|
24
|
-
border-spacing:
|
28
|
+
border-collapse: collapse;
|
29
|
+
border-spacing: 0; }
|
25
30
|
|
26
31
|
caption, th, td {
|
27
32
|
text-align: left;
|
28
|
-
font-weight: normal;
|
33
|
+
font-weight: normal;
|
34
|
+
vertical-align: middle; }
|
35
|
+
|
36
|
+
q, blockquote {
|
37
|
+
quotes: none; }
|
38
|
+
q:before, q:after, blockquote:before, blockquote:after {
|
39
|
+
content: "";
|
40
|
+
content: none; }
|
41
|
+
|
42
|
+
a img {
|
43
|
+
border: none; }
|
44
|
+
|
45
|
+
article, aside, details, figcaption, figure,
|
46
|
+
footer, header, hgroup, menu, nav, section {
|
47
|
+
display: block; }
|
29
48
|
|
30
49
|
html, body {
|
31
50
|
width: 100%;
|
32
51
|
height: 100%; }
|
33
52
|
|
34
53
|
body {
|
35
|
-
display: -webkit-box;
|
36
54
|
display: -moz-box;
|
55
|
+
display: -webkit-box;
|
37
56
|
display: box;
|
38
|
-
-webkit-box-orient: vertical;
|
39
57
|
-moz-box-orient: vertical;
|
40
|
-
box-orient: vertical;
|
58
|
+
-webkit-box-orient: vertical;
|
59
|
+
box-orient: vertical;
|
60
|
+
background: #eee;
|
61
|
+
color: #000;
|
62
|
+
font-size: 12px;
|
63
|
+
font-family: Helvetica, sans-serif; }
|
64
|
+
body body {
|
65
|
+
font-size: 75%;
|
66
|
+
line-height: 2em; }
|
67
|
+
body html > body {
|
68
|
+
font-size: 12px; }
|
69
|
+
|
70
|
+
body > header {
|
71
|
+
overflow: hidden;
|
72
|
+
*zoom: 1;
|
73
|
+
border-bottom: 1px solid #ccc; }
|
74
|
+
body > header h1 {
|
75
|
+
float: left;
|
76
|
+
padding: 6px;
|
77
|
+
font-size: 18px;
|
78
|
+
font-weight: bold; }
|
79
|
+
body > header h1 a {
|
80
|
+
color: black;
|
81
|
+
text-decoration: none;
|
82
|
+
text-shadow: 0 1px 0 white;
|
83
|
+
-moz-transition-property: 0.1s ease;
|
84
|
+
-webkit-transition-property: 0.1s ease;
|
85
|
+
-o-transition-property: 0.1s ease;
|
86
|
+
transition-property: 0.1s ease;
|
87
|
+
-moz-transition-duration: 1s;
|
88
|
+
-webkit-transition-duration: 1s;
|
89
|
+
-o-transition-duration: 1s;
|
90
|
+
transition-duration: 1s; }
|
91
|
+
body > header h1 a:hover {
|
92
|
+
color: #4183C4; }
|
93
|
+
body > header nav {
|
94
|
+
border-left: 1px solid #ccc;
|
95
|
+
border-right: 1px solid #fff; }
|
96
|
+
body > header nav.project {
|
97
|
+
float: left; }
|
98
|
+
body > header nav.app {
|
99
|
+
float: right; }
|
100
|
+
body > header nav li {
|
101
|
+
display: inline; }
|
102
|
+
body > header nav li a {
|
103
|
+
display: inline-block;
|
104
|
+
float: left;
|
105
|
+
padding: 9px;
|
106
|
+
border-left: 1px solid #fff;
|
107
|
+
border-right: 1px solid #ccc;
|
108
|
+
background: #eee;
|
109
|
+
color: #666;
|
110
|
+
font-weight: bold;
|
111
|
+
text-decoration: none;
|
112
|
+
text-shadow: 0 1px 0 white; }
|
113
|
+
body > header nav li a:hover {
|
114
|
+
background: #fff; }
|
115
|
+
body > header nav li a:active {
|
116
|
+
margin: 1px -1px -1px 1px;
|
117
|
+
-moz-box-shadow: none;
|
118
|
+
-webkit-box-shadow: none;
|
119
|
+
-o-box-shadow: none;
|
120
|
+
box-shadow: none; }
|
121
|
+
|
122
|
+
#messages {
|
123
|
+
width: 100%;
|
124
|
+
height: 10em;
|
125
|
+
overflow: auto;
|
126
|
+
background: #fff;
|
127
|
+
border-top: 1px solid #fff;
|
128
|
+
border-bottom: 1px solid #ccc; }
|
129
|
+
#messages table {
|
130
|
+
width: 100%; }
|
131
|
+
#messages table thead tr {
|
132
|
+
background: #eee;
|
133
|
+
color: #333; }
|
134
|
+
#messages table thead tr th {
|
135
|
+
padding: .25em;
|
136
|
+
font-weight: bold;
|
137
|
+
color: #666;
|
138
|
+
text-shadow: 0 1px 0 white; }
|
139
|
+
#messages table tbody tr:nth-child(even) {
|
140
|
+
background: #f0f0f0; }
|
141
|
+
#messages table tbody tr.selected {
|
142
|
+
background: Highlight;
|
143
|
+
color: HighlightText; }
|
144
|
+
#messages table tbody tr td {
|
145
|
+
padding: .25em; }
|
41
146
|
|
42
147
|
#message {
|
43
|
-
display: -webkit-box;
|
44
148
|
display: -moz-box;
|
149
|
+
display: -webkit-box;
|
45
150
|
display: box;
|
46
|
-
-webkit-box-orient: vertical;
|
47
151
|
-moz-box-orient: vertical;
|
152
|
+
-webkit-box-orient: vertical;
|
48
153
|
box-orient: vertical;
|
49
|
-
-webkit-box-flex: 1;
|
50
154
|
-moz-box-flex: 1;
|
51
|
-
box-flex: 1; }
|
52
|
-
|
53
|
-
#message iframe {
|
54
|
-
display: -webkit-box;
|
55
|
-
display: -moz-box;
|
56
|
-
display: box;
|
57
155
|
-webkit-box-flex: 1;
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#message .
|
94
|
-
|
95
|
-
|
96
|
-
#message .
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
#message .
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
#message .actions ul li.button {
|
152
|
-
display: inline-block;
|
153
|
-
float: right;
|
154
|
-
margin: 0 .25em; }
|
155
|
-
|
156
|
-
#message .actions ul li.button a {
|
157
|
-
display: inline-block;
|
158
|
-
padding: .25em .5em;
|
159
|
-
border: 1px solid #999;
|
160
|
-
background: #ddd;
|
161
|
-
color: #333;
|
162
|
-
text-decoration: none;
|
163
|
-
-moz-text-shadow: 1px 1px 0 #eee;
|
164
|
-
-webkit-text-shadow: 1px 1px 0 #eee;
|
165
|
-
text-shadow: 1px 1px 0 #eee;
|
166
|
-
-moz-box-shadow: none;
|
167
|
-
-webkit-box-shadow: none;
|
168
|
-
box-shadow: 1px 1px 0 #ccc; }
|
169
|
-
|
170
|
-
#message .actions ul li.button a:hover {
|
171
|
-
background: #fff;
|
172
|
-
-moz-box-shadow: none;
|
173
|
-
-webkit-box-shadow: none;
|
174
|
-
box-shadow: none; }
|
175
|
-
|
176
|
-
#message .actions ul li.button a:active {
|
177
|
-
margin: 1px -1px -1px 1px;
|
178
|
-
-moz-box-shadow: none;
|
179
|
-
-webkit-box-shadow: none;
|
180
|
-
box-shadow: none; }
|
181
|
-
|
182
|
-
#message .body {
|
183
|
-
width: 100%;
|
184
|
-
min-height: 20em;
|
185
|
-
height: 100%;
|
186
|
-
background: #fff; }
|
156
|
+
box-flex: 1;
|
157
|
+
border-top: 1px solid #fff; }
|
158
|
+
#message > header {
|
159
|
+
overflow: hidden;
|
160
|
+
*zoom: 1; }
|
161
|
+
#message > header .metadata {
|
162
|
+
overflow: hidden;
|
163
|
+
*zoom: 1;
|
164
|
+
padding: .5em; }
|
165
|
+
#message > header .metadata dt, #message > header .metadata dd {
|
166
|
+
padding: .25em; }
|
167
|
+
#message > header .metadata dt {
|
168
|
+
float: left;
|
169
|
+
clear: left;
|
170
|
+
width: 8em;
|
171
|
+
margin-right: .5em;
|
172
|
+
text-align: right;
|
173
|
+
font-weight: bold;
|
174
|
+
color: #666;
|
175
|
+
text-shadow: 0 1px 0 white; }
|
176
|
+
#message > header .metadata dd.subject {
|
177
|
+
font-weight: bold; }
|
178
|
+
#message > header .metadata .attachments {
|
179
|
+
display: none; }
|
180
|
+
#message > header .metadata .attachments ul {
|
181
|
+
display: inline; }
|
182
|
+
#message > header .metadata .attachments ul li {
|
183
|
+
display: -moz-inline-box;
|
184
|
+
-moz-box-orient: vertical;
|
185
|
+
display: inline-block;
|
186
|
+
vertical-align: middle;
|
187
|
+
*vertical-align: auto;
|
188
|
+
margin-right: .5em; }
|
189
|
+
#message > header .metadata .attachments ul li {
|
190
|
+
*display: inline; }
|
191
|
+
#message > header .views ul {
|
192
|
+
padding: 0 .5em;
|
193
|
+
border-bottom: 1px solid #999; }
|
194
|
+
#message > header .views .tab {
|
195
|
+
display: inline-block;
|
196
|
+
padding: .5em;
|
197
|
+
border: 1px solid #999;
|
198
|
+
background: #ddd;
|
199
|
+
color: #333;
|
200
|
+
border-width: 1px 1px 0 1px;
|
201
|
+
cursor: pointer;
|
202
|
+
text-shadow: 0 1px 0 #eeeeee; }
|
203
|
+
#message > header .views .tab:not(.selected):hover {
|
204
|
+
background-color: #ddd; }
|
205
|
+
#message > header .views .tab.selected {
|
206
|
+
background: #fff;
|
207
|
+
color: #000;
|
208
|
+
height: 13px;
|
209
|
+
-moz-box-shadow: 1px 1px 0 #cccccc;
|
210
|
+
-webkit-box-shadow: 1px 1px 0 #cccccc;
|
211
|
+
-o-box-shadow: 1px 1px 0 #cccccc;
|
212
|
+
box-shadow: 1px 1px 0 #cccccc;
|
213
|
+
margin-bottom: -2px; }
|
214
|
+
#message > header .views .button {
|
215
|
+
display: inline-block;
|
216
|
+
float: right;
|
217
|
+
margin: 0 .25em; }
|
218
|
+
#message > header .views .button a {
|
219
|
+
display: inline-block;
|
220
|
+
padding: .25em .5em;
|
221
|
+
border: 1px solid #999;
|
222
|
+
background: #ddd;
|
223
|
+
color: #333;
|
224
|
+
text-decoration: none;
|
225
|
+
text-shadow: 1px 1px 0 #eeeeee;
|
226
|
+
-moz-box-shadow: 1px 1px 0 #cccccc;
|
227
|
+
-webkit-box-shadow: 1px 1px 0 #cccccc;
|
228
|
+
-o-box-shadow: 1px 1px 0 #cccccc;
|
229
|
+
box-shadow: 1px 1px 0 #cccccc; }
|
230
|
+
#message > header .views .button a:hover {
|
231
|
+
background: #fff;
|
232
|
+
text-shadow: none; }
|
233
|
+
#message > header .views .button a:active {
|
234
|
+
margin: 1px -1px -1px 1px;
|
235
|
+
-moz-box-shadow: none;
|
236
|
+
-webkit-box-shadow: none;
|
237
|
+
-o-box-shadow: none;
|
238
|
+
box-shadow: none; }
|
239
|
+
#message .body {
|
240
|
+
display: -moz-box;
|
241
|
+
display: -webkit-box;
|
242
|
+
display: box;
|
243
|
+
-moz-box-flex: 1;
|
244
|
+
-webkit-box-flex: 1;
|
245
|
+
box-flex: 1;
|
246
|
+
width: 100%;
|
247
|
+
background: #fff; }
|
data/views/index.haml
CHANGED
@@ -7,7 +7,16 @@
|
|
7
7
|
%script{:src => "/javascripts/date.js"}
|
8
8
|
%script{:src => "/javascripts/application.js"}
|
9
9
|
%body
|
10
|
-
|
10
|
+
%header
|
11
|
+
%h1
|
12
|
+
%a{:href => "http://mailcatcher.me", :target => '_blank'} MailCatcher
|
13
|
+
%nav.app
|
14
|
+
%ul
|
15
|
+
%li.clear
|
16
|
+
%a{:href => '#', :title => 'Clear all messages'} Clear
|
17
|
+
%li.quit
|
18
|
+
%a{:href => '#', :title => 'Quit MailCatcher'} Quit
|
19
|
+
%nav#messages
|
11
20
|
%table
|
12
21
|
%thead
|
13
22
|
%tr
|
@@ -16,29 +25,26 @@
|
|
16
25
|
%th Subject
|
17
26
|
%th Received
|
18
27
|
%tbody
|
19
|
-
#message
|
20
|
-
|
21
|
-
.
|
22
|
-
%
|
23
|
-
%
|
24
|
-
|
25
|
-
%
|
26
|
-
%
|
27
|
-
|
28
|
-
%
|
29
|
-
%
|
30
|
-
|
31
|
-
%
|
32
|
-
|
33
|
-
.
|
34
|
-
%label Attachments
|
28
|
+
%article#message
|
29
|
+
%header
|
30
|
+
%dl.metadata
|
31
|
+
%dt.created_at Received
|
32
|
+
%dd.created_at
|
33
|
+
%dt.from From
|
34
|
+
%dd.from
|
35
|
+
%dt.to To
|
36
|
+
%dd.to
|
37
|
+
%dt.subject Subject
|
38
|
+
%dd.subject
|
39
|
+
%dt.attachments Attachments
|
40
|
+
%dd.attachments
|
41
|
+
%ul
|
42
|
+
%nav.views
|
35
43
|
%ul
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
%a{:href => '#'}
|
43
|
-
%span Download
|
44
|
+
%li.format.tab.html.selected{'data-message-format' => 'html'} HTML
|
45
|
+
%li.format.tab.plain{'data-message-format' => 'plain'} Plain Text
|
46
|
+
%li.format.tab.source{'data-message-format' => 'source'} Source
|
47
|
+
%li.button.download
|
48
|
+
%a{:href => '#'}
|
49
|
+
%span Download
|
44
50
|
%iframe.body
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: mailcatcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.4.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Samuel Cochran
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-05-
|
13
|
+
date: 2011-05-31 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -123,16 +123,27 @@ dependencies:
|
|
123
123
|
type: :development
|
124
124
|
version_requirements: *id010
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: compass
|
127
127
|
prerelease: false
|
128
128
|
requirement: &id011 !ruby/object:Gem::Requirement
|
129
129
|
none: false
|
130
130
|
requirements:
|
131
131
|
- - ~>
|
132
132
|
- !ruby/object:Gem::Version
|
133
|
-
version:
|
133
|
+
version: 0.11.1
|
134
134
|
type: :development
|
135
135
|
version_requirements: *id011
|
136
|
+
- !ruby/object:Gem::Dependency
|
137
|
+
name: coffee-script
|
138
|
+
prerelease: false
|
139
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ~>
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: "2.2"
|
145
|
+
type: :development
|
146
|
+
version_requirements: *id012
|
136
147
|
description: " MailCatcher runs a super simple SMTP server which catches any\n message sent to it to display in a web interface. Run\n mailcatcher, set your favourite app to deliver to\n smtp://127.0.0.1:1025 instead of your default SMTP server,\n then check out http://127.0.0.1:1080 to see the mail.\n"
|
137
148
|
email: sj26@sj26.com
|
138
149
|
executables:
|