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 CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Samuel Cochran
1
+ Copyright (c) 2010-2011 Samuel Cochran
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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
- * Written super-simply in EventMachine, easy to dig in and change.
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
- * Daemonizable to run in the background.
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
- ## Caveats
22
+ ## How
29
23
 
30
- * 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.
31
- * The interface is very basic and has not been tested on many browsers yet.
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 (c) 2010 Samuel Cochran. See LICENSE for details.
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
- [websockets]: http://www.whatwg.org/specs/web-socket-protocol/
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
@@ -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 => false,
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('-d', '--daemon', 'Run as a daemon') do
49
- options[:daemon] = true
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
- EventMachine.start_server options[:smtp_ip], options[:smtp_port], Smtp
76
+ # TODO: DRY this up
78
77
 
79
- Thin::Server.start options[:http_ip], options[:http_port], Web
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
- Daemons.daemonize :app_name => "mailcatcher"
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
@@ -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 DESC"
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
@@ -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
- $('#mail tr').live('click', __bind(function(e) {
6
+ $('#messages tr').live('click', __bind(function(e) {
7
7
  return this.loadMessage($(e.currentTarget).attr('data-message-id'));
8
8
  }, this));
9
- $('#message .actions ul li.tab').live('click', __bind(function(e) {
10
- return this.loadMessageBody($('#mail tr.selected').attr('data-message-id'), $(e.currentTarget).attr('data-message-format'));
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 $("#mail tbody tr[data-message-id=\"" + message + "\"]").length > 0;
61
+ return $("#messages tbody tr[data-message-id=\"" + message + "\"]").length > 0;
33
62
  };
34
63
  MailCatcher.prototype.addMessage = function(message) {
35
- return $('#mail 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))));
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 = $('#mail tr.selected').attr('data-message-id'));
70
+ id || (id = $('#messages tr.selected').attr('data-message-id'));
42
71
  if (id != null) {
43
- $('#mail tbody tr:not([data-message-id="' + id + '"])').removeClass('selected');
44
- $('#mail tbody tr[data-message-id="' + id + '"]').addClass('selected');
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 .received span').text(this.formatDate(message.created_at));
47
- $('#message .from span').text(message.sender);
48
- $('#message .to span').text((message.recipients || []).join(', '));
49
- $('#message .subject span').text(message.subject);
50
- $('#message .actions ul li.format').each(function(i, el) {
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 .actions ul li.tab.selected:not(:visible)").length) {
61
- $("#message .actions ul li.tab.selected").removeClass("selected");
62
- $("#message .actions ul li.tab:visible:first").addClass("selected");
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 ul li.download a').attr('href', "/messages/" + id + ".eml");
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 = $('#mail tr.selected').attr('data-message-id'));
80
- format || (format = $('#message .actions ul li.format.selected').attr('data-message-format'));
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 .actions ul li.tab[data-message-format=\"" + format + "\"]:not(.selected)").addClass('selected');
83
- $("#message .actions ul li.tab:not([data-message-format=\"" + format + "\"]).selected").removeClass('selected');
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, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {
2
- margin: 0px;
3
- padding: 0px;
4
- border: 0px;
5
- outline: 0px;
6
- font-weight: inherit;
7
- font-style: inherit;
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-family: inherit;
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: separate;
24
- border-spacing: 0px; }
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
- -moz-box-flex: 1;
59
- box-flex: 1; }
60
-
61
- #mail {
62
- height: 10em;
63
- overflow: auto;
64
- background: #fff;
65
- border-bottom: 1px solid #ccc; }
66
-
67
- #mail table {
68
- width: 100%; }
69
-
70
- #mail table thead tr {
71
- background: #eee;
72
- color: #333; }
73
-
74
- #mail table thead tr th {
75
- padding: .25em;
76
- font-weight: bold;
77
- color: #666;
78
- -moz-text-shadow: 0 1px 0 #fff;
79
- -webkit-text-shadow: 0 1px 0 #fff;
80
- text-shadow: 0 1px 0 #fff; }
81
-
82
- #mail table tbody tr:nth-child(even) {
83
- background: #f0f0f0;
84
- color: #333; }
85
-
86
- #mail table tbody tr.selected {
87
- background: Highlight;
88
- color: HighlightText; }
89
-
90
- #mail table tbody tr td {
91
- padding: .25em; }
92
-
93
- #message .metadata {
94
- padding: 1em; }
95
-
96
- #message .metadata div {
97
- padding: .25em; }
98
-
99
- #message .metadata div label {
100
- display: inline-block;
101
- width: 8em;
102
- margin-right: .5em;
103
- text-align: right;
104
- font-weight: bold;
105
- color: #666;
106
- -moz-text-shadow: 0 1px 0 #fff;
107
- -webkit-text-shadow: 0 1px 0 #fff;
108
- text-shadow: 0 1px 0 #fff; }
109
-
110
- #message .metadata .subject span {
111
- font-weight: bold; }
112
-
113
- #message .metadata .attachments {
114
- display: none; }
115
-
116
- #message .metadata .attachments ul {
117
- display: inline; }
118
-
119
- #message .metadata .attachments ul li {
120
- display: inline-block;
121
- margin-right: .5em; }
122
-
123
- #message .actions ul {
124
- border-bottom: 1px solid #999;
125
- padding: 0 .5em; }
126
-
127
- #message .actions ul li.tab {
128
- display: inline-block;
129
- padding: .5em;
130
- border: 1px solid #999;
131
- background: #ddd;
132
- color: #333;
133
- border-width: 1px 1px 0 1px;
134
- cursor: pointer;
135
- -moz-text-shadow: 0 1px 0 #eee;
136
- -webkit-text-shadow: 0 1px 0 #eee;
137
- text-shadow: 0 1px 0 #eee; }
138
-
139
- #message .actions ul li.tab:not(.selected):hover {
140
- background-color: #ddd; }
141
-
142
- #message .actions ul li.tab.selected {
143
- background: #fff;
144
- color: #000;
145
- height: 13px;
146
- -moz-box-shadow: 1px 1px 0 #ccc;
147
- -webkit-box-shadow: 1px 1px 0 #ccc;
148
- box-shadow: 1px 1px 0 #ccc;
149
- margin-bottom: -2px; }
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; }
@@ -7,7 +7,16 @@
7
7
  %script{:src => "/javascripts/date.js"}
8
8
  %script{:src => "/javascripts/application.js"}
9
9
  %body
10
- #mail
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
- .metadata
21
- .received
22
- %label Received
23
- %span
24
- .from
25
- %label From
26
- %span
27
- .to
28
- %label To
29
- %span
30
- .subject
31
- %label Subject
32
- %span
33
- .attachments
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
- .actions
37
- %ul
38
- %li.format.tab.html.selected{'data-message-format' => 'html'} HTML
39
- %li.format.tab.plain{'data-message-format' => 'plain'} Plain Text
40
- %li.format.tab.source{'data-message-format' => 'source'} Source
41
- %li.button.download
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.3.2
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-29 00:00:00 Z
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: coffee-script
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: "2.2"
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: