add content-length headers to slug downloads

--- web.coffee
+++ web.coffee
@@ -106,11 +106,15 @@

 app.get "/slugs/:id.img", (req, res) ->
   storage.get "/slug/#{req.params.id}.img", (err, get) ->
+    res.writeHead get.statusCode,
+      "Content-Length": get.headers["content-length"]
     get.on "data", (chunk) -> res.write chunk
     get.on "end",          -> res.end()

 app.get "/slugs/:id.tgz", (req, res) ->
   storage.get "/slug/#{req.params.id}.tgz", (err, get) ->
+    res.writeHead get.statusCode,
+      "Content-Length": get.headers["content-length"]
     get.on "data", (chunk) -> res.write chunk
     get.on "end",          -> res.end()


renamed showUserPage to showPage (since we are already in the usercontroller)

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -15,12 +15,12 @@
     mainRegion: (user) -> getTextView('Here be notification settings')

   # ACTIONS
-  showProfile: (username) -> @showUserPage username, @profile_options(username)
-  showNotificationSettings: (username) -> @showUserPage username, @notification_options(username)
+  showProfile: (username) -> @showPage username, @profile_options(username)
+  showNotificationSettings: (username) -> @showPage username, @notification_options(username)

   # HELPERS

-  showUserPage: (username, options) ->
+  showPage: (username, options) ->
     @main = new TabbedMainRegionLayout();
     app.mainRegion.show(@main)
     @getUser username,


Simplified preventDefault

Simplified preventDefault

--- core/app/backbone/factlink/react_text_input_view.coffee
+++ core/app/backbone/factlink/react_text_input_view.coffee
@@ -17,9 +17,7 @@
       when 27 then @_setText ''
       else eventHandled = false

-    if eventHandled
-      e.preventDefault()
-      e.stopPropagation()
+    return !eventHandled

   _onChange: (e) -> @_setText e.target.value


Groups: fixes when user does not have any existing domains, no items were defined in select box of domain creation form

--- client/Environments/views/domains/domaincreateform.coffee
+++ client/Environments/views/domains/domaincreateform.coffee
@@ -95,17 +95,30 @@

   updateDomains: ->

+    domainList = []
+    pushDomainOption = (context) ->
+      domain = "#{context}.kd.io"
+      domainList.push {title:".#{domain}", value: domain}
+
     KD.whoami().fetchDomains (err, userDomains)=>
-      warn "Failed to update domains:", err  if err
-      domainList = []
-
+      return warn "Failed to update domains:", err  if err
+
       if userDomains
         for domain in userDomains
           if not domain.regYears > 0
             domainList.push {title:".#{domain.domain}", value:domain.domain}

+      group = KD.getSingleton("groupsController").currentGroupName
+      # adds group root domain to domain selections
+      unless group is "koding"
+        pushDomainOption group
+      else
+        slug = KD.whoami().profile.nickname              # if user domain root does not exist, it is added
+        rootDomain = userDomains.filter (domain) ->      # here. actually it is not possible to delete a root
+          domain.domain is "#{slug}.kd.io"               # domain, but defensive checks are always good.
+        pushDomainOption slug  unless rootDomain.length
+
       {domains, domainName} = @subDomainEntryForm.inputs
-
       domainName.setValue ""
       domains.removeSelectOptions()
       domains.setSelectOptions domainList


home/billing: simplify back the text inputs of credit card

--- client/home/lib/billing/components/creditcard/view.coffee
+++ client/home/lib/billing/components/creditcard/view.coffee
@@ -43,14 +43,22 @@

   type = kd.utils.slugify cardType

+  placeholder = ''
+
+  # means that the value is coming from the group card.
+  # so we will use this as a placeholder
+  if /^\*/.test(value)
+    placeholder = value
+    value = ''
+
   <div className="CardNumber-wrapper #{type}">
     <MaskedInput
       mask={mask}
       className={inputClass 'card-number'}
-      formatChars={{"9": "(\\*|0|1|2|3|4|5|6|7|8|9)"}}
       onChange={pickValue(onChange ? noop)}
       value={value}
-      placeholder='0000 0000 0000 0000' />
+      alwaysShowMask={yes}
+      placeholder={placeholder} />
   </div>

@@ -90,17 +98,17 @@

 CVC = ({ onChange, value, cardType }) ->

-  _props = switch cardType
-    when 'American Express' then {mask: '9999', placeholder: '0000'}
-    else {mask: '999', placeholder: '000'}
+  mask = switch cardType
+    when 'American Express' then '9999'
+    else '999'

   <MaskedInput
-    mask={_props.mask}
+    mask={mask}
     className={inputClass 'cvc'}
     onChange={pickValue(onChange ? noop)}
     formatChars={{"9": "(\\*|0|1|2|3|4|5|6|7|8|9)"}}
     value={value}
-    placeholder={_props.placeholder} />
+    alwaysShowMask={yes} />

 pickValue = (onChange) -> (event) -> onChange event.target.value


Clean up code

Clean up code

--- lib/autoprefixer/hacks/gradient.coffee
+++ lib/autoprefixer/hacks/gradient.coffee
@@ -25,24 +25,22 @@
             'radial-gradient', 'repeating-radial-gradient']

   @regexps = { }
+  @starts  = { }
   for i in @names
     @regexps[i] = new RegExp('(^|\\s|,)' + i + '\\((.+)\\)', 'gi')
-
-  @startRegexp = new RegExp('(^|\\s*)' + @names.join('|'), 'i')
+    @starts[i]  = new RegExp('(^|\\s*)' + i, 'i')

   # Cache regexp to parse params
   constructor: (@name, @prefixes) ->
-    @regexp = Gradient.regexps[@name]
-    @gradientStartRegexp = Gradient.startRegexp
+    @regexp      = Gradient.regexps[@name]
+    @startRegexp = Gradient.starts[@name]

   # Change degrees for webkit prefix
   addPrefix: (prefix, string) ->
     string.replace @regexp, (all, before, args) =>
-
-      decls = @splitDecls(all)
       prefixedDecls = []

-      for decl in decls
+      for decl in @splitDecls(all)
         prefixedDecls.push decl.replace @regexp, (all, before, args) =>
           params = @splitParams(args)
           params = @newDirection(params)
@@ -67,7 +65,7 @@
                 @fixRadial(params)
             before + prefix + @name + '(' + params.join(', ') + ')'

-      prefixedDecls.join ','
+      prefixedDecls.join(',')

   # Direction to replace
   directions:
@@ -88,27 +86,29 @@
     'bottom right': 'top left, bottom right'
     'bottom left':  'top right, bottom left'

+  # Split gradients in background value
   splitDecls: (decl) ->
-    chunks = decl.split ','
-    decls = []
+    decls       = []
+    chunks      = decl.split(',')
     currentDecl = []
     for i in chunks
       # chunks starts with gradient declaration
-      if @gradientStartRegexp.test i
+      if @startRegexp.test i
         if currentDecl.length == 0
           # start new decl
-          currentDecl.push i
+          currentDecl.push(i)
         else
           # save current decl and start new one
           decls.push currentDecl.join ','
           currentDecl = [i]
       else
-        currentDecl.push i
+        currentDecl.push(i)

     # save last parsed decl
-    decls.push currentDecl.join ','
+    decls.push( currentDecl.join(',') )
     decls

+  # Split params in gradient
   splitParams: (params) ->
     array = []
     param = ''


add a job to clean other jobs

add a job to clean other jobs

because cozy mails can generate zombie jobs

--- config/initializers/mailjobs.coffee
+++ config/initializers/mailjobs.coffee
@@ -1,166 +1,209 @@
-@kue = require 'kue'
-#@kue.app.listen 3003
-
-Job = @kue.Job
-@jobs = @kue.createQueue()
-app.jobs = @jobs
-
-
-# helpers
-
-logStarted = (job) ->
-    msg = "#{job.data.title} # #{job.id} started at "
-    msg += new Date().toUTCString()
-    console.log msg
-
-logComplete = (job) ->
-    msg = "#{job.data.title} # #{job.id} complete at "
-    msg += new Date().toUTCString()
-    console.log msg
-
-logFailed = (job) ->
-    msg = "#{job.data.title} # #{job.id} failed at "
-    msg += new Date().toUTCString()
-    console.log msg
-
-logProgress = (job, progress) ->
-    msg = "#{job.data.title} # #{job.id} "
-    msg += "#{progress} % complete"
-    console.log msg
-
-
-## JOB 1 : check for new mails for given mailbox
-
-# Define Job
-@jobs.process "check mailbox", 1, (job, done) ->
-    logStarted job
-
-    Mailbox.find job.data.mailboxId, (err, mailbox) ->
-        if err or not mailbox
-            done err
-        else
-            mailbox.getNewMail job, done
-
-
-# Job configuration + job start
-app.createCheckJob = (mailboxId, callback) =>
-
-    jobs = @jobs
-    lastProgress = -1
-
-    Mailbox.find mailboxId, (err, mailbox) ->
-        if err or not mailbox?
-            console.error "Check error... The mailbox doesn't exist"
-            callback err if callback?
-        else
-            job = jobs.create "check mailbox",
-                mailboxId: mailboxId
-                title: "Check of #{mailbox.name}"
-            .save()
-
-            callback() if callback?
-
-            job.on 'complete', () ->
-                logComplete job
-                mailbox.fetchFinished (err) ->
-                    if err
-                        mailbox.log "Check error..."
-                    else
-                        mailbox.log "Check successful !"
+if process.env isnt "test"
+    @kue = require 'kue'
+
+    Job = @kue.Job
+    @jobs = @kue.createQueue()
+    app.jobs = @jobs
+
+
+    # helpers
+
+    logStarted = (job) ->
+        msg = "#{job.type} # #{job.id} started at "
+        msg += new Date().toUTCString()
+        console.log msg
+
+    logComplete = (job) ->
+        msg = "#{job.type} # #{job.id} complete at "
+        msg += new Date().toUTCString()
+        console.log msg
+
+    logFailed = (job) ->
+        msg = "#{job.type} # #{job.id} failed at "
+        msg += new Date().toUTCString()
+        console.log msg
+
+    logProgress = (job, progress) ->
+        msg = "#{job.type} # #{job.id} "
+        msg += "#{progress} % complete"
+        console.log msg
+
+
+    ## JOB 1 : check for new mails for given mailbox
+
+    # Define Job
+    @jobs.process "check mailbox", 1, (job, done) ->
+        logStarted job
+
+        Mailbox.find job.data.mailboxId, (err, mailbox) ->
+            if err or not mailbox
+                done err
+            else
+                mailbox.getNewMail job, done
+
+
+    # Job configuration + job start
+    app.createCheckJob = (mailboxId, callback) =>
+        jobs = @jobs
+        lastProgress = -1
+
+        Mailbox.find mailboxId, (err, mailbox) ->
+            if err or not mailbox?
+                console.error "Check error... The mailbox doesn't exist"
+                callback err if callback?
+            else
+                job = jobs.create "check mailbox",
+                    mailboxId: mailboxId
+                    title: "Check of #{mailbox.name}"
+                .save()
+
+                callback() if callback?
+
+                job.on 'complete', () ->
+                    logComplete job
+                    mailbox.fetchFinished (err) ->
+                        if err
+                            mailbox.log "Check error..."
+                        else
+                            mailbox.log "Check successful !"
+                        job.remove (err) ->
+                            console.log err if err
+                            console.log "removed completed job ##{job.id}"
+
+                job.on 'failed', () ->
+                    logFailed job
                     job.remove (err) ->
                         console.log err if err
                         console.log "removed completed job ##{job.id}"
-
-            job.on 'failed', () ->
-                logFailed job
-                mailbox.fetchFailed (err) ->
-                    mailbox.log "Mail check failed."
+                        mailbox.fetchFailed (err) ->
+                            mailbox.log "Mail check failed."
+
+                job.on 'progress', (progress) ->
+                    logProgress job, progress if progress isnt lastProgress
+
+    app.createCheckJobs = =>
+        console.log "Creating check jobs..."
+        Mailbox.all (err, mailboxes) ->
+            if mailboxes?.length > 0
+                app.createCheckJob(mailbox.id) for mailbox in mailboxes
+
+    # set-up CRON
+    setInterval app.createCheckJobs, 1000 * 60 * 4 # check every 4 minutes
+
+    # launch on startup
+    app.createCheckJobs()
+
+
+    ## JOB 2 : import a whole box
+
+    # Define Job
+    @jobs.process "import mailbox", 1, (job, done) ->
+        logStarted job
+
+        Mailbox.find job.data.mailboxId, (err, mailbox) ->
+            if err
+                done err
+            else
+                mailbox.doImport job, done
+
+    # Job launcher
+    app.createImportJob = (mailboxId) =>
+
+        jobs = @jobs
+        lastProgress = -1
+        lastTenProgress = 0
+
+        createJobs = (mailbox) ->
+            job = jobs.create "import mailbox",
+                mailboxId: mailbox.id
+                title: "Import of #{mailbox.name}"
+                waitAfterFail: 1000 * 60
+            .save()
+            .attempts(5)
+
+            LogMessage.createImportStartedInfo mailbox
+
+            job.on 'complete', ->
+                logComplete job
+                mailbox.importSuccessfull (err) ->
+                    if err
+                        mailbox.log "Import error..."
+                    else
+                        mailbox.log "Import successful !"
                     job.remove (err) ->
                         console.log err if err
                         console.log "removed completed job ##{job.id}"

-            job.on 'progress', (progress) ->
-                logProgress job, progress if progress isnt lastProgress
-
-app.createCheckJobs = =>
-    console.log "Creating check jobs..."
-    Mailbox.all (err, mailboxes) ->
-        if mailboxes?.length > 0
-            app.createCheckJob(mailbox.id) for mailbox in mailboxes
-
-# set-up CRON
-setInterval app.createCheckJobs, 1000 * 60 * 4 # check every 4 minutes
-
-# launch on startup
-app.createCheckJobs()
-
-
-## JOB 2 : import a whole box
-
-# Define Job
-@jobs.process "import mailbox", 1, (job, done) ->
-    logStarted job
-
-    Mailbox.find job.data.mailboxId, (err, mailbox) ->
-        if err
-            done err
-        else
-            mailbox.doImport job, done
-
-# Job launcher
-app.createImportJob = (mailboxId) =>
-
-    jobs = @jobs
-    lastProgress = -1
-    lastTenProgress = 0
-
-    createJobs = (mailbox) ->
-        job = jobs.create "import mailbox",
-            mailboxId: mailbox.id
-            title: "Import of #{mailbox.name}"
-            waitAfterFail: 1000 * 60
-        .save()
-        .attempts(5)
-
-        LogMessage.createImportStartedInfo mailbox
-
-        job.on 'complete', ->
-            logComplete job
-            mailbox.importSuccessfull (err) ->
-                if err
-                    mailbox.log "Import error..."
-                else
-                    mailbox.log "Import successful !"
+            job.on 'failed', ->
+                logFailed job
                 job.remove (err) ->
                     console.log err if err
                     console.log "removed completed job ##{job.id}"
-
-        job.on 'failed', ->
+                    mailbox.importFailed (err) ->
+                        mailbox.log "Import failed...."
+
+            job.on 'progress', (progress) ->
+                if progress isnt lastProgress
+                    logProgress job, progress
+
+                    lastProgress = progress
+                    if progress > lastTenProgress + 10
+                        lastTenProgress += 10
+                        mailbox.progress progress, (err) ->
+                            console.error err if err
+
+
+        Mailbox.find mailboxId, (err, mailbox) ->
+            if err or not mailbox?
+                console.error "Import error... The mailbox doesn't exist"
+            else
+                mailbox.setupImport (err) ->
+                    if err
+                        mailbox.importError ->
+                            mailbox.log "Could not prepare the import. Aborting."
+                    else
+                        createJobs mailbox
+
+
+    ## JOB 3 : Clean jobs
+
+    # Define Job
+    @jobs.process "clean jobs", 1, (job, done) =>
+        logStarted job
+
+        now = new Date()
+        now.setHours(now.getHours() - 24)
+        @jobs.active (err, jobs) =>
+            for job in jobs
+                if job.created_at <= now and
+                (job.type is "check mailbox" or job.type is "import mailbox")
+                    job.remove()
+            @jobs.failed (err, jobs) =>
+                for job in jobs
+                    if job.type is "check mailbox" or
+                    job.type is "import mailbox"
+                        job.remove()
+                @jobs.inactive (err, jobs) ->
+                    if job.type is "check mailbox" or
+                    job.type is "import mailbox"
+                        job.remove()
+        done()
+
+    createCleanJob = =>
+
+        job = @jobs.create("clean jobs", {}).save()
+
+        job.on 'complete', () ->
+            logComplete job
+            job.remove (err) ->
+                console.log err if err
+                console.log "removed completed job ##{job.id}"
+
+        job.on 'failed', () ->
             logFailed job
-            mailbox.importFailed (err) ->
-                mailbox.log "Import failed...."
-                job.remove (err) ->
-                    console.log err if err
-                    console.log "removed completed job ##{job.id}"
-
-        job.on 'progress', (progress) ->
-            if progress isnt lastProgress
-                logProgress job, progress
-
-                lastProgress = progress
-                if progress > lastTenProgress + 10
-                    lastTenProgress += 10
-                    mailbox.progress progress, (err) ->
-                        console.error err if err
-
-
-    Mailbox.find mailboxId, (err, mailbox) ->
-        if err or not mailbox?
-            console.error "Import error... The mailbox doesn't exist"
-        else
-            mailbox.setupImport (err) ->
-                if err
-                    mailbox.importError ->
-                        mailbox.log "Could not prepare the import. Aborting."
-                else
-                    createJobs mailbox
+            job.remove (err) ->
+                console.log err if err
+                console.log "removed completed job ##{job.id}"
+
+    createCleanJob()
+    setInterval createCleanJob, 1000 * 60 * 60 # clean every hour


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -174,7 +174,7 @@
    indices = []
    ["r", "a", "d"].each (letter, index) ->
      word += letter
-     incides.push(index)
+     indices.push(index)

 => ["r", "a", "d"]


Cleaning spacing and add minimap

Cleaning spacing and add minimap

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -8,6 +8,7 @@
     tabLength: 4
     tabType: "soft"
   linter: {}
+  minimap: {}
   core:
     ignoredNames: [
       ".bundle"
@@ -22,9 +23,7 @@
       "atom-dark-syntax"
     ]
     hideGitIgnoredFiles: true
-    disabledPackages: [
-
-    ]
+    disabledPackages: []
     projectHome: "$HOME/hacking"
   "spell-check":
     grammars: [


My atom preferences v1

My atom preferences v1

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -4,7 +4,8 @@
     fontFamily: "Menlo"
     invisibles: {}
     fontSize: 15
-    softWrapAtPreferredLineLength: true
+    softWrapAtPreferredLineLength: false
+    backUpBeforeSaving: true
   core:
     ignoredNames: [
       ".bundle"
@@ -15,8 +16,8 @@
       "vendor"
     ]
     themes: [
-      "unity-ui"
-      "glacier-syntax"
+      "atom-dark-ui"
+      "solarized-dark-syntax"
     ]
     hideGitIgnoredFiles: true
     disabledPackages: [
@@ -34,3 +35,6 @@
     showOnStartup: false
   "release-notes":
     viewedVersion: "0.89.0"
+  tabs:
+    usePreviewTabs: true
+  "status-bar": {}


remove default snippet

remove default snippet

--- atom.symlink/init.coffee
+++ atom.symlink/init.coffee
@@ -6,8 +6,3 @@
 #
 # An example hack to make opened Markdown files always be soft wrapped:
 #
-path = require 'path'
-
-atom.workspace.observeTextEditors (editor) ->
-  if path.extname(editor.getPath()) is '.md'
-    editor.setSoftWrapped(true)


Cleanup scroll parent

Cleanup scroll parent

--- drop.coffee
+++ drop.coffee
@@ -16,9 +16,8 @@

 $ = jQuery
-isIE = not not /msie [\w.]+/.exec navigator.userAgent.toLowerCase()
+isIE = /msie [\w.]+/.test navigator.userAgent.toLowerCase()
 debounce = if isIE then 100 else 0
-

 # Extracted from jQuery UI Core (to remove dependency)
 # https://github.com/jquery/jquery-ui/blob/24756a978a977d7abbef5e5bce403837a01d964f/ui/jquery.ui.core.js#L60
@@ -26,23 +25,22 @@
     scrollParent: ->
         scrollParent = undefined

-        if (isIE and (/(static|relative)/).test(@css('position'))) or (/absolute/).test(@css('position'))
-            scrollParent = @parents().filter(->
-                (/(relative|absolute|fixed)/).test($.css(this, 'position')) and (/(auto|scroll)/).test($.css(this, 'overflow') + $.css(this, 'overflow-y') + $.css(this, 'overflow-x'))
-            ).eq(0)
-
-        else
-            scrollParent = @parents().filter(->
-                (/(auto|scroll)/).test $.css(this, 'overflow') + $.css(this, 'overflow-y') + $.css(this, 'overflow-x')
-            ).eq(0)
-
-        return (/fixed/).test(this.css('position')) or (if not scrollParent.length then $('html') else scrollParent)
+        if (isIE and @css('position') in ['static', 'relative']) or @css('position') is 'absolute'
+            scrollParent = @parents().filter(=>
+                $.css(this, 'position') in ['relative', 'absolute', 'fixed'] and /(auto|scroll)/.test(@css('overflow') + @css('overflow-y') + @css('overflow-x'))
+            ).first()
+
+        else
+            scrollParent = @parents().filter(=>
+                /(auto|scroll)/.test(@css('overflow') + @css('overflow-y') + @css('overflow-x'))
+            ).first()
+
+        return @css('position') is 'fixed' or (if scrollParent.length then scrollParent else $('html'))

 $.fn.removeClassPrefix = (prefix) ->
     $(@).attr 'class', (index, className) ->
         className.replace(new RegExp("\\b#{ prefix }\\S+", 'g'), '').replace(/\s+/g, ' ')
-

 $ ->

@@ -379,4 +377,4 @@
     else if typeof options is 'object' or not options
         jQueryMethods.init.apply this, arguments
     else
-        $.error "jQuery.drop: Method #{ options } does not exist"+        $.error "jQuery.drop: Method #{ options } does not exist"


Re-add option "text-voice".

Re-add option "text-voice".

--- bin/main.coffee
+++ bin/main.coffee
@@ -27,6 +27,8 @@
   [ '-r', "--remote [user[:password]@]host", "A ssh remote to connect when using the T2S api. This can be a comma separated list or multiple ones." ]
   [ '-u', "--jenkins-url URL", "Jenkins server url" ]
   [ '-j', "--jenkins-job job/type", 'A job in the format "job[/type]". This can be list of comma separated list. "type" has the default "lastBuild" and can have the following jenkins types: lastBuild, lastStableBuild, lastSuccessfulBuild, lstFailedBuild, lastUnsuccessfulBuild' ]
+  # TODO This option will be dismissed later in favor of a dedicated plugin.
+  [ '-t', "--text-voice voice", "Specify the text voice." ]
   [ '-t', "--text-file filename", "Specify a path to an alternative JSON file with texts." ]
   [ '-p', "--plugins name", "Specify a plugin by its name. This can be a list of comma separated list." ]
 ]
@@ -78,6 +80,9 @@
 Parser.on "text-file", (opt, value) ->
   Options['text-file'] = value

+Parser.on "text-voice", (opt, value) ->
+  Options['text-voice'] = value
+
 Parser.on "help", (opt, value) ->
   if value
     try


Minor copy edits and removal of tagline

Minor copy edits and removal of tagline

--- app/pages/home.cjsx
+++ app/pages/home.cjsx
@@ -16,26 +16,18 @@
   home:
     hero:
       title: 'People-Powered Research'
-      tagline: '''The Zooniverse is the largest online platform for collaborative volunteer research,
-      and an opportunity for people around the world to contribute to real discoveries in fields from
-      astronomy to zoology, and everything in between.'''
+      tagline: '''The Zooniverse provides opportunities for people around the world to contribute to real discoveries in fields ranging from astronomy to zoology. Welcome to the largest online platform for collaborative volunteer research.'''
       button: 'Get involved now!'
     about:
       contribute:
         title: 'Contribute to new research'
-        content: '''The Zooniverse lets everyone take part in real, cutting-edge research online in many
-        fields across the sciences, humanities, and more. There's no previous experience required;
-        just pick a project and get started right away.'''
+        content: '''The Zooniverse lets everyone take part in real, cutting-edge research in many fields across the sciences, humanities, and more. There’s no previous experience required; just pick a project and get started right away.'''
       explore:
         title: 'Explore incredible worlds'
-        content: '''Astronomical marvels, exotic wildlife in their natural habitats, original historical
-        documents—these are just a few of the fascinating things you’ll get to experience.
-        In many cases, you'll be seeing things no one has seen before.'''
+        content: '''Astronomical marvels, exotic wildlife in their natural habitats, original historical documents: just a few of the fascinating things you’ll get to experience in the Zooniverse. In many cases, you’ll literally be seeing things no one has seen before.'''
       collaborate:
         title: 'Collaborate with researchers'
-        content:'''Professional researchers and volunteers work together on our discussion boards
-        to explore and analyse project data. Much of the most exciting research produced by
-        the Zooniverse originates from these partnerships.'''
+        content:'''Professional researchers and volunteers share our discussion boards, using them to explore and analyse project data. Much of the most exciting research produced by the Zooniverse originates from these partnerships.'''
       discover:
         title: 'Discover, teach, and learn'
         content: '''Our platform offers many opportunities for education, from using projects
@@ -43,7 +35,6 @@
         Builder to create your very own Zooniverse project!'''
     featuredProjects:
       title: 'Get started on a project right now!'
-      tagline: 'These are just a few of our projects.'
       button: 'See all projects'

 module.exports = React.createClass


Removed subtle debug output from `tail.coffee`.

--- bin/tail.coffee
+++ bin/tail.coffee
@@ -75,7 +75,7 @@

     process = null
     # What remains is the exception message.
-    message = stderr.join "\n -> "
+    message = stderr.join "\n  "
     if match = /^Error:\s([\w_]+)\[(\d+)\/(\d+)\]:\s(.*)$/.exec(message)
       [ program, pid, uid, body ] = match.slice(1)
       message = "Error: #{body}"


Fixed indent regular expression.

Fixed indent regular expression.

 * The indent regular expression was confused by a string that begins with a
   blank line.

--- bin/tail.coffee
+++ bin/tail.coffee
@@ -179,7 +179,7 @@
     ).apply(exception.location)

   # Print the actual message, indented.
-  process.stdout.write "#{exception.message.replace(/^(\s*\S.*)$/mg, "  $1")}\n"
+  process.stdout.write "#{exception.message.replace(/^([^\S\n]*\S.*)$/mg, "  $1")}\n"

   # Print JSON if any exists. If there is stderr output, replace it with a message
   # to look for the output below.
@@ -189,7 +189,7 @@
     else if exception.json.stderr? and exception.json.stderr.length
       stderr = exception.json.stderr
       exception.json.stderr = "v-V-v See below. v-V-v"
-    json = inspect(exception.json, false, 1000).replace(/^(\s*\S.*)$/mg, "    $1")
+    json = inspect(exception.json, false, 1000).replace(/^([^\S\n]*\S.*)$/mg, "    $1")
     process.stdout.write "#{json}\n"
   for element in exception.stack
     if element.method
@@ -200,7 +200,7 @@
     writeExceptions(exceptions, "Nested exception")
   else if stderr
     process.stdout.write "\n  #{makeDashes("Output from stderr")}\n"
-    process.stdout.write stderr.replace(/^(\s*\S.*)$/mg, "  $1")
+    process.stdout.write stderr.replace(/^([^\S\n]*\S.*)$/mg, "  $1")

 # In case you forget, you put this in its own method to get an empty namespace.
 sendOutput = (output) ->
@@ -228,14 +228,14 @@
           else if record.json.stderr? and record.json.stderr.length
             stderr = record.json.stderr
             record.json.stderr = "v-V-v  See below. v-V-v"
-          json = inspect(record.json, false, 1000).replace(/^(\s*\S.*)$/mg, "  $1")
+          json = inspect(record.json, false, 1000).replace(/^([^\S\n]*\S.*)$/mg, "  $1")
           process.stdout.write "#{json}\n"

         if record.exceptions
           writeExceptions(record.exceptions, "Uncaught exception")
         else if stderr
           process.stdout.write "\n  #{makeDashes("Output from stderr")}\n"
-          process.stdout.write stderr.replace(/^(\s*\S.*)$/mg, "  $1")
+          process.stdout.write stderr.replace(/^([^\S\n]*\S.*)$/mg, "  $1")

 fs.open "/var/log/messages", "r", (error, fd) ->
   throw error if error


Throw error if format option is not string

Throws InvalidResponseFormatSuppliedError if the supplied format option
is not a string of either 'json' or 'xml'

--- src/index.coffee
+++ src/index.coffee
@@ -24,8 +24,9 @@
       throw new err.InvalidKeyNameSuppliedError key

 checkSuppliedFormat = (format) ->
-  if format and format.toUpperCase() not in availableFormats
-    throw new err.InvalidResponseFormatSuppliedError format
+  if format
+    if not _(format).isString() or format.toUpperCase() not in availableFormats
+      throw new err.InvalidResponseFormatSuppliedError format

 class SL
   constructor: (@keys, @format) ->


logging

logging

--- src/index.coffee
+++ src/index.coffee
@@ -13,6 +13,6 @@

 server.start (error) ->
   if error
-    console.log error
+    console.trace error
   else
     console.log 'ce-delta-hub started'


Update to pulsar-rest-api 0.1.1. Replace 'finish' with `close` event.

--- src/index.coffee
+++ src/index.coffee
@@ -31,13 +31,13 @@
     isVerbose = chat.match[1] == '-v'

     pending = pulsarApi.createJob(app, env, 'deploy:pending')
-    pending.on('finish',()->
+    pending.on('close',()->
       chat.send @data.output
       return if(@data.status != 'FINISHED')
       deploy = pulsarApi.createJob(app, env, 'deploy')
       deploy.on('create',() ->
         chat.send "Job was created: #{@}. More info here #{@data.url}"
-      ).on('finish',() ->
+      ).on('close',() ->
         chat.send "#{@} finished with status: #{@data.status}. More details here #{@data.url}"
       ).on('error', () ->
         chat.send "#{@} failed due to #{JSON.stringify(error)}"


Always trim and uppercase key for mention debouncing

--- src/index.coffee
+++ src/index.coffee
@@ -62,6 +62,7 @@
     should = yes
     return should unless @mentions?
     room = msg.message.room
+    key = key.trim().toUpperCase()

     if @mentions[room]
       if time = @mentions[room].tickets[key]


Remove invariant check for callbacks on controlled components

I don’t remember why I added this. React’s form components don’t do
this /:

--- src/index.coffee
+++ src/index.coffee
@@ -23,10 +23,6 @@
   getControllableValue: (name) -> getControllableValue name, @state, @props
   componentDidUpdate: (prevProps, prevState) ->
     for name in @controllables
-      invariant(
-        not @props[callbackName name]? or not @props[name]?,
-        "It doesn't make sense to add change callbacks for controlled properties. Either remove the callback or let it manage its own state. (#{ name })"
-      )
       newValue = @state[name]
       oldValue = getControllableValue name, prevState, prevProps
       if newValue isnt oldValue


Expose some classes

Expose some classes

--- src/index.coffee
+++ src/index.coffee
@@ -1,4 +1,9 @@
+{ContentTree, ContentPlugin} = require './core/content'
 {Environment} = require './core/environment'
+{TemplatePlugin} = require './core/templates'

-module.exports = Environment.create
+module.exports = -> Environment.create.apply(null, arguments)
 module.exports.Environment = Environment
+module.exports.ContentPlugin = ContentPlugin
+module.exports.ContentTree = ContentTree
+module.exports.TemplatePlugin = TemplatePlugin


fix: always return `false` if BODY does not exist

--- src/coffee/isvisible.coffee
+++ src/coffee/isvisible.coffee
@@ -25,7 +25,7 @@
 isVisible = (element) ->

   # don't even bother checking elements that do not exist or are not in BODY
-  unless document.body.contains element
+  unless document.body?.contains element
     return false

   # test visibility recursively for element and all its parents, until BODY


Convert Component class to use nextTick utility.

--- src/common/Component.coffee
+++ src/common/Component.coffee
@@ -5,6 +5,7 @@
 ###

 ClientViewModel = require 'stout-client/view/ClientViewModel'
+nextTick = require 'stout-client/util/nextTick'
 vars       = require '../vars'

@@ -42,7 +43,7 @@
 ###
 makeTransitionFunc = (func, transitionClass, removeClass, event, test) ->
   (t = 0, cb) ->
-    setTimeout =>
+    nextTick =>
       if @rendered and test.call @
         @prefixedClasses.remove VISIBLE_CLS, HIDDEN_CLS, removeClass
         @prefixedClasses.add TRANS_CLS, transitionClass
@@ -50,8 +51,10 @@
         @_transitionTimer = setTimeout =>
           @[func] cb
         , t
+        # Important! We have to ask for the `offsetHeight` to trigger a browser
+        # repaint.
+        x = @root.offsetHeight
         @fire event
-    , 10
     @

@@ -234,14 +237,12 @@
   # @public
   ###
   show: (cb) ->
-    setTimeout =>
-      if @rendered
-        @_stopTransition()
-        @prefixedClasses.remove HIDDEN_CLS
-        @prefixedClasses.add VISIBLE_CLS
-        @fire 'show'
-        cb?.call null
-    , 0
+    if @rendered
+      @_stopTransition()
+      @prefixedClasses.remove HIDDEN_CLS
+      @prefixedClasses.add VISIBLE_CLS
+      @fire 'show'
+      cb?.call null
     @

@@ -257,14 +258,12 @@
   # @public
   ###
   hide: (cb) ->
-    setTimeout =>
-      if @rendered
-        @_stopTransition()
-        @prefixedClasses.remove VISIBLE_CLS
-        @prefixedClasses.add HIDDEN_CLS
-        @fire 'hide'
-        cb?.call null
-    , 0
+    if @rendered
+      @_stopTransition()
+      @prefixedClasses.remove VISIBLE_CLS
+      @prefixedClasses.add HIDDEN_CLS
+      @fire 'hide'
+      cb?.call null
     @


return a cancel method

return a cancel method

--- src/jsonp.coffee
+++ src/jsonp.coffee
@@ -48,6 +48,14 @@
         # (see jQuery bugs #2709 and #4378)
         head.insertBefore script, head.firstChild

+    return
+        cancel: ->
+            window[callback] = noop
+            done = true
+            script.onload = script.onreadystatechange = null
+            script.parentNode.removeChild script if script and script.parentNode
+            script = null
+
 noop = -> undefined

 computedUrl = (params) ->


Ensure a callback function is provided

Ensure a callback function is provided

--- app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee
+++ app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee
@@ -29,7 +29,6 @@
         @enterprise.id = data
         EnterpriseImageService.configure(@enterprise)
         RegistrationService.select('about')
-        callback.call()
       ).error((data) =>
         Loading.clear()
         if data?.errors?
@@ -37,9 +36,8 @@
           alert t('failed_to_create_enterprise') + "\n" + errors.join('\n')
         else
           alert(t('failed_to_create_enterprise_unknown'))
-
-        callback.call()
       )
+      callback.call() if callback?

     update: (step) =>
       Loading.message = t('updating') + " " + @enterprise.name


Modify dialogs when waiting for secure screen validation

--- app/src/controllers/wallet/send/wallet_send_preparing_dialog_view_controller.coffee
+++ app/src/controllers/wallet/send/wallet_send_preparing_dialog_view_controller.coffee
@@ -11,14 +11,38 @@
     # fetch amount
     ledger.wallet.transaction.createAndPrepareTransaction @params.amount, 10000, @params.address, account, account, (transaction, error) =>
       return if not @isShown()
-      @once 'dismiss', =>
-        if error?
-          reason = switch error.code
-            when ledger.errors.NetworkError then 'network_no_response'
-            when ledger.errors.NotEnoughFunds then 'unsufficient_balance'
+      if error?
+        reason = switch error.code
+          when ledger.errors.NetworkError then 'network_no_response'
+          when ledger.errors.NotEnoughFunds then 'unsufficient_balance'
+        @once 'dismiss', =>
           dialog = new WalletSendErrorDialogViewController reason: reason
           dialog.show()
-        else
-          dialog = new WalletSendValidationDialogViewController transaction: transaction
-          dialog.show()
-      @dismiss()+        @dismiss()
+      else
+        invertModes = _.invert(ledger.wallet.transaction.Transaction.ValidationModes)
+        l "[PreparingTxDialog]", invertModes[transaction.getValidationMode()], transaction
+        switch transaction.getValidationMode()
+          when ledger.wallet.transaction.Transaction.ValidationModes.KEYCARD
+            @once 'dismiss', =>
+              dialog = new WalletSendValidationDialogViewController transaction: transaction
+              dialog.show()
+            @dismiss()
+          when ledger.wallet.transaction.Transaction.ValidationModes.SECURE_SCREEN
+            l "%c[M2FA] Secure screen dialog in preparation", "#888888"
+            @_requestValidation(transaction)
+
+  _requestValidation: (tx) ->
+    ledger.m2fa.validateTxOnAll(tx).fail( (error) =>
+      # return if not @isShown()
+      @once 'dismiss', =>
+        dialog = new WalletSendErrorDialogViewController(reason: error)
+        dialog.show()
+      @dismiss()
+    ).then( () =>
+      # return if not @isShown()
+      @once 'dismiss', =>
+        dialog = new WalletSendSuccessDialogViewController()
+        dialog.show()
+      @dismiss()
+    ).done()


Adds a timeout

Adds a timeout

--- apps/geo/index.coffee
+++ apps/geo/index.coffee
@@ -1,9 +1,10 @@
 express = require 'express'
+timeout = require 'connect-timeout'
 routes = require './routes'

 module.exports = app = express()

 app.set 'trust proxy', true

-app.get '/geo/ip', routes.ip
-app.get '/geo/nearest', routes.nearest+app.get '/geo/ip', timeout('25s'), routes.ip
+app.get '/geo/nearest', timeout('25s'), routes.nearest


fix #491

fix #491

--- bokehjs/src/coffee/common/plotting.coffee
+++ bokehjs/src/coffee/common/plotting.coffee
@@ -203,7 +203,7 @@

     if tools.indexOf("hover") > -1
       hover_tool = HoverTool.Collection.create(
-        renderers: (g.ref() for g in glyphs)
+        plot: plot.ref()
       )
       added_tools.push(hover_tool)


Turn off debug info while in dev

Turn off debug info while in dev

--- src/backend/channel.coffee
+++ src/backend/channel.coffee
@@ -2,7 +2,8 @@
 CoinUtils = require "#{__dirname}/coin-utils"
 rpcClient = require "#{__dirname}/jrpcClient"

-Q.longStackSupport = true
+if NODE_ENV is 'development'
+  Q.longStackSupport = true

 T1INPUT_ID_FOR_T2_T3 = 0


LEV-5 minsize in meta

LEV-5 minsize in meta

--- modules/levelcreator/app/assets/javascripts/level_creator/plugins/draw_art_cells.coffee
+++ modules/levelcreator/app/assets/javascripts/level_creator/plugins/draw_art_cells.coffee
@@ -31,6 +31,7 @@
     shadowBlur : "float"
     shadowColor : "\"rgba(0, 0, 255, 0.3)\""
     mergeSegments : "true, false (default)"
+    minSize : "Number"

   constructor : () ->


quick fixes tp 547 implementation bug

quick fixes tp 547 implementation bug

--- dkobo/koboform/static/js/xlform/XLF.view.rowDetail.coffee
+++ dkobo/koboform/static/js/xlform/XLF.view.rowDetail.coffee
@@ -93,6 +93,9 @@

 XLF.DetailViewMixins.name = XLF.DetailViewMixins.default =
   html: ->
+    @listenTo @model, "change:value", ()=>
+      @render()
+      @afterRender()
     """
     #{@model.key}: <code>#{@model.get("value")}</code>
     """


#259 reverted patch for multiple empty criteria problem

--- dkobo/koboform/static/js/xlform/view_xlformbuilder.coffee
+++ dkobo/koboform/static/js/xlform/view_xlformbuilder.coffee
@@ -156,9 +156,6 @@
     delimSelectValue = @collection.meta.get("delimSelect")
     for checkbox in delimSelect.find("input") when checkbox.value is delimSelectValue
       checkbox.checked = "checked"
-    @collection.cleanup()
-
-    #if @collection.length == 0 then @collection.add(new XLF.SkipLogicCriterion(parentRow: @parentRow))

     @collection.each (model)=>
       @$list.append(new XLF.SkipLogicCriterionView(model: model).render().$el)


Add the same test for Linter validation.

--- spec/helpers-spec.coffee
+++ spec/helpers-spec.coffee
@@ -5,3 +5,9 @@
     expect( -> Helpers.validateResults()).toThrow()
   it "should throw an exception when a String is passed.", ->
     expect( -> Helpers.validateResults('String')).toThrow()
+
+describe "The Linter Validation Helper", ->
+  it "should throw an exception when nothing is passed.", ->
+    expect( -> Helpers.validateLinter()).toThrow()
+  it "should throw an exception when a String is passed.", ->
+    expect( -> Helpers.validateLinter('String')).toThrow()


auto remove notice and alert;

auto remove notice and alert;

--- app/assets/javascripts/jobs.js.coffee
+++ app/assets/javascripts/jobs.js.coffee
@@ -1,8 +1,12 @@
 jobsonload = ->
+  remove_notice_or_alert = ->
+    $("#notice, #alert").fadeOut 200, ->
+      $(this).remove()
   $("#notice, #alert").find(".close").click (e) ->
     e.preventDefault();
-    $("#notice, #alert").fadeOut 200, ->
-      $(this).remove()
+    remove_notice_or_alert();
+  if $("#notice, #alert").length > 0
+    setTimeout remove_notice_or_alert, 3000
   $("#run_job").click (e) ->
     e.preventDefault();
     run_job = $(this)


dashboard: fixed occasional loading error

--- app/assets/javascripts/lib/app.coffee
+++ app/assets/javascripts/lib/app.coffee
@@ -144,7 +144,7 @@
   # that you call the showLoading before adding the jsonp to api_call_stack.
   showLoading: =>
     if @has_unfinished_api_calls()
-      if charts.current().get('type') != 'd3'
+      if charts.current() && charts.current().get('type') != 'd3'
         $("#charts_wrapper").busyBox({
           spinner: '<img src="/assets/layout/ajax-loader.gif" />'
         }).fadeIn('fast')


Added support to show error message of object was not able to create.

--- app/assets/javascripts/app/controllers/_application_controller_generic.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_generic.coffee
@@ -6,13 +6,13 @@

   content: =>
     @head = @pageData.object
-    controller = new App.ControllerForm(
+    @controller = new App.ControllerForm(
       model:     App[ @genericObject ]
       params:    @item
       screen:    @screen || 'edit'
       autofocus: true
     )
-    controller.form
+    @controller.form

   onSubmit: (e) ->
     params = @formParam(e.target)
@@ -39,9 +39,10 @@
           ui.callback(item)
         ui.close()

-      fail: ->
-        ui.log 'errors'
-        ui.close()
+      fail: (settings, details) ->
+        ui.log 'errors', details
+        ui.formEnable(e)
+        ui.controller.showAlert(details.error_human || details.error || 'Unable to create object!')
     )

 class App.ControllerGenericEdit extends App.ControllerModal
@@ -54,13 +55,13 @@
     @item = App[ @genericObject ].find( @id )
     @head = @pageData.object

-    controller = new App.ControllerForm(
+    @controller = new App.ControllerForm(
       model:      App[ @genericObject ]
       params:     @item
       screen:     @screen || 'edit'
       autofocus:  true
     )
-    controller.form
+    @controller.form

   onSubmit: (e) ->
     params = @formParam(e.target)
@@ -85,9 +86,10 @@
           ui.callback(item)
         ui.close()

-      fail: ->
+      fail: (settings, details) ->
         ui.log 'errors'
-        ui.close()
+        ui.formEnable(e)
+        ui.controller.showAlert(details.error_human || details.error || 'Unable to update object!')
     )

 class App.ControllerGenericIndex extends App.Controller


Improve command line args parsing

Improve command line args parsing

--- bin/run.coffee
+++ bin/run.coffee
@@ -1,64 +1,86 @@
 fs = require('fs')
-path = require ('path')
-cmdr = require('commander')
+path = require('path')
+yargs = require('yargs');
 RallyAppBuilder = require("../lib/")
-packageLocation = path.normalize(__dirname + "/../package.json")
-version = JSON.parse(fs.readFileSync(packageLocation)).version

-
-builder = (error)->
-  if(error)  then errorHandler(error)
+builder = (error) ->
+  if error
+    errorHandler error
   else
     RallyAppBuilder.build {}, errorHandler

 errorHandler = (error) ->
   if error
-    console.error(error.message)
+    console.error error.message
   else
-    console.log("Success")
-cmdr
-  .version(version)
+    console.log 'Success'

-cmdr
-  .command('init [name] [sdk_version] [server=https://rally1.rallydev.com]')
-  .description("Creates a new Rally App project template. ")
-  .action (name, sdk_version, server)->
-    console.log("Creating a new App named #{name}")
-    RallyAppBuilder.init {name, sdk_version, server}, builder
+init = (args) ->
+  {name, version, server} = args
+  name = args._[1] || name
+  sdk_version = args._[2] || version
+  server = args._[3] || server
+  console.log "Creating a new App named #{name}."
+  RallyAppBuilder.init({name, sdk_version, server}, builder)

-cmdr
-  .command('build')
-  .description("Builds the current App.")
-  .action ()->
-    console.log("Compiling the App.")
-    RallyAppBuilder.build {}, errorHandler
+build = (args) ->
+  console.log 'Compiling the App.'
+  RallyAppBuilder.build {}, errorHandler

-cmdr
-  .command('clone [organization] [repo]')
-  .description("Creates a new Rally App project locally from an existing GitHub project. ")
-  .action (organization, repo)->
-    console.log("Cloning #{repo} repo from #{organization} account")
-    if !organization
-      console.error("Please specify an organization when using the clone command.")
-      return
-    if !repo
-      console.error("Please specify a repo when using the clone command.")
-      return
-    RallyAppBuilder.clone {organization,repo}, builder
+clone = (args) ->
+  {org, repo} = args
+  org = args._[1] || name
+  repo = args._[2] || name
+  if !organization
+    console.error 'Please specify an organization when using the clone command.'
+    return
+  if !repo
+    console.error 'Please specify a repo when using the clone command.'
+    return
+  console.log "Cloning #{repo} repo from #{organization} account"
+  RallyAppBuilder.clone {organization,repo}, builder

-cmdr
-  .command('watch')
-  .description('Watch the current app files for changes and automatically rebuild it')
-  .action ()->
-    RallyAppBuilder.watch()
+watch = (args) ->
+  RallyAppBuilder.watch()

-cmdr
-  .command('run [port=1337]')
-  .description('Launch the current app in a browser')
-  .action (port) ->
-    RallyAppBuilder.run({port})
+run = (args) ->
+  {port} = args
+  port = args._[1] || port
+  RallyAppBuilder.run {port}

-if process.argv.length == 2
-  process.argv.push("build")
-
-cmdr.parse process.argv
+yargs
+  .command(
+    'init',
+    'Creates a new Rally App project template.',
+    name: {alias: 'n', default: 'MyApp'}
+    version: {alias: 'v', default: '2.0'}
+    server: {alias: 's', default: 'https://rally1.rallydev.com'}
+    , init
+  )
+  .command(
+    'build',
+    'Builds the current App.',
+    {}
+    , build
+  )
+  .command(
+    'clone',
+    'Creates a new Rally App project locally from an existing GitHub project.',
+    org: {alias: 'o'}
+    repo: {alias: 'r'}
+    , clone
+  )
+  .command(
+    'watch',
+    'Watch the current app files for changes and automatically rebuild it.',
+    {}
+    , watch
+  )
+  .command(
+    'run',
+    'Start a local server and launch the current app in the default browser.',
+    port: {alias: 'p', default: 1337}
+    , run
+  )
+  .help()
+  .argv


Fixed #572

Fixed #572


do not let invalid chars in the subdomain

--- client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
@@ -205,7 +205,12 @@
           @showSuccess domain

     else # create a subdomain
+      # replace the dots at the end if there are any
       subDomain = domainName.replace /\.{1,}(?=[^.]*$)/, ''
+      subDomainPattern = /^[a-zA-Z0-9][a-zA-Z0-9.-]+[a-zA-Z0-9]$/
+      unless subDomainPattern.test subDomain
+        createButton.hideLoader()
+        return notifyUser "#{subDomain} is an invalid subdomain."
       domainName = "#{subDomain}.#{domains.getValue()}"

       @createDomain {domainName, regYears:0}, (err, domain)=>


- updated processors

- updated processors

--- src/crystal/generate.coffee
+++ src/crystal/generate.coffee
@@ -205,33 +205,56 @@
 	if !imports
 		throw new Error 'No imports available for output'

-	for output in outputs
+	for output_i of outputs
+		output = outputs[output_i]
+
 		# validate generator
 		if !output.generator
 			throw new Error "Generator is required for output in project (#{project.id})"
 		else if !imports[output.generator]
 			throw new Error "Generator (#{output.generator}) does not exist for output in project (#{project.id})"

+		console.log "\nOutput ##{parseInt(output_i)+1}: #{output.generator}".bold
+
 		# load generator from imports
 		generator = imports[output.generator]

 		# load processors
-		if output.processor instanceof Array
+		output_processor = output.processor
+		generator_processor = generator.processor
+		if generator_processor
+			if typeof(output_processor) == 'string'
+				output_processor = [output_processor]
+			if typeof(generator_processor) == 'string'
+				generator_processor = [generator_processor]
+
+			if output_processor && generator_processor
+				output_processor = extend true, true, output_processor, generator_processor
+			else
+				output_processor = generator_processor
+
+		if output_processor instanceof Array
 			processors = []

-			for processor in output.processor
-				if !imports[processor] and !loaded_module.exports[processor]
+			for processor in output_processor
+				if !imports[processor]
 					throw new Error "Import does not exist for alias (#{processor})"
-
-				processors.push imports[output.processor].processor
-
-			output.processor = processors
-
-		else if typeof(output.processor) == 'string'
-			if !imports[output.processor]
-				throw new Error "Import does not exist for alias (#{output.processor})"
-
-			output.processor = [imports[output.processor].processor]
+
+				processors.push {
+					alias: processor
+					callback: imports[processor].processor
+				}
+
+			output_processor = processors
+
+		else if typeof(output_processor) == 'string'
+			if !imports[output_processor]
+				throw new Error "Import does not exist for alias (#{output_processor})"
+
+			output_processor = [{
+				alias: output_processor
+				callback: imports[output_processor].processor
+			}]

 		# load spec from file
 		spec = {}
@@ -245,7 +268,14 @@
 				spec = yaml.safeLoad fs.readFileSync(spec_filename, 'utf8')

 			# parse spec variables
-			spec = parse spec, project, output.processor
+			console.log "Processed spec.".blue
+			if project.debug
+				console.log "    - Before:".green
+				console.log JSON.stringify spec[i], null, "  "
+			spec = parse spec, project, output_processor
+			if project.debug
+				console.log "    - After:".green
+				console.log JSON.stringify spec[i], null, "  "

 		# validate spec
 		if generator.schema
@@ -308,7 +338,7 @@
 					mkdirp.sync code_dir
 				fs.writeFileSync "#{copy_filename}", fs.readFileSync "#{copy_dir}/#{code_file}"

-				console.log "- #{copy_filename}".green
+				console.log "Generated file: #{copy_filename}".green

 		# validate filename
 		generator_filename = output.filename or generator.filename
@@ -363,36 +393,25 @@
 			# get content from output
 			template = generator.template

-			if template
-				template = template.replace /([  |\t]+)?>>>[a-z_]*<<<\n?/ig, (injector) ->
-					injector_tabs = injector.match /^[\s]+>/g
-					if injector_tabs
-						injector_tabs = injector_tabs[0].substr(0, injector_tabs[0].length-1)
-					else
-						injector_tabs = ''
-					injector = injector.replace /[\s]+/g, ''
-					injector = injector.substr 3, injector.length-6
-					if injectors and injectors[injector]
-						if injectors[injector] instanceof Array
-							injected = injectors[injector].join "\n"
-						else
-							injected = fs.readFileSync ".crystal/#{injectors[injector]}", 'utf8'
-						inject = ''
-						for inj in injected.split "\n"
-							inject += "#{injector_tabs}#{inj}\n"
-						inject += ""
-					else
-						''
 			if engine
 				if iterator
-					content_spec = spec[iterator][i] or spec[iterator][file]
+					content_spec = extend true, true, {}, spec[iterator][i] or spec[iterator][file]
 					if content_spec
 						content_spec = extend true, true, content_spec, spec
 						content_spec.name = file
+					if content_spec and content_spec[iterator][file]['$injector']
+						template = inject template, injectors, false
+						template = inject template, content_spec[iterator][file]['$injector'], false
+						template = inject template, null, true
+					else
+						template = inject template, injectors, true
 					content = engine content_spec, template, helpers, old_helpers
 				else
+					if template
+						template = inject template, injectors, true
 					content = engine spec, template, helpers
 			else if template
+				template = inject template, injectors, true
 				content = template
 			else if spec
 				content = spec
@@ -448,14 +467,12 @@
 				mkdirp.sync filename.substr(0, file_last_path)
 			fs.writeFileSync filename, content

-			console.log "- #{filename}".green
+			console.log "Generated file: #{filename}".green

 			#if generator.outputs
 				#loadOutputs generator.outputs, generator.imports, project, force

 generate = (project) ->
-	console.log "Generating code from: #{this.path}"
-
 	# get project
 	project = project or this.project

@@ -489,6 +506,34 @@
 		console.log "Loading outputs...".bold
 		loadOutputs project.outputs, imports, project, this.force

+inject = (template, injectors, remove_injector = true) ->
+	template.replace /([  |\t]+)?>>>[a-z_]*<<<\n?/ig, (injector) ->
+		injector_tabs = injector.match /^[\s]+>/g
+		if injector_tabs
+			injector_tabs = injector_tabs[0].substr(0, injector_tabs[0].length-1)
+		else
+			injector_tabs = ''
+		injector = injector.replace /[\s]+/g, ''
+		injector = injector.substr 3, injector.length-6
+		if injectors and injectors[injector]
+			if injectors[injector] instanceof Array
+				injected = injectors[injector].join "\n"
+			else if (injectors[injector].substr(0,1) == '/' or injectors[injector].substr(0,2) == './') and fs.existsSync ".crystal/#{injectors[injector]}"
+				injected = fs.readFileSync ".crystal/#{injectors[injector]}", 'utf8'
+			else
+				injected = injectors[injector]
+			if remove_injector == true
+				inject_final = ''
+			else
+				inject_final = "#{injector_tabs}>>>#{injector}<<<\n"
+			for inj in injected.split "\n"
+				inject_final += "#{injector_tabs}#{inj}\n"
+			inject_final += ""
+		else if remove_injector == true
+			''
+		else
+			"#{injector_tabs}>>>#{injector}<<<\n"
+
 parse = (spec, project, processors) ->
 	for i of spec
 		s = spec[i]
@@ -500,7 +545,7 @@
 					spec[i] = project[s.substr(1)]
 		if processors and processors.length
 			for processor in processors
-				spec[i] = processor spec[i]
+				spec[i] = processor.callback spec[i]
 	spec

 sortObject = (object) ->


:microscope: fix a typo

:microscope: fix a typo

--- src/BrailleBuffer.coffee
+++ src/BrailleBuffer.coffee
@@ -2,7 +2,7 @@
   termap - Terminal Map Viewer
   by Michael Strassburger <codepoet@cpan.org>

-  Simple pixel to barille character mapper
+  Simple pixel to braille character mapper

   Implementation inspired by node-drawille (https://github.com/madbence/node-drawille)
   * added color support


Updated tests to match removal of view#pass.

--- test/spec/view_spec.coffee
+++ test/spec/view_spec.coffee
@@ -227,25 +227,6 @@
             expect(d["#{index}Handler"]).was.calledOnce()
           done()

-    it 'should pass model attributes to elements', ->
-      expect(view.pass).to.be.a 'function'
-      setModel()
-      view.pass 'foo', 'p'
-      view.render()
-      p = view.$('p')
-      expect(p.text()).to.be 'content'
-      model.set foo: 'bar'
-      expect(p.text()).to.be 'bar'
-
-    it 'should pass model attributes to input elements', ->
-      setModel()
-      view.$el.html('
<input type="text" id="foo">
')
-      view.pass 'foo', '#foo'
-      input = view.$('input')
-      expect(input.val()).to.be ''
-      model.set foo: 'bar'
-      expect(input.val()).to.be 'bar'
-
     it 'should add and return subviews', ->
       expect(view.subview).to.be.a 'function'


Remove unused require

Remove unused require

--- src/underscore-plus.coffee
+++ src/underscore-plus.coffee
@@ -1,5 +1,4 @@
 _ = require 'underscore'
-isEqual = require 'tantamount'

 modifierKeyMap =
   cmd: '\u2318'


Push CSRF header

Push CSRF header

--- app/assets/javascripts/boot/boot.js.coffee
+++ app/assets/javascripts/boot/boot.js.coffee
@@ -14,3 +14,6 @@
     @clearErrors()
     true

+$.ajaxSetup
+  headers:
+    'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')


Unregisterwindow resize when its not necessary

--- client/landing/app/Applications/Chat.kdapplication/Views/mainchatpanel.coffee
+++ client/landing/app/Applications/Chat.kdapplication/Views/mainchatpanel.coffee
@@ -16,8 +16,6 @@
       if visible
         @getSingleton('windowController').addLayer @
         @once 'ReceivedClickElsewhere', @bound 'hide'
-
-    @listenWindowResize()

   createConversation:(data)->
     # Data includes chatChannel and the conversation
@@ -55,7 +53,8 @@
       @setWidth 250
       @inboxMode = no

+    @listenWindowResize @inboxMode
+
   _windowDidResize:->
-    if @inboxMode
-      {winWidth} = @getSingleton('windowController')
-      @setWidth winWidth - 160
+    {winWidth} = @getSingleton('windowController')
+    @setWidth winWidth - 160


Fix missing translation in waterfall chart

Closes #3396

--- app/assets/javascripts/d3/waterfall.coffee
+++ app/assets/javascripts/d3/waterfall.coffee
@@ -166,7 +166,7 @@
       if @model.get('key') == 'future_energy_imports'
         App.settings.get("end_year")
       else
-       "#{I18n.t('output_element_series.total')}"
+       "#{I18n.t('output_element_series.labels.total')}"

     prepare_data: =>
       out = []


Don't show navigation when there are less than 2 images.

--- app/assets/javascripts/hermitage.js.coffee
+++ app/assets/javascripts/hermitage.js.coffee
@@ -511,12 +511,16 @@
     panel.fadeIn(hermitage.animationDuration)

 canShowNextAfter = (index) ->
+  return false if hermitage.images.length < 2
+
   if index < hermitage.images.length - 1
     true
   else
     hermitage.looped

 canShowPreviousBefore = (index) ->
+  return false if hermitage.images.length < 2
+
   if index > 0
     true
   else


Changed env var name and default.

Changed env var name and default.

--- src/lunch.coffee
+++ src/lunch.coffee
@@ -5,7 +5,7 @@
 #   None
 #
 # Configuration:
-#   HUBOT_MOMENT_TIMEZONE - http://momentjs.com/timezone Timezone. If not present, defaults to Europe/London.
+#   HUBOT_LUNCHTIME_TIMEZONE - http://momentjs.com/timezone Timezone. If not present, defaults to America/New_York.
 #
 # Commands:
 #   lunch -> Isn't it a little early to be talking about lunch?
@@ -18,6 +18,6 @@

 module.exports = (robot) ->
   robot.hear /lunch/i, (msg) ->
-    timezone = process.env.HUBOT_MOMENT_TIMEZONE || "Europe/London"
+    timezone = process.env.HUBOT_TIMEZONE_TIMEZONE || "America/New_York"
     isAppropriateLunchTime = moment().tz(timezone).getHours() < 12
     if isAppropriateLunchTime then msg.send "Hey #{msg.message.user.name}, isn't it a bit early to start talking about lunch?"


favorites popout bug fix

favorites popout bug fix

clicking close button would take you to home page. selector upgraded
for disambiguation.

--- app/assets/transit/lib/views/popout.coffee
+++ app/assets/transit/lib/views/popout.coffee
@@ -11,7 +11,7 @@
     'click .directions .btn-go': 'loadDirections'
     'click .directions .btn.here': 'geolocate'
     'click .options .btn-go': 'updateTrip'
-    'click .favorites a': 'navigateFavorite'
+    'click .favorites li a': 'navigateFavorite'

   render: () ->
     @options.parent.addClass('active')


Social:  rename event

Social:  rename event

--- client/Main/notificationcontroller.coffee
+++ client/Main/notificationcontroller.coffee
@@ -37,7 +37,7 @@
           @emit notification.event, notification.contents

         else
-          @emit "#{notification.event}-out-of-context", notification.contents
+          @emit "#{notification.event}-off-context", notification.contents

     @on 'ChannelUpdateHappened', (notification) =>
       @emit notification.event, notification  if notification.event


Fix trash folder icon

Fix trash folder icon

--- client/app/constants/app_constants.coffee
+++ client/app/constants/app_constants.coffee
@@ -116,6 +116,6 @@
         inboxMailbox: 'fa-inbox'
         draftMailbox: 'fa-edit'
         sentMailbox: 'fa-share-square-o'
-        trashMailbox: 'fa-trash'
+        trashMailbox: 'fa-trash-o'
         junkMailbox: 'fa-exclamation'
-        allMailbox: 'fa-archive'+        allMailbox: 'fa-archive'


:tv::put_litter_in_its_place: Updated at https://danielx.net/editor/

--- pixie.cson
+++ pixie.cson
@@ -8,7 +8,7 @@
   extensions: "distri/extensions:v0.2.0"
   "grid-gen": "distri/grid-gen:v0.2.0"
   matrix: "distri/matrix:v0.3.1"
-  observable: "distri/observable:v0.3.1"
+  observable: "distri/observable:v0.3.7"
   point: "distri/point:v0.2.0"
   postmaster: "distri/postmaster:v0.4.0"
   runtime: "distri/runtime:v0.3.0"


Updated the tests to test against the right text for the menu.

--- test/user/menu.spec.coffee
+++ test/user/menu.spec.coffee
@@ -21,10 +21,10 @@
     it 'renders for registration when course is not registered', ->
       sinon.stub(@props.course, 'isRegistered').returns(false)
       Testing.renderComponent( Menu, props: @props ).then ({dom}) ->
-        expect(dom.textContent).to.match(/Register for Course/)
+        expect(dom.textContent).to.match(/Register for Section/)

     it 'renders modification when registered', ->
       sinon.stub(@props.course, 'isRegistered').returns(true)
       Testing.renderComponent( Menu, props: @props ).then ({dom}) ->
-        expect(dom.textContent).to.match(/Change Course/)
+        expect(dom.textContent).to.match(/Change Section/)
         expect(dom.textContent).to.match(/Change student ID/)


update login messages

update login messages

--- frontend/app/js/controllers/auth_controller.coffee
+++ frontend/app/js/controllers/auth_controller.coffee
@@ -5,10 +5,10 @@
       return
     $auth.logout().then ->
       $mdDialog.show $mdDialog.alert()
-        .title('You have been logged out')
-        .content('Bye!')
-        .ok('Got it!')
-        duration: 3
+        .title('Logout')
+        .content('Adios!')
+        .ok('Adios!!')
+        duration: 2
       return

   $scope.login = ->
@@ -16,18 +16,18 @@
       email: $scope.email
       password: $scope.password).then(->
       $mdDialog.show $mdDialog.alert()
-        .title('You have successfully logged in')
-        .content('Welcome!')
-        .ok('Got it!')
-        duration: 3
+        .title('Login')
+        .content('Bienvenido!')
+        .ok('hecho!')
+        duration: 2
       return

     ).catch (response) ->
       $mdDialog.show $mdDialog.alert()
-        .title('Bad Email or password')
-        .content('Do you have an account?')
+        .title('Datos incorrectos!')
+        .content('La informacion ingresada es incorrecta, verifique nuevamente su nomber de usuario y password')
         .ariaLabel('Password notification')
-        .ok('Got it!')
+        .ok('trate nuevamente!')
       return
     return
   $scope.isAuthenticated = ->


Removed sync

Removed sync

--- src/extensions/model.coffee
+++ src/extensions/model.coffee
@@ -14,6 +14,7 @@
 ModelStream = require('./model_stream')
 modelEach = require('./model_each')
 modelInterval = require('./model_interval')
+DatabaseURL = require('../database_url')

 require './collection' # ensure collection extensions are loaded

@@ -92,7 +93,6 @@
     model_type::sync('destroy', query, callback)

   model_type.db = -> model_type::sync('db')
-  model_type.sync = -> model_type::sync('sync')

   ###################################
   # Backbone ORM - Convenience Functions


Add handler for adding all section and group items

This commit also prevents duplicate concepts from being added to the selected list

--- coffee/cilantro/ui/concept/columns.coffee
+++ coffee/cilantro/ui/concept/columns.coffee
@@ -16,7 +16,7 @@
         template: templates.available

         events:
-            'click button': 'triggerAdd'
+            'click .column-item-button': 'triggerAdd'

         triggerAdd: (event) ->
             event.preventDefault()
@@ -34,11 +34,25 @@

         itemView: AvailableItem

+        events:
+            'click .column-section-button': 'triggerAdd'
+
+        triggerAdd: (event) ->
+            for itemModel in @model.items.models
+                itemModel.trigger 'columns:add', itemModel

     class AvailableGroup extends index.ConceptGroup
         template: templates.availableGroup

         itemView: AvailableSection
+
+        events:
+            'click .column-group-button': 'triggerAdd'
+
+        triggerAdd: (event) ->
+            for sectionModel in @model.sections.models
+                for itemModel in sectionModel.items.models
+                    itemModel.trigger 'columns:add', itemModel

     class AvailableColumns extends index.ConceptIndex
@@ -138,10 +152,17 @@
                 if (concept = @collection.get(facet.get('concept')))
                     @addColumn(concept, false)

+        isConceptUnselected: (concept) ->
+            for facet in @view.facets.models
+                if facet.attributes.concept == concept.id
+                    return false
+
+            return true
+
         addColumn: (concept, add=true) ->
             @available.currentView.find(concept)?.disable()

-            if add
+            if add and @isConceptUnselected(concept)
                 facet = new @view.facets.model
                     concept: concept.id
                 @view.facets.add(facet)


collectionShell: listen to playback:ended event

--- coffee/src/shells/collection.shell.coffee
+++ coffee/src/shells/collection.shell.coffee
@@ -168,7 +168,7 @@
         eventhub: @eventhub
         model: shellModel

-      # TODO view.on 'playback:ended', @onShellPlaybackEnded
+      view.on 'playback:ended', @_onShellPlaybackEnded
       view

     # initialize the currently selected view
@@ -225,6 +225,9 @@
   showNext: => @showView @currentIndex + 1

+  _onShellPlaybackEnded: =>
+
+

 # uniform view to edit shell data.
 class CollectionShell.RemixView extends Shell.RemixView


Down to 28 failing tests

Down to 28 failing tests

--- src/zombie/document.coffee
+++ src/zombie/document.coffee
@@ -21,6 +21,7 @@
 # opener    - Opening window (for window.open)
 module.exports = loadDocument = (options)->
   { browser } = options
+  { history } = options
   { url }     = options
   if url && browser.site
     site  = if /^(https?:|file:)/i.test(browser.site) then browser.site else "http://#{browser.site}"
@@ -61,19 +62,18 @@
           "content-type": options.encoding || "application/x-www-form-urlencoded"

       # Proceeed to load resource ...
-      headers = headers || {}
+      headers = options.headers || {}
       # HTTP header Referer, but Document property referrer
-      if options.referrer && !header.referer
-        headers.referer ||= options.referrer
+      headers.referer ||= options.referrer || history.url || ""
       # Tell the browser we're looking for an HTML document
-      headers.accept ||= "text/html,*/*"
-
-      window._eventQueue.http method, options.url, headers: options.headers, params: options.params, target: document, (error, response)->
+      headers.accept  ||= "text/html,*/*"
+
+      window._eventQueue.http method, options.url, headers: headers, params: options.params, target: document, (error, response)->
         if response
-          options.history.updateLocation(window, response.url)
+          history.updateLocation(window, response.url)
           window._response    = response
           document._URL       = response.url
-          document._location  = options.history.location
+          document._location  = history.location

         if error
           # 4xx/5xx we get an error with an HTTP response
@@ -85,8 +85,6 @@

         document.write(response.body)
         document.close()
-
-        ###

         # Handle meta refresh.  Automatically reloads new location and counts
         # as a redirect.
@@ -97,27 +95,27 @@
         #       return browser.query("meta[http-equiv='refresh']");
         #     }
         #   });
-        handleRefresh = ->
-          refresh = document.querySelector("meta[http-equiv='refresh']")
-          if refresh
-            content = refresh.getAttribute("content")
-            match   = content.match(/^\s*(\d+)(?:\s*;\s*url\s*=\s*(.*?))?\s*(?:;|$)/i)
-            if match
-              [nothing, refresh_timeout, refresh_url] = match
-            else
-              return
-            refreshTimeout = parseInt(refresh_timeout, 10)
-            refreshURL     = refresh_url || document.location.href
-            if refreshTimeout >= 0
-              window._eventQueue.enqueue ->
-                # Count a meta-refresh in the redirects count.
-                history.replace(refreshURL)
-                # This results in a new window getting loaded
-                newWindow = history.current.window
-                newWindow.addEventListener "load", ->
-                  newWindow._response.redirects++
-        ###
-        # Document parsed and such
+        refresh = document.querySelector("meta[http-equiv='refresh']")
+        if refresh
+          content = refresh.getAttribute("content")
+          match   = content.match(/^\s*(\d+)(?:\s*;\s*url\s*=\s*(.*?))?\s*(?:;|$)/i)
+          if match
+            [nothing, refresh_timeout, refresh_url] = match
+          else
+            return
+          refreshTimeout = parseInt(refresh_timeout, 10)
+          refreshURL     = refresh_url || document.location.href
+          if refreshTimeout >= 0
+            window._eventQueue.enqueue ->
+              # Count a meta-refresh in the redirects count.
+              history.replace(refreshURL)
+              console.log(refreshURL)
+              # This results in a new window getting loaded
+              newWindow = history.current.window
+              newWindow.addEventListener "load", ->
+                newWindow._response.redirects++
+            return
+
         if document.documentElement
           browser.emit("loaded", document)
         else
@@ -223,7 +221,7 @@
   window.navigator =
     appName:        "Zombie"
     cookieEnabled:  true
-    javaEnabled:    false
+    javaEnabled:    ()-> false
     language:       browser.language
     mimeTypes:      emptySet
     noUI:           true


Remove redundant logic

Remove redundant logic

--- resources/assets/coffee/forum-posts-seek.coffee
+++ resources/assets/coffee/forum-posts-seek.coffee
@@ -72,8 +72,5 @@
       when 'previous' then currentPost - 10
       when 'next' then currentPost + 10

-    n = Math.max(1, n)
-    n = Math.min(totalPosts, n)
-
     $target.blur()
     @forum.jumpTo n


should also hide on click

should also hide on click

--- resources/assets/coffee/_classes/user-card.coffee
+++ resources/assets/coffee/_classes/user-card.coffee
@@ -22,7 +22,7 @@

   constructor: ->
     $(document).on 'mouseover', '.js-usercard', @onMouseOver
-    $(document).on 'keydown', @onKeyDown
+    $(document).on 'mousedown keydown', @handleForceHide
     $(document).on 'mouseenter', '.js-react--user-card-tooltip', @onMouseEnter
     $(document).on 'mouseleave', '.js-react--user-card-tooltip', @onMouseLeave
     $(document).on 'turbolinks:before-cache', @onBeforeCache
@@ -73,13 +73,13 @@
     $(el).qtip options

+  handleForceHide: (e) =>
+    $('.qtip--user-card').qtip('hide') if (e.keyCode == 27 || e.button == 0) && !@inCard
+
+
   onBeforeCache: =>
     @inCard = false
     window.tooltipWithActiveMenu = null
-
-
-  onKeyDown: (e) =>
-    $('.qtip--user-card').qtip('hide') if e.keyCode == 27 && !@inCard

   onMouseEnter: =>


Working

Working

--- components/main_layout/header/view.coffee
+++ components/main_layout/header/view.coffee
@@ -52,7 +52,7 @@
         success: (result) =>
           totalUnread = result.get('total_unread')
           if result.get('feed').length > 0
-            if totalUnread > 0 and (Cookies.get('notification-count') not totalUnread)
+            if totalUnread > 0 and (Cookies.get('notification-count') != totalUnread)
               bundleText = if totalUnread >= 100 then "99+" else totalUnread
               @$('.mlh-bundle-count')
                 .text("#{bundleText}")


Add alive ivar to light Model

Add alive ivar to light Model

--- src/model.coffee
+++ src/model.coffee
@@ -12,6 +12,8 @@
   PropertyAccessors.includeInto(this)

   @resetNextInstanceId: -> nextInstanceId = 1
+
+  alive: true

   constructor: (params) ->
     @assignId(params?.id)


Export Rethinkdb Errors -- Related to #1926

--- drivers/javascript/rethinkdb.coffee
+++ drivers/javascript/rethinkdb.coffee
@@ -1,6 +1,7 @@
 rethinkdb = require('./ast')
 net = require('./net')
 protobuf = require('./protobuf')
+error = require('./errors')

 # Add connect from net module
 rethinkdb.connect = net.connect
@@ -8,4 +9,7 @@
 # Add protobuf_implementation from the protobuf module
 rethinkdb.protobuf_implementation = protobuf.protobuf_implementation

+# Export Rql Errors
+rethinkdb.Error = error
+
 module.exports = rethinkdb


actually added the coffee script version, sorry

--- core/app/backbone/views/facts_view.coffee
+++ core/app/backbone/views/facts_view.coffee
@@ -10,9 +10,8 @@
     "click #create_fact_for_channel": "focusCreateFactlink"
     "click #create_fact_for_channel .input-box": "focusField"

-  template: "channels/facts"
+  template: "channels/_facts"
   initialize: (options) ->
-    @views = {}
     @addShowHideToggle "loadingIndicator", "div.loading"
     @collection.on "startLoading", @loadingIndicatorOn, this
     @collection.on "stopLoading", @loadingIndicatorOff, this


enhance: team model hook and title

enhance: team model hook and title

--- app/routes/authenticated/teams.coffee
+++ app/routes/authenticated/teams.coffee
@@ -1,5 +1,11 @@
 `import Ember from 'ember'`
+`import config from 'irene/config/environment';`
+`import ScrollTopMixin from 'irene/mixins/scroll-top'`

-AuthenticatedTeamsRoute = Ember.Route.extend()
+AuthenticatedTeamsRoute = Ember.Route.extend ScrollTopMixin,
+
+  title: "Teams"  + config.platform
+  model: (params) ->
+    @get('store').findAll('team')

 `export default AuthenticatedTeamsRoute`


operations: fix ManageNodeAffiliations control flow

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -822,7 +822,7 @@
     privilegedTransaction: (t, cb) ->
         async.series @req.affiliations.map(({user, affiliation}) =>
             (cb2) =>
-                async.series [ (cb3) =>
+                async.waterfall [ (cb3) =>
                     t.getAffiliation @req.node, user, cb3
                 , (oldAffiliation, cb3) =>
                     if oldAffiliation is affiliation


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -7,4 +7,4 @@
     activeBounds: Rectangle(0, 0, App.width, App.height)

   self.bind 'update', ->
-    +    self.destroy() unless


Housekeeping on arya.coffee

Housekeeping on arya.coffee

--- src/main/coffee/arya.coffee
+++ src/main/coffee/arya.coffee
@@ -15,12 +15,12 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #----------------------------------------------------------------------

-crypto = require 'crypto'
+crypto = require "crypto"

 NO_ERROR    = null
-RANGLE      = '»'
+RANGLE      = "»"
 TIME_TAG_RE = /^»[0-9]+$/
-TYPE_TAG    = RANGLE + 'type'
+TYPE_TAG    = RANGLE + "type"
 TYPE_TAG_RE = /^»type$/
 UUID_RE     = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
 UUID_TAG_RE = /^»[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
@@ -38,7 +38,7 @@
     @_constructors[name].__proxy = proxy

   save: (obj, sink, next) ->
-    if (not obj?) or ((typeof obj) isnt 'object') or (obj instanceof Array)
+    if (not obj?) or ((typeof obj) isnt "object") or (obj instanceof Array)
       return next new Error("cannot save #{obj}")
     NEW_CONTEXT =
       arrayList: []
@@ -53,7 +53,7 @@
       dObj = {}
       # save the type of the source object
       type = obj.constructor.name
-      dObj[TYPE_TAG] = type if type isnt 'Object' and type.length > 0
+      dObj[TYPE_TAG] = type if type isnt "Object" and type.length > 0
     # dehydrate the source object into the destination object
     for key of obj
       if obj[key]?
@@ -103,7 +103,7 @@
             else
               context[uuid] = new rObjCons()
           else
-            return next new Error("Unregisterd constructor " + dObj[TYPE_TAG]);
+            return next new Error("Unregistered constructor #{dObj[TYPE_TAG]}")
         else
           context[uuid] = {}
       # now let's rehydrate the provided JSON into the canonical object


rejigger some aloha instatiation order

rejigger some aloha instatiation order

--- scripts/views/workspace/content/aloha-edit.coffee
+++ scripts/views/workspace/content/aloha-edit.coffee
@@ -9,6 +9,7 @@
     template: () -> throw 'BUG: You need to specify a template, modelKey, and optionally alohaOptions'
     modelKey: null
     alohaOptions: null
+    content: null

     templateHelpers: () ->
       return {isLoaded: @isLoaded}
@@ -17,27 +18,14 @@
       @isLoaded = false
       @model.load().done () =>
         @isLoaded = true
+        @render()

       @listenTo @model, "change:#{@modelKey}", (model, value, options) =>
         return if options.internalAlohaUpdate
-
-        # if aloha is on then disable it
-        @$el.mahalo() if @$el.mahalo
-
-        #poke in the contents
-        @$el.empty().append(value).aloha(@alohaOptions)
+        @content = value
+        @render()

     onRender: () ->
-      # Auto save after the user has stopped making changes
-      updateModelAndSave = =>
-        alohaId = @$el.attr('id')
-        # Sometimes Aloha hasn't loaded up yet
-        # Only save when the editable has changed
-        if alohaId
-          alohaEditable = Aloha.getEditableById(alohaId)
-          editableBody = alohaEditable.getContents()
-          # Change the contents but do not update the Aloha editable area
-          @model.set(@modelKey, editableBody, {internalAlohaUpdate: true})

       # Once Aloha has finished loading enable
       @$el.addClass('disabled')
@@ -45,11 +33,12 @@
         # Wait until Aloha is started before loading MathJax.
         MathJax?.Hub.Configured()

-        @$el.removeClass('disabled')
+        # if aloha is on then disable it
+        @$el.mahalo() if @$el.mahalo

-        # Grr, the `aloha-smart-content-changed` can only be listened to globally
-        # (via `Aloha.bind`) instead of on each editable.
-        #
-        # This is problematic when we have multiple Aloha editors on a page.
-        # Instead, autosave after some period of inactivity.
-        @$el.on('blur', updateModelAndSave)
+        # reset the contents if necessary
+        @$el.empty().append(@content) if @content
+
+        # reenable everything
+        @$el.aloha(@alohaOptions)
+          .removeClass('disabled')


Meteor.loggingIn() is returning true even if the user is already logged in (albeit offline). This causes a knock-on error in Iron Router ("Route dispatch never rendered. Did you forget to call this.next() in an onBeforeAction?").

--- client/entry.coffee
+++ client/entry.coffee
@@ -33,14 +33,14 @@

   signInRequired: (router, extraCondition) ->
     extraCondition ?= true
-    unless Meteor.loggingIn()
-      if Meteor.user() and extraCondition
-        router.next()
-      else
-        Session.set('fromWhere', router.url)
-        Router.go('/sign-in')
-        Session.set('entryError', t9n('error.signInRequired'))
-        router.next()
+    # unless Meteor.loggingIn()
+    if Meteor.user() and extraCondition
+      router.next()
+    else
+      Session.set('fromWhere', router.url)
+      Router.go('/sign-in')
+      Session.set('entryError', t9n('error.signInRequired'))
+      router.next()

 @AccountsEntry = AccountsEntry


load complete object

load complete object

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -3,11 +3,15 @@
 class Factory
   build: (tag) ->
     switch tag.tag
+      when 'CustomObject'
+        Builder = SObject
       when 'fields'
         Builder = Field
       when 'picklistValues'
         Builder = PicklistValue
-      else throw new Error 'unknown tag type'
+      when 'actionOverrides'
+        Builder = ActionOverride
+      else throw new Error "unknown tag type #{tag.tag}"
     thing = new Builder()
     tag.getchildren().forEach (child) ->
       thing.set child.tag, child.text, child
@@ -15,8 +19,45 @@

 factory = new Factory()

+class SObject
+  constructor: ->
+    @fields = {}
+    @actionOverrides = {}
+  set: (field, value, tag) ->
+    switch field
+    # named children
+      when 'fields'
+        field = factory.build tag
+        @fields[field.fullName] = field
+      when 'actionOverrides'
+        action = factory.build tag
+        @actionOverrides[action.actionName] = action
+
+    # smart members
+      when 'sharingModel'
+        @sharingModel = new SharingModel value
+      when 'deploymentStatus'
+        @deploymentStatus = new DeploymentStatus value
+      when 'listViews', 'nameField', 'searchLayouts'
+        # TODO: something
+        ''
+
+    # regular members
+      when 'label'
+        @label = value
+      when 'pluralLabel'
+        @pluralLabel = value
+      when 'enableActivities'
+        @enableActivities = value is 'true'
+      when 'enableFeeds'
+        @enableFeeds = value is 'true'
+      when 'enableHistory'
+        @enableHistory = value is 'true'
+      when 'enableReports'
+        @enableReports = value is 'true'
+      else throw new Error "unknown field #{field} on custom object object"
+
 class Field
-  constructor: ->
   set: (field, value, tag) ->
     switch field
     # required
@@ -48,7 +89,13 @@
         @picklist = new Picklist tag
       when 'visibleLines'
         @visibleLines = parseInt value, 10
-      else throw new Error 'unknown field on field object'
+      else throw new Error "unknown field #{field} on field object"
+
+class DeploymentStatus
+  constructor: (@status) ->
+
+class SharingModel
+  constructor: (@model) ->

 class Type
   constructor: (@value) ->
@@ -67,6 +114,15 @@
         @fullName = value
       when 'default'
         @default = value is 'true'
-      else throw new Error 'unknown field on picklist value object'
+      else throw new Error "unknown field #{field} on picklist value object"
+
+class ActionOverride
+  set: (field, value, tag) ->
+    switch field
+      when 'actionName'
+        @actionName = value
+      when 'type'
+        @type = value
+      else throw new Error "unknown field #{field} on picklist value object"

 module.exports = factory


Added unary nodes

Added unary nodes

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -1,107 +1,131 @@
 define ->

-	# Template for nodes in the expression tree. All non-terminal nodes inherit from this.
-	class BasicNode
+    # Template for nodes in the expression tree.
+    # All non-terminal nodes inherit from this.
+    class BasicNode

-		# Make a new node.
-		#
-		# @param label [String] The label of this node.
-		# @return [BasicNode] A new BasicNode.
-		constructor: (@label) ->
+        # Make a new node.
+        #
+        # @param label [String] The label of this node.
+        # @return [BasicNode] A new BasicNode.
+        constructor: (@label) ->

-		# Return an array of children.
-		#
-		# @return [Array<BasicNode>, Array<Terminal>] An array of children of this node.
-		getChildren: ->
-			return []
+        # Return an array of children.
+        #
+        # @return [Array<BasicNode>, Array<Terminal>]
+        #   An array of children of this node.
+        getChildren: ->
+            return []

-		# Get the labels of all variables involved in this node.
-		#
-		# @return [Array<String>]
-		getAllVariables: ->
-			return []
+        # Get the labels of all variables involved in this node.
+        #
+        # @return [Array<String>]
+        getAllVariables: ->
+            return []

-		# Convert this node into a drawing node for output.
-		#
-		# @throw [Error] Not implemented.
-		toDrawingNode: ->
-			throw new Error("toDrawingNode not implemented for #{self.toString()}")
+        # Convert this node into a drawing node for output.
+        #
+        # @throw [Error] Not implemented.
+        toDrawingNode: ->
+            throw new Error("toDrawingNode not implemented for #{self.toString()}")

-		# Convert this node into a LaTeX string.
-		#
-		# @return [String] A LaTeX representation of this node.
-		toLaTeX: ->
-			return @toDrawingNode().renderLaTeX()
+        # Convert this node into a LaTeX string.
+        #
+        # @return [String] A LaTeX representation of this node.
+        toLaTeX: ->
+            return @toDrawingNode().renderLaTeX()

-		# Convert this node into a string.
-		#
-		# @return [String] A string representation of this node.
-		toString: ->
-			return @toDrawingNode().renderString()
+        # Convert this node into a string.
+        #
+        # @return [String] A string representation of this node.
+        toString: ->
+            return @toDrawingNode().renderString()

-		# Convert this node into a MathML string.
-		#
-		# @return [String] A MathML representation of this node.
-		toMathML: ->
-			@toDrawingNode().renderMathML()
+        # Convert this node into a MathML string.
+        #
+        # @return [String] A MathML representation of this node.
+        toMathML: ->
+            @toDrawingNode().renderMathML()

-		# Check if this node has the same string representation as another.
-		#
-		# @param other [BasicNode] A node to compare this node to.
-		# @return [Boolean] Whether the two nodes have the same string representation.
-		stringEqual: (other) ->
-			return other.toString() == @toString()
-
-		approx: ->
-			f = @constructor.approx
-			return @getChildren().map((c) -> c.approx()).reduce((a, b) -> f(a, b))
+        # Check if this node has the same string representation as another.
+        #
+        # @param other [BasicNode] A node to compare this node to.
+        # @return [Boolean] Whether the two nodes have the same string representation.
+        stringEqual: (other) ->
+            return other.toString() == @toString()
+
+        approx: ->
+            f = @constructor.approx
+            return @getChildren().map((c) -> c.approx()).reduce((a, b) -> f(a, b))

-	# A node with any number of children.
-	class RoseNode extends BasicNode
+    # A node with any number of children.
+    class RoseNode extends BasicNode

-		# Make a new RoseNode.
-		#
-		# @param label [String] The label of this node.
-		# @param children [Array<BasicNode>, Array<Terminal>] Optional. Children to add to this node.
-		# @return [RoseNode] A new RoseNode.
-		constructor: (label, @children=null) ->
-			unless @children?
-				@children = []
+        # Make a new RoseNode.
+        #
+        # @param label [String] The label of this node.
+        # @param children [Array<BasicNode>, Array<Terminal>]
+        #   Optional. Children to add to this node.
+        # @return [RoseNode] A new RoseNode.
+        constructor: (label, @children=null) ->
+            unless @children?
+                @children = []

-			super(label)
+            super(label)

-		# Return an array of all children of this node.
-		#
-		# @return [Array<BasicNode>, Array<Terminal>] An array of all children of this node.
-		getChildren: ->
-			@children
+        # Return an array of all children of this node.
+        #
+        # @return [Array<BasicNode>, Array<Terminal>]
+        #   An array of all children of this node.
+        getChildren: ->
+            @children

-	# A node with exactly two children, a left and a right child.
-	class BinaryNode extends BasicNode
+    # A node with exactly two children, a left and a right child.
+    class BinaryNode extends BasicNode

-			# Make a new BinaryNode.
-			#
-			# @param label [String] The label of this node.
-			# @param left [BasicNode, Terminal] The left child of this node.
-			# @param right [BasicNode, Terminal] The right child of this node.
-			# @return [BinaryNode] A new BinaryNode.
-			constructor: (@label, left, right) ->
-				@children =
-					left: left
-					right: right
+            # Make a new BinaryNode.
+            #
+            # @param label [String] The label of this node.
+            # @param left [BasicNode, Terminal] The left child of this node.
+            # @param right [BasicNode, Terminal] The right child of this node.
+            # @return [BinaryNode] A new BinaryNode.
+            constructor: (@label, left, right) ->
+                @children =
+                    left: left
+                    right: right

-			# Return an array of children.
-			#
-			# @return [Array<BasicNode>, Array<Terminal>] An array containing both children of this node.
-			getChildren: ->
-				[@children.left, @children.right]
+            # Return an array of children.
+            #
+            # @return [Array<BasicNode>, Array<Terminal>]
+            #   An array containing both children of this node.
+            getChildren: ->
+                [@children.left, @children.right]

-	return {
+    # A node with exactly one child.
+    class UnaryNode extends BasicNode

-		BasicNode: BasicNode
+            # Make a new UnaryNode.
+            #
+            # @param label [String] The label of this node.
+            # @param left [BasicNode, Terminal] The child of this node.
+            # @return [UnaryNode] A new UnaryNode.
+            constructor: (@label, @child) ->

-		RoseNode: RoseNode
+            # Return an array of children.
+            #
+            # @return [Array<BasicNode>, Array<Terminal>]
+            #   An array containing the child of this node.
+            getChildren: ->
+                [@child]

-		BinaryNode: BinaryNode
+    return {

-	}
+        BasicNode: BasicNode
+
+        RoseNode: RoseNode
+
+        BinaryNode: BinaryNode
+
+        UnaryNode: UnaryNode
+
+    }


Fix divide-by-zero error

Fix divide-by-zero error

--- scripts/controllers/AxesMarker.coffee
+++ scripts/controllers/AxesMarker.coffee
@@ -76,11 +76,17 @@
 	getIntersection: =>
 		points = @marking.points().all()

-		grad1 = (points[0].y - points[1].y) / (points[0].x - points[1].x)
-		grad2 = (points[2].y - points[3].y) / (points[2].x - points[3].x)
+		grads = [
+			(points[0].y - points[1].y) / (points[0].x - points[1].x)
+			(points[2].y - points[3].y) / (points[2].x - points[3].x)
+		]

-		interX = ((points[2].y - points[0].y) + (grad1 * points[0].x - grad2 * points[2].x)) / (grad1 - grad2)
-		interY = grad1 * (interX - points[0].x) + points[0].y
+		# Check for divide-by-zero errors.
+		for grad, i in grads
+			if isNaN(grad) or grad is Infinity or grad is -Infinity then grads[i] = 0.001
+
+		interX = ((points[2].y - points[0].y) + (grads[0] * points[0].x - grads[1] * points[2].x)) / (grads[0] - grads[1])
+		interY = grads[0] * (interX - points[0].x) + points[0].y

 		x: interX, y: interY


Migrate danbooru module

Migrate danbooru module

--- modules/danbooru.coffee
+++ modules/danbooru.coffee
@@ -1,65 +1,44 @@
-{ type } = Core.db
-
-danbooru = require('request-promise').defaults {
-  baseUrl: 'https://danbooru.donmai.us/'
-  auth:
-    user: process.env.DANBOORU_LOGIN
-    pass: process.env.DANBOORU_API_KEY
-  simple: true
-}
-
-safebooru = require('request-promise').defaults {
-  baseUrl: 'https://safebooru.donmai.us/'
-  auth:
-    user: process.env.DANBOORU_LOGIN
-    pass: process.env.DANBOORU_API_KEY
-  simple: true
-}
-
-Waifu = Core.db.createModel 'Waifu', {
-  id: type.string()
-  user: type.string()
-  waifu: type.string()
-}
+# APIs
+danbooru = { baseUrl: 'https://danbooru.donmai.us/', simple: true }
+safebooru = { baseUrl: 'https://safebooru.donmai.us/', simple: true }
+# Authenticate requests if there's an API key present
+danbooru.auth = safebooru.auth = {
+  user: process.env.DANBOORU_LOGIN
+  pass: process.env.DANBOORU_API_KEY
+} if process.env.DANBOORU_API_KEY
+# request-promise instances
+danbooru = require('request-promise').defaults danbooru
+safebooru = require('request-promise').defaults safebooru

 class DanbooruModule extends BotModule
   init: =>
     { @prefix } = @engine.settings

-    @registerCommand 'danbooru', {
-      allowDM: true
-      aliases: ['d', 'safebooru', 'safe']
-      includeCommandNameInArgs: true
-    }, (msg, args, d)=>
+    @registerCommand 'danbooru', { allowDM: true, aliases: ['d'] }, (msg, tags, d)=>
       return unless d.data.allowImages
-      switch args[0]
-        when 'danbooru', 'd'
-          host = 'danbooru.donmai.us'
-          b = danbooru
-        when 'safebooru', 'safe'
-          host = 'safebooru.donmai.us'
-          b = safebooru
-      tags = args[1]
-      # Rate limiting
-      if d.danbooruDate and (new Date() - d.danbooruDate) < 3000
-        return msg.reply 'Rate limit excedeed. Wait a few seconds.'
-      d.danbooruDate = new Date()
+      b = danbooru
       # NSFW Filter
       b = safebooru unless d.data.allowNSFW or msg.channel.name.indexOf('nsfw') >= 0
-      try
-        r = await b.get('posts.json', { json: true, qs: { random: true, tags } })
-        throw 'No results' unless r.length
-      catch
-        # the random option of the Danbooru API doesn't work sometimes
-        try r = await b.get('posts.json', { json: true, qs: { tags } })
-        catch error
-          Core.log error, 2
-          msg.reply 'Something went wrong.'
+      # Make the search
+      r = await b.get('posts.json', { json: true, qs: { random: true, tags } })
       return msg.reply 'No results.' unless r.length
+      # Send the picture
       msg.reply '', false, {
         title: '[click for sauce]'
-        url: "https://#{host}/posts/#{r[0].id}"
-        image: { url: "https://#{host + r[0].file_url}" }
+        url: "https://danbooru.donmai.us/posts/#{r[0].id}"
+        image: { url: "https://danbooru.donmai.us#{r[0].file_url}" }
+      }
+
+    @registerCommand 'safebooru', { allowDM: true, aliases: ['safe'] }, (msg, tags, d)=>
+      return unless d.data.allowImages
+      # Make the search
+      r = await safebooru.get('posts.json', { json: true, qs: { random: true, tags } })
+      return msg.reply 'No results.' unless r.length
+      # Send the picture
+      msg.reply '', false, {
+        title: '[click for sauce]'
+        url: "https://safebooru.donmai.us/posts/#{r[0].id}"
+        image: { url: "https://safebooru.donmai.us#{r[0].file_url}" }
       }

     @registerCommand 'setwaifu', { allowDM: true, aliases: ['sw'] }, (msg, args, d)=>
@@ -70,38 +49,25 @@
         # Do a dummy search to check if the tag is valid
         r = await safebooru.get('/posts.json', { json: true, qs: { tags: 'solo ' + waifu } })
         return msg.reply 'Invalid safebooru tag.' unless r.length
-        # Check the DB for an existent waifu entry for the current user
-        w = (await Waifu.filter({ user: msg.author.id }).run())[0]
-        # If none, create a new entry
-        w = new Waifu { user: msg.author.id } unless w?
-        w.waifu = waifu
-        await w.save()
-        msg.reply 'Success.'
+        # Save the new waifu to the DB
+        await Core.data.set("UserWaifu:#{msg.author.id}", waifu)
+        msg.reply 'Success!'
       catch error
         Core.log error, 2
         msg.reply 'Something went wrong.'

     @registerCommand 'waifu', { allowDM: true, aliases: ['w'] }, (msg, args, d)=>
       return unless d.data.allowWaifus and d.data.allowImages
-      # Rate limiting
-      if d.danbooruDate and (new Date() - d.danbooruDate) < 3000
-        return msg.reply 'Rate limit excedeed. Wait a few seconds.'
-      d.danbooruDate = new Date()
       try
         # Get the waifu entry for the current user
-        w = (await Waifu.filter({ user: msg.author.id }).run())[0]
-        return msg.reply "Run the #{@prefix}setWaifu command first." if not w?
+        waifu = await Core.data.get("UserWaifu:#{msg.author.id}")
+        return msg.reply "Run the #{@prefix}setWaifu command first." if not waifu?
         # Make a safebooru search
-        try
-          r = await safebooru.get('/posts.json', {
-            json: true, qs: { random: true, tags: 'solo ' + w.waifu }
-          })
-          throw 'No results' unless r.length
-        catch
-          r = await safebooru.get('/posts.json', {
-            json: true, qs: { tags: 'solo ' + w.waifu }
-          })
+        r = await safebooru.get('/posts.json', {
+          json: true, qs: { random: true, tags: 'solo ' + waifu }
+        })
         return msg.reply 'No results.' unless r.length
+        # Send the picture
         msg.reply '', false, {
           title: '[click for sauce]'
           url: "https://safebooru.donmai.us/posts/#{r[0].id}"


fixing test.html

fixing test.html

--- browser.coffee
+++ browser.coffee
@@ -22,11 +22,11 @@
   xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr
   xhr.onreadystatechange = ->
     if xhr.readyState is 4
-      if xhr.status is 200
+      if xhr.status in [0, 200]
         CoffeeScript.run xhr.responseText
       else
         throw new Error "Could not load #{url}"
-      callback() if callback
+      callback() if callback
   xhr.send null

 # Activate CoffeeScript in the browser by having it compile and evaluate