mailcatcher 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: