mailcatcher 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,7 +6,7 @@ module MailCatcher::Mail
6
6
  class << self
7
7
  def db
8
8
  @@__db ||= begin
9
- SQLite3::Database.new(':memory:', :results_as_hash => true, :type_translation => true).tap do |db|
9
+ SQLite3::Database.new(':memory:', :type_translation => true).tap do |db|
10
10
  db.execute(<<-SQL)
11
11
  CREATE TABLE message (
12
12
  id INTEGER PRIMARY KEY ASC,
@@ -15,6 +15,7 @@ module MailCatcher::Mail
15
15
  subject TEXT,
16
16
  source BLOB,
17
17
  size TEXT,
18
+ type TEXT,
18
19
  created_at DATETIME DEFAULT CURRENT_DATETIME
19
20
  )
20
21
  SQL
@@ -37,10 +38,10 @@ module MailCatcher::Mail
37
38
  end
38
39
 
39
40
  def add_message(message)
40
- @@add_message_query ||= db.prepare("INSERT INTO message (sender, recipients, subject, source, size, created_at) VALUES (?, ?, ?, ?, ?, datetime('now'))")
41
+ @@add_message_query ||= db.prepare("INSERT INTO message (sender, recipients, subject, source, type, size, created_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'))")
41
42
 
42
43
  mail = Mail.new(message[:source])
43
- result = @@add_message_query.execute(message[:sender], message[:recipients].to_json, mail.subject, message[:source], message[:source].length)
44
+ result = @@add_message_query.execute(message[:sender], message[:recipients].to_json, mail.subject, message[:source], mail.mime_type || 'text/plain', message[:source].length)
44
45
  message_id = db.last_insert_row_id
45
46
  parts = mail.all_parts
46
47
  parts = [mail] if parts.empty?
@@ -69,8 +70,8 @@ module MailCatcher::Mail
69
70
 
70
71
  def messages
71
72
  @@messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at DESC"
72
- @@messages_query.execute.to_a.tap do |messages|
73
- messages.each do |message|
73
+ @@messages_query.execute.map do |row|
74
+ Hash[row.fields.zip(row)].tap do |message|
74
75
  message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
75
76
  end
76
77
  end
@@ -78,43 +79,55 @@ module MailCatcher::Mail
78
79
 
79
80
  def message(id)
80
81
  @@message_query ||= db.prepare "SELECT * FROM message WHERE id = ? LIMIT 1"
81
- @@message_query.execute(id).next.to_hash.tap do |message|
82
+ row = @@message_query.execute(id).next
83
+ row && Hash[row.fields.zip(row)].tap do |message|
82
84
  message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
83
85
  end
84
86
  end
85
87
 
86
88
  def message_has_html?(id)
87
- @@message_has_html_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type = 'text/html' LIMIT 1"
88
- !!@@message_has_html_query.execute(id).next
89
+ @@message_has_html_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type IN ('application/xhtml+xml', 'text/html') LIMIT 1"
90
+ (!!@@message_has_html_query.execute(id).next) || ['text/html', 'application/xhtml+xml'].include?(message(id)["type"])
89
91
  end
90
92
 
91
93
  def message_has_plain?(id)
92
- @@message_has_html_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type = 'text/plain' LIMIT 1"
93
- !!@@message_has_html_query.execute(id).next
94
+ @@message_has_plain_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type = 'text/plain' LIMIT 1"
95
+ (!!@@message_has_plain_query.execute(id).next) || message(id)["type"] == "text/plain"
94
96
  end
95
97
 
96
98
  def message_parts(id)
97
99
  @@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? ORDER BY filename ASC"
98
- @@message_parts_query.execute(id).to_a
100
+ @@message_parts_query.execute(id).map do |row|
101
+ Hash[row.fields.zip(row)]
102
+ end
99
103
  end
100
104
 
101
105
  def message_attachments(id)
102
106
  @@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? AND is_attachment = 1 ORDER BY filename ASC"
103
- @@message_parts_query.execute(id).to_a
107
+ @@message_parts_query.execute(id).map do |row|
108
+ Hash[row.fields.zip(row)]
109
+ end
104
110
  end
105
111
 
106
112
  def message_part(message_id, part_id)
107
113
  @@message_part_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND id = ? LIMIT 1"
108
- @@message_part_query.execute(message_id, part_id).next
114
+ row = @@message_part_query.execute(message_id, part_id).next
115
+ row && Hash[row.fields.zip(row)]
109
116
  end
110
117
 
111
118
  def message_part_type(message_id, part_type)
112
119
  @@message_part_type_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND type = ? AND is_attachment = 0 LIMIT 1"
113
- @@message_part_type_query.execute(message_id, part_type).next
120
+ row = @@message_part_type_query.execute(message_id, part_type).next
121
+ row && Hash[row.fields.zip(row)]
114
122
  end
115
123
 
116
124
  def message_part_html(message_id)
117
- message_part_type message_id, "text/html"
125
+ part = message_part_type(message_id, "text/html")
126
+ part ||= message_part_type(message_id, "application/xhtml+xml")
127
+ part ||= begin
128
+ message = message(message_id)
129
+ message if ['text/html', 'application/xhtml+xml'].include? message["type"]
130
+ end
118
131
  end
119
132
 
120
133
  def message_part_plain(message_id)
@@ -123,7 +136,11 @@ module MailCatcher::Mail
123
136
 
124
137
  def message_part_cid(message_id, cid)
125
138
  @@message_part_cid_query ||= db.prepare 'SELECT * FROM message_part WHERE message_id = ?'
126
- @@message_part_cid_query.execute(message_id).find { |part| part["cid"] == cid }
139
+ @@message_part_cid_query.execute(message_id).map do |row|
140
+ part = Hash[row.fields.zip(row)]
141
+ end.find do |part|
142
+ part["cid"] == cid
143
+ end
127
144
  end
128
145
  end
129
146
  end
@@ -40,7 +40,7 @@ module MailCatcher
40
40
  "source",
41
41
  ("html" if MailCatcher::Mail.message_has_html? id),
42
42
  ("plain" if MailCatcher::Mail.message_has_plain? id),
43
- ].flatten,
43
+ ].compact,
44
44
  "attachments" => MailCatcher::Mail.message_attachments(id).map do |attachment|
45
45
  attachment.merge({"href" => "/messages/#{escape(id)}/#{escape(attachment['cid'])}"})
46
46
  end,
@@ -1,98 +1,124 @@
1
- var MailCatcher = {
2
- init: function() {
3
- $('#mail tr').live('click', function() {
4
- MailCatcher.load($(this).attr('data-message-id'));
5
- });
6
-
7
- $('#message .actions ul li.tab').live('click', function() {
8
- MailCatcher.loadBody($('#mail tr.selected').attr('data-message-id'), $(this).attr('data-message-format'));
9
- });
10
-
11
- MailCatcher.refresh();
12
-
13
- MailCatcher.subscribe();
14
- },
15
-
16
- addMessage: function(message) {
17
- $('#mail tbody').append(
18
- $('<tr />').attr('data-message-id', message.id.toString())
19
- .append($('<td/>').text(message.sender))
20
- .append($('<td/>').text((message.recipients || []).join(', ')))
21
- .append($('<td/>').text(message.subject))
22
- .append($('<td/>').text((new Date(message.created_at)).toString("dddd, d MMM yyyy h:mm:ss tt")))
23
- );
24
- },
25
-
26
- refresh: function() {
27
- $.getJSON('/messages', function(mail) {
28
- $.each(mail, function(i, message) {
29
- MailCatcher.addMessage(message);
30
- });
31
- });
32
- },
33
-
34
- subscribe: function () {
35
- if (WebSocket !== undefined) {
36
- MailCatcher.websocket = new WebSocket("ws" + (window.location.scheme == 'https' ? 's' : '') + "://" + window.location.host + "/messages");
37
- MailCatcher.websocket.onmessage = function (event) {
38
- MailCatcher.addMessage($.parseJSON(event.data));
39
- };
40
- } else {
41
- if (!MailCatcher.refreshInterval) {
42
- MailCatcher.refreshInterval = setInterval(MailCatcher.refresh, 30000);
43
- }
1
+ (function() {
2
+ var MailCatcher;
3
+ var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
4
+ MailCatcher = (function() {
5
+ function MailCatcher() {
6
+ $('#mail tr').live('click', __bind(function(e) {
7
+ return this.loadMessage($(e.currentTarget).attr('data-message-id'));
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'));
11
+ }, this));
12
+ this.refresh();
13
+ this.subscribe();
44
14
  }
45
- },
46
-
47
- load: function(id) {
48
- id = id || $('#mail tr.selected').attr('data-message-id');
49
-
50
- if (id !== null) {
51
- $('#mail tbody tr:not([data-message-id="'+id+'"])').removeClass('selected');
52
- $('#mail tbody tr[data-message-id="'+id+'"]').addClass('selected');
53
-
54
- $.getJSON('/messages/' + id + '.json', function(message) {
55
- $('#message .received span').text((new Date(message.created_at)).toString("dddd, d MMM yyyy h:mm:ss tt"));
56
- $('#message .from span').text(message.sender);
57
- $('#message .to span').text((message.recipients || []).join(', '));
58
- $('#message .subject span').text(message.subject);
59
- $('#message .actions ul li.format').each(function(i, el) {
60
- var $el = $(el),
15
+ MailCatcher.prototype.parseDateRegexp = /^(\d{4})[-\/\\](\d{2})[-\/\\](\d{2})(?:\s+|T)(\d{2})[:-](\d{2})[:-](\d{2})(?:([ +-]\d{2}:\d{2}|\s*\S+|Z?))?$/;
16
+ MailCatcher.prototype.parseDate = function(date) {
17
+ var match;
18
+ if (match = this.parseDateRegexp.exec(date)) {
19
+ return new Date(match[1], match[2], match[3], match[4], match[5], match[6], 0);
20
+ }
21
+ };
22
+ MailCatcher.prototype.formatDate = function(date) {
23
+ if (typeof date === "string") {
24
+ date && (date = this.parseDate(date));
25
+ }
26
+ return date && (date = date.toString("dddd, d MMM yyyy h:mm:ss tt"));
27
+ };
28
+ MailCatcher.prototype.haveMessage = function(message) {
29
+ if (message.id != null) {
30
+ message = message.id;
31
+ }
32
+ return $("#mail tbody tr[data-message-id=\"" + message + "\"]").length > 0;
33
+ };
34
+ 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))));
36
+ };
37
+ MailCatcher.prototype.loadMessage = function(id) {
38
+ if ((id != null ? id.id : void 0) != null) {
39
+ id = id.id;
40
+ }
41
+ id || (id = $('#mail tr.selected').attr('data-message-id'));
42
+ if (id != null) {
43
+ $('#mail tbody tr:not([data-message-id="' + id + '"])').removeClass('selected');
44
+ $('#mail tbody tr[data-message-id="' + id + '"]').addClass('selected');
45
+ 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) {
51
+ var $el, format;
52
+ $el = $(el);
61
53
  format = $el.attr('data-message-format');
62
- if ($.inArray(format, message.formats) >= 0) {
63
- $el.show();
54
+ if ($.inArray(format, message.formats) >= 0) {
55
+ return $el.show();
56
+ } else {
57
+ return $el.hide();
58
+ }
59
+ });
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");
63
+ }
64
+ if (message.attachments.length) {
65
+ $('#message .metadata .attachments ul').empty();
66
+ $.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'])));
68
+ });
69
+ $('#message .metadata .attachments').show();
64
70
  } else {
65
- $el.hide();
71
+ $('#message .metadata .attachments').hide();
66
72
  }
67
- });
68
- if ($("#message .actions ul li.tab.selected:not(:visible)")) {
69
- $("#message .actions ul li.tab.selected").removeClass("selected");
70
- $("#message .actions ul li.tab:visible:first").addClass("selected");
71
- }
72
- if (message.attachments.length > 0) {
73
- $('#message .metadata .attachments ul').empty();
74
- $.each(message.attachments, function (i, attachment) {
75
- $('#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'])));
76
- });
77
- $('#message .metadata .attachments').show();
78
- } else {
79
- $('#message .metadata .attachments').hide();
80
- }
81
- $('#message .actions ul li.download a').attr('href', '/messages/' + id + '.eml');
82
- MailCatcher.loadBody();
83
- });
84
- }
85
- },
86
-
87
- loadBody: function(id, format) {
88
- id = id || $('#mail tr.selected').attr('data-message-id');
89
- format = format || $('#message .actions ul li.selected').first().attr('data-message-format') || 'html';
90
-
91
- $('#message .actions ul li.tab[data-message-format="'+format+'"]').addClass('selected');
92
- $('#message .actions ul li.tab:not([data-message-format="'+format+'"])').removeClass('selected');
93
-
94
- if (id !== undefined && id !== null) {
95
- $('#message iframe').attr('src', '/messages/' + id + '.' + format);
96
- }
97
- }
98
- };
73
+ $('#message .actions ul li.download a').attr('href', "/messages/" + id + ".eml");
74
+ return this.loadMessageBody();
75
+ }, this));
76
+ }
77
+ };
78
+ 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'));
81
+ 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');
84
+ if (id != null) {
85
+ return $('#message iframe').attr("src", "/messages/" + id + "." + format);
86
+ }
87
+ };
88
+ MailCatcher.prototype.refresh = function() {
89
+ return $.getJSON('/messages', __bind(function(messages) {
90
+ return $.each(messages, __bind(function(i, message) {
91
+ if (!this.haveMessage(message)) {
92
+ return this.addMessage(message);
93
+ }
94
+ }, this));
95
+ }, this));
96
+ };
97
+ MailCatcher.prototype.subscribe = function() {
98
+ if (typeof WebSocket !== "undefined" && WebSocket !== null) {
99
+ return this.subscribeWebSocket();
100
+ } else {
101
+ return this.subscribePoll();
102
+ }
103
+ };
104
+ MailCatcher.prototype.subscribeWebSocket = function() {
105
+ var secure;
106
+ secure = window.location.scheme === 'https';
107
+ this.websocket = new WebSocket("" + (secure ? 'wss' : 'ws') + "://" + window.location.host + "/messages");
108
+ return this.websocket.onmessage = __bind(function(event) {
109
+ return this.addMessage($.parseJSON(event.data));
110
+ }, this);
111
+ };
112
+ MailCatcher.prototype.subscribePoll = function() {
113
+ if (this.refreshInterval == null) {
114
+ return this.refreshInterval = setInterval((__bind(function() {
115
+ return this.refresh();
116
+ }, this)), 1000);
117
+ }
118
+ };
119
+ return MailCatcher;
120
+ })();
121
+ $(function() {
122
+ return window.MailCatcher = new MailCatcher;
123
+ });
124
+ }).call(this);
@@ -75,6 +75,8 @@ body {
75
75
  padding: .25em;
76
76
  font-weight: bold;
77
77
  color: #666;
78
+ -moz-text-shadow: 0 1px 0 #fff;
79
+ -webkit-text-shadow: 0 1px 0 #fff;
78
80
  text-shadow: 0 1px 0 #fff; }
79
81
 
80
82
  #mail table tbody tr:nth-child(even) {
@@ -101,6 +103,8 @@ body {
101
103
  text-align: right;
102
104
  font-weight: bold;
103
105
  color: #666;
106
+ -moz-text-shadow: 0 1px 0 #fff;
107
+ -webkit-text-shadow: 0 1px 0 #fff;
104
108
  text-shadow: 0 1px 0 #fff; }
105
109
 
106
110
  #message .metadata .subject span {
@@ -128,6 +132,8 @@ body {
128
132
  color: #333;
129
133
  border-width: 1px 1px 0 1px;
130
134
  cursor: pointer;
135
+ -moz-text-shadow: 0 1px 0 #eee;
136
+ -webkit-text-shadow: 0 1px 0 #eee;
131
137
  text-shadow: 0 1px 0 #eee; }
132
138
 
133
139
  #message .actions ul li.tab:not(.selected):hover {
@@ -137,7 +143,10 @@ body {
137
143
  background: #fff;
138
144
  color: #000;
139
145
  height: 13px;
140
- margin-bottom: -1px; }
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; }
141
150
 
142
151
  #message .actions ul li.button {
143
152
  display: inline-block;
@@ -148,19 +157,26 @@ body {
148
157
  display: inline-block;
149
158
  padding: .25em .5em;
150
159
  border: 1px solid #999;
151
- border-radius: .35em;
152
160
  background: #ddd;
153
161
  color: #333;
154
162
  text-decoration: none;
163
+ -moz-text-shadow: 1px 1px 0 #eee;
164
+ -webkit-text-shadow: 1px 1px 0 #eee;
155
165
  text-shadow: 1px 1px 0 #eee;
166
+ -moz-box-shadow: none;
167
+ -webkit-box-shadow: none;
156
168
  box-shadow: 1px 1px 0 #ccc; }
157
169
 
158
170
  #message .actions ul li.button a:hover {
159
171
  background: #fff;
160
- text-shadow: none; }
172
+ -moz-box-shadow: none;
173
+ -webkit-box-shadow: none;
174
+ box-shadow: none; }
161
175
 
162
176
  #message .actions ul li.button a:active {
163
177
  margin: 1px -1px -1px 1px;
178
+ -moz-box-shadow: none;
179
+ -webkit-box-shadow: none;
164
180
  box-shadow: none; }
165
181
 
166
182
  #message .body {
@@ -6,16 +6,15 @@
6
6
  %script{:src => "/javascripts/jquery.js"}
7
7
  %script{:src => "/javascripts/date.js"}
8
8
  %script{:src => "/javascripts/application.js"}
9
- :javascript
10
- $(MailCatcher.init);
11
9
  %body
12
10
  #mail
13
11
  %table
14
12
  %thead
15
- %th From
16
- %th To
17
- %th Subject
18
- %th Received
13
+ %tr
14
+ %th From
15
+ %th To
16
+ %th Subject
17
+ %th Received
19
18
  %tbody
20
19
  #message
21
20
  .metadata
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: mailcatcher
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.3.1
5
+ version: 0.3.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Samuel Cochran
@@ -122,6 +122,17 @@ dependencies:
122
122
  version: "3.1"
123
123
  type: :development
124
124
  version_requirements: *id010
125
+ - !ruby/object:Gem::Dependency
126
+ name: coffee-script
127
+ prerelease: false
128
+ requirement: &id011 !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: "2.2"
134
+ type: :development
135
+ version_requirements: *id011
125
136
  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"
126
137
  email: sj26@sj26.com
127
138
  executables: