diff --git a/.gitignore b/.gitignore index 4dfd235..a4b4b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ -rdoc -*.gem - +# Caches .bundle .sass-cache + +# Generated documentation and assets +/doc +/public/assets + +# Build gems +*.gem diff --git a/Gemfile.lock b/Gemfile.lock index b19ce46..878ea42 100644 --- a/Gemfile.lock +++ b/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 diff --git a/Rakefile b/Rakefile index 161c270..02c9dc0 100644 --- a/Rakefile +++ b/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" diff --git a/public/images/logo.png b/assets/images/logo.png similarity index 100% rename from public/images/logo.png rename to assets/images/logo.png diff --git a/public/images/logo_large.png b/assets/images/logo_large.png similarity index 100% rename from public/images/logo_large.png rename to assets/images/logo_large.png diff --git a/public/javascripts/application.coffee b/assets/javascripts/application.js.coffee similarity index 95% rename from public/javascripts/application.coffee rename to assets/javascripts/application.js.coffee index 4697331..9e10110 100644 --- a/public/javascripts/application.coffee +++ b/assets/javascripts/application.js.coffee @@ -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 \ - $('
The message you were looking for does not exist, or doesn't have content of this type.
" + delegate :call, :to => :app end end diff --git a/lib/mail_catcher/web/application.rb b/lib/mail_catcher/web/application.rb new file mode 100644 index 0000000..4c280aa --- /dev/null +++ b/lib/mail_catcher/web/application.rb @@ -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 + "The message you were looking for does not exist, or doesn't have content of this type.
" + end + end + end +end diff --git a/lib/mail_catcher/web/assets.rb b/lib/mail_catcher/web/assets.rb new file mode 100644 index 0000000..5f4e569 --- /dev/null +++ b/lib/mail_catcher/web/assets.rb @@ -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 diff --git a/mailcatcher.gemspec b/mailcatcher.gemspec index 577ece3..64d6a2e 100644 --- a/mailcatcher.gemspec +++ b/mailcatcher.gemspec @@ -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 diff --git a/public/javascripts/application.js b/public/javascripts/application.js deleted file mode 100644 index 0665b3a..0000000 --- a/public/javascripts/application.js +++ /dev/null @@ -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($('Fractal is a really neat service that applies common email design and development knowledge from Email Standards Project to your HTML email and tells you what you've done wrong or what you should do instead.
\nPlease note that this sends your email to the Fractal service for analysis. Read their terms of service if you're paranoid.
\n\n\n"); - 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); diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css deleted file mode 100644 index 85be5fe..0000000 --- a/public/stylesheets/application.css +++ /dev/null @@ -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; } diff --git a/public/javascripts/date.js b/vendor/assets/javascripts/date.js similarity index 100% rename from public/javascripts/date.js rename to vendor/assets/javascripts/date.js diff --git a/public/javascripts/flexie.min.js b/vendor/assets/javascripts/flexie.min.js similarity index 100% rename from public/javascripts/flexie.min.js rename to vendor/assets/javascripts/flexie.min.js diff --git a/public/javascripts/jquery.js b/vendor/assets/javascripts/jquery.js similarity index 100% rename from public/javascripts/jquery.js rename to vendor/assets/javascripts/jquery.js diff --git a/public/javascripts/keymaster.min.js b/vendor/assets/javascripts/keymaster.min.js similarity index 100% rename from public/javascripts/keymaster.min.js rename to vendor/assets/javascripts/keymaster.min.js diff --git a/public/javascripts/modernizr.js b/vendor/assets/javascripts/modernizr.js similarity index 100% rename from public/javascripts/modernizr.js rename to vendor/assets/javascripts/modernizr.js diff --git a/public/javascripts/xslt-3.2.js b/vendor/assets/javascripts/xslt-3.2.js similarity index 100% rename from public/javascripts/xslt-3.2.js rename to vendor/assets/javascripts/xslt-3.2.js diff --git a/views/index.haml b/views/index.haml index c0df1df..c0df3f4 100644 --- a/views/index.haml +++ b/views/index.haml @@ -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