# Add a new jQuery selector expression which does a case-insensitive :contains jQuery.expr[':'].icontains = (a, i, m) -> (a.textContent ? a.innerText ? "").toUpperCase().indexOf(m[3].toUpperCase()) >= 0 class MailCatcher constructor: -> $('#messages tr').live 'click', (e) => e.preventDefault() @loadMessage $(e.currentTarget).attr 'data-message-id' $('input[name=search]').keyup (e) => query = $.trim $(e.currentTarget).val() if query @searchMessages query else @clearSearch() $('#message .views .format.tab a').live 'click', (e) => e.preventDefault() @loadMessageBody @selectedMessage(), $($(e.currentTarget).parent('li')).data 'message-format' $('#message .views .analysis.tab a').live 'click', (e) => e.preventDefault() @loadMessageAnalysis @selectedMessage() $('#message iframe').load => @decorateMessageBody() $('#resizer').live mousedown: (e) => e.preventDefault() $(window).bind events = mouseup: (e) => e.preventDefault() $(window).unbind events mousemove: (e) => e.preventDefault() @resizeTo e.clientY @resizeToSaved() $('nav.app .clear a').live 'click', (e) => e.preventDefault() if confirm "You will lose all your received messages.\n\nAre you sure you want to clear all messages?" $.ajax url: '/messages' type: 'DELETE' success: => @unselectMessage() error: -> alert 'Error while clearing all messages.' $('nav.app .quit a').live 'click', (e) => e.preventDefault() if confirm "You will lose all your received messages.\n\nAre you sure you want to quit?" $.ajax type: 'DELETE' success: -> location.replace $('body > header h1 a').attr('href') error: -> alert 'Error while quitting.' key 'up', => if @selectedMessage() @loadMessage $('#messages tr.selected').prev().data('message-id') else @loadMessage $('#messages tbody tr[data-message-id]:first').data('message-id') false key 'down', => if @selectedMessage() @loadMessage $('#messages tr.selected').next().data('message-id') else @loadMessage $('#messages tbody tr[data-message-id]:first').data('message-id') false key '⌘+up, ctrl+up', => @loadMessage $('#messages tbody tr[data-message-id]:first').data('message-id') false key '⌘+down, ctrl+down', => @loadMessage $('#messages tbody tr[data-message-id]:last').data('message-id') false key 'left', => @openTab @previousTab() false key 'right', => @openTab @nextTab() false key 'backspace, delete', => id = @selectedMessage() if id? $.ajax url: '/messages/' + id type: 'DELETE' success: => messageRow = $("#messages tbody tr[data-message-id='#{id}']") switchTo = messageRow.next().data('message-id') || messageRow.prev().data('message-id') messageRow.remove() if switchTo @loadMessage switchTo else @unselectMessage() error: -> alert 'Error while removing message.' false @refresh() @subscribe() # Only here because Safari's Date parsing *sucks* # We throw away the timezone, but you could use it for something... parseDateRegexp: /^(\d{4})[-\/\\](\d{2})[-\/\\](\d{2})(?:\s+|T)(\d{2})[:-](\d{2})[:-](\d{2})(?:([ +-]\d{2}:\d{2}|\s*\S+|Z?))?$/ parseDate: (date) -> if match = @parseDateRegexp.exec(date) new Date match[1], match[2] - 1, match[3], match[4], match[5], match[6], 0 offsetTimeZone: (date) -> offset = Date.now().getTimezoneOffset() * 60000 #convert timezone difference to milliseconds date.setTime(date.getTime() - offset) date formatDate: (date) -> date &&= @parseDate(date) if typeof(date) == "string" date &&= @offsetTimeZone(date) date &&= date.toString("dddd, d MMM yyyy h:mm:ss tt") messagesCount: -> $('#messages tr').length - 1 tabs: -> $('#message ul').children('.tab') getTab: (i) => $(@tabs()[i]) selectedTab: => @tabs().index($('#message li.tab.selected')) openTab: (i) => @getTab(i).children('a').click() previousTab: (tab)=> i = if tab || tab is 0 then tab else @selectedTab() - 1 i = @tabs().length - 1 if i < 0 if @getTab(i).is(":visible") i else @previousTab(i-1) nextTab: (tab) => i = if tab then tab else @selectedTab() + 1 i = 0 if i > @tabs().length - 1 if @getTab(i).is(":visible") i else @nextTab(i+1) haveMessage: (message) -> message = message.id if message.id? $("#messages tbody tr[data-message-id=\"#{message}\"]").length > 0 selectedMessage: -> $('#messages tr.selected').data 'message-id' searchMessages: (query) -> selector = (":icontains('#{token}')" for token in query.split /\s+/).join "" $rows = $("#messages tbody tr") $rows.not(selector).hide() $rows.filter(selector).show() clearSearch: -> $('#messages tbody tr').show() addMessage: (message) -> $('
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.
Please note that this sends your email to the Fractal service for analysis. Read their terms of service if you're paranoid.
""") $form = $iframe.find('form') .submit (e) -> e.preventDefault() $(this) .find('input[type="submit"]').attr('disabled', 'disabled').end() .find('.loading').show() $('#message iframe').contents().find('body').xslt("/messages/#{id}/analysis.xml", "/stylesheets/analysis.xsl") refresh: -> $.getJSON '/messages', (messages) => $.each messages, (i, message) => unless @haveMessage message @addMessage message subscribe: -> if WebSocket? @subscribeWebSocket() else @subscribePoll() subscribeWebSocket: -> secure = window.location.scheme == 'https' @websocket = new WebSocket("#{if secure then 'wss' else 'ws'}://#{window.location.host}/messages") @websocket.onmessage = (event) => @addMessage $.parseJSON event.data subscribePoll: -> unless @refreshInterval? @refreshInterval = setInterval (=> @refresh()), 1000 resizeToSavedKey: 'mailcatcherSeparatorHeight' resizeTo: (height) -> $('#messages').css height: height - $('#messages').offset().top window.localStorage?.setItem(@resizeToSavedKey, height) resizeToSaved: -> height = parseInt(window.localStorage?.getItem(@resizeToSavedKey)) unless isNaN height @resizeTo height $ -> window.MailCatcher = new MailCatcher