mirror of
https://github.com/moparisthebest/mailcatcher
synced 2025-01-07 11:48:03 -05:00
Switch to sprockets for assets
This commit is contained in:
parent
272b4fa855
commit
0398d2d6a3
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,5 +1,10 @@
|
|||||||
rdoc
|
# Caches
|
||||||
*.gem
|
|
||||||
|
|
||||||
.bundle
|
.bundle
|
||||||
.sass-cache
|
.sass-cache
|
||||||
|
|
||||||
|
# Generated documentation and assets
|
||||||
|
/doc
|
||||||
|
/public/assets
|
||||||
|
|
||||||
|
# Build gems
|
||||||
|
*.gem
|
||||||
|
75
Gemfile.lock
75
Gemfile.lock
@ -1,10 +1,10 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
mailcatcher (0.5.11)
|
mailcatcher (0.6.0)
|
||||||
activesupport (~> 3.0)
|
activesupport (>= 3.0.0, < 4.1)
|
||||||
eventmachine (~> 1.0.0)
|
eventmachine (~> 1.0.0)
|
||||||
haml (>= 3.1, < 5)
|
haml (>= 3.1, < 4.1)
|
||||||
mail (~> 2.3)
|
mail (~> 2.3)
|
||||||
sinatra (~> 1.2)
|
sinatra (~> 1.2)
|
||||||
skinny (~> 0.2.3)
|
skinny (~> 0.2.3)
|
||||||
@ -14,55 +14,72 @@ PATH
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activesupport (3.2.12)
|
activesupport (4.0.4)
|
||||||
i18n (~> 0.6)
|
i18n (~> 0.6, >= 0.6.9)
|
||||||
multi_json (~> 1.0)
|
minitest (~> 4.2)
|
||||||
chunky_png (1.2.7)
|
multi_json (~> 1.3)
|
||||||
|
thread_safe (~> 0.1)
|
||||||
|
tzinfo (~> 0.3.37)
|
||||||
|
atomic (1.1.15)
|
||||||
|
chunky_png (1.3.0)
|
||||||
coffee-script (2.2.0)
|
coffee-script (2.2.0)
|
||||||
coffee-script-source
|
coffee-script-source
|
||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.6.1)
|
coffee-script-source (1.7.0)
|
||||||
compass (0.12.2)
|
compass (0.12.2)
|
||||||
chunky_png (~> 1.2)
|
chunky_png (~> 1.2)
|
||||||
fssm (>= 0.2.7)
|
fssm (>= 0.2.7)
|
||||||
sass (~> 3.1)
|
sass (~> 3.1)
|
||||||
daemons (1.1.9)
|
daemons (1.1.9)
|
||||||
eventmachine (1.0.3)
|
eventmachine (1.0.3)
|
||||||
execjs (1.4.0)
|
execjs (2.0.2)
|
||||||
multi_json (~> 1.0)
|
|
||||||
fssm (0.2.10)
|
fssm (0.2.10)
|
||||||
haml (4.0.0)
|
haml (4.0.5)
|
||||||
tilt
|
tilt
|
||||||
i18n (0.6.4)
|
hike (1.2.3)
|
||||||
mail (2.5.3)
|
i18n (0.6.9)
|
||||||
i18n (>= 0.4.0)
|
json (1.8.1)
|
||||||
|
mail (2.5.4)
|
||||||
mime-types (~> 1.16)
|
mime-types (~> 1.16)
|
||||||
treetop (~> 1.4.8)
|
treetop (~> 1.4.8)
|
||||||
mime-types (1.21)
|
mime-types (1.25.1)
|
||||||
multi_json (1.6.1)
|
minitest (4.7.5)
|
||||||
polyglot (0.3.3)
|
multi_json (1.9.0)
|
||||||
|
polyglot (0.3.4)
|
||||||
rack (1.5.2)
|
rack (1.5.2)
|
||||||
rack-protection (1.4.0)
|
rack-protection (1.5.2)
|
||||||
rack
|
rack
|
||||||
rake (10.0.3)
|
rake (10.1.1)
|
||||||
rdoc (4.0.0)
|
rdoc (4.1.1)
|
||||||
sass (3.2.7)
|
json (~> 1.4)
|
||||||
sinatra (1.3.5)
|
sass (3.2.14)
|
||||||
|
sinatra (1.4.4)
|
||||||
rack (~> 1.4)
|
rack (~> 1.4)
|
||||||
rack-protection (~> 1.3)
|
rack-protection (~> 1.4)
|
||||||
tilt (~> 1.3, >= 1.3.3)
|
tilt (~> 1.3, >= 1.3.4)
|
||||||
skinny (0.2.3)
|
skinny (0.2.3)
|
||||||
eventmachine (~> 1.0.0)
|
eventmachine (~> 1.0.0)
|
||||||
thin (~> 1.5.0)
|
thin (~> 1.5.0)
|
||||||
sqlite3 (1.3.7)
|
sprockets (2.12.0)
|
||||||
thin (1.5.0)
|
hike (~> 1.2)
|
||||||
|
multi_json (~> 1.0)
|
||||||
|
rack (~> 1.0)
|
||||||
|
tilt (~> 1.1, != 1.3.0)
|
||||||
|
sprockets-sass (1.0.3)
|
||||||
|
sprockets (~> 2.0)
|
||||||
|
tilt (~> 1.1)
|
||||||
|
sqlite3 (1.3.8)
|
||||||
|
thin (1.5.1)
|
||||||
daemons (>= 1.0.9)
|
daemons (>= 1.0.9)
|
||||||
eventmachine (>= 0.12.6)
|
eventmachine (>= 0.12.6)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
tilt (1.3.5)
|
thread_safe (0.2.0)
|
||||||
treetop (1.4.12)
|
atomic (>= 1.1.7, < 2)
|
||||||
|
tilt (1.4.1)
|
||||||
|
treetop (1.4.15)
|
||||||
polyglot
|
polyglot
|
||||||
polyglot (>= 0.3.1)
|
polyglot (>= 0.3.1)
|
||||||
|
tzinfo (0.3.39)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
@ -74,3 +91,5 @@ DEPENDENCIES
|
|||||||
rake
|
rake
|
||||||
rdoc
|
rdoc
|
||||||
sass
|
sass
|
||||||
|
sprockets
|
||||||
|
sprockets-sass
|
||||||
|
67
Rakefile
67
Rakefile
@ -1,51 +1,46 @@
|
|||||||
require 'rubygems'
|
require "fileutils"
|
||||||
require 'rubygems/package'
|
require "rubygems"
|
||||||
require File.expand_path('../lib/mail_catcher/version', __FILE__)
|
require "rubygems/package"
|
||||||
|
|
||||||
spec_file = File.expand_path __FILE__ + '/../mailcatcher.gemspec'
|
require "mail_catcher/version"
|
||||||
spec = Gem::Specification.load spec_file
|
|
||||||
|
|
||||||
require 'rdoc/task'
|
spec_file = File.expand_path("../mailcatcher.gemspec", __FILE__)
|
||||||
RDoc::Task.new :rdoc => "doc",
|
spec = Gem::Specification.load(spec_file)
|
||||||
:clobber_rdoc => "doc:clean",
|
|
||||||
:rerdoc => "doc:force" do |rdoc|
|
require "rdoc/task"
|
||||||
|
RDoc::Task.new(:rdoc => "doc",:clobber_rdoc => "doc:clean", :rerdoc => "doc:force") do |rdoc|
|
||||||
rdoc.title = "MailCatcher #{MailCatcher::VERSION}"
|
rdoc.title = "MailCatcher #{MailCatcher::VERSION}"
|
||||||
rdoc.rdoc_dir = 'doc'
|
rdoc.rdoc_dir = "doc"
|
||||||
rdoc.main = 'README.md'
|
rdoc.main = "README.md"
|
||||||
rdoc.rdoc_files.include 'lib/**/*.rb'
|
rdoc.rdoc_files.include "lib/**/*.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Compile SASS/SCSS files into SCSS"
|
# XXX: Would prefer to use Rake::SprocketsTask but can't populate
|
||||||
task "build:sass" do
|
# non-digest assets, and we don't want sprockets at runtime so
|
||||||
Dir["public/stylesheets/**/*.sass"].each do |file|
|
# can't use manifest directly. Perhaps index.html should be
|
||||||
css_file = file.sub /\.sass$/, ".css"
|
# precompiled with digest assets paths?
|
||||||
system "sass", "--no-cache", "--compass", file, css_file
|
|
||||||
|
desc "Compile assets"
|
||||||
|
task "assets" do
|
||||||
|
compiled_path = File.expand_path("../public/assets", __FILE__)
|
||||||
|
FileUtils.mkdir_p(compiled_path)
|
||||||
|
|
||||||
|
require "mail_catcher/web/assets"
|
||||||
|
sprockets = MailCatcher::Web::Assets
|
||||||
|
sprockets.each_logical_path(/\.(js|css|xsl|png)\Z/) do |logical_path|
|
||||||
|
if asset = sprockets.find_asset(logical_path)
|
||||||
|
target = File.join(compiled_path, logical_path)
|
||||||
|
asset.write_to target
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Compile CoffeeScript files into JavaScript"
|
|
||||||
task "build:coffee" do
|
|
||||||
require 'coffee-script'
|
|
||||||
Dir["public/javascripts/**/*.coffee"].each do |file|
|
|
||||||
js_file = file.sub /\.coffee$/, ".js"
|
|
||||||
File.new(js_file, "w").write CoffeeScript.compile File.read file
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
multitask "build" => ["build:sass", "build:coffee"]
|
|
||||||
|
|
||||||
desc "Package as Gem"
|
desc "Package as Gem"
|
||||||
task "package:gem" do
|
task "package" => ["assets"] do
|
||||||
Gem::Package.build spec
|
Gem::Package.build spec
|
||||||
end
|
end
|
||||||
|
|
||||||
task "package" => ["build", "package:gem"]
|
|
||||||
|
|
||||||
desc "Release Gem to RubyGems"
|
desc "Release Gem to RubyGems"
|
||||||
task "release:gem" do
|
task "release" => ["package"] do
|
||||||
%x[gem push mailcatcher-#{MailCatcher::VERSION}.gem]
|
%x[gem push mailcatcher-#{MailCatcher::VERSION}.gem]
|
||||||
end
|
end
|
||||||
|
|
||||||
task "release" => ["package", "release:gem"]
|
|
||||||
|
|
||||||
task "default" => "build"
|
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -22,7 +22,7 @@ class MailCatcher
|
|||||||
$('#message .views .analysis.tab a').live 'click', (e) =>
|
$('#message .views .analysis.tab a').live 'click', (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@loadMessageAnalysis @selectedMessage()
|
@loadMessageAnalysis @selectedMessage()
|
||||||
|
|
||||||
$('#message iframe').load =>
|
$('#message iframe').load =>
|
||||||
@decorateMessageBody()
|
@decorateMessageBody()
|
||||||
|
|
||||||
@ -177,12 +177,12 @@ class MailCatcher
|
|||||||
$('#messages tbody tr').show()
|
$('#messages tbody tr').show()
|
||||||
|
|
||||||
addMessage: (message) ->
|
addMessage: (message) ->
|
||||||
$('#messages tbody').prepend \
|
$('<tr />').attr('data-message-id', message.id.toString())
|
||||||
$('<tr />').attr('data-message-id', message.id.toString())
|
.append($('<td/>').text(message.sender or "No sender").toggleClass("blank", !message.sender))
|
||||||
.append($('<td/>').text(message.sender or "No sender").toggleClass("blank", !message.sender))
|
.append($('<td/>').text((message.recipients || []).join(', ') or "No receipients").toggleClass("blank", !message.recipients.length))
|
||||||
.append($('<td/>').text((message.recipients || []).join(', ') or "No receipients").toggleClass("blank", !message.recipients.length))
|
.append($('<td/>').text(message.subject or "No subject").toggleClass("blank", !message.subject))
|
||||||
.append($('<td/>').text(message.subject or "No subject").toggleClass("blank", !message.subject))
|
.append($('<td/>').text(@formatDate(message.created_at)))
|
||||||
.append($('<td/>').text @formatDate message.created_at)
|
.prependTo($('#messages tbody'))
|
||||||
|
|
||||||
scrollToRow: (row) ->
|
scrollToRow: (row) ->
|
||||||
relativePosition = row.offset().top - $('#messages').offset().top
|
relativePosition = row.offset().top - $('#messages').offset().top
|
||||||
@ -260,8 +260,8 @@ class MailCatcher
|
|||||||
|
|
||||||
decorateMessageBody: ->
|
decorateMessageBody: ->
|
||||||
format = $('#message .views .tab.format.selected').attr 'data-message-format'
|
format = $('#message .views .tab.format.selected').attr 'data-message-format'
|
||||||
|
|
||||||
switch format
|
switch format
|
||||||
when 'html'
|
when 'html'
|
||||||
body = $('#message iframe').contents().find('body')
|
body = $('#message iframe').contents().find('body')
|
||||||
$("a", body).attr("target", "_blank")
|
$("a", body).attr("target", "_blank")
|
@ -1,5 +1,5 @@
|
|||||||
@import compass
|
@import "compass"
|
||||||
@import compass/reset
|
@import "compass/reset"
|
||||||
|
|
||||||
html, body
|
html, body
|
||||||
width: 100%
|
width: 100%
|
||||||
@ -57,7 +57,7 @@ body > header
|
|||||||
margin-left: 6px
|
margin-left: 6px
|
||||||
padding: 6px
|
padding: 6px
|
||||||
padding-left: 30px
|
padding-left: 30px
|
||||||
background: url(/images/logo.png) left no-repeat
|
background: url(/assets/logo.png) left no-repeat
|
||||||
font-size: 18px
|
font-size: 18px
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
a
|
a
|
@ -155,7 +155,7 @@ module MailCatcher extend self
|
|||||||
|
|
||||||
puts "Starting MailCatcher"
|
puts "Starting MailCatcher"
|
||||||
|
|
||||||
Thin::Logging.silent = true
|
Thin::Logging.silent = (ENV["MAILCATCHER_ENV"] != "development")
|
||||||
|
|
||||||
# One EventMachine loop...
|
# One EventMachine loop...
|
||||||
EventMachine.run do
|
EventMachine.run do
|
||||||
@ -174,7 +174,7 @@ module MailCatcher extend self
|
|||||||
# Let Thin set itself up inside our EventMachine loop
|
# Let Thin set itself up inside our EventMachine loop
|
||||||
# (Skinny/WebSockets just works on the inside)
|
# (Skinny/WebSockets just works on the inside)
|
||||||
rescue_port options[:http_port] do
|
rescue_port options[:http_port] do
|
||||||
Thin::Server.start options[:http_ip], options[:http_port], Web
|
Thin::Server.start(options[:http_ip], options[:http_port], Web)
|
||||||
puts "==> #{http_url}"
|
puts "==> #{http_url}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,152 +1,21 @@
|
|||||||
require "pathname"
|
require "active_support/core_ext/module/delegation"
|
||||||
require "net/http"
|
require "rack/builder"
|
||||||
require "uri"
|
|
||||||
|
|
||||||
require "sinatra"
|
require "mail_catcher/web/application"
|
||||||
require "skinny"
|
|
||||||
|
|
||||||
require "mail_catcher/events"
|
module MailCatcher
|
||||||
require "mail_catcher/mail"
|
module Web extend self
|
||||||
|
def app
|
||||||
|
@@app ||= Rack::Builder.new do
|
||||||
|
if ENV["MAILCATCHER_ENV"] == "development"
|
||||||
|
require "mail_catcher/web/assets"
|
||||||
|
map("/assets") { run Assets }
|
||||||
|
end
|
||||||
|
|
||||||
class Sinatra::Request
|
map("/") { run Application }
|
||||||
include Skinny::Helpers
|
end
|
||||||
end
|
|
||||||
|
|
||||||
class MailCatcher::Web < Sinatra::Base
|
|
||||||
set :root, File.expand_path("#{__FILE__}/../../..")
|
|
||||||
set :haml, :format => :html5
|
|
||||||
|
|
||||||
get "/" do
|
|
||||||
haml :index
|
|
||||||
end
|
|
||||||
|
|
||||||
delete "/" do
|
|
||||||
if MailCatcher.quittable?
|
|
||||||
MailCatcher.quit!
|
|
||||||
status 204
|
|
||||||
else
|
|
||||||
status 403
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
get "/messages" do
|
delegate :call, :to => :app
|
||||||
if request.websocket?
|
|
||||||
request.websocket!(
|
|
||||||
:on_start => proc do |websocket|
|
|
||||||
subscription = MailCatcher::Events::MessageAdded.subscribe { |message| websocket.send_message message.to_json }
|
|
||||||
websocket.on_close do |websocket|
|
|
||||||
MailCatcher::Events::MessageAdded.unsubscribe subscription
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
MailCatcher::Mail.messages.to_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
delete "/messages" do
|
|
||||||
MailCatcher::Mail.delete!
|
|
||||||
status 204
|
|
||||||
end
|
|
||||||
|
|
||||||
get "/messages/:id.json" do
|
|
||||||
id = params[:id].to_i
|
|
||||||
if message = MailCatcher::Mail.message(id)
|
|
||||||
message.merge({
|
|
||||||
"formats" => [
|
|
||||||
"source",
|
|
||||||
("html" if MailCatcher::Mail.message_has_html? id),
|
|
||||||
("plain" if MailCatcher::Mail.message_has_plain? id)
|
|
||||||
].compact,
|
|
||||||
"attachments" => MailCatcher::Mail.message_attachments(id).map do |attachment|
|
|
||||||
attachment.merge({"href" => "/messages/#{escape(id)}/parts/#{escape(attachment["cid"])}"})
|
|
||||||
end,
|
|
||||||
}).to_json
|
|
||||||
else
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
get "/messages/:id.html" do
|
|
||||||
id = params[:id].to_i
|
|
||||||
if part = MailCatcher::Mail.message_part_html(id)
|
|
||||||
content_type part["type"], :charset => (part["charset"] || "utf8")
|
|
||||||
|
|
||||||
body = part["body"]
|
|
||||||
|
|
||||||
# Rewrite body to link to embedded attachments served by cid
|
|
||||||
body.gsub! /cid:([^'"> ]+)/, "#{id}/parts/\\1"
|
|
||||||
|
|
||||||
body
|
|
||||||
else
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
get "/messages/:id.plain" do
|
|
||||||
id = params[:id].to_i
|
|
||||||
if part = MailCatcher::Mail.message_part_plain(id)
|
|
||||||
content_type part["type"], :charset => (part["charset"] || "utf8")
|
|
||||||
part["body"]
|
|
||||||
else
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
get "/messages/:id.source" do
|
|
||||||
id = params[:id].to_i
|
|
||||||
if message = MailCatcher::Mail.message(id)
|
|
||||||
content_type "text/plain"
|
|
||||||
message["source"]
|
|
||||||
else
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
get "/messages/:id.eml" do
|
|
||||||
id = params[:id].to_i
|
|
||||||
if message = MailCatcher::Mail.message(id)
|
|
||||||
content_type "message/rfc822"
|
|
||||||
message["source"]
|
|
||||||
else
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
get "/messages/:id/parts/:cid" do
|
|
||||||
id = params[:id].to_i
|
|
||||||
if part = MailCatcher::Mail.message_part_cid(id, params[:cid])
|
|
||||||
content_type part["type"], :charset => (part["charset"] || "utf8")
|
|
||||||
attachment part["filename"] if part["is_attachment"] == 1
|
|
||||||
body part["body"].to_s
|
|
||||||
else
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
get "/messages/:id/analysis.?:format?" do
|
|
||||||
id = params[:id].to_i
|
|
||||||
if part = MailCatcher::Mail.message_part_html(id)
|
|
||||||
# TODO: Server-side cache? Make the browser cache based on message create time? Hmm.
|
|
||||||
uri = URI.parse("http://api.getfractal.com/api/v2/validate#{"/format/#{params[:format]}" if params[:format].present?}")
|
|
||||||
response = Net::HTTP.post_form(uri, :api_key => "5c463877265251386f516f7428", :html => part["body"])
|
|
||||||
content_type ".#{params[:format]}" if params[:format].present?
|
|
||||||
body response.body
|
|
||||||
else
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
delete "/messages/:id" do
|
|
||||||
id = params[:id].to_i
|
|
||||||
if message = MailCatcher::Mail.message(id)
|
|
||||||
MailCatcher::Mail.delete_message!(id)
|
|
||||||
status 204
|
|
||||||
else
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
not_found do
|
|
||||||
"<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>"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
157
lib/mail_catcher/web/application.rb
Normal file
157
lib/mail_catcher/web/application.rb
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
require "pathname"
|
||||||
|
require "net/http"
|
||||||
|
require "uri"
|
||||||
|
|
||||||
|
require "sinatra"
|
||||||
|
require "skinny"
|
||||||
|
|
||||||
|
require "mail_catcher/events"
|
||||||
|
require "mail_catcher/mail"
|
||||||
|
require "mail_catcher/web"
|
||||||
|
|
||||||
|
class Sinatra::Request
|
||||||
|
include Skinny::Helpers
|
||||||
|
end
|
||||||
|
|
||||||
|
module MailCatcher
|
||||||
|
module Web
|
||||||
|
class Application < Sinatra::Base
|
||||||
|
set :root, File.expand_path("#{__FILE__}/../../../..")
|
||||||
|
set :haml, :format => :html5
|
||||||
|
|
||||||
|
get "/" do
|
||||||
|
haml :index
|
||||||
|
end
|
||||||
|
|
||||||
|
delete "/" do
|
||||||
|
if MailCatcher.quittable?
|
||||||
|
MailCatcher.quit!
|
||||||
|
status 204
|
||||||
|
else
|
||||||
|
status 403
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/messages" do
|
||||||
|
if request.websocket?
|
||||||
|
request.websocket!(
|
||||||
|
:on_start => proc do |websocket|
|
||||||
|
subscription = Events::MessageAdded.subscribe { |message| websocket.send_message message.to_json }
|
||||||
|
websocket.on_close do |websocket|
|
||||||
|
Events::MessageAdded.unsubscribe subscription
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
Mail.messages.to_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
delete "/messages" do
|
||||||
|
Mail.delete!
|
||||||
|
status 204
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/messages/:id.json" do
|
||||||
|
id = params[:id].to_i
|
||||||
|
if message = Mail.message(id)
|
||||||
|
message.merge({
|
||||||
|
"formats" => [
|
||||||
|
"source",
|
||||||
|
("html" if Mail.message_has_html? id),
|
||||||
|
("plain" if Mail.message_has_plain? id)
|
||||||
|
].compact,
|
||||||
|
"attachments" => Mail.message_attachments(id).map do |attachment|
|
||||||
|
attachment.merge({"href" => "/messages/#{escape(id)}/parts/#{escape(attachment["cid"])}"})
|
||||||
|
end,
|
||||||
|
}).to_json
|
||||||
|
else
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/messages/:id.html" do
|
||||||
|
id = params[:id].to_i
|
||||||
|
if part = Mail.message_part_html(id)
|
||||||
|
content_type part["type"], :charset => (part["charset"] || "utf8")
|
||||||
|
|
||||||
|
body = part["body"]
|
||||||
|
|
||||||
|
# Rewrite body to link to embedded attachments served by cid
|
||||||
|
body.gsub! /cid:([^'"> ]+)/, "#{id}/parts/\\1"
|
||||||
|
|
||||||
|
body
|
||||||
|
else
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/messages/:id.plain" do
|
||||||
|
id = params[:id].to_i
|
||||||
|
if part = Mail.message_part_plain(id)
|
||||||
|
content_type part["type"], :charset => (part["charset"] || "utf8")
|
||||||
|
part["body"]
|
||||||
|
else
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/messages/:id.source" do
|
||||||
|
id = params[:id].to_i
|
||||||
|
if message = Mail.message(id)
|
||||||
|
content_type "text/plain"
|
||||||
|
message["source"]
|
||||||
|
else
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/messages/:id.eml" do
|
||||||
|
id = params[:id].to_i
|
||||||
|
if message = Mail.message(id)
|
||||||
|
content_type "message/rfc822"
|
||||||
|
message["source"]
|
||||||
|
else
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/messages/:id/parts/:cid" do
|
||||||
|
id = params[:id].to_i
|
||||||
|
if part = Mail.message_part_cid(id, params[:cid])
|
||||||
|
content_type part["type"], :charset => (part["charset"] || "utf8")
|
||||||
|
attachment part["filename"] if part["is_attachment"] == 1
|
||||||
|
body part["body"].to_s
|
||||||
|
else
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/messages/:id/analysis.?:format?" do
|
||||||
|
id = params[:id].to_i
|
||||||
|
if part = Mail.message_part_html(id)
|
||||||
|
# TODO: Server-side cache? Make the browser cache based on message create time? Hmm.
|
||||||
|
uri = URI.parse("http://api.getfractal.com/api/v2/validate#{"/format/#{params[:format]}" if params[:format].present?}")
|
||||||
|
response = Net::HTTP.post_form(uri, :api_key => "5c463877265251386f516f7428", :html => part["body"])
|
||||||
|
content_type ".#{params[:format]}" if params[:format].present?
|
||||||
|
body response.body
|
||||||
|
else
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
delete "/messages/:id" do
|
||||||
|
id = params[:id].to_i
|
||||||
|
if message = Mail.message(id)
|
||||||
|
MailCatcher::Mail.delete_message!(id)
|
||||||
|
status 204
|
||||||
|
else
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
not_found do
|
||||||
|
"<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>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
lib/mail_catcher/web/assets.rb
Normal file
15
lib/mail_catcher/web/assets.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
require "sprockets"
|
||||||
|
require "sprockets-sass"
|
||||||
|
require "compass"
|
||||||
|
|
||||||
|
require "mail_catcher/web/application"
|
||||||
|
|
||||||
|
module MailCatcher
|
||||||
|
module Web
|
||||||
|
Assets = Sprockets::Environment.new(Application.root).tap do |sprockets|
|
||||||
|
Dir["#{Application.root}/{,vendor}/assets/*"].each do |path|
|
||||||
|
sprockets.append_path(path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -21,12 +21,9 @@ Gem::Specification.new do |s|
|
|||||||
"README.md", "LICENSE", "VERSION",
|
"README.md", "LICENSE", "VERSION",
|
||||||
"bin/*",
|
"bin/*",
|
||||||
"lib/**/*.rb",
|
"lib/**/*.rb",
|
||||||
"public/favicon.ico",
|
"public/**/*",
|
||||||
"public/images/**/*",
|
"views/**/*",
|
||||||
"public/javascripts/**/*.js",
|
] - Dir["lib/mail_catcher/web/assets.rb"]
|
||||||
"public/stylesheets/**/*.{css,xsl}",
|
|
||||||
"views/**/*"
|
|
||||||
]
|
|
||||||
s.require_paths = ["lib"]
|
s.require_paths = ["lib"]
|
||||||
s.executables = ["mailcatcher", "catchmail"]
|
s.executables = ["mailcatcher", "catchmail"]
|
||||||
s.extra_rdoc_files = ["README.md", "LICENSE"]
|
s.extra_rdoc_files = ["README.md", "LICENSE"]
|
||||||
@ -47,4 +44,6 @@ Gem::Specification.new do |s|
|
|||||||
s.add_development_dependency "rake"
|
s.add_development_dependency "rake"
|
||||||
s.add_development_dependency "rdoc"
|
s.add_development_dependency "rdoc"
|
||||||
s.add_development_dependency "sass"
|
s.add_development_dependency "sass"
|
||||||
|
s.add_development_dependency "sprockets"
|
||||||
|
s.add_development_dependency "sprockets-sass"
|
||||||
end
|
end
|
||||||
|
@ -1,435 +0,0 @@
|
|||||||
(function() {
|
|
||||||
var MailCatcher,
|
|
||||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
||||||
|
|
||||||
jQuery.expr[':'].icontains = function(a, i, m) {
|
|
||||||
var _ref, _ref1;
|
|
||||||
return ((_ref = (_ref1 = a.textContent) != null ? _ref1 : a.innerText) != null ? _ref : "").toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher = (function() {
|
|
||||||
function MailCatcher() {
|
|
||||||
this.nextTab = __bind(this.nextTab, this);
|
|
||||||
this.previousTab = __bind(this.previousTab, this);
|
|
||||||
this.openTab = __bind(this.openTab, this);
|
|
||||||
this.selectedTab = __bind(this.selectedTab, this);
|
|
||||||
this.getTab = __bind(this.getTab, this);
|
|
||||||
var _this = this;
|
|
||||||
$('#messages tr').live('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
return _this.loadMessage($(e.currentTarget).attr('data-message-id'));
|
|
||||||
});
|
|
||||||
$('input[name=search]').keyup(function(e) {
|
|
||||||
var query;
|
|
||||||
query = $.trim($(e.currentTarget).val());
|
|
||||||
if (query) {
|
|
||||||
return _this.searchMessages(query);
|
|
||||||
} else {
|
|
||||||
return _this.clearSearch();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#message .views .format.tab a').live('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
return _this.loadMessageBody(_this.selectedMessage(), $($(e.currentTarget).parent('li')).data('message-format'));
|
|
||||||
});
|
|
||||||
$('#message .views .analysis.tab a').live('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
return _this.loadMessageAnalysis(_this.selectedMessage());
|
|
||||||
});
|
|
||||||
$('#message iframe').load(function() {
|
|
||||||
return _this.decorateMessageBody();
|
|
||||||
});
|
|
||||||
$('#resizer').live({
|
|
||||||
mousedown: function(e) {
|
|
||||||
var events;
|
|
||||||
e.preventDefault();
|
|
||||||
return $(window).bind(events = {
|
|
||||||
mouseup: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
return $(window).unbind(events);
|
|
||||||
},
|
|
||||||
mousemove: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
return _this.resizeTo(e.clientY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.resizeToSaved();
|
|
||||||
$('nav.app .clear a').live('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (confirm("You will lose all your received messages.\n\nAre you sure you want to clear all messages?")) {
|
|
||||||
return $.ajax({
|
|
||||||
url: '/messages',
|
|
||||||
type: 'DELETE',
|
|
||||||
success: function() {
|
|
||||||
return _this.unselectMessage();
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
return alert('Error while clearing all messages.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('nav.app .quit a').live('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (confirm("You will lose all your received messages.\n\nAre you sure you want to quit?")) {
|
|
||||||
return $.ajax({
|
|
||||||
type: 'DELETE',
|
|
||||||
success: function() {
|
|
||||||
return location.replace($('body > header h1 a').attr('href'));
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
return alert('Error while quitting.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
key('up', function() {
|
|
||||||
if (_this.selectedMessage()) {
|
|
||||||
_this.loadMessage($('#messages tr.selected').prev().data('message-id'));
|
|
||||||
} else {
|
|
||||||
_this.loadMessage($('#messages tbody tr[data-message-id]:first').data('message-id'));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
key('down', function() {
|
|
||||||
if (_this.selectedMessage()) {
|
|
||||||
_this.loadMessage($('#messages tr.selected').next().data('message-id'));
|
|
||||||
} else {
|
|
||||||
_this.loadMessage($('#messages tbody tr[data-message-id]:first').data('message-id'));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
key('⌘+up, ctrl+up', function() {
|
|
||||||
_this.loadMessage($('#messages tbody tr[data-message-id]:first').data('message-id'));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
key('⌘+down, ctrl+down', function() {
|
|
||||||
_this.loadMessage($('#messages tbody tr[data-message-id]:last').data('message-id'));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
key('left', function() {
|
|
||||||
_this.openTab(_this.previousTab());
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
key('right', function() {
|
|
||||||
_this.openTab(_this.nextTab());
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
key('backspace, delete', function() {
|
|
||||||
var id;
|
|
||||||
id = _this.selectedMessage();
|
|
||||||
if (id != null) {
|
|
||||||
$.ajax({
|
|
||||||
url: '/messages/' + id,
|
|
||||||
type: 'DELETE',
|
|
||||||
success: function() {
|
|
||||||
var messageRow, switchTo;
|
|
||||||
messageRow = $("#messages tbody tr[data-message-id='" + id + "']");
|
|
||||||
switchTo = messageRow.next().data('message-id') || messageRow.prev().data('message-id');
|
|
||||||
messageRow.remove();
|
|
||||||
if (switchTo) {
|
|
||||||
return _this.loadMessage(switchTo);
|
|
||||||
} else {
|
|
||||||
return _this.unselectMessage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
return alert('Error while removing message.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
this.refresh();
|
|
||||||
this.subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
MailCatcher.prototype.parseDateRegexp = /^(\d{4})[-\/\\](\d{2})[-\/\\](\d{2})(?:\s+|T)(\d{2})[:-](\d{2})[:-](\d{2})(?:([ +-]\d{2}:\d{2}|\s*\S+|Z?))?$/;
|
|
||||||
|
|
||||||
MailCatcher.prototype.parseDate = function(date) {
|
|
||||||
var match;
|
|
||||||
if (match = this.parseDateRegexp.exec(date)) {
|
|
||||||
return new Date(match[1], match[2] - 1, match[3], match[4], match[5], match[6], 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.offsetTimeZone = function(date) {
|
|
||||||
var offset;
|
|
||||||
offset = Date.now().getTimezoneOffset() * 60000;
|
|
||||||
date.setTime(date.getTime() - offset);
|
|
||||||
return date;
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.formatDate = function(date) {
|
|
||||||
if (typeof date === "string") {
|
|
||||||
date && (date = this.parseDate(date));
|
|
||||||
}
|
|
||||||
date && (date = this.offsetTimeZone(date));
|
|
||||||
return date && (date = date.toString("dddd, d MMM yyyy h:mm:ss tt"));
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.messagesCount = function() {
|
|
||||||
return $('#messages tr').length - 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.tabs = function() {
|
|
||||||
return $('#message ul').children('.tab');
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.getTab = function(i) {
|
|
||||||
return $(this.tabs()[i]);
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.selectedTab = function() {
|
|
||||||
return this.tabs().index($('#message li.tab.selected'));
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.openTab = function(i) {
|
|
||||||
return this.getTab(i).children('a').click();
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.previousTab = function(tab) {
|
|
||||||
var i;
|
|
||||||
i = tab || tab === 0 ? tab : this.selectedTab() - 1;
|
|
||||||
if (i < 0) {
|
|
||||||
i = this.tabs().length - 1;
|
|
||||||
}
|
|
||||||
if (this.getTab(i).is(":visible")) {
|
|
||||||
return i;
|
|
||||||
} else {
|
|
||||||
return this.previousTab(i - 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.nextTab = function(tab) {
|
|
||||||
var i;
|
|
||||||
i = tab ? tab : this.selectedTab() + 1;
|
|
||||||
if (i > this.tabs().length - 1) {
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
if (this.getTab(i).is(":visible")) {
|
|
||||||
return i;
|
|
||||||
} else {
|
|
||||||
return this.nextTab(i + 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.haveMessage = function(message) {
|
|
||||||
if (message.id != null) {
|
|
||||||
message = message.id;
|
|
||||||
}
|
|
||||||
return $("#messages tbody tr[data-message-id=\"" + message + "\"]").length > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.selectedMessage = function() {
|
|
||||||
return $('#messages tr.selected').data('message-id');
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.searchMessages = function(query) {
|
|
||||||
var $rows, selector, token;
|
|
||||||
selector = ((function() {
|
|
||||||
var _i, _len, _ref, _results;
|
|
||||||
_ref = query.split(/\s+/);
|
|
||||||
_results = [];
|
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
||||||
token = _ref[_i];
|
|
||||||
_results.push(":icontains('" + token + "')");
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
})()).join("");
|
|
||||||
$rows = $("#messages tbody tr");
|
|
||||||
$rows.not(selector).hide();
|
|
||||||
return $rows.filter(selector).show();
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.clearSearch = function() {
|
|
||||||
return $('#messages tbody tr').show();
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.addMessage = function(message) {
|
|
||||||
return $('#messages tbody').prepend($('<tr />').attr('data-message-id', message.id.toString()).append($('<td/>').text(message.sender || "No sender").toggleClass("blank", !message.sender)).append($('<td/>').text((message.recipients || []).join(', ') || "No receipients").toggleClass("blank", !message.recipients.length)).append($('<td/>').text(message.subject || "No subject").toggleClass("blank", !message.subject)).append($('<td/>').text(this.formatDate(message.created_at))));
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.scrollToRow = function(row) {
|
|
||||||
var overflow, relativePosition;
|
|
||||||
relativePosition = row.offset().top - $('#messages').offset().top;
|
|
||||||
if (relativePosition < 0) {
|
|
||||||
return $('#messages').scrollTop($('#messages').scrollTop() + relativePosition - 20);
|
|
||||||
} else {
|
|
||||||
overflow = relativePosition + row.height() - $('#messages').height();
|
|
||||||
if (overflow > 0) {
|
|
||||||
return $('#messages').scrollTop($('#messages').scrollTop() + overflow + 20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.unselectMessage = function() {
|
|
||||||
$('#messages tbody, #message .metadata dd').empty();
|
|
||||||
$('#message .metadata .attachments').hide();
|
|
||||||
$('#message iframe').attr('src', 'about:blank');
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.loadMessage = function(id) {
|
|
||||||
var messageRow,
|
|
||||||
_this = this;
|
|
||||||
if ((id != null ? id.id : void 0) != null) {
|
|
||||||
id = id.id;
|
|
||||||
}
|
|
||||||
id || (id = $('#messages tr.selected').attr('data-message-id'));
|
|
||||||
if (id != null) {
|
|
||||||
$("#messages tbody tr:not([data-message-id='" + id + "'])").removeClass('selected');
|
|
||||||
messageRow = $("#messages tbody tr[data-message-id='" + id + "']");
|
|
||||||
messageRow.addClass('selected');
|
|
||||||
this.scrollToRow(messageRow);
|
|
||||||
return $.getJSON("/messages/" + id + ".json", function(message) {
|
|
||||||
var $ul;
|
|
||||||
$('#message .metadata dd.created_at').text(_this.formatDate(message.created_at));
|
|
||||||
$('#message .metadata dd.from').text(message.sender);
|
|
||||||
$('#message .metadata dd.to').text((message.recipients || []).join(', '));
|
|
||||||
$('#message .metadata dd.subject').text(message.subject);
|
|
||||||
$('#message .views .tab.format').each(function(i, el) {
|
|
||||||
var $el, format;
|
|
||||||
$el = $(el);
|
|
||||||
format = $el.attr('data-message-format');
|
|
||||||
if ($.inArray(format, message.formats) >= 0) {
|
|
||||||
$el.find('a').attr('href', "/messages/" + id + "." + format);
|
|
||||||
return $el.show();
|
|
||||||
} else {
|
|
||||||
return $el.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if ($("#message .views .tab.selected:not(:visible)").length) {
|
|
||||||
$("#message .views .tab.selected").removeClass("selected");
|
|
||||||
$("#message .views .tab:visible:first").addClass("selected");
|
|
||||||
}
|
|
||||||
if (message.attachments.length) {
|
|
||||||
$ul = $('<ul/>').appendTo($('#message .metadata dd.attachments').empty());
|
|
||||||
$.each(message.attachments, function(i, attachment) {
|
|
||||||
return $ul.append($('<li>').append($('<a>').attr('href', attachment['href']).addClass(attachment['type'].split('/', 1)[0]).addClass(attachment['type'].replace('/', '-')).text(attachment['filename'])));
|
|
||||||
});
|
|
||||||
$('#message .metadata .attachments').show();
|
|
||||||
} else {
|
|
||||||
$('#message .metadata .attachments').hide();
|
|
||||||
}
|
|
||||||
$('#message .views .download a').attr('href', "/messages/" + id + ".eml");
|
|
||||||
if ($('#message .views .tab.analysis.selected').length) {
|
|
||||||
return _this.loadMessageAnalysis();
|
|
||||||
} else {
|
|
||||||
return _this.loadMessageBody();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.loadMessageBody = function(id, format) {
|
|
||||||
var app;
|
|
||||||
id || (id = this.selectedMessage());
|
|
||||||
format || (format = $('#message .views .tab.format.selected').attr('data-message-format'));
|
|
||||||
format || (format = 'html');
|
|
||||||
$("#message .views .tab[data-message-format=\"" + format + "\"]:not(.selected)").addClass('selected');
|
|
||||||
$("#message .views .tab:not([data-message-format=\"" + format + "\"]).selected").removeClass('selected');
|
|
||||||
if (id != null) {
|
|
||||||
$('#message iframe').attr("src", "/messages/" + id + "." + format);
|
|
||||||
return app = this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.decorateMessageBody = function() {
|
|
||||||
var body, format, message_iframe, text;
|
|
||||||
format = $('#message .views .tab.format.selected').attr('data-message-format');
|
|
||||||
switch (format) {
|
|
||||||
case 'html':
|
|
||||||
body = $('#message iframe').contents().find('body');
|
|
||||||
return $("a", body).attr("target", "_blank");
|
|
||||||
case 'plain':
|
|
||||||
message_iframe = $('#message iframe').contents();
|
|
||||||
text = message_iframe.text();
|
|
||||||
text = text.replace(/((http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@?^=%&\/~\+#])?)/g, '<a href="$1" target="_blank">$1</a>');
|
|
||||||
text = text.replace(/\n/g, '<br/>');
|
|
||||||
return message_iframe.find('html').html('<html><body>' + text + '</html></body>');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.loadMessageAnalysis = function(id) {
|
|
||||||
var $form, $iframe;
|
|
||||||
id || (id = this.selectedMessage());
|
|
||||||
$("#message .views .analysis.tab:not(.selected)").addClass('selected');
|
|
||||||
$("#message .views :not(.analysis).tab.selected").removeClass('selected');
|
|
||||||
if (id != null) {
|
|
||||||
$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=\"https://www.getfractal.com/page/terms\" target=\"_blank\">terms of service</a> if you're paranoid.</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>");
|
|
||||||
return $form = $iframe.find('form').submit(function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$(this).find('input[type="submit"]').attr('disabled', 'disabled').end().find('.loading').show();
|
|
||||||
return $('#message iframe').contents().find('body').xslt("/messages/" + id + "/analysis.xml", "/stylesheets/analysis.xsl");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.refresh = function() {
|
|
||||||
var _this = this;
|
|
||||||
return $.getJSON('/messages', function(messages) {
|
|
||||||
return $.each(messages, function(i, message) {
|
|
||||||
if (!_this.haveMessage(message)) {
|
|
||||||
return _this.addMessage(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.subscribe = function() {
|
|
||||||
if (typeof WebSocket !== "undefined" && WebSocket !== null) {
|
|
||||||
return this.subscribeWebSocket();
|
|
||||||
} else {
|
|
||||||
return this.subscribePoll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.subscribeWebSocket = function() {
|
|
||||||
var secure,
|
|
||||||
_this = this;
|
|
||||||
secure = window.location.scheme === 'https';
|
|
||||||
this.websocket = new WebSocket("" + (secure ? 'wss' : 'ws') + "://" + window.location.host + "/messages");
|
|
||||||
return this.websocket.onmessage = function(event) {
|
|
||||||
return _this.addMessage($.parseJSON(event.data));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.subscribePoll = function() {
|
|
||||||
var _this = this;
|
|
||||||
if (this.refreshInterval == null) {
|
|
||||||
return this.refreshInterval = setInterval((function() {
|
|
||||||
return _this.refresh();
|
|
||||||
}), 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.resizeToSavedKey = 'mailcatcherSeparatorHeight';
|
|
||||||
|
|
||||||
MailCatcher.prototype.resizeTo = function(height) {
|
|
||||||
var _ref;
|
|
||||||
$('#messages').css({
|
|
||||||
height: height - $('#messages').offset().top
|
|
||||||
});
|
|
||||||
return (_ref = window.localStorage) != null ? _ref.setItem(this.resizeToSavedKey, height) : void 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
MailCatcher.prototype.resizeToSaved = function() {
|
|
||||||
var height, _ref;
|
|
||||||
height = parseInt((_ref = window.localStorage) != null ? _ref.getItem(this.resizeToSavedKey) : void 0);
|
|
||||||
if (!isNaN(height)) {
|
|
||||||
return this.resizeTo(height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return MailCatcher;
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
$(function() {
|
|
||||||
return window.MailCatcher = new MailCatcher;
|
|
||||||
});
|
|
||||||
|
|
||||||
}).call(this);
|
|
@ -1,375 +0,0 @@
|
|||||||
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, img, ins, kbd, q, s, samp,
|
|
||||||
small, strike, strong, sub, sup, tt, var,
|
|
||||||
b, u, i, center,
|
|
||||||
dl, dt, dd, ol, ul, li,
|
|
||||||
fieldset, form, label, legend,
|
|
||||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
|
||||||
article, aside, canvas, details, embed,
|
|
||||||
figure, figcaption, footer, header, hgroup,
|
|
||||||
menu, nav, output, ruby, section, summary,
|
|
||||||
time, mark, audio, video {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
font: inherit;
|
|
||||||
font-size: 100%;
|
|
||||||
vertical-align: baseline; }
|
|
||||||
|
|
||||||
html {
|
|
||||||
line-height: 1; }
|
|
||||||
|
|
||||||
ol, ul {
|
|
||||||
list-style: none; }
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0; }
|
|
||||||
|
|
||||||
caption, th, td {
|
|
||||||
text-align: left;
|
|
||||||
font-weight: normal;
|
|
||||||
vertical-align: middle; }
|
|
||||||
|
|
||||||
q, blockquote {
|
|
||||||
quotes: none; }
|
|
||||||
q:before, q:after, blockquote:before, blockquote:after {
|
|
||||||
content: "";
|
|
||||||
content: none; }
|
|
||||||
|
|
||||||
a img {
|
|
||||||
border: none; }
|
|
||||||
|
|
||||||
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary {
|
|
||||||
display: block; }
|
|
||||||
|
|
||||||
html, body {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%; }
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -moz-box;
|
|
||||||
display: -ms-box;
|
|
||||||
display: box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
-ms-box-orient: vertical;
|
|
||||||
box-orient: vertical;
|
|
||||||
background: #eeeeee;
|
|
||||||
color: black;
|
|
||||||
font-size: 12px;
|
|
||||||
font-family: Helvetica, sans-serif; }
|
|
||||||
body html {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 2em; }
|
|
||||||
body.iframe {
|
|
||||||
background: white; }
|
|
||||||
body.iframe h1 {
|
|
||||||
font-size: 1.3em;
|
|
||||||
margin: 12px; }
|
|
||||||
body.iframe p, body.iframe form {
|
|
||||||
margin: 0 12px 12px 12px;
|
|
||||||
line-height: 1.25; }
|
|
||||||
body.iframe .loading {
|
|
||||||
color: #666666;
|
|
||||||
margin-left: 0.5em; }
|
|
||||||
|
|
||||||
.button {
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
border: 1px solid #cccccc;
|
|
||||||
-webkit-border-radius: 2px;
|
|
||||||
-moz-border-radius: 2px;
|
|
||||||
-ms-border-radius: 2px;
|
|
||||||
-o-border-radius: 2px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f4f4f4), color-stop(100%, #ececec)), #ececec;
|
|
||||||
background: -webkit-linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
background: -moz-linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
background: -o-linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
background: -ms-linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
background: linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
color: #666666;
|
|
||||||
text-shadow: 1px 1px 0 white;
|
|
||||||
text-decoration: none; }
|
|
||||||
.button:hover, .button:focus {
|
|
||||||
border-color: #999999;
|
|
||||||
border-bottom-color: #666666;
|
|
||||||
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #eeeeee), color-stop(100%, #dddddd)), #dddddd;
|
|
||||||
background: -webkit-linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
background: -moz-linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
background: -o-linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
background: -ms-linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
background: linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
color: #333333;
|
|
||||||
text-decoration: none; }
|
|
||||||
.button:active, .button.active {
|
|
||||||
border-color: #666666;
|
|
||||||
border-bottom-color: #999999;
|
|
||||||
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dddddd), color-stop(100%, #eeeeee)), #eeeeee;
|
|
||||||
background: -webkit-linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
background: -moz-linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
background: -o-linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
background: -ms-linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
background: linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
color: #333333;
|
|
||||||
text-decoration: none;
|
|
||||||
text-shadow: -1px -1px 0 #eeeeee; }
|
|
||||||
|
|
||||||
body > header {
|
|
||||||
overflow: hidden;
|
|
||||||
*zoom: 1;
|
|
||||||
border-bottom: 1px solid #cccccc; }
|
|
||||||
body > header h1 {
|
|
||||||
float: left;
|
|
||||||
margin-left: 6px;
|
|
||||||
padding: 6px;
|
|
||||||
padding-left: 30px;
|
|
||||||
background: url(/images/logo.png) left no-repeat;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold; }
|
|
||||||
body > header h1 a {
|
|
||||||
color: black;
|
|
||||||
text-decoration: none;
|
|
||||||
text-shadow: 0 1px 0 white;
|
|
||||||
-webkit-transition: 0.1s ease;
|
|
||||||
-moz-transition: 0.1s ease;
|
|
||||||
-ms-transition: 0.1s ease;
|
|
||||||
-o-transition: 0.1s ease;
|
|
||||||
transition: 0.1s ease; }
|
|
||||||
body > header h1 a:hover {
|
|
||||||
color: #4183c4; }
|
|
||||||
body > header nav {
|
|
||||||
border-left: 1px solid #cccccc; }
|
|
||||||
body > header nav.project {
|
|
||||||
float: left; }
|
|
||||||
body > header nav.app {
|
|
||||||
float: right; }
|
|
||||||
body > header nav li {
|
|
||||||
display: block;
|
|
||||||
float: left;
|
|
||||||
border-left: 1px solid white;
|
|
||||||
border-right: 1px solid #cccccc; }
|
|
||||||
body > header nav li input {
|
|
||||||
margin: 6px; }
|
|
||||||
body > header nav li a {
|
|
||||||
display: block;
|
|
||||||
padding: 10px;
|
|
||||||
text-decoration: none;
|
|
||||||
text-shadow: 0 1px 0 white;
|
|
||||||
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f4f4f4), color-stop(100%, #ececec)), #ececec;
|
|
||||||
background: -webkit-linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
background: -moz-linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
background: -o-linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
background: -ms-linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
background: linear-gradient(#f4f4f4, #ececec), #ececec;
|
|
||||||
color: #666666;
|
|
||||||
text-shadow: 1px 1px 0 white;
|
|
||||||
text-decoration: none; }
|
|
||||||
body > header nav li a:hover, body > header nav li a:focus {
|
|
||||||
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #eeeeee), color-stop(100%, #dddddd)), #dddddd;
|
|
||||||
background: -webkit-linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
background: -moz-linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
background: -o-linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
background: -ms-linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
background: linear-gradient(#eeeeee, #dddddd), #dddddd;
|
|
||||||
color: #333333;
|
|
||||||
text-decoration: none; }
|
|
||||||
body > header nav li a:active, body > header nav li a.active {
|
|
||||||
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #dddddd), color-stop(100%, #eeeeee)), #eeeeee;
|
|
||||||
background: -webkit-linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
background: -moz-linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
background: -o-linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
background: -ms-linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
background: linear-gradient(#dddddd, #eeeeee), #eeeeee;
|
|
||||||
color: #333333;
|
|
||||||
text-decoration: none;
|
|
||||||
text-shadow: -1px -1px 0 #eeeeee; }
|
|
||||||
|
|
||||||
#messages {
|
|
||||||
width: 100%;
|
|
||||||
height: 10em;
|
|
||||||
min-height: 3em;
|
|
||||||
overflow: auto;
|
|
||||||
background: white;
|
|
||||||
border-top: 1px solid white; }
|
|
||||||
#messages table {
|
|
||||||
overflow: hidden;
|
|
||||||
*zoom: 1;
|
|
||||||
width: 100%; }
|
|
||||||
#messages table thead tr {
|
|
||||||
background: #eeeeee;
|
|
||||||
color: #333333; }
|
|
||||||
#messages table thead tr th {
|
|
||||||
padding: 0.25em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #666666;
|
|
||||||
text-shadow: 0 1px 0 white; }
|
|
||||||
#messages table tbody tr {
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-transition: 0.1s ease;
|
|
||||||
-moz-transition: 0.1s ease;
|
|
||||||
-ms-transition: 0.1s ease;
|
|
||||||
-o-transition: 0.1s ease;
|
|
||||||
transition: 0.1s ease;
|
|
||||||
color: #333333; }
|
|
||||||
#messages table tbody tr:hover {
|
|
||||||
color: black; }
|
|
||||||
#messages table tbody tr:nth-child(even) {
|
|
||||||
background: #f0f0f0; }
|
|
||||||
#messages table tbody tr.selected {
|
|
||||||
background: Highlight;
|
|
||||||
color: HighlightText; }
|
|
||||||
#messages table tbody tr td {
|
|
||||||
padding: 0.25em; }
|
|
||||||
#messages table tbody tr td.blank {
|
|
||||||
color: #666666;
|
|
||||||
font-style: italic; }
|
|
||||||
|
|
||||||
#resizer {
|
|
||||||
padding-bottom: 5px;
|
|
||||||
cursor: ns-resize; }
|
|
||||||
#resizer .ruler {
|
|
||||||
border-top: 1px solid #cccccc;
|
|
||||||
border-bottom: 1px solid white; }
|
|
||||||
|
|
||||||
#message {
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -moz-box;
|
|
||||||
display: -ms-box;
|
|
||||||
display: box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
-ms-box-orient: vertical;
|
|
||||||
box-orient: vertical;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-moz-box-flex: 1;
|
|
||||||
-ms-box-flex: 1;
|
|
||||||
box-flex: 1; }
|
|
||||||
#message > header {
|
|
||||||
overflow: hidden;
|
|
||||||
*zoom: 1; }
|
|
||||||
#message > header .metadata {
|
|
||||||
overflow: hidden;
|
|
||||||
*zoom: 1;
|
|
||||||
padding: 0.5em;
|
|
||||||
padding-top: 0; }
|
|
||||||
#message > header .metadata dt, #message > header .metadata dd {
|
|
||||||
padding: 0.25em; }
|
|
||||||
#message > header .metadata dt {
|
|
||||||
float: left;
|
|
||||||
clear: left;
|
|
||||||
width: 8em;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
text-align: right;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #666666;
|
|
||||||
text-shadow: 0 1px 0 white; }
|
|
||||||
#message > header .metadata dd.subject {
|
|
||||||
font-weight: bold; }
|
|
||||||
#message > header .metadata .attachments {
|
|
||||||
display: none; }
|
|
||||||
#message > header .metadata .attachments ul {
|
|
||||||
display: inline; }
|
|
||||||
#message > header .metadata .attachments ul li {
|
|
||||||
display: -moz-inline-box;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
*vertical-align: auto;
|
|
||||||
margin-right: 0.5em; }
|
|
||||||
#message > header .metadata .attachments ul li {
|
|
||||||
*display: inline; }
|
|
||||||
#message > header .views ul {
|
|
||||||
padding: 0 0.5em;
|
|
||||||
border-bottom: 1px solid #cccccc; }
|
|
||||||
#message > header .views .tab {
|
|
||||||
display: -moz-inline-box;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
*vertical-align: auto; }
|
|
||||||
#message > header .views .tab {
|
|
||||||
*display: inline; }
|
|
||||||
#message > header .views .tab a {
|
|
||||||
display: -moz-inline-box;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
*vertical-align: auto;
|
|
||||||
padding: 0.5em;
|
|
||||||
border: 1px solid #cccccc;
|
|
||||||
background: #dddddd;
|
|
||||||
color: #333333;
|
|
||||||
border-width: 1px 1px 0 1px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-shadow: 0 1px 0 #eeeeee;
|
|
||||||
text-decoration: none; }
|
|
||||||
#message > header .views .tab a {
|
|
||||||
*display: inline; }
|
|
||||||
#message > header .views .tab:not(.selected):hover a {
|
|
||||||
background-color: #eeeeee; }
|
|
||||||
#message > header .views .tab.selected a {
|
|
||||||
background: white;
|
|
||||||
color: black;
|
|
||||||
height: 13px;
|
|
||||||
-webkit-box-shadow: 1px 1px 0 #cccccc;
|
|
||||||
-moz-box-shadow: 1px 1px 0 #cccccc;
|
|
||||||
box-shadow: 1px 1px 0 #cccccc;
|
|
||||||
margin-bottom: -2px;
|
|
||||||
cursor: default; }
|
|
||||||
#message > header .views .action {
|
|
||||||
display: -moz-inline-box;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
*vertical-align: auto;
|
|
||||||
float: right;
|
|
||||||
margin: 0 0.25em; }
|
|
||||||
#message > header .views .action {
|
|
||||||
*display: inline; }
|
|
||||||
|
|
||||||
.fractal-analysis {
|
|
||||||
margin: 12px 0; }
|
|
||||||
.fractal-analysis .report-intro {
|
|
||||||
font-weight: bold; }
|
|
||||||
.fractal-analysis .report-intro.valid {
|
|
||||||
color: #009900; }
|
|
||||||
.fractal-analysis .report-intro.invalid {
|
|
||||||
color: #cc3333; }
|
|
||||||
.fractal-analysis code {
|
|
||||||
font-family: Monaco, "Courier New", Courier, monospace;
|
|
||||||
background-color: ghostwhite;
|
|
||||||
color: #444444;
|
|
||||||
padding: 0 0.2em;
|
|
||||||
border: 1px solid #dedede; }
|
|
||||||
.fractal-analysis ul {
|
|
||||||
margin: 1em 0 1em 1em;
|
|
||||||
list-style-type: square; }
|
|
||||||
.fractal-analysis ol {
|
|
||||||
margin: 1em 0 1em 2em;
|
|
||||||
list-style-type: decimal; }
|
|
||||||
.fractal-analysis ul li, .fractal-analysis ol li {
|
|
||||||
display: list-item;
|
|
||||||
margin: 0.5em 0 0.5em 1em; }
|
|
||||||
.fractal-analysis .error-intro strong {
|
|
||||||
font-weight: bold; }
|
|
||||||
.fractal-analysis .unsupported-clients dt {
|
|
||||||
padding-left: 1em; }
|
|
||||||
.fractal-analysis .unsupported-clients dd {
|
|
||||||
padding-left: 2em; }
|
|
||||||
.fractal-analysis .unsupported-clients dd ul li {
|
|
||||||
display: list-item; }
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -moz-box;
|
|
||||||
display: -ms-box;
|
|
||||||
display: box;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-moz-box-flex: 1;
|
|
||||||
-ms-box-flex: 1;
|
|
||||||
box-flex: 1;
|
|
||||||
background: white; }
|
|
@ -2,15 +2,15 @@
|
|||||||
%html.mailcatcher
|
%html.mailcatcher
|
||||||
%head
|
%head
|
||||||
%title MailCatcher
|
%title MailCatcher
|
||||||
%link{:rel => "stylesheet", :href => "/stylesheets/application.css"}
|
%link{:rel => "stylesheet", :href => "/assets/application.css"}
|
||||||
%link{:href => "/favicon.ico", :rel => "shortcut icon" }
|
%link{:href => "/favicon.ico", :rel => "shortcut icon" }
|
||||||
%script{:src => "/javascripts/modernizr.js"}
|
%script{:src => "/assets/modernizr.js"}
|
||||||
%script{:src => "/javascripts/jquery.js"}
|
%script{:src => "/assets/jquery.js"}
|
||||||
%script{:src => "/javascripts/xslt-3.2.js"}
|
%script{:src => "/assets/xslt-3.2.js"}
|
||||||
%script{:src => "/javascripts/date.js"}
|
%script{:src => "/assets/date.js"}
|
||||||
%script{:src => "/javascripts/flexie.min.js"}
|
%script{:src => "/assets/flexie.min.js"}
|
||||||
%script{:src => "/javascripts/keymaster.min.js"}
|
%script{:src => "/assets/keymaster.min.js"}
|
||||||
%script{:src => "/javascripts/application.js"}
|
%script{:src => "/assets/application.js"}
|
||||||
%body
|
%body
|
||||||
%header
|
%header
|
||||||
%h1
|
%h1
|
||||||
|
Loading…
Reference in New Issue
Block a user