Allow events to fire after a changeset is recvd

This might be a mistake, since I'd previously deliberatly set the
'silent' option.  Don't remember why though and it's messing other stuff
up when events don't fire

--- client/lanes/models/Base.coffee
+++ client/lanes/models/Base.coffee
@@ -41,7 +41,7 @@
     # used by PubSub to record a remote change to the model
     addChangeSet: (change) ->
         change = new Lanes.Models.ChangeSet(change)
-        this.set( change.value(), silent: true )
+        this.set( change.value() )
         for name, value of change.value()
             this.trigger("remote-update:#{name}", "changeset", this, change)
         this.triggerChangeSet(this, change)


remove verbose logging from controller fetches

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- coffee/controllers/main.coffee
+++ coffee/controllers/main.coffee
@@ -11,8 +11,7 @@
     $scope.fetch = ()->
       Restangular.all('nodes').getList().
         then (ns) ->
-          console.log 'fetch nodes: '
-          console.log ns
+          console.log 'fetch nodes'
           $scope.nodes = ns
         , (error) ->
           console.log 'EE: cannot fetch data'
@@ -21,8 +20,7 @@
     $scope.fetch_node = (id)->
       Restangular.one('node', id).get().
         then (n) ->
-          console.log 'fetch node '+id+': '
-          console.log n
+          console.log 'fetch node '+id
           n['id'] = id
           $scope.node[id] = n
         , (error) ->


cover object type for compatibility

cover object type for compatibility

--- coffee/services/account.coffee
+++ coffee/services/account.coffee
@@ -25,12 +25,36 @@

   ###
+   decrypt object. this is used for compatibility.
+  ###
+  _decryptObject = (obj) ->
+    return CryptoJS.AES.decrypt(obj, PHRASE).toString(CryptoJS.enc.Utf8)
+
+
+  ###
+   decrypt string.
+  ###
+  _decryptString = (str) ->
+    return CryptoJS.AES.decrypt(_Json.parse(str), PHRASE).toString(CryptoJS.enc.Utf8)
+
+
+  ###
+   decrypt according it type.
+  ###
+  _decrypt = (any) ->
+    if typeof any is "string"
+      return _decryptString(any)
+    else
+      return _decryptObject(any)
+
+
+  ###
    decrypt the account data, only to sync on chrome.
   ###
-  _decrypt = () ->
-    @apiKey = CryptoJS.AES.decrypt(_Json.parse(@apiKey), PHRASE).toString(CryptoJS.enc.Utf8)
-    @id     = CryptoJS.AES.decrypt(_Json.parse(@id), PHRASE).toString(CryptoJS.enc.Utf8)
-    @pass   = CryptoJS.AES.decrypt(_Json.parse(@pass), PHRASE).toString(CryptoJS.enc.Utf8)
+  _decryptAuth = () ->
+    @apiKey = _decrypt @apiKey
+    @id     = _decrypt @id
+    @pass   = _decrypt @pass

   ###
@@ -63,7 +87,7 @@
           callback null
         else
           for a in item[ACCOUNTS]
-            _decrypt.apply(a)
+            _decryptAuth.apply(a)
           _accounts = item[ACCOUNTS]
           callback item[ACCOUNTS]

@@ -88,7 +112,7 @@
           if chrome.runtime.lastError?
             callback false
           else
-            _decrypt.apply(account)
+            _decryptAuth.apply(account)
             for a, i in _accounts when a.url is account.url
               _accounts.splice i, 1
               break


Update docker.coffee

Update docker.coffee

--- src/docker.coffee
+++ src/docker.coffee
@@ -30,7 +30,7 @@
   image: (lang) ->
     fs.appendFileSync @file, "FROM jakhu/#{lang}:latest\nRUN sudo chown -R jakhu /runner\n"
   cwd: () ->
-    fs.appendFileSync @file, 'CMD bash -c "sudo chmod 777 /runner/bin/jakhurun && source /home/jakhu/.rvm/scripts/rvm && rvm use ruby-head && ~/.jakhu/bin/jakhurun --version"'
+    fs.appendFileSync @file, 'CMD bash -c "sudo chmod 777 /runner/bin/jakhurun && source /home/jakhu/.rvm/scripts/rvm && rvm use ruby-head && /runner/bin/jakhurun start"'


:lipstick:

:lipstick:

--- src/editor.coffee
+++ src/editor.coffee
@@ -2576,7 +2576,6 @@
   getRowsPerPage: ->
     Math.max(1, Math.ceil(@getHeight() / @getLineHeightInPixels()))

-
   ###
   Section: Config
   ###
@@ -2593,7 +2592,6 @@
     else
       @displayBuffer.setInvisibles(null)

-
   ###
   Section: Event Handlers
   ###


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -2,7 +2,8 @@
 The Follow module provides a simple method to set an object's
 velocity so that it will approach another object.

-This method relies on both objects having `position` methods. GameObject
+This method relies on both objects having `position` methods.
+GameObject
 provides this method by default.

 <code><pre>


Add bare option to list command

Add bare option to list command

Allows you to generate a packages file to specify to apm install

Closes #194

--- src/list.coffee
+++ src/list.coffee
@@ -27,22 +27,31 @@

       Usage: apm list
              apm list --themes
+             apm list --installed
+             apm list --installed --bare > my-packages.txt

       List all the installed packages and also the packages bundled with Atom.
     """
+    options.alias('b', 'bare').boolean('bare').describe('bare', 'Print packages one per line with no formatting')
     options.alias('h', 'help').describe('help', 'Print this usage message')
+    options.alias('i', 'installed').boolean('installed').describe('installed', 'Only list installed packages/themes')
     options.alias('t', 'themes').boolean('themes').describe('themes', 'Only list themes')
-    options.alias('i', 'installed').boolean('installed').describe('installed', 'Only list installed packages/themes')

   isPackageDisabled: (name) ->
     @disabledPackages.indexOf(name) isnt -1

-  logPackages: (packages) ->
-    tree packages, (pack) =>
-      packageLine = pack.name
-      packageLine += "@#{pack.version}" if pack.version?
-      packageLine += ' (disabled)' if @isPackageDisabled(pack.name)
-      packageLine
+  logPackages: (packages, options) ->
+    if options.argv.bare
+      for pack in packages
+        packageLine = pack.name
+        packageLine += "@#{pack.version}" if pack.version?
+        console.log packageLine
+    else
+      tree packages, (pack) =>
+        packageLine = pack.name
+        packageLine += "@#{pack.version}" if pack.version?
+        packageLine += ' (disabled)' if @isPackageDisabled(pack.name)
+        packageLine
     console.log()

   listPackages: (directoryPath, options) ->
@@ -65,14 +74,16 @@

   listUserPackages: (options) ->
     userPackages = @listPackages(@userPackagesDirectory, options)
-    console.log "#{@userPackagesDirectory.cyan} (#{userPackages.length})"
-    @logPackages(userPackages)
+    unless options.argv.bare
+      console.log "#{@userPackagesDirectory.cyan} (#{userPackages.length})"
+    @logPackages(userPackages, options)

   listDevPackages: (options) ->
     devPackages = @listPackages(@devPackagesDirectory, options)
     if devPackages.length > 0
-      console.log "#{@devPackagesDirectory.cyan} (#{devPackages.length})"
-      @logPackages(devPackages)
+      unless options.argv.bare
+        console.log "#{@devPackagesDirectory.cyan} (#{devPackages.length})"
+      @logPackages(devPackages, options)

   listBundledPackages: (options, callback) ->
     config.getResourcePath (resourcePath) =>
@@ -87,11 +98,13 @@
       packages = packages.filter ({name}) ->
         packageDependencies.hasOwnProperty(name)

-      if options.argv.themes
-        console.log "#{'Built-in Atom themes'.cyan} (#{packages.length})"
-      else
-        console.log "#{'Built-in Atom packages'.cyan} (#{packages.length})"
-      @logPackages(packages)
+      unless options.argv.bare
+        if options.argv.themes
+          console.log "#{'Built-in Atom themes'.cyan} (#{packages.length})"
+        else
+          console.log "#{'Built-in Atom packages'.cyan} (#{packages.length})"
+
+      @logPackages(packages, options)
       callback()

   listInstalledPackages: (options) ->


Add piece

Add piece


added smooth scroll for chevron

added smooth scroll for chevron

--- components/layout/index.coffee
+++ components/layout/index.coffee
@@ -44,6 +44,11 @@
     section = $(e.currentTarget).data 'section'
     $('html,body').animate { scrollTop: $("##{section}").offset().top - 140 }, 1000

+  $('body').on 'click', 'a.header-chevron-link', (e) ->
+    e.preventDefault()
+    destination = e.currentTarget.hash
+    $('html,body').animate { scrollTop: $(destination).offset().top - 140 }, 1000
+
   rotateHeaderBanner = ->
     $active = $('.header-slide.active-banner')
     $next = if $active.next('.header-slide').length then $active.next() else $('.header-slide:first')


teams: landing: made title/subtitle selective

--- client/landing/site.landing/coffee/teams/AppView.coffee
+++ client/landing/site.landing/coffee/teams/AppView.coffee
@@ -22,6 +22,14 @@
       ]

     if KD.config.hasTeamAccess
+      @title = new KDCustomHTMLView
+        tagName : 'h1'
+        partial : "Koding for Teams!"
+
+      @subTitle = new KDCustomHTMLView
+        tagName : 'h2'
+        partial : 'Onboard, develop, deploy, test and work together with your team right away, without a setup!'
+
       @form = new TeamsSignupForm
         cssClass : 'TeamsModal--middle login-form'
         callback : (formData) ->
@@ -37,6 +45,13 @@
       @form = new TeamTeamsModal
         cssClass : 'TeamsModal--middle login-form'
         callback : (formData) ->
+      @title = new KDCustomHTMLView
+        tagName : 'h1'
+        partial : "Introducing Koding for Teams!"
+
+      @subTitle = new KDCustomHTMLView
+        tagName  : 'h2'
+        partial  : "Your own Koding for your companyuniversityclassproject"

@@ -46,8 +61,8 @@
     """
     {{> @header }}

-
Koding for Teams

-
Onboard, develop, deploy, test and work together with your team right away, without a setup!

+      {{> @title}}
+      {{> @subTitle}}
       {{> @form}}


Add mappingsForKeystrokeMatchingElement

Add mappingsForKeystrokeMatchingElement

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -82,8 +82,7 @@
     element = event.target
     element = rootView[0] if element == document.body
     keystroke = @keystrokeStringForEvent(event, @queuedKeystroke)
-    mappings = @mappingsForKeystroke(keystroke)
-    mappings = @mappingsMatchingElement(mappings, element)
+    mappings = @mappingsForKeystrokeMatchingElement(keystroke, element)

     if mappings.length == 0 and @queuedKeystroke
       @queuedKeystroke = null
@@ -117,6 +116,10 @@
         mappings.push @buildMapping(bindingSet, command, keystroke)

     mappings
+
+  mappingsForKeystrokeMatchingElement: (keystroke, element) ->
+    mappings = @mappingsForKeystroke(keystroke)
+    @mappingsMatchingElement(element, mappings)

   mappingsForKeystroke: (keystroke) ->
     mappings = @allMappings().filter (mapping) ->


registry: drop support for unregistering from register

--- test/api/unregister.coffee
+++ test/api/unregister.coffee
@@ -19,11 +19,6 @@
       mecano.unregister 'my_function'
       mecano.registered('my_function').should.be.false()

-    it 'alias register without an handler', ->
-      mecano.register 'my_function', -> 'my_function'
-      mecano.register 'my_function', null
-      mecano.registered('my_function').should.be.false()
-
   describe 'local', ->

     it 'remove property', ->
@@ -40,12 +35,6 @@
       .unregister 'my_function'
       .registered('my_function').should.be.false()

-    it 'alias register without an handler', ->
-      mecano()
-      .register 'my_function', -> 'my_function'
-      .register 'my_function', null
-      .registered('my_function').should.be.false()
-
   describe 'mixed', ->

     it.skip 'throw error if unregistering from local', (next) ->


test cleanup

test cleanup

--- test/book-list-test.coffee
+++ test/book-list-test.coffee
@@ -302,7 +302,6 @@
     it 'and it should reply with a response indicating that the booklist was loaded', ->
       expect(room.robot.emit.firstCall.args[1].content.title).equals("Booklist re-loaded")

-
     describe 'user asks hubot to load a booklist when there are books saved', ->

       beforeEach (done) ->
@@ -311,5 +310,4 @@
         setTimeout done, 1000

       it 'and it should reply with a list of books', ->
-        console.log room.robot.emit.firstCall.args[1].content.title
         expect(room.robot.emit.firstCall.args[1].content.title).to.match(/Booklist - (\d{1,100}) books$/)


Pends a test

Pends a test

--- test/models/article.coffee
+++ test/models/article.coffee
@@ -60,7 +60,7 @@
     it 'fetches related articles for super articles', (done) ->
       @article.set sections: []
       @article.fetchWithRelated success: (data) ->
-        _.defer => _.defer =>
+        _.defer => _.defer => _.defer =>
           data.relatedArticles.models[0].get('title').should.equal 'RelatedArticle'
           data.article.get('title').should.equal 'SuperArticle'
           done()


Force autosuggest.

Force autosuggest.

--- src/tour.coffee
+++ src/tour.coffee
@@ -31,8 +31,10 @@
                 $input.focus()
                 $input.typeahead('val', '')
                 # TODO: translate example query
+                $input.focus()
                 $input.typeahead('val', 'terve').focus()
                 $input.val 'terve'
+                $input.click()
             onHide: ->
                 $container = $('#search-region')
                 $input = $container.find('input')


Clean some trailing whitespaces

Clean some trailing whitespaces

--- spec/Subtree.coffee
+++ spec/Subtree.coffee
@@ -285,9 +285,9 @@
           , (n, chosen) -> # until
             return false if n.availableItems().length
             true
-
+
       Root()
-      .deliver
+      .deliver
         items: [
             id: 1
           ,
@@ -301,7 +301,7 @@
       .finally (n, results) ->
         chai.expect(results.length).to.equal 3
         done()
-
+
     it 'should work w/ thrown failedComponent', (done) ->

       failedComponent = (n,d) ->


Deprecate all Marker::on calls

Deprecate all Marker::on calls

--- src/marker.coffee
+++ src/marker.coffee
@@ -124,6 +124,8 @@
         Grim.deprecate("Use Marker::onDidChange instead")
       when 'destroyed'
         Grim.deprecate("Use Marker::onDidDestroy instead")
+      else
+        Grim.deprecate("Marker::on is deprecated. Use documented event subscription methods instead.")

     EmitterMixin::on.apply(this, arguments)


fixing the output so it works with require.js

--- src/parser.coffee
+++ src/parser.coffee
@@ -63,11 +63,11 @@
         item
     """
 // #{@filePath}
-var #{scriptName(@filePath)} = (function(exports) {
-  var module = { exports: exports };
+var #{scriptName(@filePath)} = (function() {
+  var module = { exports: {} };
   #{output.join('')}
-  return exports;
-})({})
+  return module.exports;
+})()
 """

 # object to hold all of the parsed script in ordered dependencies


Dev. checkpoint.

Dev. checkpoint.

--- src/Argument.coffee
+++ src/Argument.coffee
@@ -210,10 +210,10 @@
       @d.user_pat ?= "!>NUM<"

     is_a_match_with: (unk) ->
-      !isNaN( parseFloat(unk) )
-
-    convert: (unk) ->
-      parseFloat(unk)
+      !isNaN( Number(unk) )
+
+    convert: (unk) ->
+      Number(unk)

   @Noun:
     d: {}


uniform output

uniform output

--- src/plugin.coffee
+++ src/plugin.coffee
@@ -99,19 +99,29 @@
     }

     parser = PEG.buildParser grammar, options
-    return parser  if options.output is 'source'
+    if options.output is 'source'
+      return """(function(){
+      var original = #{parser};
+        var fun = original.parse;
+        fun.SyntaxError = original.SyntaxError;
+        fun.parse = original.parse;
+        return fun;
+      })()
+      """

     # FIXME pegjs should throw an exception if startRule is not defined
-    {SyntaxError, parse} = parser
+    parser._parse = parser.parse

-    fun = (input) ->
-      parse input, {startRule}
-    fun._ = {
+    parser.parse = (input, options = {}) ->
+      _.defaults options, {
+        startRule
+      }
+      parser._parse input, options
+    parser._ = {
       grammar
       options
     }
-    fun.SyntaxError = SyntaxError
-    fun
+    parser

   mod._ = {
     grammar


prevent recursion in xhtml schema

prevent recursion in xhtml schema

--- src/schema.coffee
+++ src/schema.coffee
@@ -15,9 +15,9 @@
         "progress rp rt ruby samp summary time"
     'strict': -> # xhtml1
         "#{do schema.html}" # FIXME
+    'xhtml': ->
+        "#{do schema.html}" # FIXME
     'xhtml1.1': ->
-        "#{do schema.xhtml}" # FIXME
-    'xhtml': ->
         "#{do schema.xhtml}" # FIXME
     'frameset': ->
         "#{do schema.xhtml}" # FIXME
@@ -42,7 +42,7 @@
     'html-ce': ->  "#{do self_closing.xhtml}"
     'strict'  : -> "#{do self_closing.xhtml}"
     'xhtml1.1': -> "#{do self_closing.xhtml}"
-    'xhtml'   : -> "#{do self_closing.xhtml}"
+    'xhtml'   : -> "#{do self_closing.html}"
     'frameset': -> "#{do self_closing.xhtml}"
     'transitional': -> "#{do self_closing.xhtml}"


Minor codefix

Minor codefix

--- core/KDWindowController.coffee
+++ core/KDWindowController.coffee
@@ -64,15 +64,15 @@
       @setWindowProperties event
       @notifyWindowResizeListeners event

-    timer = null;
-    documentBody = document.body;
+    timer = null
+    body  = {document}
     document.onscroll = =>
       clearTimeout timer
-      if not document.body.classList.contains('onscroll')
-        documentBody.classList.add('onscroll')
+      if not body.classList.contains 'onscroll'
+        body.classList.add 'onscroll'

       timer = KD.utils.wait 200, ->
-        documentBody.classList.remove('onscroll')
+        body.classList.remove 'onscroll'

     addListener "dragenter", (event)=>
       unless @dragInAction


update

update

--- src/server.coffee
+++ src/server.coffee
@@ -2,31 +2,36 @@
 Router      = express.Router
 router      = new Router()
 staticCache = express.staticCache()
-stc         = express.static(__dirname + '/server', {redirect: false})
+static_     = express.static __dirname + '/server', {redirect: false}

 plural_commands =
   get: ['statistics']

-single_commands =
-  post: ['reschedule','clear']
-  get: ['statistics','recently_finished_tasks','failed_tasks','slowest_tasks', 'processing_tasks','workers']
+singular_commands =
+  get: ['statistics', 'recently_finished_tasks', 'failed_tasks', 'slowest_tasks', 'processing_tasks', 'workers']
+  post:['reschedule', 'clear']

 exports = module.exports = (options) ->
   fairy = require('../.').connect options
-  for key,value of plural_commands
-    for element in value
-      router.route key, '/api/queues/'+ element, (req, res) ->
-        fairy.statistics (err, results) ->
-          return res.send 500, err.stack if err
-          res.send results
-  for key,value of single_commands
-    for element in value
-      router.route key, '/api/queues/:name/'+ element, (req, res) ->
-        queue = fairy.queue req.params.name
-        queue[element] (err, results) ->
-          return res.send 500, err.stack if err
-          res.send results
+
+  for method, commands of plural_commands
+    for command_ in commands
+      do (command_) ->
+        router.route method, "/api/queues/#{command_}", (req, res) ->
+          fairy[command_] (err, results) ->
+            return res.send 500, err.stack if err
+            res.send results
+
+  for method, commands of singular_commands
+    for command_ in commands
+      do (command_) ->
+        router.route method, "/api/queues/:name/#{command_}", (req, res) ->
+          queue = fairy.queue req.params.name
+          queue[command_] (err, results) ->
+            return res.send 500, err.stack if err
+            res.send results
+
   (req, res, next) ->
     router.middleware req, res, ->
       staticCache req, res, ->
-        stc(req, res, next)
+        static_ req, res, next


server is now async

server is now async

--- src/server.coffee
+++ src/server.coffee
@@ -4,7 +4,7 @@
 jit = require 'express-jit-coffee'

 exports.construct = ({ serve }) ->
-  ({ port, root, notFound }) ->
+  ({ port, root, notFound }, callback) ->
     dir = path.resolve(process.cwd(), root)
     fourofour = if notFound? then path.relative(root, notFound) else null

@@ -18,5 +18,5 @@

       app.use(serve(dir))

-    http.createServer(app).listen(port)
-    { dir, fourofour }+    http.createServer(app).listen port, ->
+      callback(null, { dir, fourofour })


Sync entire dir (diff updates seem to clear the entire directory)

--- src/server.coffee
+++ src/server.coffee
@@ -60,7 +60,9 @@

 queryDHIS2 = (req, res, lastSync, openhimTransactionID, orchestrations, callback) ->
   path = "#{getBasePath()}/api/metaData"
-  query = "assumeTrue=false&organisationUnits=true&lastUpdated=#{lastSync}"
+  # TODO for now sync entire directory - need to figure out how to do diff updates CSD-side
+  #query = "assumeTrue=false&organisationUnits=true&lastUpdated=#{lastSync}"
+  query = "assumeTrue=false&organisationUnits=true"
   url = "http://#{config.getConf().dhis2.host}:#{config.getConf().dhis2.port}#{path}?#{query}"

   logger.info "[#{openhimTransactionID}] Querying DHIS2 #{url}"


randomize death order

randomize death order

--- evolution.coffee
+++ evolution.coffee
@@ -563,9 +563,11 @@
   # give every organism an opportunity to eat or starve
   # returns whether any organisms remain alive
   die: ->
-    @visitThings (t) =>
+    mortal = []
+    @visitThings (t) ->
       return if t.dead
-      return unless t.type and t instanceof Organism
+      mortal.push t if t.type and t instanceof Organism
+    for t in shuffle mortal
       switch t.type
         when Herbivore
           for other in t.touching()
@@ -580,14 +582,10 @@
       else if t.isSick()
         t.expose other for other in t.touching() when other.type? and other.type == t.type
     @dead = true
-    for column in @cells
-      break unless @dead
-      for cell in column
-        break unless @dead
-        for t in cell.inhabitants
-          if t instanceof Organism
-            @dead = false
-            break
+    for t in mortal
+      unless t.dead
+        @dead = false
+        break
     @dead
   # set temporary outlines
   highlightTopic: (turnOn=true) ->


Material: Add extensions and post_transform_lines to vertex modifiers.

--- engine/material.coffee
+++ engine/material.coffee
@@ -183,10 +183,19 @@

             modifiers_uniforms = []
             modifiers_bodies = []
+            modifiers_post_bodies = []
+            required_extensions = {}
             for m in @vertex_modifiers
-                {uniform_lines, body_lines} = m.get_code()
+                {uniform_lines, body_lines, post_transform_lines, extensions} = m.get_code()
                 modifiers_uniforms = modifiers_uniforms.concat uniform_lines
                 modifiers_bodies = modifiers_bodies.concat body_lines
+                modifiers_post_bodies = modifiers_post_bodies.concat post_transform_lines if post_transform_lines?
+                for e in extensions ? []
+                    required_extensions[e] = 1
+
+            extension_lines = []
+            for e of required_extensions
+                extension_lines.push "#extension #{e} : require"

             varyings_decl = []
             varyings_assign = []
@@ -285,12 +294,13 @@
                     "vec4 view_co = #{var_model_view_matrix} * co;"
                     "vec4 proj_co = #{var_projection_matrix} * view_co;"
                 ])...
+                modifiers_post_bodies...
                 varyings_assign...
                 "gl_Position = proj_co;\n}"
             ].join('\n  ')

-            vs = vs_head.concat(
-                attribute_lines, modifiers_uniforms, varyings_decl, vs_body
+            vs = extension_lines.concat(
+                vs_head, attribute_lines, modifiers_uniforms, varyings_decl, vs_body
             ).join '\n'

         if is_webgl2 and glsl_version == 300


Bail on completion if no backend

Bail on completion if no backend

--- lib/autocomplete-haskell.coffee
+++ lib/autocomplete-haskell.coffee
@@ -39,6 +39,7 @@
     disableForSelector: '.source.haskell .comment'
     inclusionPriority: 0
     getSuggestions: (options) =>
+      return [] unless @backend?
       (new SuggestionBuilder options, @backend).getSuggestions()

   consumeCompBack_0_1_0: (service) ->


Set styles on element directly

Set styles on element directly

--- lib/bracket-matcher-view.coffee
+++ lib/bracket-matcher-view.coffee
@@ -80,12 +80,12 @@

   moveHighlightView: (view, bufferPosition, pixelPosition) ->
     view.bufferPosition = bufferPosition
-    view.css
-      display: 'block'
-      top: pixelPosition.top
-      left: pixelPosition.left
-      width: @editor.charWidth
-      height: @editor.lineHeight
+    [element] = view
+    element.style.display = 'block'
+    element.style.top = "#{pixelPosition.top}px"
+    element.style.left = "#{pixelPosition.left}px"
+    element.style.width = "#{@editor.charWidth}px"
+    element.style.height = "#{@editor.lineHeight}px"

   moveHighlightViews: (bufferRange) ->
     {start, end} = Range.fromObject(bufferRange)


Fix task.cancel().

Fix task.cancel().

--- src/api/task.coffee
+++ src/api/task.coffee
@@ -2,12 +2,7 @@

 #=====================================================================

-cancel = $coroutine ({id}) ->
-  try
-    task = @getObject id, 'task'
-  catch
-    @throw 'NO_SUCH_OBJECT'
-
+cancel = $coroutine ({task}) ->
   xapi = @getXAPI task

   $wait xapi.call 'task.cancel', task.ref


namespaced unbind

namespaced unbind

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -84,7 +84,7 @@

       if namespace
         eventCallbacks[event] = eventCallbacks.select (callback) ->
-          callbac
+          callback
       else
         if callback
           eventCallbacks[event].remove(callback)


Remove smoothing on canvas

Remove smoothing on canvas

--- lib/mixins/canvas-drawer.coffee
+++ lib/mixins/canvas-drawer.coffee
@@ -5,6 +5,7 @@
   initializeCanvas: ->
     @canvas = document.createElement('canvas')
     @context = @canvas.getContext('2d')
+    @canvas.webkitImageSmoothingEnabled = false
     @pendingChanges ?= []

     @offscreenCanvas = document.createElement('canvas')
@@ -17,6 +18,7 @@
     firstRow = @minimap.getFirstVisibleScreenRow()
     lastRow = @minimap.getLastVisibleScreenRow()
     intactRanges = @computeIntactRanges(firstRow, lastRow)
+
     if intactRanges.length is 0
       @drawLines(@context, firstRow, lastRow, 0)
     else


change name of variable

change name of variable

--- lib/ruby-debugger-client.coffee
+++ lib/ruby-debugger-client.coffee
@@ -6,7 +6,7 @@
 class RubyDebuggerClient
   constructor: () ->
     @client = null
-    @child = null
+    @server = null
     @cmdParser = new XmlParser()
     @cmdParser.on 'command', (command) => @handleCmd(command)

@@ -22,29 +22,29 @@

     cmd = [
       rdebugIdeBinPath
-      "--debug "
-      "--disable-int-handler "
-      "--evaluation-timeout 10 "
-      # "--rubymine-protocol-extensions "
+      "--debug"
+      "--disable-int-handler"
+      "--evaluation-timeout 10"
+      # "--rubymine-protocol-extensions"
       "--host #{host}"
       "--port #{port}"
-      # "--dispatcher-port 61514 "
+      # "--dispatcher-port 61514"
       "--"
       scriptToRun
     ].join(" ")

     console.log("running cmd: ", cmd, " in dir: ", projectDir)

-    @child = exec(cmd, cwd: projectDir)
-    @child.stdout.on 'data', (data) ->
+    @server = exec(cmd, cwd: projectDir)
+    @server.stdout.on 'data', (data) ->
       console.log 'stdout: ' + data
       return
-    @child.stderr.on 'data', (data) ->
+    @server.stderr.on 'data', (data) ->
       console.log 'stdout: ' + data
       return
-    @child.on 'close', (code) =>
+    @server.on 'close', (code) =>
       console.log 'closing code: ' + code
-      @child = null
+      @server = null
       return

     setTimeout =>
@@ -109,6 +109,6 @@
   destroy: ->
     # TODO: stop the debugger when closing project/editor & other events (which?). this method seems to only be run on Atom exit?
     @client?.end()
-    @child?.kill() # SIGTERM
-    # @child?.kill('SIGHUP')
+    @server?.kill() # SIGTERM
+    # @server?.kill('SIGHUP')


Optimize the lookup of other markers

Optimize the lookup of other markers

--- lib/search-results-model.coffee
+++ lib/search-results-model.coffee
@@ -53,13 +53,21 @@
   onContentsModified: =>
     return unless @searchModel.regex

+    isEqualToRange = (marker, range) ->
+      # Using marker.getBufferRange().compare() was slow on large sets. This is faster.
+      first = marker.bufferMarker.tailPosition or marker.bufferMarker.headPosition
+      last = marker.bufferMarker.headPosition
+      return false unless range.start.column == first.column and range.start.row == first.row
+      return false unless range.end.column == last.column and range.end.row == last.row
+      true
+
     rangesToAdd = []

     ranges = @findRanges()
     for range in ranges
       matchingMarker = null
       for marker in @markers
-        matchingMarker = marker if marker.getBufferRange().compare(range) == 0
+        matchingMarker = marker if isEqualToRange(marker, range)

       if matchingMarker and not matchingMarker.isValid()
         matchingMarker.bufferMarker.revalidate()


Small bug fix

Small bug fix

--- views/services/plugin-manager.cjsx
+++ views/services/plugin-manager.cjsx
@@ -494,6 +494,7 @@
       plugin.packageName = plugin.packageData.name
     else
       plugin.packageName = path.basename pluginPath
+    if !plugin.name? then plugin.name = plugin.packageName
     if !plugin.link?
       if pluginData[plugin.packageName]?
         plugin.link = pluginData[plugin.packageName].link


add a few more ignored errors

add a few more ignored errors

--- vue/src/shared/helpers/boot.coffee
+++ vue/src/shared/helpers/boot.coffee
@@ -30,6 +30,10 @@
             "MetaMask detected another web3",
             "AbortError: The operation was aborted",
             "ResizeObserver loop completed with undelivered notifications",
+            "TypeError: cancelled",
+            "UnhandledRejection: Non-Error promise rejection captured with value",
+            "ChunkLoadError: Loading chunk chunk-",
+            "TypeError: annulé",
             "Permission denied to access property \"dispatchEvent\" on cross-origin object"
           ]
           dsn: AppConfig.sentry_dsn


Removed require 'log'

Removed require 'log'

--- cakes.coffee
+++ cakes.coffee
@@ -1,5 +1,3 @@
-log = require 'log'
-
 colors = require 'colors'
 _ = require 'underscore'
 _.str = require 'underscore.string'


minor reorders in the billets

minor reorders in the billets

--- library/membrane/billets.coffee
+++ library/membrane/billets.coffee
@@ -130,14 +130,14 @@
     # the intermediate (client side) function that was supplied here.
     # The function itself is set to be invoked by autocall mechanism.
     @transferred: (intermediate) -> ->
-        noFun = "no valid intermediate function"
-        assert _.isFunction(intermediate), noFun
-        supplied = _.toArray arguments or Array()
-        intermediate = eval("[#{intermediate}]")[0]
-        x = _.isString intermediate.remote?.source
-        prepared = intermediate # default prepared
-        prepared = @autocall intermediate unless x
-        assert not _.isEmpty method = prepared or 0
+        noFunction = "no valid intermediate function"
+        assert _.isFunction(intermediate), noFuntion
+        supplied = _.toArray arguments or new Array()
+        intermediate = _.head eval "[#{intermediate}]"
+        x = _.isString try intermediate.remote?.source
+        prepared = intermediate # default prepared fnc
+        prepared = this.autocall intermediate unless x
+        assert not _.isEmpty method = prepared or null
         @prototype[_.uniqueId "__bts_trans_"] = method
         assert leaking = method.remote.leaking or {}
         i = _.isFunction; j = JSON.stringify # aliases


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -1,7 +1,6 @@
 ###*
 The Cooldown module provides a declarative way to manage cooldowns on
 GameObject's properties.
-

 player = GameObject


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -4,7 +4,7 @@

 player = GameObject
-  health:
+  health: 100

 player.include(Movable)


Make MPI reading compatible with go-crypto

--- src/ecc/curves.iced
+++ src/ecc/curves.iced
@@ -34,6 +34,9 @@
   mpi_bit_size : () ->
     # Rounding up needed for p521, and 3 bits needed to represent the leading 0x4
     2*@mpi_coord_bit_size() + 3
+
+  mpi_byte_size : () ->
+    Math.ceil(@mpi_bit_size() / 8)

   #----------------------------------

@@ -52,11 +55,14 @@
   _mpi_point_from_slicer_buffer : (sb) ->
     n_bits = sb.read_uint16()
     n_bytes = Math.ceil(n_bits/8)
+    if n_bytes isnt (b = @mpi_byte_size())
+      bits = @mpi_bit_size()
+      throw new Error "Need #{b} bytes (#{bits} bits) for this curve; got #{n_bytes} (#{n_bits} bits)"
     if sb.read_uint8() isnt 0x4
       throw new Error "Can only handle 0x4 prefix for MPI representations"
-    n_coord_bytes = (n_bytes - 1) / 2 # -1 byte for 0x4 prefix
-    [x,y] = [ BigInteger.fromBuffer(sb.read_buffer(n_coord_bytes)), BigInteger.fromBuffer(sb.read_buffer(n_coord_bytes))]
-    point = @mkpoint { x, y }
+    coord_bytes = @mpi_coord_byte_size()
+    [x,y] = [ BigInteger.fromBuffer(sb.read_buffer(coord_bytes)), BigInteger.fromBuffer(sb.read_buffer(coord_bytes)) ]
+    point = @mkpoint { x, y}
     unless @isOnCurve point
       throw new Error "Given ECC point isn't on the given curve; data corruption detected."
     [ null, point ]
@@ -164,23 +170,16 @@
   _mpi_point_from_slicer_buffer : (sb) ->
     n_bits = sb.read_uint16()
     n_bytes = Math.ceil(n_bits/8)
+    if n_bytes isnt (b = @mpi_byte_size())
+      bits = @mpi_bit_size()
+      throw new Error "Need #{b} bytes (#{bits} bits) for this curve; got #{n_bytes} (#{n_bits} bits)"
     if sb.read_uint8() isnt 0x40
       throw new Error "Can only handle 0x40 prefix for 25519 MPI representations"

-    point_bytes = @nbits()/8
-    skip_bytes = n_bytes - point_bytes - 1
-    if skip_bytes < 0
-      throw new Error "Not enough bytes for 25519 point. Need at least #{point_bytes}, got #{n_bytes - 1}."
-    else if skip_bytes > 0
-      padding = sb.read_buffer(skip_bytes)
-      unless padding.every((x) -> x is 0)
-        # This is not compatible with GnuPG - gpg2 will just skip
-        # bytes, shall they be 0s or not.
-        throw new Error "Number is too big to be a 25519 point."
-
     # Point is just a 32-byte buffer. We also do not validate it,
     # because DJB told us not to: https://cr.yp.to/ecdh.html
-    point = sb.read_buffer(point_bytes)
+    n_bytes = @mpi_coord_byte_size()
+    point = sb.read_buffer(n_bytes)

     [ null, point ]


Fix login/logout across browser tabs/windows.

--- app/client/js/firebase.coffee
+++ app/client/js/firebase.coffee
@@ -5,10 +5,12 @@
 class app.Firebase

   ###*
+    @param {app.user.Store} userStore
     @constructor
   ###
-  constructor: ->
+  constructor: (@userStore) ->
     @setFireBaseRefs()
+    @authClient = new window.FirebaseSimpleLogin @root, @onSimpleLogin.bind @

   ###*
     @type {Firebase}
@@ -24,20 +26,12 @@
     @root = new window.Firebase 'https://shining-fire-6810.firebaseio.com/'

   ###*
-    @param {app.user.Store} userStore
-  ###
-  simpleLogin: (userStore) ->
-    goog.asserts.assert !@authClient, 'Can be called only once'
-    @authClient = new window.FirebaseSimpleLogin @root, @onSimpleLogin.bind @, userStore
-
-  ###*
     NOTE(steida): This method is called whenever user login status has changed.
     TODO(steida): Refactor.
-    @param {app.user.Store} userStore
     @param {Object} error
     @param {Object} user
   ###
-  onSimpleLogin: (userStore, error, user) ->
+  onSimpleLogin: (error, user) ->
     if error
       # TODO(steida): Report to server.
       console.log error
@@ -45,16 +39,17 @@

     # TODO(steida): Refactor it.
     if !user
+      # debugger
       # NOTE(steida): Stop listening changes after logout.
       # @stopListeningUserValue()
       @userRef?.off 'value'
       # PATTERN(steida): Logout deletes all user data in local storage.
-      # TODO(steida): This should belong into userStore.
+      # TODO(steida): This should belong into @userStore.
       if @userRef
-        userStore.setEmpty()
+        @userStore.setEmpty()
       else
-        userStore.user = null
-      userStore.notify()
+        @userStore.user = null
+      @userStore.notify()
       # TODO(steida): Redirect to home.
       return

@@ -65,10 +60,10 @@
     @userRef.on 'value',
       (snap) =>
         # NOTE(steida): For not yet persisted user, snap.val() is null.
-        storeJson = snap.val() ? userStore.toJson()
+        storeJson = snap.val() ? @userStore.toJson()
         storeJson.user = user
-        userStore.fromJson storeJson
-        userStore.notify()
+        @userStore.fromJson storeJson
+        @userStore.notify()
     , (error) ->
       # TODO(steida): Report to server.
       console.log 'The read failed: ' + error.code
@@ -89,4 +84,5 @@
       scope: 'email'

   logout: ->
-    @authClient.logout()+    @authClient.logout()
+    console.log 'logout'


improve error reporting

improve error reporting

--- app/coffee/LockManager.coffee
+++ app/coffee/LockManager.coffee
@@ -16,11 +16,13 @@
 			stale: @LOCK_STALE
 		Lockfile.lock path, lockOpts, (error) ->
 			return callback new Errors.AlreadyCompilingError("compile in progress")	if error?.code is 'EEXIST'
-			fs.lstat path, (err, statLock)->
-				fs.lstat Path.dirname(path), (err, statDir)->
-					if error?
-						logger.err error:error, path:path, statLock:statLock, statDir: statDir, "unable to get lock"
-						return callback(error)
+				if error?
+					fs.lstat path, (statLockErr, statLock)->
+						fs.lstat Path.dirname(path), (statDirErr, statDir)->
+							fs.readdir Path.dirname(path), (readdirErr, readdirDir)->
+							logger.err error:error, path:path, statLock:statLock, statLockErr:statLockErr, statDir:statDir, statDirErr: statDirErr, readdirErr:readdirErr, readdirDir:readdirDir, "unable to get lock"
+							return callback(error)
+				else
 					runner (error1, args...) ->
 						Lockfile.unlock path, (error2) ->
 							error = error1 or error2


test travis

test travis

--- app/controllers/getter.coffee
+++ app/controllers/getter.coffee
@@ -104,7 +104,6 @@
       request_url url, (res) ->
         switch src
           when "youtube"
-            console.log res
             infos_yt JSON.parse(res).items[0], callback
           when "soundcloud"
             infos_sc JSON.parse(res), callback


タスクの開閉時にスライドアニメ

タスクの開閉時にスライドアニメ

--- app/js/project/task.js.coffee
+++ app/js/project/task.js.coffee
@@ -221,20 +221,44 @@
   if !can_open_task(task)
     throw new Error("Can't open task #{task.data('id')}: content is single line")

-  task.find('.name').html(full_task_name(task.data('name')))
-  task.find('.name').addClass('opened')
+  name = task.find('.name')
+
+  close_height = name.outerHeight(true)
+
+  name.html(full_task_name(task.data('name')))
+  name.addClass('opened')
   task.find('.task_open').hide()
   task.find('.task_close').show()
+
+  open_height = name.outerHeight(true)
+
+  name.css('height', close_height + 'px')
+  name.animate({height: open_height + 'px'}, 200, 'swing', () ->
+    name.css('height', '')
+  )
+
   update_open_all_button()

 close_task = (task) ->
   if !can_open_task(task)
     throw new Error("Can't close task #{task.data('id')}: content is single line")

-  task.find('.name').html(short_task_name(task.data('name')))
-  task.find('.name').removeClass('opened')
+  name = task.find('.name')
+
+  open_height = name.outerHeight(true)
+
+  name.html(short_task_name(task.data('name')))
+  name.removeClass('opened')
   task.find('.task_open').show()
   task.find('.task_close').hide()
+
+  close_height = name.outerHeight(true)
+
+  name.css('height', open_height + 'px')
+  name.animate({height: close_height + 'px'}, 200, 'swing', () ->
+    name.css('height', '')
+  )
+
   update_open_all_button()

 can_open_task = (elem) ->


order group.memberships()

order group.memberships()

--- app/models/group-model.coffee
+++ app/models/group-model.coffee
@@ -9,7 +9,7 @@

     relationships: ->
       @hasMany 'buckets'
-      @hasMany 'memberships'
+      @hasMany 'memberships', sortBy: 'createdAt', sortDesc: false

     draftBuckets: ->
       @getBuckets('draft', 'createdAt')


spacing

spacing


fix problem with uglification task

fix problem with uglification task

--- app/templates/gulpfile.coffee
+++ app/templates/gulpfile.coffee
@@ -165,7 +165,7 @@
     "#{conf.path.dev.js}/main.js"
   ]

-  gulp.src [files]
+  gulp.src(files)
     .pipe uglifyJs()
     .pipe rev()
     .pipe rename({suffix: '.min'})


specify exclude only for 404 from GET

specify exclude only for 404 from GET

--- src/flux/app.coffee
+++ src/flux/app.coffee
@@ -8,7 +8,10 @@

 shouldReload = (error) ->
-  (400 <= error.statusCode < 600) and not (error.statusCode is 404)
+  {statusCode, request} = error
+  isGET404 = statusCode is 404 and request.method is 'GET'
+  isInRange = 400 <= statusCode < 600
+  isInRange and not isGET404

 AppConfig =


grunt watch fix

grunt watch fix


Re-enable currentState global variable so custom tags work

--- src/generate.coffee
+++ src/generate.coffee
@@ -14,6 +14,8 @@
 bundledPlugins = null
 # Regexp for matching post filenames
 postMask = null
+# Global reference for custom tags
+currentState = null

 INCLUDE_PATH = '_includes'

@@ -77,6 +79,12 @@
     categories: {}
   }

+  # Setup current state
+  currentState = {
+    site
+    page: null
+  }
+
   # Add all values from config, but make sure not to clobber any existing
   for key, value of config
     unless key of site
@@ -156,6 +164,8 @@
 writePage = (page, bundle, callback) ->
   log.verbose "generate", "writePage %s", page.url
   { site, config, liquidOptions, compiledLayouts, mergedPlugins, context } = bundle
+
+  currentState.page = page

   # Respect published file
   return callback() unless config.future or page.published


Move more code over to promises

Move more code over to promises

--- src/generate.coffee
+++ src/generate.coffee
@@ -25,48 +25,48 @@
   # First-run initialization
   postMask = ///^(\d{4})-(\d{2})-(\d{2})-(.+)\.(#{config.markdown_ext.join '|'}|html)$///
   time.tzset config.timezone
-  async.parallel [
-    # Check directories for existence
-    (cb) -> checkDirectories config, cb
-    # Built-in enfield plugins
-    loadBundledPlugins
-  ], (err) -> refreshContent config, (err) ->
-    if err
-      return callback err
-
-    log.info "generate", "Generated %s -> %s", config.source, config.destination
-    callback()
-    return unless config.watch
-
-    destinationPath = path.resolve config.destination
-    gaze path.join(config.source, '**/*'), {debounceDelay: 500}, (err, watcher) ->
-      log.info "watch", "Watching %s for changes", config.source
-      watcher.on 'all', (event, filepath) ->
-        # Ignore any path within the destination directory
-        return if helpers.isWithinDirectory filepath, destinationPath
-
-        # Ignore any path within hidden/ignored directories
-
-        # TODO: Special case _config.yml
-        # TODO: Reload plugins
-
-        log.info "watch", "%s %s", event, filepath
-        refreshContent config, ->
-          log.info "watch", "Regenerated"
+  Q.all([checkDirectories(config), loadBundledPlugins()])
+    .then ->
+      Q.nfcall refreshContent, config
+    .then ->
+      log.info "generate", "Generated %s -> %s", config.source, config.destination
+      callback()
+      return unless config.watch
+
+      destinationPath = path.resolve config.destination
+      gaze path.join(config.source, '**/*'), {debounceDelay: 500}, (err, watcher) ->
+        log.info "watch", "Watching %s for changes", config.source
+        watcher.on 'all', (event, filepath) ->
+          # Ignore any path within the destination directory
+          return if helpers.isWithinDirectory filepath, destinationPath
+
+          # Ignore any path within hidden/ignored directories
+
+          # TODO: Special case _config.yml
+          # TODO: Reload plugins
+
+          log.info "watch", "%s %s", event, filepath
+          refreshContent config, ->
+            log.info "watch", "Regenerated"
+    .fail (err) ->
+      callback err

 refreshContent = (config, callback) ->
+  log.silly "generate", "Refreshing content"
+
   # Get content in parallel:
-  async.parallel [
-    # Mimic Jekyll behavior by clearing out destination first
-    (cb) -> fs.remove config.destination, cb
-    (cb) -> loadSitePlugins config, cb
-    (cb) -> loadIncludes config, cb
-    (cb) -> loadLayouts config, cb
-    (cb) -> loadContents config, cb
-  ], (err, [_, plugins, includes, layouts, { posts, pages, files }]) ->
-    if err then return callback err
-    log.verbose "generate", "Initial content load complete"
-    processResults {config, plugins, includes, layouts, posts, pages, files}, callback
+  Q.all([
+    # Mimic Jekyll behavior by clearing out destination before regeneration
+    Q.nfcall fs.remove, config.destination
+    loadSitePlugins(config)
+    Q.nfcall loadIncludes, config
+    Q.nfcall loadLayouts, config
+    Q.nfcall loadContents, config
+  ])
+    .then ([_, plugins, includes, layouts, { posts, pages, files }]) ->
+      log.verbose "generate", "Initial content load complete"
+      processResults {config, plugins, includes, layouts, posts, pages, files}, callback
+    .fail (err) -> callback err

 processResults = ({config, plugins, includes, layouts, posts, pages, files}, callback) ->
   # Create site data structure
@@ -568,24 +568,54 @@

   { posts, others }

-checkDirectories = (config, callback) ->
-  # Check if source & destination directories are there
-  async.series [
-    (cb) -> fs.exists config.source, (exists) ->
-      if exists then cb()
-      else cb new Error "Source directory does not exist: #{config.source}"
-    (cb) -> fs.exists config.destination, (exists) ->
-      if exists
-        fs.stat config.destination, (err, stat) ->
-          if stat.isDirectory()
-            cb()
-          else
-            cb new Error "Destination is not a directory: #{config.destination}"
+checkDirectories = (config) ->
+  Q.all [
+    checkSourceDirectory config.source
+    checkDestinationDirectory config.destination
+  ]
+
+# Must exist and be a directory
+checkSourceDirectory = (dir) ->
+  log.silly "generate", "Checking for source directory %s", dir
+  deferred = Q.defer()
+  dir or= '.'
+  isDirectory(dir)
+    .then (result) ->
+      if result
+        deferred.resolve()
       else
-        fs.mkdirs config.destination, cb
-  ], callback
-
-loadBundledPlugins = (callback) ->
+        deferred.reject new Error "Source is not a directory: #{dir}"
+    .fail ->
+      deferred.reject new Error "Source directory does not exist: #{dir}"
+  deferred.promise
+
+# May either not exist (in which case it will be created), or must exist and be
+# a directory
+checkDestinationDirectory = (dir) ->
+  log.silly "generate", "Checking for destination directory %s", dir
+  deferred = Q.defer()
+  isDirectory(dir)
+    .then (result) ->
+      if result
+        deferred.resolve()
+      else
+        # Exists but isn't directory
+        deferred.reject new Error "Destination is not a directory: #{dir}"
+    .fail ->
+      # Create the directory
+      log.verbose "generate", "Creating destination directory %s", dir
+      deferred.resolve Q.nfcall fs.mkdirs, dir
+
+  deferred.promise
+
+# True if file is directory
+# False if file exists but isn't directory
+# Fails if file does not exist
+isDirectory = (dir) ->
+  Q.nfcall(fs.stat, dir).then (stat) -> stat.isDirectory()
+
+# Built-in enfield plugins
+loadBundledPlugins = ->
   loadPlugins([path.join __dirname, 'plugins'])
     .then (plugins) ->
       bundledPlugins = plugins
@@ -593,19 +623,20 @@
       for key, filter of tinyliquid.filters
         unless key of bundledPlugins.filters
           bundledPlugins.filters[key] = filter
-      callback()
-    .fail (err) ->
-      callback err
-
-loadSitePlugins = (config, callback) ->
+      return
+
+loadSitePlugins = (config) ->
   # Resolve directories relative to source
-  dirs = config.plugins.map (dir) ->
-    path.resolve config.source, dir
-  async.filter dirs, fs.exists, (results) ->
-    loadPlugins(results)
-      .then (plugins) ->
-        callback null, plugins
-      .fail (err) -> callback err
+  dirs = config.plugins.map (dir) -> path.resolve config.source, dir
+  # Only check directories that actually exist
+  Q.allSettled(dirs.map (dir) -> Q.nfcall fs.stat, dir)
+    .then (results) ->
+      pluginDirs = []
+      for result, i in results
+        continue if result.state isnt 'fulfilled'
+        pluginDirs.push dirs[i]
+
+      loadPlugins pluginDirs

 loadPlugins = (dirs) ->
   log.verbose "generate", "Looking for plugins in: %s", dirs.join ', '
@@ -633,25 +664,27 @@
         allFiles.map(path.basename), dirs

       for file in allFiles
-        log.verbose "generate", "Loading plugin: %s", file
-        # Load file
-        plugin = require file
-
-        if 'filters' of plugin
-          for key, filter of plugin['filters']
-            log.silly "generate", "Found filter for %s", key
-            plugins.filters[key] = filter
-        if 'tags' of plugin
-          for key, tag of plugin['tags']
-            log.silly "generate", "Found tag for %s", key
-            plugins.tags[key] = tag
-        if 'converters' of plugin
-          for key, converter of plugin['converters']
-            log.silly "generate", "Found converter for %s", key
-            plugins.converters.push converter
-        if 'generators' of plugin
-          for key, generator of plugin['generators']
-            log.silly "generate", "Found generator for %s", key
-            plugins.generators.push generator
+        loadFileIntoPlugins file, plugins

       plugins
+
+loadFileIntoPlugins = (file, plugins) ->
+  log.verbose "generate", "Loading plugin: %s", file
+  plugin = require file
+
+  if 'filters' of plugin
+    for key, filter of plugin['filters']
+      log.silly "generate", "Found filter for %s", key
+      plugins.filters[key] = filter
+  if 'tags' of plugin
+    for key, tag of plugin['tags']
+      log.silly "generate", "Found tag for %s", key
+      plugins.tags[key] = tag
+  if 'converters' of plugin
+    for key, converter of plugin['converters']
+      log.silly "generate", "Found converter for %s", key
+      plugins.converters.push converter
+  if 'generators' of plugin
+    for key, generator of plugin['generators']
+      log.silly "generate", "Found generator for %s", key
+      plugins.generators.push generator


Better compatibility with 1.9.0

Better compatibility with 1.9.0

--- lib/entities/property.coffee
+++ lib/entities/property.coffee
@@ -45,8 +45,8 @@
           @setter = false
           @getter = false
           for property in @node.args[1].base?.properties
-            @setter = true if property.variable.base.value == 'set'
-            @getter = true if property.variable.base.value == 'get'
+            @setter = true if property.variable?.base.value == 'set'
+            @getter = true if property.variable?.base.value == 'get'
         else
           # @property 'test', ->
           @setter = false


Fix empty commit message error on MacOS

Fix empty commit message error on MacOS

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -43,7 +43,7 @@
   showFile: ->
     split = if atom.config.get('git-plus.openInPane') then atom.config.get('git-plus.splitPane')
     atom.workspace
-      .open(@file, split: split, activatePane: true, searchAllPanes: true)
+      .open(path.join(@dir, @file), split: split, activatePane: true, searchAllPanes: true)
       .done ({buffer}) =>
         @subscribe buffer, 'saved', =>
           @commit()


Fixed Win7 completer bug

Fixed Win7 completer bug

Autocompletes were breaking on Windows 7.

--- lib/plugins/completer.coffee
+++ lib/plugins/completer.coffee
@@ -1,26 +1,26 @@
-
-###
-
-Completer plugin
-================
-
-Provides tab completion. Options passed during creation are:
-
--   `shell`    , (required) A reference to your shell application.
-
-###
-module.exports = (settings) ->
-    # Validation
-    throw new Error 'No shell provided' if not settings.shell
-    shell = settings.shell
-    # Plug completer to interface
-    return unless shell.isShell
-    shell.interface().completer = (text) ->
-        suggestions = []
-        routes = shell.routes
-        for route in routes
-            command = route.command
-            if command.substr(0, text.length) is text
-                suggestions.push command
-        [suggestions, text]
-    null
+
+###
+
+Completer plugin
+================
+
+Provides tab completion. Options passed during creation are:
+
+-   `shell`    , (required) A reference to your shell application.
+
+###
+module.exports = (settings) ->
+    # Validation
+    throw new Error 'No shell provided' if not settings.shell
+    shell = settings.shell
+    # Plug completer to interface
+    return unless shell.isShell
+    shell.interface().completer = (text, cb) ->
+        suggestions = []
+        routes = shell.routes
+        for route in routes
+            command = route.command
+            if command.substr(0, text.length) is text
+                suggestions.push command
+        cb(false, [suggestions, text])
+    null


Run the search when toggling options only when model is active.

Fixes #102

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -93,7 +93,7 @@
     @subscribe @model, 'finished-searching', (results) => @generateResultsMessage(results)

     @findEditor.editor.on 'contents-modified', =>
-      @runRealtimeSearch() if @findEditor.getText().length > 2
+      @runRealtimeSearch(onlyRunIfChanged: true) if @findEditor.getText().length > 2

     atom.workspaceView.command 'find-and-replace:use-selection-as-find-pattern', @setSelectionAsFindPattern

@@ -134,13 +134,13 @@
     @model.toggleUseRegex()
     if @model.useRegex then @regexOptionButton.addClass('selected') else @regexOptionButton.removeClass('selected')
     @updateOptionsLabel()
-    @confirm()
+    @runRealtimeSearch()

   toggleCaseOption: ->
     @model.toggleCaseSensitive()
     if @model.caseSensitive then @caseOptionButton.addClass('selected') else @caseOptionButton.removeClass('selected')
     @updateOptionsLabel()
-    @confirm()
+    @runRealtimeSearch()

   focusNextElement: (direction) ->
     elements = [@findEditor, @replaceEditor, @pathsEditor].filter (el) -> el.has(':visible').length > 0
@@ -169,10 +169,10 @@
     @showResultPane()
     @model.search(@findEditor.getText(), @getPaths(), @replaceEditor.getText(), onlyRunIfChanged: true)

-  runRealtimeSearch: ->
+  runRealtimeSearch: (options) ->
     return unless @model.active
     @errorMessages.empty()
-    @model.search(@findEditor.getText(), @getPaths(), @replaceEditor.getText(), onlyRunIfChanged: true)
+    @model.search(@findEditor.getText(), @getPaths(), @replaceEditor.getText(), options)

   replaceAll: ->
     @errorMessages.empty()


#1067 - Skip logic operator and value fields show in reverse order

--- jsapp/xlform_model_view/view.rowDetail.SkipLogic.coffee
+++ jsapp/xlform_model_view/view.rowDetail.SkipLogic.coffee
@@ -296,9 +296,9 @@

     attach_response: () ->
       if @$('.skiplogic__responseval-wrapper').length > 0
-        @$('.skiplogic__responseval-wrapper').replaceWith(@response_value_view.el)
-      else
-        @response_value_view.attach_to(@$el)
+        @$('.skiplogic__responseval-wrapper').remove()
+
+      @response_value_view.attach_to(@$el)
       @bind_response_value()

     attach_to: (element) ->


collision with regular bounds

collision with regular bounds

--- src/collision.coffee
+++ src/collision.coffee
@@ -220,7 +220,7 @@
           xw = target.width/2
           yw = target.height/2
           return Collision.rayRectangle source, direction,
-            x: target.x +
+            x: target.x + xw
             xw: xw
             yw: yw
         else


Reset the placeholder after geolocation failure.

--- web/js/views/search_view.coffee
+++ web/js/views/search_view.coffee
@@ -136,6 +136,7 @@

     onGeolocationError: () =>
       @$positionLookupSpinner.hide()
+      @from.resetPlaceholder()
       message = new ErrorMessageView(message: strings.geolocationError).render()

     onToggleGeolocation: =>


Don't try retrieve the user's address if geolocation accuracy is too poor. Fixes #88.

--- web/js/views/search_view.coffee
+++ web/js/views/search_view.coffee
@@ -95,7 +95,7 @@

     populateFromBox: (position, callback) ->
       # TODO: Move this logic somewhere else
-      if Utils.isWithinBounds(position)
+      if Utils.isWithinBounds(position) and position.accuracy < 200
         $.getJSON "/address?coords=#{position.coords.longitude},#{position.coords.latitude}", (location) =>
           @from.val location.name
           callback()


Mixed up versioning before... fixing

Mixed up versioning before... fixing

--- src/donations.coffee
+++ src/donations.coffee
@@ -40,7 +40,7 @@
   loadExternalScripts()
   return

-if window.jQuery is `undefined` or (window.jQuery.fn.jquery and (parseInt(window.jQuery.fn.jquery[0]) <= 1 or parseInt(window.jQuery.fn.jquery[1]) <= 9 and parseInt(window.jQuery.fn.jquery[2]) <= 1))
+if window.jQuery is `undefined` or (window.jQuery.fn.jquery and (parseInt(window.jQuery.fn.jquery[0]) != 1 or parseInt(window.jQuery.fn.jquery[1]) < 9))
   loadExternalResource("js","https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js",scriptLoadHandler)
 else
   scriptLoadHandler()


Fix create fare bug

Fix create fare bug

--- view/coffee/creator/create_fare.coffee
+++ view/coffee/creator/create_fare.coffee
@@ -16,6 +16,7 @@
       submit: ->
         API.postJSON
           url: "/api/company/#{@company}/type/#{@trainType}/fares"
-          data: {fares: @fares}
+          fares = @fares.map (f) -> {km: parseFloat(f.km), cost: parseInt(f.cost)}
+          data: {fares: fares}
           success: ->
             location.reload(false)


revert bugs introduced in 2ec62c3eb0

revert bugs introduced in 2ec62c3eb0

--- src/hubot/irc.coffee
+++ src/hubot/irc.coffee
@@ -4,18 +4,16 @@
 class IrcBot extends Robot
   send: (user, strings...) ->
     for str in strings
-      do (str) ->
-        if user.room
-          console.log "#{user.room} #{str}"
-          @bot.say(user.room, str)
-        else
-          console.log "#{user.name} #{str}"
-          @bot.say(user.name, str)
+      if user.room
+        console.log "#{user.room} #{str}"
+        @bot.say(user.room, str)
+      else
+        console.log "#{user.name} #{str}"
+        @bot.say(user.name, str)

   reply: (user, strings...) ->
     for str in strings
-      do (str) ->
-        @send user, "#{user.name}: #{str}"
+      @send user, "#{user.name}: #{str}"

   join: (channel) ->
     self = @
@@ -60,8 +58,7 @@
           bot.say 'NickServ', "identify #{options.nickpass}"
         else if options.nickpass and from is 'NickServ' and text.indexOf('now identified') isnt -1
           for room in options.rooms
-            do (room) ->
-              @join room
+            @join room

     bot.addListener 'message', (from, to, message) ->
       console.log "From #{from} to #{to}: #{message}"


More debugging

More debugging

--- scripts/chat.coffee
+++ scripts/chat.coffee
@@ -92,7 +92,7 @@
 				msg.send [msg.random response_to_answer] + [if shuffled_answer.length > 1 then 'Someone else told me ' + "'" + shuffled_answer[0].answer + "'. "] + [msg.random conjunction] + question_text

 		# Add a new question via Hubot
-		robot.hear /question$4 (.*)$/i, (msg) ->
+		robot.hear /question4 (.*)$/i, (msg) ->
 			question_value = msg.match[1]
 			next_id = robot.brain.data.questions.length
 			new_question = {id:next_id, question: question_value, answers: []}
@@ -101,7 +101,7 @@
 			msg.send "OK, I've added \"#{question_value}\" as a new question"

 		# Remove an existing question via Hubot
-		robot.hear /remove$4 (.*)$/i, (msg) ->
+		robot.hear /remove4 (.*)$/i, (msg) ->
 			question_cull = msg.match[1]
 			robot.logger.info question_cull
 			# robot.brain.data.questions.splice(question_cull, 1)


Fixes missing test task

Fixes missing test task

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -24,5 +24,7 @@
   grunt.registerTask 'build', 'Builds the default project', ['development']
   grunt.registerTask 'default', 'Default task', ['development']

+  grunt.registerTask 'test', ['lint']
+
   # The future in order
   grunt.registerTask 'observe', 'Default task', ['development', 'watch']


Updated task order

Updated task order

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -26,4 +26,4 @@
   grunt.loadNpmTasks 'grunt-coffeelint'

   grunt.registerTask 'test', ['coffeelint']
-  grunt.registerTask 'default', ['coffeelint', 'coffee', 'uglify']
+  grunt.registerTask 'default', ['coffee', 'coffeelint', 'uglify']


Fixed gruntfile to add css and js minify

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -8,7 +8,7 @@
     meta:
       file: "Gertu"
       banner:
-        '/* <%= meta.file %> v<%= pkg.version %> - '                  +
+        '/* <%= meta.file %> v<%= pkg.version %> - '                 +
         '<%= grunt.template.today("yyyy/m/d") %>\n'                  +
         '<%= grunt.template.today("yyyy") %> <%= pkg.author.name %>' +
         '- Licensed <%= _.pluck(pkg.license, "type").join(", ") %> */\n'
@@ -20,11 +20,11 @@
       jade:
         files: ["views/**/*"],                 tasks: ["jade"]
       coffee:
-        files: "assets/**/*.coffee",           tasks: ["coffeelint:app", "coffee"]
+        files: "assets/**/*.coffee",           tasks: ["coffeelint:app", "coffee", "uglify"]
       coffee_server:
         files: "server/**/*.coffee",           tasks: ["coffeelint:server"]
       stylus:
-        files: "assets/stylesheets/**/*.styl", tasks: ["stylus"]
+        files: "assets/stylesheets/**/*.styl", tasks: ["stylus", "cssmin"]

     jade:
       options:


The default task is now the observe one

The default task is now the observe one

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -22,9 +22,10 @@
   grunt.registerTask 'deploy', 'Builds project in production mode and starts a local server', ['production', 'connect']

   grunt.registerTask 'build', 'Builds the default project', ['development']
-  grunt.registerTask 'default', 'Default task', ['development']

   grunt.registerTask 'test', ['lint']

   # The future in order
-  grunt.registerTask 'observe', 'Default task', ['development', 'watch']
+  grunt.registerTask 'observe', ['development', 'watch']
+
+  grunt.registerTask 'default', 'Default task', ['observe']


Added udefine to gruntfile

Added udefine to gruntfile

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -9,7 +9,7 @@
     coffee:
       compile:
         files:
-          'dist/<%= pkg.name %>.js': ['src/*.coffee']
+          'dist/<%= pkg.name %>.js': ['udefine/*.coffee', 'src/*.coffee']
     uglify:
       options:
         banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("dd-mm-yyyy") %> */\n'


reduced the timeout to 0.5s

reduced the timeout to 0.5s

--- app/assets/javascripts/species/views/search_form/taxon_concept_search_text_field.js.coffee
+++ app/assets/javascripts/species/views/search_form/taxon_concept_search_text_field.js.coffee
@@ -13,7 +13,7 @@
     @currentTimeout = Ember.run.later(@, ->
       @showDropdown()
       @set('query', event.target.value)
-    , 1000)
+    , 500)

   hideDropdown: () ->
     $('.search fieldset').removeClass('parent-focus parent-active')


Ask questions before connect/disconnect, binding and unbinding domains to VMs is fully functional now \o/

--- client/app/Applications/Environments.kdapplication/views/scene/environmentsceneview.coffee
+++ client/app/Applications/Environments.kdapplication/views/scene/environmentsceneview.coffee
@@ -1,9 +1,73 @@
 class EnvironmentScene extends KDDiaScene
+
+  containerMap =
+    EnvironmentDomainContainer  : 'domains'
+    EnvironmentMachineContainer : 'machines'
+  itemMap      =
+    EnvironmentDomainItem       : 'domain'
+    EnvironmentMachineItem      : 'machine'

   constructor:->
     super
       cssClass  : 'environments-scene'
       lineWidth : 1
+    @boxes = {}
+
+  disconnect:(dia, joint)->
+
+    removeConnection = => KDDiaScene::disconnect.call this, dia, joint
+    targetConnection = @findTargetConnection dia, joint
+    return unless targetConnection
+    {source, target} = targetConnection
+
+    items = parseItems source, target
+    {domain, machine} = items
+    return unless domain
+
+    @askForApprove items, 'delete', (modal)=>
+      if machine? # Remove a domain from a machine
+        jDomain = domain.dia.data.domain # JDomain
+        vmName  = machine.dia.data.title # JVM.hostnameAlias
+        jDomain.unbindVM hostnameAlias: vmName, (err)=>
+          modal.destroy()
+          return KD.showError err  if err
+          jDomain.hostnameAlias.splice jDomain.hostnameAlias.indexOf(vmName), 1
+          removeConnection()
+
+  connect:(source, target, internal = no)->
+
+    createConnection = => KDDiaScene::connect.call this, source, target
+    return createConnection()  if internal
+
+    items = parseItems source, target
+    {domain, machine} = items
+    return unless domain
+
+    if machine?
+      if domain.dia.data.domain.hostnameAlias.length > 0
+        return new KDNotificationView
+          title : "A domain name can only be bound to one VM."
+
+    @askForApprove items, 'create', (modal)=>
+      if machine? # Assign a domain to a machine
+        jDomain = domain.dia.data.domain # JDomain
+        vmName  = machine.dia.data.title # JVM.hostnameAlias
+        jDomain.bindVM hostnameAlias: vmName, (err)=>
+          modal.destroy()
+          return KD.showError err  if err
+          jDomain.hostnameAlias.push vmName
+          createConnection()
+
+  updateConnections:->
+    for _, machine of @boxes.machines.dias
+      for _, domain of @boxes.domains.dias
+        if domain.data.aliases and machine.data.title in domain.data.aliases
+          @connect {dia : domain , joint : 'right'}, \
+                   {dia : machine, joint : 'left' }, yes
+
+  askForApprove:(items, action, callback)->
+    modal = new EnvironmentApprovalModal {action}, items
+    modal.once 'Approved', => callback modal

   whenItemsLoadedFor:do->
     # poor man's when/promise implementation ~ GG
@@ -13,6 +77,23 @@
         container.once "DataLoaded", ->
           if counter is 1 then do callback
           counter--
+
+  addContainer:(container, pos)->
+    pos ?= x: 40 + @containers.length * 300, y: 40
+    super container, pos
+
+    {name} = container.constructor
+    label  = containerMap[name] or name
+    @boxes[label] = container
+
+  parseItems = (source, target)->
+    items = {}
+    for item in [source, target]
+      items[itemMap[item.dia.constructor.name]] = item
+    return items
+
+  type = (item)->
+    itemMap[item.dia.constructor.name] or null

   viewAppended:->
     super
@@ -31,4 +112,41 @@

     slider.on 'ValueChanged', (value)=>
       do _.throttle =>
-        @setScale (Math.floor value) / 100+        @setScale (Math.floor value) / 100
+
+class EnvironmentApprovalModal extends KDModalView
+
+  getContentFor = ({domain, machine}, action)->
+    content = 'God knows.'
+    if action is 'create'
+      content = """Do you want to assign #{domain.dia.data.title}
+                   to #{machine.dia.data.title} machine?"""
+    else if action is 'delete'
+      content = """Do you want to remove #{domain.dia.data.title}
+                   domain from #{machine.dia.data.title} machine?"""
+    return "

#{content}

"
+
+  constructor:(options={}, data)->
+
+    options.title       or= "Are you sure?"
+    options.overlay      ?= yes
+    options.overlayClick ?= no
+    options.buttons       =
+      Yes                 :
+        loader            :
+          color           : "#444444"
+          diameter        : 12
+        cssClass          : if options.action is 'delete' \
+                            then "modal-clean-red" else "modal-clean-green"
+        callback          : =>
+          @buttons.Yes.showLoader()
+          @emit 'Approved'
+      Cancel              :
+        cssClass          : "modal-cancel"
+        callback          : =>
+          @emit 'Cancelled'
+          @cancel()
+
+    options.content = getContentFor data, options.action
+
+    super options, data


Docs - Lumen added and fix for result structure

closes #61

--- scripts/docs.coffee
+++ scripts/docs.coffee
@@ -29,6 +29,8 @@
         "site:laravel.com/api/#{version} #{query}"
       else
         "site:laravel.com/api/#{TARGET_VERSION} #{query}"
+    else if doctype == 'lumen'
+      "site:lumen.laravel.com/docs #{query}"
     else if doctype == 'php'
       "site:php.net/manual/en #{query}"
     else
@@ -47,7 +49,7 @@
         $ = cheerio.load body
         result = $('#rso .g').first()
         # Jump To Link ? Main Link
-        url = result.find('.st .f a').attr('href') ? result.find('h3.r a').attr('href')
+        url = result.find('.s span.f a').attr('href') ? result.find('h3.r a').attr('href')
         callback url if callback

   docFetcher = (msg) ->
@@ -67,5 +69,5 @@
       else
         msg.send response

-  robot.respond /show (?:([^\s!]+) )?docs for (?:(api|php) )?(?:([0-9.]+) )?(.+)/i, docFetcher
-  robot.hear /(?:([^:,\s!]+)[:,\s]+)?!docs (?: (api|php))?(?: ([0-9.]+))?(?:\s?(.+))/i, docFetcher
+  robot.respond /show (?:([^\s!]+) )?docs for (?:(api|php|lumen) )?(?:([0-9.]+) )?(.+)/i, docFetcher
+  robot.hear /(?:([^:,\s!]+)[:,\s]+)?!docs (?: (api|php|lumen))?(?: ([0-9.]+))?(?:\s?(.+))/i, docFetcher


amqp: Log address when connecting

amqp: Log address when connecting

--- src/amqp.coffee
+++ src/amqp.coffee
@@ -12,7 +12,7 @@

   ## Broker connection management
   connect: (callback) ->
-    debug 'connect'
+    debug 'connect', @address
     amqp.connect @address, (err, conn) =>
       debug 'connected', err
       return callback err if err


cleanup

cleanup

--- js/chrome.coffee
+++ js/chrome.coffee
@@ -4,19 +4,9 @@

   Editor = {}

-  window.wallapattaDecodeURL = (url) ->
-   res = url
-   if res[0] isnt '/'
-    res= "/#{res}"
-   if APP.resources[res]?
-    return APP.resources[res]
-   else
-    return url
-
   class App extends Base
    @initialize ->
     @elems = {}
-    @resources = {}
     @_loading = true

     window.requestAnimationFrame =>
@@ -234,7 +224,6 @@

    addResource: (entry) ->
     entry.file (file) =>
-     #@resources[entry.fullPath] = window.URL.createObjectURL file
      reader = new FileReader()
      reader.onload = (e) =>
       @send 'addResource', dataURL: reader.result, path: entry.fullPath


Set JSON spaces to 2 in development mode

--- server/index.coffee
+++ server/index.coffee
@@ -73,6 +73,7 @@
     @app.set 'view engine', 'hbs'
     @app.set 'view cache', false
     @app.set 'query parser', 'simple' # reduces risk of MongoDB injection attack
+    @app.set 'json spaces', 2 if @config.get('env') is 'development'

     @app.use expressWinston.logger
       winstonInstance: logger


Reduce high level announcements to top 10 (temp.)

--- server/index.coffee
+++ server/index.coffee
@@ -20,7 +20,7 @@
 replays = require('./replays')

 MAX_MESSAGE_LENGTH = 250
-MAX_RANK_DISPLAYED = 100
+MAX_RANK_DISPLAYED = 10

 # A MD5 hash of all the JavaScript files used by the client. This is passed to
 # each new connection via the .jade template, and when the client connects. If


Edited fallback route to use all methods in fallback

--- server_setup.coffee
+++ server_setup.coffee
@@ -84,7 +84,7 @@
 ###Routing function implementations###

 setupFallbackRouteToIndex = (app) ->
-  app.get '*', (req, res) ->
+  app.all '*', (req, res) ->
     res.sendfile path.join(__dirname, 'public', 'index.html')

 setupFacebookCrossDomainCommunicationRoute = (app) ->


fix selection change emission

fix selection change emission

fixes #54

--- src/selection.coffee
+++ src/selection.coffee
@@ -96,7 +96,11 @@
   getNativeRange: (normalize = false) ->
     return null unless @nativeSelection
     @nativeSelection.refresh()
-    range = if @nativeSelection?.rangeCount > 0 then @nativeSelection.getRangeAt(0) else null
+    # Editor needs focus for us to consider valid range
+    return null unless @editor.root.ownerDocument.activeElement == @editor.root and @nativeSelection?.rangeCount > 0
+    range = @nativeSelection.getRangeAt(0)
+    # Selection elements needs to be within editor root
+    return null unless rangy.dom.isAncestorOf(@editor.root, range.startContainer) and rangy.dom.isAncestorOf(@editor.root, range.endContainer)
     range = normalizeNativeRange(range) if normalize
     range.isBackwards = true if @nativeSelection.isBackwards()
     return range
@@ -117,8 +121,8 @@
       fn.call(null)

   setRange: (range, silent = false) ->
-    return unless @nativeSelection? and @editor.root.ownerDocument.activeElement?.tagName != 'BODY'
-    @nativeSelection.removeAllRanges()
+    return unless @nativeSelection?
+    @nativeSelection.removeAllRanges() if @editor.root.ownerDocument.activeElement == @editor.root
     if range?
       nativeRange = rangy.createRangyRange()
       _.each([range.start, range.end], (pos, i) ->
@@ -140,8 +144,7 @@
   update: (silent = false) ->
     return if (@mouseIsDown or @keyIsDown) and !silent
     nativeRange = this.getNativeRange(false)
-    return if nativeRange? and !(rangy.dom.isAncestorOf(@editor.root, nativeRange.startContainer) and rangy.dom.isAncestorOf(@editor.root, nativeRange.endContainer))
-    unless nativeRange == @range or (nativeRange? and @range and nativeRange.equals(@range))
+    unless nativeRange == @range or (nativeRange? and @range? and nativeRange.equals(@range))
       nativeRange = normalizeNativeRange(nativeRange)
       this.setRange(_nativeRangeToRange.call(this, nativeRange), silent)


Fix Issue #2

Fix Issue #2

--- src/victorops.coffee
+++ src/victorops.coffee
@@ -52,7 +52,7 @@
   invalidChannel = (msg) ->
     channels = []
     for team, key of teams
-      channel = robot.adapter.client.getChannelGroupOrDMByName team
+      channel = getRoom message: room: team
       channels.push " <\##{channel.id}|#{channel.name}>" if channel
     return msg.reply "You can only page for oncall in one of the following project channels: #{channels}"


Removes dupe request

Removes dupe request

--- src/auth.coffee
+++ src/auth.coffee
@@ -1,5 +1,4 @@
 TeamSnap = require('./teamsnap').TeamSnap
-request = require './request'
 promises = require './promises'
 request = require './request'
 jsonMime = 'application/json'


bugfixes

bugfixes

--- src/base.coffee
+++ src/base.coffee
@@ -195,7 +195,8 @@
       promise(api).connect(connectionData, model, noErr())
       .post('stuffz', { name: 'jakob', age: 28 }, noErr())
       .list('stuffz', {}, noErr (list) ->
-        list.map((x) -> x.created__at__123).should.be.between(now, now + 1000)
+        list[0].created__at__123.should.not.be.below(now)
+        list[0].created__at__123.should.not.be.above(now+1000)
       ).then ->
         api.close(done)

@@ -218,7 +219,8 @@
       promise(api).connect(connectionData, model, noErr())
       .post('stuffz', { name: 'jakob', age: 28 }, noErr())
       .list('stuffz', {}, noErr (list) ->
-        list.map((x) -> x.upd).should.be.between(now, now + 1000)
+        list[0].upd.should.not.be.below(now)
+        list[0].upd.should.not.be.above(now+1000)
       ).then ->
         api.close(done)


Updated config

Updated config

--- atom/atom.symlink/config.cson
+++ atom/atom.symlink/config.cson
@@ -54,7 +54,7 @@
   docblockr: {}
   editor:
     autoIndentOnPaste: false
-    fontFamily: "Hack"
+    fontFamily: "Menlo"
     fontSize: 12
     invisibles: {}
     showInvisibles: true
@@ -74,7 +74,6 @@
   linter:
     clearOnChange: true
     lintOnFly: false
-    showErrorPanel: false
     showErrorTabLine: true
     showInfoMessages: true
     subtleLinterErrors: [
@@ -86,6 +85,7 @@
   "linter-jscs":
     esnext: true
     executablePath: "/usr/local/bin/jscs"
+    onlyConfig: true
   "linter-jshint":
     jshintExecutablePath: "/usr/local/bin/jshint"
   "linter-scss-lint":
@@ -131,6 +131,13 @@
     surroundKey: "S"
   welcome:
     showOnStartup: false
+".angular.html.text":
+  editor:
+    preferredLineLength: 80
+  "multi-wrap-guide":
+    columns: [
+      80
+    ]
 ".basic.html.text":
   editor:
     showInvisibles: true


Fix bug, fix https://github.com/poooi/poi/issues/281

--- views/components/ship/ship.cjsx
+++ views/components/ship/ship.cjsx
@@ -101,7 +101,7 @@
             goback[escapeId] = goback[towId] = true
         when '/kcsapi/api_req_map/start', '/kcsapi/api_req_map/next'
           if path == '/kcsapi/api_req_map/start'
-            if combined
+            if combined && parseInt(postBody.api_deck_id) == 1
               deckId = 0
               inBattle[0] = inBattle[1] = true
             else


Correct error on widget instantiation when there is not an element (fixes #7)

--- src/status.coffee
+++ src/status.coffee
@@ -133,12 +133,12 @@

     widgetSelector = @options["selector"]
     @elements.widget = document.querySelector widgetSelector
-    @setVisibility "hidden"

     if @elements.widget == null
       warn "Unable to find element with selector: #{widgetSelector}"
       return

+    @setVisibility "hidden"
     @injectStyles() if @options["css"]
     @connect()


Only adding elements with mass greater than zero.

--- src/timber.coffee
+++ src/timber.coffee
@@ -475,8 +475,10 @@
         return 0.5

     getMass : () ->
-        return @element1.mass + @element2.mass
-
+        @getElements().reduce((mass, e) ->
+            mass += e.mass if 0 < e.mass
+            return mass
+        , 0)

 class timber.CollisionHandler


use topojson binary from node_modules

use topojson binary from node_modules

--- src/data.coffee
+++ src/data.coffee
@@ -445,7 +445,7 @@
             data/contours/#{region}-prj.shp
             data/contours/#{region}.shp")
     .then ->
-      exec("topojson
+      exec("./node_modules/.bin/topojson
             contour=data/contours/#{region}-prj.shp
             -o #{demPath}
             --id-property ID


Fix crash when creating subscription

Fix crash when creating subscription

--- lib/event.coffee
+++ lib/event.coffee
@@ -6,7 +6,7 @@
     name_format: /^[a-zA-Z0-9@:._-]{1,100}$/

     constructor: (@redis, @name) ->
-        throw new Error("Missing redis connection") if not redis?
+        throw new Error("Missing redis connection") if not @redis?
         throw new Error('Invalid event name ' + @name) if not Event::name_format.test @name
         @key = "event:#{@name}"


Remove ‘dice’

Remove ‘dice’

--- src/data.coffee
+++ src/data.coffee
@@ -77,10 +77,10 @@
       name_en: 'Kenta Watashima'
       color: '#8F9294'

-    dice:
-      name_ja: '油木田 大祐'
-      name_en: 'Daisuke Yukita'
-      color: '#E6E3C5'
+    # dice:
+    #   name_ja: '油木田 大祐'
+    #   name_en: 'Daisuke Yukita'
+    #   color: '#E6E3C5'

     masa:
       name_ja: '川村 真司'


added .parseAsync Buffer support

added .parseAsync Buffer support

--- lib/index.coffee
+++ lib/index.coffee
@@ -80,7 +80,8 @@
     cb = if typeof args[args.length - 1] is 'function' then args.pop() else null

     {data, reviver, filename}  = Args([
-      { data     : Args.STRING   | Args.Required }
+      { data     : Args.STRING   | Args.Required, _check: (data) ->
+        data = data.toString() if typeof data is 'object' }
       { reviver  : Args.FUNCTION | Args.Optional }
       { filename : Args.STRING   | Args.Optional }
     ], args)


:memo: Improve config descriptions

:memo: Improve config descriptions

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -11,6 +11,8 @@
       type: 'boolean'
       default: false
     moveResultToSourceDirectory:
+      description: 'Ensures that the output file produced by a successful build
+        is stored together with the TeX document that produced it.'
       type: 'boolean'
       default: true
     openResultAfterBuild:
@@ -20,7 +22,9 @@
       type: 'boolean'
       default: true
     outputDirectory:
-      description: "All files generated during a build will be redirected here"
+      description: 'All files generated during a build will be redirected here.
+        Leave blank if you want the build output to be stored in the same
+        directory as the TeX document.'
       type: 'string'
       default: ''
     skimPath:
@@ -28,7 +32,7 @@
       default: '/Applications/Skim.app'
     texPath:
       title: 'TeX Path'
-      description: "The path to your TeX distribution's bin directory"
+      description: "The full path to your TeX distribution's bin directory."
       type: 'string'
       default: ''


add assertion

add assertion

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -702,8 +702,10 @@
 assertFunction = (f) -> assert "not a function : " + f, isFunction(f)
 isFunction = (f) -> typeof f == "function"
 assertArray = (xs) -> assert "not an array : " + xs, xs instanceof Array
+assertString = (x) -> assert "not a string : " + x, typeof x == "string"
 always = (x) -> (-> x)
 methodCall = (obj, method, args) ->
+  assertString(method)
   if args == undefined then args = []
   (value) -> obj[method]((args.concat([value]))...)
 partiallyApplied = (f, args) ->


Remove window.measure and window.profile

--- src/window.coffee
+++ src/window.coffee
@@ -117,34 +117,3 @@
   atom.deserializers.get(args...)
 window.requireWithGlobals = (args...) ->
   atom.requireWithGlobals(args...)
-
-# Public: Measure how long a function takes to run.
-#
-# * description:
-#   A String description that will be logged to the console.
-# * fn:
-#   A Function to measure the duration of.
-#
-# Returns the value returned by the given function.
-window.measure = (description, fn) ->
-  start = new Date().getTime()
-  value = fn()
-  result = new Date().getTime() - start
-  console.log description, result
-  value
-
-# Public: Create a dev tools profile for a function.
-#
-# * description:
-#   A String descrption that will be available in the Profiles tab of the dev
-#   tools.
-# * fn:
-#   A Function to profile.
-#
-# Return the value returned by the given function.
-window.profile = (description, fn) ->
-  measure description, ->
-    console.profile(description)
-    value = fn()
-    console.profileEnd(description)
-    value


- Working on blink detection... it's obivous a lot more filtering   will be necessary.

--- src/eyejs.coffee
+++ src/eyejs.coffee
@@ -98,6 +98,7 @@
     diff = open - close

     if ((diff >= blinkTime - cushion) && (diff <= blinkTime + cushion))
+      console.log 'BLINK!!!!'
       @indicator.scale(0.8, 200)
       @freeze()
       @triggerEvents 'blink mousedown mouseup click'
@@ -109,7 +110,7 @@
     if (diff < 200)
       @indicator.scale(0.8, 200)
       @freeze()
-      @triggerEvents 'doubleblink mousedown mouseup click'
+      #@triggerEvents 'doubleblink mousedown mouseup click'

   handleBlinks: (frame) ->
@@ -119,7 +120,7 @@
     left  = frame.left.avg
     right = frame.right.avg

-    if (open  && close) then @handleBlink open, close
+    if (open && close) then @handleBlink open, close
     if (dOpen && close) then @handleDoubleBlink dOpen, close

     # if ((left.x == 0 && left.y == 0) && (right.x != 0 && right.y != 0))
@@ -189,11 +190,11 @@

     if @lastFrame

-      closedThisFrame = frame.avg.x == 0 and frame.avg.y == 0
-      openLastFrame   = @lastFrame.avg.x != 0 and @lastFrame.avg.y != 0
-
-      closedLastFrame = @lastFrame.avg.x == 0 and @lastFrame.avg.y == 0
-      openThisFrame   = frame.avg.x != 0 and frame.avg.y != 0
+      closedThisFrame = !frame.available.both
+      openLastFrame   = @lastFrame.available.both
+
+      closedLastFrame = !@lastFrame.available.both
+      openThisFrame   = frame.available.both

       if closedThisFrame and openLastFrame
         @closes.push(new Date());
@@ -224,6 +225,8 @@

       @emit 'gaze', e

+      @emit 'raw', frame
+
       @indicator.move @smoothedX.top(), @smoothedY.top()

       @handleGaze()


closes #226

closes #226

--- src/init.coffee
+++ src/init.coffee
@@ -1,11 +1,13 @@
 ###*
 # @class d3plus
 ###
-d3plus        = window.d3plus or {}
-window.d3plus = d3plus
+d3plus         = {}
+window.d3plus  = d3plus if typeof window isnt "undefined"
+module.exports = d3plus

 ###*
-# The current version of **D3plus** you are using. Returns a string in [semantic versioning](http://semver.org/) format.
+# The current version of **D3plus** you are using. Returns a string in
+# [semantic versioning](http://semver.org/) format.
 # @property d3plus.version
 # @for d3plus
 # @type String


better namespacing for global function; better comments; misc refactoring.

--- simple-panes.coffee
+++ simple-panes.coffee
@@ -1,30 +1,35 @@
-window.moveItem = (placement) ->
-    direction = {above: 'up', below: 'down', left: 'left', right: 'right'}[placement]
+window.simplePanesMoveItem = (direction) ->
+    # use to dispatch commands at the editor scope
     editorElement = atom.views.getView atom.workspace.getActiveTextEditor()

+    # PaneContainerElement is not exposed in the api, so we have to get it manually
     paneContainerElement = atom.views.getView atom.workspace.getActivePane()
         .closest('atom-pane-container')
+    # Use paneContainerElement method to get the nearest pane in a given direction
     nearestPaneInDirection = paneContainerElement
-        .nearestPaneInDirection(placement)?.getModel()
+        .nearestPaneInDirection(direction)?.getModel()

+    # if a pane is returned add the current tab
     if nearestPaneInDirection
-        if ['above', 'below'].indexOf(placement) > -1
-            command = 'window:move-active-item-to-pane-' + placement
+        if ['above', 'below'].indexOf(direction) > -1
+            command = 'window:move-active-item-to-pane-' + direction
         else
-            command = 'window:move-active-item-to-pane-on-' + placement
+            command = 'window:move-active-item-to-pane-on-' + direction
         atom.commands.dispatch editorElement, command
     else
-        atom.commands.dispatch(
-            editorElement, 'pane:split-' + direction + '-and-move-active-item')
+        # pane:split-{}-and-move-active-item uses up/down instead of above/below
+        splitMoveDirection = {above: 'up', below: 'down', left: 'left', right: 'right'}[direction]
+        command = 'pane:split-' + splitMoveDirection + '-and-move-active-item'
+        atom.commands.dispatch editorElement, command

 atom.commands.add 'atom-text-editor', 'simple-panes:move-item-above-pane': () ->
-    moveItem 'above'
+    simplePanesMoveItem 'above'

 atom.commands.add 'atom-text-editor', 'simple-panes:move-item-right-of-pane': () ->
-    moveItem 'right'
+    simplePanesMoveItem 'right'

 atom.commands.add 'atom-text-editor', 'simple-panes:move-item-below-pane': () ->
-    moveItem 'below'
+    simplePanesMoveItem 'below'

 atom.commands.add 'atom-text-editor', 'simple-panes:move-item-left-of-pane': () ->
-    moveItem 'left'
+    simplePanesMoveItem 'left'


Fix broken link highlighting for double underscores

--- grammars/restructuredtext.cson
+++ grammars/restructuredtext.cson
@@ -345,10 +345,10 @@

 			{
 				name: "meta.link.reference"
-				begin: "(`)"
-				end:   "(`_)"
-				beginCaptures: 1: name: "punctuation.definition.link.restructuredtext"
-				endCaptures:   1: name: "punctuation.definition.link.restructuredtext"
+				begin: "`"
+				end:   "`__?"
+				beginCaptures: 0: name: "punctuation.definition.link.restructuredtext"
+				endCaptures:   0: name: "punctuation.definition.link.restructuredtext"
 				patterns: [
 					name: "string.other.link.title.restructuredtext"
 					match: "[^`]+"


Copy eventric js into scripts folder

Copy eventric js into scripts folder

--- gulp/development/vendor.coffee
+++ gulp/development/vendor.coffee
@@ -26,8 +26,9 @@

   gulp.task 'build:copy:scripts', ->
+    gulp.src 'node_modules/eventric/build/dist/eventric.js'
+      .pipe gulp.dest('build/ui/scripts')
     gulp.src 'node_modules/commonjs-require/commonjs-require.js'
       .pipe gulp.dest('build/ui/scripts')
     gulp.src 'bower_components/socket.io-client/socket.io.js'
       .pipe gulp.dest('build/ui/scripts')
-


previous_params

--- src/layout/header.coffee
+++ src/layout/header.coffee
@@ -6,7 +6,8 @@
 exports.header = (level) ->

   anchor = (label, key) ->
-    make.anchor label, level.get key
+    href = level.get key
+    if href then make.anchor(label, href) else " "

   track = (event, key) ->
     dict = { _LABEL: key, _EXPORTS: [event], name: -> level.get(key) }
@@ -28,6 +29,7 @@
     make.columns 'progress', [
       "Level #{level.get('level_index')} of #{level.get('level_count')}"
       anchor "skip", 'next_params'
+      anchor "back", 'previous_params'
     ]
     { name: 'Level Progress', _LABEL: 'level_progress' }
     make.columns 'stats', [


Added skiproot feature

Added skiproot feature

--- src/lib/namespace.coffee
+++ src/lib/namespace.coffee
@@ -25,6 +25,8 @@
     if settings.templates? then @templates = settings.templates
     else callback('Error: \'templates\' is not configured')

+    if settings.skiproot? then @skiproot = settings.skiproot
+
     if @groupNamespace? and @templates?
       @callback = callback
       @namespaces = []
@@ -98,7 +100,8 @@
     self = @

     next = ->
-      self.result[0] = 'var ' + self.result[0]
+      if not self.skiproot
+        self.result[0] = 'var ' + self.result[0]
       callback(null)

     # Get the maximum index for group namespaces in the namespaces array
@@ -106,7 +109,8 @@

     # Appends the group namespace declarations
     for g in [0...groupNamespaceLength]
-      self.result.push "#{@namespaces[g]} = #{@namespaces[g]} || {};"
+      if g > 0 or not @skiproot
+        self.result.push "#{@namespaces[g]} = #{@namespaces[g]} || {};"

     if groupNamespaceLength is @namespaces.length
       next()


metric: added retained DAU

metric: added retained DAU

--- src/models/metric.coffee
+++ src/models/metric.coffee
@@ -13,6 +13,21 @@
     numerator:
       select: 'sum(value) / 100'
       from: 'revenue'
+      where: (day) ->
+        "time >= #{dayToMS day}ms AND time < #{dayToMS day + 1}ms"
+  }
+  {
+    name: 'Retained DAU %'
+    isPercent: true
+    numerator:
+      select: 'count(distinct(userId))'
+      from: 'view'
+      where: (day) ->
+        "time >= #{dayToMS day}ms AND time < #{dayToMS day + 1}ms AND " +
+        "joinDay <> '#{day}'"
+    denominator:
+      select: 'count(distinct(userId))'
+      from: 'view'
       where: (day) ->
         "time >= #{dayToMS day}ms AND time < #{dayToMS day + 1}ms"
   }


MetricModel: added sessions / DAU

MetricModel: added sessions / DAU

--- src/models/metric.coffee
+++ src/models/metric.coffee
@@ -35,6 +35,19 @@
             where: (day) ->
               "time >= #{dayToMS day - 1}ms AND time < #{dayToMS day}ms AND " +
               "joinDay = '#{day - 1}'"
+        }
+        {
+          name: 'sessions / DAU'
+          numerator:
+            select: 'count(distinct(sessionId))'
+            from: 'view'
+            where: (day) ->
+              "time >= #{dayToMS day}ms AND time < #{dayToMS day + 1}ms"
+          denominator:
+            select: 'count(distinct(userId))'
+            from: 'view'
+            where: (day) ->
+              "time >= #{dayToMS day}ms AND time < #{dayToMS day + 1}ms"
         }
         # {
         #   name: 'egp / view'


Table widget testing

Table widget testing

--- widgets/table/table.coffee
+++ widgets/table/table.coffee
@@ -1 +1,4 @@
 class Dashing.Table extends Dashing.Widget
+
+    onData: (data) ->
+        console.log data


Reenable drawing'

Reenable drawing'

--- src/main.coffee
+++ src/main.coffee
@@ -16,8 +16,7 @@

 main = ->
   $('body').append(
-    #draw(world)
-    #control(T, world)
+    draw(world)
   )

 # Run Tests


Open received url

Open received url

--- www/coffee/services.coffee
+++ www/coffee/services.coffee
@@ -108,7 +108,7 @@
         # my_media.play()
         alert(e.payload.message)
       else
-        window.open(e.article_url, "_system")
+        window.open(e.payload.article_url, "_system")
         # otherwise we were launched because the user touched a notification in the notification tray.
         if (e.coldstart)
           console.log('--COLDSTART NOTIFICATION--' + '')


removed ninjaproc from main mistfile

removed ninjaproc from main mistfile

--- src/mist.coffee
+++ src/mist.coffee
@@ -14,8 +14,6 @@
 config = require 'commander'
 packageJson = require '../package'

-ninjaProc = process.env.NINJA || "#{__dirname}/ninja/ninja"
-
 config
   .version packageJson.version
 config


enforced mist working directory with Ninja

--- src/mist.coffee
+++ src/mist.coffee
@@ -18,7 +18,7 @@

 ninjaProc = process.env.NINJA || 'ninja'

-runNinja = (ninja)->
+runNinja = (ninja, base)->
   console.log 'platform:', os.platform()
   switch os.platform()
     when 'darwin' or 'linux'
@@ -36,8 +36,9 @@
       console.log 'streaming complete'
       fs.closeSync tmpFile.fd
       console.log 'running Ninja'
-      spawn ninjaProc, [ '-vf', tmpFile.name ], stdio: [ 'pipe',
-        process.stdout, process.stderr ]
+      spawn ninjaProc,
+        [ '-vf', tmpFile.name, '-C', base ],
+          stdio: [ 'pipe',process.stdout, process.stderr ]

 findMist = ()->
   curPath = process.cwd()
@@ -54,6 +55,7 @@

 try
   mistfile = findMist()
+  mistdir = path.dirname mistfile
   console.log 'evaporating Mistfile at', mistfile
   try
     result = MistParser.parse fs.readFileSync(mistfile).toString()
@@ -65,7 +67,7 @@
   console.log '\n' ##
   console.log result ## XXX DEBUG
   console.log '\n' ##
-  runNinja result
+  runNinja result, mistdir
 catch e
   console.error 'mist:', e.toString()
   throw e # XXX DEBUG


Added plugin connector + invalidate alias

--- src/molo.coffee
+++ src/molo.coffee
@@ -70,6 +70,9 @@
   # Cache and queue definition
   cache = {}
   queue = {}
+
+  # Plugin object
+  plugins = {}

   # Molo base object
   root.molo = {}
@@ -117,6 +120,8 @@
     # Abort condition
     return unless Array.isArray name

+
+
     for i in name
       do (i) ->
         # Get the result of cached dependencies
@@ -159,9 +164,14 @@
   # Delete a single module from cache, which forces it to
   # reload this module if it comes up as a dependency the
   # next time
-  root.molo.delete = (name) ->
+  root.molo.delete = root.molo.invalidate = (name) ->
     delete cache[name] if cache[name]
     delete queue[name] if queue[name]
+
+  # Plugin connector
+  root.molo.plugins =
+    add: (pluginName, pluginFunc) -> plugins[pluginName] = pluginFunc
+    delete: (p) -> delete plugins[p] if plugins[p]

   # Completely clear all caches
   root.molo.clear = ->
@@ -173,7 +183,7 @@
   mainHasBeenCalled = false

   root.molo.main = (name, callback) ->
-    if mainHasBeenCalled then throw new TypeError('molo.main can only be called once.')
+    if mainHasBeenCalled then throw new TypeError 'molo.main can only be called once.'

     root.molo.require name, callback


Stop exporting utils.zip.

Stop exporting utils.zip.

--- src/scripts/utils.coffee
+++ src/scripts/utils.coffee
@@ -160,4 +160,3 @@
     m = if gotMap then obj else Immutable.Map(obj)
     m = m.filterNot keyIn '_id', '_rev'
     unless gotMap then m.toObject() else m
-  zip: zip


JS: Properly detect when modifiers are present

--- evesrp/static/js/request.coffee
+++ evesrp/static/js/request.coffee
@@ -30,7 +30,7 @@
     $statusBadge.addClass "label-#{ util.statusColor request.status }"
     $statusBadge.text (ui.i18n.gettext (capitalize request.status))
     # Update modifiers log
-    if modifiers?
+    if request.modifiers.length != 0
         modifiers = for modifier in request.modifiers
             if modifier.void
                 voidedModifierTemplate modifier


Removes console.log in kill task

Removes console.log in kill task

--- tasks/kill.coffee
+++ tasks/kill.coffee
@@ -21,7 +21,6 @@
       lock.killTask.skip[dist] = true
       cb = (err) ->
         if err.code == 'ENOENT' or err.code == 1
-          console.error err
           done()
         else
           done err