mirror of
https://github.com/moparisthebest/mailcatcher
synced 2025-01-06 03:08:07 -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
|
||||
*.gem
|
||||
|
||||
# Caches
|
||||
.bundle
|
||||
.sass-cache
|
||||
|
||||
# Generated documentation and assets
|
||||
/doc
|
||||
/public/assets
|
||||
|
||||
# Build gems
|
||||
*.gem
|
||||
|
75
Gemfile.lock
75
Gemfile.lock
@ -1,10 +1,10 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
mailcatcher (0.5.11)
|
||||
activesupport (~> 3.0)
|
||||
mailcatcher (0.6.0)
|
||||
activesupport (>= 3.0.0, < 4.1)
|
||||
eventmachine (~> 1.0.0)
|
||||
haml (>= 3.1, < 5)
|
||||
haml (>= 3.1, < 4.1)
|
||||
mail (~> 2.3)
|
||||
sinatra (~> 1.2)
|
||||
skinny (~> 0.2.3)
|
||||
@ -14,55 +14,72 @@ PATH
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (3.2.12)
|
||||
i18n (~> 0.6)
|
||||
multi_json (~> 1.0)
|
||||
chunky_png (1.2.7)
|
||||
activesupport (4.0.4)
|
||||
i18n (~> 0.6, >= 0.6.9)
|
||||
minitest (~> 4.2)
|
||||
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-source
|
||||
execjs
|
||||
coffee-script-source (1.6.1)
|
||||
coffee-script-source (1.7.0)
|
||||
compass (0.12.2)
|
||||
chunky_png (~> 1.2)
|
||||
fssm (>= 0.2.7)
|
||||
sass (~> 3.1)
|
||||
daemons (1.1.9)
|
||||
eventmachine (1.0.3)
|
||||
execjs (1.4.0)
|
||||
multi_json (~> 1.0)
|
||||
execjs (2.0.2)
|
||||
fssm (0.2.10)
|
||||
haml (4.0.0)
|
||||
haml (4.0.5)
|
||||
tilt
|
||||
i18n (0.6.4)
|
||||
mail (2.5.3)
|
||||
i18n (>= 0.4.0)
|
||||
hike (1.2.3)
|
||||
i18n (0.6.9)
|
||||
json (1.8.1)
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
mime-types (1.21)
|
||||
multi_json (1.6.1)
|
||||
polyglot (0.3.3)
|
||||
mime-types (1.25.1)
|
||||
minitest (4.7.5)
|
||||
multi_json (1.9.0)
|
||||
polyglot (0.3.4)
|
||||
rack (1.5.2)
|
||||
rack-protection (1.4.0)
|
||||
rack-protection (1.5.2)
|
||||
rack
|
||||
rake (10.0.3)
|
||||
rdoc (4.0.0)
|
||||
sass (3.2.7)
|
||||
sinatra (1.3.5)
|
||||
rake (10.1.1)
|
||||
rdoc (4.1.1)
|
||||
json (~> 1.4)
|
||||
sass (3.2.14)
|
||||
sinatra (1.4.4)
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.3)
|
||||
tilt (~> 1.3, >= 1.3.3)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (~> 1.3, >= 1.3.4)
|
||||
skinny (0.2.3)
|
||||
eventmachine (~> 1.0.0)
|
||||
thin (~> 1.5.0)
|
||||
sqlite3 (1.3.7)
|
||||
thin (1.5.0)
|
||||
sprockets (2.12.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)
|
||||
eventmachine (>= 0.12.6)
|
||||
rack (>= 1.0.0)
|
||||
tilt (1.3.5)
|
||||
treetop (1.4.12)
|
||||
thread_safe (0.2.0)
|
||||
atomic (>= 1.1.7, < 2)
|
||||
tilt (1.4.1)
|
||||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.39)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@ -74,3 +91,5 @@ DEPENDENCIES
|
||||
rake
|
||||
rdoc
|
||||
sass
|
||||
sprockets
|
||||
sprockets-sass
|
||||
|
67
Rakefile
67
Rakefile
@ -1,51 +1,46 @@
|
||||
require 'rubygems'
|
||||
require 'rubygems/package'
|
||||
require File.expand_path('../lib/mail_catcher/version', __FILE__)
|
||||
require "fileutils"
|
||||
require "rubygems"
|
||||
require "rubygems/package"
|
||||
|
||||
spec_file = File.expand_path __FILE__ + '/../mailcatcher.gemspec'
|
||||
spec = Gem::Specification.load spec_file
|
||||
require "mail_catcher/version"
|
||||
|
||||
require 'rdoc/task'
|
||||
RDoc::Task.new :rdoc => "doc",
|
||||
:clobber_rdoc => "doc:clean",
|
||||
:rerdoc => "doc:force" do |rdoc|
|
||||
spec_file = File.expand_path("../mailcatcher.gemspec", __FILE__)
|
||||
spec = Gem::Specification.load(spec_file)
|
||||
|
||||
require "rdoc/task"
|
||||
RDoc::Task.new(:rdoc => "doc",:clobber_rdoc => "doc:clean", :rerdoc => "doc:force") do |rdoc|
|
||||
rdoc.title = "MailCatcher #{MailCatcher::VERSION}"
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.main = 'README.md'
|
||||
rdoc.rdoc_files.include 'lib/**/*.rb'
|
||||
rdoc.rdoc_dir = "doc"
|
||||
rdoc.main = "README.md"
|
||||
rdoc.rdoc_files.include "lib/**/*.rb"
|
||||
end
|
||||
|
||||
desc "Compile SASS/SCSS files into SCSS"
|
||||
task "build:sass" do
|
||||
Dir["public/stylesheets/**/*.sass"].each do |file|
|
||||
css_file = file.sub /\.sass$/, ".css"
|
||||
system "sass", "--no-cache", "--compass", file, css_file
|
||||
# XXX: Would prefer to use Rake::SprocketsTask but can't populate
|
||||
# non-digest assets, and we don't want sprockets at runtime so
|
||||
# can't use manifest directly. Perhaps index.html should be
|
||||
# precompiled with digest assets paths?
|
||||
|
||||
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
|
||||
|
||||
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"
|
||||
task "package:gem" do
|
||||
task "package" => ["assets"] do
|
||||
Gem::Package.build spec
|
||||
end
|
||||
|
||||
task "package" => ["build", "package:gem"]
|
||||
|
||||
desc "Release Gem to RubyGems"
|
||||
task "release:gem" do
|
||||
task "release" => ["package"] do
|
||||
%x[gem push mailcatcher-#{MailCatcher::VERSION}.gem]
|
||||
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) =>
|
||||
e.preventDefault()
|
||||
@loadMessageAnalysis @selectedMessage()
|
||||
|
||||
|
||||
$('#message iframe').load =>
|
||||
@decorateMessageBody()
|
||||
|
||||
@ -177,12 +177,12 @@ class MailCatcher
|
||||
$('#messages tbody tr').show()
|
||||
|
||||
addMessage: (message) ->
|
||||
$('#messages tbody').prepend \
|
||||
$('<tr />').attr('data-message-id', message.id.toString())
|
||||
.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.subject or "No subject").toggleClass("blank", !message.subject))
|
||||
.append($('<td/>').text @formatDate message.created_at)
|
||||
$('<tr />').attr('data-message-id', message.id.toString())
|
||||
.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.subject or "No subject").toggleClass("blank", !message.subject))
|
||||
.append($('<td/>').text(@formatDate(message.created_at)))
|
||||
.prependTo($('#messages tbody'))
|
||||
|
||||
scrollToRow: (row) ->
|
||||
relativePosition = row.offset().top - $('#messages').offset().top
|
||||
@ -260,8 +260,8 @@ class MailCatcher
|
||||
|
||||
decorateMessageBody: ->
|
||||
format = $('#message .views .tab.format.selected').attr 'data-message-format'
|
||||
|
||||
switch format
|
||||
|
||||
switch format
|
||||
when 'html'
|
||||
body = $('#message iframe').contents().find('body')
|
||||
$("a", body).attr("target", "_blank")
|
@ -1,5 +1,5 @@
|
||||
@import compass
|
||||
@import compass/reset
|
||||
@import "compass"
|
||||
@import "compass/reset"
|
||||
|
||||
html, body
|
||||
width: 100%
|
||||
@ -57,7 +57,7 @@ body > header
|
||||
margin-left: 6px
|
||||
padding: 6px
|
||||
padding-left: 30px
|
||||
background: url(/images/logo.png) left no-repeat
|
||||
background: url(/assets/logo.png) left no-repeat
|
||||
font-size: 18px
|
||||
font-weight: bold
|
||||
a
|
@ -155,7 +155,7 @@ module MailCatcher extend self
|
||||
|
||||
puts "Starting MailCatcher"
|
||||
|
||||
Thin::Logging.silent = true
|
||||
Thin::Logging.silent = (ENV["MAILCATCHER_ENV"] != "development")
|
||||
|
||||
# One EventMachine loop...
|
||||
EventMachine.run do
|
||||
@ -174,7 +174,7 @@ module MailCatcher extend self
|
||||
# Let Thin set itself up inside our EventMachine loop
|
||||
# (Skinny/WebSockets just works on the inside)
|
||||
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}"
|
||||
end
|
||||
|
||||
|
@ -1,152 +1,21 @@
|
||||
require "pathname"
|
||||
require "net/http"
|
||||
require "uri"
|
||||
require "active_support/core_ext/module/delegation"
|
||||
require "rack/builder"
|
||||
|
||||
require "sinatra"
|
||||
require "skinny"
|
||||
require "mail_catcher/web/application"
|
||||
|
||||
require "mail_catcher/events"
|
||||
require "mail_catcher/mail"
|
||||
module MailCatcher
|
||||
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
|
||||
include Skinny::Helpers
|
||||
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
|
||||
map("/") { run Application }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
get "/messages" do
|
||||
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>"
|
||||
delegate :call, :to => :app
|
||||
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",
|
||||
"bin/*",
|
||||
"lib/**/*.rb",
|
||||
"public/favicon.ico",
|
||||
"public/images/**/*",
|
||||
"public/javascripts/**/*.js",
|
||||
"public/stylesheets/**/*.{css,xsl}",
|
||||
"views/**/*"
|
||||
]
|
||||
"public/**/*",
|
||||
"views/**/*",
|
||||
] - Dir["lib/mail_catcher/web/assets.rb"]
|
||||
s.require_paths = ["lib"]
|
||||
s.executables = ["mailcatcher", "catchmail"]
|
||||
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 "rdoc"
|
||||
s.add_development_dependency "sass"
|
||||
s.add_development_dependency "sprockets"
|
||||
s.add_development_dependency "sprockets-sass"
|
||||
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
|
||||
%head
|
||||
%title MailCatcher
|
||||
%link{:rel => "stylesheet", :href => "/stylesheets/application.css"}
|
||||
%link{:rel => "stylesheet", :href => "/assets/application.css"}
|
||||
%link{:href => "/favicon.ico", :rel => "shortcut icon" }
|
||||
%script{:src => "/javascripts/modernizr.js"}
|
||||
%script{:src => "/javascripts/jquery.js"}
|
||||
%script{:src => "/javascripts/xslt-3.2.js"}
|
||||
%script{:src => "/javascripts/date.js"}
|
||||
%script{:src => "/javascripts/flexie.min.js"}
|
||||
%script{:src => "/javascripts/keymaster.min.js"}
|
||||
%script{:src => "/javascripts/application.js"}
|
||||
%script{:src => "/assets/modernizr.js"}
|
||||
%script{:src => "/assets/jquery.js"}
|
||||
%script{:src => "/assets/xslt-3.2.js"}
|
||||
%script{:src => "/assets/date.js"}
|
||||
%script{:src => "/assets/flexie.min.js"}
|
||||
%script{:src => "/assets/keymaster.min.js"}
|
||||
%script{:src => "/assets/application.js"}
|
||||
%body
|
||||
%header
|
||||
%h1
|
||||
|
Loading…
Reference in New Issue
Block a user