Change insert to save

Change insert to save

--- coffee/collection.coffee
+++ coffee/collection.coffee
@@ -32,7 +32,7 @@
   return res.send 400, 'Missing Request Payload' if Object.keys(req.body).length is 0
   # @TODO fix required fields
   # @TODO check _id if existing
-  req.col.insert req.body, safe: true, (err, doc) ->
+  req.col.save req.body, safe: true, (err, doc) ->
     return next(err) if err
     return res.json 201, documents: doc, count: doc.length


main.coffee: Remove redundant setting

main.coffee: Remove redundant setting

This settings is overriden when the next setting arg is given
to coala. Hence, let's remove it.

--- lib/main.coffee
+++ lib/main.coffee
@@ -19,7 +19,6 @@
         filePath = textEditor.getPath()
         parameters = []
         parameters.push('--find-config')
-        parameters.push('-S=autoapply=false')
         parameters.push('--files=' + filePath)
         parameters.push('--settings')
         parameters.push("format_str=" +


Fix undo, toggling added a non-op to history

Returning undefined from mutateSelection() fixes this

--- lib/main.coffee
+++ lib/main.coffee
@@ -47,6 +47,7 @@
       @registerCommand()
       mutateSelection: (selection)->
         toggleLines selection.editor, getLines selection.getBufferRange()
+        return

     TextObject = Base.getClass "TextObject"
     class Hunk extends TextObject


Remove dead code for activation

Remove dead code for activation

--- lib/main.coffee
+++ lib/main.coffee
@@ -110,7 +110,6 @@
         atom.config.unset('autocomplete-plus.maxSuggestions')

     @getAutocompleteManager()
-    # @activateTimeout = setTimeout(@getAutocompleteManager, 0)

   # Public: Cleans everything up, removes all AutocompleteManager instances
   deactivate: ->
@@ -118,13 +117,10 @@
     @autocompleteManager = null

   getAutocompleteManager: ->
-    if @activateTimeout?
-      clearTimeout(@activateTimeout)
-      @activateTimeout = null
-    return @autocompleteManager if @autocompleteManager?
-    AutocompleteManager = require('./autocomplete-manager')
-    @autocompleteManager = new AutocompleteManager()
-    return @autocompleteManager
+    unless @autocompleteManager?
+      AutocompleteManager = require('./autocomplete-manager')
+      @autocompleteManager = new AutocompleteManager()
+    @autocompleteManager

   ###
   Section: Provider API


handle aborted key bindings

handle aborted key bindings

fixes #8

--- lib/main.coffee
+++ lib/main.coffee
@@ -43,16 +43,17 @@
       @registerHelper command, "./commands"

   registerCommand: (cmd, path, options = {}) ->
-    atom.workspaceView.command "markdown-writer:#{cmd}", =>
-      return unless options.optOutGrammars or @isMarkdown()
+    atom.workspaceView.command "markdown-writer:#{cmd}", (e) =>
+      unless options.optOutGrammars or @isMarkdown()
+        return e.abortKeyBinding()

       CmdModule[path] ?= require(path)
       cmdInstance = new CmdModule[path](options.args)
       cmdInstance.display()

   registerHelper: (cmd, path) ->
-    atom.workspaceView.command "markdown-writer:#{cmd}", =>
-      return unless @isMarkdown()
+    atom.workspaceView.command "markdown-writer:#{cmd}", (e) =>
+      return e.abortKeyBinding() unless @isMarkdown()

       CmdModule[path] ?= require(path)
       CmdModule[path].trigger(cmd)


#update #14

#update #14

--- lib/main.coffee
+++ lib/main.coffee
@@ -24,7 +24,7 @@

 flashPath = packageFile.flash.path
 flashVersion = packageFile.flash.version
-if process.platform != 'win32'
+if process.platform == 'win32'
   flashPath = "C:\\Users\\wii\\AppData\\Local\\Google\\Chrome\\User\ Data\\PepperFlash\\19.0.0.226\\pepflashplayer.dll"

 console.log "use ppapi-flash-path: #{flashPath}"


Defend against exceptions during exception reporting

I’m trying to be extra cautious here since I’ve introduced a new code
path. I don’t want secondary exceptions to mask the originals or cause
any extra problems.

--- lib/main.coffee
+++ lib/main.coffee
@@ -14,13 +14,23 @@
       Reporter ?= require './reporter'
       Reporter.send(message, url, line, column, originalError)

-      NewReporter ?= require './new-reporter'
-      NewReporter.reportUncaughtException(originalError)
+      try
+        NewReporter ?= require './new-reporter'
+        NewReporter.reportUncaughtException(originalError)
+      catch secondaryException
+        try
+          console.error "Error reporting uncaught exception", secondaryException
+          NewReporter.reportUncaughtException(secondaryException)

     if atom.onDidFailAssertion?
       @subscriptions.add atom.onDidFailAssertion (error) ->
-        NewReporter ?= require './new-reporter'
-        NewReporter.reportFailedAssertion(error)
+        try
+          NewReporter ?= require './new-reporter'
+          NewReporter.reportFailedAssertion(error)
+        catch secondaryException
+          try
+            console.error "Error reporting assertion failure", secondaryException
+            NewReporter.reportUncaughtException(secondaryException)

   deactivate: ->
     @subscriptions?.dispose()


Fix stupid lint issues.

Fix stupid lint issues.

--- lib/test.coffee
+++ lib/test.coffee
@@ -48,9 +48,10 @@
     (res)->
         mapSrc = res.text.substr(4)
         map = JSON.parse(mapSrc)
-        map.sections.length.should.equal
+        map.sections.length.should.equal(
             expected.sections.length,
             'Sourcemap sections match.'
+        )
         false

 describe "DS Asset Middleware", ->


Added response headers. Closes issue #58.

--- lib/test.coffee
+++ lib/test.coffee
@@ -80,6 +80,9 @@
     assert.isNull error
     assert.isNotNull response, 'Response'

+    # Headers
+    @response.headers = response.headers
+
     # Status code
     assert.equal response.statusCode, @response.status, """
       Got unexpected response code:


More verbose debugging output

More verbose debugging output

--- lib/util.coffee
+++ lib/util.coffee
@@ -4,16 +4,22 @@
 FS = require('fs')

 module.exports = Util =
-  debug: (message) ->
+  debug: (messages...) ->
     if atom.config.get('haskell-ghc-mod.debug')
-      console.log "haskell-ghc-mod debug: #{message}"
+      console.log "haskell-ghc-mod debug:", messages...
+      console.trace "haskell-ghc-mod trace:"

   getRootDir: (buffer) ->
     [dir] = atom.project.getDirectories().filter (dir) ->
       dir.contains(buffer.getUri())
-    dir ? atom.project.getDirectories()[0] ? buffer.file?.getParent?() ? new Directory
+    res = dir ? atom.project.getDirectories()[0] ? buffer.file?.getParent?() ? new Directory
+    Util.debug "getRootDir path = #{res.getPath()}",
+      "atom.project.getDirectories()[0] = #{atom.project.getDirectories()[0]?.getPath?()}",
+      "buffer.file?.getParent?() = #{buffer.file?.getParent?()?.getPath?()}"
+    return res

   getProcessOptions: (rootPath) ->
+    Util.debug "getProcessOptions(#{rootPath})"
     env = {}
     for k, v of process.env
       env[k] = v


Fix an error where tabUnshift would fail

This happens due to error relating to
file different from current buffer.

I have no solution save for reading file
from disk, and I'd prefer to avoid that,
so this is just to prevent crashing.

--- lib/util.coffee
+++ lib/util.coffee
@@ -196,6 +196,7 @@
     columnl = 0
     columnr = point.column
     while(columnl < columnr)
+      break unless line? and line[columnl]?
       if line[columnl] is '\t'
         columnr -= 7
       columnl += 1


Show error notifications when failed to compile to file

Fixes #33

--- lib/util.coffee
+++ lib/util.coffee
@@ -64,13 +64,13 @@
       text     = @compile editor.getText(), editor
       destPath = fsUtil.resolvePath editor.getPath()

-      # Fail silently with just console error when destination is not in project
       unless atom.project.contains(destPath)
-        console.error "Cannot write files outside of project root"
-        return
+        atom.notifications.addError "Compile-compile: Failed to compile to file",
+          detail: "Cannot write outside of project root"

       destPath = fsUtil.toExt destPath, 'js'
       fsUtil.writeFile destPath, text, callback

     catch e
-      console.error "Coffee-compile: #{e.stack}"
+      atom.notifications.addError "Compile-compile: Failed to compile to file",
+        detail: e.stack


Set autoHeight to true when creating TextEditor

--- lib/util.coffee
+++ lib/util.coffee
@@ -92,7 +92,7 @@
     @compileToFile(sourceEditor) if shouldWriteToFile

   buildCoffeeCompileEditor: (sourceEditor) ->
-    previewEditor = atom.workspace.buildTextEditor()
+    previewEditor = atom.workspace.buildTextEditor(autoHeight: true)

     shouldCompileToFile = sourceEditor? and fsUtil.isPathInSrc(sourceEditor.getPath()) and
       pluginManager.isEditorLanguageSupported(sourceEditor, true)


Only resize on ready

Only resize on ready

--- scripts/players.coffee
+++ scripts/players.coffee
@@ -275,6 +275,7 @@

     window.resizeMedia = (width, height) ->
         return if not isVideo
+        return if not ready
         console.log(width, height)
         media.width = width
         media.height = height - 5


Tweak to weather script

Tweak to weather script

--- scripts/weather.coffee
+++ scripts/weather.coffee
@@ -58,8 +58,7 @@
       else
         celsius = result.currently.temperature
         fahrenheit = celsius * (9 / 5) + 32
-      response = "Currently: #{result.currently.summary} (#{fahrenheit}°F/"
-      response += "#{celsius}°C). "
+      response = "Currently: #{result.currently.summary} (#{celsius}°C)"
       response += "Today: #{result.hourly.summary} "
       response += "Coming week: #{result.daily.summary}"
       cb response


fix syntax/group input

fix syntax/group input

--- server/kcServer.coffee
+++ server/kcServer.coffee
@@ -8,6 +8,7 @@
 	wss: require('websocket').server
 	sha: require('sha1')
 	aes: require('aes256')
+	readline: require('readline')

 # Initialize modules
 WebServer = https.createServer
@@ -81,14 +82,7 @@
 	toJSON: () ->
 		id: @id
 		passphrase: @passphrase
-		members: x.toJSON() for x in @members
-
-User.fromJSON = (jsonExpression) -> new User(jsonExpression.id, jsonExpression.powerLevel, jsonExpression.auth)
-Group.fromJSON = (jsonExpression) -> new Group(jsonExpression.id, User.fromJSON userExp for userExp in jsonExpression.members, jsonExpression.passphrase)
-
-# Set up variables
-userRegistry.push User.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "users.json"
-groupRegistry.push Group.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "groups.json"
+		members: x.id for x in @members

 # Set up common functions
 getUserById = (id) ->
@@ -99,6 +93,14 @@
 	for group in groupRegistry
 		return group if group.id is id

+User.fromJSON = (jsonExpression) -> new User(jsonExpression.id, jsonExpression.powerLevel, jsonExpression.auth)
+Group.fromJSON = (jsonExpression) -> new Group(jsonExpression.id, getUserById userExp for userExp in jsonExpression.members, jsonExpression.passphrase)
+
+# Set up variables
+userRegistry.push User.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "users.json"
+groupRegistry.push Group.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "groups.json"
+
+
 containsUser = (id) -> (getUserById id)?
 containsGroup = (id) -> (getGroupById id)?

@@ -106,11 +108,13 @@
 eventHandlers = [
 	{
 		expectedType: "dm"
+		expectedArgs: ["dest"]
 		onMessage: (msg, source) ->

 	}
 ]
-setupHandlersForUser
+setupHandlersForUser = (conn, id) ->
+

 # Login procedure
 WSServer.on 'request', (req) ->


1. 修复无法下载的问题

1. 修复无法下载的问题

--- lib/package/package-center.coffee
+++ lib/package/package-center.coffee
@@ -36,7 +36,7 @@
     @callback = params.callback

   onInstallButtonClick: ->
-    @callback this, @data.repo if @callback
+    @callback this, @data.repository_url if @callback
   #标识正在下载
   setInstalling: (install) ->
     @installButton.attr('class', 'btn btn-info icon icon-cloud-download install-button ' + if install then 'is-installing disabled')
@@ -181,6 +181,8 @@
         }).then (destPath)->
           console.log 'package pull finished'
           callback(destPath) if callback
+      .catch (error)->
+       callback(destPath) if callback

     getTitle: ->


Add image load events

Add image load events

--- framer/Events.coffee
+++ framer/Events.coffee
@@ -28,6 +28,10 @@
 # Scroll events
 Events.Scroll = "scroll"

+# Image events
+Events.ImageLoaded = "load"
+Events.ImageLoadError = "error"
+
 # Extract touch events for any event
 Events.touchEvent = (event) ->
 	touchEvent = event.touches?[0]


add show on value example

add show on value example

--- site/coffee/app.coffee
+++ site/coffee/app.coffee
@@ -14,6 +14,7 @@
       .when('/demo/simple', templateUrl: 'demo/simple.html')
       .when('/demo/post', templateUrl: 'demo/post.html')
       .when('/demo/wizard', templateUrl: 'demo/wizard.html')
+      .when('/demo/show', templateUrl: 'demo/show.html')
 )

 angular.module('app')


Revert "Fixed windows trunkation of files."

This reverts commit 28fb4426f047f2d1a6bc6e52557b6c227702c7ab.

--- lib/coo/builder.coffee
+++ lib/coo/builder.coffee
@@ -1,306 +1,305 @@
-fs = require 'fs'
-path = require 'path'
-crypto = require 'crypto'
-XRegExp = require('xregexp').XRegExp
-mkdirp = require 'mkdirp'
-walkdir = require 'walkdir'
-_ = require 'underscore'
-util = require 'util'
-growl = require 'growl'
-# Compilers
-Coco = require('./compiler/coco').Coco
-CoffeeScript = require('./compiler/coffee-script').CoffeeScript
-Contracts = require('./compiler/contracts').Contracts
-IcedCoffeeScript = require('./compiler/iced-coffee-script').IcedCoffeeScript
-Kaffeine = require('./compiler/kaffeine').Kaffeine
-Move = require('./compiler/move').Move
-Sibilant = require('./compiler/sibilant').Sibilant
-Roy = require('./compiler/roy').Roy
-Haml = require('./compiler/haml').Haml
-Jade = require('./compiler/jade').Jade
-Markdown = require('./compiler/markdown').Markdown
-Less = require('./compiler/less').Less
-Stylus = require('./compiler/stylus').Stylus
-# Processors
-UglifyJs = require('./processor/uglify-js').UglifyJs
-CleanCss = require('./processor/clean-css').CleanCss
-
-generateCompilerConfig = (compiler, extension, target) ->
-  path: "**.#{extension}"
-  compiler: compiler
-  compile: true
-  targetFile: (file) ->
-    repRegex = new RegExp "(.#{target})?\\.#{extension}$"
-    if @compile then file.replace repRegex, ".#{target}" else file
-
-exports.Builder = class Builder
-  sourcePath: null
-  tmpPath: null
-  outputPath: null
-  compileDir: 'compiled'
-  srcHashDb: 'src-hash'
-  compileHashDb: 'compile-hash'
-  buildFileHashDb: 'build-file-hash'
-  copyFileHashDb: 'copy-file-hash'
-  defaultVersion: 'development'
-  compileConfigTree: {}
-  postConfigTree: {}
-  fileConfigTree: {}
-  # Versions are created in the output path, the first version is build default
-  versions:
-    development:
-      postProcess: false
-    production: {}
-  files: {}
-  compilePaths: [
-    {
-      # Default config
-      path: '**'
-      targetFile: (file) -> file
-    }
-    # JS compilers
-    generateCompilerConfig new CoffeeScript, 'coffee', 'js'
-    generateCompilerConfig new Contracts, 'contracts', 'js'
-    generateCompilerConfig new IcedCoffeeScript, 'iced', 'js'
-    generateCompilerConfig new Coco, 'co', 'js'
-    generateCompilerConfig new Kaffeine, 'k', 'js'
-    generateCompilerConfig new Move, 'mv', 'js'
-    generateCompilerConfig new Sibilant, 'sibilant', 'js'
-    generateCompilerConfig new Roy, 'roy', 'js'
-    # HTML compilers
-    generateCompilerConfig new Haml, 'haml', 'html'
-    generateCompilerConfig new Jade, 'jade', 'html'
-    generateCompilerConfig new Markdown, 'md', 'html'
-    # CSS compilers
-    generateCompilerConfig new Less, 'less', 'css'
-    generateCompilerConfig new Stylus, 'styl', 'css'
-  ]
-  postPaths: [
-    {
-      # Mangle and compress JS
-      path: '**.js'
-      process: true
-      processors: [
-        new UglifyJs
-      ]
-    }
-    {
-      # Compress CSS
-      path: '**.css'
-      process: true
-      processors: [
-        new CleanCss
-      ]
-    }
-  ]
-  # Load in a config file and run the config's 'config' function for this
-  loadConfig: (file) =>
-    require(file).config this
-  # Create a file builder given options
-  buildFile: (file, options) ->
-    @files[file] = options
-  # Set pre compile config for a path
-  compileConfig: (p, options) ->
-    options.path = p
-    @compilePaths.push options
-  # Set post compile config for a path
-  postConfig: (p, options) ->
-    options.path = p
-    @postPaths.push options
-  parseWildcardPath: (p) ->
-    escaped = p.split('*').map( (s) -> XRegExp.escape(s)).join '*'
-    converted = escaped.replace('**', '.*').replace /(.?)(.?)\*/g, ($0, $1, $2) ->
-      if $2 isnt '.' or $1 isnt '\\' then $0 else "#{$1}#{$2}[^/\\\\]*"
-    new RegExp "^#{converted}$"
-  # Check if a path (needle) matches another string, RegExp or array of paths
-  matchPath: (needle, haystack) ->
-    # Array case
-    if _.isArray haystack
-      for hs in haystack
-        return true if @matchPath needle, hs
-      return false
-    # Single case
-    haystack = @parseWildcardPath haystack unless _.isRegExp haystack
-    return haystack.test(needle)
-  # Build the source tree
-  setRootPath: (p) ->
-    @sourcePath = path.join p, 'src'
-    @tmpPath = path.join p, 'tmp'
-    @outputPath = path.join p, 'build'
-  build: (version) ->
-    version = @defaultVersion unless version?
-    throw "#{version} is not a valid version name" unless @versions[version]?
-    @compile version unless @versions[version].compile is false
-    @buildFiles version unless @versions[version].buildFiles is false
-    @postProcess version unless @versions[version].postProcess is false
-  compile: (version) ->
-    version = @defaultVersion unless version?
-    throw "#{version} is not a valid version name" unless @versions[version]?
-    # Create new files when required
-    targets = []
-    srcHashes = @loadDatabase version, @srcHashDb
-    for f, conf of @generateCompileConfigTree(version)
-      sourceFile = path.join @sourcePath, f
-      targetFile = path.join @getCompilePath(version), conf.targetFile(f)
-      targets.push targetFile unless conf.ignore is true
-      unless conf.ignore
-        srcHash = @getFileHash sourceFile
-        unless path.existsSync(targetFile) and srcHashes[sourceFile] is srcHash
-          mkdirp.sync path.dirname(targetFile)
-          if conf.compile
-            console.log "Compiling #{f}..."
-            source = fs.readFileSync sourceFile, 'utf8'
-            try
-              compiled = conf.compiler.compile(source)
-            catch e
-              title = "Error while compiling #{f}"
-              errorMesage = "#{title}\n#{e.toString()}"
-              console.log errorMesage
-              growl e.toString(),
-                title: title
-                name: 'coo'
-              continue
-            fs.writeFileSync targetFile, compiled
-          else
-            console.log "Copying #{f}..."
-            fs.writeFileSync targetFile, fs.readFileSync(sourceFile)
-          srcHashes[sourceFile] = srcHash
-          @saveDatabase version, @srcHashDb, srcHashes
-    # Prune orphaned files
-    buildFileNames = []
-    for bf in _.keys @files
-      buildFileNames.push path.join(@getCompilePath(version), bf)
-    walkdir.sync @getCompilePath(version), (f, stat) ->
-      return if stat.isDirectory()
-      f = path.normalize(f)
-      if targets.indexOf(f) is -1 and buildFileNames.indexOf(f) is -1
-        console.log "Removing orphaned file #{f}..."
-        fs.unlinkSync f
-  buildFiles: (version) ->
-    version = @defaultVersion unless version?
-    throw "#{version} is not a valid version name" unless @versions[version]?
-    builtFiles = []
-    compiledFiles = []
-    cvPath = @getCompilePath version
-    buildFileHashes = @loadDatabase version, @buildFileHashDb
-    for c, f of @generateCompileConfigTree() when not f.ignore
-      compiledFiles.push f.targetFile(c)
-    for bf, bfConf of @files
-      # Check if we need update, searching sources one by one to get correct
-      # dependency order
-      bfPath = path.join cvPath, bf
-      sources = [bfConf.source] unless _.isArray bfConf.source
-      matchedFiles = []
-      matchedFileHashes = []
-      requiresBuild = false
-      for s in sources
-        for cf in compiledFiles when (matchedFiles.indexOf cf is -1) and
-        @matchPath cf, s
-          cfPath = path.join cvPath, cf
-          throw "Cannot find compiled file #{cf}" unless path.existsSync cfPath
-          matchedFiles.push cfPath
-          matchedFileHashes.push @getFileHash(cfPath)
-          builtFiles.push cf
-      buildFileExists = path.existsSync bfPath
-      if buildFileExists and matchedFiles.length is 0
-        console.log "Removing #{bf}..."
-        fs.unlinkSync bfPath
-      else if matchedFiles.length > 0
-        newMatchedFilesHash = @getHash matchedFileHashes.join()
-        unless path.existsSync(bfPath) and
-        buildFileHashes[bfPath] is newMatchedFilesHash
-          console.log "Building #{bf}..."
-          mkdirp.sync path.dirname(bfPath)
-          bfFile = fs.openSync bfPath, 'w'
-          for mf in matchedFiles
-            fs.writeSync(bfFile, fs.readFileSync(mf, 'utf8'), null)
-          buildFileHashes[bfPath] = newMatchedFilesHash
-          @saveDatabase version, @buildFileHashDb, buildFileHashes
-      compiledFiles.push bf
-    # Move files to build dir
-    copyFileHashes = @loadDatabase version, @copyFileHashDb
-    ovPath = path.join(@outputPath, version)
-    mkdirp.sync ovPath
-    buildFileNames = _.keys @files
-    walkdir.sync cvPath, (f, stat) =>
-      return if stat.isDirectory()
-      f = path.relative(cvPath, f)
-      outf = path.join ovPath, f
-      if builtFiles.indexOf(f) is -1 or buildFileNames.indexOf(f) isnt -1
-        compf = path.join cvPath, f
-        compfHash = @getFileHash compf
-        unless path.existsSync(outf) and
-        copyFileHashes[compf] is compfHash
-          # Copy
-          console.log "Outputting #{f}..."
-          mkdirp.sync path.dirname(outf)
-          fs.writeFileSync outf, fs.readFileSync(compf)
-          copyFileHashes[compf] = compfHash
-          @saveDatabase version, @copyFileHashDb, copyFileHashes
-      else if path.existsSync(outf)
-        console.log "Removing orphaned file #{f}..."
-        fs.unlinkSync outf
-    # Prune orphaned files
-    walkdir.sync ovPath, (f, stat) ->
-      return if stat.isDirectory()
-      relf = path.relative(ovPath, f)
-      unless path.existsSync(path.join(cvPath, relf))
-        console.log "Removing orphaned file #{f}..."
-        fs.unlinkSync f
-  postProcess: (version) ->
-    version = @defaultVersion unless version?
-    throw "#{version} is not a valid version name" unless @versions[version]?
-    return if @versions[version].postProcess is false
-    # Run processors
-    ovPath = path.join @outputPath, version
-    for f, c of @generatePostConfigTree(version) when not c.ignore
-      if c.process
-        p = path.join ovPath, f
-        source = fs.readFileSync p, 'utf8'
-        try
-          source = processor.process(source) for processor in c.processors
-        catch e
-          console.log "Error while processing #{f}:"
-          console.log e.toString()
-          continue
-        fs.writeFileSync p, source
-  generateCompileConfigTree: ->
-    @generateFileConfigTree @sourcePath, @compilePaths
-  generatePostConfigTree: (version) ->
-    @generateFileConfigTree path.join(@outputPath, version), @postPaths
-  generateFileConfigTree: (p, configs) ->
-    cJson = JSON.stringify configs
-    @fileConfigTree[p] = {} unless @fileConfigTree[p]?
-    @fileConfigTree[p][cJson] = {} unless @fileConfigTree[p][cJson]?
-    # Prune missing items from the tree
-    for f in _.keys @fileConfigTree[p][cJson]
-      abs = path.join @sourcePath, f
-      delete @fileConfigTree[p][cJson][f] unless path.existsSync abs
-    # Add new items to the tree
-    walkdir.sync p, (f, stat) =>
-      # Trim source dir off path
-      return if stat.isDirectory()
-      f = path.relative(p, f)
-      return if @fileConfigTree[p][cJson][f]?
-      for c in configs when @matchPath f, c.path
-        pathConf = @fileConfigTree[p][cJson][f] or {}
-        pathConf = _.defaults c, pathConf
-        @fileConfigTree[p][cJson][f] = pathConf
-    @fileConfigTree[p][cJson]
-  getHash: (data) ->
-    hash = crypto.createHash 'sha1'
-    hash.update data
-    hash.digest 'base64'
-  getFileHash: (file) -> @getHash fs.readFileSync(file, 'utf8')
-  getCompilePath: (version) -> path.join @tmpPath, version, @compileDir
-  getDatabaseFileName: (version, name) ->
-    "#{path.join(@tmpPath, version, name)}.json"
-  loadDatabase: (version, name) ->
-    file = @getDatabaseFileName version, name
-    return {} unless path.existsSync file
-    JSON.parse fs.readFileSync(file, 'utf8')
-  saveDatabase: (version, name, data) ->
-    file = @getDatabaseFileName version, name
-    fs.writeFileSync file, JSON.stringify(data)
-  # Link to top level, made accessible inside the object for configuration.
-  generateCompilerConfig: generateCompilerConfig
+fs = require 'fs'
+path = require 'path'
+crypto = require 'crypto'
+XRegExp = require('xregexp').XRegExp
+mkdirp = require 'mkdirp'
+walkdir = require 'walkdir'
+_ = require 'underscore'
+util = require 'util'
+growl = require 'growl'
+# Compilers
+Coco = require('./compiler/coco').Coco
+CoffeeScript = require('./compiler/coffee-script').CoffeeScript
+Contracts = require('./compiler/contracts').Contracts
+IcedCoffeeScript = require('./compiler/iced-coffee-script').IcedCoffeeScript
+Kaffeine = require('./compiler/kaffeine').Kaffeine
+Move = require('./compiler/move').Move
+Sibilant = require('./compiler/sibilant').Sibilant
+Roy = require('./compiler/roy').Roy
+Haml = require('./compiler/haml').Haml
+Jade = require('./compiler/jade').Jade
+Markdown = require('./compiler/markdown').Markdown
+Less = require('./compiler/less').Less
+Stylus = require('./compiler/stylus').Stylus
+# Processors
+UglifyJs = require('./processor/uglify-js').UglifyJs
+CleanCss = require('./processor/clean-css').CleanCss
+
+generateCompilerConfig = (compiler, extension, target) ->
+  path: "**.#{extension}"
+  compiler: compiler
+  compile: true
+  targetFile: (file) ->
+    repRegex = new RegExp "(.#{target})?\\.#{extension}$"
+    if @compile then file.replace repRegex, ".#{target}" else file
+
+exports.Builder = class Builder
+  sourcePath: null
+  tmpPath: null
+  outputPath: null
+  compileDir: 'compiled'
+  srcHashDb: 'src-hash'
+  compileHashDb: 'compile-hash'
+  buildFileHashDb: 'build-file-hash'
+  copyFileHashDb: 'copy-file-hash'
+  defaultVersion: 'development'
+  compileConfigTree: {}
+  postConfigTree: {}
+  fileConfigTree: {}
+  # Versions are created in the output path, the first version is build default
+  versions:
+    development:
+      postProcess: false
+    production: {}
+  files: {}
+  compilePaths: [
+    {
+      # Default config
+      path: '**'
+      targetFile: (file) -> file
+    }
+    # JS compilers
+    generateCompilerConfig new CoffeeScript, 'coffee', 'js'
+    generateCompilerConfig new Contracts, 'contracts', 'js'
+    generateCompilerConfig new IcedCoffeeScript, 'iced', 'js'
+    generateCompilerConfig new Coco, 'co', 'js'
+    generateCompilerConfig new Kaffeine, 'k', 'js'
+    generateCompilerConfig new Move, 'mv', 'js'
+    generateCompilerConfig new Sibilant, 'sibilant', 'js'
+    generateCompilerConfig new Roy, 'roy', 'js'
+    # HTML compilers
+    generateCompilerConfig new Haml, 'haml', 'html'
+    generateCompilerConfig new Jade, 'jade', 'html'
+    generateCompilerConfig new Markdown, 'md', 'html'
+    # CSS compilers
+    generateCompilerConfig new Less, 'less', 'css'
+    generateCompilerConfig new Stylus, 'styl', 'css'
+  ]
+  postPaths: [
+    {
+      # Mangle and compress JS
+      path: '**.js'
+      process: true
+      processors: [
+        new UglifyJs
+      ]
+    }
+    {
+      # Compress CSS
+      path: '**.css'
+      process: true
+      processors: [
+        new CleanCss
+      ]
+    }
+  ]
+  # Load in a config file and run the config's 'config' function for this
+  loadConfig: (file) =>
+    require(file).config this
+  # Create a file builder given options
+  buildFile: (file, options) ->
+    @files[file] = options
+  # Set pre compile config for a path
+  compileConfig: (p, options) ->
+    options.path = p
+    @compilePaths.push options
+  # Set post compile config for a path
+  postConfig: (p, options) ->
+    options.path = p
+    @postPaths.push options
+  parseWildcardPath: (p) ->
+    escaped = p.split('*').map( (s) -> XRegExp.escape(s)).join '*'
+    converted = escaped.replace('**', '.*').replace /(.?)(.?)\*/g, ($0, $1, $2) ->
+      if $2 isnt '.' or $1 isnt '\\' then $0 else "#{$1}#{$2}[^/\\\\]*"
+    new RegExp "^#{converted}$"
+  # Check if a path (needle) matches another string, RegExp or array of paths
+  matchPath: (needle, haystack) ->
+    # Array case
+    if _.isArray haystack
+      for hs in haystack
+        return true if @matchPath needle, hs
+      return false
+    # Single case
+    haystack = @parseWildcardPath haystack unless _.isRegExp haystack
+    return haystack.test(needle)
+  # Build the source tree
+  setRootPath: (p) ->
+    @sourcePath = path.join p, 'src'
+    @tmpPath = path.join p, 'tmp'
+    @outputPath = path.join p, 'build'
+  build: (version) ->
+    version = @defaultVersion unless version?
+    throw "#{version} is not a valid version name" unless @versions[version]?
+    @compile version unless @versions[version].compile is false
+    @buildFiles version unless @versions[version].buildFiles is false
+    @postProcess version unless @versions[version].postProcess is false
+  compile: (version) ->
+    version = @defaultVersion unless version?
+    throw "#{version} is not a valid version name" unless @versions[version]?
+    # Create new files when required
+    targets = []
+    srcHashes = @loadDatabase version, @srcHashDb
+    for f, conf of @generateCompileConfigTree(version)
+      sourceFile = path.join @sourcePath, f
+      targetFile = path.join @getCompilePath(version), conf.targetFile(f)
+      targets.push targetFile unless conf.ignore is true
+      unless conf.ignore
+        srcHash = @getFileHash sourceFile
+        unless path.existsSync(targetFile) and srcHashes[sourceFile] is srcHash
+          mkdirp.sync path.dirname(targetFile)
+          if conf.compile
+            console.log "Compiling #{f}..."
+            source = fs.readFileSync sourceFile, 'utf8'
+            try
+              compiled = conf.compiler.compile(source)
+            catch e
+              title = "Error while compiling #{f}"
+              errorMesage = "#{title}\n#{e.toString()}"
+              console.log errorMesage
+              growl e.toString(),
+                title: title
+                name: 'coo'
+              continue
+            fs.writeFileSync targetFile, compiled
+          else
+            console.log "Copying #{f}..."
+            fs.writeFileSync targetFile, fs.readFileSync(sourceFile)
+          srcHashes[sourceFile] = srcHash
+          @saveDatabase version, @srcHashDb, srcHashes
+    # Prune orphaned files
+    buildFileNames = []
+    for bf in _.keys @files
+      buildFileNames.push path.join(@getCompilePath(version), bf)
+    walkdir.sync @getCompilePath(version), (f, stat) ->
+      return if stat.isDirectory()
+      if targets.indexOf(f) is -1 and buildFileNames.indexOf(f) is -1
+        console.log "Removing orphaned file #{f}..."
+        fs.unlinkSync f
+  buildFiles: (version) ->
+    version = @defaultVersion unless version?
+    throw "#{version} is not a valid version name" unless @versions[version]?
+    builtFiles = []
+    compiledFiles = []
+    cvPath = @getCompilePath version
+    buildFileHashes = @loadDatabase version, @buildFileHashDb
+    for c, f of @generateCompileConfigTree() when not f.ignore
+      compiledFiles.push f.targetFile(c)
+    for bf, bfConf of @files
+      # Check if we need update, searching sources one by one to get correct
+      # dependency order
+      bfPath = path.join cvPath, bf
+      sources = [bfConf.source] unless _.isArray bfConf.source
+      matchedFiles = []
+      matchedFileHashes = []
+      requiresBuild = false
+      for s in sources
+        for cf in compiledFiles when (matchedFiles.indexOf cf is -1) and
+        @matchPath cf, s
+          cfPath = path.join cvPath, cf
+          throw "Cannot find compiled file #{cf}" unless path.existsSync cfPath
+          matchedFiles.push cfPath
+          matchedFileHashes.push @getFileHash(cfPath)
+          builtFiles.push cf
+      buildFileExists = path.existsSync bfPath
+      if buildFileExists and matchedFiles.length is 0
+        console.log "Removing #{bf}..."
+        fs.unlinkSync bfPath
+      else if matchedFiles.length > 0
+        newMatchedFilesHash = @getHash matchedFileHashes.join()
+        unless path.existsSync(bfPath) and
+        buildFileHashes[bfPath] is newMatchedFilesHash
+          console.log "Building #{bf}..."
+          mkdirp.sync path.dirname(bfPath)
+          bfFile = fs.openSync bfPath, 'w'
+          for mf in matchedFiles
+            fs.writeSync(bfFile, fs.readFileSync(mf, 'utf8'), null)
+          buildFileHashes[bfPath] = newMatchedFilesHash
+          @saveDatabase version, @buildFileHashDb, buildFileHashes
+      compiledFiles.push bf
+    # Move files to build dir
+    copyFileHashes = @loadDatabase version, @copyFileHashDb
+    ovPath = path.join(@outputPath, version)
+    mkdirp.sync ovPath
+    buildFileNames = _.keys @files
+    walkdir.sync cvPath, (f, stat) =>
+      return if stat.isDirectory()
+      f = path.relative(cvPath, f)
+      outf = path.join ovPath, f
+      if builtFiles.indexOf(f) is -1 or buildFileNames.indexOf(f) isnt -1
+        compf = path.join cvPath, f
+        compfHash = @getFileHash compf
+        unless path.existsSync(outf) and
+        copyFileHashes[compf] is compfHash
+          # Copy
+          console.log "Outputting #{f}..."
+          mkdirp.sync path.dirname(outf)
+          fs.writeFileSync outf, fs.readFileSync(compf)
+          copyFileHashes[compf] = compfHash
+          @saveDatabase version, @copyFileHashDb, copyFileHashes
+      else if path.existsSync(outf)
+        console.log "Removing orphaned file #{f}..."
+        fs.unlinkSync outf
+    # Prune orphaned files
+    walkdir.sync ovPath, (f, stat) ->
+      return if stat.isDirectory()
+      relf = path.relative(ovPath, f)
+      unless path.existsSync(path.join(cvPath, relf))
+        console.log "Removing orphaned file #{f}..."
+        fs.unlinkSync f
+  postProcess: (version) ->
+    version = @defaultVersion unless version?
+    throw "#{version} is not a valid version name" unless @versions[version]?
+    return if @versions[version].postProcess is false
+    # Run processors
+    ovPath = path.join @outputPath, version
+    for f, c of @generatePostConfigTree(version) when not c.ignore
+      if c.process
+        p = path.join ovPath, f
+        source = fs.readFileSync p, 'utf8'
+        try
+          source = processor.process(source) for processor in c.processors
+        catch e
+          console.log "Error while processing #{f}:"
+          console.log e.toString()
+          continue
+        fs.writeFileSync p, source
+  generateCompileConfigTree: ->
+    @generateFileConfigTree @sourcePath, @compilePaths
+  generatePostConfigTree: (version) ->
+    @generateFileConfigTree path.join(@outputPath, version), @postPaths
+  generateFileConfigTree: (p, configs) ->
+    cJson = JSON.stringify configs
+    @fileConfigTree[p] = {} unless @fileConfigTree[p]?
+    @fileConfigTree[p][cJson] = {} unless @fileConfigTree[p][cJson]?
+    # Prune missing items from the tree
+    for f in _.keys @fileConfigTree[p][cJson]
+      abs = path.join @sourcePath, f
+      delete @fileConfigTree[p][cJson][f] unless path.existsSync abs
+    # Add new items to the tree
+    walkdir.sync p, (f, stat) =>
+      # Trim source dir off path
+      return if stat.isDirectory()
+      f = path.relative(p, f)
+      return if @fileConfigTree[p][cJson][f]?
+      for c in configs when @matchPath f, c.path
+        pathConf = @fileConfigTree[p][cJson][f] or {}
+        pathConf = _.defaults c, pathConf
+        @fileConfigTree[p][cJson][f] = pathConf
+    @fileConfigTree[p][cJson]
+  getHash: (data) ->
+    hash = crypto.createHash 'sha1'
+    hash.update data
+    hash.digest 'base64'
+  getFileHash: (file) -> @getHash fs.readFileSync(file, 'utf8')
+  getCompilePath: (version) -> path.join @tmpPath, version, @compileDir
+  getDatabaseFileName: (version, name) ->
+    "#{path.join(@tmpPath, version, name)}.json"
+  loadDatabase: (version, name) ->
+    file = @getDatabaseFileName version, name
+    return {} unless path.existsSync file
+    JSON.parse fs.readFileSync(file, 'utf8')
+  saveDatabase: (version, name, data) ->
+    file = @getDatabaseFileName version, name
+    fs.writeFileSync file, JSON.stringify(data)
+  # Link to top level, made accessible inside the object for configuration.
+  generateCompilerConfig: generateCompilerConfig


Update template.cson

Update template.cson

I prefer the brackets for this because it's inherently one-way anyway.

--- snippets/template.cson
+++ snippets/template.cson
@@ -9,14 +9,14 @@
   "template dom-if":
     "prefix": "tm-if"
     "body": """
-    <template is="dom-if" if="$1">
+    <template is="dom-if" if="[[$1]]">
       $2
     </template>
     """
   "template dom-repeat":
     "prefix": "tm-repeat"
     "body": """
-    <template is="dom-repeat" items="{{$1}}">
+    <template is="dom-repeat" items="[[$1]]">
       $2
     </template>
     """


:bug: Remove editor-scoped messages on editor destroy

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -102,7 +102,8 @@
       @linters.lint({onChange, editorLinter})
     editorLinter.onDidDestroy =>
       editorLinter.deactivate()
-      @editorLinters.delete editor
+      @editorLinters.delete(editor)
+      @messages.deleteEditorMessages(editor)

   deactivate: ->
     @subscriptions.dispose()


don't mess the unsubs in test

don't mess the unsubs in test

--- spec/SpecHelper.coffee
+++ spec/SpecHelper.coffee
@@ -163,8 +163,8 @@
     src = srcF()
     expect(src instanceof Bacon.EventStream).to.equal(true)
   before (done) ->
-    unsub = null
     newSink = ->
+      unsub = null
       (event) ->
         if event.isEnd()
           done()


:racehorse: Only show marker views on rendered lines

--- lib/marker-view.coffee
+++ lib/marker-view.coffee
@@ -32,11 +32,14 @@
       @isMarkerValid = isValid

   onEditorDisplayUpdated: (eventProperties) =>
-    [first, last] = [@editor.firstRenderedScreenRow, @editor.lastRenderedScreenRow]
-    range = @getScreenRange()
-    if @updateDisplayPosition and (range.start.row >= first or range.end.row <= last)
+    if @updateDisplayPosition and @isMarkerVisible()
       @updateDisplay()
       @updateDisplayPosition = false
+
+  isMarkerVisible: ->
+    {start, end} = @getScreenRange()
+    [firstRenderedRow, lastRenderedRow] = [@editor.firstRenderedScreenRow, @editor.lastRenderedScreenRow]
+    end.row >= firstRenderedRow and start.row <= lastRenderedRow

   updateDisplay: ->
     @clearRegions()


Changed the naming of options to avoid a name collision

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -28,7 +28,7 @@
     atom.workspaceView.command "script:kill-process", => @stop()

     @ansiFilter = new AnsiFilter
-    @options =
+    @run_options =
         cmd_cwd: null
         cmd_args: []
         script_args: []
@@ -63,9 +63,9 @@
       @customOptionView.toggle()

   saveOptions: =>
-    @options.cmd_cwd = @customOptionView.inputCwd.val()
-    @options.cmd_args = (item for item in @customOptionView.inputCommandArgs.val().split(' ') when item != '')
-    @options.script_args = (item for item in @customOptionView.inputScriptArgs.val().split(' ') when item != '')
+    @run_options.cmd_cwd = @customOptionView.inputCwd.val()
+    @run_options.cmd_args = (item for item in @customOptionView.inputCommandArgs.val().split(' ') when item != '')
+    @run_options.script_args = (item for item in @customOptionView.inputScriptArgs.val().split(' ') when item != '')

   resetView: (title='Loading...') ->
     # Display window and load message
@@ -177,7 +177,7 @@
     options =
       cwd: @getCwd()
       env: process.env
-    args = (@options.cmd_args.concat args).concat @options.script_args
+    args = (@run_options.cmd_args.concat args).concat @run_options.script_args

     stdout = (output) => @display("stdout", output)
     stderr = (output) => @display("stderr", output)
@@ -200,10 +200,10 @@
     )

   getCwd: ->
-    if @options.cmd_cwd is null || @options.cmd_cwd == ''
+    if @run_options.cmd_cwd is null || @run_options.cmd_cwd == ''
       atom.project.getPath()
     else
-      @options.cmd_cwd
+      @run_options.cmd_cwd

   stop: ->


Make token fs based

Make token fs based

--- lib/token/token.coffee
+++ lib/token/token.coffee
@@ -1,18 +1,20 @@
 # TODO: Persist token in a secure manner
 data = require('../data/data')

-token = null
+TOKEN_KEY = 'token'

 exports.saveToken = (newToken, callback) ->
-	token = newToken
-	return callback(null, token)
+	data.set(TOKEN_KEY, newToken, encoding: 'utf8', callback)

 exports.hasToken = (callback) ->
-	return callback(token?)
+	data.has(TOKEN_KEY, callback)

 exports.getToken = (callback) ->
-	return callback(null, token or undefined)
+	data.get(TOKEN_KEY, encoding: 'utf8', callback)

 exports.clearToken = (callback) ->
-	token = null
-	return callback?(null)
+	data.has TOKEN_KEY, (hasToken) ->
+		if hasToken
+			return data.remove(TOKEN_KEY, callback)
+		else
+			return callback?()


Defer any execution in config.coffee

Defer any execution in config.coffee

If it's simply required, too much gets cached and it can result in an
unreliable config tree being built. This change makes it maximally
reliable and also the slowest.

--- lib/util/config.coffee
+++ lib/util/config.coffee
@@ -1,17 +1,18 @@
 extend = require('config-extend')
-env = require('./env')()
+env = require('./env')

 evaluateTemplateTree = require('./../evaluate-template-tree')
 userRequire = require('./user-require')

-masterFileConfig = require('./../../config/files')
-masterAppConfig = require('./../../config/application')
-masterEnvConfig = require("./../../config/#{env}")
-userFileConfig = userRequire('./config/files')
-userAppConfig = userRequire('./config/application')
-userEnvConfig = userRequire("./config/#{env}")
+module.exports = ->
+  e = env()
+  masterFileConfig = require('./../../config/files')
+  masterAppConfig = require('./../../config/application')
+  masterEnvConfig = require("./../../config/#{e}")
+  userFileConfig = userRequire('./config/files')
+  userAppConfig = userRequire('./config/application')
+  userEnvConfig = userRequire("./config/#{e}")

-module.exports = ->
   config = extend(masterAppConfig(), files:masterFileConfig(), masterEnvConfig(),
                   userAppConfig(),   files:userFileConfig(),   userEnvConfig())
   evaluateTemplateTree(config, config)


Use bcat in a VM without X, and without a browser.

By logging the server port, the user can access the page from an other OS.

--- lib/zombie/bcat.coffee
+++ lib/zombie/bcat.coffee
@@ -76,7 +76,7 @@
         res.end()
         server.close()
         process.exit 0
-    server.listen port, "127.0.0.1"
+    server.listen port

 # Serve that input stream to a browser.
 #
@@ -87,4 +87,5 @@
   bcat = new BCat
   input ||= process.openStdin()
   bcat.serve input, port
+  console.log "open your browser on http://127.0.0.1:#{port}/"
   bcat.open browser, port


up.util.isCrossOrigin(): Don't parse URLs that don't contain a hostname

--- lib/assets/javascripts/unpoly/util.coffee
+++ lib/assets/javascripts/unpoly/util.coffee
@@ -126,9 +126,14 @@
   APP_PROTOCOL = location.protocol
   APP_HOSTNAME = location.hostname

-  isCrossOrigin = (targetURL) ->
-    targetURL = parseURL(targetURL)
-    return APP_HOSTNAME != targetURL.hostname || APP_PROTOCOL != targetURL.protocol
+  isCrossOrigin = (urlOrAnchor) ->
+    # If the given URL does not contain a hostname we know it cannot be cross-origin.
+    # In that case we don't need to parse the URL.
+    if isString(urlOrAnchor) && urlOrAnchor.indexOf('//') == -1
+      return false
+
+    parts = parseURL(urlOrAnchor)
+    return APP_HOSTNAME != parts.hostname || APP_PROTOCOL != parts.protocol

   ###**
   Parses the given URL into components such as hostname and path.


Fix other placeholder selector

Fix other placeholder selector

--- lib/autoprefixer/hacks/placeholder.coffee
+++ lib/autoprefixer/hacks/placeholder.coffee
@@ -26,6 +26,6 @@
     else if '-ms-' == prefix
       ':-ms-input-placeholder'
     else
-      "::#{ prefix }-placeholder"
+      "::#{ prefix }placeholder"

 module.exports = Placeholder


Fix debug

Fix debug

--- index.cjsx
+++ index.cjsx
@@ -405,7 +405,7 @@
   reactClass: React.createClass
     getInitialState: ->
       # Load map data
-      mapspot = null
+      mapspot = CSON.parseCSONFile join(__dirname, 'assets', 'data', 'mapspot.cson')
       mainFleet: new Fleet()
       enemyFleet: new Fleet()
       escortFleet: new Fleet()
@@ -441,7 +441,7 @@
           store.dispatch
             type: '@@poi-plugin-prophet/updateMapspot'
             data: mapspot
-        .catch (e) =>
+        .catch (e) =>
           console.log 'Failed to load map data!', e.stack
       fs.readFileAsync(join(__dirname, 'assets', 'data', 'maproute.cson'))
         .then (data) =>
@@ -449,7 +449,7 @@
           store.dispatch
             type: '@@poi-plugin-prophet/updateMaproute'
             data: maproute
-        .catch (e) =>
+        .catch (e) =>
           console.log 'Failed to load map route!', e.stack

     handleResponse: (e) ->


Use CompositeDisposable to group disposables

--- lib/fonts-cjk.coffee
+++ lib/fonts-cjk.coffee
@@ -1,7 +1,10 @@
+{CompositeDisposable} = require('atom')
+
 module.exports =
   run: ->
     @body = document.body
     @style = document.createElement('style')
+    @disposables = new CompositeDisposable

     triggerMeasurements = ->
       atom.workspace.increaseFontSize()
@@ -32,9 +35,9 @@

     document.head.appendChild(@style)

-    @observer = atom.config.observe('fonts-cjk.editorFont', -> applyFont())
-    @observer = atom.config.observe('fonts-cjk.markdownPreviewFont', -> applyFont())
-    @observer = atom.config.observe('fonts-cjk.workspaceFont', -> applyFont())
+    @disposables.add(atom.config.observe('fonts-cjk.editorFont', applyFont))
+    @disposables.add(atom.config.observe('fonts-cjk.markdownPreviewFont', applyFont))
+    @disposables.add(atom.config.observe('fonts-cjk.workspaceFont', applyFont))

     setTimeout(triggerMeasurements, 500)

@@ -43,5 +46,4 @@
     @body.removeAttribute('data-fonts-cjk-markdownPreviewFont')
     @body.removeAttribute('data-fonts-cjk-workspaceFont')
     @style.parentNode.removeChild(@style)
-
-    @observer?.dispose()
+    @disposables.dispose()


Throw proper errors when encountering break labels.

--- lib/js2coffee.coffee
+++ lib/js2coffee.coffee
@@ -257,9 +257,6 @@
   '?': ->
     "(if #{build @left()} then #{build @children[1]} else #{build @children[2]})"

-  'label': ->
-    throw "Not supported yo"
-
   'for': ->
     c = new Code

@@ -377,6 +374,7 @@
   'other':  -> @unsupported "#{@typeName()} is not supported yet"
   'getter': -> @unsupported "getter syntax is not supported; use __defineGetter__"
   'setter': -> @unsupported "setter syntax is not supported; use __defineSetter__"
+  'label':  -> @unsupported "labels are not supported by CoffeeScript"
   'const':  -> @unsupported "consts are not supported by CoffeeScript"

 Tokens.block = Tokens.script


Enabling/disabling toolbar buttons

Enabling/disabling toolbar buttons

--- lib/spark-ide.coffee
+++ lib/spark-ide.coffee
@@ -86,11 +86,23 @@
     atom.packages.activatePackage('toolbar')
       .then (pkg) =>
         @toolbar = pkg.mainModule
-        @toolbar.appendButton 'flash', 'spark-ide:flash-cloud', 'Flash using cloud', 'ion'
-        @toolbar.appendButton 'checkmark-circled', 'spark-ide:compile-cloud', 'Compile in the cloud', 'ion'
         @toolbar.appendSpacer()
-        @toolbar.appendButton 'wifi', 'spark-ide:setup-wifi', 'Setup Core\'s WiFi...', 'ion'
+        @flashButton = @toolbar.appendButton 'flash', 'spark-ide:flash-cloud', 'Flash using cloud', 'ion'
+        @compileButton = @toolbar.appendButton 'checkmark-circled', 'spark-ide:compile-cloud', 'Compile in the cloud', 'ion'
+
+        @toolbar.appendSpacer()
+
+        @wifiButton = @toolbar.appendButton 'wifi', 'spark-ide:setup-wifi', 'Setup Core\'s WiFi...', 'ion'
         @toolbar.appendButton 'usb', 'spark-ide:show-serial-monitor', 'Show serial monitor', 'ion'
+
+        @updateToolbarButtons()
+
+    atom.workspaceView.command 'spark-ide:update-login-status', =>
+      @updateToolbarButtons()
+
+    atom.workspaceView.command 'spark-ide:update-core-status', =>
+      @updateToolbarButtons()
+
   deactivate: ->
     @statusView?.destroy()

@@ -159,6 +171,20 @@

       pane.activate()
       atom.workspace.open(uri, searchAllPanes: true)
+
+  updateToolbarButtons: ->
+    if @SettingsHelper.isLoggedIn()
+      @compileButton.setEnabled true
+      @wifiButton.setEnabled true
+
+      if @SettingsHelper.hasCurrentCore()
+        @flashButton.setEnabled true
+      else
+        @flashButton.setEnabled false
+    else
+      @flashButton.setEnabled false
+      @compileButton.setEnabled false
+      @wifiButton.setEnabled false

   # Function for selecting port or showing Listen dialog
   choosePort: (delegate) ->


Use the correct motion for yank line

Use the correct motion for yank line

This works beautifully, but the tests are failing.

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -74,7 +74,7 @@
       'delete-right': => [new operators.Delete(@editor), new motions.MoveRight(@editor)]
       'delete-to-last-character-of-line': => [new operators.Delete(@editor), new motions.MoveToLastCharacterOfLine(@editor)]
       'yank': => @linewiseAliasedOperator(operators.Yank)
-      'yank-line': => [new operators.Yank(@editor, @), new motions.MoveToLastCharacterOfLine(@editor)]
+      'yank-line': => [new operators.Yank(@editor, @), new motions.MoveToLine(@editor)]
       'put-before': => new operators.Put(@editor, @, location: 'before')
       'put-after': => new operators.Put(@editor, @, location: 'after')
       'join': => new operators.Join(@editor)


Make gUgU, gugu and g~g~ change casing on the whole line

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -83,9 +83,9 @@
       'delete-right': => [new Operators.Delete(@editor, @), new Motions.MoveRight(@editor, @)]
       'delete-left': => [new Operators.Delete(@editor, @), new Motions.MoveLeft(@editor, @)]
       'delete-to-last-character-of-line': => [new Operators.Delete(@editor, @), new Motions.MoveToLastCharacterOfLine(@editor, @)]
-      'toggle-case': => new Operators.ToggleCase(@editor, @)
-      'upper-case': => new Operators.UpperCase(@editor, @)
-      'lower-case': => new Operators.LowerCase(@editor, @)
+      'toggle-case': => @linewiseAliasedOperator(Operators.ToggleCase)
+      'upper-case': => @linewiseAliasedOperator(Operators.UpperCase)
+      'lower-case': => @linewiseAliasedOperator(Operators.LowerCase)
       'toggle-case-now': => new Operators.ToggleCase(@editor, @, complete: true)
       'yank': => @linewiseAliasedOperator(Operators.Yank)
       'yank-line': => [new Operators.Yank(@editor, @), new Motions.MoveToRelativeLine(@editor, @)]


router: redirect to the last route, not the first

--- client/Main/maincontroller/maincontroller.coffee
+++ client/Main/maincontroller/maincontroller.coffee
@@ -157,14 +157,13 @@
         # window location path is set to last route to ensure visitor is not
         # redirected to another page
         @utils.defer ->
-          firstRoute = KD.getSingleton("router").visitedRoutes.first
-
-          firstRoute = KD.getSingleton("router").visitedRoutes.first
-          if firstRoute and /^\/(?:Reset|Register|Verify|Confirm)\//.test firstRoute
-            firstRoute = "/Activity"
+          lastRoute = KD.getSingleton("router").visitedRoutes.last
+
+          if lastRoute and /^\/(?:Reset|Register|Verify|Confirm)\//.test lastRoute
+            lastRoute = "/Activity"

           {entryPoint} = KD.config
-          KD.getSingleton('router').handleRoute firstRoute or '/Activity', {replaceState: yes, entryPoint}
+          KD.getSingleton('router').handleRoute lastRoute or '/Activity', {replaceState: yes, entryPoint}

   setVisitor:(visitor)-> @visitor = visitor
   getVisitor: -> @visitor


KiteSubscription: subscribe user to first kite plan

--- client/Social/Apps/Views/AppsListItemView.coffee
+++ client/Social/Apps/Views/AppsListItemView.coffee
@@ -31,7 +31,19 @@
       cssClass : 'run'
       title    : 'open'
       callback : =>
-        log "kite run clicked"
+        kite = @getData()
+        kite.fetchPlans (err, plans)->
+          return KD.showError err if err
+          user = KD.whoami()
+          user.fetchPaymentMethods (err, paymentMethods)->
+            return KD.showError err if err
+            {paymentMethodId} = paymentMethods.first
+            # TODO: THAT IS FOR TESTING PURPOSES
+            # NEED TO CHANGE BEFORE MERGE
+            plans.first.subscribe paymentMethodId, {},(err, subscription)->
+              log subscription
+
+          log plans.first

   # Override KDView::render since I'm updating all the manifest at once ~ GG


fix #104

fix #104

--- lib/browser-plus-view.coffee
+++ lib/browser-plus-view.coffee
@@ -1,7 +1,7 @@
 {CompositeDisposable}  = require 'atom'
 {View,$} = require 'atom-space-pen-views'
 $ = jQ = require '../node_modules/jquery/dist/jquery.js'
-require '../node_modules/jquery-ui/autocomplete'
+require '../node_modules/jquery-ui/autocomplete.js'
 path = require 'path'
 require 'JSON2'
 require 'jstorage'


Remove unused Range require

Remove unused Range require

--- lib/snippets-provider.coffee
+++ lib/snippets-provider.coffee
@@ -1,5 +1,3 @@
-{Range}  = require('atom')
-
 module.exports =
 class SnippetsProvider
   selector: '*'


(empty)

(empty)

--- modules/config/commands.coffee
+++ modules/config/commands.coffee
@@ -86,7 +86,7 @@
           { name: 'Parameter', value: args[0], inline: true }
           {
             name: 'Current Value'
-            value: d.data[args[0]] || @params[args[0]].def
+            value: d.data[args[0]] || @params[args[0]].def || '(empty)'
             inline: true
           }
         ]
@@ -125,7 +125,7 @@
         color: 0x00AAFF
         fields: [
           { name: 'Parameter', value: args[0], inline: true }
-          { name: 'Current Value', value: d.data[args[0]], inline: true }
+          { name: 'Current Value', value: d.data[args[0]] || '(empty)', inline: true }
         ]
       }


Fix VDOM array bug

Fix VDOM array bug

--- app/assets/components/core.coffee
+++ app/assets/components/core.coffee
@@ -3,7 +3,7 @@
 VNode = Preact.h('a', null).constructor

 export createElement = (element, props, children...) ->
-  if props? && (typeof props != 'object' || props instanceof VNode)
+  if props? && (Array.isArray(props) || typeof props != 'object' || props instanceof VNode)
     children.unshift props
     props = {}


groups

groups

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -75,6 +75,8 @@
     # if window.matchMedia("(min-device-width: 320px)").matches and window.matchMedia("(max-device-width: 480px)").matches
     #   location.hash = "#/verify"

+    Subject.group = "5417014a3ae7400bda000001"
+
     @loggedOutClassificationCount = 0

     window.classifier = @
@@ -409,7 +411,7 @@
     @insertMetadata()
     # @el.find('.do-you-see-a-transit').fadeIn()
     # @el.find('#no-transits-button').fadeIn()
-
+
     # @el.find('#finished-marking').fadeIn()
     # @el.find('#finished-feedback').fadeIn()

@@ -533,7 +535,7 @@
     @course.prompt_el.hide()
     @classifySummary.fadeOut(150)
     @hideMarkingButtons()
-
+
     # # switch to verify mode
     # if @course.count % @verifyRate is 0
     #   location.hash = "#/verify"
@@ -551,7 +553,19 @@
     @sendClassification()
     @canvasGraph.marks.destroyAll() #clear old marks
     @recordedClickEvents = []
-    @Subject.next()
+
+    @sim_count ||= 0
+    @sim_count +=1
+
+    if @sim_count%10 == 0
+      Subject.group = "5417014b3ae7400bda000002"
+      Subject.fetch 1, (subjects) =>
+        console.log "got sim subjects ", subjects
+    else
+      Subject.group = "5417014a3ae7400bda000001"
+      @Subject.next()
+
+

     @noTransitsButton.show()

@@ -568,7 +582,7 @@
   showSummaryScreen: ->
     # reveal ids
     @el.find('.star-id').fadeIn()
-
+
     if @classification.subject.id is 'TUTORIAL_SUBJECT'
       @onClickNextSubject()
     else
@@ -578,7 +592,7 @@
       @classifySummary.fadeIn(150)
     # @finishSubject()

-  checkSupplementalTutorial: ->
+  checkSupplementalTutorial: ->
     for classification_count in @whenToDisplayTips
       if @course.count is classification_count
         # console.log "*** DISPLAY SUPPLEMENTAL TUTOTIAL # #{classification_count} *** "
@@ -639,7 +653,7 @@
       recordedClickEvents: [@recordedClickEvents...]

     console.log JSON.stringify( @classification ) # DEBUG CODE
-
+
     # send classification (except for tutorial subject)
     unless @classification.subject.id is 'TUTORIAL_SUBJECT'
       @classification.send()
@@ -675,7 +689,7 @@

   displayKnownTransits: ->
     return unless @simulationsPresent()
-
+
     @hideMarkingButtons()
     @continueButton.show()

@@ -687,13 +701,13 @@
     @course.prompt_el.find('#course-no').hide()
     @course.prompt_el.find('#course-never').hide()
     @course.prompt_el.find('#course-message').html "This light curve contains at least one simulated transit, highlighted in red."
-
+
     return

   simulationsPresent: ->
     if @known_transits.length > 0
       return true
-    else
+    else
       return false

   finishSubject: ->
@@ -790,7 +804,7 @@
     else
       @el.find('#ui-slider').attr('disabled', true)
       @el.find("#zoom-button").removeClass("zoomed")
-
+
   zoomReset: =>
     @canvasGraph.zoomOut()
     @isZoomed = false
@@ -860,32 +874,32 @@

   validateComment: (comment)=>
     is_valid = comment.length > 0 && comment.length <= 140
-
+
   submitComment: =>
     comment = @talkComment.val()
     is_valid = @validateComment comment
     return unless is_valid
     request = Api.current.post "https://dev.zooniverse.org/projects/planet_hunter/talk/subjects/APH000001x/comments", comment: comment
-
+
     # request = Api.current.post "/projects/#{Api.current.project}/talk/subjects/#{Subject.current?.zooniverse_id}/comments", comment: comment

     # time = new Date
-
+
     # @comments.unshift
     #   user_name: User.current.name
     #   user_zooniverse_id: User.current.zooniverse_id
     #   body: comment
     #   timeago: $.timeago time
     #   date: DateWidget.formatDate 'd MM yy', time
-
+
     # @render()
-
+
     # clearTimeout @timeout if @timeout?
-
-    # @timeout = setTimeout =>
+
+    # @timeout = setTimeout =>
     #   @fetchComments()
     # , @refresh * 1000 if @refresh?
-
+

   #
   # END TALK COMMENT METHODS


fix noTransitButton behavior

fix noTransitButton behavior

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -105,12 +105,24 @@

     @guideShowing = false

+    @hideMarkingButtons()
+
+
     User.on 'change', @onUserChange
     Subject.on 'fetch', @onSubjectFetch
     Subject.on 'select', @onSubjectSelect
     @Subject = Subject

-    $(document).on 'mark-change', => @updateButtons()
+    $(document).on 'mark-change', =>
+      if @canvasGraph.marks?.all.length > 0
+        @noTransitsButton.hide()
+        @finishedMarkingButton.show()
+      else
+        @finishedMarkingButton.hide()
+
+
+        @noTransitsButton.show()
+
     @marksContainer = @el.find('#marks-container')[0]

     @initialTutorial = new Tutorial
@@ -328,8 +340,6 @@
     else
       jsonFile = @subject.selected_light_curve.location

-    # console.log 'jsonFile: ', jsonFile
-
     # handle ui elements
     $('#graph-container').addClass 'loading-lightcurve'
     @el.find('#loading-screen').fadeIn()
@@ -347,7 +357,6 @@
     # read json data
     $.getJSON jsonFile, (data) =>

-      # DEBUG: USE THIS FOR NOW
       if data.metadata.known_transits
         @known_transits = data.metadata.known_transits
         @planet_rad    = data.metadata.planet_rad
@@ -356,34 +365,29 @@
       else
         @known_transits = ''

-      # if @subject.metadata.synthetic_id?
-        # @calcKnownTransits(data)
-
       @canvasGraph?.marks.destroyAll()
       @marksContainer.appendChild(@canvas)
       @canvasGraph = new CanvasGraph(@canvas, data)
       @zoomReset()
       @canvasGraph.plotPoints()
       @el.find('#loading-screen').fadeOut()
+      @canvasGraph.enableMarking()
+      @magnification = [ '1x (all days)', '10 days', '2 days' ]
+      @showZoomMessage(@magnification[@canvasGraph.zoomLevel])
+
       $('#graph-container').removeClass 'loading-lightcurve'
-      @canvasGraph.enableMarking()
-      # @zoomRanges = [@canvasGraph.largestX, 10, 2]
-      @magnification = [ '1x (all days)', '10 days', '2 days' ]
-      # update ui elements
-      @showZoomMessage(@magnification[@canvasGraph.zoomLevel])
+      @noTransitsButton.fadeIn(150)
+
       @el.find("#ui-slider").noUiSlider
         start: 0
         range:
           min: @canvasGraph.smallestX
           max: @canvasGraph.largestX #- @zoomRange
+
       @el.find(".noUi-handle").hide()

+
     @insertMetadata()
-    # @el.find('.do-you-see-a-transit').fadeIn()
-    # @el.find('#no-transits-button').fadeIn()
-
-    # @el.find('#finished-marking').fadeIn()
-    # @el.find('#finished-feedback').fadeIn()

   insertMetadata: ->
     # ukirt data
@@ -436,10 +440,18 @@
     clickEvent = { event: 'tutorial-clicked', timestamp: (new Date).toUTCString() }
     @recordedClickEvents.push clickEvent
     @canvasGraph.marks.destroyAll() # clear previous marks
-    @updateButtons()
+
+    @finishedMarkingButton.hide()
+    # @noTransitsButton.show()
+
+
+
+
     @launchTutorial()

   launchTutorial: ->
+    @noTransitsButton.hide()
+
     if $('#graph-container').hasClass 'loading-lightcurve'
       @notify 'Please wait until current lightcurve is loaded.'
       return
@@ -472,15 +484,6 @@
         location: 'https://s3.amazonaws.com/demo.zooniverse.org/planet_hunter/subjects/09631995_16-3.json'
     tutorialSubject

-  updateButtons: ->
-    # console.log 'updateButtons()'
-    if @canvasGraph.marks?.all.length > 0
-      @noTransitsButton.hide()
-      @finishedMarkingButton.show()
-    else
-      @finishedMarkingButton.hide()
-      @noTransitsButton.show()
-
   #
   # BEGIN MARKING TRANSITIONS
   #
@@ -559,7 +562,7 @@
       Subject.group = MAIN_SUBJECT_GROUP
       @Subject.next()

-    @noTransitsButton.show()
+    # @noTransitsButton.show()

   #
   # END MARKING TRANSITIONS


Extend the overlay.

Extend the overlay.

--- app/javascripts/timeSeries.coffee
+++ app/javascripts/timeSeries.coffee
@@ -28,6 +28,9 @@

       # The image resource is the time series name.
       resource = timeSeries.name
-      # Extend the image.
+      # Extend the image with load capability.
       Image.extend(timeSeries.image, imageSequence, resource)
+      # If there is an overlay, then extend it as a Loader.
+      if timeSeries.overlay?
+        Image.extend(timeSeries.overlay, imageSequence, resource)
   ]


[remove] GeocodingAPI

[remove] GeocodingAPI

--- app/scripts/apis/geocoding.coffee
+++ app/scripts/apis/geocoding.coffee
@@ -1,17 +0,0 @@
-define [
-  'apis/base'
-], (BaseAPI) ->
-
-  class GeocodingAPI extends BaseAPI
-
-    baseUrl: 'http://maps.googleapis.com/maps/api/geocode/json'
-
-    dataDefaults:
-      sensor: 'false'
-      language: 'en'
-      # address: undefined
-
-    prepareResult: ->
-      data = super?.results
-      data = data? && _(data).first()?.geometry?.location
-      @result = data || {}


Profile: slug/username router handlings are updated

--- client/Main/linkcontroller.coffee
+++ client/Main/linkcontroller.coffee
@@ -11,16 +11,17 @@
     options = {}
     route   = switch data.constructor
       when JAccount
-        entryPoint = type : "account" # everytime user clicks on avatar view,
-        "/#{data.profile.nickname}"   # regardless of group, user will always
-      when JGroup                     # routed to /USER_NAME page. ~ EA
+        {slug} = KD.getGroup()
+        {profile: {nickname}} = data
+        href = if slug is "koding" then "/#{nickname}" else "/#{slug}/#{nickname}"
+      when JGroup
         "/#{data.slug}"
       when JTag
         {group, slug} = data
         route = if group is KD.defaultSlug then '' else "/#{group}"
         route += "/Activity/?tagged=#{slug}"

-    KD.getSingleton('router').handleRoute route, {state : data, entryPoint}  if route?
+    KD.getSingleton('router').handleRoute route, {state : data}  if route?

   registerLink:(link)->
     id = link.getId()


restore inner block indent after :else, :case, etc

--- client/codemirror-apl-mode.coffee
+++ client/codemirror-apl-mode.coffee
@@ -56,7 +56,8 @@
     iInd: [0]   # "inner indents" -- a stack of the indents within the bodies of { :if :for etc

   token: (stream, state) ->
-    if stream.sol() && !stream.match /\s*$/, false then state.iInd[state.iInd.length - 1] = stream.indentation()
+    if stream.sol() && !stream.match /^\s*(:|$)/, false
+      state.iInd[state.iInd.length - 1] = stream.indentation()
     if state.hdr
       delete state.hdr; stream.match /[^⍝\n\r]*/; s = stream.current()
       if /^\s*:/.test(s) || dfnHeader.test s


set thumbnail width

set thumbnail width

--- client/js/views/album_view.coffee
+++ client/js/views/album_view.coffee
@@ -14,6 +14,7 @@
       pictures:
         thumb:
           src: -> @thumbnail.src
+          width: -> @thumbnail.width
           alt: -> @title
         link:
           href: -> @path


change wording for signup vs login

change wording for signup vs login

--- coach/src/user/login-gateway.cjsx
+++ coach/src/user/login-gateway.cjsx
@@ -3,7 +3,7 @@
 User  = require './model'
 api   = require '../api'
 classnames = require 'classnames'
-
+SIGNUP_TYPE = 'signup'
 CURRENT_WINDOW = undefined

 SECOND = 1000
@@ -46,7 +46,7 @@
         "left="  + (windowImpl.screen.width - width)   / 2].join()

       url = @urlForLogin()
-      url += '&go=signup' if options.type is 'signup'
+      url += '&go=signup' if options.type is SIGNUP_TYPE
       CURRENT_WINDOW = windowImpl.open(url, 'oxlogin', windowOptions)

@@ -89,6 +89,12 @@
     else
       _.delay( @windowClosedCheck, SECOND)

+  getActionText: ->
+    if @props.loginType is SIGNUP_TYPE
+      'create an OpenStax account'
+    else
+      'log in using your OpenStax account'
+
   render: ->
     classes = classnames('login-gateway', @props.className,
       'is-open': @state.loginWindow
@@ -97,7 +103,7 @@

-        Please log in using your OpenStax account in the window. <a data-bypass
+        Please {@getActionText()} in the window. Click to reopen window.


Fix Blackberry 10 data

Fix Blackberry 10 data

--- data/prefixes.coffee
+++ data/prefixes.coffee
@@ -6,6 +6,7 @@
     selector: true
     browsers: [
       "android 4.4"
+      "bb 10"
       "chrome 4"
       "chrome 5"
       "chrome 6"


wave 7 is released

wave 7 is released

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -2,10 +2,6 @@
 exportObj = exports ? this

 exportObj.unreleasedExpansions = [
-    "Hound's Tooth Expansion Pack"
-    "Kihraxz Fighter Expansion Pack"
-    "K-Wing Expansion Pack"
-    "TIE Punisher Expansion Pack"
     "Imperial Assault Carrier Expansion Pack"
     "Ghost Expansion Pack"
     "Inquisitor's TIE Expansion Pack"


Minor bug fix

Minor bug fix

--- muleview/coffee/app/Graphs.coffee
+++ muleview/coffee/app/Graphs.coffee
@@ -14,6 +14,7 @@

     # Obtain data:
     Muleview.Mule.getKeyData Muleview.currentKey, (data) =>
+      firstTab = null
       for own retention, retentionData of data
         @retentions[retention] = @createRetentionGraphs(retention, retentionData)
         firstTab ||= @retentions[retention].graph


Add main package, rename Foreach to Spire, improve routing

--- model/Recipe/Bitly/BitlyShortenUrl.coffee
+++ model/Recipe/Bitly/BitlyShortenUrl.coffee
@@ -43,7 +43,7 @@
             _3DCart:
               avatarId: steps["_3DCartChooseAvatar"].avatarId
               params: {}
-        if Foreach.isDebug
+        if Spire.isDebug
           _.extend input._3DCartDownloadOrders._3DCartReadOrders.input.params,
             datestart: "06/15/2012"
             dateend: "07/15/2012"


Fix Blob to smoothly increase to weight cap

--- src/Blob.coffee
+++ src/Blob.coffee
@@ -41,7 +41,7 @@
       # Set weight to preferred weight
       if entBlob.weightCap
         entBlob.weight += (entBlob.weightCap - entBlob.weight) / 4
-        if Math.abs entBlob.weight - entBlob.weightCap < 3
+        if Math.abs(entBlob.weight - entBlob.weightCap) < 3
           entBlob.weight = entBlob.weightCap
           entBlob.weightCap = null
         else continue


Translate to Spanish new options in Settings tab

Related to #47

--- def/es/settings.cson
+++ def/es/settings.cson
@@ -106,8 +106,8 @@
       }
       {
         _id: 'core.closeDeletedFileTabs'
-        title: "Close Deleted File Tabs"
-        desc: "Close corresponding editors when a file is deleted outside Atom."
+        title: "Cerrar Pestañas de Archivos Eliminados"
+        desc: "Cierra los editores correspondientes cuando un archivo es eliminado fuera de Atom."
       }
       {
         _id: 'core.closeEmptyWindows'
@@ -176,7 +176,7 @@
       {
         _id: 'core.restorePreviousWindowsOnStart'
         title: "Restaurar Ventana Previa al Iniciar"
-        desc: "When checked restores the last state of all Atom windows when started from the icon or atom by itself from the command line; otherwise a blank environment is loaded."
+        desc: "Cuando está marcada restaura el último estado de todas las ventanas de Atom cuando es iniciado desde el ícono o con atom desde la línea de comandos; caso contrario un ambiente en blanco es cargado."
       }
       {
         _id: 'core.telemetryConsent'
@@ -192,8 +192,8 @@
       }
       {
         _id: 'core.useProxySettingsWhenCallingApm'
-        title: "Use detected proxy settings when calling the apm command-line tool."
-        desc: "Use detected proxy settings when calling the apm command-line tool."
+        title: "Usar ajustes detectados del proxy cuando la herramienta de línea de comandos apm es llamada."
+        desc: "Usar ajustes detectados del proxy cuando la herramienta de línea de comandos apm es llamada."
       }
       {
         _id: 'core.useCustomTitleBar'
@@ -304,8 +304,8 @@
       }
       {
         _id: 'editor.showCursorOnSelection'
-        title: "Show Cursor On Selection"
-        desc: "Show cursor while there is a selection."
+        title: "Mostrar Cursor Sobre Selección"
+        desc: "Mostrar cursor mientras hay una selección."
       }
       {
         _id: 'editor.showIndentGuide'


Better names for variables.

Better names for variables.

These variable names are misleading.  The things being manipulated are
actually hint descriptors.

So this renames the variables accordingly.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -112,19 +112,20 @@
   # A count of the number of Tab presses since the last non-Tab keyboard event.
   tabCount: 0

-  constructor: (elements, mode = OPEN_IN_CURRENT_TAB) ->
+  constructor: (hintDescriptors, mode = OPEN_IN_CURRENT_TAB) ->
     # we need documentElement to be ready in order to append links
     return unless document.documentElement

-    # For these modes, we filter out those elements which don't have an HREF (since there's nothing we can do
-    # with them).
-    elements = (el for el in elements when el.hasHref) if mode in [ COPY_LINK_URL, OPEN_INCOGNITO ]
-
-    if elements.length == 0
+    if mode in [COPY_LINK_URL, OPEN_INCOGNITO]
+      # For these modes, we filter out those descriptors which don't have an HREF (since there's nothing we
+      # can do with them).
+      hintDescriptors = (desc for desc in hintDescriptors when desc.hasHref)
+
+    if hintDescriptors.length == 0
       HUD.showForDuration "No links to select.", 2000
       return

-    hintMarkers = (@createMarkerFor(el) for el in elements)
+    hintMarkers = (@createMarkerFor desc for desc in hintDescriptors)
     @markerMatcher = new (if Settings.get "filterLinkHints" then FilterHints else AlphabetHints)
     @markerMatcher.fillInMarkers hintMarkers

@@ -163,15 +164,15 @@
   createMarkerFor: do ->
     # This count is used to rank equal-scoring hints when sorting, thereby making JavaScript's sort stable.
     stableSortCount = 0
-    (link) ->
+    (desc) ->
       marker = DomUtils.createElement "div"
       marker.className = "vimiumReset internalVimiumHintMarker vimiumHintMarker"
       marker.stableSortCount = ++stableSortCount
-      # Extract other relevant fields from the hint descriptor. TODO(smblott) "link" here is misleading.
+      # Extract other relevant fields from the hint descriptor.
       extend marker,
-        {hintDescriptor: link, linkText: link.linkText, showLinkText: link.showLinkText, rect: link.rect}
-
-      clientRect = link.rect
+        {hintDescriptor: desc, linkText: desc.linkText, showLinkText: desc.showLinkText, rect: desc.rect}
+
+      clientRect = desc.rect
       marker.style.left = clientRect.left + window.scrollX + "px"
       marker.style.top = clientRect.top  + window.scrollY  + "px"


minor fixes to GameError

minor fixes to GameError

--- packages/cbga:core/core.coffee
+++ packages/cbga:core/core.coffee
@@ -14,7 +14,6 @@
 class CBGA.GameError extends Error
     constructor: (@message) ->
         @name = 'GameError'
-        base = new Error()
         if Error.captureStackTrace?
             Error.captureStackTrace @, @constructor
         else
@@ -22,15 +21,14 @@
             # but that's obviously the correct behaviour in this case.
             # V8 has captureStackTrace which correctly ignores this method.
             # Firefox has a \n-separated string.
-            stack = base.stack
+            stack = (new Error).stack
             if typeof stack is 'string'
                 stack = stack.split '\n'
                 loop
                     first = stack.shift()
-                    console.log 'shifted'
                     if first.match new RegExp "^#{@constructor.name}"
                         break
-                caller = stack[0].match /.*@(.*):(\d+):(\d+)/
+                caller = stack[0].match /.*@(.*?):(\d+)(?::(\d+))?$/
                 if caller?
                     @fileName = caller[1]
                     @lineNumber = caller[2]


Use login instead of label account in menu

--- client/app/components/menu.coffee
+++ client/app/components/menu.coffee
@@ -240,11 +240,11 @@
                             className: 'avatar'
                             style:
                                 'background-color': accountColor
-                            account.get('label')[0]
+                            account.get('login')[0]
                         span
                             'data-account-id': key,
                             className: 'item-label',
-                            account.get 'label'
+                            account.get 'login'

                     if progress = refreshes.get(accountID)
                         if progress.get('errors').length


half-finished #userlinks dropdown menu click/blur handling

--- client/code/view/sitelinks.coffee
+++ client/code/view/sitelinks.coffee
@@ -6,6 +6,7 @@
     'click #userlink': 'userClicked'

   render: ->
+    console.log 'rendered'
     @el.innerHTML = JST.sitelinks window.user.effective
     @

@@ -20,13 +21,12 @@
       $('a[href="#userlinks"]').addClass 'active'
       $('#sidebar.open').removeClass 'open'
       $('a[href="#sidebar"]').removeClass 'active'
-      # $(document).on('click.userlinks', (e) ->
-      #         console.log 'userlinks is open and you clicked the document'
-      #         if $('#userlinks').has(e.target).length == 0
-      #           console.log 'your click was outside the userlinks menu'
-      #           # closeMenu('#' + $m.attr('id'));
-      #           $(document).off('.userlinks');
-      #           $('#userlinks').removeClass 'open'
-      #           $('a[href="#userlinks"]').removeClass 'active'
-      #       )
+      # TODO: make this work (ask Zarino)
+      # $(document).on 'click.userlinks', (e) ->
+      #   console.log 'userlinks is open and you clicked the document'
+      #   if $('#userlinks').has(e.target).length == 0
+      #     console.log 'your click was outside the userlinks menu'
+      #     $(document).off('.userlinks');
+      #     $('#userlinks').removeClass 'open'
+      #     $('a[href="#userlinks"]').removeClass 'active'


payment: get rid of weird xml attribute

payment: get rid of weird xml attribute

xml deserialized to node.$.nil = nil should deserialize to null instead

--- node_modules_koding/koding-payment/lib/main.coffee
+++ node_modules_koding/koding-payment/lib/main.coffee
@@ -477,23 +477,28 @@
   @deleteAccount: (accountCode, callback)->
     del "accounts/#{accountCode}", callback

-  createBillingObject = (b)->
-    cardFirstName     : b.first_name[0]
-    cardLastName      : b.last_name[0]
-    cardNumber        : Array(13).join('*') + b.last_four
-    cardMonth         : b.month[0]._
-    cardYear          : b.year[0]._
-    cardCV            : '***'
-    cardType          : b.card_type[0]
-    address1          : b.address1[0]
-    address2          : ifString b.address2[0]
-    city              : ifString b.city[0]
-    state             : ifString b.state[0]
-    zip               : b.zip[0]
-    country           : b.country[0]
-    company           : b.company[0]
-    vatNumber         : b.vat_number[0]
-    phone             : b.phone[0]
+  filterNils = (ob) ->
+    ob[key] = null  for own key, val of ob when val?.$?.nil?
+    ob
+
+  createBillingObject = (b) ->
+    filterNils
+      cardFirstName     : b.first_name[0]
+      cardLastName      : b.last_name[0]
+      cardNumber        : Array(13).join('*') + b.last_four
+      cardMonth         : b.month[0]._
+      cardYear          : b.year[0]._
+      cardCV            : '***'
+      cardType          : b.card_type[0]
+      address1          : b.address1[0]
+      address2          : ifString b.address2[0]
+      city              : ifString b.city[0]
+      state             : ifString b.state[0]
+      zip               : b.zip[0]
+      country           : b.country[0]
+      company           : b.company[0]
+      vatNumber         : b.vat_number[0]
+      phone             : b.phone[0]

   @fetchBillingByAccountCode: (accountCode, callback)->
     get "accounts/#{accountCode}/billing_info", (err, res)->


Removing the regex around the search

Removing the regex around the search

--- src/scripts/elasticsearch.coffee
+++ src/scripts/elasticsearch.coffee
@@ -2,12 +2,12 @@
 #   Get ElasticSearch Cluster Information
 #
 # Commands:
-#   hubot: elasticsearch|es cluster [server] - Gets the cluster information for the given server
+#   hubot: elasticsearch cluster [server] - Gets the cluster information for the given server or alias
 #   hubot: elasticsearch node [server] - Gets the node information for the given server
 #   hubot: elasticsearch query [server] [query] - Runs a specific query against an ElasticSearch cluster
-#   hubot: elasticsearch|es show aliases - shows the aliases for the list of ElasticSearch instances
-#   hubot: elasticsearch|es add alias [alias name] [url] - sets the alias for a given url
-#   hubot: elasticsearch|es clear alias [alias name] - please note that this needs to include any port numbers as appropriate
+#   hubot: elasticsearch show aliases - shows the aliases for the list of ElasticSearch instances
+#   hubot: elasticsearch add alias [alias name] [url] - sets the alias for a given url
+#   hubot: elasticsearch clear alias [alias name] - please note that this needs to include any port numbers as appropriate
 #
 # Notes:
 #   The server must be a fqdn (with the port!) to get to the elasticsearch cluster
@@ -66,32 +66,32 @@
     node_health msg, msg.match[1], (text) ->
       msg.send text

-  robot.hear /(elasticsearch|es) cluster (.*)/i, (msg) ->
+  robot.hear /elasticsearch cluster (.*)/i, (msg) ->
     if msg.message.user.id is robot.name
       return

-    cluster_health msg, msg.match[2], (text) ->
+    cluster_health msg, msg.match[1], (text) ->
       msg.send text

-  robot.hear /(elasticsearch|es) show aliases/i, (msg) ->
+  robot.hear /elasticsearch show aliases/i, (msg) ->
     if msg.message.user.id is robot.name
       return

     showAliases msg, (text) ->
       msg.send(text)

-  robot.hear /(elasticsearch|es) add alias (.*) (.*)/i, (msg) ->
+  robot.hear /elasticsearch add alias (.*) (.*)/i, (msg) ->
     if msg.message.user.id is robot.name
       return

-    setAlias msg, msg.match[2], msg.match[3], (text) ->
+    setAlias msg, msg.match[1], msg.match[2], (text) ->
       msg.send(text)

-  robot.hear /(elasticsearch|es) clear alias (.*)/i, (msg) ->
+  robot.hear /elasticsearch clear alias (.*)/i, (msg) ->
     if msg.message.user.id is robot.name
       return

-    clearAlias msg, msg.match[2], (text) ->
+    clearAlias msg, msg.match[1], (text) ->
       msg.send(text)

   showAliases = (msg) ->


As hubot is frequently used in business settings, turn safe search to maximum (active) setting out of the box.

--- src/scripts/google-images.coffee
+++ src/scripts/google-images.coffee
@@ -29,7 +29,7 @@

 imageMe = (msg, query, cb) ->
   msg.http('http://ajax.googleapis.com/ajax/services/search/images')
-    .query(v: "1.0", rsz: '8', q: query)
+    .query(v: "1.0", rsz: '8', q: query, safe: 'active')
     .get() (err, res, body) ->
       images = JSON.parse(body)
       images = images.responseData.results


Implemented PPU even/odd frame behaviour.

--- emulator/core/units/ppu.coffee
+++ emulator/core/units/ppu.coffee
@@ -50,6 +50,7 @@
         @renderedSprite = null  # Currently rendered sprite
         @delayedNMI = false     # Whether to generate NMI after next instruction
         @suppressVBlank = false # Whether to supress VBLANK flag setting and NMI generation during VBLANK
+        @oddFrame = false       # Whether odd frame is being rendered

     setNTSCMode: (ntscMode) ->
         @ntscMode = ntscMode
@@ -390,8 +391,12 @@
     incementScanline: ->
         @cycle = 0
         @scanline++
-        @scanline = 0 if @scanline > 261
-
+        @incrementFrame() if @scanline > 261
+
+    incrementFrame: ->
+        @scanline = 0
+        @cycle++ if @oddFrame and @$isRenderingEnabled()
+        @oddFrame = not @oddFrame

     ###########################################################
     # Background rendering


Done. Should add character/column detail and evidence in the near future but so far it works.

--- src/commandline.coffee
+++ src/commandline.coffee
@@ -7,7 +7,7 @@

 path = require("path")
-fs   = require("fs")
+fs     = require("fs")
 glob = require("glob")
 optimist = require("optimist")
 thisdir = path.dirname(fs.realpathSync(__filename))
@@ -75,12 +75,12 @@

     stylize : (message, styles...) ->
         map = {
-            bold  : [1,  22],
+            bold    : [1,    22],
             yellow: [33, 39],
             green: [32, 39],
             red: [31, 39]
         }
-        return styles.reduce (m, s)  ->
+        return styles.reduce (m, s)    ->
             return "\u001b[" + map[s][0] + "m" + m + "\u001b[" + map[s][1] + "m"
         , message

@@ -115,7 +115,7 @@
             [@warn, 'yellow']
         else
             [@ok, 'green']
-        @print "  #{overall} #{@stylize(path, color, 'bold')}"
+        @print "    #{overall} #{@stylize(path, color, 'bold')}"
         for e in errors
             o = if e.level == 'error' then @err else @warn
             msg = "     " +
@@ -137,6 +137,27 @@
                 f = [path, e.lineNumber, e.level, e.message]
                 @print f.join(",")

+# Outputs a JSLint-like XML Report
+class JSLintReporter extends Reporter
+
+    publish : () ->
+        @print "<?xml version=\"1.0\" encoding=\"utf-8\"?><jslint>"
+
+        for path, errors of @errorReport.paths
+            if errors.length
+                @print "<file name=\"#{path}\">"
+
+                for e in errors
+                    @print """
+                    <issue line="#{e.lineNumber}"
+                            reason="[#{e.level}] #{e.message}"
+                            evidence=""/>
+                    """
+                @print "</file>"
+
+        @print "</jslint>"
+
+
 # Return an error report from linting the given paths.
 lintFiles = (paths, config) ->
     errorReport = new ErrorReport()
@@ -152,7 +173,12 @@

 # Publish the error report and exit with the appropriate status.
 reportAndExit = (errorReport, options) ->
-    ReporterClass = if options.argv.csv then CSVReporter else Reporter
+    if options.argv.jslint
+        ReporterClass = JSLintReporter
+    else if options.argv.csv
+        ReporterClass = CSVReporter
+    else
+        ReporterClass = Reporter
     reporter = new ReporterClass(errorReport)
     reporter.publish()
     process.exit(errorReport.getExitCode())
@@ -169,8 +195,10 @@
             .describe("v", "Print current version number.")
             .describe("r", "Recursively lint .coffee files in subdirectories.")
             .describe("csv", "Use the csv reporter.")
+            .describe("jslint", "Use the JSLint XML reporter.")
             .describe("s", "Lint the source from stdin")
             .boolean("csv")
+            .boolean("jslint")
             .boolean("r")
             .boolean("s")

@@ -204,5 +232,4 @@

         # Lint the code.
         errorReport = lintFiles(scripts, config)
-        reportAndExit errorReport, options
-
+        reportAndExit errorReport, options


Add conflicting dependency checks

Add conflicting dependency checks

--- picksel.coffee
+++ picksel.coffee
@@ -271,6 +271,19 @@
     callback(!error && response.statusCode == 200)

+# Checks whether or not a dependency is already present.
+#
+# @param [Number] id the ID of the dependency to check for
+# @param [String] id the resolution of the dependency to check for
+# @param [Destination] id the destination of the dependency to check for
+#
+alreadyHasImage = (id, resolution, destination) ->
+  duplicates = project.images.filter (obj) ->
+    (obj.id == id && obj.resolution == resolution) || \
+    obj.destination == destination
+  duplicates.length > 0
+
+
 # Adds an image as an asset.
 #
 # @param [Object] args the arguments to the program
@@ -285,23 +298,29 @@
       log.error 'You didn\'t provide enough arguments! Like this:' \
         + ' \'picksel add <id> <resolution> <destination>\'' # ID not passed in.
   else
-    if isValidResolutionCode resolution
-      validateId id, (success) ->
-        if success
-          image =
-            id: id
-            resolution: resolution
-            destination: destination
-          project.images.push image # Add new image to project file.
-          persistProject null, (err) -> # Update project file.
-            if err
-              log.error 'Error writing your project file to disk!'
-            else
-              log.info "Added image with ID '#{image.id}' as asset." # Success!
-        else
-          log.error "That ID #{id} isn't valid." # Invalid ID
-    else
-      log.error "That resolution '#{resolution}' isn't valid." # Bad resolution.
+    if alreadyHasImage id, resolution, destination
+      log.warning 'A conflicting dependency is already present in your' \
+        + ' project!' # No exact duplicates.
+    else
+      if isValidResolutionCode resolution
+        validateId id, (success) ->
+          if success
+            image =
+              id: id
+              resolution: resolution
+              destination: destination
+            project.images.push image # Add new image to project file.
+            persistProject null, (err) -> # Update project file.
+              if err
+                log.error 'Error writing your project file to disk!'
+              else
+                # Success!
+                log.info "Added image with ID '#{image.id}' as asset."
+          else
+            log.error "That ID #{id} isn't valid." # Invalid ID
+      else
+        # Bad resolution.
+        log.error "That resolution '#{resolution}' isn't valid."

 # Removes an image from installed assets.


Require correct file in trinary tests

Require correct file in trinary tests

--- trinary/trinary_test.spec.coffee
+++ trinary/trinary_test.spec.coffee
@@ -1,4 +1,4 @@
-Trinary = require './example'
+Trinary = require './trinary'

 describe 'Trinary', ->


Run  handler works if no stop handler

Run  handler works if no stop handler

--- src/config/game.coffee
+++ src/config/game.coffee
@@ -2,7 +2,7 @@
   _LABEL: "game"
   interval: 500
   speed: 0
-  _EXPORTS: ["run", "stop"]
+  _EXPORTS: ["run"]
   run: (world, args) ->
     world.put('speed', 1)
     step_and_repeat = (self) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -147,7 +147,7 @@
           moduleName = Module
           Module = Module.constantize()
         else
-
+          for key

         unless I.included_modules.include moduleName
           I.included_modules.push moduleName


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -160,7 +160,7 @@

           if moduleName
             unless I.includedModules.include moduleName
-              I.included_modules.push moduleName
+              I.includedModules.push moduleName
               self.extend Module(I, self)
           else
             warn "Unable to discover name for module: ", Module


Set menu bar visible when no longer autohiding

--- src/atom.coffee
+++ src/atom.coffee
@@ -730,3 +730,5 @@

   setAutoHideMenuBar: (autoHide) ->
     ipc.send('call-window-method', 'setAutoHideMenuBar', autoHide)
+    unless autoHide
+      ipc.send('call-window-method', 'setMenuBarVisibility', true)


declutter the sign-in/sign-up page further

--- src/smc-webapp/landing_page.cjsx
+++ src/smc-webapp/landing_page.cjsx
@@ -482,6 +482,7 @@

     # this is an info blob on the landing page, clarifying to the user that "free" is a perpetual trial
     render_trial_info: ->
+        return # try disabling this -- it's clutter.
         if not @props.is_commercial
             return
         <React.Fragment>
@@ -500,10 +501,12 @@
                     for special options.

             </Alert>
-

-                If you have any questions or comments, create a <ShowSupportLink />.
-

         </React.Fragment>
+
+    render_support: ->
+

+            Questions? Create a <ShowSupportLink />.
+

     render_main_page: ->
         if @props.remember_me and not @props.get_api_key
@@ -573,7 +576,7 @@
                   <div style={ display          : 'inline-block', \
                                backgroundImage  : "url('#{topbar.img_icon}')", \
                                backgroundSize   : 'contain', \
-                               height           : 55, width: 55, \
+                               height           : 75, width: 75, \
                                margin           : 5,\
                                verticalAlign    : 'center',\
                                backgroundRepeat : 'no-repeat'}>
@@ -584,8 +587,8 @@
                               fontSize         : "28px",\
                               top              : UNIT,\
                               left             : UNIT * 7,\
-                              width            : 250,\
-                              height           : 55,\
+                              width            : 300,\
+                              height           : 75,\
                               position         : 'absolute',\
                               color            : topbar.color,\
                               opacity          : topbar.img_opacity,\
@@ -618,10 +621,12 @@
                         />
                 </Col>
                 <Col sm={6}>
-

+

                         Create a new account to the left or sign in with an existing account above.

                         {@render_trial_info()}
+

+                        {@render_support()}

                         {
                             if not @props.get_api_key


terminal: defend against absence of remote object

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -102,6 +102,10 @@
           @reconnectionInPrgress = false
           throw err

+      unless remote?
+        console.warn "Terminal: No remote object was received!"
+        return
+
       @setOption "session", remote.session
       @terminal.eventHandler = (data)=> @emit "WebTermEvent", data
       @terminal.server       = remote


append only the folder select list

append only the folder select list

--- client/app/views/konnectors.coffee
+++ client/app/views/konnectors.coffee
@@ -21,7 +21,7 @@
             success: =>
                 request.get 'folders', (err, paths) =>
                     for path in paths
-                        $("select").append """
+                        $(".folder").append """
                         <option value="#{path}">#{path}</option>
                         """


Add back return if no query in autorun

Add back return if no query in autorun

--- client/controllers/eventMap.coffee
+++ client/controllers/eventMap.coffee
@@ -45,6 +45,7 @@
       totalEventCount = allEvents.length
     else
       map.removeLayer instance.mapMarkers
+      return

     filteredMapLocations = instance.filteredMapLocations = {}
     templateEvents = []


fix url variable name

fix url variable name

--- client/lanes/workspace/Navbar.cjsx
+++ client/lanes/workspace/Navbar.cjsx
@@ -14,13 +14,11 @@
             "menu-hidden": !@model.screen_menu_shown

     Logo: ->
-        if @settings.logo?.url
+        if @settings.logo?.thumbnail_url
             url = "//#{Lanes.config.api_host}#{@settings.logo.thumbnail_url}"

         else
-
-                {Lanes.Extensions.controlling().title()}
-
+            {Lanes.Extensions.controlling().title()}

     render: ->


An intermeadiry commit

An intermeadiry commit

--- client/src/ActionController.coffee
+++ client/src/ActionController.coffee
@@ -84,13 +84,27 @@
         else
             return false

+    deactivateRecipe: (recipeName) =>
+        recipe = @recipes[recipeName]
+        if(@recipeState[recipeName] == 'active')
+
+            setTimeout (recipe.tearDownDelay or 0)=>
+                @executeAction(recipe.tearDown)
+                @recipeState[recipeName] = 'inactive'
+
+
+
     tearDownRecipe: (recipeName) =>
         recipe = @recipes[recipeName]
         actionName = recipe.tearDown
         if(@recipeState[recipeName] == 'active')
             console.log "Tear down: #{recipeName}"
-            @executeAction(actionName)
-            @recipeState[recipeName] = 'inactive'
+            if(recipe.tearDownDelay)
+                console.log "Tear down: #{recipeName}"
+                setTimeout(()=>
+                    @executeAction(actionName)
+                    @recipeState[recipeName] = 'inactive'
+                ), recipe.TearDownDelay
             return true
         else
             return false