more test with json output

more test with json output

--- test/datomic.coffee
+++ test/datomic.coffee
@@ -36,26 +36,26 @@
       datoms.should.not.be.empty
       done()

-###
   it 'should get an entity', (done) ->

     datomic.entity 1, (err, entity) ->
-      entity.should.include ':db/id 1'
+      entity['db/id'].should.equal 1
       done()

   it 'should get an entity with options', (done) ->

     datomic.entity {e:1, since:0}, (err, entity) ->
-      entity.should.include ':db/id 1'
+      entity['db/id'].should.equal 1
       done()

   it 'should allow to query', (done) ->

     datomic.transact '[[:db/add 1 :movie/title "trainspotting"]]', ->
       datomic.q '[:find ?m :where [?m :movie/title "trainspotting"]]', (err, movies) ->
-        movies.should.equal '[[1]]'
+        movies[0][0].should.equal 1
         done()

+###
   it 'should allow to query with opt', (done) ->

     datomic.transact '[[:db/add 2 :movie/title "the matrix"]]', ->


Fixes #1853 -- fs.watch trouble.

Fixes #1853 -- fs.watch trouble.

--- command.coffee
+++ command.coffee
@@ -99,7 +99,9 @@
       path.exists source, (exists) ->
         if topLevel and not exists and source[-7..] isnt '.coffee'
           return compile "#{source}.coffee", sourceIndex, topLevel
-        throw new Error "File not found: #{source}" if topLevel and not exists
+        if topLevel and not exists
+          console.error "File not found: #{source}"
+          process.exit 1
         fs.stat source, (err, stats) ->
           throw err if err
           if stats.isDirectory()
@@ -174,23 +176,28 @@
 # time the file is updated. May be used in combination with other options,
 # such as `--lint` or `--print`.
 watch = (source, base) ->
-  return unless fs.watch
-  fs.stat source, (err, prevStats)->
-    throw err if err
-    watcher = fs.watch source, callback = (event) ->
-      if event is 'rename'
-        watcher.close()
-        try  # if source no longer exists, never mind
-          watcher = fs.watch source, callback
-      else if event is 'change'
-        fs.stat source, (err, stats) ->
-          throw err if err
-          return if stats.size is prevStats.size and
-            stats.mtime.getTime() is prevStats.mtime.getTime()
-          prevStats = stats
-          fs.readFile source, (err, code) ->
-            throw err if err
-            compileScript(source, code.toString(), base)
+
+  prevStats = null
+
+  compile = ->
+    fs.stat source, (err, stats) ->
+      throw err if err
+      return if prevStats and (stats.size is prevStats.size and
+        stats.mtime.getTime() is prevStats.mtime.getTime())
+      prevStats = stats
+      fs.readFile source, (err, code) ->
+        throw err if err
+        compileScript(source, code.toString(), base)
+
+  watcher = fs.watch source, callback = (event) ->
+    if event is 'change'
+      compile()
+    else if event is 'rename'
+      watcher.close()
+      setTimeout ->
+        compile()
+        watcher = fs.watch source, callback
+      , 250

 # Write out a JavaScript source file with the compiled code. By default, files
 # are written out in `cwd` as `.js` files with the same name, but the output


refactor: added single get function for relations

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -17,6 +17,21 @@
       @.api()[api_name].read(options.belongs_to, @.id, params).done callback
     else
       @.api()[api_name].read(@.id, params).done callback
+
+single_get_fn = (id_param, api_name, name, model, options) ->
+  (params = {}, callbackOrObservable) =>
+    callback = (data) =>
+      @[name] model(data)
+
+    ###if typeof callbackOrObservable == 'undefined' and typeof callbackOrObservable['push'] == 'undefined'
+      if typeof callbackOrObservable != 'function'
+        console.error 'Collection.find 2nd parameter needs to be either a function or a pushable object (Array, ObservableArray).\nGiven:'
+        console.error callbackOrObservable
+
+      callback = callbackOrObservable
+    ###
+
+    model.get @[id_param], callback

 lazy_get_fn = (id_param, api_name, name, model, options) ->
   unless callback?


More type locations and dont unify JS for now

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -981,8 +981,8 @@
     throw new Error "matching on hash maps not supported yet"
   else
     [labels, items] = unzip pairs _terms form
-    keyType = new TypeApp hashmapType, stringType
-    compiledItems = uniformCollectionCompile ctx, form, items, keyType
+    keyedType = markOrigin (new TypeApp hashmapType, stringType), form
+    compiledItems = uniformCollectionCompile ctx, form, items, keyedType
     keys = (map (__ string_, _labelName), labels)
     assignCompile ctx, form, (irMap keys, compiledItems)

@@ -991,7 +991,7 @@
   {constraints, itemType, compiled} = termsCompileExpectingSameType ctx, items
   (labelOperator form) if not isCall form
   form.tea = new Constrained (join moreConstraints, constraints),
-      new TypeApp collectionType, itemType
+      (markOrigin (new TypeApp collectionType, itemType), form)
   compiled

 termsCompileExpectingSameType = (ctx, items) ->
@@ -1054,7 +1054,7 @@
         # throw new Error "type name #{atom.symbol} was not defined" unless kind
         malformed ctx, atom, "This type name has not been defined"
         kindOfType = star
-      atomicType atom.symbol, kindOfType
+      markOrigin (atomicType atom.symbol, kindOfType), atom
     else
       expanded
   finalKind = kind type
@@ -1072,7 +1072,8 @@
 typeTupleCompile = (ctx, form) ->
   (labelOperator form)
   elemTypes = _terms form
-  applyKindFn (tupleType elemTypes.length), (typesCompile ctx, elemTypes)...
+  applyKindFn (markOrigin (tupleType elemTypes.length), form),
+    (typesCompile ctx, elemTypes)...

 typeConstructorCompile = (ctx, call) ->
   op = _operator call
@@ -1081,13 +1082,13 @@
   if isAtom op
     name = op.symbol
     compiledArgs = typesCompile ctx, args
-    if name is 'Fn'
+    markOrigin (if name is 'Fn'
       (labelOperator op)
       typeFn compiledArgs...
     else
       arity = args.length
       operatorType = typeNameCompile ctx, op, (kindFn arity)
-      applyKindFn operatorType, compiledArgs...
+      applyKindFn operatorType, compiledArgs...), call
   else
     malformed ctx, op, 'Expected a type constructor instead'

@@ -1101,7 +1102,8 @@
       className = op.symbol
       {constraint} = ctx.classNamed className
       paramKinds = (map kind, constraint.types.types)
-      new ClassConstraint op.symbol, new Types (typesCompile ctx, args, paramKinds)
+      markOrigin (new ClassConstraint op.symbol,
+        new Types (typesCompile ctx, args, paramKinds)), expression
     else
       malformed ctx, expression, 'Class name required in a constraint'
   else
@@ -4031,8 +4033,9 @@

 unify = (ctx, t1, t2) ->
   throw new Error "invalid args to unify" unless ctx instanceof Context and t1 and t2
-  if (includesJsType t1) or (includesJsType t2)
-    return
+  # TODO: need to taint the other type
+  # if (includesJsType t1) or (includesJsType t2)
+  #   return
   sub = ctx.substitution
   ctx.extendSubstitution mostGeneralUnifier (substitute sub, t1), (substitute sub, t2)


Replace matchXML with xml2js deepEqual implementation (to deal with ordering issues)

--- test/helpers.coffee
+++ test/helpers.coffee
@@ -67,15 +67,14 @@

 global.expect = require('chai').expect

-flattenXML = (xml) ->
-  if (!xml)
-    return xml
-  xml.split("\n").join('').   # remove newlines
-    replace(/>\s+</g, '><').  # prunes whitespace between elements
-    replace(/^\s+|\s+$/g, '') # trims whitespace from ends
-
 matchXML = (xml1, xml2) ->
-  expect(flattenXML(xml1)).to.equal(flattenXML(xml2))
+  results = []
+  parser = new (require('xml2js').Parser)()
+  [xml1, xml2].forEach (xml) ->
+    parser.parseString xml, (e,r) ->
+      if e then throw e
+      results.push(r)
+  expect(results[0]).to.eql(results[1])

 MockService = AWS.Service.defineService 'mockService',
   serviceIdentifier: 'mock'


Adding tests to #128

Adding tests to #128

--- test/hotkeys.coffee
+++ test/hotkeys.coffee
@@ -323,6 +323,19 @@
     expect(hotkeys.get('b')).toBe false
     expect(hotkeys.get('c')).toBe false

+  it 'should support pause/unpause for temporary disabling of hotkeys', ->
+    executed = false
+
+    hotkeys.add 'w', ->
+      executed = true
+
+    hotkeys.pause()
+    KeyEvent.simulate('w'.charCodeAt(0), 90)
+    expect(executed).toBe false
+    hotkeys.unpause()
+    KeyEvent.simulate('w'.charCodeAt(0), 90)
+    expect(executed).toBe true
+

   describe 'misc regression tests', ->


Added a missing emit

Added a missing emit

--- src/ui/draggable.coffee
+++ src/ui/draggable.coffee
@@ -79,6 +79,8 @@

 		@_deltas.push correctedDelta

+		@emit Events.DragMove, event
+
 	_touchStart: (event) =>

 		@view.animateStop()


need es5 shim for phantomjs

need es5 shim for phantomjs

--- test/phantom.coffee
+++ test/phantom.coffee
@@ -5,13 +5,14 @@
 url = "http://localhost:8000/test/runner.html"
 page.open url
 page.onInitialized = ->
+  page.injectJs 'lib/es5-shim-v2.2.0.js'
   page.evaluate ->
     window.mocha_callback = (failed) ->
       window.callPhantom failed

 page.onResourceError = (error) ->
-    console.log "tests failed to load: #{error.errorString}"
-    phantom.exit 3
+  console.log "tests failed to load: #{error.errorString}"
+  phantom.exit 3

 page.onCallback = (failed) ->
   if failed > 0


dob2date fix

dob2date fix

--- client/controllers/alle.coffee
+++ client/controllers/alle.coffee
@@ -18,7 +18,7 @@
 			if typeof kind.dob == 'string'
 				console.log "convert dob-string (#{dob}) of #{kind.vorname} #{kind.nachname} to date"
 				dobMoment = moment(kind.dob, "DD.MM.YY")
-				kind.dob = dobMoment.doDate()
+				kind.dob = dobMoment.toDate()
 			else
 				dobMoment = moment(dob.toISOString())
 			now = moment()


moved guid parsing to buffer reading

moved guid parsing to buffer reading

--- src/value-parser.coffee
+++ src/value-parser.coffee
@@ -1,5 +1,6 @@
 iconv = require('iconv-lite')
 sprintf = require('sprintf').sprintf
+guidParser = require('./guid-parser')
 require('./buffertools')

 NULL = (1 << 16) - 1
@@ -176,7 +177,7 @@
         when 0
           value = null
         when 0x10
-          value = buffer.readArray(0x10)
+          value = guidParser.arrayToGuid( buffer.readArray(0x10) )
         else
           throw new Error(sprintf('Unsupported guid size %d at offset 0x%04X', dataLength - 1, buffer.position))
     else


Fixing behaviour of shift+up or shift+down.

--- client/lib/chatMessages.coffee
+++ client/lib/chatMessages.coffee
@@ -207,6 +207,8 @@
 				this.clearEditing()
 				return
 		else if k is 38 or k is 40 # Arrow Up or down
+			return true if event.shiftKey
+
 			if k is 38
 				return if input.value.slice(0, input.selectionStart).match(/[\n]/) isnt null
 				this.toPrevMessage()


Pass through error to callback

Pass through error to callback

--- src/install.coffee
+++ src/install.coffee
@@ -256,7 +256,7 @@
                 process.stdout.write '\u2717\n'.red
               else
                 process.stdout.write '\u2713\n'.green
-              callback()
+              callback(error)

   # Install all the package dependencies found in the package.json file.
   #


added 4th level (ward) to operators

added 4th level (ward) to operators

--- static/coffee/flows/services.coffee
+++ static/coffee/flows/services.coffee
@@ -449,6 +449,7 @@
         { type:'phone', name: 'Has a phone', verbose_name:'has a phone number', operands: 0, voice:true }
         { type:'state', name: 'Has a state', verbose_name:'has a state', operands: 0 }
         { type:'district', name: 'Has a district', verbose_name:'has a district', operands: 1, auto_complete: true, placeholder:'@flow.state' }
+        { type:'ward', name: 'Has a ward', verbose_name:'has a ward', operands: 2, operand_required: false,  auto_complete: true, }
         { type:'regex', name: 'Regex', verbose_name:'matches regex', operands: 1, voice:true, localized:true }
         { type:'true', name: 'Other', verbose_name:'contains anything', operands: 0 }
       ]


timer desync fix addition 2

timer desync fix addition 2

--- src/keyring.coffee
+++ src/keyring.coffee
@@ -118,7 +118,7 @@
   timeToGuestExpiration: (strGuestTag)->
     Utils.ensure(strGuestTag)
     entry = @guestKeyTimeouts[strGuestTag]
-    return 0 if not timeoutEntry
+    return 0 if not entry
     Math.max(Config.RELAY_SESSION_TIMEOUT - (Date.now() - entry.startTime), 0)

   # Synchronous


search test compatibility with americano

--- tests/search.coffee
+++ tests/search.coffee
@@ -1,35 +1,30 @@
 Client = require('request-json').JsonClient
 should = require 'should'
-app = require '../server'
 fixtures = require 'cozy-fixtures'
 path = require 'path'

-client = new Client "http://localhost:8888/"
-
-instantiateApp = require '../server'
-app = instantiateApp()
+helpers = require './helpers'
+helpers.options =
+    serverHost: 'localhost'
+    serverPort: '8888'
+client = new Client "http://#{helpers.options.serverHost}:#{helpers.options.serverPort}/"

 fixtures.setDefaultValues
     dirPath: path.resolve __dirname, './fixtures/'
     silent: true
     removeBeforeLoad: false # useless because we clean the DB before tests

-# clean all the data in database
-cleanDB = (done) -> fixtures.resetDatabase callback: done
+describe "Search management", ->

-describe "Doctype list management", ->
+    before helpers.startApp
+    before helpers.cleanDB

-    before (done) -> app.listen 8888, "127.0.0.1", done
-    before cleanDB
-
-    after (done) ->
-        app.compound.server.close()
-        done()
-    after cleanDB
+    after helpers.stopApp
+    after helpers.cleanDB

     describe "When the database is empty", ->

-        it "There shouldn't be items in doctypes list", (done) ->
+        it "doctypes list should be empty", (done) ->
             client.get 'doctypes', (err, res, body) ->
                 should.exist res
                 res.should.have.property 'statusCode'
@@ -39,15 +34,15 @@

     describe "When we add 2 documents of one doctype (Alarm)", ->

-        before cleanDB
+        before helpers.cleanDB
         before (done) -> fixtures.load doctypeTarget: 'alarm', callback: done
         before (done) -> fixtures.load doctypeTarget: 'metadoctype', callback: done
-        after cleanDB
+        after helpers.cleanDB

         describe "When dataSystem and searchEngine are created'", (done) =>
             before (done) =>
-                @dataSystem = require './../db/dataSystem'
-                @searchEngine = require('./../db/searchEngine')(@dataSystem)
+                @dataSystem = require './../server/db/dataSystem'
+                @searchEngine = require('./../server/db/searchEngine')(@dataSystem)
                 done()

             it "The alarm 'dball' request shouldn't be registered", =>


Remove unused file.

Remove unused file.

--- lib/dart-tools-view.coffee
+++ lib/dart-tools-view.coffee
@@ -1,24 +0,0 @@
-{View} = require 'atom'
-
-module.exports =
-class DartToolsView extends View
-  @content: ->
-    @div class: 'dart-tools overlay from-top', =>
-      @div "The DartTools package is Alive! It's ALIVE!", class: "message"
-
-  initialize: (serializeState) ->
-    atom.workspaceView.command "dart-tools:toggle", => @toggle()
-
-  # Returns an object that can be retrieved when package is activated
-  serialize: ->
-
-  # Tear down any state and detach
-  destroy: ->
-    @detach()
-
-  toggle: ->
-    console.log "DartToolsView was toggled!"
-    if @hasParent()
-      @detach()
-    else
-      atom.workspaceView.append(this)


Add source documentation for the core binders.

--- src/binders.coffee
+++ src/binders.coffee
@@ -1,26 +1,34 @@
-# Core binders that are included with Rivets.js.
-
+# Basic set of core binders that are included with Rivets.js.
+
+# Sets the element's text value.
 Rivets.binders.text = (el, value) ->
   if el.textContent?
     el.textContent = if value? then value else ''
   else
     el.innerText = if value? then value else ''

+# Sets the element's HTML content.
 Rivets.binders.html = (el, value) ->
   el.innerHTML = if value? then value else ''

+# Shows the element when value is true.
 Rivets.binders.show = (el, value) ->
   el.style.display = if value then '' else 'none'

+# Hides the element when value is true (negated version of `show` binder).
 Rivets.binders.hide = (el, value) ->
   el.style.display = if value then 'none' else ''

+# Enables the element when value is true.
 Rivets.binders.enabled = (el, value) ->
   el.disabled = !value

+# Disables the element when value is true (negated version of `enabled` binder).
 Rivets.binders.disabled = (el, value) ->
   el.disabled = !!value

+# Checks a checkbox or radio input when the value is true. Also sets the model
+# property when the input is checked or unchecked (two-way binder).
 Rivets.binders.checked =
   publishes: true
   bind: (el) ->
@@ -33,6 +41,9 @@
     else
       el.checked = !!value

+# Unchecks a checkbox or radio input when the value is true (negated version of
+# `checked` binder). Also sets the model property when the input is checked or
+# unchecked (two-way binder).
 Rivets.binders.unchecked =
   publishes: true
   bind: (el) ->
@@ -45,6 +56,8 @@
     else
       el.checked = !value

+# Sets the element's value. Also sets the model property when the input changes
+# (two-way binder).
 Rivets.binders.value =
   publishes: true
   bind: (el) ->
@@ -63,6 +76,7 @@
       else if value?.toString() isnt el.value?.toString()
         el.value = if value? then value else ''

+# Inserts and binds the element and it's child nodes into the DOM when true.
 Rivets.binders.if =
   block: true

@@ -102,6 +116,8 @@
   update: (models) ->
     @nested?.update models

+# Removes and unbinds the element and it's child nodes into the DOM when true
+# (negated version of `if` binder).
 Rivets.binders.unless =
   block: true

@@ -117,6 +133,7 @@
   update: (models) ->
     Rivets.binders.if.update.call @, models

+# Binds an event handler on the element.
 Rivets.binders['on-*'] =
   function: true

@@ -127,6 +144,7 @@
     Rivets.Util.unbindEvent el, @args[0], @handler if @handler
     Rivets.Util.bindEvent el, @args[0], @handler = @eventHandler value

+# Appends bound instances of the element in place for each item in the array.
 Rivets.binders['each-*'] =
   block: true

@@ -197,6 +215,7 @@

     view.update data for view in @iterated

+# Adds or removes the class from the element when value is true or false.
 Rivets.binders['class-*'] = (el, value) ->
   elClass = " #{el.className} "

@@ -206,6 +225,8 @@
     else
       elClass.replace(" #{@args[0]} ", ' ').trim()

+# Sets the attribute on the element. If no binder above is matched it will fall
+# back to using this binder.
 Rivets.binders['*'] = (el, value) ->
   if value
     el.setAttribute @type, value


capsule query calls to builder to put out change values instead of pending old values from eg dom

--- src/browser.coffee
+++ src/browser.coffee
@@ -80,6 +80,7 @@
         opts.use ?= []
         opts.use = [opts.use] unless isArray opts.use
         @use(plugin) for plugin in opts.use
+        @registerQuery(@query) if @query?

     initialize: () ->
         do @listen
@@ -115,6 +116,17 @@
     removePlaceholder: -> # create a dom object
         throw new Error "Adapter::removePlaceholder not defined."

+    registerQuery: (query) ->
+        old_query = @builder.query
+        @builder.query = (type, tag, key) ->
+            # use changes if they're not synced yet
+            if Object.keys(tag._changes ? {}).indexOf(type) is -1
+                query.call(this, type, tag, key, old_query)
+            else if type is 'attr'
+                tag._changes.attr[key]
+            else
+                tag._changes[type]
+
     # flow control : eventlisteners

     onadd: (parent, el) ->
@@ -166,7 +178,10 @@
                 @animation.push(newtag._browser.replace.call)

     ontext: (el, text) ->
+        el._changes ?= {}
+        el._changes.text = text
         @animation.push prepare_cancelable_manip(el, yes).call =>
+            delete el._changes.text
             @fn.text(el, text)

     onraw: (el, html) ->
@@ -174,7 +189,13 @@
             @fn.raw(el, html)

     onattr: (el, key, value) ->
+        el._changes ?= {}
+        el._changes.attr ?= {}
+        el._changes.attr[key]= value
         @animation.push prepare_cancelable_manip(el, yes).call =>
+            delete el._changes.attr[key]
+            unless Object.keys(el._changes.attr).length
+                delete el._changes.attr
             @fn.attr(el, key, value)

     onshow: (el) ->


backbone-validation to backbone.validation
colorbox to jquery-colorbox
fix overriding missing in builder

git-svn-id: 191398521daea7cb7f19b1ef9fcebb2e52674c35@723 b2333513-9f29-43eb-bce0-aa7d4792c706

--- src/builder.coffee
+++ src/builder.coffee
@@ -39,7 +39,7 @@

     bowerConfig = require sysPath.resolve APPLICATION_PATH, 'bower.json'
     config.dependencies = bowerConfig.dependencies
-    config.overrides = bowerConfig.overrides
+    config.overrides = bowerConfig.overrides || {}

     config.paths = {
         APPLICATION_PATH


Fixing assertion to work cross-browser

Fixing assertion to work cross-browser

--- tests/www/tests/routing.coffee
+++ tests/www/tests/routing.coffee
@@ -138,7 +138,7 @@
       it 'check /deep route\'s state to assure it has changed' , (done)->
         browser.eval '$("#deep-param").data("initial");', (err, status)->
           should.not.exist err
-          should.not.exist status
+          should.not.equal true
           done null

       it 'check if `dynamic-param` is theoricus', (done)->


Update version number

Update version number

--- src/lib/job.coffee
+++ src/lib/job.coffee
@@ -1,2 +1,2 @@
 window.JoB =
-  version: '0.0.1'
+  version: '0.0.3'


rewrite to stupidly easy to follow

rewrite to stupidly easy to follow

--- lib/operator-insert.coffee
+++ lib/operator-insert.coffee
@@ -191,7 +191,6 @@
     super

   modifySelection: ->
-
     switch @vimState.submode
       when 'characterwise'
         # `I(or A)` is short-hand of `ctrl-v I(or A)`
@@ -263,13 +262,15 @@
     #   {
     #     a
     #   }
-    text = ''
-    text = "\n" if swrap.detectVisualModeSubmode(@editor) is 'linewise'
-
+    isLinewiseTarget = swrap.detectVisualModeSubmode(@editor) is 'linewise'
     for selection in @editor.getSelections()
       @setTextToRegisterForSelection(selection)
-      range = selection.insertText(text, autoIndent: true)
-      selection.cursor.moveLeft() unless range.isEmpty()
+      if isLinewiseTarget
+        selection.insertText("\n", autoIndent: true)
+        selection.cursor.moveLeft()
+      else
+        selection.insertText('', autoIndent: true)
+

 class ChangeOccurrence extends Change
   @extend()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -146,7 +146,7 @@
       text: null
       width: 100

-    name =
+    {name} =

     if not I[options.name]
       I[options.name] = 100


Another primitive (for Clojure + Linux JDK)

--- src/natives.coffee
+++ src/natives.coffee
@@ -383,6 +383,8 @@
         Array: [
           o 'newArray(L!/!/Class;I)L!/!/Object;', (rs, _this, len) ->
               rs.heap_newarray _this.$type, len
+          o 'getLength(Ljava/lang/Object;)I', (rs, arr) ->
+              rs.check_null(arr).array.length
         ]
         Proxy: [
           o 'defineClass0(L!/!/ClassLoader;L!/!/String;[BII)L!/!/Class;', (rs,cl,name,bytes,offset,len) ->


Fix bug where opts.stations undefined

Fix bug where opts.stations undefined

--- src/options.coffee
+++ src/options.coffee
@@ -47,7 +47,7 @@
         @enable_scrobbling.attr 'checked', 'checked'
       if opts.notifications == false
         @disable_notifications.attr 'checked', 'checked'
-      if opts.stations != null && opts.stations.length > 0
+      if opts.stations && opts.stations.length > 0
         @show_cached_stations opts.stations
       if opts.theme == 'dark'
         @dark_theme.attr 'checked', 'checked'


Catch error

Catch error

--- src/package.coffee
+++ src/package.coffee
@@ -180,13 +180,12 @@
     grammarsDirPath = path.join(@path, 'grammars')
     grammarPaths = fs.listSync(grammarsDirPath, ['json', 'cson'])
     for grammarPath in grammarPaths
-      console.log 'load sync', grammarPath
       try
         grammar = atom.syntax.readGrammarSync(grammarPath)
         grammar.packageName = @name
         @grammars.push(grammar)
         grammar.activate()
-      catch
+      catch error
         console.warn("Failed to load grammar: #{grammarPath}", error.stack ? error)

     @grammarsLoaded = true


Prepared statement's parameters must be passed even if they're empty

--- src/request.coffee
+++ src/request.coffee
@@ -68,8 +68,7 @@
     @parameters = []

     @addOutputParameter('handle', TYPES.Int)
-    if (@originalParameters.length)
-      @addParameter('params', TYPES.NVarChar, @makeParamsParameter(@originalParameters))
+    @addParameter('params', TYPES.NVarChar, @makeParamsParameter(@originalParameters))
     @addParameter('stmt', TYPES.NVarChar, @sqlTextOrProcedure)

     @sqlTextOrProcedure = 'sp_prepare'


Get this Book Button on Textbook View

Get this Book Button on Textbook View

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -31,6 +31,7 @@
       chapter = currentPage.chapter ? currentPage._parent?.get('chapter') ? ''
       coach = @mediaBody.regions.coach.$el?.get(0)
       isCoach = @isCoach()
+      downloadable = downloads || pageDownloads;

       return {
         jumpToCC: isCoach and coach instanceof Node and document.body.contains(coach)
@@ -38,8 +39,7 @@
         currentPage: currentPage
         chapter: chapter
         pageTitle: currentPage.searchTitle ? currentPage.title
-        hasDownloads: (_.isArray(downloads) and downloads?.length) or
-          (_.isArray(pageDownloads) and pageDownloads?.length)
+        hasDownloads: if _.isArray downloadable then _.some downloadable, (link) -> link.state != 'missing' else false
         derivable: @isDerivable()
         authenticated: session.get('id')
         editable: @isEditable()


[master] Callback save

[master] Callback save

--- compact.coffee
+++ compact.coffee
@@ -28,12 +28,14 @@
           callback()
     , (err) ->
       throw err if err?
-      redis.save()
-      fs.writeFileSync "#{date.format 'YYYY-MM-DD'}.json", JSON.stringify(accumulator)
-      fs.copySync 'dump.rdb', "#{date.format 'YYYY-MM-DD'}.rdb"
-      redis.flushall()
-      debug 'compact finish'
-      callback()
+      redis.save (err) ->
+        throw err if err?
+        fs.writeFileSync "#{date.format 'YYYY-MM-DD'}.json", JSON.stringify(accumulator)
+        fs.copySync 'dump.rdb', "#{date.format 'YYYY-MM-DD'}.rdb"
+        debug 'compact finish'
+        redis.flushall (err) ->
+          throw err if err?
+          callback()

 if require.main == module
   f moment(), require('redis').createClient(), ->


Revert to array instead of using span to group icon and count

--- resources/assets/coffee/react/_components/beatmapset-panel.coffee
+++ resources/assets/coffee/react/_components/beatmapset-panel.coffee
@@ -52,23 +52,25 @@

     condenseDifficulties = beatmapset.beatmaps.length > maxDisplayedDifficulty

+    beatmapIcon = (beatmap, showTitle) ->
+      div
+        className: "#{'beatmapset-panel__difficulty-icon' if !condenseDifficulties}"
+        key: beatmap.id
+        el BeatmapIcon, beatmap: beatmap, showTitle: showTitle
+
     difficulties =
       for own mode, beatmaps of BeatmapHelper.group beatmapset.beatmaps
         if condenseDifficulties
           [..., hardestDiff] = beatmaps
-          span
-            key: mode
-            style:
-              display: "flex"
-              alignItems: "center"
-            el BeatmapIcon, beatmap: hardestDiff, showTitle: false
-            span className: 'beatmapset-panel__difficulty-count', beatmaps.length
+          [
+            beatmapIcon hardestDiff, false
+            span
+              className: 'beatmapset-panel__difficulty-count'
+              key: "#{(hardestDiff.id)}-count", beatmaps.length
+          ]
         else
           for b in beatmaps
-            div
-              className: 'beatmapset-panel__difficulty-icon'
-              key: b.id
-              el BeatmapIcon, beatmap: b
+            beatmapIcon b, true

     div
       className: "beatmapset-panel #{'beatmapset-panel--previewing' if @state.preview != 'ended'}"


support for more expression string conversion types (multip, subexps)

--- math/expression_to_string_spec.coffee
+++ math/expression_to_string_spec.coffee
@@ -23,10 +23,15 @@
       expect(@to_string(exp)).toEqual "10 ^ "
       expect(@to_html_string(exp)).toEqual "10 ˆ "

-  it "displays correctly for multiplication", ->
-    exp = @builder(10, '*',  10)
-    expect(@to_string(exp)).toEqual "10 * 10"
-    expect(@to_html_string(exp)).toEqual "10 × 10"
+  describe "multiplication", ->
+    it "", ->
+      exp = @builder(10, '*',  10)
+      expect(@to_string(exp)).toEqual "10 * 10"
+      expect(@to_html_string(exp)).toEqual "10 × 10"

-
+  describe "sub-expressions", ->
+    it "displays them as parentheses", ->
+      exp = @builder([10, '*',  10])
+      expect(@to_string(exp)).toEqual "( 10 * 10 )"
+      expect(@to_html_string(exp)).toEqual "( 10 × 10 )"


small style change

small style change

--- node_modules/broker/lib/broker.coffee
+++ node_modules/broker/lib/broker.coffee
@@ -28,7 +28,7 @@
             exchange.publish event, payload, options

     on: (channel, event, listener) ->
-        eventMap = @eventMap
+        {eventMap} = @
         key = channel+DELIMITER+event
         return if eventMap[key]


My bad. -cov crept into a require.

My bad. -cov crept into a require.

--- app.coffee
+++ app.coffee
@@ -2,7 +2,7 @@

 solid = require 'solid'

-{BattleServer} = require './server-cov'
+{BattleServer} = require './server'
 server = new BattleServer()

 # Start responding to clients


Improve how params and routes are looked up

--- app.coffee
+++ app.coffee
@@ -19,11 +19,11 @@
   saveit(req, resp)

 saveit = (req, resp) ->
-  content = req.query['content']
+  content = req.param('content')
   unless (content)
     resp.json({error: 'File content is missing'}, 406)
   else
-    resp.attachment(req.param['filename'])
+    resp.attachment(req.param('filename'))
     resp.send(content)

 app.listen process.env.PORT or 4000, -> console.log 'Listening...'


be ready to serve files from per-user containers

--- app.coffee
+++ app.coffee
@@ -42,6 +42,12 @@
 	else
 		next new Error("invalid project id")

+app.param 'user_id', (req, res, next, project_id) ->
+	if project_id?.match /^[a-zA-Z0-9_-]+$/
+		next()
+	else
+		next new Error("invalid user id")
+
 app.param 'build_id', (req, res, next, build_id) ->
 	if build_id?.match OutputCacheManager.BUILD_REGEX
 		next()
@@ -71,6 +77,11 @@
 			'-' + Number(stat.size).toString(16) + '"'
 		res.set("Etag", etag(path, stat))
 	res.set("Content-Type", ContentTypeMapper.map(path))
+
+app.get "/project/:project_id/user/:user_id/build/:build_id/output/*", (req, res, next) ->
+	# for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId)
+	req.url = "/#{req.params.project_id}-#{req.params.user_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}")
+	staticServer(req, res, next)

 app.get "/project/:project_id/build/:build_id/output/*", (req, res, next) ->
 	# for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId)


print files to be ignored

print files to be ignored

--- app.coffee
+++ app.coffee
@@ -58,6 +58,7 @@
     try
       files = fs.readFileSync '../commit_analyzer_ignore.txt', 'utf-8'
       files = files.split('\n')
+      console.log "\n\nIgnoring #{files.join(', ')}"
       _.each files, (file) -> Ignored.files.push file
     catch err
       console.log "\n\nunable to find commit_analyzer_ignore.txt file; not ignoring any files...\n\n"


Made config check more nuanced

Made config check more nuanced

--- app/facebook.coffee
+++ app/facebook.coffee
@@ -30,7 +30,7 @@
 ###

 config = (require '../config.coffee').facebook
-unless config
+unless config?.appId and config?.secretKey
 	throw new Error('facebook module config missing')

 fb = require 'fb'


Stray console.log

Stray console.log

--- apps/article/test/templates.coffee
+++ apps/article/test/templates.coffee
@@ -90,7 +90,6 @@
       embed: ->
       asset: ->
       sd: {}
-    console.log fixtures.article
     html.should.containEql 'icon-logotype'
     html.should.containEql '
Top Ten Booths
'
     html.should.containEql 'Just before the lines start forming'


Add try/catch for JSON.parse.

Add try/catch for JSON.parse.

--- src/robot-direct.coffee
+++ src/robot-direct.coffee
@@ -51,7 +51,7 @@
       return [regex, options, cb]

     checker = (obj) ->
-      false unless obj?
+      return false unless obj?
       switch prop
         when "stamp"  then obj.stamp_set? && obj.stamp_index?
         when "yesno"  then obj.question? && not obj.options?
@@ -61,7 +61,7 @@
         else obj[prop]?

     regex = /({.*})/
-    cb = (msg) -> callback msg if checker(msg.json = JSON.parse msg.match[1])
+    cb = (msg) -> callback msg if checker(msg.json = try JSON.parse msg.match[1] catch e then null)
     return [regex, options, cb]

 module.exports =


Remove unused method

Remove unused method

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -231,12 +231,6 @@
     {repository} = metadata
     repoUrl = repository?.url ? repository ? ''
     repoUrl.replace(/\.git$/, '').replace(/\/+$/, '')
-
-  getAuthorUserName: (pack) ->
-    return null unless repoUrl = @getRepositoryUrl(pack)
-    repoName = url.parse(repoUrl).pathname
-    chunks = repoName.match '/(.+?)/'
-    chunks?[1]

   checkNativeBuildTools: ->
     deferred = Q.defer()


Handle apm process errors

Handle apm process errors

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -32,7 +32,7 @@

   loadInstalled: (callback) ->
     args = ['ls', '--json']
-    @runCommand args, (code, stdout, stderr) ->
+    apmProcess = @runCommand args, (code, stdout, stderr) ->
       if code is 0
         try
           packages = JSON.parse(stdout)
@@ -45,6 +45,8 @@
         error.stdout = stdout
         error.stderr = stderr
         callback(error)
+
+    handleProcessErrors(apmProcess, 'Fetching local packages failed.', callback)

   loadFeatured: (loadThemes, callback) ->
     unless callback
@@ -56,7 +58,7 @@
     args.push('--themes') if loadThemes
     args.push('--compatible', version) if semver.valid(version)

-    @runCommand args, (code, stdout, stderr) ->
+    apmProcess = @runCommand args, (code, stdout, stderr) ->
       if code is 0
         try
           packages = JSON.parse(stdout) ? []
@@ -70,13 +72,15 @@
         error.stdout = stdout
         error.stderr = stderr
         callback(error)
+
+    handleProcessErrors(apmProcess, 'Fetching featured packages failed.', callback)

   loadOutdated: (callback) ->
     args = ['outdated', '--json']
     version = atom.getVersion()
     args.push('--compatible', version) if semver.valid(version)

-    @runCommand args, (code, stdout, stderr) ->
+    apmProcess = @runCommand args, (code, stdout, stderr) ->
       if code is 0
         try
           packages = JSON.parse(stdout) ? []
@@ -90,11 +94,13 @@
         error.stdout = stdout
         error.stderr = stderr
         callback(error)
+
+    handleProcessErrors(apmProcess, 'Fetching outdated packages and themes failed.', callback)

   loadPackage: (packageName, callback) ->
     args = ['view', packageName, '--json']

-    @runCommand args, (code, stdout, stderr) ->
+    apmProcess = @runCommand args, (code, stdout, stderr) ->
       if code is 0
         try
           packages = JSON.parse(stdout) ? []
@@ -108,11 +114,13 @@
         error.stdout = stdout
         error.stderr = stderr
         callback(error)
+
+    handleProcessErrors(apmProcess, "Fetching package '#{packageName}' failed.", callback)

   loadCompatiblePackageVersion: (packageName, callback) ->
     args = ['view', packageName, '--json', '--compatible', @normalizeVersion(atom.getVersion())]

-    @runCommand args, (code, stdout, stderr) ->
+    apmProcess = @runCommand args, (code, stdout, stderr) ->
       if code is 0
         try
           packages = JSON.parse(stdout) ? []
@@ -126,6 +134,8 @@
         error.stdout = stdout
         error.stderr = stderr
         callback(error)
+
+    handleProcessErrors(apmProcess, "Fetching package '#{packageName}' failed.", callback)

   getInstalled: ->
     Q.nbind(@loadInstalled, this)()
@@ -157,7 +167,7 @@
     else if options.packages
       args.push '--packages'

-    @runCommand args, (code, stdout, stderr) ->
+    apmProcess = @runCommand args, (code, stdout, stderr) ->
       if code is 0
         try
           packages = JSON.parse(stdout) ? []
@@ -170,6 +180,10 @@
         error.stdout = stdout
         error.stderr = stderr
         deferred.reject(error)
+
+    apmProcess.onWillThrowError ({error, handle}) ->
+        handle()
+        deferred.reject(createProcessError("Searching for \u201C#{query}\u201D failed.", error))

     deferred.promise

@@ -218,6 +232,12 @@

     @unload(name)
     args = ['install', "#{name}@#{version}"]
+
+    onError = (error) =>
+      error.packageInstallError = not theme
+      @emitPackageEvent 'install-failed', pack, error
+      callback(error)
+
     exit = (code, stdout, stderr) =>
       if code is 0
         if activateOnSuccess
@@ -232,18 +252,21 @@
         error = new Error("Installing \u201C#{name}@#{version}\u201D failed.")
         error.stdout = stdout
         error.stderr = stderr
-        error.packageInstallError = not theme
-        @emitPackageEvent 'install-failed', pack, error
-        callback(error)
-
-    @runCommand(args, exit)
+        onError(error)
+
+    apmProcess = @runCommand(args, exit)
+    handleProcessErrors(apmProcess, "Installing \u201C#{name}@#{version}\u201D failed.", onError)

   uninstall: (pack, callback) ->
     {name} = pack

     atom.packages.deactivatePackage(name) if atom.packages.isPackageActive(name)

-    @runCommand ['uninstall', '--hard', name], (code, stdout, stderr) =>
+    onError = (error) =>
+      @emitPackageEvent 'uninstall-failed', pack, error
+      callback(error)
+
+    apmProcess = @runCommand ['uninstall', '--hard', name], (code, stdout, stderr) =>
       if code is 0
         @unload(name)
         callback?()
@@ -252,8 +275,9 @@
         error = new Error("Uninstalling \u201C#{name}\u201D failed.")
         error.stdout = stdout
         error.stderr = stderr
-        @emitPackageEvent 'uninstall-failed', pack, error
-        callback(error)
+        onError(error)
+
+    handleProcessErrors(apmProcess, "Uninstalling \u201C#{name}\u201D failed.", onError)

   canUpgrade: (installedPackage, availableVersion) ->
     return false unless installedPackage?
@@ -275,11 +299,15 @@
   checkNativeBuildTools: ->
     deferred = Q.defer()

-    @runCommand ['install', '--check'], (code, stdout, stderr) =>
+    apmProcess = @runCommand ['install', '--check'], (code, stdout, stderr) =>
       if code is 0
         deferred.resolve()
       else
         deferred.reject(new Error())
+
+    apmProcess.onWillThrowError ({error, handle}) ->
+        handle()
+        deferred.reject(error)

     deferred.promise

@@ -303,3 +331,14 @@
   error.stdout = ''
   error.stderr = "#{parseError.message}: #{stdout}"
   error
+
+createProcessError = (message, processError) ->
+  error = new Error(message)
+  error.stdout = ''
+  error.stderr = processError.message
+  error
+
+handleProcessErrors = (apmProcess, message, callback) ->
+  apmProcess.onWillThrowError ({error, handle}) ->
+      handle()
+      callback(createProcessError(message, error))


Packages with grammars have settings as well

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -23,6 +23,10 @@
       false

   packageHasSettings: (packageName) ->
+    grammars = atom.grammars.getGrammars() ? []
+    for grammar in grammars when grammar.path
+      return true if grammar.packageName is packageName
+
     pack = atom.packages.getLoadedPackage(packageName)
     pack.activateConfig() if pack? and not atom.packages.isPackageActive(packageName)
     schema = atom.config.getSchema(packageName)


remove canvases in the current pane only

--- lib/pdf-editor-view.coffee
+++ lib/pdf-editor-view.coffee
@@ -33,7 +33,7 @@
   renderPdf: ->
     pdfData = new Uint8Array(fs.readFileSync(@filePath));

-    $("canvas").remove()
+    @container.find("canvas").remove()

     PDFJS.getDocument(pdfData).then (pdfDocument) =>
       for pdfPageNumber in [1..pdfDocument.numPages]


[client][app][CommonView][opinions] changed if structure

--- client/landing/app/CommonViews/opinions/discussionactivityopinionview.coffee
+++ client/landing/app/CommonViews/opinions/discussionactivityopinionview.coffee
@@ -21,8 +21,8 @@
       if opinionsData?.length and items.length < 2
         opinions = opinionsData[0..1]
         for opinion in opinions
-          for item in items
-            @opinionList.addItem opinion if opinion isnt item.getData()
+          for item in items when opinion isnt item.getData()
+            @opinionList.addItem opinion

     # the snapshot opinion list gets populated with 2 items at max initially


make getAppIcon reusable

make getAppIcon reusable

--- client/landing/app/MainApp/filetree/modals/openwith/openwithmodalitem.coffee
+++ client/landing/app/MainApp/filetree/modals/openwith/openwithmodalitem.coffee
@@ -6,28 +6,7 @@

     super options, data

-    {authorNick, name, version, icns} = manifest = @getData()
-
-    resourceRoot = "#{KD.appsUri}/#{authorNick}/#{name}/#{version}/"
-
-    if manifest.devMode
-      resourceRoot = "https://#{authorNick}.koding.com/.applications/#{__utils.slugify name}/"
-
-    image  = if name is "Ace" then "icn-ace" else "default.app.thumb"
-    thumb  = "#{KD.apiUri}/images/#{image}.png"
-
-    for size in [64, 128, 160, 256, 512]
-      if icns and icns[String size]
-        thumb = "#{resourceRoot}/#{icns[String size]}"
-        break
-
-    @img = new KDCustomHTMLView
-      tagName     : "img"
-      bind        : "error"
-      error       : =>
-        @img.$().attr "src", "/images/default.app.thumb.png"
-      attributes  :
-        src       : thumb
+    @img = KD.utils.getAppIcon @getData()

     @setClass "not-supported" unless @getOptions().supported

@@ -38,7 +17,8 @@
       delegate.selectedApp = @

   pistachio: ->
+    data = @getData()
     """
-      {{> @img}}
-
#{@getData().name}

-    """
+      {{> @img }}
+
#{data.name}

+    """


Update CampaignView width to fit volcano world

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -927,7 +927,7 @@
     return if 0.2 < fraction < 0.8
     direction = if fraction < 0.5 then 1 else -1
     magnitude = 0.2 * bodyWidth * (if direction is -1 then fraction - 0.8 else 0.2 - fraction) / 0.2
-    portalsWidth = 2536  # TODO: if we add campaigns or change margins, this will get out of date...
+    portalsWidth = 2853  # TODO: if we add campaigns or change margins, this will get out of date...
     scrollTo = $portals.offset().left + direction * magnitude
     scrollTo = Math.max bodyWidth - portalsWidth, scrollTo
     scrollTo = Math.min 0, scrollTo


Removed debug statement from slash command

--- src/scripts/time.coffee
+++ src/scripts/time.coffee
@@ -201,7 +201,6 @@
     body = req.body
     if body.token is process.env.SLASH_PUNCH_TOKEN
       msg = body.text
-      console.log body
       channel_name = body.channel_name?.replace('#', '')
       if channel_name and channel_name is 'directmessage'
         channel_name = body.user_name


Fix right padding when no commits behind ahead

--- lib/status-bar-view.coffee
+++ lib/status-bar-view.coffee
@@ -119,6 +119,8 @@
     else
       @commitsBehind.hide()

+    if repo.upstream.ahead > 0 or repo.upstream.behind > 0 then @commitsArea.show() else @commitsArea.hide()
+
     status = repo.statuses[itemPath]
     if repo.isStatusModified(status)
       @gitStatusIcon.addClass('icon icon-diff-modified status-modified')


Fix derby-auth

Fix derby-auth

--- src/server/index.coffee
+++ src/server/index.coffee
@@ -64,7 +64,7 @@
   # Adds req.getModel method
   .use(store.modelMiddleware())
   # Adds auth
-  .use(auth(store, strategies, options))
+  .use(auth.middleware(strategies, options))
   # Creates an express middleware from the app's routes
   .use(app.router())
   .use(expressApp.router)


fixing tests

fixing tests

--- test/javascripts/remote_test.coffee
+++ test/javascripts/remote_test.coffee
@@ -179,15 +179,15 @@
       server.respondWith("POST", "/foo/bar",
             [200, { "Content-Type": "application/json" },
              '[{ "id": 12, "comment": "Hey there" }]']);
+
       remote = new TurboGraft.Remote
         httpRequestType: "POST"
         httpUrl: "/foo/bar"
         refreshOnSuccess: "a b c"

       server.respond()
-
       assert @refreshStub.calledWith
-        response: remote
+        response: sinon.match.has('responseText', '[{ "id": 12, "comment": "Hey there" }]')
         onlyKeys: ['a', 'b', 'c']

     it 'XHR=200: will trigger Page.refresh with refresh-on-success when full-refresh is provided', ->
@@ -235,7 +235,7 @@
       server.respond()

       assert @refreshStub.calledWith
-        response: remote
+        response: sinon.match.has('responseText', '[{ "id": 12, "comment": "Hey there" }]')
         onlyKeys: ['a', 'b', 'c']

     it 'will not trigger Page.refresh if no refresh-on-error is present', ->


tracker.click: Don't run trackURLs on an empty clickTrackingURLTemplates array

--- src/tracker.coffee
+++ src/tracker.coffee
@@ -109,7 +109,7 @@
         @trackingEvents = []

     click: ->
-        if @clickTrackingURLTemplates?
+        if @clickTrackingURLTemplates?.length
             @trackURLs @clickTrackingURLTemplates

         if @clickThroughURLTemplate?


Return non-throwing errors if Cypher query fails

--- neo4jdriver.coffee
+++ neo4jdriver.coffee
@@ -61,7 +61,10 @@
             if results?.data
               for result in results.data
                 if clones?[result.id]
-                  @batchResults[result.id] = result.body
+                  if _.isEmpty result.body.errors
+                    @batchResults[result.id] = result.body
+                  else
+                    @batchResults[result.id] = result.body.errors

                   clones[result.id] = undefined
                   delete clones[result.id]


change sudo to 2fa; improve responses

change sudo to 2fa; improve responses

--- src/uapanfw.coffee
+++ src/uapanfw.coffee
@@ -22,6 +22,7 @@

 modulename = 'fw'
 blacklistfile = modulename + ".json"
+displayfmt = '%-4s %-50s %-15s %s'

 isAuthorized = (robot, msg) ->
   u = msg.envelope.user
@@ -29,10 +30,10 @@
   msg.reply "Not authorized.  Missing fw role."
   return false

-isSudo = (robot, msg) ->
+is2fa = (robot, msg) ->
   u = msg.envelope.user
-  return true if robot.auth.isSudo(u)
-  msg.reply "Sudo required."
+  return true if robot.auth.is2fa(u)
+  msg.reply "2fa required.  Use `auth 2fa` to validate identity."
   return false

 module.exports = (robot) ->
@@ -125,8 +126,7 @@
     bl_search = msg.match[2] if msg.match[2]

     arr = []
-    fmt = '%-4s %-50s %-15s %s'
-    arr.push sprintf fmt, 'Type', 'Value', 'Expiration', 'Creator'
+    arr.push sprintf displayfmt, 'Type', 'Value', 'Expiration', 'Creator'
     for obj in fwdata.blacklist
       expires = moment(obj.expires)
       if bl_type and bl_type != obj.type
@@ -140,7 +140,7 @@
         continue
       #console.log 'adding to array: '
       #console.log obj
-      arr.push sprintf fmt, obj.type, obj.val, expires.fromNow(), obj.creator
+      arr.push sprintf displayfmt, obj.type, obj.val, expires.fromNow(), obj.creator

     msg.reply "Blacklist items and expirations\n```\n"+ arr.join("\n") + "\n```"

@@ -152,7 +152,7 @@

   robot.respond /fw (?:blacklist|b) add (url|cidr) ([^ ]+)(?: ([^ ]+)|)$/i, (msg) ->
     return unless isAuthorized robot, msg
-    return unless isSudo robot, msg
+    return unless is2fa robot, msg

     who = msg.envelope.user.name

@@ -205,7 +205,7 @@

   robot.respond /fw (?:blacklist|b) (?:delete|del|d) (url|cidr) ([^ ]+)$/i, (msg) ->
     return unless isAuthorized robot, msg
-    return unless isSudo robot, msg
+    return unless is2fa robot, msg

     who = msg.envelope.user.name
     bl_type = msg.match[1]
@@ -217,8 +217,7 @@

     arr = []
     newdata = []
-    fmt = '%-4s %-50s %-15s %s'
-    arr.push sprintf fmt, 'Type', 'Value', 'Expiration', 'Creator'
+    arr.push sprintf displayfmt, 'Type', 'Value', 'Expiration', 'Creator'
     for obj in fwdata.blacklist
       expires = moment(obj.expires)
       if bl_type and bl_type != obj.type
@@ -230,13 +229,13 @@
       if expires.isBefore() # now
         newdata.push obj
         continue
-      arr.push sprintf fmt, obj.type, obj.val, expires.fromNow(), obj.creator
+      arr.push sprintf displayfmt, obj.type, obj.val, expires.fromNow(), obj.creator

     deltaN = fwdata.blacklist.length - newdata.length
     if deltaN > 0
       usermsg = "Removed `#{deltaN}` entries from firewall blacklist.  " +
-        "Change will be applied in < 5 minutes.\n" +
-        "Removed: ```"+ arr.join("\n") + "```"
+        "Change will be applied in < 5 minutes. Removed: " +
+        "```"+ arr.join("\n") + "```"
       fwdata.blacklist = newdata
       fs.writeFileSync blacklistfile, JSON.stringify(fwdata), 'utf-8'
     else


improve isAuthorized and isSudo

improve isAuthorized and isSudo

--- src/uapanfw.coffee
+++ src/uapanfw.coffee
@@ -25,11 +25,11 @@

 isAuthorized = (robot, msg) ->
   u = msg.envelope.user
-  return true if robot.auth.hasRole(u,'fw') and robot.auth.isSudo(u)
-  msg.reply "Not authorized.  Missing fw role or not sudo."
+  return true if robot.auth.hasRole(u,'fw')
+  msg.reply "Not authorized.  Missing fw role."
   return false

-isSudo = (msg) ->
+isSudo = (robot, msg) ->
   u = msg.envelope.user
   return true if robot.auth.isSudo(u)
   msg.reply "Sudo required."
@@ -138,7 +138,7 @@

   robot.respond /fw (?:blacklist|b) add (url|cidr) ([^ ]+)(?: ([^ ]+)|)$/i, (msg) ->
     return unless isAuthorized robot, msg
-    return unless isSudo msg
+    return unless isSudo robot, msg

     bl =
       created: moment().format()
@@ -178,7 +178,7 @@

   robot.respond /fw (?:blacklist|b) (?:delete|del|d) (url|cidr) ([^ ]+)$/i, (msg) ->
     return unless isAuthorized robot, msg
-    return unless isSudo msg
+    return unless isSudo robot, msg

     bl_type = msg.match[1]
     bl_search = msg.match[2]


Remove redundant 's' binding

Remove redundant 's' binding

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -42,7 +42,6 @@
   'D': 'vim-mode:delete-to-last-character-of-line'
   'c': 'vim-mode:change'
   'C': 'vim-mode:change-to-last-character-of-line'
-  's': 'vim-mode:change'
   'S': 'vim-mode:substitute-line'
   'r': 'vim-mode:replace'
   'I': 'vim-mode:insert-at-beginning-of-line'


Remove unneeded fsPlus prefix

Remove unneeded fsPlus prefix

--- src/fs-plus.coffee
+++ src/fs-plus.coffee
@@ -424,8 +424,8 @@

   isCaseInsensitive: ->
     unless fsPlus.caseInsensitiveFs?
-      lowerCaseStat = fsPlus.statSyncNoException(__filename.toLowerCase())
-      upperCaseStat = fsPlus.statSyncNoException(__filename.toUpperCase())
+      lowerCaseStat = statSyncNoException(__filename.toLowerCase())
+      upperCaseStat = statSyncNoException(__filename.toUpperCase())
       if lowerCaseStat and upperCaseStat
         fsPlus.caseInsensitiveFs = lowerCaseStat.dev is upperCaseStat.dev and lowerCaseStat.ino is upperCaseStat.ino
       else


Add title to Slack file upload

Add title to Slack file upload

The default title is the query string, which isn't very user friendly.
This PR will use the panel title itself instead.

--- src/grafana.coffee
+++ src/grafana.coffee
@@ -389,10 +389,12 @@
             uploadData =
               url: slack_url + '/api/files.upload'
               formData:
+                title: "#{title}"
                 channels: msg.envelope.room
                 token: slack_token
                 # grafanaDashboardRequest() is the method that downloads the .png
                 file: grafanaDashboardRequest()
+                filetype: 'png'

             # Try to upload the image to slack else pass the link over
             request.post uploadData, (err, httpResponse, body) ->


Set same ivars in constructor in all cases

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -16,10 +16,12 @@
 class Grammar
   Emitter.includeInto(this)

-  constructor: (@registry, {@name, @fileTypes, @scopeName, injections, injectionSelector, patterns, repository, @foldingStopMarker, firstLineMatch}) ->
+  constructor: (@registry, options={}) ->
+    {@name, @fileTypes, @scopeName, @foldingStopMarker} = options
+    {injections, injectionSelector, patterns, repository, firstLineMatch} = options
+
     @repository = null
     @initialRule = null
-    @firstLineRegex = null
     @maxTokensPerLine = 100

     @rawPatterns = patterns
@@ -28,8 +30,14 @@

     if injectionSelector?
       @injectionSelector = new ScopeSelector(injectionSelector)
+    else
+      @injectionSelector = null

-    @firstLineRegex = new OnigRegExp(firstLineMatch) if firstLineMatch
+    if firstLineMatch
+      @firstLineRegex = new OnigRegExp(firstLineMatch)
+    else
+      @firstLineRegex = null
+
     @fileTypes ?= []
     @includedGrammarScopes = []


Add OPTIONS_GHC to the recognized pragmas.

--- src/haskell.coffee
+++ src/haskell.coffee
@@ -511,7 +511,7 @@
       begin: /\{-#/
       end: /#-\}/
       patterns: [
-          match: /\b(LANGUAGE|UNPACK|INLINE)(?!')\b/
+          match: /\b(LANGUAGE|UNPACK|INLINE|OPTIONS_GHC)(?!')\b/
           name: 'keyword.other.preprocessor.haskell'
       ]
     function_type_declaration:


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -149,9 +149,9 @@
       if not I[I.name]
         I[I.name] = 100

-      if not I["max#{Iname.capitalize()}"]
+      if not I["max#{I.name.capitalize()}"]
         if I[name]
-          I["max#{name.capitalize()}"] = I[name]
+          I["max#{I.name.capitalize()}"] = I[name]
         else
           I["max#{name.capitalize()}"] = 100


Groups: For koding group only personal vms are fetched

--- client/Environments/views/scene/environmentmachinecontainer.coffee
+++ client/Environments/views/scene/environmentmachinecontainer.coffee
@@ -14,10 +14,7 @@

     vmc = KD.getSingleton 'vmController'

-    {entryPoint} = KD.config
-    cmd = if entryPoint then 'fetchGroupVMs' else 'fetchVMs'
-
-    vmc[cmd] yes, (err, vms)=>
+    vmc.fetchGroupVMs yes, (err, vms)=>
       if err or vms.length is 0
         @emit "DataLoaded"
         return warn "Failed to fetch VMs", err  if err


* [SubkeyCache] extract forceCreate and addRef option from options object before using

--- src/subkey-cache.coffee
+++ src/subkey-cache.coffee
@@ -8,7 +8,12 @@

   constructor: -> super
   createSubkey: (keyPath, Subkey, options, callback) ->
-    if options && options.forceCreate == true
+    if options
+      forceCreate = options.forceCreate
+      addRef = options.addRef
+      delete options.forceCreate
+      delete options.addRef
+    if forceCreate is true
       result = new Subkey(options, callback)
     else
       result = @get keyPath
@@ -19,7 +24,7 @@
         @set keyPath, result, options
         result.on "destroyed", (item) =>
           @del keyPath
-      result.addRef() if !options || options.addRef != false
+      result.addRef() if addRef isnt false
     result
   subkeys: (aKeyPattern)->
     result = {}


strings/newLineToBr: Properly replace windows/mac linebreaks as well

Replace \r\n (win) and \r (mac) as well as good ol' \n

--- src/swag.strings.coffee
+++ src/swag.strings.coffee
@@ -37,4 +37,4 @@
     "#{space}#{str}#{space}"

 Handlebars.registerHelper 'newLineToBr', (str) ->
-    str.replace /\n/g, '
'
+    str.replace /\r?\n|\r/g, '
'


Logging; auto scroll to end.

Logging; auto scroll to end.

--- background_scripts/bg_utils.coffee
+++ background_scripts/bg_utils.coffee
@@ -73,6 +73,8 @@
             milliseconds = "00" + milliseconds if milliseconds < 10
             milliseconds = "0" + milliseconds if milliseconds < 100
             dateString = "#{hours}:#{minutes}:#{seconds}.#{milliseconds}"
-            viewWindow.document.getElementById("log-text").value += "#{dateString}: #{message}\n"
+            logElement = viewWindow.document.getElementById "log-text"
+            logElement.value += "#{dateString}: #{message}\n"
+            logElement.scrollTop = 2000000000

 root.BgUtils = BgUtils


Single quotes are clearer here.

Single quotes are clearer here.

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -49,7 +49,7 @@
       keyChar = keyChar.toLowerCase() unless keyChar.length == 1
       modifiers = (modifier.toLowerCase() for modifier in modifiers)
       modifiers.sort()
-      ["<#{[modifiers..., keyChar].join "-"}>", @parseKeySequence(RegExp.$2)...]
+      ["<#{[modifiers..., keyChar].join '-'}>", @parseKeySequence(RegExp.$2)...]
     else
       [key[0], @parseKeySequence(key[1..])...]


Move Sync.storeAndPropagate to Settings.storeAndPropagate

This function does nothing related to Sync, and only affects Settings.

--- background_scripts/settings.coffee
+++ background_scripts/settings.coffee
@@ -4,10 +4,6 @@
 #   changes to localStorage and into vimium's internal state.
 # * Sync.fetchAsync() polls chrome.storage.sync at startup, similarly propagating
 #   changes to localStorage and into vimium's internal state.
-#
-# Changes are propagated into vimium's state using the same mechanism
-# (Settings.performPostUpdateHook) that is used when options are changed on
-# the options page.
 #
 # The effect is best-effort synchronization of vimium options/settings between
 # chrome/vimium instances.
@@ -33,30 +29,12 @@
     @storage.get null, (items) =>
       unless chrome.runtime.lastError
         for own key, value of items
-          @storeAndPropagate key, value
+          Settings.storeAndPropagate key, value if @shouldSyncKey key

   # Asynchronous message from synced storage.
   handleStorageUpdate: (changes, area) ->
     for own key, change of changes
-      @storeAndPropagate key, change?.newValue
-
-  # Only ever called from asynchronous synced-storage callbacks (fetchAsync and handleStorageUpdate).
-  storeAndPropagate: (key, value) ->
-    return unless key of Settings.defaults
-    return if not @shouldSyncKey key
-    return if value and key of localStorage and localStorage[key] is value
-    defaultValue = Settings.defaults[key]
-    defaultValueJSON = JSON.stringify(defaultValue)
-
-    if value and value != defaultValueJSON
-      # Key/value has been changed to non-default value at remote instance.
-      localStorage[key] = value
-      Settings.performPostUpdateHook key, JSON.parse(value)
-    else
-      # Key has been reset to default value at remote instance.
-      if key of localStorage
-        delete localStorage[key]
-      Settings.performPostUpdateHook key, defaultValue
+      Settings.storeAndPropagate key, change?.newValue if @shouldSyncKey key

   # Only called synchronously from within vimium, never on a callback.
   # No need to propagate updates to the rest of vimium, that's already been done.
@@ -111,6 +89,23 @@
   # postUpdateHooks convenience wrapper
   performPostUpdateHook: (key, value) ->
     @postUpdateHooks[key] value if @postUpdateHooks[key]
+
+  # Only ever called from asynchronous synced-storage callbacks (fetchAsync and handleStorageUpdate).
+  storeAndPropagate: (key, value) ->
+    return unless key of @defaults
+    return if value and key of localStorage and localStorage[key] is value
+    defaultValue = @defaults[key]
+    defaultValueJSON = JSON.stringify(defaultValue)
+
+    if value and value != defaultValueJSON
+      # Key/value has been changed to non-default value at remote instance.
+      localStorage[key] = value
+      @performPostUpdateHook key, JSON.parse(value)
+    else
+      # Key has been reset to default value at remote instance.
+      if key of localStorage
+        delete localStorage[key]
+      @performPostUpdateHook key, defaultValue

   # options.coffee and options.html only handle booleans and strings; therefore all defaults must be booleans
   # or strings


computecontroller: start passing callback to new machine request

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -507,7 +507,7 @@
       else callback @lastKnownUserPlan = subscription.planTitle

-  handleNewMachineRequest: ->
+  handleNewMachineRequest: (callback = noop)->

     return  if @_inprogress
     @_inprogress = yes
@@ -528,6 +528,8 @@

             new ComputePlansModal.Paid options
             @_inprogress = no
+
+            callback()

           else

@@ -538,6 +540,8 @@
               if err? or machines.length > 0
                 new ComputePlansModal.Free options
                 @_inprogress = no
+
+                callback()

               else if machines.length is 0

@@ -550,6 +554,8 @@
                 }, (err, machine)=>

                   @_inprogress = no
+
+                  callback()

                   unless KD.showError err
                     KD.userMachines.push machine


needs double-arrow

needs double-arrow

--- js/inner/pages/org_dashboard.coffee
+++ js/inner/pages/org_dashboard.coffee
@@ -8,5 +8,5 @@
     @title = @username
     @show_branch = true

-  refresh: () ->
+  refresh: () =>
     VM.loadOrg(@username, true)


Write changed settings to appStorage.

Write changed settings to appStorage.

--- client/ide/workspace/panes/settingspane.coffee
+++ client/ide/workspace/panes/settingspane.coffee
@@ -5,86 +5,98 @@
     super options, data

     @unsetClass 'kdview'
+
+    {name, version} = @getStorageInformation()
+    controller      = KD.getSingleton 'appStorageController'
+    @appStorage     = controller.storage name, version

     @createElements()
     @getSettings()

     @on 'SettingsFetched', @bound 'setSettings'
+    @on 'SettingsChanged', @bound 'handleSettingsChanged'

   getSettings: ->
-    {name, version}  = @getStorageInformation()
-    settingKeys      = @getSettingKeys()
-    appStorage       = KD.getSingleton('appStorageController').storage name, version
-
-    appStorage.fetchStorage =>
-      @settings      = {}
+    settingKeys = @getSettingKeys()
+
+    @appStorage.fetchStorage =>
+      @settings = {}

       for key in settingKeys
-        value = appStorage.getValue key
-        @settings[key] = value or @defaults[key]
+        value = @appStorage.getValue key
+        @settings[key] = value ? @defaults[key]

       @emit 'SettingsFetched'

   setSettings: ->
     @[key].setDefaultValue value  for own key, value of @settings

+  handleSettingsChanged: (key, state) ->
+    @appStorage.setValue key, state
+

 class EditorSettingsView extends IDESettingsView

   createElements: ->
     @useSoftTabs         = new KodingSwitch
       cssClass           : "tiny settings-on-off"
-      callback           : (state) -> console.log state
+      callback           : (state) => @emit 'SettingsChanged', 'useSoftTabs', state

     @showGutter          = new KodingSwitch
       cssClass           : "tiny settings-on-off"
-      callback           : (state) -> console.log state
+      callback           : (state) => @emit 'SettingsChanged', 'showGutter', state

     @useWordWrap         = new KodingSwitch
       cssClass           : "tiny settings-on-off"
-      callback           : (state) -> console.log state
+      callback           : (state) => @emit 'SettingsChanged', 'useWordWrap', state

     @showPrintMargin     = new KodingSwitch
       cssClass           : "tiny settings-on-off"
-      callback           : (state) -> console.log state
+      callback           : (state) => @emit 'SettingsChanged', 'showPrintMargin', state

     @highlightActiveLine = new KodingSwitch
       cssClass           : "tiny settings-on-off"
-      callback           : (state) -> console.log state
+      callback           : (state) => @emit 'SettingsChanged', 'highlightActiveLine', state

     @highlightWord       = new KodingSwitch
       cssClass           : "tiny settings-on-off"
-      callback           : (state) -> console.log state
+      callback           : (state) => @emit 'SettingsChanged', 'highlightWord', state

     @showInvisibles      = new KodingSwitch
       cssClass           : "tiny settings-on-off"
-      callback           : (state) -> console.log state
+      callback           : (state) => @emit 'SettingsChanged', 'showInvisibles', state

     @scrollPastEnd       = new KodingSwitch
       cssClass           : "tiny settings-on-off"
-      callback           : (state) -> console.log state
+      callback           : (state) => @emit 'SettingsChanged', 'scrollPastEnd', state

     @openRecentFiles     = new KodingSwitch
       cssClass           : "tiny settings-on-off"
-      callback           : (state) -> console.log state
+      callback           : (state) => @emit 'SettingsChanged', 'openRecentFiles', state

     @keyboardHandler     = new KDSelectBox
       selectOptions      : IDE.settings.editor.keyboardHandlers
+      callback           : (state) => @emit 'SettingsChanged', 'keyboardHandler', state

     @softWrap            = new KDSelectBox
       selectOptions      : IDE.settings.editor.softWrapOptions
+      callback           : (state) => @emit 'SettingsChanged', 'softWrap', state

     @syntax              = new KDSelectBox
       selectOptions      : IDE.settings.editor.getSyntaxOptions()
+      callback           : (state) => @emit 'SettingsChanged', 'syntax', state

     @fontSize            = new KDSelectBox
       selectOptions      : IDE.settings.editor.fontSizes
+      callback           : (state) => @emit 'SettingsChanged', 'fontSize', state

     @theme               = new KDSelectBox
       selectOptions      : IDE.settings.editor.themes
+      callback           : (state) => @emit 'SettingsChanged', 'theme', state

     @tabSize             = new KDSelectBox
       selectOptions      : IDE.settings.editor.tabSizes
+      callback           : (state) => @emit 'SettingsChanged', 'tabSize', state

   getStorageInformation: ->
     return { name: 'Ace', version: '1.0.1' }
@@ -131,11 +143,25 @@
 class TerminalSettingsView extends IDESettingsView

   createElements: ->
-    @font       = new KDSelectBox  selectOptions : IDE.settings.terminal.fonts
-    @fontSize   = new KDSelectBox  selectOptions : IDE.settings.terminal.fontSizes
-    @theme      = new KDSelectBox  selectOptions : IDE.settings.terminal.themes
-    @scrollback = new KDSelectBox  selectOptions : IDE.settings.terminal.scrollback
-    @visualBell = new KodingSwitch size: "tiny settings-on-off"
+    @font           = new KDSelectBox
+      selectOptions : IDE.settings.terminal.fonts
+      callback      : (state) => @emit 'SettingsChanged', 'font', state
+
+    @fontSize       = new KDSelectBox
+      selectOptions : IDE.settings.terminal.fontSizes
+      callback      : (state) => @emit 'SettingsChanged', 'fontSize', state
+
+    @theme          = new KDSelectBox
+      selectOptions : IDE.settings.terminal.themes
+      callback      : (state) => @emit 'SettingsChanged', 'theme', state
+
+    @scrollback     = new KDSelectBox
+      selectOptions : IDE.settings.terminal.scrollback
+      callback      : (state) => @emit 'SettingsChanged', 'scrollback', state
+
+    @visualBell     = new KodingSwitch
+      size          : "tiny settings-on-off"
+      callback      : (state) => @emit 'SettingsChanged', 'visualBell', state

   getStorageInformation: -> { name: 'Terminal', version: '1.0.1' }


file attribute and size from props

file attribute and size from props

--- client/lanes/components/shared/ImageSaver.cjsx
+++ client/lanes/components/shared/ImageSaver.cjsx
@@ -3,6 +3,9 @@
     propTypes:
         model: Lanes.PropTypes.Model
         name: React.PropTypes.string.isRequired
+        size: React.PropTypes.oneOf([
+            'thumb', 'medium', 'original'
+        ]).isRequired

     listenNetworkEvents: true
     bindDataEvents: ->
@@ -17,16 +20,27 @@
         @model.set("#{@props.name}_file", ev.target.files[0])

     renderImage: ->
-
+
+
+    blankImage: ->
+        null

     render: ->
-        value = if @model.hasImage(@props.name) then @renderImage() else null
-        className = _.classnames(@props.className, 'image-saver', {'with-image': @model.hasImage(@props.name)})
-        <LC.FieldWrapper {...@props} className={className} value={value}>
-            {value}
+        hasImage  = @model.hasImage(@props.name)
+        Component = if hasImage then @renderImage else @blankImage
+        className = _.classnames(@props.className, 'image-saver', {
+            'with-image': hasImage
+        })
+
+        <LC.FieldWrapper
+            {...@props}
+            className={className}
+            displayComponent={Component}
+        >
+            <Component />
             <form>

-                    {if value then 'Update' else 'Choose'}
+                    {if hasImage then 'Update' else 'Choose'}
                     <input id='file' className="file" type="file" onChange={@handleImageChange} />

             </form>


Don't use hardcoded index files

Don't use hardcoded index files

contentTree.index now returns first item in tree beginning with index

Fixes #28

--- src/content.coffee
+++ src/content.coffee
@@ -94,7 +94,9 @@
   Object.defineProperty @, 'filename',
     get: -> filename
   Object.defineProperty @, 'index',
-    get: -> @['index.md'] or @['index.markdown']
+    get: ->
+      for key, item of this
+        if key[0...5] is 'index' then return item

 ContentTree.fromDirectory = (directory, base, callback) ->
   if !callback?


set_icon

--- src/nvim/ui.coffee
+++ src/nvim/ui.coffee
@@ -4,6 +4,7 @@

 shell = require 'shell'
 EventEmitter = require('events').EventEmitter
+remote = require 'remote'
 config = require './config'

 # keyIdentifier -> vim key name
@@ -314,7 +315,10 @@
       @scroll_right - @scroll_left + 1, \
       clr_bottom - clr_top + 1

+  nv_set_icon: (icon) -> remote.getCurrentWindow().setRepresentedFilename? icon.toString()
+
   nv_set_title: (title) ->
+    title = title.toString()
     match = /(.*) - VIM$/.exec(title)
     document.title = if match? then match[1] + ' - Neovim.AS' else title


Changing j

Changing j

--- src/control.coffee
+++ src/control.coffee
@@ -1,8 +1,12 @@
 exports.control = (T, world) ->
+  me = world.get("ME")
   T.div {class: "controls"}, [
     T.div {class: "controls"}, [
       T.button {
-        init: -> @click => $(this).text('I been clicked.')
+        init: -> @click =>
+          console.log(me.get("j"))
+          me.put("j", me.get("j") - 1)
+          console.log(me.get("j"))
       }, "West"
       T.button "North"
       T.button "South"


Scroll to bottom if not viewing history

Scroll to bottom if not viewing history

--- assets/javascripts/views.js.coffee
+++ assets/javascripts/views.js.coffee
@@ -22,6 +22,46 @@
   layoutName: "settings"
   classNames: ["settings"]

+App.RoomMessagesView = Ember.View.extend
+  templateName: "room-messages"
+  classNames: ["messages-wrapper"]
+
+  latestMsgId: -1
+
+
+  scrollIfRequired: ->
+    messages = @get("controller.activeState.room.messages")
+    return if messages.length == 0
+
+    lastMsg = messages[messages.length - 1]
+    @set("latestMsgId", lastMsg.id)
+
+    viewableHeight   = @$().height()
+    coveredHeight    = if @$().scrollTop() < 1 then 1 else @$().scrollTop()
+    scrollableHeight = @$().find(".messages").height()
+
+    bottomHiddenContentSize = scrollableHeight - (coveredHeight + viewableHeight)
+    bottomHiddenContentSize = 0 if bottomHiddenContentSize < 0
+
+    # Assume that 16px == 1em. And if the scrolled lines are less than 4, then don't scroll
+    #TODO use 32 if devicePixelRatio is 2
+
+    if (bottomHiddenContentSize / 16) < 4
+      Ember.run.scheduleOnce "afterRender", @, =>
+        # $(".messages-wrapper").scrollTop( $(".messages-wrapper").prop("scrollHeight") )
+        @$().scrollTop(
+          @$().find(".messages").prop("scrollHeight") + 100
+        )
+
+
+  onMessagesChange: (->
+    @scrollIfRequired()
+  ).observes("controller.activeState.room.messages.@each")
+
+  onActiveStateChange: (->
+    @scrollIfRequired()
+  ).observes("controller.activeState")
+

 App.NewMessageView = Ember.View.extend
   templateName: "new-message"


Conditionally include deprecations in Package

--- src/package.coffee
+++ src/package.coffee
@@ -4,10 +4,9 @@
 async = require 'async'
 CSON = require 'season'
 fs = require 'fs-plus'
-EmitterMixin = require('emissary').Emitter
 {Emitter, CompositeDisposable} = require 'event-kit'
 Q = require 'q'
-{deprecate} = require 'grim'
+{includeDeprecations, deprecate} = require 'grim'

 ModuleCache = require './module-cache'
 ScopedProperties = require './scoped-properties'
@@ -21,8 +20,6 @@
 # stylesheets, keymaps, grammar, editor properties, and menus.
 module.exports =
 class Package
-  EmitterMixin.includeInto(this)
-
   @isBundledPackagePath: (packagePath) ->
     if atom.packages.devMode
       return false unless atom.packages.resourcePath.startsWith("#{process.resourcesPath}#{path.sep}")
@@ -43,11 +40,11 @@
     metadata ?= {}
     metadata.name = packageName

-    if metadata.stylesheetMain?
+    if includeDeprecations and metadata.stylesheetMain?
       deprecate("Use the `mainStyleSheet` key instead of `stylesheetMain` in the `package.json` of `#{packageName}`", {packageName})
       metadata.mainStyleSheet = metadata.stylesheetMain

-    if metadata.stylesheets?
+    if includeDeprecations and metadata.stylesheets?
       deprecate("Use the `styleSheets` key instead of `stylesheets` in the `package.json` of `#{packageName}`", {packageName})
       metadata.styleSheets = metadata.stylesheets

@@ -86,14 +83,6 @@
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidDeactivate: (callback) ->
     @emitter.on 'did-deactivate', callback
-
-  on: (eventName) ->
-    switch eventName
-      when 'deactivated'
-        deprecate 'Use Package::onDidDeactivate instead'
-      else
-        deprecate 'Package::on is deprecated. Use event subscription methods instead.'
-    EmitterMixin::on.apply(this, arguments)

   ###
   Section: Instance Methods
@@ -174,7 +163,7 @@
     if @mainModule?
       if @mainModule.config? and typeof @mainModule.config is 'object'
         atom.config.setSchema @name, {type: 'object', properties: @mainModule.config}
-      else if @mainModule.configDefaults? and typeof @mainModule.configDefaults is 'object'
+      else if includeDeprecations and @mainModule.configDefaults? and typeof @mainModule.configDefaults is 'object'
         deprecate """Use a config schema instead. See the configuration section
         of https://atom.io/docs/latest/hacking-atom-package-word-count and
         https://atom.io/docs/api/latest/Config for more details"""
@@ -268,7 +257,7 @@
       [stylesheetPath, atom.themes.loadStylesheet(stylesheetPath, true)]

   getStylesheetsPath: ->
-    if fs.isDirectorySync(path.join(@path, 'stylesheets'))
+    if includeDeprecations and fs.isDirectorySync(path.join(@path, 'stylesheets'))
       deprecate("Store package style sheets in the `styles/` directory instead of `stylesheets/` in the `#{@name}` package", packageName: @name)
       path.join(@path, 'stylesheets')
     else
@@ -339,7 +328,7 @@

     deferred = Q.defer()

-    if fs.isDirectorySync(path.join(@path, 'scoped-properties'))
+    if includeDeprecations and fs.isDirectorySync(path.join(@path, 'scoped-properties'))
       settingsDirPath = path.join(@path, 'scoped-properties')
       deprecate("Store package settings files in the `settings/` directory instead of `scoped-properties/`", packageName: @name)
     else
@@ -467,7 +456,7 @@
         else if _.isArray(commands)
           @activationCommands[selector].push(commands...)

-    if @metadata.activationEvents?
+    if includeDeprecations and @metadata.activationEvents?
       deprecate """
         Use `activationCommands` instead of `activationEvents` in your package.json
         Commands should be grouped by selector as follows:
@@ -585,3 +574,15 @@
       stack = error.stack ? error

     atom.notifications.addFatalError(message, {stack, detail, dismissable: true})
+
+if includeDeprecations
+  EmitterMixin = require('emissary').Emitter
+  EmitterMixin.includeInto(Package)
+
+  Package::on = (eventName) ->
+    switch eventName
+      when 'deactivated'
+        deprecate 'Use Package::onDidDeactivate instead'
+      else
+        deprecate 'Package::on is deprecated. Use event subscription methods instead.'
+    EmitterMixin::on.apply(this, arguments)


Only call emit when including deprecations

--- src/package.coffee
+++ src/package.coffee
@@ -356,7 +356,7 @@
         @mainModule?.deactivate?()
       catch e
         console.error "Error deactivating package '#{@name}'", e.stack
-    @emit 'deactivated'
+    @emit 'deactivated' if includeDeprecations
     @emitter.emit 'did-deactivate'

   deactivateConfig: ->


Add back size check when creating buffers

--- src/project.coffee
+++ src/project.coffee
@@ -189,6 +189,9 @@
   #
   # Returns a promise that resolves to the {TextBuffer}.
   buildBuffer: (absoluteFilePath) ->
+    if fs.getSizeSync(absoluteFilePath) >= 1048576 # 1MB
+      throw new Error("Atom can only handle files < 1MB, for now.")
+
     buffer = new TextBuffer({filePath: absoluteFilePath})
     @addBuffer(buffer)
     buffer.load()


:memo: Use TomDoc

:memo: Use TomDoc

--- src/publish.coffee
+++ src/publish.coffee
@@ -49,9 +49,9 @@

   # Create a new version and tag use the `npm version` command.
   #
-  #  * version: The new version or version increment.
-  #  * callback: The callback function to invoke with an error as the first
-  #    argument and a the generated tag string as the second argument.
+  # version  - The new version or version increment.
+  # callback - The callback function to invoke with an error as the first
+  #            argument and a the generated tag string as the second argument.
   versionPackage: (version, callback) ->
     process.stdout.write 'Preparing and tagging a new version '
     versionArgs = ['version', version, '-m', 'Prepare %s release']
@@ -65,9 +65,9 @@

   # Push a tag to the remote repository.
   #
-  #  * tag: The tag to push.
-  #  * callback: The callback function to invoke with an error as the first
-  #    argument.
+  #  tag - The tag to push.
+  #  callback - The callback function to invoke with an error as the first
+  #             argument.
   pushVersion: (tag, callback) ->
     process.stdout.write "Pushing #{tag} tag "
     pushArgs = ['push', 'origin', 'HEAD', tag]
@@ -81,9 +81,9 @@

   # Does the given package already exist in the registry?
   #
-  #  * packageName: The string package name to check.
-  #  * callback: The callback function invoke with an error as the first
-  #    argument and true/false as the second argument.
+  # packageName - The string package name to check.
+  # callback    - The callback function invoke with an error as the first
+  #               argument and true/false as the second argument.
   packageExists: (packageName, callback) ->
     @getToken (error, token) ->
       if error?
@@ -114,8 +114,8 @@

   # Register the current repository with the package registry.
   #
-  #  * pack: The package metadata.
-  #  * callback: The callback function.
+  # pack - The package metadata.
+  # callback - The callback function.
   registerPackage: (pack, callback) ->
     unless pack.name
       callback('Required name field in package.json not found')
@@ -158,10 +158,10 @@

   # Create a new package version at the given Git tag.
   #
-  #  * packageName: The string name of the package.
-  #  * tag: The string Git tag of the new version.
-  #  * callback: The callback function to invoke with an error as the first
-  #    argument.
+  # packageName - The string name of the package.
+  # tag - The string Git tag of the new version.
+  # callback - The callback function to invoke with an error as the first
+  #            argument.
   createPackageVersion: (packageName, tag, callback) ->
     @getToken (error, token) ->
       if error?
@@ -188,10 +188,10 @@

   # Publish the version of the package associated with the given tag.
   #
-  #  * pack: The package metadata.
-  #  * tag: The Git tag string of the package version to publish.
-  #  * callback: The callback function to invoke when done with an error as the
-  #    first argument.
+  # pack - The package metadata.
+  # tag - The Git tag string of the package version to publish.
+  # callback - The callback function to invoke when done with an error as the
+  #            first argument.
   publishPackage: (pack, tag, callback) ->
     process.stdout.write "Publishing #{pack.name}@#{tag} "
     @createPackageVersion pack.name, tag, (error) ->


domain: manually apply patch from master

This file was renamed, so it was easier to bring this change over than to merge

--- client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreateform.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreateform.coffee
@@ -166,9 +166,10 @@
       warn "Failed to update domains:", err  if err
       domainList = []

-      for domain in userDomains
-        if not domain.regYears > 0
-          domainList.push {title:".#{domain.domain}", value:domain.domain}
+      if userDomains
+        for domain in userDomains
+          if not domain.regYears > 0
+            domainList.push {title:".#{domain.domain}", value:domain.domain}

       {domains, domainName} = @subDomainEntryForm.inputs


[Profile] changed wording

[Profile] changed wording

--- client/landing/app/Applications/Members.kdapplication/ContentDisplays/externalprofileview.coffee
+++ client/landing/app/Applications/Members.kdapplication/ContentDisplays/externalprofileview.coffee
@@ -20,7 +20,7 @@

     super

-    @setTooltip title : "Click to bind your #{@getOption 'nicename'} account"
+    @setTooltip title : "Click to link your #{@getOption 'nicename'} account"
     @setLinkedState()

   setLinkedState:->


Add new line

Add new line

--- lib/helpers/metadata_builder.coffee
+++ lib/helpers/metadata_builder.coffee
@@ -57,4 +57,4 @@
     NS.Helpers.MetadataBuilder = MetadataBuilder
   else
     module.exports = MetadataBuilder
-)(if typeof module is 'undefined' and typeof window isnt 'undefined' then window.Fmk else module.exports);+)(if typeof module is 'undefined' and typeof window isnt 'undefined' then window.Fmk else module.exports)


request: conditionally add the @params parameter

in certain cases, adding a @params parameter with the value
N'' will cause sp_execsql to fail (eg: a CREATE FUNCTION statement).
If there are no parameters in the request, don't bother adding the
parameter (which causes the statement to succeed).

--- src/request.coffee
+++ src/request.coffee
@@ -55,7 +55,8 @@
     @parameters = []

     @addParameter('statement', TYPES.NVarChar, @sqlTextOrProcedure)
-    @addParameter('params', TYPES.NVarChar, @makeParamsParameter(@originalParameters))
+    if (@originalParameters.length)
+      @addParameter('params', TYPES.NVarChar, @makeParamsParameter(@originalParameters))

     for parameter in @originalParameters
       @parameters.push(parameter)
@@ -67,7 +68,8 @@
     @parameters = []

     @addOutputParameter('handle', TYPES.Int)
-    @addParameter('params', TYPES.NVarChar, @makeParamsParameter(@originalParameters))
+    if (@originalParameters.length)
+      @addParameter('params', TYPES.NVarChar, @makeParamsParameter(@originalParameters))
     @addParameter('stmt', TYPES.NVarChar, @sqlTextOrProcedure)

     @sqlTextOrProcedure = 'sp_prepare'


i want to reload vendor too

i want to reload vendor too

--- src/require.coffee
+++ src/require.coffee
@@ -8,6 +8,7 @@
   "/Users/corey/code/mine/Atomicity/src"
   "/Users/corey/code/mine/Atomicity/plugins"
   "/Users/corey/code/mine/Atomicity/test"
+  "/Users/corey/code/mine/Atomicity/vendor"

   "#{resourcePath}/src"
   "#{resourcePath}/plugins"


fix(service): fix edge case when deconstructor invoked multiple times

--- src/service.coffee
+++ src/service.coffee
@@ -280,7 +280,9 @@
           unregisterFn = util.registerHandler(address, callback)
         ### and return the deregister callback ###
         deconstructor = ->
-          unregisterFn() if unregisterFn
+          if unregisterFn
+            unregisterFn()
+            unregisterFn = undefined
           # Remove from internal map
           if wrapped.handlers[address]
             index = wrapped.handlers[address].indexOf(callback)


Fix initial window size

Fix initial window size

--- src/initial.coffee
+++ src/initial.coffee
@@ -15,7 +15,7 @@
 # Start of logic for initial start
 mainWindow = new BrowserWindow {
   width: 500
-  height: 300
+  height: 350
   show: false
   resizable: false
   title: "Setup LP-Tracker"


Use `error.coffee` module in `tael.coffee`

--- src/js/tael.coffee
+++ src/js/tael.coffee
@@ -1,6 +1,7 @@
 # tael.js
 # Copyright (c) 2015 Carter Hinsley
 # MIT License
+error = require './error.coffee'

 tiles = [
     type: 'container'
@@ -27,7 +28,7 @@
                     split: 'horizontal'
                     divider_location: 1
         when 'branch'
-            throw "Branch tiles cannot spawn new children post-creation."
+            error.throw "Branch tiles cannot spawn new children post-creation."

 newTile(tiles[0])


Fixes incorrect reference

Fixes incorrect reference

--- src/udefine.coffee
+++ src/udefine.coffee
@@ -33,6 +33,7 @@
       result = define.apply @, arguments if define.amd or define.umd
     else
       if hasModule
+        path = require 'path'
         requireArr = []

         for dep in deps
@@ -74,13 +75,14 @@
     return unless obj? and name?
     obj[name] = res

-  root.udefine.modules = {}
+  root.udefine.inject.modules = {}

   root.udefine.inject.add = (name) ->
     root.udefine.inject.modules[name] = undefined

   root.udefine.inject.reset = -> root.udefine.inject.modules = {}

+  # TODO: Reflect if these two object could and should be merged together
   # Dependencies for browser (global object)
   root.udefine.globals or= {}


Fix typo in dom#on method

Fix typo in dom#on method

--- src/lib/dom.coffee
+++ src/lib/dom.coffee
@@ -124,11 +124,11 @@
   on: (eventName, listener) ->
     @node.addEventListener(eventName, (event) ->
       arg = if lastKeyEvent and (eventName == 'keydown' or eventName == 'keyup') then lastKeyEvent else event
-      propogate = listener(arg)
-      unless propogate
+      propagate = listener(arg)
+      unless propagate
         event.preventDefault()
         event.stopPropagation()
-      return propogate
+      return propagate
     )
     return this


First pass at projectile offsets instead of hardcoded values. Still not perfect

--- src/weapons.coffee
+++ src/weapons.coffee
@@ -35,7 +35,8 @@

 class Kona.EnemyWeapon extends Kona.Weapon
   constructor: (opts={}) ->
-    @targets = opts.targets
+    @targets    = opts.targets
+    @projOffset = opts.offset || { x: 0, y: 0 }
     super(opts)

   # Return a random entity from the list of target groups
@@ -62,8 +63,8 @@
       projDx = speed * Math.cos(angle) * (if targetLeft then -1 else 1)
       projDy = speed * Math.sin(angle) * (if targetUp then -1 else 1)

-      startX = if targetLeft then @holder.left() - 20 else @holder.right() + 20 # TODO: REMOVE HARDCODING
-      startY = @holder.top() + 25
+      startX = if targetLeft then @holder.left()-@projOffset.x else @holder.right()+@projOffset.x # TODO: REMOVE HARDCODING
+      startY = @holder.top() + @projOffset.y

       proj   = new @projType { group: 'projectiles', x: startX, y: startY, dx: projDx, dy: projDy, target: target }
       Kona.Scenes.currentScene.addEntity(proj)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -7,6 +7,7 @@
   health: 100
   maxHealth: 100

+enem

 player.position()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

 player = GameObject
-
+  health: 10

 player.position()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -10,7 +10,7 @@
 enemy = GameObject
   health: 500

-player.meter()
+player.meter 'heal'
 # => Uncaught TypeError: Object has no method 'position'

 player.include(Bounded)


fixes for raw bit conversion

fixes for raw bit conversion

--- src/methods.coffee
+++ src/methods.coffee
@@ -271,18 +271,21 @@
       ],
       Float: [
         o 'floatToRawIntBits(F)I', (rs, f_val) ->  #note: not tested for weird values
+            return 0 if f_val is 0
             sign = if f_val < 0 then 1 else 0
             f_val = Math.abs(f_val)
             exp = Math.floor(Math.log(f_val)/Math.LN2)
-            sig = (f_val/Math.pow(2,exp)-1)/Math.pow(2,-23)
+            sig = (f_val/Math.pow(2,exp)-1)*Math.pow(2,23)
             (sign<<31)+((exp+127)<<23)+sig
       ]
       Double: [
-        o 'doubleToRawLongBits(D)J', (rs, d_val) ->#note: not tested at all
+        o 'doubleToRawLongBits(D)J', (rs, d_val) ->
+            return gLong.fromInt(0) if d_val is 0 or isNaN(d_val) or not isFinite(d_val)
             sign = gLong.fromInt(if d_val < 0 then 1 else 0)
             d_val = Math.abs(d_val)
-            exp = gLong.fromNumber(Math.floor(Math.log(d_val)/Math.LN2)+1023)
-            sig = gLong.fromNumber((d_val/Math.pow(2,exp)-1)/Math.pow(2,-52))
+            exp = gLong.fromNumber(Math.floor(Math.log(d_val)/Math.LN2))
+            sig = gLong.fromNumber((d_val/Math.pow(2,exp.toInt())-1)*Math.pow(2,52))
+            exp = exp.add(gLong.fromInt(1023))
             sign.shiftLeft(63).add(exp.shiftLeft(52)).add(sig)
         o 'longBitsToDouble(J)D', (rs, l_val) ->
             s = if l_val.shiftRight(63).equals(gLong.fromInt(0)) then 1 else -1
@@ -515,6 +518,10 @@
         o 'initialize()V', (rs) ->  # NOP???
       ]
       Unsafe: [
+        o 'compareAndSwapInt(Ljava/lang/Object;JII)Z', (rs, _this, obj, offset, expected, x) ->
+            field_name = rs.class_lookup(obj.type).fields[offset.toInt()]
+            obj.fields[field_name] = x.ref
+            true
         o 'compareAndSwapLong(Ljava/lang/Object;JJJ)Z', (rs, _this, obj, offset, expected, x) ->
             field_name = rs.class_lookup(obj.type).fields[offset.toInt()]
             obj.fields[field_name] = x.ref


Removed native methods that... weren't native methods. Whoops.

--- src/natives.coffee
+++ src/natives.coffee
@@ -279,32 +279,14 @@
         o 'halt0(I)V', (rs) -> throw new util.HaltException(rs.curr_frame().locals[0])
       ]
       StrictMath: [
-        o 'abs(D)D', (rs, d_val) -> Math.abs(d_val)
-        o 'abs(F)F', (rs, f_val) -> Math.abs(f_val)
-        o 'abs(I)I', (rs, i_val) -> if i_val == util.INT_MIN then util.INT_MIN else Math.abs(i_val)
-        o 'abs(L)L', (rs, l_val) -> if l_val.isNegative then l_val.negate else l_val
         o 'acos(D)D', (rs, d_val) -> Math.acos(d_val)
         o 'asin(D)D', (rs, d_val) -> Math.asin(d_val)
         o 'atan(D)D', (rs, d_val) -> Math.atan(d_val)
         o 'atan2(DD)D', (rs, y, x) -> Math.atan2(y, x)
-        o 'ceil(D)D', (rs, d_val) -> Math.ceil(d_val)
         o 'cos(D)D', (rs, d_val) -> Math.cos(d_val)
         o 'exp(D)D', (rs, d_val) -> Math.exp(d_val)
-        o 'floor(D)D', (rs, d_val) -> Math.floor(d_val)
         o 'log(D)D', (rs, d_val) -> Math.log(d_val)
-        o 'max(DD)D', (rs, a, b) -> Math.max(a, b)
-        o 'max(FF)F', (rs, a, b) -> Math.max(a, b)
-        o 'max(II)I', (rs, a, b) -> Math.max(a, b)
-        o 'max(LL)L', (rs, a, b) -> if a.greaterThan(b) then a else b
-        o 'min(DD)D', (rs, a, b) -> Math.min(a, b)
-        o 'min(FF)F', (rs, a, b) -> Math.min(a, b)
-        o 'min(II)I', (rs, a, b) -> Math.min(a, b)
-        o 'min(LL)L', (rs, a, b) -> if a.lessThan(b) then a else b
         o 'pow(DD)D', (rs) -> Math.pow(rs.cl(0),rs.cl(2))
-        o 'random()D', (rs) -> Math.random()
-        o 'rint(D)D', (rs, d_val) -> Math.round(d_val)
-        o 'round(D)L', (rs, d_val) -> gLong.fromNumber(Math.round(d_val))
-        o 'round(F)I', (rs, f_val) -> util.wrap_int Math.round(f_val)
         o 'sin(D)D', (rs, d_val) -> Math.sin(d_val)
         o 'sqrt(D)D', (rs, d_val) -> Math.sqrt(d_val)
         o 'tan(D)D', (rs, d_val) -> Math.tan(d_val)


adding a note

adding a note

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -593,7 +593,7 @@
   131: new root.Opcode 'lxor', { execute: (rs) -> rs.push2(rs.pop2().xor(rs.pop2()), null) }
   132: new root.IIncOpcode 'iinc'
   133: new root.Opcode 'i2l', { execute: (rs) -> rs.push2 gLong.fromInt(rs.pop()), null }
-  134: new root.Opcode 'i2f', { execute: (rs) -> }
+  134: new root.Opcode 'i2f', { execute: (rs) -> }  # Intentional no-op: ints and floats have the same representation
   135: new root.Opcode 'i2d', { execute: (rs) -> rs.push null }
   136: new root.Opcode 'l2i', { execute: (rs) -> rs.push rs.pop2().toInt() }
   137: new root.Opcode 'l2f', { execute: (rs) -> rs.push rs.pop2().toNumber() }


tilemap update

tilemap update

--- src/tilemap.coffee
+++ src/tilemap.coffee
@@ -1,4 +1,4 @@
-(() ->
+( ->
   Map = (data, entityCallback) ->
     tileHeight = data.tileHeight
     tileWidth = data.tileWidth
@@ -14,7 +14,7 @@
       data.layers.each (layer, layerIndex) ->
         return unless layer.name.match /entities/i

-        layer.tiles.each (row, y) ->
+        layer.tiles?.each (row, y) ->
           row.each (tileIndex, x) ->
             if spriteLookup[tileIndex]
               entityData = $.extend(
@@ -26,6 +26,19 @@
               , data.tileset[tileIndex]?.properties)

               entityCallback(entityData)
+
+        if entities = layer.entities
+          for entity in entities
+            {tileIndex} = entity
+            entityData = $.extend(
+              layer: layerIndex
+              sprite: spriteLookup[tileIndex]
+              x: entity.x
+              y: entity.y
+            , data.tileset[tileIndex]?.properties
+            , entity.properties)
+
+            entityCallback(entityData)

     loadEntities()

@@ -57,7 +70,8 @@
     return proxy

   loadByName = (name, callback, entityCallback) ->
-    url = "#{BASE_URL}/data/#{name}.tilemap"
+    #TODO: Better cachebusting
+    url = "#{BASE_URL}/data/#{name}.tilemap?#{new Date().getTime()}"

     proxy =
       draw: $.noop


Backwards compability

Backwards compability

--- src/udefine.coffee
+++ src/udefine.coffee
@@ -176,7 +176,7 @@
       context[e] = (platformDef) =>
         if udefine.env[e] then platformDef.call @

-    configFunc.apply context, [udefine, root]
+    configFunc.apply context, [root, udefine]

   # Export udefine function on CommonJS environments
   if hasModule then module.exports = udefine else root.udefine = udefine


Cleanup

Cleanup

--- src/xmpp/ns.coffee
+++ src/xmpp/ns.coffee
@@ -1,7 +1,5 @@
 module.exports =
     PUBSUB: "http://jabber.org/protocol/pubsub"
-    PUBSUB_CREATE_AND_CONFIGURE: "http://jabber.org/protocol/pubsub#create-and-configure"
-    PUBSUB_CREATE_NODES: "http://jabber.org/protocol/pubsub#create-nodes"
     PUBSUB_EVENT: "http://jabber.org/protocol/pubsub#event"
     PUBSUB_OWNER: "http://jabber.org/protocol/pubsub#owner"
     PUBSUB_NODE_CONFIG: "http://jabber.org/protocol/pubsub#node_config"


handle attachment upload with multiparty

--- server/controllers/attachments.coffee
+++ server/controllers/attachments.coffee
@@ -1,35 +1,67 @@
 fs = require "fs"
 db = require('../helpers/db_connect_helper').db_connect()
 deleteFiles = require('../helpers/utils').deleteFiles
+multiparty = require 'multiparty'
+
+

 ## Actions

 # POST /data/:id/attachments/
 module.exports.add = (req, res, next) ->
-    if req.files["file"]?
-        file = req.files["file"]
-        if req.body.name? then name = req.body.name else name = file.name
+    files = {}

-        fileData =
-            name: name
-            "content-type": file.type
+    # Parse given form to extract image blobs.
+    form = new multiparty.Form
+        uploadDir: __dirname + '../../uploads'
+        keepExtensions: true
+        maxFilesSize: 10 * 1024 * 1024 * 1024
+    form.parse req

-        stream = db.saveAttachment req.doc, fileData, (err) ->
-            if err
-                console.log "[Attachment] err: " + JSON.stringify err
-                deleteFiles req.files
-                next new Error err.error
-            else
-                deleteFiles req.files
-                res.send 201, success: true
-                next()
+    # Get fields from form.
+    form.on 'field', (name, value) ->
+        req.body[name] = value
+        cid = value if name is 'cid'

-        fs.createReadStream(file.path).pipe stream
+    # Get files from form.
+    form.on 'file', (name, val) ->
+        val.name = val.originalFilename
+        val.type = val.headers['content-type'] or null
+        files[name] = val

-    else
-        err = new Error "No file sent"
-        err.status = 400
+    form.on 'progress', (bytesReceived, bytesExpected) ->
+        # TODO handle progress
+
+    form.on 'error', (err) ->
         next err
+
+    # When form is fully parsed, data are saved into CouchDB.
+    form.on 'close', ->
+        if files.file?
+            file = files.file
+            if req.body.name? then name = req.body.name else name = file.name
+
+            fileData =
+                name: name
+                "content-type": file.type
+
+            stream = db.saveAttachment req.doc, fileData, (err) ->
+                if err
+                    console.log "[Attachment] err: " + JSON.stringify err
+                    deleteFiles req.files
+                    next new Error err.error
+                else
+                    deleteFiles req.files
+                    res.send 201, success: true
+                    next()
+
+            fs.createReadStream(file.path).pipe stream
+
+        else
+            err = new Error "No file sent"
+            err.status = 400
+            next err
+

 # GET /data/:id/attachments/:name
 module.exports.get = (req, res, next) ->
@@ -42,15 +74,14 @@
             next err
         else if err
             next new Error err.error
-        else
-            res.send 200, success: true

     if req.headers['range']?
         stream.setHeader 'range', req.headers['range']

+    res.on 'close', -> stream.abort()
+
     stream.pipe res

-    res.on 'close', -> stream.abort()

 # DELETE /data/:id/attachments/:name