more time troubleshooting

more time troubleshooting

--- src/api/apps/articles/test/model/save.test.coffee
+++ src/api/apps/articles/test/model/save.test.coffee
@@ -17,7 +17,11 @@
     app.use '/__gravity', gravity
     @server = app.listen 5000, ->
       done()
-    sandbox.useFakeTimers(new Date("Tue Jan 01 2019 00:00:00 GMT-0000").getTime())
+
+    date = new Date("Tue Jan 01 2019 00:00:00 GMT")
+    offset = date.getTimezoneOffset()
+    dateWithOffset = moment(date).add(offset, 'm').toDate();
+    sandbox.useFakeTimers(dateWithOffset)

   after ->
     @server.close()
@@ -112,7 +116,7 @@
           author: name: 'Molly'
           published_at: '2017-07-dsdfdf26T17:37:03.065Zsdfdf'
         }, (err, article) =>
-          article.slugs[0].should.equal 'molly-clockwork-12-31-18'
+          article.slugs[0].should.equal 'molly-clockwork-01-01-19'
           done()
       )(null, {
         slugs: ['molly-clockwork']
@@ -125,7 +129,7 @@
           published: true
           author: name: 'Molly'
         }, (err, article) =>
-          article.slugs[0].should.equal 'molly-clockwork-12-31-18'
+          article.slugs[0].should.equal 'molly-clockwork-01-01-19'
           done()
       )(null, {
         slugs: ['molly-clockwork']
@@ -200,7 +204,7 @@
           published: false
           author: name: 'Molly'
         }, (err, article) =>
-          article.slugs[0].should.equal 'molly-clockwork-1546300800'
+          article.slugs[0].should.equal 'molly-clockwork-1546318800'
           done()
       )(null, {
         slugs: ['molly-clockwork']


#22 Collection

#22 Collection

--- lib/models/collection.coffee
+++ lib/models/collection.coffee
@@ -1,131 +1,123 @@
-((NS) ->
-  "use strict"
-  # Filename: models/model.coffee
-  NS = NS or {}
-  isInBrowser = typeof module is 'undefined' and typeof window isnt 'undefined'
-  metadataBuilder = if isInBrowser then NS.Helpers.metadataBuilder else require("../helpers/metadata_builder").metadataBuilder
-  # Base class for all models. Define all transverses methods on the model.
-  class Collection extends Backbone.Collection
-    # The model name can be define.
-    modelName: undefined
+"use strict"
+# Filename: models/model.coffee
+metadataBuilder = require("../helpers/metadata_builder").metadataBuilder
+# Base class for all models. Define all transverses methods on the model.
+class Collection extends Backbone.Collection
+  # The model name can be define.
+  modelName: undefined

-
-    addModel: (model)->
-      if model.isNew()
-        @changes.creates[model.cid] = model.toSaveJSON()
-      else @updateModel(model)
-    updateModel: (model)->
-      (delete @changes.deletes[model.cid]) if @changes.deletes[model.cid]?
-      @changes.updates[model.cid] = model.toSaveJSON()
-    deleteModel:(model) ->
-      if @changes.creates[model.cid]?
-        delete @changes.creates[model.cid]
-      if @changes.updates[model.cid]?
-        @changes.deletes[model.cid] = model.toSaveJSON()
-        delete @changes.updates[model.cid]
-    resetModels:(models) ->
-      #Reset the changes.
-      @changes = {
-        # The newly created models.
-        creates: {}
-        # The updated models.
-        updates: {}
-        # The deleted models.
-        deletes: {}
-      }
-      # Call add model foreach model.
-      models.forEach(@addModel, @)
-    ###
-      Process the collection metdatas.
-    ###
-    processMetadatas: ->
-      this.metadatas = metadataBuilder.getMetadatas(_.pick(this, "modelName", "metadatas"))
-      # Change the idAttribute depending on the metadatas.
-    initialize: (options)->
-      options = options or {}

-      # Changes inside the collection.
-      @changes = {
-        # The newly created models.
-        creates: {}
-        # The updated models.
-        updates: {}
-        # The deleted models.
-        deletes: {}
-      }
-      @processMetadatas()
-      # Bind models events on order to track changes.
-      @on('add',(model)=> @addModel(model))
-      @on('remove', (model)=> @deleteModel(model))
-      @on('change', (model)=> @addModel(model))
-      @on('reset', (models)=> @resetModels(models))
-      # Add the collection elements if they are .
-      # @resetModels(options) if _.isArray(options)
-    # To json function.
-    toJSON: ->
-      jsonModel = super()
-      jsonModel.modelName =  @modelName or this.get('modelName')
-      return jsonModel
-
-    # Return a json to Save constructed with the changes object.
-    toSaveJSON:(propertyPrefix) ->
-      propertyPrefix = propertyPrefix or "coll"
-      creates = "#{propertyPrefix}Creates"
-      updates = "#{propertyPrefix}Updates"
-      deletes = "#{propertyPrefix}Deletes"
-      labels = {};
-      labels[creates] = @changes.creates #_.map(@changes.creates, (value, key) -> return value);
-      labels[updates] = @changes.updates #_.map(@changes.updates, (value, key) -> return value);
-      labels[deletes] = @changes.deletes # _.map(@changes.deletes, (value, key) -> return value);
-      return labels
-    #Save the pervious collection values in order to restore it.
-    savePrevious: ->
-      @previousCollectionValues = @toJSON()
-    # Restore the previous attributes in the model.
-    restorePrevious: (options)->
-      options = options or {}
-      options.silent = options.isSilent or false
-      @reset(@previousCollectionValues, options)
-    # Tells if the previous model is different from the current.
-    isDifferent: ->
-      return not _.isEqual @previousCollectionValues, @toJSON()
-    ###
-      Set errors on a collection element.
-    ###
-    setErrors:(errors, options)->
-      if _.isArray(errors)
-        @setErrorsFromArray(errors, options)
-      else if _.isObject(errors)
-        @setErrorsFromObject(errors, options)
-    # Set the errors on the collection from an array of model.
-    setErrorsFromArray:(errors, options)->
-      for error in errors
-        console.warn('invalid error', error) if not error.index? and typeof error.index isnt "number"
-        console.warn('invalid error', error) if not error.errors? and typeof error.errors isnt "object"
-
-        #For the model at the given position in the collection:
-        # Set the error depending on its index.
-        @at(error.index).set({
-            errors: error.errors
-        }, options)
-    setErrorsFromObject:(errors, options)->
-      for err of errors
-        console.warn('invalid error', err) if not _.isString(err)
-        @get(err).set({
-            errors: errors[err]
-        }, options)
-    # Unset errors.
-    unsetErrors: (options) ->
-      @forEach((mdl)-> mdl.unsetErrors(options))
-    jsonFromSaveJson:(saveJSON)->
-      changes = []
-      _.each(saveJSON.creates, (elt)->changes.push(elt))
-      _.each(saveJSON.updates, (elt)->changes.push(elt))
-      return changes;
-
-  if isInBrowser
-    NS.Models = NS.Models or {}
-    NS.Models.Collection = Collection
-  else
-    module.exports = Collection
-)(if typeof module is 'undefined' and typeof window isnt 'undefined' then window.Fmk else module.exports)+  addModel: (model)->
+    if model.isNew()
+      @changes.creates[model.cid] = model.toSaveJSON()
+    else @updateModel(model)
+  updateModel: (model)->
+    (delete @changes.deletes[model.cid]) if @changes.deletes[model.cid]?
+    @changes.updates[model.cid] = model.toSaveJSON()
+  deleteModel: (model) ->
+    if @changes.creates[model.cid]?
+      delete @changes.creates[model.cid]
+    if @changes.updates[model.cid]?
+      @changes.deletes[model.cid] = model.toSaveJSON()
+      delete @changes.updates[model.cid]
+  resetModels: (models) ->
+    #Reset the changes.
+    @changes = {
+    # The newly created models.
+      creates: {}
+    # The updated models.
+      updates: {}
+    # The deleted models.
+      deletes: {}
+    }
+    # Call add model foreach model.
+    models.forEach(@addModel, @)
+  ###
+    Process the collection metdatas.
+  ###
+  processMetadatas: ->
+    this.metadatas = metadataBuilder.getMetadatas(_.pick(this, "modelName", "metadatas"))
+  # Change the idAttribute depending on the metadatas.
+  initialize: (options)->
+    options = options or {}
+
+    # Changes inside the collection.
+    @changes = {
+    # The newly created models.
+      creates: {}
+    # The updated models.
+      updates: {}
+    # The deleted models.
+      deletes: {}
+    }
+    @processMetadatas()
+    # Bind models events on order to track changes.
+    @on('add', (model)=> @addModel(model))
+    @on('remove', (model)=> @deleteModel(model))
+    @on('change', (model)=> @addModel(model))
+    @on('reset', (models)=> @resetModels(models))
+  # Add the collection elements if they are .
+  # @resetModels(options) if _.isArray(options)
+  # To json function.
+  toJSON: ->
+    jsonModel = super()
+    jsonModel.modelName = @modelName or this.get('modelName')
+    return jsonModel
+
+  # Return a json to Save constructed with the changes object.
+  toSaveJSON: (propertyPrefix) ->
+    propertyPrefix = propertyPrefix or "coll"
+    creates = "#{propertyPrefix}Creates"
+    updates = "#{propertyPrefix}Updates"
+    deletes = "#{propertyPrefix}Deletes"
+    labels = {};
+    labels[creates] = @changes.creates #_.map(@changes.creates, (value, key) -> return value);
+    labels[updates] = @changes.updates #_.map(@changes.updates, (value, key) -> return value);
+    labels[deletes] = @changes.deletes # _.map(@changes.deletes, (value, key) -> return value);
+    return labels
+  #Save the pervious collection values in order to restore it.
+  savePrevious: ->
+    @previousCollectionValues = @toJSON()
+  # Restore the previous attributes in the model.
+  restorePrevious: (options)->
+    options = options or {}
+    options.silent = options.isSilent or false
+    @reset(@previousCollectionValues, options)
+  # Tells if the previous model is different from the current.
+  isDifferent: ->
+    return not _.isEqual @previousCollectionValues, @toJSON()
+  ###
+    Set errors on a collection element.
+  ###
+  setErrors: (errors, options)->
+    if _.isArray(errors)
+      @setErrorsFromArray(errors, options)
+    else if _.isObject(errors)
+      @setErrorsFromObject(errors, options)
+  # Set the errors on the collection from an array of model.
+  setErrorsFromArray: (errors, options)->
+    for error in errors
+      console.warn('invalid error', error) if not error.index? and typeof error.index isnt "number"
+      console.warn('invalid error', error) if not error.errors? and typeof error.errors isnt "object"
+
+      #For the model at the given position in the collection:
+      # Set the error depending on its index.
+      @at(error.index).set({
+        errors: error.errors
+      }, options)
+  setErrorsFromObject: (errors, options)->
+    for err of errors
+      console.warn('invalid error', err) if not _.isString(err)
+      @get(err).set({
+        errors: errors[err]
+      }, options)
+  # Unset errors.
+  unsetErrors: (options) ->
+    @forEach((mdl)-> mdl.unsetErrors(options))
+  jsonFromSaveJson: (saveJSON)->
+    changes = []
+    _.each(saveJSON.creates, (elt)-> changes.push(elt))
+    _.each(saveJSON.updates, (elt)-> changes.push(elt))
+    return changes;
+
+module.exports = Collection


FSItem: fix rename/move this also needs to be fixed in klient code @fatih knows details

[fixes #77048848]

--- client/finder/fs/fsitem.coffee
+++ client/finder/fs/fsitem.coffee
@@ -224,17 +224,16 @@
     .then =>
       @emit "fs.delete.finished"

-  # Move file to provided path, its using ::rename
-  move: (path, callback)->
-
-    newName = FSHelper.plainPath "#{ path }/#{ @name }"
-    @rename path: newName, callback
-
+  # Rename file with provided new name, its using ::move
   rename: (newName, callback)->

-    @emit "fs.job.started"
-
-    newPath = "#{FSHelper.getParentPath @getPath()}/#{newName}"
+    @move "#{FSHelper.getParentPath @getPath()}/#{newName}", callback
+
+  move: (newPath, callback)->
+
+    @emit "fs.job.started"
+
+    newPath = FSHelper.plainPath newPath

     kite = @getKite()

@@ -242,7 +241,7 @@

     .then =>

-      kite.fsRename
+      kite.fsMove
         oldpath : @getPath()
         newpath : newPath


添加调试代码

添加调试代码

--- lib/pushservices/apns.coffee
+++ lib/pushservices/apns.coffee
@@ -50,11 +50,17 @@
             if note_bytesize > 265
                 difference = note_bytesize - 265
             alert_bytesize =  oldAlert_bytesize - difference - 3
+            logger.verbos oldAlert_bytesize
+            logger.verbos difference
+            logger.verbos alert
             alertNew = ''
             i = 0
             while i <= alert.length
+
                 logger.verbose alertNew.replace(/[^\x00-\xff]/gi, "--").length + alert.charAt(i).replace(/[^\x00-\xff]/gi, "--").length
+                logger.verbose alert_bytesize
                 logger.verbose alertNew.replace(/[^\x00-\xff]/gi, "--").length + alert.charAt(i).replace(/[^\x00-\xff]/gi, "--").length <= alert_bytesize
+
                 if alertNew.replace(/[^\x00-\xff]/gi, "--").length + alert.charAt(i).replace(/[^\x00-\xff]/gi, "--").length <= alert_bytesize
                     alertNew = alertNew + alert.charAt(i)
                 else


去掉中文注释

去掉中文注释

--- lib/pushservices/apns.coffee
+++ lib/pushservices/apns.coffee
@@ -43,7 +43,7 @@
                 note.payload = payload.data

-            # 处理APNs消息长度
+
             note_bytesize = JSON.stringify(note).replace(/[^\x00-\xff]/gi, "--").length
             oldAlert_bytesize = alert.replace(/[^\x00-\xff]/gi, "--").length
             if note_bytesize > 265


tweaks to onboarding language and ux

tweaks to onboarding language and ux

--- @client/new_forum_onboarding.coffee
+++ @client/new_forum_onboarding.coffee
@@ -38,6 +38,7 @@
   #NewForumOnBoarding .onboard_item {
     display: flex;
     margin-bottom: 18px;
+    position: relative;
   }

   #NewForumOnBoarding .onboard_item .checkbox {
@@ -102,7 +103,7 @@

   render : ->
     current_user = fetch '/current_user'
-    return SPAN null if !current_user.is_admin
+    return SPAN null if !current_user.is_admin || screencasting()

     subdomain = fetch '/subdomain'
     return SPAN null if subdomain.customizations.onboarding_complete
@@ -122,9 +123,9 @@

       LI
         className: "onboard_item #{if completed then 'complete' else 'incomplete'}"
-
         SPAN
           className: 'checkbox'
+          "data-tooltip": if !completed then "This will be checked automatically when you complete the item"

         SPAN
           className: 'label'
@@ -174,7 +175,7 @@

           onboard_item "Edit the Banner to introduce people to your forum.", task_one_complete
           onboard_item "Experiment with the question below. Delete it when you’re done.", task_two_complete
-          onboard_item "Start your own conversation.", task_three_complete
+          onboard_item "Add a request for feedback or an open-ended question.", task_three_complete

       DIV
         style:


Modified region boundary style.

Modified region boundary style.

--- src/servicemap.coffee
+++ src/servicemap.coffee
@@ -222,7 +222,14 @@
     show_division = (div) ->
         if division_layer
             map.removeLayer division_layer
-        division_layer = L.geoJson div.boundary
+        division_layer = L.geoJson div.boundary,
+            style: (feature) ->
+                fillOpacity: 0
+                lineJoin: 'round'
+                weight: 10
+                color: 'rgb(53, 69, 96)'
+                opacity: 0.5
+
         map.addLayer division_layer
         map.fitBounds division_layer.getBounds()
         data =


fix updater view

fix updater view

--- lib/updater.coffee
+++ lib/updater.coffee
@@ -53,15 +53,15 @@
                 title: 'Update Learn IDE'
                 resizable: false

-              console.log('loading window')
               win = new BrowserWindow(args)

               win.on 'closed', ->
                 win = null

-              updatePath = path.resolve(path.join(__dirname, '..', '..', 'static', 'update_check.html'))
+              updatePath = path.resolve(path.join(__dirname, '..', 'static', 'update_check.html'))

-              win.loadUrl("file://#{ updatePath }")
+              updatePageURL = "file://#{ updatePath }"
+              win.loadUrl(updatePageURL)

               win.webContents.on 'did-finish-load', ->
                 win.show()


:fire: seems to no longer necessary function

--- lib/selection-wrapper.coffee
+++ lib/selection-wrapper.coffee
@@ -132,7 +132,6 @@
       when 'characterwise'
         @translateSelectionEndAndClip('forward') # equivalent to core selection.selectRight but keep goalColumn
       when 'linewise'
-        @complementGoalColumn()
         # Even if end.column is 0, expand over that end.row( don't care selection.getRowRange() )
         {start, end} = @getBufferRange()
         range = getBufferRangeForRowRange(@selection.editor, [start.row, end.row])
@@ -142,16 +141,6 @@
         new BlockwiseSelection(@selection)

     @setWiseProperty(wise)
-
-  complementGoalColumn: ->
-    unless @selection.cursor.goalColumn?
-      column = @getBufferPositionFor('head', from: ['property', 'selection']).column
-      @selection.cursor.goalColumn = column
-
-  captureProperties: ->
-    head = @selection.getHeadBufferPosition()
-    tail = @selection.getTailBufferPosition()
-    {head, tail}

   selectByProperties: ({head, tail}, options) ->
     # No problem if head is greater than tail, Range constructor swap start/end.


Bug fix for codo documentation

Bug fix for codo documentation

--- src/core/core.coffee
+++ src/core/core.coffee
@@ -33,7 +33,7 @@
 #   @param [Any] default_value the default value. Can be a value, string or ko.observable
 #   @return [ko.observable] the constructor does not return 'this' but a ko.observable
 #
-# @method .formattedObservable(format, arg1, arg2, ...)
+# @method .formattedObservable(format, arg1, arg2, etc)
 #   Factory to create a new kb.FormattedObservable. See {kb.FormattedObservable#constructor} for information on options. If you are using knockback-core or knockback-core-stack, you can include this from the lib/knockback-formatting component.
 #   @param [String|ko.observable] format the format string. Format: `"{0} and {1}"` where `{0}` and `{1}` would be synchronized with the arguments (eg. "Bob and Carol" where `{0}` is Bob and `{1}` is Carol)
 #   @param [Array] args arguments to be passed to the kb.LocaleManager's get() method
@@ -66,11 +66,11 @@

   # Checks if an object has been released.
   # @param [Any] obj the object to release and also release its keys
-  @wasReleased = (obj) -> return not obj or obj.__kb_released
+  @wasReleased: (obj) -> return not obj or obj.__kb_released

   # Checks if an object can be released. Used to perform minimal nested releasing on objects by checking if self or next level contained items can be released.
   # @param [Any] obj the object to release and also release its keys
-  @isReleaseable = (obj, depth=0) ->
+  @isReleaseable: (obj, depth=0) ->
     # must be an object and not already released
     if (not obj or (obj isnt Object(obj))) or obj.__kb_released
       return false
@@ -103,7 +103,7 @@
   # @example
   #   var todos = kb.collectionObservable(collection);
   #   kb.utils.release(todos); todos = null;
-  @release = (obj) ->
+  @release: (obj) ->
     return unless kb.isReleaseable(obj)

     # release array's items
@@ -138,7 +138,7 @@
     return

   # Releases and clears all of the keys on an object using the conventions of release(), destroy(), dispose() without releasing the top level object itself.
-  @releaseKeys = (obj) ->
+  @releaseKeys: (obj) ->
     ((obj[key] = null; kb.release(value)) if (key isnt '__kb') and kb.isReleaseable(value)) for key, value of obj
     return

@@ -153,7 +153,7 @@
   #   kb.releaseOnNodeRemove(view_model, el);
   #   ...
   #   ko.removeNode(el); // removes el from the DOM and calls kb.release(view_model)
-  @releaseOnNodeRemove = (view_model, node) ->
+  @releaseOnNodeRemove: (view_model, node) ->
     view_model or _throwUnexpected(@, 'missing view model')
     node or _throwUnexpected(@, 'missing node')
     ko.utils.domNodeDisposal.addDisposeCallback(node, -> kb.release(view_model))
@@ -166,7 +166,7 @@
   #   var el = kb.renderTemplate('my_template', kb.viewModel(new Backbone.Model({name: 'Bob'})));
   #   ...
   #   ko.removeNode(el); // removes el from the DOM and calls kb.release(view_model)
-  @renderTemplate = (template, view_model, options={}) ->
+  @renderTemplate: (template, view_model, options={}) ->
     return console?.log 'renderTemplate: document is undefined' unless document?

     el = document.createElement('div')
@@ -185,17 +185,17 @@
   #   kb.applyBindings(kb.viewModel(new Backbone.Model({name: 'Bob'})), el);
   #   ...
   #   ko.removeNode(el); // removes el from the DOM and calls kb.release(view_model)
-  @applyBindings = (view_model, node) ->
+  @applyBindings: (view_model, node) ->
     ko.applyBindings(view_model, node)
     kb.releaseOnNodeRemove(view_model, node)

-  @getValue = (model, key, args) ->
+  @getValue: (model, key, args) ->
     return unless model
     return model[key]() if _.isFunction(model[key]) and kb.orm.useFunction(model, key)
     return model.get(key) unless args
     model.get.apply(model, _.map([key].concat(args), (value) -> _peekObservable(value)))

-  @setValue = (model, key, value) ->
+  @setValue: (model, key, value) ->
     return unless model
     return model[key](value) if _.isFunction(model[key]) and kb.orm.useFunction(model, key)
     (attributes = {})[key] = value


Revert "Fixed incorrect import paths in Windows"

--- lib/typescript-import.coffee
+++ lib/typescript-import.coffee
@@ -88,7 +88,7 @@
       symbolLocation = @index[selection]
       if symbolLocation && selection
         fileFolder = path.resolve(filePath + '/..')
-        relative = path.relative(fileFolder, symbolLocation).replace(/\.(js|ts)$/, '').replace(/\\/g, '/')
+        relative = path.relative(fileFolder, symbolLocation).replace(/\.(js|ts)$/, '');
         importClause = "import #{selection} from './#{relative}';\n"
         @addImportStatement(importClause)
 #        editor.insertText(selection + "\nimport #{selection} from './#{relative}'")


Don't log parsed fragments of `cp -v` output

--- src/steps/move.coffee
+++ src/steps/move.coffee
@@ -52,7 +52,6 @@
     processLine = (line) ->
       if line.trim() isnt ""
         pieces = /[‘`](.*?)[’'] -> [‘`](.*?)[’']/.exec line
-        console.log pieces[1..2]
         fileData =
           id: Math.random()
           src: pathMod.normalize pieces[1]


cache sum of a chunk in memory

cache sum of a chunk in memory

--- coffee/PrimeNumberStore.coffee
+++ coffee/PrimeNumberStore.coffee
@@ -29,21 +29,14 @@
 #     - 0  : [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
 #

-# count and sum of all the primes in a chunk is stored in a separate table
-#
-#- primeSumTbl
-#     - 0  : [25,  1060]
-#
-# The sum table and prime table use the same index (field value) to store related
-# chunks. All indexes are numbers
-
 PRIME_INDEX_TBL="primeIndexTbl"
-# PRIME_SUM_TBL="primeSumTbl"
 PRIME_TBL = "primeTbl"

 primeIndexTbl = {}
-# primeSumTbl = {}
 primeTbl = {}
+
+# cache sum in memory
+primeSumTbl = {}

 qLoadIndexTbl = (self) ->
     q = Q.defer()
@@ -67,26 +60,6 @@
     )
     return q.promise

-qLoadSumTbl = () ->
-    return Q()
-
-# qLoadSumTbl = (self) ->
-#     q = Q.defer()
-#     # load primeSumTbl from persistent store
-#     self.store.qGetTable(PRIME_SUM_TBL)
-#     .then((info)->
-#         _.each(info, (value, index)->
-#             index = Number(index)
-#             primeSumTbl[index] = value
-#         )
-#         return q.resolve(primeSumTbl)
-#     )
-#     .fail((err)->
-#         return q.reject(err)
-#     )
-#     return q.promise
-
-
 #
 # Make a list of all the primes between
 # start - end. While we are at it
@@ -117,7 +90,7 @@
     }

     debug('found %d primes in range: [%s-%s]',  primes.length, start, end)
-
+
     # memUsage = process.memoryUsage()
     # debug("memory usage:" +  Math.round(memUsage.heapUsed / 1024 / 1024) + " MB")

@@ -132,18 +105,12 @@
         return q.promise

     debug('setting primes at index:%d', chunkIndex)
-
-    # primeSumTbl[chunkIndex] = [ chunkPrimeTbl.primes.length ,  chunkPrimeTbl.sum ]
+    primeSumTbl[chunkIndex] = chunkPrimeTbl.sum
     primeTbl[chunkIndex] = _.uniq(_.concat(primeTbl[chunkIndex] or [], chunkPrimeTbl.primes or []))

     # store the primes
     self.store.qSet(PRIME_TBL, chunkIndex, chunkPrimeTbl.primes)
     .then(()->
-        # # store the metadata
-        # self.store.qSet(PRIME_SUM_TBL, chunkIndex, primeSumTbl[chunkIndex])
-        # .then(()->
-        #     return q.resolve()
-        # )
         return q.resolve()
     )
     .fail((err)->
@@ -192,6 +159,7 @@
                         return

                     debug('caching %d primes for index : %d', primeRecords[index].length, value)
+                    primeSumTbl[value] = primeRecords[index].reduce(getSum, 0)
                     primeTbl[value] = primeRecords[index]
                 )
             )
@@ -224,11 +192,7 @@
         # Load index table
         qLoadIndexTbl(self)
         .then(()->
-            # Load sum table
-            qLoadSumTbl(self)
-            .then(()->
-                return q.resolve()
-            )
+            return q.resolve()
         )
         .fail((err)->
             return q.reject(err)
@@ -299,7 +263,7 @@
                     debug("processed index:%d added %d primes", index, arrLen)
                     primes[index] = arr
                     nprimes += arrLen
-                    sum += arr.reduce(getSum, 0)
+                    sum += primeSumTbl[index]
                     return

                 filtered = _.filter(arr, (prime)->


fixed broken too long lines [linter]

fixed broken too long lines [linter]

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

--- coffee/controllers/main.coffee
+++ coffee/controllers/main.coffee
@@ -31,8 +31,10 @@
     # smart toggle function: toggle a field in a group
     # if no field is active in the group, just activate this field
     # if this field is already active, deactivate it
-    # if a field is choosen while another is active, the new one will become active
-    # example: toggleShow('nav', 'config'), toggleShow('nav', 'mirrors'), toggleShow('nav', 'mirrors')
+    # if a field is choosen while another is active, the new one
+    #   will become active
+    # example: toggleShow('nav', 'config'), toggleShow('nav', 'mirrors'),
+    # toggleShow('nav', 'mirrors')
     $scope.toggleShow = (group, field)->
       if $scope.show[group] == field
         $scope.show[group] = undefined


Kill player after current tick in SinglePlayerListener

--- singleplayerlistener.coffee
+++ singleplayerlistener.coffee
@@ -1,8 +1,8 @@
 define(['listener'], (Listener) ->
   class SinglePlayerListener extends Listener
     notify: (player1, player2, point) ->
-     console.log "Collision: #{player1} #{player2} #{point}"
-     @game.killPlayer(player2, point)
+     console.log "Collision: #{player1.name} #{player2.name} #{point}"
+     @game.runAfterTick((game) -> game.killPlayer(player2, point))

   SinglePlayerListener


ace: minor styling :rooster:

ace: minor styling :rooster:

--- client/ace/lib/ace.coffee
+++ client/ace/lib/ace.coffee
@@ -216,7 +216,7 @@
     # deletes a command's exec ref.
     @_commandFns = _.reduce @editor.commands.commands, (acc, val, key) ->
       acc[key] = val.exec
-      acc
+      return acc
     , {}

     { shortcuts } = kd.singletons
@@ -264,7 +264,7 @@

   setShortcut: (model) ->

-    disabled = model.options and model.options.enabled is false
+    disabled = model.options?.enabled is no

     if disabled
       @editor.commands.removeCommand model.name


Fix selection spec to to check marker destruction properly

--- spec/app/selection-spec.coffee
+++ spec/app/selection-spec.coffee
@@ -71,4 +71,4 @@
     it "destroys its marker", ->
       selection.setBufferRange([[2, 0], [2, 10]])
       selection.destroy()
-      expect(editSession.getMarkerBufferRange(selection.marker)).toBeUndefined()
+      expect(selection.marker.isDestroyed()).toBeTruthy()


Don't scroll to top when teacher edition is toggled

--- src/components/reference-book/reference-book.cjsx
+++ src/components/reference-book/reference-book.cjsx
@@ -31,8 +31,9 @@
     # Pop open the menu unless the page was explicitly navigated to
     {isMenuVisible: not cnxId}

-  showTeacherEdition: ->
+  toggleTeacherEdition: (ev) ->
     @setState(showTeacherEdition: not @state.showTeacherEdition)
+    ev?.preventDefault() # stops react-router from scrolling to top

   onMenuClick: ->
     @toggleMenuState() unless window.innerWidth > MENU_VISIBLE_BREAKPOINT
@@ -49,7 +50,7 @@
     classnames = ["reference-book"]
     classnames.push('menu-open') if @state.isMenuVisible
     if course and _.findWhere(course.roles, type: 'teacher')
-      showTeacher = @showTeacherEdition
+      toggleTeacher = @toggleTeacherEdition
       teacherLinkText = if @state.showTeacherEdition
         classnames.push('is-teacher')
         "Hide Teacher Edition"
@@ -60,7 +61,7 @@
       <NavBar
         toggleTocMenu={@toggleMenuState}
         teacherLinkText={teacherLinkText}
-        showTeacherEdition={showTeacher}
+        showTeacherEdition={toggleTeacher}
         courseId={courseId}/>
       <div className="content">
         <Menu onMenuSelection={@onMenuClick} />


Navigation Callbacks for WebKit

Navigation Callbacks for WebKit

--- app/assets/javascripts/admin/utils/services/navigation_check.js.coffee
+++ app/assets/javascripts/admin/utils/services/navigation_check.js.coffee
@@ -15,7 +15,8 @@
       onBeforeUnloadHandler: ($event) =>
         message = @getMessage()
         if message
-          ($event or $window.event).preventDefault()
+          # following: https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload
+          ($event or $window.event).returnValue = message
           message

       # Action for angular navigation.


Improve rerender of articles.

Improve rerender of articles.

--- app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee
@@ -20,7 +20,7 @@
     @el.append( all )

 class ArticleViewItem extends App.Controller
-  hasChangedAttributes: ['from', 'to', 'cc', 'subject', 'body', 'internal', 'preferences']
+  hasChangedAttributes: ['from', 'to', 'cc', 'subject', 'body', 'preferences']

   elements:
     '.textBubble-content':           'textBubbleContent'
@@ -59,7 +59,7 @@
     App.TicketArticle.unsubscribe(@subscribeId)

   setHighlighter: =>
-    return if !@el.is(':visible')
+    return if @el.is(':hidden')
     # use delay do no ui blocking
     #@highligher.loadHighlights(@ticket_article_id)
     d = =>
@@ -69,19 +69,19 @@
   hasChanged: (article) =>

     # if no last article exists, remember it and return true
-    if !@article_last_updated
-      @article_last_updated = {}
+    if !@articleAttributesLastUpdate
+      @articleAttributesLastUpdate = {}
       for item in @hasChangedAttributes
-        @article_last_updated[item] = article[item]
+        @articleAttributesLastUpdate[item] = article[item]
       return true

     # compare last and current article attributes
-    article_last_updated_check = {}
+    articleAttributesLastUpdateCheck = {}
     for item in @hasChangedAttributes
-      article_last_updated_check[item] = article[item]
-    diff = difference(@article_last_updated, article_last_updated_check)
+      articleAttributesLastUpdateCheck[item] = article[item]
+    diff = difference(@articleAttributesLastUpdate, articleAttributesLastUpdateCheck)
     return false if !diff || _.isEmpty( diff )
-    @article_last_updated = article_last_updated_check
+    @articleAttributesLastUpdate = articleAttributesLastUpdateCheck
     true

   render: (article) =>
@@ -90,16 +90,22 @@
     @article = App.TicketArticle.fullLocal( @ticket_article_id )

     # set @el attributes
-    @el.addClass("ticket-article-item #{@article.sender.name.toLowerCase()}")
-    if @article.internal is true
-      @el.addClass('is-internal')
-    else
-      @el.removeClass('is-internal')
-    @el.attr('data-id',  @article.id)
-    @el.attr('id', "article-#{@article.id}")
+    if !article
+      @el.addClass("ticket-article-item #{@article.sender.name.toLowerCase()}")
+      @el.attr('data-id',  @article.id)
+      @el.attr('id', "article-#{@article.id}")
+
+    # set internal change directly in dom, without rerender while article
+    if !article || ( @lastArticle && @lastArticle.internal isnt @article.internal )
+      if @article.internal is true
+        @el.addClass('is-internal')
+      else
+        @el.removeClass('is-internal')

     # check if rerender is needed
-    return if !@hasChanged(@article)
+    if !@hasChanged(@article)
+      @lastArticle = @article.attributes()
+      return

     # prepare html body
     if @article.content_type is 'text/html'
@@ -128,14 +134,16 @@

     # set see more
     @shown = false
-    @setSeeMore()
+    a = =>
+      @setSeeMore()
+    @delay( a, 50 )

     # set highlighter
     @setHighlighter()

   # set see more options
   setSeeMore: =>
-    return if !@el.is(':visible')
+    return if @el.is(':hidden')
     return if @shown
     @shown = true


Rename Discussions --> Challenges

Rename Discussions --> Challenges

Only show this feed in KL mode; and show it first

--- core/app/backbone/views/react_feed_selection.coffee
+++ core/app/backbone/views/react_feed_selection.coffee
@@ -37,6 +37,12 @@
       if currentSession.signedIn()
         [
           _div ['feed-selection-row'],
+            if window.is_kennisland
+              [
+                _input [ 'radio-toggle-button', type: 'radio', name: 'FeedChoice', value: 'discussions', id: 'FeedChoice_Discussions', onChange: @_handleFeedChoiceChange, checked: @state.feedChoice=='discussions' ]
+                _label [ htmlFor: 'FeedChoice_Discussions' ],
+                  'Challenges'
+              ]
             _input [ 'radio-toggle-button', type: 'radio', name: 'FeedChoice', value: 'global', id: 'FeedChoice_Global', onChange: @_handleFeedChoiceChange, checked: @state.feedChoice=='global'  ]
             _label [ htmlFor: 'FeedChoice_Global' ],
               'Global'
@@ -45,9 +51,6 @@
             _label [ htmlFor: 'FeedChoice_Personal' ],
               'Personal'

-            _input [ 'radio-toggle-button', type: 'radio', name: 'FeedChoice', value: 'discussions', id: 'FeedChoice_Discussions', onChange: @_handleFeedChoiceChange, checked: @state.feedChoice=='discussions' ]
-            _label [ htmlFor: 'FeedChoice_Discussions' ],
-              'Discussions'

             if window.is_kennisland
               _button ['button-success feed-selection-install-extension-button', onClick: @_toggle_create_challenge],


Fixed amounts fields pre filling when selecting variant in purchase order form

--- app/assets/javascripts/backend/trade.js.coffee
+++ app/assets/javascripts/backend/trade.js.coffee
@@ -83,7 +83,7 @@
             item.find(options.tax or "*[data-trade-component='tax']").val(data.tax_id)

           # Compute totals
-          if event.type == "change"
+          if ['change', 'readystatechange'].includes event.type
             E.trade.updateUnitPretaxAmount(item)

         error: (request, status, error) ->


Fixed minor fuckup

Fixed minor fuckup

--- app/assets/javascripts/dashboard.js.erb.coffee
+++ app/assets/javascripts/dashboard.js.erb.coffee
@@ -130,7 +130,8 @@
     $.getJSON "/chat/messages", (data) =>
       i = data.length - 1
       $.each data, (key, val) =>
-        @addMessage(val.timestamp,val.sender,val.content)
+        @chat_container.prepend("

#{val.sender}

#{val.content}

")
+        @correctWindow(true)
         if key == i
           window.setTimeout ( =>
             @correctWindow(true)
@@ -145,7 +146,7 @@

   addMessage: (timestamp,sender,message) ->
     readyForCorrection = @isReadingHistory()
-    @chat_container.prepend("

#{sender}

#{message}

")
+    @chat_container.append("

#{sender}

#{message}

")
     @correctWindow(readyForCorrection)

   validateInput: () ->


Close button

Close button

--- app/assets/javascripts/flash_message.js.coffee
+++ app/assets/javascripts/flash_message.js.coffee
@@ -4,7 +4,7 @@
 # 2) you've got a div with the id flash_hook somewhere in your html code

 show_ajax_message = (msg, type) ->
-  $("#flash-message").html "
#{msg}
"
+  $("#flash-message").html "
×#{msg}
"
   $("#flash-#{type}").delay(3000).slideUp()

 root = exports ? this


fix: Update the coordinates of the relic after a change of address

--- app/assets/javascripts/profile/location.coffee
+++ app/assets/javascripts/profile/location.coffee
@@ -185,15 +185,29 @@
       $('#map_canvas').set_marker($('#relic_latitude').val().toNumber(), $('#relic_longitude').val().toNumber())

     $('form.relic').on 'change',  '.foreign-location select, .foreign-location input, .street_input input, #relic_polish_relic', ->
+      $(this).addClass('wait')
       geocode_location (lat, lng) ->
-        $('#map_canvas').zoom_at(lat, lng)
-        map.removeMarkers()
-        $('#map_canvas').circle_marker(lat, lng)
-        $('form.relic').removeClass('geocoded')
-        $('#relic_geocoded').val("f")
+        $('#map_canvas').set_marker(lat, lng)
+        $form = $('form.relic')
+        $form.removeClass('wait')
+        $form.submit() if $form.hasClass('submit')

   countries_locations = jQuery.parseJSON($('#countries_location').html())
   $('#relic_country_code').select2()
   $('#relic_country_code').change ->
     location = countries_locations[$(this).val()]
     $('#map_canvas').zoom_at(location[0], location[1], 5)
+
+  $('form.relic').on 'ajax:before', ->
+
+    # Wait for lat/lng
+    if $(this).hasClass('wait')
+      $(this).addClass('submit')
+      return false
+
+    unless $(this).hasClass('geocoded')
+      alert("Podaj dokładny adres lub przeciągnij znacznik na mapę.")
+      return false
+
+  $('.administrative-level select').on 'change', ->
+    $('#relic_street').val('')


Update browserify

Update browserify

--- config/browserify/tests.coffee
+++ config/browserify/tests.coffee
@@ -5,7 +5,7 @@
     output: './_temp/browserify/knockback.tests.js'
     files: ['./test/spec/core/**/*.tests.coffee', './test/spec/plugins/**/*.tests.coffee']
     options:
-      basedir: path.resolve(__dirname, '../..')
+      # basedir: path.resolve(__dirname, '../..')
       shim:
         knockback: {path: './knockback.js', exports: 'kb', depends: {jquery: 'jQuery', underscore: '_', backbone: 'Backbone', knockout: 'ko'}}

@@ -13,6 +13,6 @@
     output: './_temp/browserify/knockback-core.tests.js'
     files: ['./test/spec/core/**/*.tests.coffee']
     options:
-      basedir: path.resolve(__dirname, '../..')
+      # basedir: path.resolve(__dirname, '../..')
       shim:
         knockback: {path: './knockback.js', exports: 'kb', depends: {jquery: 'jQuery', underscore: '_', backbone: 'Backbone', knockout: 'ko'}}


Implementing color selection for us status

--- app/coffee/modules/admin/project-values.coffee
+++ app/coffee/modules/admin/project-values.coffee
@@ -234,9 +234,13 @@
             status = target.scope().status
             $el.find(".select-color").hide()
             target.siblings(".select-color").show()
-            #body = angular.element("body")
-            #body.one "click", (event) ->
-            #    $el.find(".select-color").hide()
+            # Hide when click outside
+            body = angular.element("body")
+            body.on "click", (event) =>
+                if angular.element(event.target).parent(".select-color").length == 0
+                    $el.find(".select-color").hide()
+                    body.ubind("click")
+

         $el.on "click", ".select-color .color", (event) ->
             # Selecting one color on color selector


remove old unused functions

remove old unused functions

--- services/document-updater/app/coffee/RedisManager.coffee
+++ services/document-updater/app/coffee/RedisManager.coffee
@@ -176,25 +176,3 @@

 	getDocIdsInProject: (project_id, callback = (error, doc_ids) ->) ->
 		rclient.smembers keys.docsInProject(project_id: project_id), callback
-
-
-getDocumentsProjectId = (doc_id, callback)->
-	rclient.get keys.projectKey({doc_id:doc_id}), (err, project_id)->
-		callback err, {doc_id:doc_id, project_id:project_id}
-
-getAllProjectDocsIds = (project_id, callback)->
-	rclient.SMEMBERS keys.docsInProject(project_id:project_id), (err, doc_ids)->
-		if callback?
-			callback(err, doc_ids)
-
-getDocumentsAndExpire = (doc_ids, callback)->
-	multi = rclient.multi()
-	oneDay = 86400
-	doc_ids.forEach (doc_id)->
-		#		rclient.expire keys.docLines(doc_id:doc_id), oneDay, ->
-	doc_ids.forEach (doc_id)->
-		multi.get keys.docLines(doc_id:doc_id)
-	multi.exec (err, docsLines)->
-		callback err, docsLines
-
-


Remove some old logging

Remove some old logging

--- services/real-time/app/coffee/WebsocketController.coffee
+++ services/real-time/app/coffee/WebsocketController.coffee
@@ -154,16 +154,10 @@

 	applyOtUpdate: (client, doc_id, update, callback = (error) ->) ->
-		cbc_0 = 0 # Callback counter
-		cbc_1 = 0
 		Utils.getClientAttributes client, ["user_id", "project_id"], (error, {user_id, project_id}) ->
-			cbc_0++
 			return callback(error) if error?
 			return callback(new Error("no project_id found on client")) if !project_id?
-			# Omit this logging for now since it's likely too noisey
-			#logger.log {user_id, project_id, doc_id, client_id: client.id, update: update}, "applying update"
 			AuthorizationManager.assertClientCanEditProjectAndDoc client, doc_id, (error) ->
-				cbc_1++
 				if error?
 					logger.error {err: error, doc_id, client_id: client.id, version: update.v}, "client is not authorized to make update"
 					setTimeout () ->
@@ -178,11 +172,10 @@
 				metrics.set "editor.active-projects", project_id, 0.3
 				metrics.set "editor.active-users", user_id, 0.3

-				logger.log {user_id, doc_id, project_id, client_id: client.id, version: update.v, cbc_0, cbc_1}, "sending update to doc updater"
+				logger.log {user_id, doc_id, project_id, client_id: client.id, version: update.v}, "sending update to doc updater"

 				DocumentUpdaterManager.queueChange project_id, doc_id, update, (error) ->
 					if error?
 						logger.error {err: error, project_id, doc_id, client_id: client.id, version: update.v}, "document was not available for update"
 						client.disconnect()
 					callback(error)
-					#logger.log {user_id, project_id, doc_id, client_id: client.id}, "applied update"


Just for fun

Just for fun

--- app/models/Yeti.coffee
+++ app/models/Yeti.coffee
@@ -65,6 +65,10 @@
     super

+  onAction: (player) =>
+    mediator.dialogManager.showDialog {"text":"PIKACHU!!!","options":["Yeah...right."]}
+
+
   getSpritePacket: =>
     x = Math.floor((Date.now() - @spriteState.creationTime)/@spriteState.animationRate) % @tileSet.tilesX
     y = @spriteState.viewDirection


add support tab to erb files

add support tab to erb files

allow keymap tab to use in erb files

--- keymaps/emmet.cson
+++ keymaps/emmet.cson
@@ -30,7 +30,7 @@

 # language-specific Tab triggers
 # you can add more triggers by changing `grammar` attribute values
-'atom-text-editor[data-grammar="text html basic"]:not([mini]), atom-text-editor[data-grammar~="jade"]:not([mini]), atom-text-editor[data-grammar~="css"]:not([mini]), atom-text-editor[data-grammar~="sass"]:not([mini])':
+'atom-text-editor[data-grammar="text html basic"]:not([mini]), atom-text-editor[data-grammar~="erb"]:not([mini]), atom-text-editor[data-grammar~="jade"]:not([mini]), atom-text-editor[data-grammar~="css"]:not([mini]), atom-text-editor[data-grammar~="sass"]:not([mini])':
   'tab': 'emmet:expand-abbreviation-with-tab'

 '.platform-darwin atom-text-editor:not([mini])':


add missing keybindings to show specific item on windows

related issue on discuss: https://discuss.atom.io/t/ctrl-x-tab-selection/13615

--- keymaps/win32.cson
+++ keymaps/win32.cson
@@ -77,6 +77,15 @@
   'ctrl-k ctrl-down':  'window:focus-pane-below'
   'ctrl-k ctrl-left':  'window:focus-pane-on-left'
   'ctrl-k ctrl-right': 'window:focus-pane-on-right'
+  'alt-1': 'pane:show-item-1'
+  'alt-2': 'pane:show-item-2'
+  'alt-3': 'pane:show-item-3'
+  'alt-4': 'pane:show-item-4'
+  'alt-5': 'pane:show-item-5'
+  'alt-6': 'pane:show-item-6'
+  'alt-7': 'pane:show-item-7'
+  'alt-8': 'pane:show-item-8'
+  'alt-9': 'pane:show-item-9'

 'atom-workspace atom-text-editor':
   # Platform Bindings


Add support for '/me/authentications' endpoint


ProviderSelection: enable vagrant provider for sandbox

--- client/stacks/lib/views/stacks/onboarding/providerselectionview.coffee
+++ client/stacks/lib/views/stacks/onboarding/providerselectionview.coffee
@@ -21,7 +21,7 @@
       'aws', 'vagrant', 'azure', 'digitalocean', 'googlecloud', 'rackspace'
     ]
     enabledProviders = ['aws']
-    enabledProviders.push 'vagrant'  if globals.config.environment is 'dev'
+    enabledProviders.push 'vagrant'  if globals.config.environment in ['dev', 'sandbox']

     @providers = new kd.CustomHTMLView cssClass: 'providers box-wrapper'


:green_heart: Fix broken tests

:green_heart: Fix broken tests

--- spec/tabs-to-spaces-spec.coffee
+++ spec/tabs-to-spaces-spec.coffee
@@ -14,98 +14,103 @@
     filePath = path.join(directory, 'tabs-to-spaces.txt')
     fs.writeFileSync(filePath, '')
     fs.writeFileSync(path.join(directory, 'sample.txt'), 'Some text.\n')
-    atom.config.set('editor.tabLength', 3)

     waitsForPromise ->
-      atom.workspace.open(filePath).then (e) ->
-        editor = e
-        buffer = editor.getBuffer()
-        editor.setTabLength(2)
+      atom.workspace.open(filePath).then (e) -> editor = e
+
+    runs ->
+      buffer = editor.getBuffer()

     waitsForPromise ->
       atom.packages.activatePackage('tabs-to-spaces')

   describe 'tabify', ->
+    beforeEach ->
+      editor.setTabLength(3)
+
     it 'does not change an empty file', ->
       atom.workspaceView.trigger 'tabs-to-spaces:tabify'
       expect(editor.getText()).toBe ''

     it 'does not change spaces at the end of a line', ->
-      editor.insertText('foobarbaz     ')
+      buffer.setText('foobarbaz     ')
       atom.workspaceView.trigger 'tabs-to-spaces:tabify'
       expect(editor.getText()).toBe 'foobarbaz     '

     it 'does not change spaces in the middle of a line', ->
-      editor.insertText('foo  bar  baz')
+      buffer.setText('foo  bar  baz')
       atom.workspaceView.trigger 'tabs-to-spaces:tabify'
       expect(editor.getText()).toBe 'foo  bar  baz'

     it 'converts one tab worth of spaces to a tab', ->
       editor.setTabLength(2)
-      editor.insertText('  foo')
+      buffer.setText('  foo')
       atom.workspaceView.trigger 'tabs-to-spaces:tabify'
       expect(editor.getText()).toBe '\tfoo'

     it 'converts almost two tabs worth of spaces to one tab and some spaces', ->
       editor.setTabLength(4)
-      editor.insertText('       foo')
+      buffer.setText('       foo')
       atom.workspaceView.trigger 'tabs-to-spaces:tabify'
       expect(editor.getText()).toBe '\t   foo'

     it 'changes multiple lines of leading spaces to tabs', ->
       editor.setTabLength(4)
-      editor.insertText('    foo\n       bar')
+      buffer.setText('    foo\n       bar')
       atom.workspaceView.trigger 'tabs-to-spaces:tabify'
       expect(editor.getText()).toBe '\tfoo\n\t   bar'

     it 'leaves successive newlines alone', ->
       editor.setTabLength(2)
-      editor.insertText('  foo\n\n  bar\n\n  baz\n\n')
+      buffer.setText('  foo\n\n  bar\n\n  baz\n\n')
       atom.workspaceView.trigger 'tabs-to-spaces:tabify'
       expect(editor.getText()).toBe '\tfoo\n\n\tbar\n\n\tbaz\n\n'

   describe 'untabify', ->
+    beforeEach ->
+      editor.setTabLength(3)
+
     it 'does not change an empty file', ->
       atom.workspaceView.trigger 'tabs-to-spaces:untabify'
       expect(editor.getText()).toBe ''

     it 'does not change tabs at the end of a string', ->
-      editor.insertText('foobarbaz\t')
+      buffer.setText('foobarbaz\t')
       atom.workspaceView.trigger 'tabs-to-spaces:untabify'
       expect(editor.getText()).toBe 'foobarbaz\t'

     it 'does not change tabs in the middle of a string', ->
-      editor.insertText('foo\tbar\tbaz')
+      buffer.setText('foo\tbar\tbaz')
       atom.workspaceView.trigger 'tabs-to-spaces:untabify'
       expect(editor.getText()).toBe 'foo\tbar\tbaz'

     it 'changes one tab to the correct number of spaces', ->
       editor.setTabLength(2)
-      editor.insertText('\tfoo')
+      buffer.setText('\tfoo')
       atom.workspaceView.trigger 'tabs-to-spaces:untabify'
       expect(editor.getText()).toBe '  foo'

     it 'changes two tabs to the correct number of spaces', ->
       editor.setTabLength(2)
-      editor.insertText('\t\tfoo')
+      buffer.setText('\t\tfoo')
       atom.workspaceView.trigger 'tabs-to-spaces:untabify'
       expect(editor.getText()).toBe '    foo'

     it 'changes multiple lines of leading tabs to spaces', ->
       editor.setTabLength(2)
-      editor.insertText('\t\tfoo\n\t\tbar\n\n')
+      buffer.setText('\t\tfoo\n\t\tbar\n\n')
       atom.workspaceView.trigger 'tabs-to-spaces:untabify'
       expect(editor.getText()).toBe '    foo\n    bar\n\n'

   describe 'on save', ->
     it 'will untabify before an editor saves a buffer', ->
       atom.config.set('tabs-to-spaces.onSave', 'untabify')
-      editor.insertText('\t\tfoo\n\t\tbar\n\n')
+      buffer.setText('\t\tfoo\n\t\tbar\n\n')
       editor.save()
       expect(editor.getText()).toBe '    foo\n    bar\n\n'

     it 'will tabify before an editor saves a buffer', ->
       atom.config.set('tabs-to-spaces.onSave', 'tabify')
-      editor.insertText('    foo\n    bar\n\n')
+      buffer.setText('    foo\n    bar\n\n')
       editor.save()
       expect(editor.getText()).toBe '\t\tfoo\n\t\tbar\n\n'


Track teacher contacts

Track teacher contacts

--- app/views/teachers/TeachersContactModal.coffee
+++ app/views/teachers/TeachersContactModal.coffee
@@ -67,6 +67,9 @@
       data
       context: @
       success: ->
+        window.tracker?.trackEvent 'Teacher Contact',
+          category: 'Contact',
+          licensesNeeded: formValues.licensesNeeded
         @state.set({ sendingState: 'sent' })
         me.set('enrollmentRequestSent', true)
         setTimeout(=>


add import for typeahead

add import for typeahead

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -1,6 +1,7 @@
 window.korpApp = angular.module 'korpApp', [
                                             "ui.bootstrap.typeahead"
                                             "uib/template/typeahead/typeahead-popup.html"
+                                            "uib/template/typeahead/typeahead-match.html"
                                             "ui.bootstrap.tooltip"
                                             "uib/template/tooltip/tooltip-popup.html"
                                             "uib/template/tooltip/tooltip-html-popup.html"


add expectations about params to $.ajax

add expectations about params to $.ajax

--- spec/javascripts/patient_lists_spec.js.coffee
+++ spec/javascripts/patient_lists_spec.js.coffee
@@ -16,6 +16,9 @@
   it "calls @onSuccess when it's sucessful", ->
     spyOn(subj, 'onSuccess')
     spyOn($, 'ajax').andCallFake((params)->
+      expect(params.url).toEqual('/memberships')
+      expect(params.type).toMatch(/post/i)
+      expect(params.dataType).toMatch(/json/i)
       params.success()
     )
     subj.create(1, 123);


Degrade another warning to info status.

Degrade another warning to info status.

--- lib/assets/JavaScript.coffee
+++ lib/assets/JavaScript.coffee
@@ -437,7 +437,7 @@
                     isRequired: true
                   outgoingRelations.push outgoingRelation
               else
-                warnings.push new errors.SyntaxError(
+                infos.push new errors.SyntaxError(
                   "Skipping non-string JavaScriptAmdDefine item: #{node.print_to_string()}"
                 )
             ), this


separator line

separator line

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -209,6 +209,7 @@
     # In this case, user see modified icon(mark) on tab.
     # Explicitly setting modified start here prevent this
     @setModifiedState(false)
+
     if @editorElement.component?
       attachedPromise = Promise.resolve()
     else


add objective command

add objective command

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -118,6 +118,7 @@
       'narrow-ui:stop-insert': => @setReadOnly(true)
       'narrow-ui:update-real-file': => @updateRealFile()
       'narrow-ui:exclude-file': => @excludeFile()
+      'narrow-ui:exclude-files': => @excludeFiles()
       'narrow-ui:clear-excluded-files': => @clearExcludedFiles()
       'narrow-ui:move-to-next-file-item': => @moveToNextFileItem()
       'narrow-ui:move-to-previous-file-item': => @moveToPreviousFileItem()
@@ -375,6 +376,9 @@
   getQuery: ->
     @editor.lineTextForBufferRow(0)

+  excludeFiles: ->
+    # TODO
+
   excludeFile: ->
     filePath = @items.getSelectedItem()?.filePath
     if filePath? and (filePath not in @excludedFiles)


Fixed html quoting in contenteditable title.

--- app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee
@@ -335,6 +335,9 @@
       .replace(/<.+?>/g, '')
     title = ('' + title)
       .replace(/ /g, ' ')
+      .replace(/&/g, '&')
+      .replace(/</g, '<')
+      .replace(/>/g, '>')
     if title is '-'
       title = ''
     @ticket.title = title


2359: Changed map default zoom level

2359: Changed map default zoom level

--- app/assets/javascripts/backbone/views/dashboard_map_view.js.coffee
+++ app/assets/javascripts/backbone/views/dashboard_map_view.js.coffee
@@ -10,7 +10,8 @@
     # create the map
     @map = new google.maps.Map($('div.response_locations')[0], {
       mapTypeId: google.maps.MapTypeId.ROADMAP,
-      zoom: 1,
+      # This default zoom level shows most of the world on a big screen, but avoids grey bars at top/bottom.
+      zoom: 3,
       streetViewControl: false,
       draggableCursor: 'pointer'
     })


Patching a minor bug in cart emptying

Patching a minor bug in cart emptying

--- app/assets/javascripts/darkswarm/directives/empties_cart.js.coffee
+++ app/assets/javascripts/darkswarm/directives/empties_cart.js.coffee
@@ -5,12 +5,13 @@
   template: "{{action}} <strong>{{hub.name}}</strong>"
   link: (scope, elm, attr)->
     # A hub is selected, we're changing to a different hub, and the cart isn't empty
-    if CurrentHub.id and CurrentHub.id isnt scope.hub.id and not CurrentOrder.empty()
+    if CurrentHub.id and CurrentHub.id isnt scope.hub.id
       scope.action = attr.change
-      elm.bind 'click', (ev)->
-        ev.preventDefault()
-        if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
-          storage.clearAll() # One day this will have to be moar GRANULAR
-          Navigation.go scope.hub.path
+      unless CurrentOrder.empty()
+        elm.bind 'click', (ev)->
+          ev.preventDefault()
+          if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
+            storage.clearAll() # One day this will have to be moar GRANULAR
+            Navigation.go scope.hub.path
     else
       scope.action = attr.shop


Urgent fix - Not able to do form submission for Form having id =22 created by craig

--- coffee/fields/checkboxes.coffee
+++ coffee/fields/checkboxes.coffee
@@ -59,9 +59,9 @@
   evalCondition: (clicked_element, cid, condition, set_value) ->
     do(
        elem_val = '' ,
-       check_result = false
+       check_result = false
     ) =>
-      elem_val = clicked_element.find("[value = " + set_value+"]").is(':checked')
+      elem_val = clicked_element.find("[value = '" + set_value+"']").is(':checked')
       check_result = eval("'#{elem_val}' #{condition} 'true'")
       check_result

@@ -70,4 +70,4 @@
       for i, input_elem in $el.find('input:checked').length
         $("." + cid)
                 .find("[name = "+cid+"_1]")
-                .attr("required", required)
+                .attr("required", required)


Send holder on /api/queues/:key

Send holder on /api/queues/:key

--- controllers/queues.coffee
+++ controllers/queues.coffee
@@ -1,20 +1,26 @@
 models = require '../models'
+
+includeParams = [
+    model: models.Spot
+    include: [
+      model: models.User
+      as: 'Holder'
+    ]
+  ]

 exports.index = (req, res, next) ->
   # -- no params --

   # this is kind of an unfortunate query, but I'm not good enought at SQL nor
   # sequelize to be able to join all the queues and their lengths
-  models.Queue.findAll
-      include: [models.Spot]
+  models.Queue.findAll include: includeParams
     .then (queues) ->
       res.json queues

 exports.show = (req, res, next) ->
   key = req.params.key

-  models.Queue.findById key,
-      include: [models.Spot]
+  models.Queue.findById key, include: includeParams
     .then (queue) ->
       res.json queue


add Bucket#contributionCount computed property that only accepts positive contributions, use in bucket details

--- src/node_modules/bucket-model/bucket-model.coffee
+++ src/node_modules/bucket-model/bucket-model.coffee
@@ -22,6 +22,13 @@
         new ContributionModel(contrib)

       defineProperties @,
+        contributionCount:
+          deps: ['contributions']
+          get: ->
+            _.filter @contributions, (contribution) ->
+              contribution.amount.gt(0)
+            .length
+
         contributionTotal:
           deps: ['contributions']
           get: ->


Only show results view if an Editor is active

--- lib/find-results-view.coffee
+++ lib/find-results-view.coffee
@@ -1,5 +1,6 @@
 _ = require 'underscore'
 {View} = require 'space-pen'
+Editor = require 'editor'
 Selection = require 'selection'
 MarkerView = require './marker-view'

@@ -20,10 +21,14 @@
     super

   getEditor: ->
-    rootView.getActiveView()
+    activeView = rootView.getActiveView()
+    if activeView instanceof Editor then activeView else null

   markersUpdated: (@markers) ->
     @destroyMarkerViews()
+
+    editor = @getEditor()
+    return if not editor

     editor = @getEditor()
     for marker in @markers


Port PR #305 to 5.5 branch

Port PR #305 to 5.5 branch

--- lib/git-plus-commands.coffee
+++ lib/git-plus-commands.coffee
@@ -49,7 +49,7 @@
       commands.push ['git-plus:commit-amend', 'Commit Amend', -> GitCommitAmend(repo)]
       commands.push ['git-plus:add-and-commit', 'Add And Commit', -> git.add(repo, file: currentFile).then -> GitCommit(repo)]
       commands.push ['git-plus:add-all-and-commit', 'Add All And Commit', -> git.add(repo).then -> GitCommit(repo)]
-      commands.push ['git-plus:add-all-commit-and-push', 'Add All Commit And Push', -> git.add(repo).then -> GitCommit(repo, andPush: true)]
+      commands.push ['git-plus:add-all-commit-and-push', 'Add All, Commit And Push', -> git.add(repo).then -> GitCommit(repo, andPush: true)]
       commands.push ['git-plus:checkout', 'Checkout', -> GitBranch.gitBranches(repo)]
       commands.push ['git-plus:checkout-remote', 'Checkout Remote', -> GitBranch.gitRemoteBranches(repo)]
       commands.push ['git-plus:new-branch', 'Checkout New Branch', -> GitBranch.newBranch(repo)]


fix error of media click #39

fix error of media click #39

--- packages/custom/transparency/coffee/public/controllers/flowController.coffee
+++ packages/custom/transparency/coffee/public/controllers/flowController.coffee
@@ -126,10 +126,11 @@
         $scope.org = {}
         $scope.org.name = node.name
         $scope.org.orgType = if node.type is 'o' then 'org' else 'media'
-        $scope.org.street = node.addressData.street
-        $scope.org.zipCode = node.addressData.zipCode
-        $scope.org.city = node.addressData.city_de
-        $scope.org.country = node.addressData.country_de
+        if $scope.org.orgType is 'org'
+            $scope.org.street = node.addressData.street
+            $scope.org.zipCode = node.addressData.zipCode
+            $scope.org.city = node.addressData.city_de
+            $scope.org.country = node.addressData.country_de
         update()
         window.scrollTo 0,0


ComputeController: no need to recreate kite instance in ::build

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -219,7 +219,7 @@
       status      : Machine.State.Building
       percentage  : 0

-    machine.getBaseKite().disconnect()
+    machine.getBaseKite( createIfNotExists = no ).disconnect()

     call = @kloud.build { machineId: machine._id }


ComputeController: use globalFlags in JAccount for plans and show correct modal based on the plan

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -349,22 +349,25 @@
   # Utils beyond this point
   #

+  getUserPlan:->
+
+    knownPlans = ['super', 'professional', 'developer', 'hobbyist']
+    flags = KD.whoami().globalFlags or []
+
+    for plan in knownPlans
+      return plan  if "plan-#{plan}" in flags
+
+    return 'free'
+

   handleNewMachineRequest: ->

-
-    # Temporary waiting flow we will replace
-    # this with payment/plan fetch process.
-
-    # loading = new ComputePlansModal.Loading
-    # KD.utils.wait 1000, ->
-    #   loading.destroy()
-    #
-    # we can create the flow once it is ready,
-    # no need to make user wait to warn him. - SY
-
-    # KD.utils.defer -> new ComputePlansModal.Free
-    KD.utils.defer -> new ComputePlansModal.Paid
+    plan = @getUserPlan()
+
+    if plan in ['developer', 'professional', 'super']
+      new ComputePlansModal.Paid { plan }
+    else
+      new ComputePlansModal.Free { plan }

   triggerReviveFor:(machineId)->


actually make faders update when db changes

--- client/components/audiofader/audiofader.coffee
+++ client/components/audiofader/audiofader.coffee
@@ -64,6 +64,8 @@
       self.$('.fader').slider('disable')
     else
       self.$('.fader').slider('enable')
+  @autorun ->
+    self.audiofader.setLevel Template.currentData().level

 Template.audiofader.destroyed = ->
   @audiofader = null


Add terminal settings UI elements.

Add terminal settings UI elements.

--- client/ide/workspace/panes/settingspane.coffee
+++ client/ide/workspace/panes/settingspane.coffee
@@ -60,6 +60,36 @@
       click              : => log "show shortcuts"

   createTerminalSettings: ->
+
+    @terminalFont     = new KDSelectBox
+      selectOptions   : __terminalSettings.fonts
+
+    @terminalFontSize = new KDSelectBox
+      selectOptions   : __terminalSettings.fontSizes
+
+    @terminalTheme    = new KDSelectBox
+      selectOptions   : __terminalSettings.themes
+
+    @bell             = new KodingSwitch
+      size            : "tiny settings-on-off"
+
+    mainView          = KD.getSingleton "mainView"
+    @fullscreen       = new KodingSwitch
+      size            : "tiny settings-on-off"
+      callback        : (state) =>
+        if state
+          mainView.enableFullscreen()
+        else
+          mainView.disableFullscreen()
+        {menu} = @getOptions()
+        menu.contextMenu.destroy()
+        menu.click()
+      defaultValue  : mainView.isFullscreen()
+
+    @scrollback     = new KDSelectBox
+      selectOptions : __terminalSettings.scrollback
+
+
   pistachio: ->
     """

Use soft tabs                           {{> @useSoftTabs}}

@@ -80,4 +110,12 @@

{{> @shortcuts}}

Open Recent Files                       {{> @openRecentFiles}}

+

+
Font               {{> @terminalFont}}

+
Font size          {{> @terminalFontSize}}

+
Theme              {{> @terminalTheme}}

+
Scrollback         {{> @scrollback}}

+

+
Use visual bell                        {{> @bell}}

+
Fullscreen                             {{> @fullscreen}}

     """


Support to unsubscribe daily emails

Support to unsubscribe daily emails

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -247,12 +247,12 @@
       '/member/:username': ({params:{username}})->
         @handleRoute "/#{username}", replaceState: yes

-      '/:name?/Unsubscribe/:unsubscribeToken/:all?': \
-      ({params:{unsubscribeToken, all}})->
-        all              = decodeURIComponent all
+      '/:name?/Unsubscribe/:unsubscribeToken/:opt?': \
+      ({params:{unsubscribeToken, opt}})->
+        opt              = decodeURIComponent opt
         unsubscribeToken = decodeURIComponent unsubscribeToken
         KD.remote.api.JEmailNotificationGG.unsubscribeWithId \
-        unsubscribeToken, all, (err, content)=>
+        unsubscribeToken, opt, (err, content)=>
           if err or not content
             title   = 'An error occured'
             content = 'Invalid unsubscribe token provided.'


StackEditor: save flow for stacktemplate and readme content

--- client/new-stack-editor/lib/views/index.coffee
+++ client/new-stack-editor/lib/views/index.coffee
@@ -20,6 +20,8 @@
 CredentialsController = require '../controllers/credentials'

 { yamlToJson } = require 'app/util/stacks/yamlutils'
+updateStackTemplate = require 'app/util/stacks/updatestacktemplate'
+
 Help = require './help'

@@ -143,6 +145,29 @@
       @_current = id

     kd.utils.defer @editor.bound 'focus'
+
+
+  save: (callback) ->
+
+    { title, config = {} } = stackTemplate = @getData()
+
+    rawContent  = @editor.getContent()
+    description = @readme.getContent()
+
+    [ err, convertedDoc ] = @getConvertedContent()
+    return callback err  if err
+
+    { contentObject, content } = convertedDoc
+
+    config.buildDuration = contentObject.koding?.buildDuration
+    template = convertedDoc.content
+
+    dataToSave = {
+      title, stackTemplate, template, description, rawContent
+    }
+
+    debug 'saving data', dataToSave
+    updateStackTemplate dataToSave, callback

   check: (callback) ->


Last task logic Default to true. Update if more than one rectangle is marked. Should catch the case of zero rectangles marked (full page illustration.)

--- overrides.coffee
+++ overrides.coffee
@@ -27,7 +27,7 @@
 ms.on 'marking-surface:add-tool', (tool) ->
   @rescale() if @scaleX is 0

-LAST_TASK = false
+LAST_TASK = true
 INITIAL_STEPS = 2 # number of initial steps before annotating rectangles
 ANNOTATION_STEPS = 2 # number of annotation steps per rectangle
 MARGIN = 25 # margin on cropped images
@@ -60,16 +60,16 @@
   if task.key in ['details', 'parts'] and rectangles.length > 0
     page_zoom.disabled = false
     current_tool = rectangles[rect_index]
-    w = current_tool.mark.width + MARGIN * 2
-    h = current_tool.mark.height + MARGIN * 2
-    ms.rescale current_tool.mark.left - MARGIN, current_tool.mark.top - MARGIN, w, h
+    w = current_tool?.mark.width + MARGIN * 2
+    h = current_tool?.mark.height + MARGIN * 2
+    ms.rescale current_tool?.mark.left - MARGIN, current_tool?.mark.top - MARGIN, w, h

   if task.key is 'details'
-    value = current_tool.mark.details
+    value = current_tool?.mark.details
     task.reset value if value?

   if task.key is 'details'
-    LAST_TASK = rect_index == rectangles.length - 1
+    LAST_TASK = rect_index == rectangles.length - 1 if rectangles.length
     if LAST_TASK
       task.next = 'review'
     else
@@ -88,8 +88,8 @@
     label = 'Finish'
   decisionTree.currentTask.confirmButton.innerHTML = label if label?

-  current_tool.mark.details = value if key is 'details'
-  current_tool.mark.parts = value if key is 'parts'
+  current_tool?.mark.details = value if key is 'details'
+  current_tool?.mark.parts = value if key is 'parts'

 classify_page.on classify_page.LOAD_SUBJECT, (e, subject)->
   ms.rescale 0, 0, subjectViewer.maxWidth, subjectViewer.maxHeight


Add a progress bar for replacements.

Add a progress bar for replacements.

Fixes #49

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -13,6 +13,9 @@

       @ul outlet: 'errorMessages', class: 'error-messages block'
       @ul outlet: 'infoMessages', class: 'info-messages block'
+      @div outlet: 'replacmentInfoBlock', class: 'block', =>
+        @progress outlet: 'replacementProgress', class: 'inline-block'
+        @span outlet: 'replacmentInfo', class: 'inline-block', 'Replaced 2 files of 10 files'

       @div class: 'find-container block', =>
         @label class: 'text-subtle', 'Find'
@@ -83,6 +86,29 @@

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

+    @handleEventsForReplace()
+
+  handleEventsForReplace: ->
+    @replacementsMade = 0
+    @model.on 'replace', (promise) =>
+      # if @model.getPathCount()
+      @replacementsMade = 0
+      @replacmentInfoBlock.show()
+      @replacementProgress.removeAttr('value')
+
+    @model.on 'path-replaced', (result) =>
+      @replacementsMade++
+      @replacementProgress[0].value = @replacementsMade / @model.getPathCount()
+      @replacmentInfo.text("Replaced #{@replacementsMade} of #{_.pluralize(@model.getPathCount(), 'file')}")
+
+    @model.on 'finished-replacing', ({pathsReplaced, replacements}) =>
+      @replacmentInfoBlock.hide()
+      if pathsReplaced
+        @addInfoMessage("Replaced #{_.pluralize(replacements, 'result')} in #{_.pluralize(pathsReplaced, 'file')}")
+      else
+        shell.beep()
+        @addInfoMessage("Nothing replaced")
+
   attach: ->
     if @hasParent()
       el = @lastFocusedElement or @findEditor
@@ -136,12 +162,19 @@
     @showResultPane()
     @model.search(@findEditor.getText(), paths)

+  replaceAll: ->
+    @clearMessages()
+    pattern = @findEditor.getText()
+    replacementText = @replaceEditor.getText()
+    @model.replace(pattern, replacementText, @model.getPaths())
+
   showResultPane: ->
     options = null
     options = {split: 'right'} if config.get('find-and-replace.openProjectFindResultsInRightPane')
     rootView.openSingletonSync(ResultsPaneView.URI, options)

   clearMessages: ->
+    # @replacmentInfoBlock.hide()
     @errorMessages.hide().empty()
     @infoMessages.hide().empty()

@@ -158,19 +191,3 @@
     if editor
       pattern = editor.activeEditSession.getSelectedText()
       @findEditor.setText(pattern)
-
-  replaceAll: ->
-    @clearMessages()
-
-    pattern = @findEditor.getText()
-    replacementText = @replaceEditor.getText()
-    promise = @model.replace(pattern, replacementText, @model.getPaths())
-
-    if @model.getPathCount()
-      @model.once 'finished-replacing', ({pathsReplaced, replacements}) =>
-        @addInfoMessage("Replaced #{_.pluralize(replacements, 'result')} in #{_.pluralize(pathsReplaced, 'file')}")
-    else
-      shell.beep()
-      @addInfoMessage("Nothing replaced")
-
-    promise


moved from 403 to 401

moved from 403 to 401

--- lib/Context.coffee
+++ lib/Context.coffee
@@ -58,7 +58,7 @@
             @logger = @_createLogger @ID

             @_checkDeprecation config
-
+
             @permissions = config.permissions

             @identity = @_createIdentity()
@@ -377,7 +377,7 @@
                 permit = (ok)=>
                     if !ok
                         @logger.warning 'access to %s denied to %s', key, identity.id
-                        return fn new floyd.error.Forbidden @ID+'.'+key
+                        return fn new floyd.error.Unauthorized @ID+'.'+key

                     if check = checks.shift()
                         check permit


Simplify requestProvider

Simplify requestProvider

--- lib/snippets-provider.coffee
+++ lib/snippets-provider.coffee
@@ -6,32 +6,27 @@
   id: 'autocomplete-snippets-snippetsprovider'
   selector: '*'

-  requestHandler: (options) ->
-    return unless options?.cursor? and options.prefix?.length
-    scopeSnippets = atom.config.get('snippets', {scope: options.cursor.getScopeDescriptor()})
+  requestHandler: ({cursor, prefix}) ->
+    return unless prefix?.length
+    scopeSnippets = atom.config.get('snippets', {scope: cursor.getScopeDescriptor()})
     snippets = []
     for key, val of scopeSnippets
       val.label = key
       snippets.push(val)

-    suggestions = @findSuggestionsForWord(snippets, options.prefix)
-    return unless suggestions?.length
-    return suggestions
+    @findSuggestionsForPrefix(snippets, prefix)

-  findSuggestionsForWord: (snippets, prefix) ->
+  findSuggestionsForPrefix: (snippets, prefix) ->
     return [] unless snippets? and prefix?

     # Only accept snippets that start with prefix
     matchesPrefix = (snippet) ->
       snippet.prefix.lastIndexOf(prefix, 0) isnt -1

-    results = for snippet in snippets when matchesPrefix(snippet)
+    for snippet in snippets when matchesPrefix(snippet)
       suggestion =
         snippet: snippet
         word: snippet.prefix
         prefix: prefix
         label: snippet.name
         isSnippet: true
-      suggestion
-
-    return results


Add BBC Radio Pop Out Support

Add BBC Radio Pop Out Support

--- src/background/mappings.coffee
+++ src/background/mappings.coffee
@@ -20,6 +20,7 @@
   'audiogalaxy': 'AudiogalaxyController.js'
   'audiomack': 'AudiomackController.js'
   'bandcamp': 'BandcampController.js'
+  'bbc': 'BBCRadioPopOutController.js'
   'beatsmusic': 'BeatsMusicController.js'
   'bop': 'BopController.js'
   'bronytunes': 'BronyTunesController.js'


[browser] Add Application.instance for get app instance with remote.require

--- src/browser/Application.coffee
+++ src/browser/Application.coffee
@@ -7,6 +7,7 @@
 {Disposable}    = require "event-kit"

 module.exports = class Application extends EventEmitter
+    @instance       : null

     windows         : null
     lastFocusedWindow   : null
@@ -22,6 +23,8 @@
         @packageJson    = require "../package.json"
         @command        = require "./CommandManager"

+        Object.defineProperty Application, "instance",
+            value   : @

         @handleEvents()


Added Model#initSyncMachine

Added Model#initSyncMachine

--- src/chaplin/models/model.coffee
+++ src/chaplin/models/model.coffee
@@ -10,11 +10,13 @@
     # Mixin a Subscriber
     _(@prototype).extend Subscriber

-    # Creates a new deferred and mixes it into the model
-    # This method can be called multiple times to reset the
-    # status of the Deferred to 'pending'.
+    # Mixin a Deferred
     initDeferred: ->
       _(this).extend $.Deferred()
+
+    # Mixin a synchronization state machine
+    initSyncMachine: ->
+      _(this).extend SyncMachine

     # This method is used to get the attributes for the view template
     # and might be overwritten by decorators which cannot create a


renderElements uses createDocumentFragment to improve speed

--- lib/web/views/ki-flow.coffee
+++ lib/web/views/ki-flow.coffee
@@ -63,33 +63,42 @@
 # - supports modifier function for named fields
 # - gFun is a global function applied to all matched element
 this.renderElements = (destId, templateId, data = {}, gFun = null) ->
-  template_source = $(templateId)
-  if !template_source.is("script")
-    throw "Template '#{templateId}' is not a script tag!"
-  if template_source.size() == 0
-    throw "Could not locate template script tag for '#{templateId}'"
   dest = $(destId)
   if dest.size() == 0
     throw "Could not locate destination '#{destId}'"
-  destTag = document.createElement(dest[0].tagName)

   if !Array.isArray(data)
     data = [data]
-  destTag.innerHTML = template_source.html()
-  original_template = destTag
+
+  original_template = document.createElement(dest[0].tagName)
+  original_template.innerHTML = getTemplate(templateId)
   clone = data.length > 1
   # repeat for every item in data, good for rendering a list
+  fragment = document.createDocumentFragment()
   for item in data
     template = original_template
     if clone
       template = original_template.cloneNode(true)
     fillTemplate($(template), item, gFun)
     while template.hasChildNodes()
-      dest.append template.removeChild(template.firstChild)
+      fragment.appendChild( template.removeChild(template.firstChild))
+  dest.append(fragment)
   if clone
     dest
   else
     template
+
+templateCache = {}
+getTemplate = (templateId) ->
+  cached = templateCache[templateId]
+  if !cached?
+    template_source = $(templateId)
+    if !template_source.is("script")
+      throw "Template '#{templateId}' is not a script tag!"
+    if template_source.size() == 0
+      throw "Could not locate template script tag for '#{templateId}'"
+    templateCache[templateId] = cached = template_source.html()
+  cached

 fillTemplate = (template, item, gFun) ->
   # go throug every named key-value pair in item


Small comment

Small comment

--- lib/compile.coffee
+++ lib/compile.coffee
@@ -296,6 +296,8 @@
         helpers.logVerbose 'match 4'
         updated = true

+        # bo-src and bo-href don't use interpolations and use expressions directly so we
+        # we need to wrap them
         if _.contains attrName, 'bo-'
           match = match.replace attrVal, """{{expression "#{attrVal}"}}"""


retain connection - resolves time overlap when multiple watchers are established also results in better performance

--- lib/connect.coffee
+++ lib/connect.coffee
@@ -1,25 +1,31 @@
 {Server, Db} = require 'mongodb'
 {getTimestamp} = require './util'

+existingStream = null
+
 module.exports = ({host, port}, done) ->
+  if existingStream
+    return done null, existingStream

-  client = new Db 'local', new Server(host, port, {native_parser: true}), {w: 0}
+  else
+    client = new Db 'local', new Server(host, port, {native_parser: true}), {w: 0}

-  client.open (err) ->
-    return done err if err
-
-    client.collection 'oplog.rs', (err, oplog) ->
+    client.open (err) ->
       return done err if err

-      connOpts =
-        tailable: true
-        awaitdata: true
-        oplogreplay: true # does this do anything?
-        numberOfRetries: -1
+      client.collection 'oplog.rs', (err, oplog) ->
+        return done err if err

-      currentTime = getTimestamp()
+        connOpts =
+          tailable: true
+          awaitdata: true
+          oplogreplay: true # does this do anything?
+          numberOfRetries: -1

-      cursor = oplog.find {ts: {$gte: currentTime}}, connOpts
-      stream = cursor.stream()
+        currentTime = getTimestamp()

-      done null, stream
+        cursor = oplog.find {ts: {$gte: currentTime}}, connOpts
+        stream = cursor.stream()
+        existingStream = stream
+
+        done null, stream


Make node title a draggable attribute for CODAP

If `@props.showGraphButton` is true (i.e. we are in CODAP), allow dragging
of the title node in the same way that the graph icon can be dragged.

[#144034943]

--- src/code/views/node-view.coffee
+++ src/code/views/node-view.coffee
@@ -325,16 +325,21 @@
             if @props.data.isTransfer
               (div {className: "node-title"}) # empty title to set node width the same
             else
-              (NodeTitle {
-                isEditing: @props.editTitle
-                title: @props.data.title
-                onChange: @changeTitle
-                onStopEditing: @stopEditing
-                onStartEditing: @startEditing
-                node: @props.data
-                nodeKey: @props.nodeKey
-                graphStore: @props.graphStore
-              })
+              (div {
+                draggable: @props.showGraphButton
+                onDragStart: ((evt) => @handleCODAPAttributeDrag evt, @props.data.codapID)
+              },
+                (NodeTitle {
+                  isEditing: @props.editTitle
+                  title: @props.data.title
+                  onChange: @changeTitle
+                  onStopEditing: @stopEditing
+                  onStartEditing: @startEditing
+                  node: @props.data
+                  nodeKey: @props.nodeKey
+                  graphStore: @props.graphStore
+                })
+              )
           )
         )
         (div {className: @nodeSliderClasses() ,"data-node-key": @props.nodeKey},


Remove unneeded step declaration

Remove unneeded step declaration

--- src/coffee/cilantro/core.coffee
+++ src/coffee/cilantro/core.coffee
@@ -80,7 +80,7 @@

         isSerranoOutdated: ->
             serranoVersion = @getSerranoVersion()
-            for i in [0..2] by 1
+            for i in [0..2]
                 if serranoVersion[i] < c.minimumSerranoVersion[i]
                     return true


removed warning which was more an info

removed warning which was more an info

--- src/coffee/elasticsearch.coffee
+++ src/coffee/elasticsearch.coffee
@@ -16,8 +16,6 @@
 			, (error, response) ->
 				if (error)
 					node.error error, response
-				else
-					node.warn "written to elasticsearch"
 				es.close

 	RED.nodes.registerType("elasticsearch",ElasticsearchNode);


Initialise metrics at begining of app

Initialise metrics at begining of app

--- services/clsi/app.coffee
+++ services/clsi/app.coffee
@@ -1,3 +1,8 @@
+Metrics = require "metrics-sharelatex"
+Metrics.initialize("clsi")
+Metrics.open_sockets.monitor(logger)
+Metrics.memory.monitor(logger)
+
 CompileController = require "./app/js/CompileController"
 Settings = require "settings-sharelatex"
 logger = require "logger-sharelatex"
@@ -11,11 +16,6 @@

 Path = require "path"
 fs = require "fs"
-
-Metrics = require "metrics-sharelatex"
-Metrics.initialize("clsi")
-Metrics.open_sockets.monitor(logger)
-Metrics.memory.monitor(logger)

 ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager"
 OutputCacheManager = require "./app/js/OutputCacheManager"


Remove function accidentally committed with merge

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -20,10 +20,3 @@
     basename = path.basename(filePath)
     position = basename.indexOf('.')
     if position > 0 then basename[position..] else ''
-
-  updateEditorsForPath: (oldPath, newPath) ->
-    editors = atom.workspace.getTextEditors()
-    for editor in editors
-      filePath = editor.getPath()
-      if filePath?.startsWith(oldPath)
-        editor.getBuffer().setPath(filePath.replace(oldPath, newPath))


Added method for getting cursor from collection/cursor. Added method for observing a cursor without firing added() callback for existing items.

--- Collections.coffee
+++ Collections.coffee
@@ -20,13 +20,22 @@
   get: (arg) ->
     if Types.isString(arg)
       # Collection name.
-      return global[arg]
+      global[arg]
     else if @isCursor(arg)
-      return arg.collection
+      arg.collection
     else if @isCollection(arg)
-      return arg
+      arg
     else
-      return null
+      null
+
+# @param {String|Meteor.Collection|Cursor} arg
+# @returns The underlying Cursor or null if none is found.
+  getCursor: (arg) ->
+    if @isCursor(arg)
+      arg
+    else
+      collection = @get(arg)
+      collection.find()

 # @param {String|Meteor.Collection|Cursor} arg
 # @returns {Meteor.Collection|Cursor} Either a Meteor collection, a cursor, or null if none is
@@ -83,3 +92,24 @@
   removeAllDocs: (collection) ->
     _.each collection.find().fetch(), (order) ->
       collection.remove(order._id)
+
+# @param {Meteor.Collection|Cursor|String} arg
+# @param {Object} args
+# @param {Function} [args.added]
+# @param {Function} [args.changed]
+# @param {Function} [args.removed]
+# @param {Boolean} [args.ignoreExisting=false] - Doesn't fire the insert() callback for existing
+# items in the collection.
+  observe: (collection, args) ->
+    args ?= {}
+    isObserving = !args.ignoreExisting
+    wrapHandler = (handler) -> -> handler.apply(@, arguments) if isObserving
+    observeArgs = {}
+    _.each ['added', 'changed', 'removed'], (methodName) ->
+      handler = args[methodName]
+      if handler
+        observeArgs[methodName] = wrapHandler(handler)
+    @getCursor(collection).observe(observeArgs)
+    # TODO(aramk) Temporary solution to prevent refreshing due to added callback firing for all
+    # existing docs.
+    isObserving = true


Verify is renaming when abort() or done() is called

--- lib/watcher.coffee
+++ lib/watcher.coffee
@@ -45,7 +45,7 @@

   activate: ->
     # Setup model
-    @ripper = new @module.Ripper @editor #TODO deprecate pass Editor in v0.4
+    @ripper = new @module.Ripper()

     # Start listening
     @editorView.on 'cursor:moved', @onCursorMoved
@@ -91,13 +91,7 @@
     text = @editor.buffer.getText()
     if text isnt @cachedText
       @cachedText = text
-      @ripper.parse text, (errors) => #TODO deprecate verification of array in v0.4
-        @onParseEnd unless errors?
-          null
-        else unless Array.isArray errors
-          [errors]
-        else
-          errors
+      @ripper.parse text, @onParseEnd
     else
       @onParseEnd()

@@ -116,7 +110,7 @@

   createErrors: (errors) =>
     @errorMarkers = for { location, range, message } in errors
-      if location? #TODO deprecate verification of the location in v0.4
+      if location? #TODO deprecate verification of the location in v0.5
         range = locationDataToRange location

       marker = @editor.markBufferRange range
@@ -181,7 +175,7 @@

   abort: =>
     # When this editor is not active, do nothing.
-    return unless @isActive()
+    return unless @isActive() and @renamingCursor? and @renamingMarkers?

     # Verify all cursors are in renaming markers.
     # When the cursor is out of marker at least one, abort renaming.
@@ -200,7 +194,7 @@

   done: ->
     # When this editor is not active, returns false to abort keyboard binding.
-    return false unless @isActive()
+    return false unless @isActive() and @renamingCursor? and @renamingMarkers?

     # Stop renaming life cycle.
     @editorView.off 'cursor:moved', @abort


Detect if ID has already been created

Detect if ID has already been created

--- app._coffee
+++ app._coffee
@@ -87,6 +87,8 @@
             DeepZoomImage.create _, contentPath, contentPath, DEFAULT_TILE_SIZE,
               DEFAULT_TILE_OVERLAP, DEFAULT_FORMAT
         catch err
+            if error?.code is 1
+                return console.log "ID #{id} is already taken"
             console.error {error: err?.stack ? err}

 app.get '/:id', (req, res, _) ->


Trailing whitespace causes CI linter to reject the change.

--- src/hooks-worker-client.coffee
+++ src/hooks-worker-client.coffee
@@ -301,7 +301,7 @@

     @runner.hooks.afterAll (transactions, hookCallback) =>

-      # This is needed for transaction modification integration tests:
+      # This is needed for transaction modification integration tests:
       # https://github.com/apiaryio/dredd-hooks-template/blob/master/features/execution_order.feature
       if process.env['TEST_DREDD_HOOKS_HANDLER_ORDER'] == "true"
         console.error 'FOR TESTING ONLY'


Add logging to auth.coffee

Add logging to auth.coffee

--- auth.coffee
+++ auth.coffee
@@ -2,50 +2,50 @@
 GoogleStrategy = require('passport-google-oauth').OAuth2Strategy
 models         = require './models'

-hostname       = ''
-
-# Passport session setup.
-#   To support persistent login sessions, Passport needs to be able to
-#   serialize users into and deserialize users out of the session.  Typically,
-#   this will be as simple as storing the user ID when serializing, and finding
-#   the user by ID when deserializing.  However, since this example does not
-#   have a database of user records, the complete Google profile is
-#   serialized and deserialized.
-passport.serializeUser (user, done) ->
-  done null, user.id
-
-passport.deserializeUser (id, done) ->
-  models.User.findById id
-    .then (user) ->
-      done null, user
-    .error (err) ->
-      done err, null
-
-passport.use new GoogleStrategy
-  clientID: process.env.GOOGLE_CLIENT_ID
-  clientSecret: process.env.GOOGLE_CLIENT_SECRET
-  callbackURL: "#{hostname}/auth/google/callback"
-, (accessToken, refreshToken, profile, done) ->
-  # This is an array, but we need just a single email, so let's at least be
-  # smart about which array element we take.
-  emails = profile.emails.filter (email, idx, arr) ->
-    email.type == 'account' and email.value.indexOf('andrew.cmu.edu') > 0
-  .map (email, idx, arr) -> email.value
-
-  user =
-    id:          profile.id
-    displayName: profile.displayName
-    givenName:   profile.name.givenName
-    familyName:  profile.name.familyName
-    email:       emails[0]
-
-  models.User.findOrCreate where: user
-    .spread (user, created) ->
-      done null, user
-    .error (err) ->
-      done err, null
-
 module.exports = (app) ->
   hostname = app.get 'hostname'
+  console.log hostname
+
+  # Passport session setup.
+  #   To support persistent login sessions, Passport needs to be able to
+  #   serialize users into and deserialize users out of the session.  Typically,
+  #   this will be as simple as storing the user ID when serializing, and finding
+  #   the user by ID when deserializing.  However, since this example does not
+  #   have a database of user records, the complete Google profile is
+  #   serialized and deserialized.
+  passport.serializeUser (user, done) ->
+    done null, user.id
+
+  passport.deserializeUser (id, done) ->
+    models.User.findById id
+      .then (user) ->
+        done null, user
+      .error (err) ->
+        done err, null
+
+  passport.use new GoogleStrategy
+    clientID: process.env.GOOGLE_CLIENT_ID
+    clientSecret: process.env.GOOGLE_CLIENT_SECRET
+    callbackURL: "#{hostname}/auth/google/callback"
+  , (accessToken, refreshToken, profile, done) ->
+    # This is an array, but we need just a single email, so let's at least be
+    # smart about which array element we take.
+    emails = profile.emails.filter (email, idx, arr) ->
+      email.type == 'account' and email.value.indexOf('andrew.cmu.edu') > 0
+    .map (email, idx, arr) -> email.value
+
+    user =
+      id:          profile.id
+      displayName: profile.displayName
+      givenName:   profile.name.givenName
+      familyName:  profile.name.familyName
+      email:       emails[0]
+
+    models.User.findOrCreate where: user
+      .spread (user, created) ->
+        done null, user
+      .error (err) ->
+        done err, null
+
   app.use passport.initialize()
   app.use passport.session()


Client side validation - simulations -> input from json

--- app/assets/javascripts/scenarios/scenario_registration.js.coffee
+++ app/assets/javascripts/scenarios/scenario_registration.js.coffee
@@ -103,18 +103,14 @@
       parameter.allowed_values = allowed
       delete parameter.min
       delete parameter.max
-      $('#param-config #param_min').removeAttr('required')
-      $('#param-config #param_max').removeAttr('required')
-      $('#param-config #param_allowed_area').attr('required','required')
+

     else if (param_type == 'integer' || param_type == 'float')
       param_min = $('#param-config #param_min').val()
       parameter.min = (param_type == 'integer' && parseInt(param_min) || parseFloat(param_min))
       param_max = $('#param-config #param_max').val()
       parameter.max = (param_type == 'integer' && parseInt(param_max) || parseFloat(param_max))
-      $('#param-config #param_min').attr('required','required')
-      $('#param-config #param_max').attr('required','required')
-      $('#param-config #param_allowed_area').removeAttr('required')
+

     @updateTree()
     @loadParamToEditor(null)
@@ -187,12 +183,22 @@
     if type == 'string'
       $('#param-allowed').show()
       $('#param-range').hide()
+      $('#param-config #param_min').removeAttr('required')
+      $('#param-config #param_max').removeAttr('required')
+      $('#param-config #param_allowed_area').attr('required','required')
     else if (type == 'integer' || type == 'float')
+
       $('#param-allowed').hide()
       $('#param-range').show()
+      $('#param-config #param_min').attr('required','required')
+      $('#param-config #param_max').attr('required','required')
+      $('#param-config #param_allowed_area').removeAttr('required')
     else
       $('#param-allowed').hide()
       $('#param-range').hide()
+      $('#param-config #param_min').removeAttr('required')
+      $('#param-config #param_max').removeAttr('required')
+      $('#param-config #param_allowed_area').removeAttr('required')

   loadParamToEditor: (p) =>
     if p
@@ -284,11 +290,17 @@
     $("#input-designer #param-config").enable()
     $("#input-upload").hide()
     $("#input-upload #simulation_input_file").disable()
+    $('#param-config #param_id').attr('required','required')
+    $('#param-config #param_min').attr('required','required')
+    $('#param-config #param_max').attr('required','required')

   inputDesignerOff: (event) =>
     @inputDesignerOnDiv.removeClass("clicked")
     @inputDesignerOffDiv.addClass("clicked")
-
+    $('#param-config #param_id').removeAttr('required')
+    $('#param-config #param_min').removeAttr('required')
+    $('#param-config #param_max').removeAttr('required')
+    $('#param-config #param_allowed_area').removeAttr('required')
     $("#input-designer").hide()
     $("#input-designer #param-config").disable()
     $("#input-upload").show()


marking required fields in products_form template

--- app/assets/javascripts/templates/product_form_template.js.coffee
+++ app/assets/javascripts/templates/product_form_template.js.coffee
@@ -13,13 +13,13 @@

         <fieldset class="float-left">

-                Name
+                Name*

                     <input type="text" value="{{name}}" size="30" name="product[name]" id="product_name" class="input-xlarge">

-                Instagram tag
+                Instagram Tag*

                       #
@@ -28,13 +28,13 @@

-                Description
+                Description*

                     <textarea rows="5" name="product[description]" id="product_description" cols="40" class="input-xlarge">{{description}}</textarea>

-              Price
+              Price*

                       $
@@ -45,7 +45,7 @@
         </fieldset>
         <fieldset class='float-left'>

-                Quantity
+                Quantity*

                     <input type="text" value="{{^unlimitedQuantity}}{{quantity}}{{/unlimitedQuantity}}" size="30" name="product[quantity]" id="product_quantity" {{#unlimitedQuantity}}disabled="disabled"{{/unlimitedQuantity}} class="input-xlarge">


[WIP] Switch to fixed-timestep system with 60fps cap

--- js/particles.coffee
+++ js/particles.coffee
@@ -58,15 +58,17 @@

   force: (pos, vel) =>
     difference = Vector.subtract(MOUSE.pos, pos)
-    dist_sq = Math.max(difference.mag_sq(), 1)
+    # Set the minimum at 25 to avoid a mouse-singularity
+    dist_sq = Math.max(difference.mag_sq(), 25)

     velocity = @vel.mag()
-    g_force = 10000000 * MOUSE.mass * @mass / dist_sq
-    spring_force = 10000 * Math.sqrt(dist_sq)
+    g_force = 1000 * MOUSE.mass * @mass / dist_sq
+    spring_force = 0.01 * Math.sqrt(dist_sq)
+
     spring_and_g_force_vector = Vector.unit(difference)
-      .s_mult(Math.min(spring_force, g_force))
+      .s_mult(Math.min(g_force, spring_force))

-    damp_force_vector = Vector.s_mult(@vel, -10)
+    damp_force_vector = Vector.s_mult(@vel, -0.01)

     return spring_and_g_force_vector.plus(damp_force_vector)

@@ -95,7 +97,7 @@
       @pos.y = Math.min(Math.max(@pos.y, 0), canvas.height)
       @vel.y = -@vel.y * 0.5

-    @mass = Math.min(Math.max(@vel.mag()/10000, 0.1), 1)
+    @mass = Math.min(Math.max(@vel.mag()/1, 0.1), 1)

   draw: (ctx) =>
     ctx.save()
@@ -111,13 +113,14 @@
     ctx.restore()

 class Animator
+  MS_PER_TICK: 1000 / 60.0
   constructor: (canvas) ->
     @$canvas = canvas
     @canvas = @$canvas[0]
     @$canvas.mousemove(@update_mouse_pos)
     @$canvas.mousedown(@explode_particles)
-    @particles = @generate_particles(50)
-    @tick = .001
+    @particles = @generate_particles(1)
+    @tick = window.performance.now()

   generate_particles: (num) ->
     colors = ['#FF5454', '#FF904A', '#FFCD57', '#FFFC85', '#FFF']
@@ -140,24 +143,23 @@
       difference = Vector.subtract(MOUSE.pos, particle.pos)
       dist = Math.max(difference.mag(), 1)

-      push_force = -1000000
+      push_force = -1000
       vel_mag = push_force / dist
       particle.vel.plus(Vector.unit(difference).s_mult(vel_mag))

   draw: =>
-    start = Date.now()
+    while window.performance.now() > @tick
+      for particle in @particles
+        particle.update(@canvas, ctx, 1)
+      @tick += @MS_PER_TICK
+
     ctx = @canvas.getContext("2d")
     ctx.globalCompositeOperation = 'source-over'
-    ctx.fillStyle = "rgba(0, 0, 0, #{255 * 1.5 * @tick})"
+    ctx.fillStyle = "rgba(0, 0, 0, 128)"
     ctx.fillRect(0, 0, @canvas.width, @canvas.height)
     #ctx.clearRect(0, 0, @canvas.width, @canvas.height)
-
     for particle in @particles
-      particle.update(@canvas, ctx, @tick)
       particle.draw(ctx)
-
-    end = Date.now()
-    @tick = (Math.max(end - start, 1)) / 1000.0

     window.requestAnimationFrame(@draw)

@@ -165,10 +167,10 @@
   pos:
     x: 0
     y: 0
-  mass: 1000
+  mass: 1
 $ ->
   MOUSE =
     pos: new Vector(0, 0)
-    mass: 1000
+    mass: 1
   animator = new Animator($("canvas"))
   window.requestAnimationFrame(animator.draw)


Added animation to location marker

Added animation to location marker

--- js/views/map.coffee
+++ js/views/map.coffee
@@ -66,6 +66,7 @@
       @locationMarker.setMap null

     @locationMarker = new google.maps.Marker
+      animation: google.maps.Animation.DROP
       position: center
       map: @map
       title: 'Current Location'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -11,7 +11,7 @@
       log I.age
       log I.duration
       log (I.age + 1 / I.duration)
-      I.alpha = 1 - (I.age + 1 / I.duration)
+      I.alpha = 1 - ((I.age + 1) / I.duration)

     if I.duration != -1 && I.age + 1 >= I.duration
       I.active = false


Add source map

Add source map

--- plugins/webpack/webpack.coffee
+++ plugins/webpack/webpack.coffee
@@ -25,6 +25,7 @@
       environments:
         development:
           debug: true
+          devtool: 'cheap-source-map'
           output:
             pathinfo: true
           plugins: []