Cleaned up the workspace module

Cleaned up the workspace module

--- lib/workspace.coffee
+++ lib/workspace.coffee
@@ -1,149 +1,130 @@
-{CompositeDisposable,Emitter} = require('atom')
-
-# ------------------------------------------------------------------------------
+{CompositeDisposable, Emitter} = require 'atom'

 class Workspace
-
-  ## Construction --------------------------------------------------------------
-
   constructor: ->
     @emitter = new Emitter
     @levelCodeEditorsById = {}
     @activeLevelCodeEditor = null
-    @activeLevelCodeEditorSubscrs = new CompositeDisposable
-
-  ## Event subscription --------------------------------------------------------
+    @activeLevelCodeEditorSubscriptions = new CompositeDisposable

   onDidEnterWorkspace: (callback) ->
-    @emitter.on('did-enter-workspace',callback)
+    @emitter.on 'did-enter-workspace', callback

   onDidExitWorkspace: (callback) ->
-    @emitter.on('did-exit-workspace',callback)
+    @emitter.on 'did-exit-workspace', callback

   observeLevelCodeEditors: (callback) ->
-    callback(levelCodeEditor) for levelCodeEditor in @getLevelCodeEditors()
-    @onDidAddLevelCodeEditor(callback)
+    for levelCodeEditor in @getLevelCodeEditors()
+      callback levelCodeEditor
+    @onDidAddLevelCodeEditor callback

   onDidAddLevelCodeEditor: (callback) ->
-    @emitter.on('did-add-level-code-editor',callback)
+    @emitter.on 'did-add-level-code-editor', callback

   onDidDestroyLevelCodeEditor: (callback) ->
-    @emitter.on('did-destroy-level-code-editor',callback)
+    @emitter.on 'did-destroy-level-code-editor', callback

   onDidChangeActiveLevelCodeEditor: (callback) ->
-    @emitter.on('did-change-active-level-code-editor',callback)
+    @emitter.on 'did-change-active-level-code-editor', callback

   onDidChangeActiveLanguage: (callback) ->
-    @emitter.on('did-change-active-language',callback)
+    @emitter.on 'did-change-active-language', callback

   onDidChangeActiveLevel: (callback) ->
-    @emitter.on('did-change-active-level',callback)
+    @emitter.on 'did-change-active-level', callback

   onDidChangeActiveTerminal: (callback) ->
-    @emitter.on('did-change-active-terminal',callback)
-
-  ## Workspace properties ------------------------------------------------------
+    @emitter.on 'did-change-active-terminal', callback

   isActive: ->
-    @activeLevelCodeEditor?
-
-  ## Managing level code editors -----------------------------------------------
+    return @activeLevelCodeEditor?

   addLevelCodeEditor: (levelCodeEditor) ->
     @levelCodeEditorsById[levelCodeEditor.getId()] = levelCodeEditor
-    @emitter.emit('did-add-level-code-editor',levelCodeEditor)
+    @emitter.emit 'did-add-level-code-editor', levelCodeEditor
+    return

   destroyLevelCodeEditor: (levelCodeEditor) ->
     delete @levelCodeEditorsById[levelCodeEditor.getId()]
     levelCodeEditor.destroy()
-    @emitter.emit('did-destroy-level-code-editor',levelCodeEditor)
+    @emitter.emit 'did-destroy-level-code-editor', levelCodeEditor
+    return

   getLevelCodeEditorForId: (levelCodeEditorId) ->
-    @levelCodeEditorsById[levelCodeEditorId]
+    return @levelCodeEditorsById[levelCodeEditorId]

   getLevelCodeEditorForTextEditor: (textEditor) ->
-    @levelCodeEditorsById[textEditor.id]
+    return @levelCodeEditorsById[textEditor.id]

   getLevelCodeEditors: ->
-    levelCodeEditor for _,levelCodeEditor of @levelCodeEditorsById
+    levelCodeEditor for _, levelCodeEditor of @levelCodeEditorsById

   isLevelCodeEditor: (textEditor) ->
-    @getLevelCodeEditorForTextEditor(textEditor)?
-
-  ## Managing the active level code editor -------------------------------------
+    return @getLevelCodeEditorForTextEditor(textEditor)?

   getActiveLevelCodeEditor: ->
-    @activeLevelCodeEditor
+    return @activeLevelCodeEditor

   getActiveLanguage: ->
-    @activeLevelCodeEditor?.getLanguage()
+    return @activeLevelCodeEditor?.getLanguage()

   getActiveLevel: ->
-    @activeLevelCodeEditor?.getLevel()
+    return @activeLevelCodeEditor?.getLevel()

   getActiveTerminal: ->
-    @activeLevelCodeEditor?.getTerminal()
+    return @activeLevelCodeEditor?.getTerminal()

   setActiveLevelCodeEditor: (levelCodeEditor) ->
     if @isActive()
-      unless levelCodeEditor.getId() is @activeLevelCodeEditor.getId()
+      if levelCodeEditor.getId() != @activeLevelCodeEditor.getId()
         newLanguage = levelCodeEditor.getLanguage()
         oldLanguage = @activeLevelCodeEditor.getLanguage()
         newLevel = levelCodeEditor.getLevel()
         oldLevel = @activeLevelCodeEditor.getLevel()
-        # NOTE currently a level code editor change is also a terminal change
-        # but in the future it might be possible to alternatively share one
-        # terminal with all level code editors
         newTerminal = levelCodeEditor.getTerminal()
         oldTerminal = @activeLevelCodeEditor.getTerminal()
-        # -------------------------------------------------------------------
+
         @unsubscribeFromActiveLevelCodeEditor()
         @activeLevelCodeEditor = levelCodeEditor
-        if newLanguage.getName() isnt oldLanguage.getName()
-          @emitter.emit('did-change-active-level',newLevel)
+        if newLanguage.getName() != oldLanguage.getName()
+          @emitter.emit 'did-change-active-level', newLevel
           @emitter.emit 'did-change-active-language',
             activeLanguage: newLanguage
             activeLevel: newLevel
-        else if newLevel.getName() isnt oldLevel.getName()
-          @emitter.emit('did-change-active-level',newLevel)
+        else if newLevel.getName() != oldLevel.getName()
+          @emitter.emit 'did-change-active-level', newLevel
         @subscribeToActiveLevelCodeEditor()
-        # NOTE see above
-        @emitter.emit('did-change-active-terminal',newTerminal)
-        # --------------
-        @emitter.emit('did-change-active-level-code-editor',\
-        @activeLevelCodeEditor)
+        @emitter.emit 'did-change-active-terminal', newTerminal
+        @emitter.emit 'did-change-active-level-code-editor', @activeLevelCodeEditor
     else
       @activeLevelCodeEditor = levelCodeEditor
       @subscribeToActiveLevelCodeEditor()
-      @emitter.emit('did-enter-workspace',@activeLevelCodeEditor)
+      @emitter.emit 'did-enter-workspace', @activeLevelCodeEditor
+    return

   unsetActiveLevelCodeEditor: ->
     if @isActive()
       @unsubscribeFromActiveLevelCodeEditor()
       @activeLevelCodeEditor = null
-      @emitter.emit('did-exit-workspace')
+      @emitter.emit 'did-exit-workspace'
+    return

   subscribeToActiveLevelCodeEditor: ->
-    @activeLevelCodeEditorSubscrs.add \
-      @activeLevelCodeEditor.onDidChangeLanguage ({language,level}) =>
-        @emitter.emit 'did-change-active-language',
-          activeLanguage: language
-          activeLevel: level
-    @activeLevelCodeEditorSubscrs.add \
-      @activeLevelCodeEditor.onDidChangeLevel (level) =>
-        @emitter.emit('did-change-active-level',level)
+    @activeLevelCodeEditorSubscriptions.add @activeLevelCodeEditor.onDidChangeLanguage ({language, level}) =>
+      @emitter.emit 'did-change-active-language',
+        activeLanguage: language
+        activeLevel: level
+    @activeLevelCodeEditorSubscriptions.add @activeLevelCodeEditor.onDidChangeLevel (level) =>
+      @emitter.emit 'did-change-active-level', level
+    return

   unsubscribeFromActiveLevelCodeEditor: ->
-    @activeLevelCodeEditorSubscrs.dispose()
-
-# ------------------------------------------------------------------------------
+    @activeLevelCodeEditorSubscriptions.dispose()
+    return

 module.exports =
 class WorkspaceProvider
-
   instance = null

   @getInstance: ->
-    instance ?= new Workspace
-
-# ------------------------------------------------------------------------------
+    instance ?= new Workspace


restore combined pedestrian/bike, add bike/moped restriction

--- dev/europe.cson
+++ dev/europe.cson
@@ -575,13 +575,15 @@
     { type: 'circle-o', color: 'red' }
     { type: 'pedestrian', color: 'black', transform: '{fit_border_circle}' }
   ]
-prohibitory_pedestrians:
-  category: 'prohibitory'
-  name: 'pedestrians prohibited'
-  elements: [
-    { type: 'circle-bg', color: 'white' }
-    { type: 'circle-o', color: 'red' }
-    { type: 'pedestrian', color: 'black', transform: '{fit_border_circle}' }
+
+prohibitory_pedestrians_and_bicycles:
+  category: 'prohibitory'
+  name: 'pedestrians and bicycles prohibited'
+  elements: [
+    { type: 'circle-bg', color: 'white' }
+    { type: 'circle-o', color: 'red' }
+    { type: 'footpath', color: 'black', transform: '{fit_border_circle} {top_half}' }
+    { type: 'bicycle', color: 'black', transform: '{fit_border_circle} {bottom_half}' }
   ]

 prohibitory_bicycles:
@@ -591,6 +593,16 @@
     { type: 'circle-bg', color: 'white' }
     { type: 'circle-o', color: 'red' }
     { type: 'bicycle', color: 'black', transform: '{fit_border_circle}' }
+  ]
+
+prohibitory_mopeds_and_bicycles:
+  category: 'prohibitory'
+  name: 'mopeds and bicycles prohibited'
+  elements: [
+    { type: 'circle-bg', color: 'white' }
+    { type: 'circle-o', color: 'red' }
+    { type: 'moped', color: 'black', transform: '{fit_border_circle} {top_half} scale(.7)' }
+    { type: 'bicycle', color: 'black', transform: '{fit_border_circle} {bottom_half}' }
   ]

 prohibitory_moped:


Correct country_id function call inside checkout.js.coffee

--- frontend/app/assets/javascripts/store/checkout.js.coffee
+++ frontend/app/assets/javascripts/store/checkout.js.coffee
@@ -8,19 +8,20 @@
   if ($ '#checkout_form_address').is('*')
     ($ '#checkout_form_address').validate()

-    country_id = ->
+    country_id = (region) ->
       $('p#' + region + 'country select').val()

     update_state = (region) ->
-      if country_id != undefined
-        if Spree.checkout[country_id] == undefined
-          $.get Spree.routes.states_search + "/?country_id=#{country_id}", (data) ->
-            Spree.checkout[country_id] = {}
-            Spree.checkout[country_id]['states'] = data.states
-            Spree.checkout[country_id]['states_required'] = data.states_required
-            fill_in_states(Spree.checkout[country_id], region)
+      c_id = country_id(region)
+      if c_id != undefined
+        if Spree.checkout[c_id] == undefined
+          $.get Spree.routes.states_search + "/?country_id=#{c_id}", (data) ->
+            Spree.checkout[c_id] = {}
+            Spree.checkout[c_id]['states'] = data.states
+            Spree.checkout[c_id]['states_required'] = data.states_required
+            fill_in_states(Spree.checkout[c_id], region)
         else
-          fill_in_states(Spree.checkout[country_id], region)
+          fill_in_states(Spree.checkout[c_id], region)

     fill_in_states = (data, region) ->
       states_required = data.states_required
@@ -69,10 +70,7 @@
       else
         ($ '#shipping .inner').show()
         ($ '#shipping .inner input, #shipping .inner select').prop 'disabled', false
-        if Spree.checkout[country_id].states.length == 0
-          ($ 'span#sstate input').hide().prop 'disabled', true
-        else
-          ($ 'span#sstate select').hide().prop 'disabled', true
+        update_state('s')
     ).triggerHandler 'click'

   if ($ '#checkout_form_payment').is('*')


linter name

linter name

--- lib/omnisharp-atom/linter.coffee
+++ lib/omnisharp-atom/linter.coffee
@@ -55,7 +55,8 @@
                 line: line,
                 col: column,
                 level: error.LogLevel.toLowerCase(),
-                range: new Rng([line, start], [line, end])
+                range: new Rng([line, start], [line, end]),
+                linter: "CSharp"
             }


Fix name of okular viewer

Fix name of okular viewer

--- lib/viewers/okular-viewer.coffee
+++ lib/viewers/okular-viewer.coffee
@@ -1,7 +1,7 @@
 BaseViewer = require './base-viewer'

 module.exports =
-class SumatraViewer extends BaseViewer
+class OkularViewer extends BaseViewer
   _getArgs = (opts = {}) ->
     args = ["--unique"]
     args.push "--noraise" if opts?.keepFocus


payment: remove unused method from virtualization controller

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -374,22 +374,6 @@
     workflow.on 'Cancel', -> modal.destroy()

     workflow.enter()
-
-  showPaymentMethodForm: (modal) ->
-
-    paymentForm1 = modal.modalTabs.forms['Payment method']
-    paymentForm2 = modal.modalTabs.forms['Payment method 2']
-
-    unless modal.paymentForm
-      modal.paymentForm = new PaymentForm
-
-      paymentController = KD.getSingleton 'paymentController'
-      paymentController.observePaymentSave modal.paymentForm, (err, paymentMethod) ->
-        paymentForm1.addCustomData paymentMethodId: paymentMethod.paymentMethodId
-
-      paymentForm2.addSubView modal.paymentForm
-
-    modal.modalTabs.showNextPane()

   askForApprove:(command, callback)->


Changed semantics of hemlock command.

Changed semantics of hemlock command.

--- lib/hemlock.coffee
+++ lib/hemlock.coffee
@@ -8,10 +8,6 @@
 wrench   = require 'wrench'

 Hem      = require 'hem'
-
-help = ->
-  optimist.showHelp()
-  process.exit()

 argv = optimist.usage([
   ' Usage: hem COMMAND',
@@ -98,18 +94,24 @@

     @readConfig app.settings.env

-    app.run = ->
+    app.run = (cb) ->
       app.listen app.settings.port, ->
         console.log "up & running @ http://localhost:#{app.settings.port}"
+        if cb
+          cb()
       app

     app

   server: ->
     app = @createServer()
-    app.listen app.settings.port, ->
+    app.run ->
       if process.platform == 'darwin'
-        child.exec "up & running @ http://localhost:#{app.settings.port}", -> return
+        child.exec "open http://localhost:#{app.settings.port}", -> return
+
+  help: ->
+    optimist.showHelp()
+    process.exit()

   readConfig: (config = 'defaults') ->
     configPath = path.join process.cwd(), @options.configPath, config
@@ -122,7 +124,9 @@
     @options

   exec: (command = argv._[0]) ->
-    return help() unless @[command]
+    if not command
+      command = 'server'
+    return @help() unless @[command]
     @[command]()
     switch command
       when 'build' then console.log 'Built application'


plusAll() and minusAll() no longer require an explicit Sequence object.

--- lib/indexed.coffee
+++ lib/indexed.coffee
@@ -254,17 +254,17 @@

   # Returns a new set with the given keys inserted as elements, or
   # this set if it already contains all those elements.
-  plus: -> @plusAll new Sequence arguments
+  plus: -> @plusAll arguments

   # Like plus, but takes a sequence of keys
-  plusAll: (seq) -> @update_ seq.reduce @root, @constructor.plusOne
+  plusAll: (seq) -> @update_ Sequence.reduce seq, @root, @constructor.plusOne

   # Returns a new set with the given keys removed, or this set if it
   # does not contain any of them.
-  minus: -> @minusAll new Sequence arguments
+  minus: -> @minusAll arguments

   # Like plus, but takes a sequence of keys
-  minusAll: (seq) -> @update_ seq.reduce @root, @constructor.minusOne
+  minusAll: (seq) -> @update_ Sequence.reduce seq, @root, @constructor.minusOne

   # Creates a mapped collection of the same type
   map: (fun) -> new @constructor().plusAll @entries.map fun


Stores pane id as a local variable to minimize bloats

--- lib/minimap.coffee
+++ lib/minimap.coffee
@@ -38,21 +38,22 @@
     # store it and it will be used in the `deactivate` method to removes
     # the callback.
     @eachPaneViewSubscription = atom.workspaceView.eachPaneView (paneView) =>
+      paneId = paneView.model.id
       view = new MinimapView(paneView)
       view.onActiveItemChanged(paneView.getActiveItem())
       @updateAllViews()

-      @minimapViews[paneView.model.id] = view
+      @minimapViews[paneId] = view
       @emit('minimap-view:created', view)

       paneView.model.on 'destroyed', =>
-        view = @minimapViews[paneView.model.id]
+        view = @minimapViews[paneId]

         if view?
           @emit('minimap-view:before-destruction', view)

           view.destroy()
-          delete @minimapViews[paneView.model.id]
+          delete @minimapViews[paneId]
           @updateAllViews()


Node 0.10+ gets slow groc.  Sadface.

Node 0.10+ gets slow groc.  Sadface.

--- lib/project.coffee
+++ lib/project.coffee
@@ -31,11 +31,15 @@
     #   to `doc/some/source.file` and not `doc/lib/some/source.file`.
     @stripPrefixes = []

+  # Annoyingly, we seem to be hitting a race condition within Node 0.10's
+  # emulation for old-style streams.  For now, we're dropping concurrent doc
+  # generation to play it safe.  People are still using groc with 0.6.
+  oldNode = process.version.match /v0\.[0-8]\./
   # This is both a performance (over-)optimization and debugging aid.  Instead of spamming the
   # system with file I/O and overhead all at once, we only process a certain number of source files
   # concurrently.  This is similar to what [graceful-fs](https://github.com/isaacs/node-graceful-fs)
   # accomplishes.
-  BATCH_SIZE: 10
+  BATCH_SIZE: if oldNode then 10 else 1

   # Where the magic happens.
   #


renames template vars

renames template vars

--- lib/publish.coffee
+++ lib/publish.coffee
@@ -12,15 +12,15 @@
     @opts = opts
     @ext = '.js'
     params = @parseParams(pubParams)
-    @filename = params.filenameRoot + @ext
+    @filename = params.resourceName + @ext
     @pubName = params.pubName
+    @resName = params.resourceName
     @pubsPath = "server/publications/"
     @fullFilePath = @pubsPath + @filename
     @templatePath = "#{templatePath}publish/publish" + @ext

     @createPublicationFile()
     @appendTemplateIntoPubFile()
-    #@renameTemplateVariables()
     puts "Inserted '#{@pubName}' into #{@fullFilePath}"

   # private
@@ -30,10 +30,13 @@

   appendTemplateIntoPubFile: ->
     template = fs.readFileSync(@templatePath, {encoding: "utf-8"})
+    template = @renameTemplateVariables(template)
     fs.appendFileSync(@fullFilePath, template)

-  renameTemplateVariables: ->
-
+  # returns transformed {String}
+  renameTemplateVariables: (tmpl) ->
+    tmplString = transformVariables(@resName, tmpl)
+    tmplString = tmplString.replace('__pub-name__', @pubName)

   # find the filename and publication name
   parseParams:(params) ->
@@ -43,7 +46,7 @@
       @printBadSynaxHelp()
       process.exit(1)
     else
-      return {filenameRoot: matches[1], pubName: matches[2]}
+      return {resourceName: matches[1], pubName: matches[2]}

   printBadSynaxHelp: ->
     puts "\nBad Publish Name Syntax, see mgen publish --help"


Fixed: tests

Fixed: tests

--- tests/poller.coffee
+++ tests/poller.coffee
@@ -15,16 +15,19 @@
         before ->
             @sandbox = sinon.sandbox.create useFakeTimers: true
             @spy = @sandbox.spy poller, 'prepareNextCheck'
+            @data = new Konnector
+                isImporting: false
+                importInterval: 'week'
+                lastAutoImport: moment().format()
+                slug: 'test'
+            @sandbox.stub Konnector, 'find', (id, callback) =>
+                callback null, @data
+
         after ->
             @sandbox.restore()

         it 'When the cron function is called', ->
-            data = new Konnector
-                isImporting: false
-                importInterval: 'week'
-                lastAutoImport: moment().format()
-                slug: 'test'
-            poller.create(data)
+            poller.create @data
             @spy.callCount.should.equal 1

         it 'Then the cron function should not have been called after 6 days', ->
@@ -43,15 +46,17 @@
         before ->
             @sandbox = sinon.sandbox.create useFakeTimers: true
             @spy = @sandbox.spy poller, 'prepareNextCheck'
+            @data = new Konnector
+                isImporting: false
+                importInterval: 'day'
+                slug: 'test'
+            @sandbox.stub Konnector, 'find', (id, callback) =>
+                callback null, @data
         after ->
             @sandbox.restore()

         it 'When the cron function is called', ->
-            data = new Konnector
-                isImporting: false
-                importInterval: 'day'
-                slug: 'test'
-            poller.create(data)
+            poller.create @data
             @spy.callCount.should.equal 1

         it 'Then the cron function should not have been called after 23 hours', ->
@@ -70,15 +75,17 @@
         before ->
             @sandbox = sinon.sandbox.create useFakeTimers: true
             @spy = @sandbox.spy poller, 'prepareNextCheck'
+            @data = new Konnector
+                isImporting: false
+                importInterval: 'hour'
+                slug: 'test'
+            @sandbox.stub Konnector, 'find', (id, callback) =>
+                callback null, @data
         after ->
             @sandbox.restore()

         it 'When the cron function is called', ->
-            data = new Konnector
-                isImporting: false
-                importInterval: 'hour'
-                slug: 'test'
-            poller.create(data)
+            poller.create @data
             @spy.callCount.should.equal 1

         it 'Then the cron function should not have been called after 59 minutes', ->
@@ -97,15 +104,17 @@
         before ->
             @sandbox = sinon.sandbox.create useFakeTimers: true
             @spy = @sandbox.spy poller, 'prepareNextCheck'
+            @data = new Konnector
+                isImporting: false
+                importInterval: 'month'
+                slug: 'test'
+            @sandbox.stub Konnector, 'find', (id, callback) =>
+                callback null, @data
         after ->
             @sandbox.restore()

         it 'When the cron function is called', ->
-            data = new Konnector
-                isImporting: false
-                importInterval: 'month'
-                slug: 'test'
-            poller.create(data)
+            poller.create @data
             @spy.callCount.should.equal 1

         it 'Then the cron function should not have been called after 22 days', ->


`@head`: now returns empty body (instead of undefined).

This might be specific to Node 0.10.x.

--- tests/routes.coffee
+++ tests/routes.coffee
@@ -110,5 +110,6 @@
     c.post '/', (err,res) -> t.equal 2, res.body, 'posted'
     c.put '/', (err,res) -> t.equal 3, res.body, 'put'
     c.del '/', (err,res) -> t.equal 4, res.body, 'deleted'
-    c.head '/', (err,res) -> t.equal 5, res.body, undefined
+    # c.head '/', (err,res) -> t.equal 5, res.body, undefined
+    c.head '/', (err,res) -> t.equal 5, res.body, ''
     c.patch '/', (err,res) -> t.equal 6, res.body, 'patched'


Update for v2.0

Update for v2.0

--- source/index.coffee
+++ source/index.coffee
@@ -1,374 +1,278 @@
-configure = (configuration) ->
-
-  class Configuration
+configure = ({Async, Promise, Path, promiseCallback} = {}) ->
+
+  if typeof require is 'function'
+    Async ?= require 'async'
+    Promise ?= require 'bluebird'
+    Path ?= require 'path'
+
+  promiseCallback ?= (promise, done) ->
+    fn = if typeof promise.done is 'function' then 'done' else 'then'
+
+    resolve = (args...) -> done null, args...
+    reject = (error) -> done error
+    promise[fn] resolve, reject
+
+  class Exception extends Error
+    name: null
+    payload: null
+
+    constructor: (payload) ->
+      return new Exception payload unless @ instanceof Exception
+      @payload = payload
+      @name = @constructor.name
+      Error.captureStackTrace @, @constructor
+
+  class NotImplemented extends Exception
+    constructor: (payload) ->
+      return new NotImplemented payload unless @ instanceof NotImplemented
+      super payload
+      @message = "Not implemented."
+
+  class ComponentAlreadyDefined extends Exception
+    constructor: (payload) ->
+      return new ComponentAlreadyDefined payload unless @ instanceof ComponentAlreadyDefined
+      super payload
+      @message = "Component already defined: '#{@payload.key}'."
+
+  class ComponentNotDefined extends Exception
+    constructor: (payload) ->
+      return new ComponentNotDefined payload unless @ instanceof ComponentNotDefined
+      super payload
+      @message = "Component not defined: '#{@payload.key}'."
+
+  class ComponentDependenciesNotDefined extends Exception
+    constructor: (payload) ->
+      return new ComponentDependenciesNotDefined payload unless @ instanceof ComponentDependenciesNotDefined
+      super payload
+      @message = "Component '#{@payload.key}' has undefined dependencies: #{@payload.dependencies.join(',')}."
+
+  class ComponentNotAcyclic extends Exception
+    constructor: (payload) ->
+      return new ComponentNotAcyclic payload unless @ instanceof ComponentNotAcyclic
+      super payload
+
+      count = @payload.cycles.length
+      cycles = @payload.cycles
+        .map((cycle, index) -> "\t#{index}: #{cycle.join '/'}")
+        .join "\n"
+
+      @message = "Component '#{@payload.key}' has #{count} dependency cycle(s):\n#{cycles}"
+
+  class Component
+    key: null
+    dependencies: null
+    factory: null
+    factoryType: null

     constructor: (props = {}) ->
-      @[key] = val for own key,val of props
-
-    require: (name) ->
-      require name
-
-    getFunctionArguments: (fn) ->
-      pattern = /\(([^)]*)\)/
-      fn.toString().match(pattern)[1].split /,\s*/
-
-    isObject: (value) ->
-      value? and typeof value is "object"
-
-    isFunction: (value) ->
-      typeof value is "function"
-
-    isArray: (value) ->
-      Array.isArray value
-
-    isEmpty: (value) ->
-      value? and value.length is 0
-
-    assign: (target, sources...) ->
-      sources.forEach (source) ->
-        target[key] = value for own key, value of source
-      return target
-
-    createEventEmitter: ->
-      new (@require("events").EventEmitter)
-
-    createObject: (args...) ->
-      Object.create args...
-
-    defineProperty: (args...) ->
-      Object.defineProperty args...
-
-    defineProperties: (args...) ->
-      Object.defineProperties args...
-
-    createPromise: (resolver) ->
-      @require("when").promise resolver
-
-    asyncMap: (array, mapFn, done) ->
-      @require("async").map array, mapFn, done
-
-    asyncWaterfall: (series, done) ->
-      @require("async").waterfall series, done
-
-  class Exception extends Error
-    name: undefined
+      @key = props.key
+      @factoryType = props.factoryType ? null
+      @dependencies = props.dependencies ? null
+      @factory = props.factory ? (injections..., done) ->
+        return done NotImplemented() if typeof done is 'function'
+        throw NotImplemented()
+
+    getDependencyKeys: ->
+      throw NotImplemented()
+
+    guessFactoryType: ->
+      'async'
+
+    getFactoryType: ->
+      @factoryType ? @guessFactoryType()
+
+    injectPromiseFactory: (injections, done) ->
+      @injectSyncFactory injections, (error, promise) ->
+        return done error if error?
+        promiseCallback promise, done
+
+    injectAsyncFactory: (injections, done) ->
+      return done NotImplemented() if typeof done is 'function'
+      throw NotImplemented()
+
+    injectSyncFactory: (injections, done) ->
+      return done NotImplemented() if typeof done is 'function'
+      throw NotImplemented()
+
+    inject: (injections, done) ->
+      console.log @key, @getFactoryType(), injections
+
+      switch @getFactoryType()
+        when 'promise' then @injectPromiseFactory injections, done
+        when 'async' then @injectAsyncFactory injections, done
+        when 'sync' then @injectSyncFactory injections, done
+        else throw Error("Cannot inject component, unknown factoryType: '#{@factoryType}'")
+
+  class ComponentWithDependenciesArray extends Component
+
+    getDependencyKeys: ->
+      @dependencies ? []
+
+    guessFactoryType: ->
+      return 'sync' if @factory.length is @dependencies.length
+      return 'async'
+
+    injectAsyncFactory: (injections, done) ->
+      try @factory injections..., done
+      catch error then done error
+
+    injectSyncFactory: (injections, done) ->
+      try done null, @factory(injections...)
+      catch error then done error
+
+  class ComponentWithDependenciesObject extends Component
+
+    getDependencyKeys: ->
+      ({key, val} for own key, val of @dependencies).map ({key, val}) ->
+        if typeof val is 'string' then val else key
+
+    guessFactoryType: ->
+      return 'sync' if @dependencies.length is 0 and @factory.length is 0
+      return 'sync' if @factory.length is 1
+      return 'async'
+
+    createInjectionObject: (injections) ->
+      collectInjections = (memo, key, index) ->
+        memo[key] = injections[index]
+        memo
+
+      injectionKeys = (key for own key of @dependencies)
+      injectionObject = injectionKeys.reduce collectInjections, {}
+
+      console.log @key, 'injectionObject', injectionObject
+      injectionObject
+
+    injectSyncFactory: (injections, done) ->
+      injectionObject = @createInjectionObject injections
+
+      try done null, @factory(injectionObject)
+      catch error then done error
+
+    injectAsyncFactory: (injections, done) ->
+      injectionObject = @createInjectionObject injections
+
+      try @factory injectionObject, done
+      catch error then done error
+
+  class ComponentWithoutDependencies extends Component
+    getDependencyKeys: ->
+      []
+
+    guessFactoryType: ->
+      return 'async' if @factory.length is 1
+      return 'sync'
+
+    injectAsyncFactory: (injections, done) ->
+      try @factory done
+      catch error then done error
+
+    injectSyncFactory: (injections, done) ->
+      try done null, @factory()
+      catch error then done error
+
+  class Container
+    components: null
+    promises: null
+    componentFactory: null

     constructor: (props = {}) ->
-      @[key] = val for own key,val of props
-      @name = @constructor.name
-      @message = @getMessage()
-      Error.captureStackTrace @, @constructor
-
-    getMessage: -> @constructor.name
-
-  class NotImplemented extends Exception
-    getMessage: -> "Abstract method not implemented."
-
-  class StrategyNotFound extends Exception
-    factory: undefined
-    args: undefined
-
-    getMessage: ->
-      factory = @factory.constructor.name
-      "No strategy found for factory '#{factory}' using arguments: [#{@args}]"
-
-  class ComponentDependenciesUndefined extends Exception
-    componentName: undefined
-    undefinedDependencies: undefined
-
-    getMessage: ->
-      "Component '#{@componentName}' " +
-      "has undefined dependencies: [#{@undefinedDependencies}]"
-
-  class ComponentCyclic extends Exception
-    componentName: undefined
-    cycles: undefined
-
-    getMessage: ->
-      "Component '#{@componentName}' has #{@cycles.length} cycle(s): " +
-      @inspectCycles()
-
-    inspectCycles: ->
-      mapFn = (cycle) => "['#{cycle.join("','")}']"
-      @cycles.map(mapFn).join(", ")
-
-  class ComponentUndefined extends Exception
-    componentName: undefined
-
-    getMessage: ->
-      "Component not defined: '#{@componentName}'"
-
-  class DictionaryEvent
-    constructor: (props = {}) ->
-      @name = @constructor.name
-      @setPayload props.payload
-
-    setPayload: (payload = {}) ->
-      @payload = {}
-      @payload.dictionary = payload.dictionary
-      @payload.key = payload.key
-
-  class DictionaryValueSet extends DictionaryEvent
-
-  class DictionaryValueRemoved extends DictionaryEvent
-
-  class Dictionary
-
-    constructor: (props = {}) ->
-      _emitter = props.eventEmitter ? $.createEventEmitter()
-
-      @emit = (args...) -> _emitter.emit args...
-      @addListener = (args...) -> _emitter.addListener args...
-      @removeListener = (args...) -> _emitter.removeListener args...
-      @removeAllListeners = (args...) -> _emitter.removeAllListeners args...
-
-      _definitions = $.assign $.createObject(null), props.definitions
-
-      $.defineProperty @, "definitions", enumerable: true, get: ->
-        $.assign {}, _definitions
-
-      @get = (key) ->
-        _definitions[key]
-
-      @set = (key, value) ->
-        result = _definitions[key] = value
-        @emit "set", new DictionaryValueSet
-          payload: { dictionary: @, key: key }
-        return result
-
-      @remove = (key) ->
-        value = _definitions[key]
-        result = delete _definitions[key]
-        @emit "remove", new DictionaryValueRemoved
-          payload: { dictionary: @, key: key }
-        return result
-
-      @forEach = (callback) =>
-        callback value, key, @ for own key, value of _definitions
-
-    reduce: (reduceFn, memo) ->
-      @forEach (value, key, target) -> memo = reduceFn memo, value, key, target
-      memo
-
-    addListener: (args...) -> @emitter.addListener args...
-    removeListener: (args...) -> @emitter.removeListener args...
-    removeAllListeners: (args...) -> @emitter.removeAllListeners args...
-
-    isDefined: (key) ->
-      @get(key) != undefined
-
-    isUndefined: (key) ->
-      @get(key) == undefined
-
-  class Components extends Dictionary
-
-    getUndefinedDependencies: (name) ->
-      @get(name).dependencies.filter @isUndefined.bind(@)
-
-    getCycles: (name, stack = []) ->
-      return [stack.concat(name)] if stack.indexOf(name) isnt -1
-      return [] unless @isDefined(name)
-      stack = stack.concat name
-
-      reduceDependencyCycles = (memo, dependency) =>
-        memo.concat @getCycles(dependency, stack)
-
-      dependencies = @get(name).dependencies
-      dependencies.reduce reduceDependencyCycles, []
-
-    getParentsOf: (dependee, parents = []) ->
-
-      reduceParents = (parents, component, name) =>
-        return parents if component.dependencies.indexOf(dependee) is -1
-        return parents unless parents.indexOf(name) is -1
-        @getParentsOf name, parents.concat(name)
-
-      @reduce reduceParents, parents
-
-    assertDefined: (name) ->
-      return if @isDefined(name)
-      throw new ComponentUndefined
-        componentName: name
-
-    assertAcyclic: (name) ->
-      cycles = @getCycles name
-      return if cycles.length is 0
-      throw new ComponentCyclic
-        componentName: name
-        cycles: cycles
-
-    assertDependenciesDefined: (name) ->
-      undefinedDependencies = @getUndefinedDependencies name
-      return if undefinedDependencies.length is 0
-      throw new ComponentDependenciesUndefined
-        componentName: name
-        undefinedDependencies: undefinedDependencies
-
-    validate: (name) ->
-      @assertDefined name
-      @assertAcyclic name
-      @assertDependenciesDefined name
-
-  class Promises extends Dictionary
-
-  class Container
-
-    constructor: (props = {}) ->
-      @componentFactory = props.componentFactory ? new ComponentFactory
-      _components = new Components props.components
-      _promises = new Promises props.promises
-
-      _components.addListener "set", (event) =>
-        @handleComponentChange event.payload.key
-
-      _components.addListener "remove", (event) =>
-        @handleComponentChange event.payload.key
-
-      $.defineProperty @, "components", enumerable: true, get: -> _components
-      $.defineProperty @, "promises", enumerable: true, get: -> _promises
-
-    defineComponent: (name, args...) ->
-      component = @componentFactory.construct args...
-      @components.set name, component
-
-    handleComponentChange: (key) ->
-      @promises.remove key
-      @components.getParentsOf(key).forEach (parent) =>
-        @promises.remove parent
-
-    createComponentPromise: (name) ->
-      $.createPromise (resolve, reject) =>
-        @components.get(name).resolve this, (error, result) ->
-          return reject error if error?
-          return resolve result
-
-    ensureComponentPromiseSet: (name) ->
-      return if @promises.isDefined name
-      @promises.set name, @createComponentPromise(name)
-
-    resolveComponent: (name, done) ->
-      try
-        @components.validate(name) unless @promises.isDefined(name)
-        @ensureComponentPromiseSet(name)
-        @promises.get(name).done done.bind(null, null), done
-      catch error then done error
-
-  class Component
-
-    constructor: (props = {}) ->
-      _dependencies = props.dependencies.slice() if props.dependencies != null
-      _dependencies ?= []
-      _factory = props.factory
-
-      $.defineProperties @,
-        dependencies:
-          enumerable: true, get: -> _dependencies.slice()
-        factory:
-          enumerable: true, get: -> _factory
-
-    resolveDependencies: (container, done) ->
-      resolveComponent = container.resolveComponent.bind container
-      $.asyncMap @dependencies, resolveComponent, done
-
-    applyFactory: (args, done) ->
-      @factory args..., done
-
-    resolve: (container, done) ->
-      series = Array \
-        @resolveDependencies.bind(this, container),
-        @applyFactory.bind this
-
-      $.asyncWaterfall series, done
-
-  class Strategies
-    constructor: (props = {}) ->
-      @collection = props.collection ? []
-
-    unshift: (strategy) ->
-      @strategies.unshift strategy
-
-    push: (strategy) ->
-      @collection.push strategy
-
-    find: (args...) ->
-      reduceFn = (memo, strategy) ->
-        return memo if memo?
-        return strategy if strategy.test(args...)
-      @collection.reduce reduceFn, null
-
-  class Strategy
-    test: -> throw new NotImplemented
-    use: -> throw new NotImplemented
-
-  class PropertiesStrategy extends Strategy
-    test: (props, rest...) ->
-      use = $.isEmpty(rest) and
-            $.isObject(props) and
-            $.isFunction(props.factory)
-
-    constructComponent: (props) ->
-      new Component props
-
-    use: (props) ->
-      @constructComponent props
-
-  class ArgumentsStrategy extends Strategy
-    test: (dependencies = [], factory, rest...) ->
-      use = $.isEmpty(rest) and
-            $.isArray(dependencies) and
-            $.isFunction(factory)
-
-    use: (dependencies = [], factory) ->
-      new Component dependencies: dependencies, factory: factory
-
-  class AutoFactoryStrategy extends Strategy
-    test: (factory, rest...) ->
-      use = $.isEmpty(rest) and
-            $.isFunction(factory)
-
-    use: (factory) ->
-      dependencies = $.getFunctionArguments(factory)[0...-1]
-      new Component dependencies: dependencies, factory: factory
+      @components = props.components ? Object.create(null)
+      @promises = props.promises ? Object.create(null)
+      @componentFactory = props.componentFactory ? new ComponentFactory()
+
+    defineComponent: (key, args...) ->
+      throw ComponentAlreadyDefined {key} if @components[key]?
+      @components[key] = @componentFactory.construct args...
+      delete @promises[key]
+
+    createComponentPromise: (key) ->
+      new Promise (resolve, reject) =>
+        component = @components[key]
+        dependencyKeys = component.getDependencyKeys()
+
+        Async.map dependencyKeys, @resolveComponent.bind(@), (error, injections) ->
+          return reject(error) if error?
+
+          component.inject injections, (error, result) ->
+            if error? then reject(error) else resolve(result)
+
+    isComponentDefined: (key) ->
+      @components[key]?
+
+    assertComponentDefined: (key) ->
+      throw ComponentNotDefined {key} unless @isComponentDefined(key)
+
+    assertComponentDependenciesDefined: (key) ->
+      component = @components[key]
+      dependencies = component.getDependencyKeys().filter (key) => !@isComponentDefined(key)
+
+      throw ComponentDependenciesNotDefined {key, dependencies} if dependencies.length
+
+    getComponentCycles: (key, stack = []) ->
+      return [stack.concat(key)] if key in stack
+
+      component = @components[key]
+      return [] unless component?
+
+      stack = stack.concat key
+
+      reduceDependencyCycles = (memo, key) =>
+        memo.concat @getComponentCycles(key, stack)
+
+      dependencyKeys = component.getDependencyKeys()
+      dependencyKeys.reduce reduceDependencyCycles, []
+
+    assertComponentAcyclic: (key) ->
+      cycles = @getComponentCycles key
+      throw ComponentNotAcyclic {key, cycles} if cycles.length
+
+    validateComponent: (key) ->
+      @assertComponentDefined key
+      @assertComponentDependenciesDefined key
+      @assertComponentAcyclic key
+
+    validateComponents: ->
+      @validateComponent key for own key of @components
+
+    resolveComponent: (key, done) ->
+
+      unless @promises[key]
+        try @validateComponent key
+        catch error then done error
+        @promises[key] = @createComponentPromise key
+
+      promiseCallback @promises[key], done

   class ComponentFactory
-    constructor: (props = {}) ->
-      @strategies = props.strategies
-
-      unless @strategies?
-        @strategies = new Strategies
-        @strategies.push new PropertiesStrategy
-        @strategies.push new ArgumentsStrategy
-        @strategies.push new AutoFactoryStrategy
-
-    construct: (args...) ->
-      strategy = @strategies.find args...
-      throw new StrategyNotFound factory: this, args: args unless strategy?
-      strategy.use args...
-
-  $ = new Configuration configuration
-
-  IOC =
-    configuration: $
-    configure: configure
-    Configuration: Configuration
-    Exception: Exception
-    NotImplemented: NotImplemented
-    StrategyNotFound: StrategyNotFound
-    ComponentCyclic: ComponentCyclic
-    ComponentDependenciesUndefined: ComponentDependenciesUndefined
-    ComponentUndefined: ComponentUndefined
-    Component: Component
-    Components: Components
-    Container: Container
-    Dictionary: Dictionary
-    DictionaryEvent: DictionaryEvent
-    DictionaryValueSet: DictionaryValueSet
-    DictionaryValueRemoved: DictionaryValueRemoved
-    Promises: Promises
-    Strategies: Strategies
-    Strategy: Strategy
-    ComponentFactory: ComponentFactory
-    PropertiesStrategy: PropertiesStrategy
-    ArgumentsStrategy: ArgumentsStrategy
+
+    construct: ({key, dependencies, depends, factory, factoryType}) ->
+      dependencies ?= depends
+
+      unless dependencies?
+        return new ComponentWithoutDependencies {key, factory, factoryType}
+
+      if Array.isArray dependencies
+        return new ComponentWithDependenciesArray {key, dependencies, factory, factoryType}
+
+      if typeof dependencies is 'object'
+        return new ComponentWithDependenciesObject {key, dependencies, factory, factoryType}
+
+      throw Error "Cannot construct component, invalid dependencies."
+
+  IOC = {
+    configure,
+    Container,
+    ComponentFactory,
+    Component,
+    ComponentWithoutDependencies,
+    ComponentWithDependenciesObject,
+    ComponentWithDependenciesArray,
+    Exception,
+    NotImplemented,
+    ComponentNotDefined,
+    ComponentDependenciesNotDefined,
+    ComponentNotAcyclic,
+    ComponentAlreadyDefined
+  }

 module.exports = configure()


 Merge branch 'master.teamwork.xss' into 'master'

teamwork: XSS encode session key in the search query

--- client/Teamwork/AppView.coffee
+++ client/Teamwork/AppView.coffee
@@ -13,7 +13,7 @@
   handleQuery: (query) ->
     @teamworkApp = @createApp query  unless @teamworkApp
     importUrl = query.importUrl or query.import
-    if query.sessionKey then @handleSessionKey query.sessionKey
+    if query.sessionKey then @handleSessionKey Encoder.XSSEncode query.sessionKey
     else if importUrl then @handleImportUrl importUrl
     else if @teamworkApp.getOption "sessionKey" then @restoreLocation()
     else @teamworkApp.emit "NewSessionRequested"


editable dataset titles

editable dataset titles

--- client/code/view/titles.coffee
+++ client/code/view/titles.coffee
@@ -1,17 +1,21 @@
 class Cu.View.Title extends Backbone.View
+  tagName: 'h2'
   render: ->
-    @$el.html """
#{@options.text}
"""
+    @$el.html """#{@options.text}"""
     @

 class Cu.View.DataSetTitle extends Backbone.View
+  tagName: 'h2'
   events:
-    'click a': 'nameClicked'
+    'click .editable': 'nameClicked'
     'blur input': 'editableNameBlurred'
-    'keypress input': 'enterOnEditableName'
+    'keypress input': 'keypressOnEditableName'

   render: ->
     tpl = """
-      #{@model.get 'displayName'}
+      My Datasets
+      /
+      #{@model.get 'displayName'}
       <input type="text" id="txtName" style="display: none"/>
     """
     @$el.html tpl
@@ -19,24 +23,34 @@

   nameClicked: (e) ->
     e.preventDefault()
-    @$el.find('a').hide()
-    @$el.find('input').val @model.name()
-    @$el.find('input').show 0, ->
+    $a = @$el.find('.editable')
+    $a.hide()
+    @$el.find('input').val(@model.name()).css('width', $a.width() + 20).show 0, ->
       @focus()
-      @select()

   editableNameBlurred: ->
-    label = @$el.find('a')
-    if not label.is(':visible')
-      label.show()
-      @newName = (@$el.find('input').hide()).val()
-      @$el.find('a').text @newName
+    $label = @$el.find('.editable')
+    $input = @$el.find('input')
+    @newName = $.trim($input.val())
+    if @newName == '' or @newName == $label.text()
+      $label.show().next().hide();
+    else
+      $input.hide()
+      $label.text(@newName).show()
       @model.save displayName: @newName,
         success: =>
+          $label.addClass 'saved'
+          setTimeout ->
+            $label.removeClass 'saved'
+          , 1000
         error: (e) =>
-          @$el.find('a').text @model.name()
-          console.log e
+          $label.text @model.name()
+          console.log 'error saving new name', e

+  editableNameEscaped: (e) ->
+    e.preventDefault()
+    @$el.find('.editable').show().next().hide()

-  enterOnEditableName: (event) ->
+  keypressOnEditableName: (event) ->
     @editableNameBlurred() if event.keyCode is 13
+    @editableNameEscaped() if event.keyCode is 27


Change schema registration so that ids will work

--- spec/schemas.coffee
+++ spec/schemas.coffee
@@ -6,8 +6,12 @@
 path = require 'path'
 fs = require 'fs'

+schemaPath = '../schema'
 getSchema = (name) ->
-  filepath = path.join __dirname, '../schema', name+'.json'
+  filepath = path.join __dirname, schemaPath, name
+  loadSchema filepath
+
+loadSchema = (filepath) ->
   content = fs.readFileSync filepath, { encoding: 'utf-8' }
   return JSON.parse content

@@ -16,28 +20,35 @@
   content = fs.readFileSync filepath, { encoding: 'utf-8' }
   return yaml.safeLoad content

-schemas =
-  'siteconfig': 'Site config'
-  'contentblock': 'Content block'
+describe 'Schemas', ->
+  before: ->
+    metaSchema = loadSchema path.join __dirname, 'json-schema.json'
+    tv4.addSchema 'http://json-schema.org/draft-04/schema', metaSchema
+  after: ->
+    tv4.reset()

-describe 'Schemas', ->
+  schemas = fs.readdirSync path.join __dirname, schemaPath
+  schemas.forEach (schemaFile) ->
+    schema = getSchema schemaFile
+    tv4.addSchema schema.id, schema
+    schemaName = path.basename schemaFile, '.json'

-  Object.keys(schemas).forEach (schemaName) ->
-    description = schemas[schemaName]
-
-    describe description, ->
-      cases = getExamples schemaName
-      schema = getSchema schemaName
+    describe schema.title, ->
+      try
+        cases = getExamples schemaName
+      catch e
+        return

       cases.forEach (testcase) ->

         describe "#{testcase._name}", ->
+          tv4.addSchema schema.id, schema
           if testcase._valid
             it "should be valid", ->
-              results = tv4.validateMultiple testcase._data, schema
+              results = tv4.validateMultiple testcase._data, schema.id
               chai.expect(results.errors).to.eql []
           else
             it "should be invalid", ->
-              results = tv4.validateMultiple testcase._data, schema
+              results = tv4.validateMultiple testcase._data, schema.id
               chai.expect(results.errors).to.not.eql []


fix window.BuscaCep

fix window.BuscaCep

--- src/BuscaCep.coffee
+++ src/BuscaCep.coffee
@@ -1,7 +1,7 @@
-class BuscaCep
+class window.BuscaCep
   @init = (url, format)->
-    BuscaCep.URL = url || '/api/address/cep/:cep.json'
-    BuscaCep.format = format ||
+    window.BuscaCep.URL = url || '/api/address/cep/:cep.json'
+    window.BuscaCep.format = format ||
       cidade_id: 'city.id'
       cidade: 'city'
       cidade_nome: 'city.nome'
@@ -12,7 +12,7 @@
       resultado: 'resultado'

   constructor: (@$el)->
-    @_url = BuscaCep.URL
+    @_url = window.BuscaCep.URL
     @find('[data-endereco-cep]').on 'blur', (e)=>
       @get e.target.value

@@ -45,7 +45,7 @@

   convertData: (data)->
     ret = {}
-    for k of BuscaCep.format
+    for k of window.BuscaCep.format
       v = @refObj data, BuscaCep.format[k]
       ret[k] = v
     ret


disable public share of whole project

disable public share of whole project

--- salvus/page/project_settings.cjsx
+++ salvus/page/project_settings.cjsx
@@ -629,6 +629,8 @@
         </Button>

     render : ->
+        # TEMPORARILY DISABLED -- this very badly broken, due to hackish design involving componentWillReceiveProps above.
+        return

         if not @props.public_paths?
             return <Loading />
         project_id = @props.project.get('project_id')


add angle support to ArtGenerator.drawText

--- src/3d/extra/ArtGenerator.coffee
+++ src/3d/extra/ArtGenerator.coffee
@@ -94,9 +94,13 @@
     options.font ?= '40px Helvetica'
     options.x ?= 0
     options.y ?= 0
+    options.angle ?= 0

     @ctx.save()
     @ctx.font = options.font
+
+    if options.angle != 0
+      @ctx.rotate(options.angle * Math.PI / 180)

     if options.strokeStyle?
       @ctx.miterLimit = 2


fix bug in 'next' button behavior (book metadata modal)

--- scripts/views/layouts/workspace/bookshelf.coffee
+++ scripts/views/layouts/workspace/bookshelf.coffee
@@ -25,8 +25,6 @@
       }

     editBook: (book) ->
-      console.log book.get('subject'), book
-
       modal = $('#edit-book-modal').modal {show:true}
       modal.find('.nav.nav-tabs li:first a').click()

@@ -51,9 +49,9 @@
       _.each book.get('translators'), (translator) -> modal.find('[name="translators"]').tagsinput('add', translator)
       _.each book.get('illustrators'), (illustrator) -> modal.find('[name="illustrators"]').tagsinput('add', illustrator)

-      modal.find('[data-edit-toggle]').click ->
+      modal.find('[data-edit-toggle]').off('click').click ->
         $(this).hide().siblings('input').show().focus()
-      .siblings('input').blur ->
+      .siblings('input').off('blur').blur ->
         $(this).hide().siblings('[data-edit-toggle]').show().find('[data-title]').text($(this).val())

       modal.find('a[data-toggle="tab"]').on('shown', (e) ->
@@ -66,7 +64,7 @@
       # populate book data in the form
       modal.find('input[name="title"]').val(book.get('title')).blur()

-      modal.find('[data-tab-next]').click ->
+      modal.find('[data-tab-next]').off('click').click ->
         next = modal.find('.nav li.active').next()

         if next.length


small changes on validateemamil file

small changes on validateemamil file

--- servers/lib/server/handlers/validateemail.coffee
+++ servers/lib/server/handlers/validateemail.coffee
@@ -16,7 +16,7 @@

     JUser.login clientId, { username : email, password, tfcode }, (err, info) ->

-      {isValid : isEmail} = JUser.validateAt 'email', email, yes
+      { isValid : isEmail } = JUser.validateAt 'email', email, yes

       if err?.name is 'VERIFICATION_CODE_NEEDED'
         return res.status(400).send 'TwoFactor auth Enabled'
@@ -26,6 +26,7 @@

       else if err and isEmail
         JUser.emailAvailable email, (err_, response) ->
+
           return res.status(400).send 'Bad request'  if err_

           return if response
@@ -37,5 +38,5 @@
       unless info
         return res.status(500).send 'An error occurred'

-      res.cookie 'clientId', info.replacementToken, path : '/'
+      res.cookie 'clientId', info.replacementToken, { path : '/' }
       return res.status(200).send 'User is logged in!'


removed outdated info

removed outdated info

--- docs/api.coffee
+++ docs/api.coffee
@@ -1,43 +1 @@
 # API Ideas
-
-# Radfish
-class Radfish
-  plugins: []
-  keybindings: {}
-
-class View =
-  app: null
-
-  html: ->
-    # gets or creates the html for the view
-
-class Pane extends View
-
-class Modal extends View
-
-class Plugin
-  app: null
-
-# Atomicity
-class App extends Radfish
-  baseURL: null
-  openURLs: []
-
-  startup: (@baseURL) ->
-
-  shutdown: ->
-
-  open: (path...) ->
-
-  close: (paths...) ->
-
-  save: (paths...) ->
-
-class Editor
-  app: null
-
-class TreeView
-  app: null
-
-class Tabs
-  app: null


DockController: make the DevTools persistent

--- client/Main/maincontroller/dockcontroller.coffee
+++ client/Main/maincontroller/dockcontroller.coffee
@@ -8,7 +8,7 @@
     { title : "Editor",    path : "/Ace",      order : 40, type :"persistent" }
     { title : "Apps",      path : "/Apps",     order : 50, type :"persistent" }
     # { title : "About",     path : "/About",    order : 70, type :"persistent" }
-    { title : "DevTools",  path : "/DevTools", order : 60, useFakeIcon: yes }
+    { title : "DevTools",  path : "/DevTools", order : 60, type :"persistent", useFakeIcon: yes }
   ]

   constructor:(options = {}, data)->


Activity: added scroll option to activity pane

--- client/Social/Activity/views/activitypane.coffee
+++ client/Social/Activity/views/activitypane.coffee
@@ -4,6 +4,7 @@
     options.type        or= ''
     options.cssClass     ?= "activity-pane #{options.type}"
     options.wrapper      ?= yes
+    options.scrollView   ?= yes
     options.lastToFirst  ?= no

     KDTabPaneView.call this, options, data


Activity: activityPane openPane method name changed

--- client/Social/Activity/views/activitypane.coffee
+++ client/Social/Activity/views/activitypane.coffee
@@ -95,10 +95,10 @@
       maxHandleWidth    : Infinity
       paneData          : @getPaneData()
     @tabView.tabHandleContainer.setClass 'filters'
-    @tabView.on 'PaneDidShow', @bound 'openPane'
-
-
-  openPane: (pane) ->
+    @tabView.on 'PaneDidShow', @bound 'handlePaneShown'
+
+
+  handlePaneShown: (pane) ->

     switch pane
       when @mostLiked?.parent


TrollMode: Fix unmark user as Troll menu item is not shown for Troll Users bug

--- client/Social/Activity/views/settingsview.coffee
+++ client/Social/Activity/views/settingsview.coffee
@@ -53,34 +53,33 @@
     return @menu

-  addAdminMenu: ->
-
+  prepareAdminMenu: ->
     post = @getData()

     @addOwnerMenu()

     {activityController} = KD.singletons

-    if KD.checkFlag 'exempt', KD.whoami()
-      @addMenuItem 'Unmark User as Troll', ->
-        activityController.emit "ActivityItemUnMarkUserAsTrollClicked", post
-    else
-      @addMenuItem 'Mark User as Troll', ->
-        activityController.emit "ActivityItemMarkUserAsTrollClicked", post
-
-    @addMenuItem 'Block User', ->
-      activityController.emit "ActivityItemBlockUserClicked", post.account._id
-    @addMenuItem 'Impersonate', ->
-      {constructorName, _id} = post.account
-      KD.remote.cacheable constructorName, _id, (err, owner) ->
-        return KD.showErrorNotification err  if err
-        return KD.showNotification "Account not found"  unless owner
+    {constructorName, _id} = post.account
+    KD.remote.cacheable constructorName, _id, (err, owner) =>
+      return KD.showErrorNotification err  if err
+      return KD.showNotification "Account not found"  unless owner
+
+      if owner.isExempt
+        @addMenuItem 'Unmark User as Troll', ->
+          activityController.emit "ActivityItemUnMarkUserAsTrollClicked", post
+      else
+        @addMenuItem 'Mark User as Troll', ->
+          activityController.emit "ActivityItemMarkUserAsTrollClicked", post
+
+      @addMenuItem 'Block User', ->
+        activityController.emit "ActivityItemBlockUserClicked", post.account._id
+      @addMenuItem 'Impersonate', ->
         KD.impersonate owner.profile.nickname

-    # TODO since system tag is not implemented for new social menu item is regressed
-    # @addMenuItem 'Add System Tag', => @selectSystemTag post
-
-    return @menu
+      # TODO since system tag is not implemented for new social menu item is regressed
+      # @addMenuItem 'Add System Tag', => @selectSystemTag post
+      @settings.getOptions().menu = @menu

   settingsMenu: ->
@@ -89,7 +88,7 @@

     @addFollowActionMenu()
     @addOwnerMenu()  if KD.isMyPost @getData()
-    @addAdminMenu()  if KD.checkFlag('super-admin')
+    @prepareAdminMenu()  if KD.checkFlag('super-admin')

     return @menu


Add link to github

Add link to github

--- web/src/views/main-menu.cjsx
+++ web/src/views/main-menu.cjsx
@@ -33,4 +33,8 @@

                 Atom feed

+
+
+                View Source
+


if a person has a type, don't override it

--- models/models.coffee
+++ models/models.coffee
@@ -83,7 +83,7 @@
     for email in newEmails
       Person.firstOrNew { email: email }, (error, member) =>
         @members.push member
-        member.type = 'Participant'
+        member.type or= 'Participant'
         member.inviteTo this, ->
           fn() if --threads is 0


Only update floor lights when the value changes

--- components/ConvertFloor.coffee
+++ components/ConvertFloor.coffee
@@ -9,11 +9,20 @@
   c.outPorts.add 'floor',
     datatype: 'int'
     addressable: true
+  c.previous = null
+  c.tearDown = (callback) ->
+    c.previous = null
+    callback()
   c.process (input, output) ->
     return unless input.hasData 'colors'
     colors = input.getData 'colors'
     return output.done() unless colors.length
-    [r, g, b] = colors[0]
+    followed = colors[0]
+    if c.previous and followed[0] is c.previous[0] and followed[1] is c.previous[1] and followed[2] is c.previous[2]
+      # No state change
+      return output.done()
+    c.previous = followed
+    [r, g, b] = followed
     output.send
       floor: new noflo.IP 'data', r,
         index: 0
@@ -23,4 +32,4 @@
     output.send
       floor: new noflo.IP 'data', b,
         index: 2
-    output.done()+    output.done()


bump the module timeout to 30 seconds

bump the module timeout to 30 seconds

--- models/person.coffee
+++ models/person.coffee
@@ -28,7 +28,7 @@
 PersonSchema.plugin auth,
   everymodule:
     everyauth:
-      moduleTimeout: 10000
+      moduleTimeout: 30000
       findUserById: (userId, fn) -> Person.findById(userId, fn)
       handleLogout: (req, res) ->
         req.logout()


set a height even if there is no aspect ratio (closes #4153)

--- models/mixins/image_sizes.coffee
+++ models/mixins/image_sizes.coffee
@@ -67,6 +67,8 @@
       else
         maxDimension = @get('original_height')
       if height > maxDimension then maxDimension else Math.floor(height)
+    else
+      height

   maxHeightForWidth: (width, maxDimension) ->
     aspectRatio = @aspectRatio()


html highlight removed

html highlight removed

--- docs/components/common/Code.cjsx
+++ docs/components/common/Code.cjsx
@@ -101,8 +101,6 @@
             color: #689F38; // light green 700
           }
           .rendered .hljs-title{
-            color: #37474F; // blue grey 800
-            text-decoration: underline;
           }
         "}</style>


Contestant login should only require public github username and email.

--- models/person.coffee
+++ models/person.coffee
@@ -39,7 +39,7 @@
         res.redirect(req.param('returnTo') || req.header('referrer') || '/')
   github:
     everyauth:
-      scope: 'user'
+      scope: 'user:email'
       redirectPath: '/login/done'
       myHostname: env.hostname
       appId: env.github_app_id


stray console

stray console

--- models/person.coffee
+++ models/person.coffee
@@ -157,7 +157,6 @@
     return callback(error) if error
     person ||= new Person
     try person.updateFromFacebook facebook catch e then callback(e)
-    console.log(person, 'person')
     person.save (err) -> callback(err, person)

 Person = mongoose.model 'Person', PersonSchema


fixed coffeelint errors on server/helpers.coffee

--- servers/lib/server/helpers.coffee
+++ servers/lib/server/helpers.coffee
@@ -181,7 +181,7 @@
         delegate    : account
       sessionToken  : sessionToken

-    { JUser }= koding.models
+    { JUser } = koding.models
     return JUser.authenticateWithOauth client, { provider, isUserLoggedIn }, (err, response) ->

       return res.status(400).send err  if err
@@ -266,3 +266,5 @@
   isInAppRoute
   isMainDomain
 }
+
+


Teamwork: Send default size options and fix sharable terminal mode.

--- client/CollaborativeWorkspace/panes/sharableclientterminalpane.coffee
+++ client/CollaborativeWorkspace/panes/sharableclientterminalpane.coffee
@@ -7,13 +7,15 @@
     options.vmRegion = sessionOptions.vmRegion
     options.joinUser = sessionOptions.host
     options.session  = sessionOptions.key
+    options.sizeX    = 100
+    options.sizeY    = 100
     options.delay    = 0

     super options, data

   vmOn: -> Promise.resolve()

-  getMode: -> 'shared'
+  getMode: -> 'resume'

   fetchVm: (callback)->
     {vmName, vmRegion} = @getOptions()


Fix error message

Fix error message

--- drivers/javascript/cursor.coffee
+++ drivers/javascript/cursor.coffee
@@ -259,6 +259,7 @@
     _promptNext: ->
         while @_cbQueue[0]?
             if @bufferEmpty() is true
+                # This can happen if the response sent back is empty (else a response is shifted in `_handleRow`
                 if @_responses.length is 1
                     @_responses.shift()

@@ -357,9 +358,9 @@
     _makeEmitter: ->
         @emitter = new EventEmitter
         @each = ->
-            throw new err.RqlDriverError "You cannot the cursor interface and the EventEmitter interface at the same time."
+            throw new err.RqlDriverError "You cannot use the cursor interface and the EventEmitter interface at the same time."
         @next = ->
-            throw new err.RqlDriverError "You cannot the cursor interface and the EventEmitter interface at the same time."
+            throw new err.RqlDriverError "You cannot use the cursor interface and the EventEmitter interface at the same time."

     addListener: (args...) ->


Now displaying sum as Fixed instead of only rounded.

--- app/assets/javascripts/active_list.jquery.js.coffee
+++ app/assets/javascripts/active_list.jquery.js.coffee
@@ -106,7 +106,7 @@
       if currency_precision and currency_symbol
         magnitude = Math.pow(10, currency_precision)
         rounded = Math.round(total * magnitude) / magnitude
-        displayable_total = "#{rounded} #{currency_symbol}"
+        displayable_total = "#{rounded.toFixed(currency_precision)} #{currency_symbol}"
       list.find("#computation-results td[data-list-result-for=\"#{column}\"] #list-computation-result").html(displayable_total)
     for column in list.find("th[data-list-column-computation]")
       col_number = $(column).data('list-column-cells')


Closing modal resets url

Closing modal resets url

--- app/assets/javascripts/admin/routers/home.js.coffee
+++ app/assets/javascripts/admin/routers/home.js.coffee
@@ -24,5 +24,7 @@
       onClose: (dialog) ->
         $.modal.close()
         $("#integrations-select").select2 "val", ""
+        $("#new-integration-modal").html('')
+        Backbone.history.navigate '/', trigger: true
     )
 )


Correct phase ordering client-side.

Correct phase ordering client-side.

--- app/assets/javascripts/manuscript_manager.js.coffee
+++ app/assets/javascripts/manuscript_manager.js.coffee
@@ -123,7 +123,7 @@
       Tahi.utils.bindColumnResize()

       $.getJSON @props.route, (data,status) =>
-        @setState flows: data.flows, paper: data.paper
+        @setState flows: _.sortBy(data.flows, (f) -> f.position), paper: data.paper

     componentDidUpdate: ->
       Tahi.utils.resizeColumnHeaders()
@@ -141,6 +141,16 @@
           flow.tasks = _.reject flow.tasks, (task) ->
             task.taskId == taskId
           @setState flows: newFlows
+
+    insertPhase: (newPhase) ->
+      newPhase.paper = @state.paper
+      newFlows = @state.flows.slice(0) #don't mutate old state
+      [head, tail] =  _.partition newFlows, (flow) ->
+        flow.position < newPhase.position
+      _.map tail, (flow) ->
+        flow.position += 1
+      updatedFlows = head.concat newPhase, tail
+      @setState flows: updatedFlows

     addColumn: (position) ->
       newPosition = position + 1
@@ -156,14 +166,9 @@
         data:
           phase:
             task_manager_id: @state.paper.task_manager_id
-            position: column.position
+            position: newPosition
             name: column.name
-        success: (data) =>
-          newPhase = data.phase
-          newPhase.paper = @state.paper
-          newFlows = @state.flows.slice(0) #don't mutate old state
-          newFlows.splice(newPosition, 0, newPhase)
-          @setState flows: newFlows
+        success: (data) => @insertPhase(data.phase)

   Column: React.createClass


Adapted the event handler that marks entries as read via AJAX to Bootstrap 3.

This event handler had stopped working because the event name has changed in Bootstrap 3. Instead of "show" now it's "shown.bs.collapse".

--- app/assets/javascripts/modules/read_entry.js.coffee
+++ app/assets/javascripts/modules/read_entry.js.coffee
@@ -10,7 +10,7 @@
   #-------------------------------------------------------
   # Mark entry as read as soon as the user opens it
   #-------------------------------------------------------
-  $("body").on "show", "[data-entry-summary-id]", ->
+  $("body").on "shown.bs.collapse", "[data-entry-summary-id]", ->
     if opening_entry this
       update_entry_state_path = $(this).attr "data-entry-state-update-path"
       entry_id = $(this).attr "data-entry-summary-id"


Lodash dependency

Lodash dependency

--- src/adaptorMethods/update.coffee
+++ src/adaptorMethods/update.coffee
@@ -1,3 +1,4 @@
+_ = require 'lodash'
 sendAttributesForSaving = require './_redisObjectSave'

 removeIndexedSearchableString = (attr, words, id) ->


FIX : previous merge should have remove this line

--- client/app/components/compose.coffee
+++ client/app/components/compose.coffee
@@ -442,6 +442,7 @@
                 success(error, message) if _.isFunction success
                 return

+            @state.mailboxIDs = message.mailboxIDs
             @props.lastUpdate = message.date

             # Refresh URL


Hide views out of sight

Hide views out of sight

So that click targets are not visible

--- framer/Components/NavComponent.coffee
+++ framer/Components/NavComponent.coffee
@@ -198,13 +198,6 @@

 	_runTransition: (transition, direction, animate, from, to) =>

-		# @_runningTransition =
-		# 	transition: transition
-		# 	direction: direction
-		# 	animate: animate
-		# 	from: from
-		# 	to: to
-
 		@emit(Events.TransitionStart, from, to, {direction: direction, modal: @isModal})

 		# Start the transition with a small delay added so it only runs after all
@@ -213,7 +206,6 @@

 		Utils.delay 0, =>
 			transition[direction] animate, =>
-				# @_runningTransition = null
 				@emit(Events.TransitionEnd, from, to, {direction: direction, modal: @isModal})

 	_buildTransition: (transitionFunction, layerA, layerB, overlay) ->
@@ -224,27 +216,6 @@
 		# # Buld a new transtition object with empty states
 		transition = {}

-		# seen = @_seen
-
-		# layers =
-		# 	layerA: layerA
-		# 	layerB: layerB
-		# 	overlay: overlay
-
-		# for layerName, layer of layers
-
-		# 	continue unless (layer and template[layerName])
-
-		# 	throw Error("NavComponent.transition: #{layerName} needs a 'show' state") unless template[layerName].show
-		# 	throw Error("NavComponent.transition: #{layerName} needs a 'hide' state") unless template[layerName].hide
-
-		# 	transition.states[layerName] = new LayerStates(layer)
-		# 	transition.states[layerName].show = template[layerName].show
-		# 	transition.states[layerName].hide = template[layerName].hide
-
-		# 	delete transition.states[layerName].initial
-
-
 		# Add the forward function for this state to transition forward
 		transition.forward = (animate=true, callback) =>

@@ -252,13 +223,14 @@
 			options = {instant:!animate}

 			if overlay and template.overlay
+				overlay.visible = true
 				overlay.ignoreEvents = false
-				overlay.visible = true
 				overlay.placeBehind(layerB)
 				overlay.props = template.overlay.hide
 				animations.push(new Animation(overlay, template.overlay.show, options))

 			if layerA and template.layerA
+				layerA.visible = true
 				animations.push(new Animation(layerA, template.layerA.hide, options))

 			if layerB and template.layerB
@@ -269,23 +241,34 @@
 			group = new AnimationGroup(animations)
 			group.start()

+			group.once Events.AnimationEnd, ->
+				if layerA and template.layerA and not (overlay and template.overlay)
+					layerA.visible = false
+
 		transition.back = (animate=true, callback) ->

 			animations = []
 			options = {instant:!animate}

+			if overlay and template.overlay
+				overlay.visible = true
+				overlay.ignoreEvents = true
+				animations.push(new Animation(overlay, template.overlay.hide, options))
+
 			if layerA and template.layerA
+				layerA.visible = true
 				animations.push(new Animation(layerA, template.layerA.show, options))

 			if layerB and template.layerB
+				layerB.visible = true
 				animations.push(new Animation(layerB, template.layerB.hide, options))
-
-			if overlay and template.overlay
-				animations.push(new Animation(overlay, template.overlay.hide, options))
-				overlay.ignoreEvents = true

 			group = new AnimationGroup(animations)
 			group.start()
+
+			group.once Events.AnimationEnd, ->
+				if layerB and template.layerB
+					layerB.visible = false

 		return transition


fixed various typo

fixed various typo

--- src/arangodb.coffee
+++ src/arangodb.coffee
@@ -15,55 +15,55 @@
 generateConnObject = (settings) ->
   if settings.url
     parsed = url.parse settings.url
-
+
     generated = {}
     generated.protocol = 'http:'
     generated.hostname = (parsed.hostname or '127.0.0.1')
     generated.port     = (parsed.port or 8529)
-
+
     generated.auth = parsed.auth if parsed.auth?
-
+
     database = parsed.path[1..].split('/')[0] or 'loopback_db'
-
+
     dbUrl = url.format generated
   else
     obj = {}
     obj.protocol = 'http:'
     obj.hostname = (settings.host or '127.0.0.1')
     obj.port = (settings.port or 8529)
-
+
     obj.auth = "#{settings.username}:#{settings.password}" if settings.username and settings.password

     database = (settings.database or settings.db or 'loopback_db')
-
+
     dbUrl = url.format obj
-
+
   promise = (settings.promise or false)
-
+
   # TODO: add more arango specifig objects for a connection
-
+
   config = {
     url: dbUrl
     databaseName: database
     promise: promise
   }
-
+
   return config

 exports.generateConnObject = generateConnObject
 ###
   Initialize the ArangoDB connector for the given data source
-
+
   @param dataSource [DataSource] The data source instance
   @param callback [Function] The callback function
 ###
 initialize = (dataSource, callback) ->
   return if not ajs?
   dataSource.driver = ajs;
-
+
   settings = generateConnObject dataSource.settings
-
+
   dataSource.connector = new ArangoDBConnector settings, dataSource
   dataSource.connector.connect callback if callback?

@@ -72,7 +72,7 @@

 ###
   Loopback Arango Connector
-
+
   @author Navid Nikpour
 ###
 class ArangoDBConnector extends Connector
@@ -91,8 +91,8 @@
   #   obj.included?.apply(@)
   #   this
   #
-
-
+
+
   ###
     The constructor for ArangoDB connector
     @constructor
@@ -102,7 +102,7 @@
     @option settings port [Number] The port to connect with
     @option settings database/db [String] The database to connect with
     @option settings headers [Object] Object with header to include in every request
-
+
     @param dataSource [DataSource] The data source instance
   ###
   constructor: (settings, dataSource) ->
@@ -110,18 +110,18 @@
     # debug
     @debug = dataSource.settings.debug or debug.enabled
     debug 'constructor called' if @debug
-
-
+
+
     # link to datasource
     @dataSource = dataSource
-
-
+
+
     # Query builder
     @qb = require('aqb')
-
+
     @db = require('arangojs') @settings
     @api = @db.route '_api'
-
+
   # one file per functionality
   # @extend require('./CoreMixin')
   # =============
@@ -136,16 +136,16 @@
     debug 'connect called' if @debug
     process.nextTick () ->
       callback && callback null, @db
-
+
   ###
     Get the types of this connector

     @return [Array<String>] The types of connectors this connector belongs to
   ###
-  getTypes: () =>
+  getTypes: () =>
     return ['db', 'nosql', 'arangodb']

-
+
   ###
     The default Id type

@@ -165,7 +165,7 @@
   getModelClass: (model) =>
     return @_models[model]

-
+
   ###
     Get the collection name for a certain model name

@@ -176,10 +176,10 @@
   getCollectionName: (model) =>
     if @getModelClass(model).settings.options?.arangodb?
       model = @getModelClass(model).settings.options?.arangodb?.collection or model
-
+
     return model
-
-
+
+
   ###
     Converts the retrieved data from the database to JSON, based on the properties of a given model

@@ -201,11 +201,11 @@
           when key is '_id' then val.split('/')[0]
         delete data[key]
         continue
-
+
       # Buffer
       if properties[key]?.type is Buffer and val?
         data[key] = new Buffer(val, 'base64')
-
+
       # TODO: Look how to read ArangoDB binary data into a buffer
       # if properties[key]?.type is Buffer and val?
       #   #from binary
@@ -213,7 +213,7 @@
       #     // Convert the Binary into Buffer
       #     data[p] = data[p].read(0, data[p].length());
       #   ]
-
+
       # String
       # if properties[key]?.type is String and val?
       #   # from binary
@@ -221,15 +221,15 @@
       #     // Convert the Binary into String
       #     data[p] = data[p].toString();
       #   ]
-
+
       # Date
       if properties[key]?.type is Date and val?
         data[key] = new Date val
-
+
       # GeoPoint
       if properties[key]?.type is GeoPoint and val?
         data[key] = new GeoPoint { lat: val.lat, lng: val.lng }
-
+
       # TODO: still to come: Boolean, Number, Array and any arbitrary type

     return data
@@ -242,11 +242,11 @@
     @param model [String] The model name to look up the properties
     @param data [Object] The JSON object to transferred to the database

-    @return [Object] The converted data as an Plain Javascript Object
+    @return [Object] The converted data as an Plain Javascript Object
   ###
   toDatabase: (model, data) =>
     return null if not data?
-
+
     properties = @getModelClass(model).properties

     for key, val of data
@@ -257,11 +257,11 @@
           when key is '_id' then val.split('/')[0]
         delete data[key]
         continue
-
+
       # Buffer
       if properties[key]?.type is Buffer and val?
         data[key] = val.toString('base64')
-
+
       # Date
       if properties[key]?.type is Date and val?
         data[key] = new Date val
@@ -272,7 +272,7 @@
         data[key] = val

       # TODO: still to come: Boolean, Number, Array and any arbitrary type
-
+
     return data

@@ -306,23 +306,23 @@
   ###
   query: (query, bindVars, callback) =>
     debug 'query', query, bindVars if @debug
-
+
     if typeof bindVars is 'function'
       callback = bindVars
       bindVars = {}
-
+
     @db.query query, bindVars, (err, cursor) ->
       # workaround: when there is no error (e.g. wrong AQL syntax etc.) and no cursor, the authentication failed
       if not err? and cursor.length = 0
         authErr = new Error 'Authentication failed'
         callback authErr, null
-      else
+      else
         callback err, cursor
-
-
+
+
   ###
     Checks the version of the ArangoDB
-
+
     @param callback [Function] The calback function, called with a (possible) error object and the server versio
   ###
   getVersion: (callback) =>
@@ -333,7 +333,7 @@
         callback err if err
         @version = result
         callback null, @version
-
+
   # @extend require('./CrudMixin')
   # ==============
   # = CRUD Mixin =
@@ -351,7 +351,7 @@
     aql = qb.insert('@data').in('@@collection').returnNew('inserted')
     bindVars = [
       data: data,
-      collection: @getCollection(model)
+      collection: @getCollectionName(model)
     ]

     @query aql, bindVars, (err, result) ->
@@ -371,7 +371,7 @@
     @_updateOrCreate model, data, true, (err, result) ->
       callback err if err
       callback null, result.length
-
+
   ###
     Update if the model instance exists with the same id or create a new instance
     # TODO: change this to UPSERT AQL when 2.6 is out
@@ -396,18 +396,18 @@
       update_params = [ id: params.id, data: params.data, collection: params.collection ]
       update_query = db._query(update_aql, update_params).toArray()
       return update_result[0] if update_result.length > 0
-
+
       # if update failed we try to create it
       create_params = [ data: params.data, collection: params.collection ]
       create_result = db._query(create_aql, create_params).toArray()
-
+
       throw create_result if (create_result instanceof Error)
-
+
       return create_result[0] if create_result.length > 0
       return create_result

-    @transaction @getCollection(model), action, [ id: id, data: data, collection: @getCollection(model) ], (err, result) ->
+    @transaction @getCollectionName(model), action, [ id: id, data: data, collection: @getCollectionName(model) ], (err, result) ->
       callback err if err
       callback null, result

@@ -416,7 +416,7 @@
       callback err if err
       callback result[0] if result.length > 0
       callback result
-
+

   ###
     Check if a model instance exists by id
@@ -444,7 +444,7 @@

     aql = qb.for('retDoc').in('@@collection').filter(qb.eq("retDoc._key", '@id')).limit(1).return('retDoc')
     bindVars = [
-      collection: @getCollection(model),
+      collection: @getCollectionName(model),
       id: id
     ]

@@ -465,7 +465,7 @@

     aql = qb.for('removeDoc').in('@@collection').filter(qb.eq('removeDoc._key', '@id')).returnOld('removed')
     bindVars = [
-      collection: @getCollection(model)
+      collection: @getCollectionName(model)
       id: id
     ]

@@ -512,10 +512,10 @@
        - where[prop][op] = value: this is the long version and stands for "prop" "op" "value"
     ###
     for condProp, condValue of filter.where
-
+
       # correct if the conditionProperty falsely references to 'id'
-      condProp = '_key' if condProp is 'id'
-
+      condProp = '_key' if condProp is 'id'
+
       #  special case: if conditionValue is a Object (instead of a string or number) we have a conditionOperator
       if condValue and condValue.constructor.name is 'Object'
         #  condition operator is the only keys value, the new condition value is shifted one level deeper and can be a object with keys and values
@@ -526,55 +526,55 @@
         # and condition value is stringified for the comparison
         condOp = 'eq'
         condValue = condValue.toString()
-
+
       # special treatment for "and" and "or" operator, since there value is an array of conditions
       if condOp in ['and', 'or']
         # 'and' and 'or' have multiple conditions so we run buildWhere recursively on their array to
         if _.isArray condValue
           # condValue is an array of conditions so get the conditions from it via a recursive buildWhere call
           conditionalPart = @_filter2where model, condValue, returnVariable
-
+
           # add the condArray resp. the boundVars
           aqlArray.push qb[condOp].apply null, conditionalPart.aqlArray
           merge true, boundVars, conditionalPart.boundVars
-
+
       # If the value is not an array, fall back to regular fields
       cond2push = switch
         # number comparison
         when condOp in ['lte', 'lt', 'gte', 'gt', 'eq', 'neq']
-          qb[condOp] "#{returnVariable}.#{condProp}", "#{assignNewQueryVariable(condValue)}"
-
+          qb[condOp] "#{returnVariable}.#{condProp}", "#{assignNewQueryVariable(condValue)}"
+
         # range comparison
         when condOp is 'between'
           [qb.gte("#{returnVariable}.#{condProp}", "#{assignNewQueryVariable(condValue[0])}"),  qb.lte("#{returnVariable}.#{condProp}", "#{assignNewQueryVariable(condValue[1])}")]
-
+
         # string comparison
         when condOp is 'like'
           qb.not qb.LIKE "#{returnVariable}.#{condProp}", "#{assignNewQueryVariable(condValue)}"
         when condOp is 'nlike'
           qb.LIKE "#{returnVariable}.#{condProp}", "#{assignNewQueryVariable(condValue)}"
-
+
         # array comparison
         when condOp is 'nin'
           qb.not qb.in "#{returnVariable}.#{condProp}", "#{assignNewQueryVariable(condValue)}"
         when condOp is 'in'
           qb.in "#{returnVariable}.#{condProp}", "#{assignNewQueryVariable(condValue)}"
-
+
         # geo comparison (extra object)
         when 'near'
           # 'near' does not create a condition in the filter part, it returnes the lat/long pair
           # the query will be done from the querying method itself
           [lat, long] = condValue.split(',')
-          collection = @getCollection model
+          collection = @getCollectionName model
           if filter.limit?
             geoExpr = qb.NEAR collection, lat, long, filter.limit
           else
             geoExpr = qb.NEAR collection, lat, long
-
+
         #  if we don't have a matching operator or no operator at all (condOp = false) then use the equivalence operator
         else
           qb.eq "(#{returnVariable}.#{condProp}", "#{assignNewQueryVariable(condValue)}"
-
+
     return [
       aqlArray: aqlArray
       boundVars: boundVars
@@ -599,7 +599,7 @@
     # KEEP only the specified fields to include if includes not empty
     return qb.fn('KEEP') returnVariable, includes if includes.length > 0

-    # remove/UNSET the
+    # remove/UNSET the
     return qb.fn('UNSET') returnVariable, excludes if excludes.length > 0

@@ -702,9 +702,9 @@
   ###
   _filter2query: (model, filter) =>
     debug '_filter2query', model, filter if @debug
-
-
-    collection = @getCollection model
+
+
+    collection = @getCollectionName model
     collVariable = collection.charAt 0
     returnVariable = 'result'

@@ -719,7 +719,7 @@
     coll = if parts.where.geoObject? then parts.where.geoObject else '@@collection'
     aql = aql.in(coll)

-    # 2) use filter from where-part to filter, merge the boundVars from where into the global boundVars
+    # 2) use filter from where-part to filter, merge the boundVars from where into the global boundVars
     merge true, boundVars, parts.where.boundVars
     aql = aql.filter qb.and.apply null, parts.where.aqlArray

@@ -747,17 +747,17 @@
     @param [Function] [callback] Callback with (possible) error object and list of objects
   ###
   all: (model, filter, callback) =>
-    all_aql = @filter2query model, filter
+    all_aql = @_filter2query model, filter

     @query all_aql.aql, all_aql.boundVars, (err, result) ->
       callback err if err
-
+
       # filter include
       if filter.include?
         @getModelClass.model.include result, filter.include, callback
       else
         callback null, result
-
+

   ###
@@ -765,12 +765,12 @@

     @param [String] model The model name
     @param [Object] [where] The filter for where
-    @param [Function] [callback] Callback with (possible) error object and the number of affected objects
+    @param [Function] [callback] Callback with (possible) error object and the number of affected objects
   ###
   destroyAll: (model, where, callback) =>
     debug 'destroyAll', model, where if @debug

-    collection = @getCollection(model)
+    collection = @getCollectionName(model)
     collVariable = collection.charAt 0

     bindVars = [
@@ -803,7 +803,7 @@
   count: (model, callback, where) =>
     debug 'count', model, where if @debug

-    collection = @getCollection(model)
+    collection = @getCollectionName(model)
     collVariable = collection.charAt 0

     bindVars = [
@@ -837,7 +837,7 @@
   updateAttributes: (model, id, data, cb) =>
     debug 'updateAttributes', model, id, data if @debug

-    collection = @getCollection(model)
+    collection = @getCollectionName(model)
     collVariable = collection.charAt 0

     bindVars = [
@@ -867,7 +867,7 @@
     debug 'updateAll', model, where, data if @debug

     # TODO: FOR & FILTER (from id) & UPDATE (with data)
-    collection = @getCollection(model)
+    collection = @getCollectionName(model)
     collVariable = collection.charAt 0

     bindVars = [
@@ -888,7 +888,7 @@
     @query aql, bindVars, (err, result) ->
       callback err if err
       callback null, result.length
-
+
   # @extend require('./MigrationMixin')
   # ===================
   # = Migration Mixin =
@@ -902,22 +902,22 @@
       autoupdate: (models, cb) =>
         if @db
           debug 'autoupdate' if @debug
-
+
           if (not cb) and (typeof models is 'function')
             cb = models
             models = undefined
-
+
           # First argument is a model name
           models = [models] if typeof models is 'string'
-
+
           models = models or Object.keys @_models
-
+
           async.each( models, ((model, modelCallback) ->
             indexes = @_models[model].settings.indexes or []
             indexList = []
             index = {}
             options = {}
-
+
             if typeof indexes is 'object'
               for indexName, index of indexes
                 if index.keys
@@ -931,16 +931,16 @@
                     keys: index
                     options: options
                   }
-
+
                 indexList.push index
             else if Array.isArray indexes
               indexList = indexList.concat indexes
-
+
             for propIdx, property of @_models[model].properties
               if property.index
                 index = {}
                 index[propIdx] = 1
-
+
                 if typeof property.index is 'object'
                   # If there is a arangodb key for the index, use it
                   if typeof property.index.arangodb is 'object'
@@ -951,30 +951,30 @@
                   else
                     # If there isn't an  properties[p].index.mongodb object, we read the properties from  properties[p].index
                     options = property.index
-
+
                   options.background = true if options.background is undefined
-
+
                 # If properties[p].index isn't an object we hardcode the background option and check for properties[p].unique
                 else
                   options = { background: true }
-                  options.unique = true if property.unique
-
-
+                  options.unique = true if property.unique
+
+
                 indexList.push { keys: index, options: options }
-
+
             debug 'create indexes' if @debug
             async.each( indexList, ((index, indexCallback) ->
               debug 'ensureIndex' if @debug
-
+
               # TODO: ensureIndex for ArangoDB
               # self.collection(model).ensureIndex(index.fields || index.keys, index.options, indexCallback);
             ), modelCallback )
           ), cb)
         else
           @dataSource.once 'connected', () -> @autoupdate models, cb
-
-
-
+
+
+
       ###
         Perform automigrate for the given models. It drops the corresponding collections and calls ensureIndex

@@ -984,26 +984,26 @@
       automigrate: (models, cb) =>
         if @db
           debug 'automigrate' if @debug
-
+
           if (not cb) and (typeof models is 'function')
             cb = models
             models = undefined
-
+
           # First argument is a model name
           models = [models] if typeof models is 'string'
-
+
           async.each(models, ((model, modelCallback) ->
             debug "drop collection: #{model}"
             @db.dropCollection model, (err, collection) ->
               if err
                 #  For errors other than 'ns not found' (collection doesn't exist)
                 return modelcallback err if not (err.name is 'MongoError' and err.ok is 0 and err.errmsg is 'ns not found')
-
+
               # Recreate the collection
               debug "create collection: #{model}"
-
+
               @db.createCollection model, modelcallback
-
+
           ), ((err) ->
             return cb and cb err if err
             @autoupdate models, cb
@@ -1013,4 +1013,4 @@

 # require('util').inherits ArangoDBConnector, Connector

-exports.ArangoDBConnector = ArangoDBConnector+exports.ArangoDBConnector = ArangoDBConnector


Begin trying to add colors to theme

Begin trying to add colors to theme

--- nemesis.aroma.coffee
+++ nemesis.aroma.coffee
@@ -0,0 +1,55 @@
+module.exports =
+  author: "github.com/thomscode"
+  colorSpaceName: "sRGB"
+  gutterSettings:
+    background: "#073642"
+    divider: "#586e75"
+    foreground: "#839496"
+    selectionBackground: "#586e75"
+    selectionForeground: "#a6e22e"
+  name: "Nemesis"
+  semanticClass: "theme.dark.nemesis"
+  settings: [
+    {
+      settings:
+        activeGuide: "#9d550fb0"
+        background: "#222222"
+        bracketContentsForeground: "#f8f8f2a5"
+        bracketContentsOptions: "underline"
+        bracketsForeground: "#f8f8f2a5"
+        bracketsOptions: "underline"
+        caret: "#f8f8f0"
+        findHighlight: "#ffe792"
+        findHighlightForeground: "#000000"
+        foreground: "#f8f8f2"
+        invisibles: "#3b3a32"
+        lineHighlight: "#333333"
+        selection: "#444444"
+        selectionBorder: "#1c1c1c"
+        tagsOptions: "stippled_underline"
+    }
+    {
+      name: "Comment"
+      scope: "comment"
+      settings:
+        foreground: "#75715e"
+    }
+    {
+      name: "String"
+      scope: "string"
+      settings:
+        foreground: "#ff0000"
+    }
+    {
+      name: "JSON String"
+      scope: "meta.structure.dictionary.json string.quoted.double.json"
+      settings:
+        foreground: "#cfcfc2"
+    }
+    {
+      name: "Number"
+      scope: "constant.numeric"
+      settings:
+        foreground: "#be84ff"
+    }
+  ]


do not include private partners in exhibitors fetch

--- models/fair.coffee
+++ models/fair.coffee
@@ -55,6 +55,8 @@
     galleries = new @aToZCollection('show', 'partner')
     galleries.fetchUntilEnd
       url: "#{@url()}/partners"
+      data:
+        private_partners: false
       cache: true
       success: ->
         aToZGroup = galleries.groupByAlphaWithColumns 3


fix profile icon

fix profile icon

--- models/icon.coffee
+++ models/icon.coffee
@@ -5,10 +5,9 @@
 { Image } = require 'artsy-backbone-mixins'

 module.exports = class Icon extends Backbone.Model
-
   _.extend @prototype, Image(sd.SECURE_IMAGES_URL)

   urlRoot: "#{sd.ARTSY_URL}/api/v1/#{@profileId}/icon"

   imageUrl: ->
-    Image.imageUrl.apply(@, arguments).replace('.jpg', '.png')
+    Image(sd.SECURE_IMAGES_URL).imageUrl.apply(@, arguments).replace('.jpg', '.png')


use strict

use strict

--- frontend/app/js/player-service.coffee
+++ frontend/app/js/player-service.coffee
@@ -3,6 +3,7 @@
 PLAYER_STATE_CHANGE = "player:state_change"

 mod.factory "player", ["$rootScope", "mpdService", ($rootScope, mpdService) ->
+  'use strict'
   that = this
   # The public methods/data we expose
   api =


Add line numbers to error messages

Add line numbers to error messages

--- src/biscotto.coffee
+++ src/biscotto.coffee
@@ -240,14 +240,14 @@
                       parser.parseFile relativePath if _.some(SRC_DIRS, (dir) -> ///^#{dir}///.test(shortPath))
                     catch error
                       throw error if options.debug
-                      console.log "Cannot parse file #{ filename }: #{ error.message }"
+                      console.log "Cannot parse file #{ filename }@#{error.location.first_line}: #{ error.message }"
               else
                 if input.match /\._?coffee$/
                   try
                     parser.parseFile input
                   catch error
                     throw error if options.debug
-                    console.log "Cannot parse file #{ filename }: #{ error.message }"
+                    console.log "Cannot parse file #{ filename }@#{error.location.first_line}: #{ error.message }"

           if options.metadata
             @generateMetadata(package_json_path, parser, options)


Tweaking collision detection to be called properly

--- app/_src/components/blog/estimation-survey/collision.coffee
+++ app/_src/components/blog/estimation-survey/collision.coffee
@@ -64,6 +64,8 @@
 			 .attr("preserveAspectRatio", "xMinYMin meet")
 			 .attr("viewBox", "0 0 " + W + " " + H)
 			 .classed("svg-content", true)
+			 .attr("width", W)
+			 .attr("height", H);

 		@svg.selectAll("circle")
 			.data( NODES )
@@ -86,7 +88,7 @@
 			n = NODES.length

 			while (++i < n)
-				q.visit(() => collide(NODES[i]))
+				q.visit(collide(NODES[i]))

 			@svg.selectAll("circle")
 				.attr("cx", (d) -> d.x )
@@ -106,20 +108,19 @@
 		@svg.on("touchmove", () -> onInteraction(this) )

 		collide = (node) ->
-			r = node.r + 25
+			r = node.r
 			nx1 = node.x - r
 			nx2 = node.x + r
 			ny1 = node.y - r
 			ny2 = node.y + r
 			(quad, x1, y1, x2, y2) ->
-				`var r`
 				if quad.point and quad.point != node
 					x = node.x - (quad.point.x)
 					y = node.y - (quad.point.y)
 					l = Math.sqrt(x * x + y * y)
-					r = node.r + quad.point.r + 25
-					if l < r
-						l = (l - r) / l
+					minDistance = node.r + quad.point.r + 5
+					if l < minDistance
+						l = (l - minDistance) / l * .05
 						node.x -= x *= l
 						node.y -= y *= l
 						quad.point.x += x


Notifications now use backbone for navigation.

--- core/app/backbone/views/notification_view.coffee
+++ core/app/backbone/views/notification_view.coffee
@@ -2,6 +2,12 @@
   tagName: "li"
   className: "activity"
   template: "notifications/_generic_activity"
+  events:
+    'click a': 'click'
+
+  click: (e) ->
+    @trigger 'activityActivated'
+    return Backbone.View.prototype.defaultClickHandler(e)

   onRender: ->
     @$el.addClass "unread" if @model.get("unread") is true


Fix fetch pool

Fix fetch pool

--- assets/javascripts/views/fetch_posts_pool.js.coffee
+++ assets/javascripts/views/fetch_posts_pool.js.coffee
@@ -69,6 +69,7 @@
     pool = @pool.sortBy (i) -> i.published_at
     for i in [0...@num_new_posts]
       post = pool.pop()
+      @pool.collection.pop()
       @posts.unshift(post)
       TentStatus.Views.Post.insertNewPost(post, @$elements.posts_list, @postsFeedView)


Fix callback calls. See #650. Regression since #546

--- src/adapter.coffee
+++ src/adapter.coffee
@@ -77,7 +77,7 @@
                 callbacks = map.callbacks

                 if callbacks[keypath]
-                  cb() for cb in callbacks[keypath]
+                  cb() for cb in callbacks[keypath].slice() when cb in callbacks[keypath]
                 @observeMutations newValue, obj[@id], keypath

     unless callback in callbacks[keypath]


Use http.request instead of http.createClient. Closes #2

--- src/akismet.coffee
+++ src/akismet.coffee
@@ -16,7 +16,6 @@
   #            port: Akismet API server port, defaults to 80
   #            userAgent: The user agent string passed to Akismet API, defaults to 'Generic Node.js/1.0.0 | Akismet 2.4.0'
   #            charSet: The character set of comment texts, defaults to 'utf-8'
-  #            debug: set to true to print debug information, defaults to false
   constructor: (options) ->
     @blog = options.blog or ''
     @apiKey = options.apiKey
@@ -25,10 +24,6 @@
     @port = options.port or 80
     @userAgent = options.userAgent or 'Generic Node.js/1.0.0 | Akismet 2.4.0'
     @charset = options.charset or 'utf-8'
-    @debug = options.debug or false
-
-    @keyClient = null
-    @client = null

   # Verifies the key.
@@ -36,8 +31,7 @@
   # callback: err, isValid (true or false)
   verifyKey: (callback) ->
     callback or= () ->
-    @keyClient or= http.createClient @port, @host
-    @postRequest @keyClient, '/1.1/verify-key', { key: @apiKey, blog: @blog }, (err, status, headers, body) ->
+    @postRequest @host, '/1.1/verify-key', { key: @apiKey, blog: @blog }, (err, status, headers, body) ->
       callback err, (if status >= 200 and status < 300 and body is 'valid' then true else false)

@@ -49,8 +43,7 @@
     callback or= () ->
     options.blog = @blog
     options.user_agent = @userAgent
-    @client or= http.createClient @port, @endPoint
-    @postRequest @client, '/1.1/comment-check', options, (err, status, headers, body) ->
+    @postRequest @endPoint, '/1.1/comment-check', options, (err, status, headers, body) ->
       callback err, (if status >= 200 and status < 300 and body is 'true' then true else false)

@@ -62,8 +55,7 @@
     callback or= () ->
     options.blog = @blog
     options.user_agent = @userAgent
-    @client or= http.createClient @port, @endPoint
-    @postRequest @client, '/1.1/submit-spam', options, (err, status, headers, body) ->
+    @postRequest @endPoint, '/1.1/submit-spam', options, (err, status, headers, body) ->
       callback err

@@ -75,38 +67,39 @@
     callback or= () ->
     options.blog = @blog
     options.user_agent = @userAgent
-    @client or= http.createClient @port, @endPoint
-    @postRequest @client, '/1.1/submit-ham', options, (err, status, headers, body) ->
+    @postRequest @endpoint, '/1.1/submit-ham', options, (err, status, headers, body) ->
       callback err

   # Posts a request to the Akismet API server.
-  postRequest: (client, path, query, callback) ->
+  postRequest: (hostname, path, query, callback) ->

-    try
-      query = qs.stringify query
+    query = qs.stringify query

-      req = client.request 'POST', path, {
-          'host': client.host
-          'content-type': 'application/x-www-form-urlencoded; charset=' + @charset
-          'content-length': query.length
-          'user-agent': @userAgent
-        }
-      req.write query
-      req.end()
+    options =
+      'method': 'POST'
+      'hostname': hostname
+      'path': path
+      'port': @port
+      'headers':
+        'content-type': 'application/x-www-form-urlencoded; charset=' + @charset
+        'content-length': Buffer.byteLength(query)
+        'user-agent': @userAgent

-      if @debug
-        util.log 'Request: ' + util.inspect req
+    req = http.request options, (res) ->
+      res.setEncoding 'utf8'
+      res.on 'data', (body) ->
+        console.log "Query: " + query
+        console.log util.inspect options
+        console.log "Body: " + body
+        callback null, res.statusCode, res.headers, body

-      req.on 'response', (res) ->
-        if @debug
-          util.log 'Response: ' + util.inspect res
-        res.setEncoding 'utf-8'
-        res.on 'data', (body) ->
-          callback null, res.statusCode, res.headers, body
+    req.on 'error', (err) ->
+      callback err

-    catch err
-      callback err, null, null, null
+
+    req.write query
+    req.end()

 # Creates and returns a new Akismet client.


Release repo when destroy is called

Release repo when destroy is called

--- src/app/git.coffee
+++ src/app/git.coffee
@@ -56,7 +56,10 @@
       @statusTask.off()
       @statusTask = null

-    @repo = null
+    if @repo?
+      @repo.release()
+      @repo = null
+
     @unsubscribe()

   getWorkingDirectory: ->