From 0398d2d6a36092ea814d92ec6eaf8e031805a90e Mon Sep 17 00:00:00 2001 From: Samuel Cochran Date: Mon, 17 Mar 2014 16:53:13 +1100 Subject: [PATCH] Switch to sprockets for assets --- .gitignore | 11 +- Gemfile.lock | 75 +-- Rakefile | 67 ++- {public => assets}/images/logo.png | Bin {public => assets}/images/logo_large.png | Bin .../javascripts/application.js.coffee | 18 +- {public => assets}/stylesheets/analysis.xsl | 0 .../stylesheets/application.css.sass | 6 +- lib/mail_catcher.rb | 4 +- lib/mail_catcher/web.rb | 159 +------ lib/mail_catcher/web/application.rb | 157 +++++++ lib/mail_catcher/web/assets.rb | 15 + mailcatcher.gemspec | 11 +- public/javascripts/application.js | 435 ------------------ public/stylesheets/application.css | 375 --------------- {public => vendor/assets}/javascripts/date.js | 0 .../assets}/javascripts/flexie.min.js | 0 .../assets}/javascripts/jquery.js | 0 .../assets}/javascripts/keymaster.min.js | 0 .../assets}/javascripts/modernizr.js | 0 .../assets}/javascripts/xslt-3.2.js | 0 views/index.haml | 16 +- 22 files changed, 299 insertions(+), 1050 deletions(-) rename {public => assets}/images/logo.png (100%) rename {public => assets}/images/logo_large.png (100%) rename public/javascripts/application.coffee => assets/javascripts/application.js.coffee (95%) rename {public => assets}/stylesheets/analysis.xsl (100%) rename public/stylesheets/application.sass => assets/stylesheets/application.css.sass (98%) create mode 100644 lib/mail_catcher/web/application.rb create mode 100644 lib/mail_catcher/web/assets.rb delete mode 100644 public/javascripts/application.js delete mode 100644 public/stylesheets/application.css rename {public => vendor/assets}/javascripts/date.js (100%) rename {public => vendor/assets}/javascripts/flexie.min.js (100%) rename {public => vendor/assets}/javascripts/jquery.js (100%) rename {public => vendor/assets}/javascripts/keymaster.min.js (100%) rename {public => vendor/assets}/javascripts/modernizr.js (100%) rename {public => vendor/assets}/javascripts/xslt-3.2.js (100%) 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 \ - $('').attr('data-message-id', message.id.toString()) - .append($('').text(message.sender or "No sender").toggleClass("blank", !message.sender)) - .append($('').text((message.recipients || []).join(', ') or "No receipients").toggleClass("blank", !message.recipients.length)) - .append($('').text(message.subject or "No subject").toggleClass("blank", !message.subject)) - .append($('').text @formatDate message.created_at) + $('').attr('data-message-id', message.id.toString()) + .append($('').text(message.sender or "No sender").toggleClass("blank", !message.sender)) + .append($('').text((message.recipients || []).join(', ') or "No receipients").toggleClass("blank", !message.recipients.length)) + .append($('').text(message.subject or "No subject").toggleClass("blank", !message.subject)) + .append($('').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") diff --git a/public/stylesheets/analysis.xsl b/assets/stylesheets/analysis.xsl similarity index 100% rename from public/stylesheets/analysis.xsl rename to assets/stylesheets/analysis.xsl diff --git a/public/stylesheets/application.sass b/assets/stylesheets/application.css.sass similarity index 98% rename from public/stylesheets/application.sass rename to assets/stylesheets/application.css.sass index e479541..301ac5f 100644 --- a/public/stylesheets/application.sass +++ b/assets/stylesheets/application.css.sass @@ -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 diff --git a/lib/mail_catcher.rb b/lib/mail_catcher.rb index a3cba42..8b9ba04 100644 --- a/lib/mail_catcher.rb +++ b/lib/mail_catcher.rb @@ -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 diff --git a/lib/mail_catcher/web.rb b/lib/mail_catcher/web.rb index ac231b8..1cda8cc 100644 --- a/lib/mail_catcher/web.rb +++ b/lib/mail_catcher/web.rb @@ -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 - "

No Dice

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 + "

No Dice

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($('').attr('data-message-id', message.id.toString()).append($('').text(message.sender || "No sender").toggleClass("blank", !message.sender)).append($('').text((message.recipients || []).join(', ') || "No receipients").toggleClass("blank", !message.recipients.length)).append($('').text(message.subject || "No subject").toggleClass("blank", !message.subject)).append($('').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 = $('