mailcatcher 0.5.1 → 0.5.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/VERSION +1 -0
- data/lib/mail_catcher.rb +1 -0
- data/lib/mail_catcher/events.rb +2 -4
- data/lib/mail_catcher/growl.rb +2 -1
- data/lib/mail_catcher/mail.rb +118 -118
- data/lib/mail_catcher/smtp.rb +36 -38
- data/lib/mail_catcher/web.rb +106 -92
- data/public/images/logo_large.png +0 -0
- data/public/javascripts/application.js +14 -5
- metadata +64 -42
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.2
|
data/lib/mail_catcher.rb
CHANGED
@@ -55,6 +55,7 @@ module_function
|
|
55
55
|
@@defaults.dup.tap do |options|
|
56
56
|
OptionParser.new do |parser|
|
57
57
|
parser.banner = "Usage: mailcatcher [options]"
|
58
|
+
parser.version = File.read(File.expand_path("../../VERSION", __FILE__))
|
58
59
|
|
59
60
|
parser.on("--ip IP", "Set the ip address of both servers") do |ip|
|
60
61
|
options[:smtp_ip] = options[:http_ip] = ip
|
data/lib/mail_catcher/events.rb
CHANGED
data/lib/mail_catcher/growl.rb
CHANGED
@@ -6,7 +6,8 @@ module MailCatcher
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def notify message
|
9
|
-
|
9
|
+
image_path = File.expand_path(File.join(__FILE__, '..', '..', '..', 'public', 'images', 'logo_large.png'))
|
10
|
+
system "growlnotify", "--image", image_path, "--name", "MailCatcher", "--message", "Message received:\n#{message["subject"]}"
|
10
11
|
end
|
11
12
|
|
12
13
|
# TODO: Native support on MacRuby with click backs
|
data/lib/mail_catcher/mail.rb
CHANGED
@@ -3,152 +3,152 @@ require 'sqlite3'
|
|
3
3
|
require 'eventmachine'
|
4
4
|
|
5
5
|
module MailCatcher::Mail
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def db
|
9
|
+
@@__db ||= begin
|
10
|
+
SQLite3::Database.new(':memory:', :type_translation => true).tap do |db|
|
11
|
+
db.execute(<<-SQL)
|
12
|
+
CREATE TABLE message (
|
13
|
+
id INTEGER PRIMARY KEY ASC,
|
14
|
+
sender TEXT,
|
15
|
+
recipients TEXT,
|
16
|
+
subject TEXT,
|
17
|
+
source BLOB,
|
18
|
+
size TEXT,
|
19
|
+
type TEXT,
|
20
|
+
created_at DATETIME DEFAULT CURRENT_DATETIME
|
21
|
+
)
|
22
|
+
SQL
|
23
|
+
db.execute(<<-SQL)
|
24
|
+
CREATE TABLE message_part (
|
25
|
+
id INTEGER PRIMARY KEY ASC,
|
26
|
+
message_id INTEGER NOT NULL,
|
27
|
+
cid TEXT,
|
28
|
+
type TEXT,
|
29
|
+
is_attachment INTEGER,
|
30
|
+
filename TEXT,
|
31
|
+
charset TEXT,
|
32
|
+
body BLOB,
|
33
|
+
size INTEGER,
|
34
|
+
created_at DATETIME DEFAULT CURRENT_DATETIME
|
35
|
+
)
|
36
|
+
SQL
|
37
37
|
end
|
38
38
|
end
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
mail = Mail.new(message[:source])
|
44
|
-
result = @@add_message_query.execute(message[:sender], message[:recipients].to_json, mail.subject, message[:source], mail.mime_type || 'text/plain', message[:source].length)
|
45
|
-
message_id = db.last_insert_row_id
|
46
|
-
parts = mail.all_parts
|
47
|
-
parts = [mail] if parts.empty?
|
48
|
-
parts.each do |part|
|
49
|
-
body = part.body.to_s
|
50
|
-
# Only parts have CIDs, not mail
|
51
|
-
cid = part.cid if part.respond_to? :cid
|
52
|
-
add_message_part(message_id, cid, part.mime_type || 'text/plain', part.attachment? ? 1 : 0, part.filename, part.charset, body, body.length)
|
53
|
-
end
|
41
|
+
def add_message(message)
|
42
|
+
@@add_message_query ||= db.prepare("INSERT INTO message (sender, recipients, subject, source, type, size, created_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'))")
|
54
43
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
44
|
+
mail = Mail.new(message[:source])
|
45
|
+
result = @@add_message_query.execute(message[:sender], message[:recipients].to_json, mail.subject, message[:source], mail.mime_type || 'text/plain', message[:source].length)
|
46
|
+
message_id = db.last_insert_row_id
|
47
|
+
parts = mail.all_parts
|
48
|
+
parts = [mail] if parts.empty?
|
49
|
+
parts.each do |part|
|
50
|
+
body = part.body.to_s
|
51
|
+
# Only parts have CIDs, not mail
|
52
|
+
cid = part.cid if part.respond_to? :cid
|
53
|
+
add_message_part(message_id, cid, part.mime_type || 'text/plain', part.attachment? ? 1 : 0, part.filename, part.charset, body, body.length)
|
59
54
|
end
|
60
55
|
|
61
|
-
|
62
|
-
|
63
|
-
|
56
|
+
EventMachine.next_tick do
|
57
|
+
message = MailCatcher::Mail.message message_id
|
58
|
+
MailCatcher::Events::MessageAdded.push message
|
64
59
|
end
|
60
|
+
end
|
65
61
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
62
|
+
def add_message_part(*args)
|
63
|
+
@@add_message_part_query ||= db.prepare "INSERT INTO message_part (message_id, cid, type, is_attachment, filename, charset, body, size, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))"
|
64
|
+
@@add_message_part_query.execute(*args)
|
65
|
+
end
|
70
66
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
67
|
+
def latest_created_at
|
68
|
+
@@latest_created_at_query ||= db.prepare "SELECT created_at FROM message ORDER BY created_at DESC LIMIT 1"
|
69
|
+
@@latest_created_at_query.execute.next
|
70
|
+
end
|
79
71
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
72
|
+
def messages
|
73
|
+
@@messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at ASC"
|
74
|
+
@@messages_query.execute.map do |row|
|
75
|
+
Hash[row.fields.zip(row)].tap do |message|
|
84
76
|
message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
|
85
77
|
end
|
86
78
|
end
|
79
|
+
end
|
87
80
|
|
88
|
-
|
89
|
-
|
90
|
-
|
81
|
+
def message(id)
|
82
|
+
@@message_query ||= db.prepare "SELECT * FROM message WHERE id = ? LIMIT 1"
|
83
|
+
row = @@message_query.execute(id).next
|
84
|
+
row && Hash[row.fields.zip(row)].tap do |message|
|
85
|
+
message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
|
91
86
|
end
|
87
|
+
end
|
92
88
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
89
|
+
def message_has_html?(id)
|
90
|
+
@@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"
|
91
|
+
(!!@@message_has_html_query.execute(id).next) || ['text/html', 'application/xhtml+xml'].include?(message(id)["type"])
|
92
|
+
end
|
97
93
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end
|
103
|
-
end
|
94
|
+
def message_has_plain?(id)
|
95
|
+
@@message_has_plain_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type = 'text/plain' LIMIT 1"
|
96
|
+
(!!@@message_has_plain_query.execute(id).next) || message(id)["type"] == "text/plain"
|
97
|
+
end
|
104
98
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
99
|
+
def message_parts(id)
|
100
|
+
@@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? ORDER BY filename ASC"
|
101
|
+
@@message_parts_query.execute(id).map do |row|
|
102
|
+
Hash[row.fields.zip(row)]
|
110
103
|
end
|
104
|
+
end
|
111
105
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
106
|
+
def message_attachments(id)
|
107
|
+
@@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? AND is_attachment = 1 ORDER BY filename ASC"
|
108
|
+
@@message_parts_query.execute(id).map do |row|
|
109
|
+
Hash[row.fields.zip(row)]
|
116
110
|
end
|
111
|
+
end
|
117
112
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
113
|
+
def message_part(message_id, part_id)
|
114
|
+
@@message_part_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND id = ? LIMIT 1"
|
115
|
+
row = @@message_part_query.execute(message_id, part_id).next
|
116
|
+
row && Hash[row.fields.zip(row)]
|
117
|
+
end
|
123
118
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
message if message.present? and ['text/html', 'application/xhtml+xml'].include? message["type"]
|
130
|
-
end
|
131
|
-
end
|
119
|
+
def message_part_type(message_id, part_type)
|
120
|
+
@@message_part_type_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND type = ? AND is_attachment = 0 LIMIT 1"
|
121
|
+
row = @@message_part_type_query.execute(message_id, part_type).next
|
122
|
+
row && Hash[row.fields.zip(row)]
|
123
|
+
end
|
132
124
|
|
133
|
-
|
134
|
-
|
125
|
+
def message_part_html(message_id)
|
126
|
+
part = message_part_type(message_id, "text/html")
|
127
|
+
part ||= message_part_type(message_id, "application/xhtml+xml")
|
128
|
+
part ||= begin
|
129
|
+
message = message(message_id)
|
130
|
+
message if message.present? and ['text/html', 'application/xhtml+xml'].include? message["type"]
|
135
131
|
end
|
132
|
+
end
|
136
133
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
134
|
+
def message_part_plain(message_id)
|
135
|
+
message_part_type message_id, "text/plain"
|
136
|
+
end
|
137
|
+
|
138
|
+
def message_part_cid(message_id, cid)
|
139
|
+
@@message_part_cid_query ||= db.prepare 'SELECT * FROM message_part WHERE message_id = ?'
|
140
|
+
@@message_part_cid_query.execute(message_id).map do |row|
|
141
|
+
part = Hash[row.fields.zip(row)]
|
142
|
+
end.find do |part|
|
143
|
+
part["cid"] == cid
|
144
144
|
end
|
145
|
+
end
|
145
146
|
|
146
|
-
|
147
|
-
|
148
|
-
|
147
|
+
def delete!
|
148
|
+
@@delete_messages_query ||= db.prepare 'DELETE FROM message'
|
149
|
+
@@delete_message_parts_query ||= db.prepare 'DELETE FROM message_part'
|
149
150
|
|
150
|
-
|
151
|
-
|
152
|
-
end
|
151
|
+
@@delete_messages_query.execute and
|
152
|
+
@@delete_message_parts_query.execute
|
153
153
|
end
|
154
154
|
end
|
data/lib/mail_catcher/smtp.rb
CHANGED
@@ -1,48 +1,46 @@
|
|
1
1
|
require 'eventmachine'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
3
|
+
class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
|
4
|
+
def current_message
|
5
|
+
@current_message ||= {}
|
6
|
+
end
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
def receive_reset
|
9
|
+
@current_message = nil
|
10
|
+
true
|
11
|
+
end
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
def receive_sender(sender)
|
14
|
+
current_message[:sender] = sender
|
15
|
+
true
|
16
|
+
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
def receive_recipient(recipient)
|
19
|
+
current_message[:recipients] ||= []
|
20
|
+
current_message[:recipients] << recipient
|
21
|
+
true
|
22
|
+
end
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
def receive_data_chunk(lines)
|
25
|
+
current_message[:source] ||= ""
|
26
|
+
current_message[:source] += lines.join("\n")
|
27
|
+
true
|
28
|
+
end
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
puts " Please submit this as an issue at http://github.com/sj26/mailcatcher/issues"
|
43
|
-
false
|
44
|
-
ensure
|
45
|
-
@current_message = nil
|
30
|
+
def receive_message
|
31
|
+
MailCatcher::Mail.add_message current_message
|
32
|
+
puts "==> SMTP: Received message from '#{current_message[:sender]}' (#{current_message[:source].length} bytes)"
|
33
|
+
true
|
34
|
+
rescue
|
35
|
+
puts "*** Error receiving message: #{current_message.inspect}"
|
36
|
+
puts " Exception: #{$!}"
|
37
|
+
puts " Backtrace:"
|
38
|
+
$!.backtrace.each do |line|
|
39
|
+
puts " #{line}"
|
46
40
|
end
|
41
|
+
puts " Please submit this as an issue at http://github.com/sj26/mailcatcher/issues"
|
42
|
+
false
|
43
|
+
ensure
|
44
|
+
@current_message = nil
|
47
45
|
end
|
48
46
|
end
|
data/lib/mail_catcher/web.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'sinatra'
|
2
2
|
require 'pathname'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
3
5
|
|
4
6
|
require 'skinny'
|
5
7
|
|
@@ -7,120 +9,132 @@ class Sinatra::Request
|
|
7
9
|
include Skinny::Helpers
|
8
10
|
end
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
set :haml, :format => :html5
|
12
|
+
class MailCatcher::Web < Sinatra::Base
|
13
|
+
set :root, Pathname.new(__FILE__).dirname.parent.parent
|
14
|
+
set :haml, :format => :html5
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
get '/' do
|
17
|
+
haml :index
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
delete '/' do
|
21
|
+
MailCatcher.quit!
|
22
|
+
status 204
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
|
36
|
-
|
25
|
+
get '/messages' do
|
26
|
+
if request.websocket?
|
27
|
+
puts "Gots a websocket"
|
28
|
+
request.websocket!(
|
29
|
+
:protocol => "MailCatcher 0.2 Message Push",
|
30
|
+
:on_start => proc do |websocket|
|
31
|
+
subscription = MailCatcher::Events::MessageAdded.subscribe { |message| websocket.send_message message.to_json }
|
32
|
+
websocket.on_close do |websocket|
|
33
|
+
MailCatcher::Events::MessageAdded.unsubscribe subscription
|
34
|
+
end
|
35
|
+
end)
|
36
|
+
else
|
37
|
+
MailCatcher::Mail.messages.to_json
|
37
38
|
end
|
39
|
+
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
delete '/messages' do
|
42
|
+
MailCatcher::Mail.delete!
|
43
|
+
status 204
|
44
|
+
end
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
46
|
+
get '/messages/:id.json' do
|
47
|
+
id = params[:id].to_i
|
48
|
+
if message = MailCatcher::Mail.message(id)
|
49
|
+
message.merge({
|
50
|
+
"formats" => [
|
51
|
+
"source",
|
52
|
+
("html" if MailCatcher::Mail.message_has_html? id),
|
53
|
+
("plain" if MailCatcher::Mail.message_has_plain? id),
|
54
|
+
].compact,
|
55
|
+
"attachments" => MailCatcher::Mail.message_attachments(id).map do |attachment|
|
56
|
+
attachment.merge({"href" => "/messages/#{escape(id)}/#{escape(attachment['cid'])}"})
|
57
|
+
end,
|
58
|
+
}).to_json
|
59
|
+
else
|
60
|
+
not_found
|
60
61
|
end
|
62
|
+
end
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
64
|
+
get '/messages/:id.html' do
|
65
|
+
id = params[:id].to_i
|
66
|
+
if part = MailCatcher::Mail.message_part_html(id)
|
67
|
+
content_type part["type"], :charset => (part["charset"] || "utf8")
|
66
68
|
|
67
|
-
|
69
|
+
body = part["body"]
|
68
70
|
|
69
|
-
|
70
|
-
|
71
|
+
# Rewrite body to link to embedded attachments served by cid
|
72
|
+
body.gsub! /cid:([^'"> ]+)/, "#{id}/parts/\\1"
|
71
73
|
|
72
|
-
|
73
|
-
|
74
|
+
# Rewrite body to open links in a new window
|
75
|
+
body.gsub! /<a\s+/, '<a target="_blank" '
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
77
|
+
body
|
78
|
+
else
|
79
|
+
not_found
|
79
80
|
end
|
81
|
+
end
|
80
82
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
83
|
+
get "/messages/:id.plain" do
|
84
|
+
id = params[:id].to_i
|
85
|
+
if part = MailCatcher::Mail.message_part_plain(id)
|
86
|
+
content_type part["type"], :charset => (part["charset"] || "utf8")
|
87
|
+
part["body"]
|
88
|
+
else
|
89
|
+
not_found
|
89
90
|
end
|
91
|
+
end
|
90
92
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
93
|
+
get "/messages/:id.source" do
|
94
|
+
id = params[:id].to_i
|
95
|
+
if message = MailCatcher::Mail.message(id)
|
96
|
+
content_type "text/plain"
|
97
|
+
message["source"]
|
98
|
+
else
|
99
|
+
not_found
|
99
100
|
end
|
101
|
+
end
|
100
102
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
103
|
+
get "/messages/:id.eml" do
|
104
|
+
id = params[:id].to_i
|
105
|
+
if message = MailCatcher::Mail.message(id)
|
106
|
+
content_type "message/rfc822"
|
107
|
+
message["source"]
|
108
|
+
else
|
109
|
+
not_found
|
109
110
|
end
|
111
|
+
end
|
110
112
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
113
|
+
get "/messages/:id/parts/:cid" do
|
114
|
+
id = params[:id].to_i
|
115
|
+
if part = MailCatcher::Mail.message_part_cid(id, params[:cid])
|
116
|
+
content_type part["type"], :charset => (part["charset"] || "utf8")
|
117
|
+
attachment part["filename"] if part["is_attachment"] == 1
|
118
|
+
body part["body"].to_s
|
119
|
+
else
|
120
|
+
not_found
|
120
121
|
end
|
122
|
+
end
|
121
123
|
|
122
|
-
|
123
|
-
|
124
|
+
get "/messages/:id/analysis.?:format?" do
|
125
|
+
id = params[:id].to_i
|
126
|
+
if part = MailCatcher::Mail.message_part_html(id)
|
127
|
+
# TODO: Server-side cache? Make the browser cache based on message create time? Hmm.
|
128
|
+
uri = URI.parse("http://api.getfractal.com/api/v2/validate#{"/format/#{params[:format]}" if params[:format].present?}")
|
129
|
+
response = Net::HTTP.post_form(uri, api_key: "5c463877265251386f516f7428", html: part["body"])
|
130
|
+
content_type ".#{params[:format]}" if params[:format].present?
|
131
|
+
body response.body
|
132
|
+
else
|
133
|
+
not_found
|
124
134
|
end
|
125
135
|
end
|
136
|
+
|
137
|
+
not_found do
|
138
|
+
"<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>"
|
139
|
+
end
|
126
140
|
end
|
Binary file
|
@@ -34,6 +34,7 @@
|
|
34
34
|
}
|
35
35
|
});
|
36
36
|
$('nav.app .clear a').live('click', __bind(function(e) {
|
37
|
+
e.preventDefault();
|
37
38
|
if (confirm("You will lose all your received messages.\n\nAre you sure you want to clear all messages?")) {
|
38
39
|
return $.ajax({
|
39
40
|
url: '/messages',
|
@@ -50,6 +51,7 @@
|
|
50
51
|
}
|
51
52
|
}, this));
|
52
53
|
$('nav.app .quit a').live('click', __bind(function(e) {
|
54
|
+
e.preventDefault();
|
53
55
|
if (confirm("You will lose all your received messages.\n\nAre you sure you want to quit?")) {
|
54
56
|
return $.ajax({
|
55
57
|
type: 'DELETE',
|
@@ -148,15 +150,22 @@
|
|
148
150
|
}
|
149
151
|
};
|
150
152
|
MailCatcher.prototype.loadMessageAnalysis = function(id) {
|
151
|
-
var $iframe;
|
153
|
+
var $form, $iframe;
|
152
154
|
id || (id = this.selectedMessage());
|
153
155
|
$("#message .views .analysis.tab:not(.selected)").addClass('selected');
|
154
156
|
$("#message .views :not(.analysis).tab.selected").removeClass('selected');
|
155
157
|
if (id != null) {
|
156
|
-
$iframe = $('#message iframe').contents().children().html("<html
|
157
|
-
return
|
158
|
-
|
159
|
-
|
158
|
+
$iframe = $('#message iframe').contents().children().html("<html>\n<head>\n<title>Analysis</title>\n" + ($('link[rel="stylesheet"]')[0].outerHTML) + "\n</head>\n<body class=\"iframe\">\n<h1>Analyse your email with Fractal</h1>\n<p><a href=\"http://getfractal.com/\" target=\"_blank\">Fractal</a> is a really neat service that applies common email design and development knowledge from <a href=\"http://www.email-standards.org/\" target=\"_blank\">Email Standards Project</a> to your HTML email and tells you what you've done wrong or what you should do instead.</p>\n<p>Please note that this <strong>sends your email to the Fractal service</strong> for analysis. Read their <a href=\"http://getfractal.com/terms\" target=\"_blank\">terms of service</a> if you're paranoid.</p>\n<p>(This output is still just raw XML. Someone keen to transform this into something prettier would be greatly appreciated!)</p>\n<form>\n<input type=\"submit\" value=\"Analyse\" /><span class=\"loading\" style=\"color: #999; display: none\">Analysing…</span>\n</form>\n</body>\n</html>");
|
159
|
+
return $form = $iframe.find('form').submit(function(e) {
|
160
|
+
e.preventDefault();
|
161
|
+
$(this).find('input[type="submit"]').attr('disabled', 'disabled').end().find('.loading').show();
|
162
|
+
return $.ajax({
|
163
|
+
url: "/messages/" + id + "/analysis.xml",
|
164
|
+
dataType: "text",
|
165
|
+
success: function(data) {
|
166
|
+
$form.replaceWith('<h2>Results</h2><pre id="result"></pre>');
|
167
|
+
return $iframe.find("#result").text(data);
|
168
|
+
}
|
160
169
|
});
|
161
170
|
});
|
162
171
|
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mailcatcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,12 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
13
|
-
default_executable:
|
12
|
+
date: 2011-10-09 00:00:00.000000000Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: activesupport
|
17
|
-
requirement: &
|
16
|
+
requirement: &70205943804660 !ruby/object:Gem::Requirement
|
18
17
|
none: false
|
19
18
|
requirements:
|
20
19
|
- - ~>
|
@@ -22,10 +21,10 @@ dependencies:
|
|
22
21
|
version: '3.0'
|
23
22
|
type: :runtime
|
24
23
|
prerelease: false
|
25
|
-
version_requirements: *
|
24
|
+
version_requirements: *70205943804660
|
26
25
|
- !ruby/object:Gem::Dependency
|
27
26
|
name: eventmachine
|
28
|
-
requirement: &
|
27
|
+
requirement: &70205943804020 !ruby/object:Gem::Requirement
|
29
28
|
none: false
|
30
29
|
requirements:
|
31
30
|
- - ~>
|
@@ -33,32 +32,32 @@ dependencies:
|
|
33
32
|
version: '0.12'
|
34
33
|
type: :runtime
|
35
34
|
prerelease: false
|
36
|
-
version_requirements: *
|
35
|
+
version_requirements: *70205943804020
|
37
36
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
39
|
-
requirement: &
|
37
|
+
name: haml
|
38
|
+
requirement: &70205943803120 !ruby/object:Gem::Requirement
|
40
39
|
none: false
|
41
40
|
requirements:
|
42
41
|
- - ~>
|
43
42
|
- !ruby/object:Gem::Version
|
44
|
-
version: '
|
43
|
+
version: '3.1'
|
45
44
|
type: :runtime
|
46
45
|
prerelease: false
|
47
|
-
version_requirements: *
|
46
|
+
version_requirements: *70205943803120
|
48
47
|
- !ruby/object:Gem::Dependency
|
49
|
-
name:
|
50
|
-
requirement: &
|
48
|
+
name: mail
|
49
|
+
requirement: &70205943802440 !ruby/object:Gem::Requirement
|
51
50
|
none: false
|
52
51
|
requirements:
|
53
52
|
- - ~>
|
54
53
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
54
|
+
version: '2.3'
|
56
55
|
type: :runtime
|
57
56
|
prerelease: false
|
58
|
-
version_requirements: *
|
57
|
+
version_requirements: *70205943802440
|
59
58
|
- !ruby/object:Gem::Dependency
|
60
|
-
name:
|
61
|
-
requirement: &
|
59
|
+
name: sinatra
|
60
|
+
requirement: &70205943801860 !ruby/object:Gem::Requirement
|
62
61
|
none: false
|
63
62
|
requirements:
|
64
63
|
- - ~>
|
@@ -66,54 +65,54 @@ dependencies:
|
|
66
65
|
version: '1.2'
|
67
66
|
type: :runtime
|
68
67
|
prerelease: false
|
69
|
-
version_requirements: *
|
68
|
+
version_requirements: *70205943801860
|
70
69
|
- !ruby/object:Gem::Dependency
|
71
70
|
name: skinny
|
72
|
-
requirement: &
|
71
|
+
requirement: &70205943790820 !ruby/object:Gem::Requirement
|
73
72
|
none: false
|
74
73
|
requirements:
|
75
74
|
- - ~>
|
76
75
|
- !ruby/object:Gem::Version
|
77
|
-
version: '0.
|
76
|
+
version: '0.2'
|
78
77
|
type: :runtime
|
79
78
|
prerelease: false
|
80
|
-
version_requirements: *
|
79
|
+
version_requirements: *70205943790820
|
81
80
|
- !ruby/object:Gem::Dependency
|
82
|
-
name:
|
83
|
-
requirement: &
|
81
|
+
name: sqlite3
|
82
|
+
requirement: &70205943790260 !ruby/object:Gem::Requirement
|
84
83
|
none: false
|
85
84
|
requirements:
|
86
85
|
- - ~>
|
87
86
|
- !ruby/object:Gem::Version
|
88
|
-
version: '1.
|
87
|
+
version: '1.3'
|
89
88
|
type: :runtime
|
90
89
|
prerelease: false
|
91
|
-
version_requirements: *
|
90
|
+
version_requirements: *70205943790260
|
92
91
|
- !ruby/object:Gem::Dependency
|
93
|
-
name:
|
94
|
-
requirement: &
|
92
|
+
name: thin
|
93
|
+
requirement: &70205943789560 !ruby/object:Gem::Requirement
|
95
94
|
none: false
|
96
95
|
requirements:
|
97
96
|
- - ~>
|
98
97
|
- !ruby/object:Gem::Version
|
99
|
-
version: '
|
98
|
+
version: '1.2'
|
100
99
|
type: :runtime
|
101
100
|
prerelease: false
|
102
|
-
version_requirements: *
|
101
|
+
version_requirements: *70205943789560
|
103
102
|
- !ruby/object:Gem::Dependency
|
104
|
-
name:
|
105
|
-
requirement: &
|
103
|
+
name: coffee-script
|
104
|
+
requirement: &70205943788860 !ruby/object:Gem::Requirement
|
106
105
|
none: false
|
107
106
|
requirements:
|
108
107
|
- - ~>
|
109
108
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
109
|
+
version: '2.2'
|
111
110
|
type: :development
|
112
111
|
prerelease: false
|
113
|
-
version_requirements: *
|
112
|
+
version_requirements: *70205943788860
|
114
113
|
- !ruby/object:Gem::Dependency
|
115
114
|
name: compass
|
116
|
-
requirement: &
|
115
|
+
requirement: &70205943788140 !ruby/object:Gem::Requirement
|
117
116
|
none: false
|
118
117
|
requirements:
|
119
118
|
- - ~>
|
@@ -121,18 +120,40 @@ dependencies:
|
|
121
120
|
version: 0.11.1
|
122
121
|
type: :development
|
123
122
|
prerelease: false
|
124
|
-
version_requirements: *
|
123
|
+
version_requirements: *70205943788140
|
125
124
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
127
|
-
requirement: &
|
125
|
+
name: rake
|
126
|
+
requirement: &70205943787700 !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: *70205943787700
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: rdoc
|
137
|
+
requirement: &70205943787160 !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
type: :development
|
144
|
+
prerelease: false
|
145
|
+
version_requirements: *70205943787160
|
146
|
+
- !ruby/object:Gem::Dependency
|
147
|
+
name: sass
|
148
|
+
requirement: &70205943786420 !ruby/object:Gem::Requirement
|
128
149
|
none: false
|
129
150
|
requirements:
|
130
151
|
- - ~>
|
131
152
|
- !ruby/object:Gem::Version
|
132
|
-
version: '
|
153
|
+
version: '3.1'
|
133
154
|
type: :development
|
134
155
|
prerelease: false
|
135
|
-
version_requirements: *
|
156
|
+
version_requirements: *70205943786420
|
136
157
|
description: ! " MailCatcher runs a super simple SMTP server which catches any\n
|
137
158
|
\ message sent to it to display in a web interface. Run\n mailcatcher, set
|
138
159
|
your favourite app to deliver to\n smtp://127.0.0.1:1025 instead of your default
|
@@ -148,6 +169,7 @@ extra_rdoc_files:
|
|
148
169
|
files:
|
149
170
|
- README.md
|
150
171
|
- LICENSE
|
172
|
+
- VERSION
|
151
173
|
- bin/catchmail
|
152
174
|
- bin/mailcatcher
|
153
175
|
- lib/mail_catcher/events.rb
|
@@ -157,14 +179,14 @@ files:
|
|
157
179
|
- lib/mail_catcher/web.rb
|
158
180
|
- lib/mail_catcher.rb
|
159
181
|
- public/images/logo.png
|
182
|
+
- public/images/logo_large.png
|
160
183
|
- public/javascripts/application.js
|
161
184
|
- public/javascripts/date.js
|
162
185
|
- public/javascripts/jquery.js
|
163
186
|
- public/javascripts/modernizr.js
|
164
187
|
- public/stylesheets/application.css
|
165
188
|
- views/index.haml
|
166
|
-
|
167
|
-
homepage: http://github.com/sj26/mailcatcher
|
189
|
+
homepage: http://mailcatcher.me
|
168
190
|
licenses: []
|
169
191
|
post_install_message:
|
170
192
|
rdoc_options: []
|
@@ -184,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
206
|
version: '0'
|
185
207
|
requirements: []
|
186
208
|
rubyforge_project:
|
187
|
-
rubygems_version: 1.
|
209
|
+
rubygems_version: 1.8.7
|
188
210
|
signing_key:
|
189
211
|
specification_version: 3
|
190
212
|
summary: Runs an SMTP server, catches and displays email in a web interface.
|