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.
- data/lib/mail_catcher/mail.rb +33 -16
- data/lib/mail_catcher/web.rb +1 -1
- data/public/javascripts/application.js +120 -94
- data/public/stylesheets/application.css +19 -3
- data/views/index.haml +5 -6
- metadata +12 -1
data/lib/mail_catcher/mail.rb
CHANGED
@@ -6,7 +6,7 @@ module MailCatcher::Mail
|
|
6
6
|
class << self
|
7
7
|
def db
|
8
8
|
@@__db ||= begin
|
9
|
-
SQLite3::Database.new(':memory:', :
|
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.
|
73
|
-
|
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
|
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
|
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
|
-
@@
|
93
|
-
!!@@
|
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).
|
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).
|
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
|
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).
|
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
|
data/lib/mail_catcher/web.rb
CHANGED
@@ -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
|
-
].
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
63
|
-
|
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
|
-
$
|
71
|
+
$('#message .metadata .attachments').hide();
|
66
72
|
}
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
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
|
-
|
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 {
|
data/views/index.haml
CHANGED
@@ -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
|
-
%
|
16
|
-
|
17
|
-
|
18
|
-
|
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.
|
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:
|