Social: send recipients to backend for processing (adding/removing)

--- client/Social/Activity/views/privatemessagerecipientmodal.coffee
+++ client/Social/Activity/views/privatemessagerecipientmodal.coffee
@@ -40,12 +40,13 @@

   addParticipants: ->

-    recipients = (nickname for {profile:{nickname}} in @autoComplete.getSelectedItemData())
+    recipients = (socialApiId for {socialApiId} in @autoComplete.getSelectedItemData())

     return KD.showError message : 'Please add a recipient.'  unless recipients.length

+    options  = {channelId: @getData().id, accountIds:recipients}

-    KD.singletons.socialapi.channel.addParticipants (err, result) ->
+    KD.singletons.socialapi.channel.addParticipants options, (err, result) ->

       return KD.showError err  if err

@@ -78,4 +79,4 @@

   fetchAccounts: PrivateMessageModal::fetchAccounts

-  handleRecipientKeydown: PrivateMessageModal::handleRecipientKeydown+  handleRecipientKeydown: PrivateMessageModal::handleRecipientKeydown


SidebarSharedMachinesList: Hide if there is no machine.

--- client/app/lib/activity/sidebar/sidebarsharedmachineslist.coffee
+++ client/app/lib/activity/sidebar/sidebarsharedmachineslist.coffee
@@ -1,4 +1,5 @@
 SidebarMachineList = require './sidebarmachinelist'
+kd = require 'kd'

 module.exports = class SidebarSharedMachinesList extends SidebarMachineList
@@ -17,3 +18,5 @@
     data = shared.concat collaboration

     super options, data
+
+    @hide()  if data.length is 0


groups: minor copy change

groups: minor copy change

--- client/landing/app/Applications/Feeder.kdapplication/FeedController.coffee
+++ client/landing/app/Applications/Feeder.kdapplication/FeedController.coffee
@@ -140,7 +140,7 @@
     if @noItemFound? then @noItemFound.destroy()
     controller.scrollView.addSubView @noItemFound = new KDCustomHTMLView
       cssClass : "lazy-loader"
-      partial  : noItemFoundText or @getOptions().noItemFoundText or "There is no item found."
+      partial  : noItemFoundText or @getOptions().noItemFoundText or "There is no activity."
     @noItemFound.hide()

   # this is a temporary solution for a bug that


CustomScrolls: implements scrolltrack click to scroll

--- src/components/scrollview/scrolltrack.coffee
+++ src/components/scrollview/scrolltrack.coffee
@@ -12,6 +12,8 @@

     {@type} = @getOptions()

+    @scrollView = @getDelegate()
+
     @thumb = new KDScrollThumb
       cssClass : 'kdscrollthumb'
       type     : @type
@@ -25,6 +27,23 @@
     @addSubView @thumb

+  click: (event) ->
+
+    return  unless 'kdscrolltrack' in event.target.classList
+
+    if @type is 'vertical'
+      scrollHeight = @scrollView.getScrollHeight()
+      thumbHeight  = @thumb.getHeight()
+      @scrollView.scrollTo
+        top : (event.offsetY - thumbHeight / 2) / @getHeight() * scrollHeight
+
+    else
+      scrollWidth = @scrollView.getScrollWidth()
+      thumbWidth  = @thumb.getWidth()
+      @scrollView.scrollTo
+        left : (event.offsetX - thumbWidth / 2) / @getWidth() * scrollWidth
+
+
   show:->

     @getDelegate().emit 'ScrollTrackShown', @type


Support for older revisions of Node.js for CoffeeScript.run()

--- src/coffee-script.coffee
+++ src/coffee-script.coffee
@@ -54,7 +54,10 @@
   # Clear the module cache
   root.moduleCache = {} if root.moduleCache
   # Compile
-  root._compile exports.compile(code, options), root.filename
+  if path.extname(root.filename) isnt '.coffee' or require.extensions
+    root._compile exports.compile(code, options), root.filename
+  else
+    root._compile code, root.filename

 # Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
 # The CoffeeScript REPL uses this to run the input.


Remove deprecated api calls

Remove deprecated api calls

--- src/coffee/sphere.coffee
+++ src/coffee/sphere.coffee
@@ -4,45 +4,7 @@
 utils = require './utils'
 {_u} = require 'sphere-node-utils'

-#TODO refactor use with node connect GET / POST functions only (better use sphere-node-client / sync only)
-
-###
-# Retrieves asynchronously all categories from Sphere.
-#
-# @return {Object} If success returns promise with response body otherwise rejects with error message
-###
-exports.fetchCategories = (rest) ->
-  deferred = Q.defer()
-  rest.PAGED '/categories', (error, response, body) ->
-    if error
-      deferred.reject error
-    else
-      if response.statusCode isnt 200
-        message = "Error on fetch categories; \n Response body: '#{utils.pretty body}'"
-        deferred.reject message
-      else
-        deferred.resolve body
-  deferred.promise
-
-###
-# Creates asynchronously category in Sphere.
-#
-# @param {Object} payload Create category request as JSON
-# @return {Object} If success returns promise with success message otherwise rejects with error message
-###
-exports.createCategory = (rest, payload) ->
-  deferred = Q.defer()
-  rest.POST '/categories', payload, (error, response, body) ->
-    if error
-      deferred.reject "HTTP error on new category creation; Error: #{error}; Request body: \n #{utils.pretty payload} \n\n Response body: '#{utils.pretty body}'"
-    else
-      if response.statusCode isnt 201
-        message = "Error on new category creation; Request body: \n #{utils.pretty payload} \n\n Response body: '#{utils.pretty body}'"
-        deferred.reject message
-      else
-        message = 'New category created.'
-        deferred.resolve message
-  deferred.promise
+#TODO refactor use with node connect GET / POST functions only (better use sphere-node-client / sync)

 ###
 # Updates asynchronously category in Sphere.
@@ -61,64 +23,6 @@
         deferred.reject message
       else
         message = "Category with id: '#{data.id}' updated."
-        deferred.resolve message
-  deferred.promise
-
-###
-# Retrieves asynchronously all product types from Sphere.
-#
-# @return {Object} If success returns promise with response body otherwise rejects with error message
-###
-exports.fetchProductTypes = (rest) ->
-  deferred = Q.defer()
-  rest.PAGED '/product-types', (error, response, body) ->
-    if error
-      deferred.reject error
-    else
-      if response.statusCode isnt 200
-        message = "Error on fetch product-types; \n Response body: '#{utils.pretty body}'"
-        deferred.reject message
-      else
-        deferred.resolve body
-  deferred.promise
-
-###
-# Updates asynchronously product type in Sphere.
-#
-# @param {Object} data Update product type data
-# @return {Object} If success returns promise with success message otherwise rejects with error message
-###
-exports.updateProductType = (rest, data) ->
-  deferred = Q.defer()
-  rest.POST "/product-types/#{data.id}", data.payload, (error, response, body) ->
-    if error
-      deferred.reject "HTTP error on product type update; Error: #{error}; Request body: \n #{utils.pretty data} \n\n Response body: '#{utils.pretty body}'"
-    else
-      if response.statusCode isnt 200
-        message = "Error on product type update; Request body: \n #{utils.pretty data} \n\n Response body: '#{utils.pretty body}'"
-        deferred.reject message
-      else
-        message = "Product type with id: '#{data.id}' updated."
-        deferred.resolve message
-  deferred.promise
-
-###
-# Creates asynchronously product in Sphere.
-#
-# @param {Object} payload Create product request as JSON
-# @return {Object} If success returns promise with success message otherwise rejects with error message
-###
-exports.createProduct = (rest, payload) ->
-  deferred = Q.defer()
-  rest.POST '/products', payload, (error, response, body) ->
-    if error
-      deferred.reject "HTTP error on new product creation; Error: #{error}; Request body: \n #{utils.pretty payload} \n\n Response body: '#{utils.pretty body}'"
-    else
-      if response.statusCode isnt 201
-        message = "Error on new product creation; Request body: \n #{utils.pretty payload} \n\n Response body: '#{utils.pretty body}'"
-        deferred.reject message
-      else
-        message = 'New product created.'
         deferred.resolve message
   deferred.promise

@@ -236,20 +140,6 @@
 exports.queryOrders = (rest, where, expand) ->
   @get(rest, @pathWhere("/orders", where, null, [expand]))

-exports.updateOrder = (rest, id, data) ->
-  deferred = Q.defer()
-  rest.POST "/orders/#{id}", data, (error, response, body) ->
-    if error
-      deferred.reject "HTTP error on order update; Error: #{error}; Request body: \n #{utils.pretty data} \n\n Response body: #{utils.pretty body}"
-    else
-      if response.statusCode isnt 200
-        message = "Error on order update (status: #{response.statusCode}); Request body: \n #{utils.pretty data} \n\n Response body: #{utils.pretty body}"
-        deferred.reject message
-      else
-        message = "Order with id: '#{id}' updated."
-        deferred.resolve message
-  deferred.promise
-
 exports.addDelivery = (rest, order, deliveryItems) ->
   action =
     action: 'addDelivery'
@@ -282,28 +172,6 @@
     actions: actions

   @post(rest, "/orders/#{order.id}", json)
-
-exports.get = (rest, path) ->
-  d = Q.defer()
-  rest.PAGED path, (error, response, body) ->
-    if error
-      d.reject "HTTP error on GET; Request path: '#{path}'; Error: #{error}; Response body: #{utils.pretty body}"
-    else if response.statusCode is 200
-      d.resolve body
-    else
-      d.reject "GET failed. StatusCode: '#{response.statusCode}'; Request path: '#{path}'; \n Response body: \n #{utils.pretty body}"
-  d.promise
-
-exports.post = (rest, path, json) ->
-  d = Q.defer()
-  rest.POST path, json, (error, response, body) ->
-    if error
-      d.reject "HTTP error on POST; Request path: '#{path}'; Error: #{error}; \n Request body: \n #{utils.pretty json} \n\n Response body: \n #{utils.pretty body}"
-    else if response.statusCode is 200 or response.statusCode is 201
-      d.resolve body
-    else
-      d.reject "POST failed. StatusCode: '#{response.statusCode}'; Request path: '#{path}'; \n Request body: \n #{utils.pretty json} \n\n Response body: \n #{utils.pretty body}"
-  d.promise

 exports.pathWhere = (path, where, sort = [], expand = [], limit = 0, offset = 0) ->
   sorting = if not _.isEmpty(sort) then "&" + _.map(sort, (s) -> "sort=" + encodeURIComponent(s)).join("&") else ""
@@ -349,3 +217,25 @@
         Q(state)

     Q.all finalPromises
+
+exports.get = (rest, path) ->
+  d = Q.defer()
+  rest.PAGED path, (error, response, body) ->
+    if error
+      d.reject "HTTP error on GET; Request path: '#{path}'; Error: #{error}; Response body: #{utils.pretty body}"
+    else if response.statusCode is 200
+      d.resolve body
+    else
+      d.reject "GET failed. StatusCode: '#{response.statusCode}'; Request path: '#{path}'; \n Response body: \n #{utils.pretty body}"
+  d.promise
+
+exports.post = (rest, path, json) ->
+  d = Q.defer()
+  rest.POST path, json, (error, response, body) ->
+    if error
+      d.reject "HTTP error on POST; Request path: '#{path}'; Error: #{error}; \n Request body: \n #{utils.pretty json} \n\n Response body: \n #{utils.pretty body}"
+    else if response.statusCode is 200 or response.statusCode is 201
+      d.resolve body
+    else
+      d.reject "POST failed. StatusCode: '#{response.statusCode}'; Request path: '#{path}'; \n Request body: \n #{utils.pretty json} \n\n Response body: \n #{utils.pretty body}"
+  d.promise


added possibility to display username

added possibility to display username

--- modules/gui/lib/widgets/LoginForm.coffee
+++ modules/gui/lib/widgets/LoginForm.coffee
@@ -32,6 +32,14 @@

                                 input name:'pass', value:'', type:'password', placeholder:@data.strings.pass

+                        if user && _fn = @data.username
+                            if typeof _fn is 'function'
+                                username = _fn.apply @
+                            else
+                                username = user
+
+                            span class:'username', username
+
                         attr =
                             type: 'submit'
                             name:'button'
@@ -44,7 +52,7 @@

                         if @data.button?.title
                             attr.title = title
-
+
                         button attr, ->
                             if !@data.button?.title
                                 text title


Use ‘source’ option rather than internal state in Config spec

--- spec/config-spec.coffee
+++ spec/config-spec.coffee
@@ -75,7 +75,7 @@
       expect(observeHandler).toHaveBeenCalledWith 42

     describe "when the value equals the default value", ->
-      it "does not store the value", ->
+      it "does not store the value in the user's config", ->
         atom.config.setDefaults "foo",
           same: 1
           changes: 1
@@ -91,10 +91,12 @@
         atom.config.set('foo.null', undefined)
         atom.config.set('foo.undefined', null)
         atom.config.set('foo.sameObject', {b: 2, a: 1})
-        expect(atom.config.settings.foo).toEqual {changes: 2}
-
+
+        expect(atom.config.get("foo.same", sources: [atom.config.getUserConfigPath()])).toBeUndefined()
+
+        expect(atom.config.get("foo.changes", sources: [atom.config.getUserConfigPath()])).toBe 2
         atom.config.set('foo.changes', 1)
-        expect(atom.config.settings.foo).toEqual {}
+        expect(atom.config.get("foo.changes", sources: [atom.config.getUserConfigPath()])).toBeUndefined()

   describe ".getDefault(keyPath)", ->
     it "returns a clone of the default value", ->


spec: Nicer naming

spec: Nicer naming

--- spec/coordinator.coffee
+++ spec/coordinator.coffee
@@ -22,7 +22,7 @@
     coordinator = null
     first = null

-    describe ", transport=#{type}: ", () ->
+    describe "#{type} transport", () ->

       beforeEach (done) ->
         broker = transport.getBroker address


Add check to fix issuse #13

Add check to fix issuse #13

--- lib/debugger-view.coffee
+++ lib/debugger-view.coffee
@@ -191,7 +191,7 @@
       line = editor.bufferPositionForScreenPosition(position).row
       @toggleBreak(editor, line)
       selection = editor.selectionsForScreenRows(line, line + 1)[0]
-      selection.clear()
+      selection?.clear()

   handleEvents: ->
     @subscriptions = new CompositeDisposable


:bug: Fix a few typos

:bug: Fix a few typos

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -66,8 +66,9 @@
       else
         []
     @linter.getLinters().forEach (linter) =>
+      return if linter.modifiesBuffer isnt bufferModifying
       return unless Helpers.shouldTriggerLinter(linter, wasTriggeredOnChange, scopes)
-      currentLinter =>
+      currentLinter = =>
         return Promise.resolve().then( =>
           return linter.lint(@editor, Helpers)
         ).then((results) =>
@@ -83,7 +84,7 @@
         ToReturn.then -> currentLinter()
       else
         ToReturn.push(currentLinter())
-    Promises
+    ToReturn

   # This method sets or gets the lock status of given type
   lock: (wasTriggeredOnChange, value) ->


add edit-project-config-file command

add edit-project-config-file command

--- lib/flex-tool-bar.coffee
+++ lib/flex-tool-bar.coffee
@@ -55,7 +55,7 @@
     @storeProject()
     @storeGrammar()
     @registerTypes()
-    @registerCommand()
+    @registerCommands()
     @registerEvents()
     @registerWatch()
     @registerProjectWatch()
@@ -79,10 +79,10 @@
               text: 'Edit Config'
               onDidClick: => atom.workspace.open @configFilePath
             }]
-            if @projectToolbarConfigPath?
+            if @projectConfigFilePath?
               buttons.push [{
                 text: 'Edit Project Config'
-                onDidClick: => atom.workspace.open @projectToolbarConfigPath
+                onDidClick: => atom.workspace.open @projectConfigFilePath
               }]
             atom.notifications.addError 'Invalid toolbar config', {
               detail: err.stack or err.toString()
@@ -156,7 +156,7 @@

   resolveProjectConfigPath: ->
     persistent = atom.config.get 'flex-tool-bar.persistentProjectToolBar'
-    @projectToolbarConfigPath = null unless persistent and fs.isFileSync(@projectToolbarConfigPath)
+    @projectConfigFilePath = null unless persistent and fs.isFileSync(@projectConfigFilePath)
     relativeProjectConfigPath = atom.config.get 'flex-tool-bar.toolBarProjectConfigurationFilePath'
     editor = atom.workspace.getActivePaneItem()
     file = editor?.buffer?.file or editor?.file
@@ -166,20 +166,24 @@
         if file.getParent().path.includes(pathToCheck)
           pathToCheck = path.join pathToCheck, relativeProjectConfigPath
           if fs.isFileSync(pathToCheck)
-            @projectToolbarConfigPath = pathToCheck
+            @projectConfigFilePath = pathToCheck
           else
             found = fs.resolve pathToCheck, 'toolbar', ['cson', 'json5', 'json', 'js', 'coffee']
-            @projectToolbarConfigPath = found if found
-
-    if @projectToolbarConfigPath is @configFilePath
-      @projectToolbarConfigPath = null
-
-    return true if @projectToolbarConfigPath
-
-  registerCommand: ->
+            @projectConfigFilePath = found if found
+
+    if @projectConfigFilePath is @configFilePath
+      @projectConfigFilePath = null
+
+    return true if @projectConfigFilePath
+
+  registerCommands: ->
     @subscriptions.add atom.commands.add 'atom-workspace',
       'flex-tool-bar:edit-config-file': =>
         atom.workspace.open @configFilePath if @configFilePath
+
+    @subscriptions.add atom.commands.add 'atom-workspace',
+      'flex-tool-bar:edit-project-config-file': =>
+        atom.workspace.open @projectConfigFilePath if @projectConfigFilePath

   registerEvents: ->
     @subscriptions.add atom.packages.onDidActivateInitialPackages  =>
@@ -210,9 +214,9 @@
       @watcherList.push watcher

   registerProjectWatch: ->
-    if @projectToolbarConfigPath and @watchList.indexOf(@projectToolbarConfigPath) < 0
-      @watchList.push @projectToolbarConfigPath
-      watcher = chokidar.watch @projectToolbarConfigPath
+    if @projectConfigFilePath and @watchList.indexOf(@projectConfigFilePath) < 0
+      @watchList.push @projectConfigFilePath
+      watcher = chokidar.watch @projectConfigFilePath
         .on 'change', (event, filename) =>
           @reloadToolbar(@reloadToolBarNotification)
       @watcherList.push watcher
@@ -289,10 +293,10 @@
           text: 'Edit Config'
           onDidClick: => atom.workspace.open @configFilePath
         }]
-        if @projectToolbarConfigPath?
+        if @projectConfigFilePath?
           buttons.push [{
             text: 'Edit Project Config'
-            onDidClick: => atom.workspace.open @projectToolbarConfigPath
+            onDidClick: => atom.workspace.open @projectConfigFilePath
           }]
         atom.notifications.addError 'Invalid toolbar config', {
           detail: btnErrors.join '\n\n'
@@ -333,27 +337,27 @@
         config = CSON.requireCSONFile @configFilePath
         @removeCache(@configFilePath)

-    if @projectToolbarConfigPath
-      ext = path.extname @projectToolbarConfigPath
+    if @projectConfigFilePath
+      ext = path.extname @projectConfigFilePath

       switch ext
         when '.js', '.coffee'
-          projConfig = require(@projectToolbarConfigPath)
-          @removeCache(@projectToolbarConfigPath)
+          projConfig = require(@projectConfigFilePath)
+          @removeCache(@projectConfigFilePath)

         when '.json'
-          projConfig = require @projectToolbarConfigPath
-          @removeCache(@projectToolbarConfigPath)
+          projConfig = require @projectConfigFilePath
+          @removeCache(@projectConfigFilePath)

         when '.json5'
           require 'json5/lib/require'
-          projConfig = require @projectToolbarConfigPath
-          @removeCache(@projectToolbarConfigPath)
+          projConfig = require @projectConfigFilePath
+          @removeCache(@projectConfigFilePath)

         when '.cson'
           CSON = require 'cson'
-          projConfig = CSON.requireCSONFile @projectToolbarConfigPath
-          @removeCache(@projectToolbarConfigPath)
+          projConfig = CSON.requireCSONFile @projectConfigFilePath
+          @removeCache(@projectConfigFilePath)

       for i of projConfig
         config.push projConfig[i]


fix #55 providing api as required by atom/metrics package

--- lib/hg-repository.coffee
+++ lib/hg-repository.coffee
@@ -221,6 +221,12 @@
   # * `path` (optional) {String} path in the repository to get this information
   #   for, only needed if the repository has submodules.
   getOriginUrl: (path) -> null
+
+  # Public: Returns the origin url of the repository.
+  #
+  # * `path` (optional) {String} path in the repository to get this information
+  #   for, only needed if the repository has submodules.
+  getOriginURL: (path) -> null

   # Public: Returns the upstream branch for the current HEAD, or null if there
   # is no upstream branch for the current HEAD.


Fix and close #40

Fix and close #40

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -173,7 +173,20 @@
         message.text = "#{code} #{message.text}"
         line = message.range[0][0]
         col = message.range[0][1]
-        message.range = helpers.rangeFromLineNumber(textEditor, line, col)
+        editorLine = textEditor.buffer.lines[line]
+        if not editorLine or not editorLine.length
+          colEnd = 0
+        else
+          colEnd = editorLine.indexOf(' ', col+1)
+          if colEnd == -1
+            colEnd = editorLine.length
+          else
+            colEnd = 3 if colEnd - col < 3
+            colEnd = if colEnd < editorLine.length then colEnd else editorLine.length
+        message.range = [
+          [line, col]
+          [line, colEnd]
+        ]
         message


"Add check to verify that changed plunks have a minimum of one file"

--- lib/plunker/index.coffee
+++ lib/plunker/index.coffee
@@ -164,19 +164,24 @@

       plunk.files = new_files

-    # Update index
-    plunk.files[plunk.index] or plunk.index = do ->
-      filenames = _.keys(plunk.files)
-
-      if "index.html" in filenames then "index.html"
-      else
-        html = _.filter filenames, (filename) -> /.html?$/.test(filename)
-
-        if html.length then html[0]
-        else filenames[0]
+    if _.keys(plunk.files).length <= 0 then errors.push
+      message: "Minimum of 1 file required"
+      field: "files"
+
+    else
+      # Update index
+      plunk.files[plunk.index] or plunk.index = do ->
+        filenames = _.keys(plunk.files)
+
+        if "index.html" in filenames then "index.html"
+        else
+          html = _.filter filenames, (filename) -> /.html?$/.test(filename)
+
+          if html.length then html[0]
+          else filenames[0]

-    if errors.length then next(errors)
+    if errors.length then next {message: "Validation failed", errors: errors}
     else next(null, plunk)


Bug fix

Bug fix

--- lib/relations/one.coffee
+++ lib/relations/one.coffee
@@ -69,7 +69,7 @@
       return if key is @virtual_id_accessor then related_model.id else related_model

     # asynchronous path, needs load
-    if callback and not @isVirtual() and not @manual_fetch and not (is_loaded = model.isLoaded(@key) or not model.id) # already loaded or not loadable)
+    if callback and not @isVirtual() and not @manual_fetch and not (is_loaded = model.isLoaded(@key)) # already loaded
       @cursor(model, key).toJSON (err, json) =>
         return callback(err) if err
         model.setLoaded(@key, true)
@@ -219,12 +219,14 @@
       if @type is 'belongsTo'
         query.$zero = true unless query.id = model.attributes[@key]?.id
       else
+        throw new Error 'Cannot create cursor for non-loaded model' unless model.id
         query[@reverse_relation.foreign_key] = model.id
     else
       # json
       if @type is 'belongsTo'
         query.$zero = true unless query.id = model[@foreign_key]
       else
+        throw new Error 'Cannot create cursor for non-loaded model' unless model.id
         query[@reverse_relation.foreign_key] = model.id

     query.$values = ['id'] if key is @virtual_id_accessor


Handle permissions errors with errors.handle

--- lib/resin/cli/cli.coffee
+++ lib/resin/cli/cli.coffee
@@ -3,6 +3,7 @@
 pluralize = require('pluralize')
 indefiniteArticle = require('indefinite-article')
 log = require('../log/log')
+errors = require('../errors/errors')
 cliPermissions = require('./cli-permissions')

 exports.getArgument = (name) ->
@@ -25,6 +26,9 @@
 	return permissionFunction(action, onError)

 exports.addCommand = (options = {}) ->
+
+	_.defaults options,
+		onError: errors.handle

 	if options.permission?
 		action = applyPermissions(options.permission, options.action, options.onError)


Call getLoadedPackages() on atom.packages

--- lib/settings-view.coffee
+++ lib/settings-view.coffee
@@ -95,4 +95,4 @@
       finally
         callback()

-    async.each atom.getLoadedPackages(), iterator, finishedCallback
+    async.each atom.packages.getLoadedPackages(), iterator, finishedCallback


Removed test error message from sign in modal.

--- public/src/controllers.coffee
+++ public/src/controllers.coffee
@@ -56,7 +56,6 @@
     }

   showSignInModal: () ->
-    @error.signIn = 'Unexpected Error occured!'
     @signInModal.$promise.then(@signInModal.show);

   signIn: () ->


better mock engine implementation

better mock engine implementation

--- js/src/engine/MockKryptnosticEngine.coffee
+++ js/src/engine/MockKryptnosticEngine.coffee
@@ -1,14 +1,17 @@
 define 'kryptnostic.mock.fhe-engine', [
   'require'
+  'kryptnostic.binary-utils'
 ], (require) ->
+
+  BinaryUtils = require 'kryptnostic.binary-utils'

   # explode a string to a binary array.
   explode = (string) ->
-    return new Uint8Array(_.map(string, (c) -> c.charCodeAt()))
+    return BinaryUtils.stringToUint8(string)

   # collapse a binary array into a string.
-  implode = (uint8array) ->
-    uint8array.map((byte) -> String.fromCharCode(byte)).join()
+  collapse = (uint8) ->
+    return BinaryUtils.uint8ToString(uint8)

   #
   # Stand-in for the FHE engine which returns mocked values.
@@ -57,11 +60,11 @@

     # produce an encrypted search term, which can be submitted as a query.
     getEncryptedSearchTerm: (token) ->
-      return explode('search.token')
+      return explode('search.token.' + collapse(token))

     # compute address on the server for a token
     getTokenAddress: (token, documentKey) ->
-      return explode('search.address')
+      return explode('search.address.' + collapse(token))

     # sharing
     # =======


(Temporarily?) turn off notifications of occupancy control state transitions

--- lib/controls/OccupancySensorControl.coffee
+++ lib/controls/OccupancySensorControl.coffee
@@ -5,10 +5,11 @@
     occupancy = @getConnectionTarget('occupancySensor')
     occupied: occupancy?.getAspect('occupancySensor').getDatum('value')

-  describeStateTransition: (before, after) ->
-    if after.occupied == true
-      'became occupied'
-    else if after.occupied == false
-      'became unoccupied'
-    else
-      null+# Super chatty and not useful
+#  describeStateTransition: (before, after) ->
+#    if after.occupied == true
+#      'became occupied'
+#    else if after.occupied == false
+#      'became unoccupied'
+#    else
+#      null


:bug: Fix plugin broken by latest highlight-selected update

Fixes #8, Closes #7

--- lib/minimap-highlight-selected-view.coffee
+++ lib/minimap-highlight-selected-view.coffee
@@ -11,7 +11,7 @@

     getActiveTextEditor: -> @getActiveMinimap()?.getTextEditor()

-    ['markBufferRange', 'scanInBufferRange', 'getEofBufferPosition', 'getSelections', 'getLastSelection', 'bufferRangeForBufferRow', 'getTextInBufferRange', 'onDidChangeSelectionRange'].forEach (key) ->
+    ['markBufferRange', 'scanInBufferRange', 'getEofBufferPosition', 'getSelections', 'getLastSelection', 'bufferRangeForBufferRow', 'getTextInBufferRange', 'onDidAddSelection', 'onDidChangeSelectionRange'].forEach (key) ->
       FakeEditor::[key] = -> @getActiveTextEditor()?[key](arguments...)

     ['decorateMarker'].forEach (key) ->


created app.coffee as a content script > extremely minimal implementation of the phishing detection

--- ext/app/app.coffee
+++ ext/app/app.coffee
@@ -1,32 +1,54 @@
 'use strict'

 # Declare app level module which depends on filters, and services
-App = angular.module('app', [
-  'ngCookies'
-  'ngResource'
-  'ngRoute'
-  'app.controllers'
-  'app.directives'
-  'app.filters'
-  'app.services'
-  'partials'
-])
+#App = angular.module('app', [
+  #'ngCookies'
+  #'ngResource'
+  #'ngRoute'
+  #'app.controllers'
+  #'app.directives'
+  #'app.filters'
+  #'app.services'
+  #'partials'
+#])

-App.config([
-  '$routeProvider'
-  '$locationProvider'
+#App.config([
+  #'$routeProvider'
+  #'$locationProvider'

-($routeProvider, $locationProvider, config) ->
+#($routeProvider, $locationProvider, config) ->

-  $routeProvider
+  #$routeProvider

-    .when('/todo', {templateUrl: '/partials/todo.html'})
-    .when('/view1', {templateUrl: '/partials/partial1.html'})
-    .when('/view2', {templateUrl: '/partials/partial2.html'})
+    #.when('/todo', {templateUrl: '/partials/todo.html'})
+    #.when('/view1', {templateUrl: '/partials/partial1.html'})
+    #.when('/view2', {templateUrl: '/partials/partial2.html'})

-    # Catch all
-    .otherwise({redirectTo: '/todo'})
+    ## Catch all
+    #.otherwise({redirectTo: '/todo'})

-  # Without server side support html5 must be disabled.
-  $locationProvider.html5Mode(false)
-])
+  ## Without server side support html5 must be disabled.
+  #$locationProvider.html5Mode(false)
+#])
+
+url = new URL(window.location.href).hostname
+
+phishing_template = """
+
+

+
 Phishing Alert !!!

+
This is a known phishing website! you should not use it!

+     ignore  |
+     learn more
+

+"""
+
+verify_success = (response)->
+  if response.phishy
+    $('body').html(phishing_template)
+
+verify_error = (response)->
+  console.log response.responseJSON
+
+api = new PhisherApi()
+api.verify url: url, success: verify_success, error: verify_error


finally put #2333 to rest by resorting to using indirect eval

related: #1772, #1776; we're relying on the underlying engine having the
string escaping behaviour we want instead of implementing it manually.

--- nodes.coffee
+++ nodes.coffee
@@ -802,33 +802,15 @@
   compileNode: (o) ->
     props = @properties
     propNames = []
-    normaliseString = (s) ->
-      quoteNormalised = switch s[0]
-        when '"' then s[1...-1].replace /\\"/g, '"'
-        when "'" then s[1...-1].replace /\\'/g, "'"
-      escapeNormalised = quoteNormalised.replace /\\x([0-9a-f]{2})|\\u([0-9a-f]{4})/ig, (match, h, u) ->
-        String.fromCharCode parseInt (h ? u), 16
-      escapeNormalised.replace /\\([\s\S])/g, (match, c) ->
-        {0:"\0", b:"\b", t:"\t", n:"\n", v:"\v", f:"\f", r:"\r", "\\":"\\", "\n":""}[c] ? c
-    isDuplicate = (x) ->
-      mx = x.match /^['"]/
-      (y) ->
-        return true if y is x or +y is +x
-        my = y.match /^['"]/
-        if mx and my
-          return true if normaliseString(x) is normaliseString y
-        else if mx
-          return true if y is normaliseString x
-        else if my
-          return true if x is normaliseString y
-        false
+    normalise = (v) -> if IDENTIFIER.test v then v else "" + do Function "return #{v}"
     for prop in @properties
       prop = prop.variable if prop.isComplex()
       if prop?
         propName = prop.unwrapAll().value.toString()
-        if some.call propNames, isDuplicate propName
+        normalisedPropName = normalise propName
+        if normalisedPropName in propNames
           throw SyntaxError "multiple object literal properties named \"#{propName}\""
-        propNames.push propName
+        propNames.push normalisedPropName
     return (if @front then '({})' else '{}') unless props.length
     if @generated
       for node in props when node instanceof Value


Clarify how auto-detection works

Clarify how auto-detection works

--- src/config-schema.coffee
+++ src/config-schema.coffee
@@ -177,7 +177,7 @@
         type: 'string'
         default: 'auto'
         enum: ['auto', 'soft', 'hard']
-        description: 'Determine character inserted when Tab key is pressed. Possible values: "auto", "soft" and "hard". When set to "soft" or "hard", soft tabs (spaces) or hard tabs (tab characters) are used. When set to "auto", the editor auto-detects the tab type based on the contents of the buffer, or uses the value of the Soft Tabs config setting if auto-detection fails.'
+        description: 'Determine character inserted when Tab key is pressed. Possible values: "auto", "soft" and "hard". When set to "soft" or "hard", soft tabs (spaces) or hard tabs (tab characters) are used. When set to "auto", the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.'
       softWrapAtPreferredLineLength:
         type: 'boolean'
         default: false


should check to see if package exists before trying to uninstall it

--- src/uninstall.coffee
+++ src/uninstall.coffee
@@ -66,13 +66,21 @@
       try
         unless options.argv.dev
           packageDirectory = path.join(packagesDirectory, packageName)
-          packageVersion = @getPackageVersion(packageDirectory)
-          fs.removeSync(packageDirectory)
-          if packageVersion
-            uninstallsToRegister.push({packageName, packageVersion})
+          if fs.existsSync(packageDirectory)
+            packageVersion = @getPackageVersion(packageDirectory)
+            fs.removeSync(packageDirectory)
+            if packageVersion
+              process.stdout.write "packVersion"
+              uninstallsToRegister.push({packageName, packageVersion})
+          else
+            throw new Error("Does not exist")

         if options.argv.hard or options.argv.dev
-          fs.removeSync(path.join(devPackagesDirectory, packageName))
+          packageDirectory = path.join(devPackagesDirectory, packageName)
+          if fs.existsSync(packageDirectory)
+            fs.removeSync(packageDirectory)
+          else
+            throw new Error("Does not exist")

         @logSuccess()
       catch error


deleting values from columns with an empty header

--- dkobo/koboform/static/js/xlform/XLF.model.row.coffee
+++ dkobo/koboform/static/js/xlform/XLF.model.row.coffee
@@ -21,6 +21,10 @@
   comparator: (m)-> m.ordinal

 class XLF.Row extends XLF.BaseModel
+  constructor: (attributes={}, options={})->
+    for key, val of attributes when key is ""
+      delete attributes[key]
+    super(attributes, options)
   initialize: ->
     ###
     The best way to understand the @details collection is


:memo: doc parameters in workspace.coffee

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -48,9 +48,9 @@

   # Public: Asynchronously opens a given a filepath in Atom.
   #
-  # * filePath: A file path
-  # * options
-  #   + initialLine: The buffer line number to open to.
+  # filePath - A {String} file path.
+  # options  - An options {Object} (default: {}).
+  #   :initialLine - The buffer line number to open to.
   #
   # Returns a promise that resolves to the {Editor} for the file URI.
   open: (filePath, options={}) ->


payment: expose new apis to client

payment: expose new apis to client

--- client/app/lib/payment/paymentcontroller.coffee
+++ client/app/lib/payment/paymentcontroller.coffee
@@ -27,6 +27,15 @@
       callback err, result

+  subscribeGroup: (token, callback) ->
+
+    params = { token }
+
+    @api().subscribeGroup params, (err, plan) =>
+      callback err, plan
+      @emit 'GroupPlanUpdated'  unless err
+
+
   subscriptions: (callback) ->

     # return plan as 'koding' on default environment
@@ -47,6 +56,9 @@
       card = null  if isNoCard card

       return callback err, card
+
+
+  fetchGroupCreditCard: (callback) -> @api().fetchGroupCreditCard callback

   canUserPurchase: (callback) -> @api().canUserPurchase callback


video: fix docs

video: fix docs

--- client/app/lib/videocollaboration/helper.coffee
+++ client/app/lib/videocollaboration/helper.coffee
@@ -9,10 +9,10 @@
 ###*
  * Helper utility to be able to pass a fake publisher to the events. Events
  * mostly don't care about OpenTok specific videoData, so it being `null`
- * shouldn't affect anything, so be careful when you are passing
+ * shouldn't affect anything, but be careful when you are passing
  * `ParticipantType.Participant` instances around.
  *
- * @return {object} publisher - a fake object imitates `ParticipantType.Publisher`
+ * @return {object} publisher - a fake object mimics `ParticipantType.Publisher`
 ###
 defaultPublisher = ->
   nick      : getNick()


make it simple

make it simple

--- client/landing/app/CommonViews/FormViews.coffee
+++ client/landing/app/CommonViews/FormViews.coffee
@@ -338,8 +338,8 @@

         joinedTags   = plainNewTags.concat plainOldTags

-        # log newTags, oldTags, joinedTags
-        @memberData.addTags 'skillTags', newTags, (err)=>
+        # log formData.skillTags, newTags, oldTags, joinedTags
+        @memberData.addTags formData.skillTags, (err)=>
           if err
             log "An error occured:", err
           else


add create method to rounds store

add create method to rounds store

--- src/node_modules/rounds-store/rounds-store.coffee
+++ src/node_modules/rounds-store/rounds-store.coffee
@@ -4,6 +4,11 @@

   get: (round_id) ->
     Restangular.one('rounds', round_id).get()
+    .then (round) ->
+      new RoundModel(round.plain())
+
+  create: (round) ->
+    Restangular.all('rounds').post(round)
     .then (round) ->
       new RoundModel(round.plain())


Added more format unit tests

Added more format unit tests

--- test/format_test.coffee
+++ test/format_test.coffee
@@ -10,7 +10,126 @@
     ]

     formatted = chalkboard.format parsed, header: true
-    expected = '\nhello\n===\n\nwah\n---\n\nType: `function`  \n\n'
+    expected  = '\nhello\n===\n\nwah\n---\n\nType: `function`  \n\n'
     test.equal formatted, expected, "Should only format the first section"

-    test.done()+    test.done()
+
+  "rendering private functions/variables": (test) ->
+    test.expect 2
+
+    parsed = [
+      {chalk: "function", name: "Test", access: "private"}
+    ]
+
+    formatted = chalkboard.format parsed, private: false
+    test.equal formatted, "", "Should not render private function"
+
+    formatted2 = chalkboard.format parsed, private: true
+    expected   = "\nTest\n===\n### Access\nprivate\n"
+    test.equal formatted2, expected, "Should render private function"
+
+    test.done()
+
+  "depcreated": (test) ->
+    test.expect 1
+
+    parsed = [
+      {chalk:"overview", name:"hello"}
+      {chalk:"function", name: "Test", deprecated: true}
+    ]
+
+    formatted = chalkboard.format parsed
+    expected  = "\nhello\n===\n\nTest (Deprecated)\n---\n\n"
+
+    test.equal formatted, expected, "Should indicate function as deprecated"
+    test.done()
+
+  "header with url": (test) ->
+    test.expect 2
+
+    parsed = [
+      {chalk:"overview", name:"Hello", url: "http://www.example.com"}
+    ]
+
+    parsed2 = [
+      {chalk:"overview", name:"Hello"}
+    ]
+
+    formatted = chalkboard.format parsed
+    expected  = "\n[Hello](http://www.example.com)\n===\n"
+    test.equal formatted, expected, "Should included url with the header name"
+
+    formatted2 = chalkboard.format parsed2
+    expected2  = "\nHello\n===\n"
+    test.equal formatted2, expected2, "Should render header name without url"
+
+    test.done()
+
+  "description": (test) ->
+    test.expect 1
+
+    parsed = [
+      {chalk:"overview", name:"Test", description:"Just another description"}
+    ]
+
+    formatted = chalkboard.format parsed
+    expected  = "\nTest\n===\nJust another description  \n"
+    test.equal formatted, expected, "Should render description without title"
+
+    test.done()
+
+  "type": (test) ->
+    test.expect 2
+
+    parsed = [
+      {chalk: "overview", name:"Test", type:["one"]}
+    ]
+
+    formatted = chalkboard.format parsed
+    expected  = "\nTest\n===\nType: `one`  \n\n"
+    test.equal formatted, expected, "Should render one type"
+
+    parsed2 = [
+      {chalk: "overview", name:"Test", type:["one", "two"]}
+    ]
+
+    formatted2 = chalkboard.format parsed2
+    expected2  = "\nTest\n===\nType: `one, two`  \n\n"
+    test.equal formatted2, expected2, "Should render both types"
+
+    test.done()
+
+  "version": (test) ->
+    test.expect 1
+
+    parsed = [
+      {chalk: "overview", name:"Test", version: "1.0.0"}
+    ]
+
+    formatted = chalkboard.format parsed
+    expected  = "\nTest\n===\nVersion: `1.0.0`  \n\n"
+    test.equal formatted, expected, "Should render version"
+
+    test.done()
+
+  "author and email": (test) ->
+    test.expect 2
+
+    parsed = [
+      {chalk: "overview", name:"Test", author:"Adrian", email: "test@example.com"}
+    ]
+
+    formatted = chalkboard.format parsed
+    expected  = "\nTest\n===\n## Author\nAdrian (test@example.com)\n"
+    test.equal formatted, expected, "Should render author and email"
+
+    parsed2 = [
+      {chalk: "overview", name:"Test", author:"Adrian"}
+    ]
+
+    formatted2 = chalkboard.format parsed2
+    expected2  = "\nTest\n===\n## Author\nAdrian\n"
+    test.equal formatted2, expected2, "Should render author"
+
+    test.done()


CLI output is different on windows for tests.

Just the forward slashes. Should fix it.

--- test/spec/system.coffee
+++ test/spec/system.coffee
@@ -1,8 +1,10 @@
 fs = require "fs"
+os = require "os"
 path = require "path"
 chai = require "chai"
 pkg = require "./../../package"
 system = require "./../helpers/system"
+on_win = os.platform() == "win32"
 expect = chai.expect

 FIXTURES = path.join __dirname, "..", "fixtures"
@@ -32,7 +34,13 @@
 CLI_OUTPUT_TEST_JS_TS_DIR_COLOR = path
   .join FIXTURES, "cli_output", "test.dir.color.txt"

-read = (path) -> fs.readFileSync(path).toString()
+cli_output = (path) ->
+  data = fs.readFileSync(path).toString()
+
+  if on_win
+    data.replace(/\\/g, "/")
+  else
+    data

 # TODO: consider testing more in depth for false positives
 #       -> currently these tests cover a lot of it indirectly
@@ -45,7 +53,7 @@
       system.exec cmd,
         (error, stdout, stderr) ->
           expect(error.code).to.eql 0
-          expect(stdout).to.eql(read CLI_OUTPUT_TEST_JS)
+          expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_JS)
           done()

     describe "es modules", ->
@@ -56,7 +64,7 @@
           system.exec cmd,
             (error, stdout, stderr) ->
               expect(error.code).to.eql 0
-              expect(stdout).to.eql(read CLI_OUTPUT_TEST_ES_MODULES)
+              expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_ES_MODULES)
               done()

       describe "when estype is set as script", ->
@@ -68,7 +76,7 @@
               expect(error.code).to.eql 1
               expect(stderr).to.match /esprima/i
               expect(stderr).to.match /line 1: unexpected token/i
-              expect(stdout).to.eql(read CLI_OUTPUT_TEST_ES_MODULES_FAIL)
+              expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_ES_MODULES_FAIL)
               done()

     describe "when a file fails to parse", ->
@@ -85,7 +93,7 @@

             expect(stderr).to.match /line 1: unexpected token/i
             expect(stderr).match /esprima/i
-            expect(stdout).to.eql(read CLI_OUTPUT_TEST_ES_MODULES_FAIL)
+            expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_ES_MODULES_FAIL)
             done()

   describe "typescript", ->
@@ -95,7 +103,7 @@
       system.exec cmd,
         (error, stdout, stderr) ->
           expect(error.code).to.eql 0
-          expect(stdout).to.eql(read CLI_OUTPUT_TEST_TS)
+          expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_TS)
           done()

   describe "in general", ->
@@ -133,7 +141,7 @@
       system.exec cmd,
         (error, stdout, stderr) ->
           expect(error.code).to.eql 0
-          expect(stdout).to.eql(read CLI_OUTPUT_TEST_JS_SIM)
+          expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_JS_SIM)
           done()

     it "can set ngram level", (done) ->
@@ -142,7 +150,7 @@
       system.exec cmd,
         (error, stdout, stderr) ->
           expect(error.code).to.eql 0
-          expect(stdout).to.eql(read CLI_OUTPUT_TEST_JS_NGRAM)
+          expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_JS_NGRAM)
           done()

     it "can set token level", (done) ->
@@ -151,7 +159,7 @@
       system.exec cmd,
         (error, stdout, stderr) ->
           expect(error.code).to.eql 0
-          expect(stdout).to.eql(read CLI_OUTPUT_TEST_JS_TOKEN)
+          expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_JS_TOKEN)
           done()

     it "can output in colors", (done) ->
@@ -160,7 +168,7 @@
       system.exec cmd,
         (error, stdout, stderr) ->
           expect(error.code).to.eql 0
-          expect(stdout).to.eql(read CLI_OUTPUT_TEST_JS_TS_DIR_COLOR)
+          expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_JS_TS_DIR_COLOR)
           done()

     it "can compare via a dir", (done) ->
@@ -169,5 +177,5 @@
       system.exec cmd,
         (error, stdout, stderr) ->
           expect(error.code).to.eql 0
-          expect(stdout).to.eql(read CLI_OUTPUT_TEST_JS_TS_DIR)
+          expect(stdout).to.eql(cli_output CLI_OUTPUT_TEST_JS_TS_DIR)
           done()


Use 'or' instead of '||'

Use 'or' instead of '||'

--- test/xml2js.test.coffee
+++ test/xml2js.test.coffee
@@ -31,7 +31,7 @@
   if xpath == '/sample/validatortest/numbertest'
     return Number(newValue)
   else if xpath in ['/sample/arraytest', '/sample/validatortest/emptyarray', '/sample/validatortest/oneitemarray']
-    if not newValue || not ('item' of newValue)
+    if not newValue or not ('item' of newValue)
       return {'item': []}
   else if xpath in ['/sample/arraytest/item', '/sample/validatortest/emptyarray/item', '/sample/validatortest/oneitemarray/item']
     if not currentValue


Fix block -> block.start for List bug

Fix block -> block.start for List bug

--- src/controller.coffee
+++ src/controller.coffee
@@ -1483,7 +1483,7 @@

 Editor::spliceRememberedSocketOffsets = (block) ->
   if block.getDocument()?
-    blockBegin = block.getLocation().count
+    blockBegin = block.start.getLocation().count
     offsets = []
     newRememberedSockets = []
     for el, i in @rememberedSockets


修复Model和DataModel的整体重构后遗留的BUG

修复Model和DataModel的整体重构后遗留的BUG

--- src/core/model.coffee
+++ src/core/model.coffee
@@ -1057,7 +1057,7 @@
 			data: undefined
 			alias: alias
 			path: path
-			splittedPath: path.split(".")
+			splittedPath: path?.split(".") or []
 		return

 	getAliasTargetData: (alias) ->


refactor empty-filter and default-filter to use one common isEmpty function

--- src/liquid/standard_filters.coffee
+++ src/liquid/standard_filters.coffee
@@ -7,13 +7,20 @@
   Number input

 toObjectString = Object::toString
+hasOwnProperty = Object::hasOwnProperty

 isString = (input) ->
   toObjectString.call(input) is "[object String]"

+isArray = (input) ->
+  Array.isArray(input)
+
+isArguments = (input) ->
+  toObjectString(input) is "[object Arguments]"
+
 # from jQuery
 isNumber = (input) ->
-  !Array.isArray(input) and (input - parseFloat(input)) >= 0
+  !isArray(input) and (input - parseFloat(input)) >= 0

 toString = (input) ->
   unless input?
@@ -41,6 +48,20 @@

   new Date input if input?

+# from underscore.js
+has = (input, key) ->
+  input? and hasOwnProperty.call(input, key)
+
+# from underscore.js
+isEmpty = (input) ->
+  return true unless input?
+  return input.length is 0 if isArray(input) or isString(input) or isArguments(input)
+  (return false if has key, input) for key of input
+  true
+
+isBlank = (input) ->
+  !(isNumber(input) or input is true) and isEmpty(input)
+
 HTML_ESCAPE = (chr) ->
   switch chr
     when "&" then '&'
@@ -72,9 +93,7 @@
     toString(prefix) + toString(input)

   empty: (input) ->
-    return true unless input
-    return false unless input.length?
-    true
+    isEmpty(input)

   capitalize: (input) ->
     toString(input).replace /^([a-z])/, (m, chr) ->
@@ -221,16 +240,6 @@
       strftime format, input

   default: (input, defaultValue) ->
-    if arguments.length < 2
-      defaultValue = ''
-
-    if !input? || input == '' || input == false
-      return defaultValue
-
-    if Array.isArray input && input.length == 0
-      return defaultValue
-
-    if typeof input == 'object' && Object.keys(input).length == 0
-      return defaultValue
-
-    return input
+    defaultValue = '' if arguments.length < 2
+    blank = input?.isBlank?() ? isBlank(input)
+    if blank then defaultValue else input


Cleanup config test

Cleanup config test

--- src/test/test_config.coffee
+++ src/test/test_config.coffee
@@ -59,15 +59,20 @@
     t.ok sprite = sprites.find_child(), "missing sprite"

     t.equal sprite.get('v').all().toString(), "1,0", "sprite has direction vector"
-    t.equal sprite.get('i'), 2.5, "sprite has position"
+    console.log "1 sprite #{sprite}", sprite, sprite.get('p').all(), sprite.get('i')
+    world.send('reset')
+    t.equal sprite.get('i'), 1.5, "sprite has position"
     t.equal sprite.get('v_i'), 1, "sprite has direction"
     sprite.call('step')
-    t.equal sprite.get('i'), 3.5, "sprite moves position"
+    console.log "2 sprite", sprite, sprite.get('p').all(), sprite.get('i')
+    t.equal sprite.get('i'), 2.5, "sprite moves position"
     sprite.call('step')
+    console.log "3 sprite", sprite, sprite.get('p').all(), sprite.get('i')
     t.equal sprite.get('v_j'), 1, "sprite changes direction"

     world.send('reset')
-    t.equal sprite.get('i'), 2.5, "sprite has original position"
+    console.log "4 sprite", sprite, sprite.get('p').all(), sprite.get('i')
+    t.equal sprite.get('i'), 1.5, "sprite has original position"
     t.equal sprite.get('v_i'), 1, "sprite has original direction"

     t.end()


Use CamelCase in CoffeeScript

Use CamelCase in CoffeeScript

--- lib/spec-runner-view.coffee
+++ lib/spec-runner-view.coffee
@@ -32,5 +32,5 @@
       cwd: atom.project.getPath()
     stdout = (output) => @addLine(output)
     stderr = (output) => @addLine(output)
-    exit = (return_code) -> console.log("Exited with #{return_code}")
+    exit = (returnCode) -> console.log("Exited with #{returnCode}")
     process = new BufferedProcess({command, args, options, stdout, stderr, exit})


Fix reference to Turbolinks.Visit#location

Accidentally using `window.location` which happened to work fine in the
browser because its adapter doesn't ask for `hasSnapshot()` before history
changes.

--- src/turbolinks/visit.coffee
+++ src/turbolinks/visit.coffee
@@ -50,7 +50,7 @@
       @request.send()

   hasSnapshot: ->
-    if snapshot = @controller.getSnapshotForLocation(location)
+    if snapshot = @controller.getSnapshotForLocation(@location)
       if @location.anchor?
         snapshot.hasAnchor(@location.anchor)
       else


Activate/deactivate grammars directly

Activate/deactivate grammars directly

--- src/atom-package.coffee
+++ src/atom-package.coffee
@@ -104,7 +104,7 @@
     atom.keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps
     atom.contextMenu.add(menuPath, map['context-menu']) for [menuPath, map] in @menus
     atom.menu.add(map.menu) for [menuPath, map] in @menus when map.menu
-    atom.syntax.addGrammar(grammar) for grammar in @grammars
+    grammar.activate() for grammar in @grammars
     for [scopedPropertiesPath, selector, properties] in @scopedProperties
       atom.syntax.addProperties(scopedPropertiesPath, selector, properties)

@@ -151,7 +151,7 @@
     @grammars = []
     grammarsDirPath = path.join(@path, 'grammars')
     for grammarPath in fs.listSync(grammarsDirPath, ['.json', '.cson'])
-      @grammars.push(atom.syntax.loadGrammarSync(grammarPath))
+      @grammars.push(atom.syntax.readGrammarSync(grammarPath))

   loadScopedProperties: ->
     @scopedProperties = []
@@ -179,7 +179,7 @@
     @configActivated = false

   deactivateResources: ->
-    atom.syntax.removeGrammar(grammar) for grammar in @grammars
+    grammar.deactivate() for grammar in @grammars
     atom.syntax.removeProperties(scopedPropertiesPath) for [scopedPropertiesPath] in @scopedProperties
     atom.keymap.remove(keymapPath) for [keymapPath] in @keymaps
     atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in @stylesheets


Added option to provide crudMenu as a reactive variable.

--- src/collectionTable.coffee
+++ src/collectionTable.coffee
@@ -1,7 +1,8 @@
+TemplateClass = Template.collectionTable
 selectedClass = 'selected'
 domNodeField = 'domNodeField'
 selectEventName = 'select'
-TemplateClass = Template.collectionTable
+crudMenuDisableClass = 'no-crud-menu'

 getNextId = -> Collections.generateId()

@@ -223,7 +224,9 @@
   $('tr th', $footer).attr('colspan', colCount).append $nav
   $('tbody', $table).after $footer
   if settings.crudMenu == false
-    $collectionTable.addClass 'no-crud-menu'
+    $collectionTable.addClass crudMenuDisableClass
+  else if settings.crudMenu instanceof ReactiveVar
+    @autorun => $collectionTable.toggleClass crudMenuDisableClass, !settings.crudMenu.get()
   setDomTableId domNode, @tableId
   collection = @collection
   createRoute = @createRoute


engine: Copy change

engine: Copy change

--- src/lib/engine.coffee
+++ src/lib/engine.coffee
@@ -58,7 +58,7 @@

       logger.debug 'Starting to process repositories'
       async.eachSeries queries, ((query, done) =>
-        logger.info "Processing #{logger.highlight query.name}"
+        logger.info "Repository #{logger.highlight query.name}"
         git.prepareRepo query.name, query.urls, (err, repo) =>
           return done err if err?
           @updateRepo repo, query, (err, branches) =>


Facade#create() re-throws errors to avoid deeper stack

--- src/lib/facade.coffee
+++ src/lib/facade.coffee
@@ -161,7 +161,10 @@
     ###
     create: (Class, params...) ->
         if (Class::) instanceof BaseModel
-            return @createModel(Class.className, params...)
+            try
+                return @createModel(Class.className, params...)
+            catch e
+                throw @error(e.message) # to avoid deeper stack

         ClassWithConstructor = Class


reload channels when bot is added or removed from channel

See #60

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -236,6 +236,9 @@
         return true

     userAdded: (msg) =>
+        # update channels when this bot is added to a new channel
+        if msg.data.user_id == @self.id
+          @client.loadChannels()
         try
           mmUser = @client.getUserByID msg.data.user_id
           @userChange mmUser
@@ -247,6 +250,9 @@
           return false

     userRemoved: (msg) =>
+        # update channels when this bot is removed from a channel
+        if msg.broadcast.user_id == @self.id
+          @client.loadChannels()
         try
           mmUser = @client.getUserByID msg.data.user_id
           user = @robot.brain.userForId mmUser.id


simplify MKTS table wrapper code

simplify MKTS table wrapper code

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -443,23 +443,22 @@
   yield tex "\n\n"
   yield tex "% ==========================================================================================================\n"
   yield tex "% Beginning of MKTS Table (layout: #{rpr layout_name})\n"
-  # yield tex "\\par% Beginning of MKTS Table (layout: #{rpr layout_name})\n"
-  yield texr 'ð1000', "{\\setlength{\\fboxsep}{0mm}"
-  yield texr 'ð1001', "\\mktsColorframebox{green}{% debugging framebox" if me.debug
-  yield texr 'ð1002', "\\begin{minipage}[t][#{me._tmp.table_height_txt}][t]{#{me._tmp.table_width_txt}}"
-  yield texr 'ð1003', "\\begin{tikzpicture}[ overlay, yshift = 0mm, yscale = -1, line cap = rect ]"
+  yield texr 'ð1003', "\\begin{mktsSamepage}\\setlength{\\fboxsep}{0mm}"
+  yield texr 'ð1003', "{\\tfRaise{1.35}\\begin{tikzpicture}[ overlay, yshift = 0mm, yscale = -1, line cap = rect ]"
   yield texr 'ð1004', "\\tikzset{x=#{me._tmp.unitwidth_txt}};\\tikzset{y=#{me._tmp.unitheight_txt}};"
   yield return

 #-----------------------------------------------------------------------------------------------------------
 @_walk_closing_events = ( me ) ->
   layout_name = me.name
-  yield texr 'ð1005', "\\end{tikzpicture}"
-  yield texr 'ð1006', "\\end{minipage}}"
-  yield texr 'ð1007', "}% debugging framebox" if me.debug
-  # yield texr 'ð1008', "\\mktsVspace{1}"
-  yield texr 'ð1000', "\\mktsVspaceAbsolute{#{me._tmp.table_height_lh}}"
-  yield tex "\\par% End of MKTS Table (layout: #{rpr layout_name})\n"
+  yield texr 'ð1005', "\\end{tikzpicture}}\\makebox[#{me._tmp.table_width_txt}][t]{~~}\\mktsVspaceAbsolute{#{me._tmp.table_height_lh}}"
+  #.........................................................................................................
+  # ### alternative solution with individual newlines: ###
+  # yield texr 'ð1005', "~\\\\" for _ in [ 1 .. me._tmp.table_height_lh ]
+  # yield texr 'ð1005', "\\end{tikzpicture}"
+  #.........................................................................................................
+  yield tex "~\\par% End of MKTS Table (layout: #{rpr layout_name})\n"
+  yield texr 'ð1005', "\\end{mktsSamepage}"
   yield tex "% ==========================================================================================================\n"
   yield return

@@ -476,7 +475,7 @@
     continue unless ( borders = me.fieldborders[ fieldnr ] )?
     for i from _TMP_BORDERSEGMENTS.walk_segments fieldnr, borders
       # urge '33455', "#{me.name}/#{fieldnr}", jr i
-      yield texr 'ð1010', "%>>> #{me.name}/#{fieldnr} #{jr i}% #{fieldnr} border "
+      # yield texr 'ð1010', "%>>> #{me.name}/#{fieldnr} #{jr i}% #{fieldnr} border "
       switch i.mode
         when 'rectangle'
           yield texr 'ð1011', "\\draw[#{i.style}] (#{d.left},#{d.top}) rectangle (#{d.right},#{d.bottom});% #{fieldnr} border "


Add autodie pragma to Perl-template snippet

--- user/snippets.cson
+++ user/snippets.cson
@@ -30,7 +30,7 @@

 	Perl:
 		prefix: "pl"
-		body: "#!/usr/bin/env perl\nuse strict;\nuse warnings;\n$1"
+		body: "#!/usr/bin/env perl\nuse strict;\nuse warnings;\nuse autodie;\n\n$1"

 	"Boilerplate HTML":
 		prefix: "htmldoc"
@@ -154,6 +154,9 @@
 	"Invocant":
 		prefix: "me"
 		body: "my \$self = shift;\n"
+	"Executable":
+		prefix: "pl"
+		body: "#!/usr/bin/env perl\nuse strict;\nuse warnings;\nuse autodie;\n\n$1"

 # Roff


fix attachment test logic. close #193 (#194)

* fix attachment test logic. close #193

Also, retain the message text while init a new attachment.

Signed-off-by: Xia Kai(夏恺) <3e0bbecdde3a2d12ad8acfab839778c1b7f26c76@gmail.com>

* fix this test for normal text-only message.

Signed-off-by: Xia Kai(夏恺) <3e0bbecdde3a2d12ad8acfab839778c1b7f26c76@gmail.com>

--- src/rocketchat.coffee
+++ src/rocketchat.coffee
@@ -163,7 +163,7 @@
 								@robot.receive new EnterMessage user, null, newmsg._id
 							else
 							# check for the presence of attachments in the message
-							if newmsg.file? and newmsg.attachments.length
+							if newmsg.attachments? and newmsg.attachments.length
 								attachment = newmsg.attachments[0]

 								if attachment.image_url?
@@ -176,7 +176,7 @@
 									attachment.link = "#{RocketChatURL}#{attachment.video_url}"
 									attachment.type = 'video'

-								message = new AttachmentMessage user, attachment, attachment.title, newmsg._id
+								message = new AttachmentMessage user, attachment, newmsg.msg, newmsg._id
 							else
 								message = new TextMessage user, newmsg.msg, newmsg._id


Fix camera follow regression, photonstorm/phaser@aee0212

--- src/state-play.coffee
+++ src/state-play.coffee
@@ -42,6 +42,7 @@
       @textLayout = null

       _.extend @camera, CameraMixin
+      @_fixCamera()

       @game.onBlur.add @onBlur
       @game.onFocus.add @onFocus
@@ -250,6 +251,15 @@
       tween = @fadeTo text, Timer.SECOND, 1
       return

+    # photonstorm/phaser@aee0212
+    _fixCamera: _.once ->
+      @camera.flawedUpdateTarget = @camera.updateTarget
+      @camera.updateTarget = =>
+        @camera.target.world.copyFrom @camera.target.position
+        @camera.flawedUpdateTarget()
+        return
+      return
+
     _isPlayerReadyToEnd: ->
       (@player.state is 'still' and @player.control is on and
        @player.sprite.y <= @endingPoint.y)


First stab at using the D3 zoom behavior

Still some functionality missing
  * Redraw ticks and labels on the time axis
  * Point data is not correctly scaled / translated

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -220,81 +220,21 @@
         d3.select(window).on('resize', resize)

         # zooming
-        # (done via a seperate function, because we need to bind to two differen event listeners)
         zoom = =>
-            if @debug
-                time = {}
-                time.start = new Date()
-
-            direction = d3.event.wheelDelta if d3.event.wheelDelta
-            direction = d3.event.detail * -1 if d3.event.detail
-
-            if direction > 0
-                if @element.zoomLevel < 10
-                    @options.pixelPerDay *= 1.5
-                    @element.zoomLevel += 1
-            else
-                if @element.zoomLevel > -10
-                    @options.pixelPerDay /= 1.5
-                    @element.zoomLevel -= 1
-
-            console.log("Zooming to level #{@element.zoomLevel}") if @debug
-
-            # update scale
-            @scales.x.range([0, @options.numberOfDays * @options.pixelPerDay])
-
-            # update axis
-            # TODO make cleaner
-            switch
-                when @element.zoomLevel < -7
-                    @axis.x.ticks(d3.time.months.utc, 2).tickFormat(d3.time.format.utc('%Y-%m'))
-                    @grid.x.ticks(d3.time.months.utc, 1)
-                when @element.zoomLevel < -5
-                    @axis.x.ticks(d3.time.months.utc, 1).tickFormat(d3.time.format.utc('%Y-%m'))
-                    @grid.x.ticks(d3.time.months.utc, 1)
-                when @element.zoomLevel < -3
-                    @axis.x.ticks(d3.time.mondays.utc, 2).tickFormat(d3.time.format.utc('%Y-%m-%d'))
-                    @grid.x.ticks(d3.time.mondays.utc, 1)
-                when @element.zoomLevel < -1
-                    @axis.x.ticks(d3.time.mondays.utc, 1).tickFormat(d3.time.format.utc('%Y-%m-%d'))
-                    @grid.x.ticks(d3.time.days.utc, 1)
-                when @element.zoomLevel < 1
-                    @axis.x.ticks(d3.time.days.utc, 3).tickFormat(d3.time.format.utc('%Y-%m-%d'))
-                    @grid.x.ticks(d3.time.days.utc, 1)
-                when @element.zoomLevel < 3
-                    @axis.x.ticks(d3.time.days.utc, 1).tickFormat(d3.time.format.utc('%Y-%m-%d'))
-                    @grid.x.ticks(d3.time.days.utc, 1)
-                when @element.zoomLevel < 4
-                    @axis.x.ticks(d3.time.days.utc, 1).tickFormat(d3.time.format.utc('%Y-%m-%d'))
-                    @grid.x.ticks(d3.time.hours.utc, 6)
-                when @element.zoomLevel <= 5
-                    @axis.x.ticks(d3.time.hours.utc, 12).tickFormat(d3.time.format.utc('%Y-%m-%d %I:%M'))
-                    @grid.x.ticks(d3.time.hours.utc, 3)
-                when @element.zoomLevel <= 7
-                    @axis.x.ticks(d3.time.hours.utc, 6).tickFormat(d3.time.format.utc('%Y-%m-%d %I:%M'))
-                    @grid.x.ticks(d3.time.hours.utc, 1)
-                when @element.zoomLevel <= 9
-                    @axis.x.ticks(d3.time.hours.utc, 3).tickFormat(d3.time.format.utc('%Y-%m-%d %I:%M'))
-                    @grid.x.ticks(d3.time.minutes.utc, 30)
-                else
-                    @axis.x.ticks(d3.time.minutes.utc, 90).tickFormat(d3.time.format.utc('%Y-%m-%d %I:%M'))
-                    @grid.x.ticks(d3.time.minutes.utc, 30)
-
-            # update brush
-            @brush.x(@scales.x).extent(@brush.extent())
-
-            # repaint the scales and axis
+            console.log "Scale #{d3.event.scale}, Translate #{d3.event.translate}"
+
+            d3.select(@element).selectAll('path')
+                .attr('transform', "translate(#{d3.event.translate[0]}, 0)scale(#{d3.event.scale}, 1)")
+            d3.select(@element).selectAll('circle')
+                .attr('transform', "translate(#{d3.event.translate[0] * d3.event.scale}, 0)")
+
+            # repaint the scales and the axis
             d3.select(@element).select('g.axis').call(@axis.x)
             d3.select(@element).select('g.grid').call(@grid.x)
             d3.select(@element).select('g.brush').call(@brush)

-            # repaint the datasets
-
-            if @debug
-                console.log("Done zooming, took #{new Date().getTime() - time.start.getTime()} milliseconds.")
-
-        d3.select(element).on('DOMMouseScroll', zoom)
-        d3.select(element).on('mousewheel', zoom)
+
+        @root.call(d3.behavior.zoom().x(@scales.x).on('zoom', zoom))

     # Function pair to allow for easy hiding and showing the time slider
     hide: ->


Helper for getting the thumbnail cover image

--- models/partner_show.coffee
+++ models/partner_show.coffee
@@ -71,6 +71,10 @@
         @trigger "fetch:posterImageUrl", imageUrl
     @artworks.fetch options
     false
+
+  thumbImageUrl: ->
+    return @imageUrl('general') if @hasImage('general')
+    return @imageUrl('medium_rectangle') if @hasImage('medium_rectangle')

   title: ->
     @get 'name'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -8,4 +8,4 @@

   self.bind 'update', ->
     self.destroy() unless I.activeBounds.left <= I.x <= I.activeBounds.right
-      +    self.destroy() unless I.activeBo


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -14,5 +14,6 @@
   log I.activeBounds

   self.bind 'update', ->
+    log I.activeBounds
     self.destroy() unless I.activeBounds.x <= I.x <= I.activeBounds.x + I.activeBounds.width
     self.destroy() unless I.activeBounds.y <= I.y <= I.activeBounds.y + I.activeBounds.height


comment

comment

--- src/dom_text_mapper.coffee
+++ src/dom_text_mapper.coffee
@@ -42,7 +42,7 @@
       throw new Error "Can't find iframe with specified ID!"
     @rootWin = iframe.contentWindow
     unless @rootWin?
-      throw new Error "Can't access contents of the spefified iframe!"
+      throw new Error "Can't access contents of the specified iframe!"
     @rootNode = @rootWin.document
     @pathStartNode = @getBody()


refactor, hide unnecessary vm methods

refactor, hide unnecessary vm methods

--- src/app/events/eventDetail.controller.coffee
+++ src/app/events/eventDetail.controller.coffee
@@ -28,7 +28,7 @@
   # # TODO: hide event.address, event.location from JS introspection,
   #         reload values on `event:participant-changed`
   ###
-  vm.setVisibleLocation = (event, user) ->
+  setVisibleLocation = (event, user) ->
     userid = (user || vm.me || {}).id
     event.visibleAddress = event.neighborhood
     participantIds = _.pluck vm.lookup['Participations'], 'participantId'
@@ -47,7 +47,7 @@
   ###
   # these are hasMany or belongsTo lookups
   ###
-  vm.getParticipationByUser = (user)->
+  getParticipationByUser = (user)->
     # TODO: memo result in vm.lookup
     return false if !user
     participation = _.find vm.lookup['Participations'], {participantId: user.id}
@@ -470,8 +470,8 @@
     event.seatsOpen = Math.max(0, event.seatsTotal - summary.booking['seats'])

     if vm.acl.isParticipant()
-      # event.myParticipation = vm.getParticipationByUser(vm.me)
-      vm.lookup['MyParticipation'] = vm.getParticipationByUser(vm.me)
+      # event.myParticipation = getParticipationByUser(vm.me)
+      vm.lookup['MyParticipation'] = getParticipationByUser(vm.me)
       myParticipation = summary['myParticipation']
       myParticipation['isFullyParticipating'] = false

@@ -606,7 +606,7 @@
         vm.event['participantIds'].push result.participantId
       vm.lookup['Participations'][result.id] = result
       vm.lookup['Users'][result.participantId] = person
-      vm.lookup['MyParticipation'] = vm.getParticipationByUser(person)
+      vm.lookup['MyParticipation'] = getParticipationByUser(person)
       $scope.$broadcast 'lookup-data:changed', {className:'Participations'}
       return result
     .then (participation)->
@@ -788,7 +788,7 @@
       return event
     .then (event)->
       # TODO: reset on 'event:participant-changed'
-      vm.setVisibleLocation(event)
+      setVisibleLocation(event)
       getMap(event)
       return event

@@ -858,7 +858,7 @@
       return event
     .then (event)->
       # TODO: reset on 'event:participant-changed'
-      vm.setVisibleLocation(event)
+      setVisibleLocation(event)
       getMap(event)
       return event


add snap to draggable and message to identify piece name moved

--- views/index.coffee
+++ views/index.coffee
@@ -82,17 +82,22 @@
     else
       now.name = prompt("What's your name?", "")
       $.cookie('klop_name', now.name)
+
     className = ""
     $( ".chess_piece" ).draggable(
       opacity: 0.55
-      refreshPositions: true
+      delay: 200
+      distance: 10
+      snap: true
+      snapMode: 'inner'
+      snapTolerance: 30
       start: (event, ui) ->
         className = ui.helper.context.className.toString()
       stop: (event, ui) ->
         piecePath= ui.helper.context.src.match(/chess_pieces*.*/)
         pieceName = piecePath.toString().replace("chess_pieces/", "")
         setPosition(className, ui.position.top, ui.position.left)
-        now.distributeMessage('has moved a piece')
+        now.distributeMessage("has moved the #{pieceName.replace('_', ' ').replace('.png', '')}")
       )

     $("#text_input").keyup (event) ->
@@ -109,4 +114,3 @@
     now.receiveElement= (className, top, left) ->
       $(".#{className.replace(" ",  ".").replace(" ui-draggable", "")}").css({"position":"relative","left":"#{left}px","top":"#{top}px"})

-


config: run worker_daemon function for workers with supervisord instruction

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -826,7 +826,7 @@
         node scripts/create-default-workspace

         # Run all the worker daemons in KONFIG.workers
-        #{("worker_daemon_"+key+"\n" for key,val of KONFIG.workers).join(" ")}
+        #{("worker_daemon_"+key+"\n" for key,val of KONFIG.workers when val.supervisord).join(" ")}

         # Check backend option, if it's then bypass client build
         if [ "$1" == "backend" ] ; then


Simplify watcher output cleanup

Simplify watcher output cleanup

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -878,12 +878,9 @@
         # Do npm i incase of packages.json changes
         npm i --silent

-        # this is a temporary adition, normally file watcher should delete the created file later on
-        cd #{projectRoot}/go/bin
-
         # Remove old watcher files (do we still need this?)
-        rm -rf goldorf-main-*
-        rm -rf watcher-*
+        rm -rf #{projectRoot}/go/bin/goldorf-main-*
+        rm -rf #{projectRoot}/go/bin/watcher-*

         # Run Go builder
         #{projectRoot}/go/build.sh


Fix target resolution with action buttons

--- src/views/game.coffee
+++ src/views/game.coffee
@@ -321,19 +321,20 @@
     e.preventDefault()
     target = $(e.target)

-    @trigger 'sfx:play', 'button'
-
-    # User is probably actually clicking a span inside the button
-    if target.is 'span' then target = target.parents '.button'
-
-    @$(".button.#{@action}", @elem).removeClass 'active'
-
-    if target.hasClass 'mark'
+    # User is probably actually clicking an element inside the button
+    unless target.hasClass('button') then target = target.parents('.button')
+    return if target.hasClass('active')
+
+    @trigger('sfx:play', 'button')
+
+    @elem.find(".button.#{@action}").removeClass('active')
+
+    if target.hasClass('mark')
       @action = 'mark'
-      target.addClass 'active'
+      target.addClass('active')
     else
       @action = 'fill'
-      target.addClass 'active'
+      target.addClass('active')

   # Update timer elem


wtf-dumb-robot

wtf-dumb-robot

Don't fail for dumb reasons like an asshole

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -142,6 +142,7 @@
   #   This is not a standard feature, and requires an adapter
   #   that emits a `say` event when the robot speaks
   robot.adapter.on 'say', (envelope, str) ->
+    str = str or ""
     buffer.store robot.name.trim(), str.trim()

   # We listen to everything.


change form append so that it's more resilient to change

--- lib/assets/javascripts/turbograft/remote.coffee
+++ lib/assets/javascripts/turbograft/remote.coffee
@@ -62,12 +62,12 @@
     else
       @contentType = "application/x-www-form-urlencoded; charset=UTF-8"
       formData = @formAppend(formData, "_method", @opts.httpRequestType) if formData.indexOf("_method") == -1 && @opts.httpRequestType && @actualRequestType != 'GET'
-      formData = formData.slice(0,-1) if formData.charAt(formData.length - 1) == "&"

     formData

   formAppend: (uriEncoded, key, value) ->
-    uriEncoded += "#{encodeURIComponent(key)}=#{encodeURIComponent(value)}&"
+    uriEncoded += "&" if uriEncoded.length
+    uriEncoded += "#{encodeURIComponent(key)}=#{encodeURIComponent(value)}"

   uriEncodeForm: (form) ->
     formData = ""


:lipstick:

:lipstick:

--- src/app/root-view.coffee
+++ src/app/root-view.coffee
@@ -266,15 +266,9 @@
     editor.save() for editor in @getEditors()

   eachEditor: (callback) ->
-    for editor in @getEditors()
-      callback(editor)
-
-    @on 'editor:attached', (e, editor) ->
-      callback(editor)
+    callback(editor) for editor in @getEditors()
+    @on 'editor:attached', (e, editor) -> callback(editor)

   eachBuffer: (callback) ->
-    for buffer in @project.getBuffers()
-      callback(buffer)
-
-    @project.on 'buffer-created', (buffer) ->
-      callback(buffer)
+    callback(buffer) for buffer in @project.getBuffers()
+    @project.on 'buffer-created', (buffer) -> callback(buffer)


Cleanup

Cleanup

--- test/default-network.coffee
+++ test/default-network.coffee
@@ -9,7 +9,6 @@
     before (done) ->
       defaultNetwork.get (error, result) ->
         data = result
-        console.log data
         done(error)
     it 'is an data object', ->
       expect(data).to.be.an.any('object')


created a wrapper to clean up code a bit. Not 100% sure on the naming or implementation yet

--- web/scripts/index.coffee
+++ web/scripts/index.coffee
@@ -6,37 +6,36 @@
     editor.setTheme "ace/theme/monokai"
     editor.getSession().setMode "ace/mode/java"
     editor.setValue levelOne
-    editor.clearSelection
+    editor.clearSelection()
     editor.gotoLine 0, 0, false
     editor.setReadOnly true
     editor.focus()

-    $('#switchUp').click ->
-        source = editor.getSession().getDocument()
+    button = (func) -> ->
+        # This is a wrapper for the functions which are tied to buttons.
+        text = editor.getSession().getDocument()
         currentRow = editor.getCursorPosition().row
+        func.call @, text, currentRow
+        editor.focus()
+
+    $('#switchUp').click button (text, currentRow) ->
         if currentRow > 0
             previousRow = currentRow - 1
-            previousLine = source.getLine previousRow
-            source.removeLines previousRow, previousRow
-            source.insertLines currentRow, [previousLine]
-        editor.focus()
+            previousLine = text.getLine previousRow
+            text.removeLines previousRow, previousRow
+            text.insertLines currentRow, [previousLine]
         return

-    $('#switchDown').click ->
-        source = editor.getSession().getDocument()
-        maxRow = source.getLength()
-        currentRow = editor.getCursorPosition().row
+    $('#switchDown').click button (text, currentRow) ->
+        maxRow = text.getLength()
         if currentRow < maxRow - 1
             nextRow = currentRow + 1
-            nextLine = source.getLine nextRow
-            source.removeLines nextRow, nextRow
-            source.insertLines currentRow, [nextLine]
-        editor.focus()
+            nextLine = text.getLine nextRow
+            text.removeLines nextRow, nextRow
+            text.insertLines currentRow, [nextLine]
         return

-    $('#deleteLine').click ->
-        source = editor.getSession().getDocument()
-        currentRow = editor.getCursorPosition().row
-        source.removeLines currentRow, currentRow
+    $('#deleteLine').click button (text, currentRow) ->
+        text.removeLines currentRow, currentRow
         return
     return


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -26,6 +26,6 @@
     y: 20

   equals engine.first('type=testObj').length, 1
-  equ
+  equals engine.fin

 module()


created more specific wrappers which pass along arguments. Changed the implementation of SwitchUp and SwitchDown to fix a bug where SwitchUp was not changing the position of the cursor when it was at the very end of the source text. Added some comments.

--- web/scripts/index.coffee
+++ web/scripts/index.coffee
@@ -7,14 +7,18 @@
         go: 1,
         turnRight: 0
     }
-    new GameEditor levelOne, commands
+    editor = new GameEditor levelOne, commands
+    window.Editor = editor # For testing only, puts editor in global namespace.
     return

 class GameEditor
     constructor: (@codeText, @commands) ->
         @editor = ace.edit "editor"
+        @editSession = @editor.getSession()
+        # @undoManager = @editSession.getUndoManager()
+
         @editor.setTheme "ace/theme/chrome"
-        @editor.getSession().setMode "ace/mode/java"
+        @editSession.setMode "ace/mode/java"
         @editor.setValue @codeText
         @editor.clearSelection()
         @editor.gotoLine 0, 0, false
@@ -24,7 +28,13 @@
         @setUpInsertCommands()
         @addButtonEventListeners()

+        # @undoManager.reset()
+
     setUpInsertCommands: ->
+        ###
+        Adds an option to the Selector for the insert button for each command
+        this game has.
+        ###
         selector = $('#commandToInsert')
         for command of @commands
             selector.append $ "<option/>",
@@ -35,6 +45,11 @@
         return

     displaySelectInputFields: (event) ->
+        ###
+        An even triggered when the Selector for the insert button is changed.
+        It creates a number of input fields equal to the number of inputs to the
+        selected command.
+        ###
         selectedOption = event.target.options[event.target.selectedIndex].text
         numberOfInputs = @commands[selectedOption]
         inputsDiv = $('#insertInputs')
@@ -43,46 +58,33 @@
             inputsDiv.append("<input id='#{i}' type='text' size='5'>")
         return

-    button: (func) ->
-        # This is a wrapper for the functions which are tied to buttons.
-        gameEditor = @
-        return ->
-            text = gameEditor.editor.getSession().getDocument()
-            currentRow = gameEditor.editor.getCursorPosition().row
-            func.call gameEditor, text, currentRow
-            gameEditor.editor.focus()
-
     addButtonEventListeners: ->
-        $('#switchUp').click @button @switchUp
-        $('#switchDown').click @button @switchDown
-        $('#deleteLine').click @button @deleteLine
-        $('#insertLine').click @button @insertLine
+        $('#switchUp').click @button @usesCurrentPosition @switchUp
+        $('#switchDown').click @button @usesCurrentPosition @switchDown
+        $('#deleteLine').click @button @editsText @usesCurrentRow @deleteLine
+        $('#insertLine').click @button @editsText @usesCurrentRow @insertLine
         $('#resetText').click @button @resetText
         selector = $('#commandToInsert')
         selector.change @displaySelectInputFields.bind @
         selector.change()
         return

-    switchUp: (text, currentRow) ->
+
+    switchUp: (currentRow, currentColumn) ->
         if currentRow > 0
-            previousRow = currentRow - 1
-            previousLine = text.getLine previousRow
-            text.removeLines previousRow, previousRow
-            text.insertLines currentRow, [previousLine]
+            @editSession.moveLinesUp(currentRow, currentRow)
+            @editor.gotoLine currentRow, currentColumn, false
         return

-    switchDown: (text, currentRow) ->
-        maxRow = text.getLength()
+    switchDown: (currentRow, currentColumn) ->
+        maxRow = @editSession.getLength()
         if currentRow < maxRow - 1
-            nextRow = currentRow + 1
-            nextLine = text.getLine nextRow
-            text.removeLines nextRow, nextRow
-            text.insertLines currentRow, [nextLine]
+            @editSession.moveLinesDown(currentRow, currentRow)
+            @editor.gotoLine currentRow + 2, currentColumn, false
         return

     deleteLine: (text, currentRow) ->
         text.removeLines currentRow, currentRow
-        @editor.gotoLine currentRow, 0, false
         return

     insertLine: (text, currentRow) ->
@@ -99,6 +101,56 @@
         text.insertLines currentRow, [toInsert]
         return

-    resetText: (text, currentRow) ->
-        text.setValue @codeText
+    resetText: ->
+        @editor.setValue @codeText
+        @editor.clearSelection()
+        @editor.gotoLine 0, 0, false
         return
+
+    button: (func) ->
+        ###
+        This is a wrapper for the functions which are tied to buttons.
+        It restores focus to the editor after the button has been pressed.
+        ###
+        gameEditor = @
+        return ->
+            func.call gameEditor
+            gameEditor.editor.focus()
+            return
+
+    usesCurrentRow: (func) ->
+        ###
+        This is a wrapper for the functions which need to know the current row.
+        It figures out the current row and passes it to the function.
+        ###
+        gameEditor = @
+        return ->
+            currentRow = gameEditor.editor.getCursorPosition().row
+            arguments[arguments.length++] = currentRow
+            func.apply gameEditor, arguments
+            return
+
+    usesCurrentPosition: (func) ->
+        ###
+        This is a wrapper for the functions which need to know the cursor's row and column
+        It figures out the current row and column and passes them to the function.
+        ###
+        gameEditor = @
+        return ->
+            cursorPosition = gameEditor.editor.getCursorPosition()
+            arguments[arguments.length++] = cursorPosition.row
+            arguments[arguments.length++] = cursorPosition.column
+            func.apply gameEditor, arguments
+            return
+
+    editsText: (func) ->
+        ###
+        This is a wrapper for functions which edit the text in the editor directly.
+        It gets a reference to the text and passes it to the function.
+        ###
+        gameEditor = @
+        return ->
+            text = gameEditor.editSession.getDocument()
+            arguments[arguments.length++] = text
+            func.apply gameEditor, arguments
+            return


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -32,7 +32,7 @@
       equals obj, obj2
     else if index is 2
       equals obj, obj3
-    el
+    else if index i

 test "#find", ->
   engine = Engine()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -16,6 +16,8 @@
     enemy: true
     x: 0
     y: 15
+
+

 test "#each", ->
   engine = Engine()


gulp-help not used for testing either

gulp-help not used for testing either

--- test/sourcegate.spec.coffee
+++ test/sourcegate.spec.coffee
@@ -42,7 +42,7 @@
     gulp = null
     for [testName, paths, so] in testWriteFiles
       before ->
-        gulp = require('gulp-help')(require('gulp'))
+        gulp = require('gulp')
         for file in paths
           try fs.unlinkSync(file)
       after ->


Added unit test for NormalizedRange#text()

--- test/spec/range_spec.coffee
+++ test/spec/range_spec.coffee
@@ -73,6 +73,8 @@
       it "should parse test range #{i} (#{testData[i][5]})", testBrowserRange(i)

   describe "NormalizedRange", ->
+    sel = null
+
     beforeEach ->
       sel = mockSelection(7)
       browserRange = new Range.BrowserRange(sel.getRangeAt(0))
@@ -82,8 +84,10 @@
       textNodes = r.textNodes()

       expect($.type(textNodes)).toEqual('array')
-      expect(textNodes.length).toEqual(testData[8][4])
+      expect(textNodes.length).toEqual(sel.endOffset)

       # Should contain the contents of the first <strong> element.
       expect(textNodes[0].nodeValue).toEqual('Pellentesque habitant morbi tristique')

+    it "text() returns the textual contents of the range", ->
+      expect(r.text()).toEqual(sel.expectation)


test path creation

test path creation

--- test/src/exploreSpec.coffee
+++ test/src/exploreSpec.coffee
@@ -1,5 +1,15 @@
 describe 'Curve.Path', ->
   beforeEach ->
     loadFixtures 'canvas.html'
+    @raphael = Raphael("canvas")

   it 'can be created', ->
+    path = new Curve.Path(@raphael)
+    path.addPathPoint(new Curve.PathPoint([50, 50], [-10, 0], [10, 0]))
+    path.addPathPoint(new Curve.PathPoint([80, 60], [-10, -5], [10, 5]))
+    path.addPathPoint(new Curve.PathPoint([60, 80], [10, 0], [-10, 0]))
+    path.close()
+
+    el = $('svg path')
+    expect(el.attr('d')).toMatch(/^M50,50C60,50/)
+    expect(el.attr('d')).toMatch(/Z$/)


Fix test syntax

Fix test syntax

--- test/testJsonPointer.coffee
+++ test/testJsonPointer.coffee
@@ -39,6 +39,6 @@

       check = (expression, expected) ->
         actual = jsonPointer.get targetAsString, expression
-        actual.should.deep.equal expected
+        actual.should.be.deep.equal expected

       check(expression, expected) for expression, expected of specExamples


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animated.coffee
+++ test/animated.coffee
@@ -355,7 +355,7 @@
          "complete":"Idle1",
          "interruptible":false,
          "speed":"110",
-         "transition": ["Matrix.HORIZONTAL_FLIP"]
+         "transition":["Matrix.HORIZONTAL_FLIP"],
          "triggers": {
            "0":["whiteParticles"],
            "4":["blueParticles","greenParticles"],
@@ -498,7 +498,7 @@

   animation.update()

-  equals animation.I.transform == Matrix.HORIZONTAL_FLIP
+  equals animation.I.transform, Matrix.HORIZONTAL_FLIP

 module()


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -34,6 +34,7 @@
     ok false

   o.bind "test", callback
+  # Unbind specific event
   o.unbind "test", callback
   o.trigger "test"


Changed anchors.fill: parent to include children positions

--- src/renderer/impl/base/level1/anchors.coffee
+++ src/renderer/impl/base/level1/anchors.coffee
@@ -153,7 +153,7 @@
                 child = target.firstChild
                 while child
                     if child._visible
-                        tmp = child._width
+                        tmp = child._x + child._width
                         if tmp > size
                             size = tmp
                     child = child.nextSibling
@@ -169,7 +169,7 @@
                 child = target.firstChild
                 while child
                     if child._visible
-                        tmp = child._height
+                        tmp = child._y + child._height
                         if tmp > size
                             size = tmp
                     child = child.nextSibling
@@ -234,8 +234,10 @@
     onChildInsert = (child) ->
         child.onVisibleChange update, @
         if @source is 'fillWidthSize'
+            child.onXChange update, @
             child.onWidthChange update, @
         if @source is 'fillHeightSize'
+            child.onYChange update, @
             child.onHeightChange update, @

         update.call @
@@ -244,8 +246,10 @@
     onChildPop = (child) ->
         child.onVisibleChange.disconnect update, @
         if @source is 'fillWidthSize'
+            child.onXChange.disconnect update, @
             child.onWidthChange.disconnect update, @
         if @source is 'fillHeightSize'
+            child.onYChange.disconnect update, @
             child.onHeightChange.disconnect update, @

         update.call @


Make the appropriate title element editable

--- src/scripts/modules/media/title/title.coffee
+++ src/scripts/modules/media/title/title.coffee
@@ -26,7 +26,7 @@
       }

     editable:
-      '.media-title > .title > h1':
+      '.media-title > .title > h2':
         value: 'title'
         type: 'textinput'


Fix distance calculation

Fix distance calculation

--- webpack/src/index.coffee
+++ webpack/src/index.coffee
@@ -31,12 +31,14 @@
   found = null
   for element in parsed.elements
     elemLatLng = new L.LatLng(element.lat, element.lon)
-    if origin.distanceTo(elemLatLng) < minDistance
+    newDistance = origin.distanceTo(elemLatLng)
+    if newDistance < minDistance
+      minDistance = newDistance
       found = elemLatLng
   found

 document.addEventListener('DOMContentLoaded', ->
-  map = L.map('map').setView([25.1701, 121.5948], 13)
+  map = L.map('map').setView([25.1701, 121.6], 15)

   L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png',
     attribution: '© OpenStreetMap contributors'


Use speed in delay

Use speed in delay

* Make Flight remove the transitioned DOM element exactly when the
duration of the animation has finished.

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -14,7 +14,7 @@
   back: false

   transitionAnimation: true
-  speed: '0.4s'
+  speed: 0.4
   mainMenu: '#main-menu'
   menuCloseButton: '#close-menu-btn'

@@ -125,9 +125,11 @@
       @back = false
     , 0

+    # delay node removal time to speed
+    delay = @speed * 1000
     window.setTimeout =>
       currentPageClone.parentNode.removeChild currentPageClone
-    , 800
+    , delay

   # Private: Perform a slide from bottom transition.
   #
@@ -184,7 +186,7 @@
   #
   # Returns nothing.
   translate: (page, axis, distance, duration) ->
-    duration = @speed unless duration?
+    duration = @speed + "s" unless duration?
     page.style.webkitTransition = "#{duration} cubic-bezier(.10, .10, .25, .90)"
     page.style.webkitTransform = "translate#{axis}(#{distance})"


Update future workflow.

Update future workflow.

--- src/coffee/import.coffee
+++ src/coffee/import.coffee
@@ -39,11 +39,13 @@
   # - create/update products based on matches
   #
   # ideally workflow:
-  # - stream csv -> chunk
-  # - validate chunk
-  # - map products in chunk
-  # - lookup mapped products in sphere
-  # - create/update products based on matches from result
+  # - get all product types, categories, customer groups, taxes, channels (maybe get them ondemand?)
+  # - stream csv -> chunk (100)
+  # - base csv validation of chunk
+  # - map products to json in chunk
+  # - lookup mapped products in sphere (depending on given matcher - id, sku, slug, custom attribute)
+  # - validate products against their product types (we might not have to product type before)
+  # - create/update products based on matches
   # - next chunk
   import: (fileContent) ->
     @validator.parse fileContent


Fix InStateMenu swallowing toggleKey event.

Minor hardening.

--- src/in-state-menu.coffee
+++ src/in-state-menu.coffee
@@ -34,6 +34,7 @@

       @toggleKey = @input.keyboard.addKey @toggleKeyCode
       @toggleKey.onDown.add => @toggle()
+      @input.keyboard.removeKeyCapture @toggleKeyCode

       @toggle off


IDE: Filter files when applying host's snapshot.

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -6,6 +6,7 @@
 kd                            = require 'kd'
 KDNotificationView            = kd.NotificationView
 KDModalView                   = kd.ModalView
+FSFile                        = require 'app/util/fs/fsfile'
 nick                          = require 'app/util/nick'
 getCollaborativeChannelPrefix = require 'app/util/getCollaborativeChannelPrefix'
 showError                     = require 'app/util/showError'
@@ -361,13 +362,18 @@

         return  unless remainingPanes.length

+        snapshotAsArray = IDELayoutManager.convertSnapshotToFlatArray snapshot
+        snapshotFiles   = {}
+
+        for item in snapshotAsArray when item.context?.paneType is 'editor'
+          { path, name } = item.context.file
+          snapshotFiles[path] = name
+
         for pane in remainingPanes
-          isAdded = no
-
-          @forEachSubViewInIDEViews_ (p) ->
-            isAdded = yes  if p.hash is pane.view.hash
-
-          @activeTabView.addPane pane  unless isAdded
+          if pane.data instanceof FSFile # editor, tailer
+            @activeTabView.addPane pane  unless snapshotFiles[pane.data.path]
+          else
+            @activeTabView.addPane pane

         @doResize()


Setting initial value setter

Setting initial value setter

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -1081,3 +1081,8 @@

   getHostSnapshot: -> IDELayoutManager.convertSnapshotToFlatArray @getWorkspaceSnapshot()

+
+  setInitialSessionSetting: (name, value) ->
+
+    @initialSettings ?= {}
+    @initialSettings[name] = value


Ajax request get initial tests

Ajax request get initial tests

--- test/ajax.spec.coffee
+++ test/ajax.spec.coffee
@@ -5,7 +5,24 @@
 ajax = Rye.require('Ajax')
 escape = (v) -> v.replace /[[\]]/g, (v) -> ('[': '%5B', ']': '%5D')[v]

+class Number.Countdown
+    constructor: (@index = 0, @done) ->
+    valueOf: -> @index
+    toString: -> @index.toString()
+    fire: => if --@index then @done()
+
 suite 'Ajax', ->
+
+    test 'get', (done) ->
+        countdown = new Number.Countdown(2, done)
+        ajax.request '/echo', (data) ->
+            assert.equal data, 'get no data'
+            countdown.fire()
+
+        ajax.request url: '/echo', data: {fizz: 1, bar: 2}, callback: (data) ->
+            assert.equal data, 'get 2'
+            countdown.fire()
+

     test 'params', ->
         assert.equal ajax.param({foo: {one: 1, two: 2}}), escape 'foo[one]=1&foo[two]=2'


Special rename requests

Special rename requests

--- client/landing/app/MainApp/fs/fshelper.coffee
+++ client/landing/app/MainApp/fs/fshelper.coffee
@@ -87,7 +87,7 @@
       withArgs : {path}
     , callback

-  @getGlobList = (pattern, vmName, callback)->
+  @glob = (pattern, vmName, callback)->
     [vmName, callback] = [callback, vmName]  if typeof vmName is "function"
     KD.getSingleton('vmController').run
       method   : "fs.glob"


move-abc animations now move along the actual pixels that need to be traveled

--- lib/assets/javascripts/up/motion.js.coffee
+++ lib/assets/javascripts/up/motion.js.coffee
@@ -218,43 +218,59 @@
   )

   animation('move-to-top', ($ghost, options) ->
-    $ghost.css('margin-top': '0%')
-    animate($ghost, { 'margin-top': '-100%' }, options)
+    box = u.measure($ghost)
+    travelDistance = box.top + box.height
+    $ghost.css('margin-top': '0px')
+    animate($ghost, { 'margin-top': "-#{travelDistance}px" }, options)
   )

   animation('move-from-top', ($ghost, options) ->
-    $ghost.css('margin-top': '-100%')
-    animate($ghost, { 'margin-top': '0%' }, options)
+    box = u.measure($ghost)
+    travelDistance = box.top + box.height
+    $ghost.css('margin-top': "-#{travelDistance}px")
+    animate($ghost, { 'margin-top': '0px' }, options)
   )

   animation('move-to-bottom', ($ghost, options) ->
-    $ghost.css('margin-top': '0%')
-    animate($ghost, { 'margin-top': '100%' }, options)
+    box = u.measure($ghost)
+    travelDistance = u.clientSize().height - box.top
+    $ghost.css('margin-top': '0px')
+    animate($ghost, { 'margin-top': "#{travelDistance}px" }, options)
   )

   animation('move-from-bottom', ($ghost, options) ->
-    $ghost.css('margin-top': '100%')
-    animate($ghost, { 'margin-top': '0%' }, options)
+    box = u.measure($ghost)
+    travelDistance = u.clientSize().height - box.top
+    $ghost.css('margin-top': "#{travelDistance}px")
+    animate($ghost, { 'margin-top': '0px' }, options)
   )

   animation('move-to-left', ($ghost, options) ->
-    $ghost.css('margin-left': '0%')
-    animate($ghost, { 'margin-left': '-100%' }, options)
+    box = u.measure($ghost)
+    travelDistance = box.left + box.width
+    $ghost.css('margin-left': '0px')
+    animate($ghost, { 'margin-left': "-#{travelDistance}px" }, options)
   )

   animation('move-from-left', ($ghost, options) ->
-    $ghost.css('margin-left': '-100%')
-    animate($ghost, { 'margin-left': '0%' }, options)
+    box = u.measure($ghost)
+    travelDistance = box.left + box.width
+    $ghost.css('margin-left': "-#{travelDistance}px")
+    animate($ghost, { 'margin-left': '0px' }, options)
   )

   animation('move-to-right', ($ghost, options) ->
-    $ghost.css('margin-left': '0%')
-    animate($ghost, { 'margin-left': '100%' }, options)
+    box = u.measure($ghost)
+    travelDistance = u.clientSize().width - box.left
+    $ghost.css('margin-left': '0px')
+    animate($ghost, { 'margin-left': "#{travelDistance}px" }, options)
   )

   animation('move-from-right', ($ghost, options) ->
-    $ghost.css('margin-left': '100%')
-    animate($ghost, { 'margin-left': '0%' }, options)
+    box = u.measure($ghost)
+    travelDistance = u.clientSize().width - box.left
+    $ghost.css('margin-left': "#{travelDistance}px")
+    animate($ghost, { 'margin-left': '0px' }, options)
   )

   animation('roll-down', ($ghost, options) ->


take vendor into account if given as parameter

--- server/lib/filter_existing.coffee
+++ server/lib/filter_existing.coffee
@@ -1,5 +1,6 @@

-module.exports = (log, model, suffix) ->
+module.exports = (log, model, suffix, vendor) ->
+
     (requiredFields, entries, body, next) ->
         entries.filtered = []

@@ -7,9 +8,23 @@
         model.all (err, entryObjects) ->
             return next err if err
             entryHash = {}
+
+            # Build an hash where key is the date and valie is the entry
             for entry in entryObjects
-                entryHash[entry.date.toISOString()] = entry
+
+                # If a vendor parameter is given, entry should be of given
+                # vendor to be added to the hash (useful for bills).
+                if vendor?
+                    if entry.vendor is vendor
+                        entryHash[entry.date.toISOString()] = entry
+                    # else do nothing
+
+                # Simply add the entry
+                else
+                    entryHash[entry.date.toISOString()] = entry
+
             # Keep only non already existing entries.
             entries.filtered = entries.fetched.filter (entry) ->
                 not entryHash[entry.date.toISOString()]?
+
             next()