And we also need toObject ...

And we also need toObject ...

--- lib/assets/javascripts/chronicle.js.coffee
+++ lib/assets/javascripts/chronicle.js.coffee
@@ -55,3 +55,9 @@
     query = @getQuery()
     query[k] = v
     @setQuery query
+
+
+Array.prototype.toObject = ->
+  obj = Object.extended()
+  this.each (a) -> obj[a[0]] = a[1]
+  return obj


派发timeupdate时需保证已经开始播放

派发timeupdate时需保证已经开始播放

--- src/js/player.coffee
+++ src/js/player.coffee
@@ -144,6 +144,8 @@
                 if st is STATES.END
                     self.next(true)
             ).on(EVENTS.POSITIONCHANGE, (pos) ->
+                return unless pos
+
                 self.trigger('timeupdate', pos)

                 if self.getUrl()


expanded scope of vehicles controller to connect to sensor API

--- lib/controllers/vehicles_controller.coffee
+++ lib/controllers/vehicles_controller.coffee
@@ -1,5 +1,8 @@
 Sensor.VehiclesController = SC.ArrayProxy.extend
   content: []
+  loaded: false
+  vehiclesUrl: ( -> "#{Sensor.API_URL}/units?callback=?&oauth_token=#{Sensor.ACCESS_TOKEN}").property()
+  messagesUrl: ( -> "#{Sensor.API_URL}/messages?callback=?&oauth_token=#{Sensor.ACCESS_TOKEN}").property()
   findById: (id) -> @findProperty('id',id)
   findByImei: (imei) -> @findProperty('imei',imei)
   findByNickname: (nickname) -> @findProperty('nickname',nickname)
@@ -11,5 +14,38 @@
     console.log "calculating stopped"
     @filterProperty('state','stopped')
   ).property('@each.state').cacheable()
-
-
+  selected: ( -> @filterProperty('selected') ).property('@each.selected').cacheable()
+  load: ->
+    console.time "Downloading vehicles"
+    $.getJSON @get('vehiclesUrl'), (data) =>
+      console.timeEnd "Downloading vehicles"
+      console.log "#{data.units.length} vehicles downloaded"
+      @loadVehicles(data.units)
+      @set('loadedVehicles',true)
+      @getMessages()
+  loadVehicles: (vehicles) ->
+    vehicles.forEach (vehicleData) =>
+      vehicle = Sensor.Vehicle.create(vehicleData)
+      @pushObject vehicle
+  getMessages: ->
+    console.time "downloading messages"
+    $.getJSON @get('messagesUrl'), (data) =>
+      console.timeEnd "downloading messages"
+      messages = data.messages
+      console.log "#{messages.length} messages downloaded"
+      @processMessages(messages)
+  processMessages: (messages) ->
+    console.time "processing messages"
+    @get('content').forEach (vehicle) ->
+      # TODO: this can probably be optimised by removing the messages from the data array
+      # so that after the messages have been added to a vehicle, we don't need to cycle
+      # through those messages again on the next iteration
+      vehicleId = vehicle.get('id')
+      messagesForVehicle = messages.filter (m) -> m.unit_id == vehicleId
+      messageObjects = messagesForVehicle.map (m) ->
+        Sensor.Message.create(m)
+      vehicle.updateWithMessages(messageObjects)
+    console.timeEnd "processing messages"
+    @set('loadedMessages', true)
+    @subscribeToWebsockets()
+  subscribeToWebsockets: ->


Change verify thumbnails option

Change verify thumbnails option

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -388,7 +388,6 @@
     needResize = true

     if isOpen
-
       setAnimation opened, false

       opened.updateDimensions()
@@ -400,8 +399,10 @@

   setAnimation = (chocolate, enable = true) ->
     method = if enable then 'add' else 'remove'
-    if chocolate.thumbnails?
+
+    if chocolate.options.thumbnails
       classList chocolate.thumbnails, choco_animated, null, method
+
     classList chocolate.slider, choco_animated, null, method

   unless isTouch


adding quad trees for collisions

adding quad trees for collisions

--- src/quad_tree.coffee
+++ src/quad_tree.coffee
@@ -11,7 +11,7 @@
       maxDepth: 4
       maxChildren: 5

-    root = Node(I.bounds, I.maxDepth, I.maxChildren()
+    root = Node(I.bounds, I.maxDepth, I.maxChildren())

     self =
       clear: ->
@@ -69,8 +69,7 @@
       clear: ->
         I.children.clear()

-        I.nodes.each (node) ->
-          node.clear()
+        I.nodes.invoke('clear')

         I.nodes.clear()


defer check

defer check

--- spec/SpecHelper.coffee
+++ spec/SpecHelper.coffee
@@ -432,9 +432,10 @@
   expect(events[0].isEnd()).to.deep.equal(true)

 module.exports.verifyCleanup = verifyCleanup = ->
-  for seq in seqs
-    expect(seq.source.dispatcher.hasSubscribers()).to.deep.equal(false)
-  seqs = []
+  deferred ->
+    for seq in seqs
+      expect(seq.source.dispatcher.hasSubscribers()).to.deep.equal(false)
+    seqs = []

 lastNonError = (events) ->
   Bacon._.last(Bacon._.filter(((e) -> toValue(e) != "<error>"), events))


:lipstick: Tests description

:lipstick: Tests description

--- spec/color-spec.coffee
+++ spec/color-spec.coffee
@@ -107,7 +107,7 @@
       expect(color).toBeColor(102, 128, 153, 0.4)

   describe '::hwba', ->
-    it 'returns an array with the hue, whiteness and blackness components', ->
+    it 'returns an array with the hue, whiteness, blackness and alpha components', ->
       expect(color.hwba).toBeComponentArrayCloseTo([16, 20, 0, 0.4])

     it 'sets the color components based on the passed-in values', ->
@@ -116,10 +116,10 @@
       expect(color).toBeColor(102, 128, 153, 0.7)

   describe '::hex', ->
-    it 'returns the color component as a hexadecimal string', ->
+    it 'returns the color as a hexadecimal string', ->
       expect(color.hex).toEqual('ff6933')

-    it 'parses the affected string and sets the color components accordingly', ->
+    it 'parses the string and sets the color components accordingly', ->
       color.hex = '00ff00'

       expect(color).toBeColor(0,255,0,0.4)
@@ -128,7 +128,7 @@
     it 'returns the color component as a hexadecimal string', ->
       expect(color.hexARGB).toEqual('66ff6933')

-    it 'parses the affected string and sets the color components accordingly', ->
+    it 'parses the string and sets the color components accordingly', ->
       color.hexARGB = 'ff00ff00'

       expect(color).toBeColor(0,255,0,1)


rename spec descriptions to use keybinding names

--- spec/panes-spec.coffee
+++ spec/panes-spec.coffee
@@ -30,7 +30,7 @@
         getActiveView: -> editorView
       }

-    describe "focus pane right", ->
+    describe "the ctrl-w l keybinding", ->
       beforeEach ->
         spyOn(atom.workspaceView, 'focusPaneViewOnRight')

@@ -40,7 +40,7 @@

         expect(atom.workspaceView.focusPaneViewOnRight).toHaveBeenCalled()

-    describe "focus pane left", ->
+    describe "the ctrl-w h keybinding", ->
       beforeEach ->
         spyOn(atom.workspaceView, 'focusPaneViewOnLeft')

@@ -50,7 +50,7 @@

         expect(atom.workspaceView.focusPaneViewOnLeft).toHaveBeenCalled()

-    describe "focus pane below", ->
+    describe "the ctrl-w j keybinding", ->
       beforeEach ->
         spyOn(atom.workspaceView, 'focusPaneViewBelow')

@@ -60,7 +60,7 @@

         expect(atom.workspaceView.focusPaneViewBelow).toHaveBeenCalled()

-    describe "focus pane above", ->
+    describe "the ctrl-w k keybinding", ->
       beforeEach ->
         spyOn(atom.workspaceView, 'focusPaneViewAbove')


collision

collision

--- src/collision.coffee
+++ src/collision.coffee
@@ -35,6 +35,9 @@

   @param a
   @param b
+
+  @type boolean
+  @returns true if the rectangles overlap, false otherwise
   ###
   rectangular: (a, b) ->
     a.x < b.x + b.width &&
@@ -74,6 +77,9 @@

   @param a
   @param b
+
+  @type boolean
+  @returns true is the circles overlap, false otherwise
   ###
   circular: (a, b) ->
     r = a.radius + b.radius


updated store API to better support changes for #59

--- src/lib/utils.coffee
+++ src/lib/utils.coffee
@@ -30,8 +30,8 @@
         propExpr = prop[2]
         if propName.indexOf('.') is 0
           path = propName.slice(1).split '.'
-          obj = element
-          obj = obj[path.shift()] while obj and path.length > 1
+          obj  = element
+          obj  = obj[path.shift()] while obj and path.length > 1
           if obj
             obj[path] = i18n.get value, subs
             i18nProcess element, subMap if path is 'innerHTML'
@@ -47,6 +47,24 @@
 # Private functions
 # -----------------

+# Attempt to dig down in to the `root` object and stop on the parent of the
+# target property.
+# Return the progress of the mining as an array in this structure;
+# `[root-object, base-object, base-path, target-parent, target-property]`.
+dig = (root, path, force, parseFirst = yes) ->
+  result = [root]
+  if path and path.indexOf('.') isnt -1
+    path   = path.split '.'
+    object = base = root[basePath = path.shift()]
+    object = base = tryParse object if parseFirst
+    while object and path.length > 1
+      object = object[path.shift()]
+      object = {} if not object? and force
+    result.push base, basePath, object, path.shift()
+  else
+    result.push root, path, root, path
+  result
+
 # Find all elements to be internationalized and call their corresponding
 # handler(s).
 i18nProcess = (node, subMap) ->
@@ -66,6 +84,16 @@
       break
   subs

+# Attempt to parse `value` as a JSON object if it's not `null`; otherwise just
+# return `value`.
+tryParse = (value) ->
+  if value? then JSON.parse value else value
+
+# Attempt to stringify `value` in to a JSON string if it's not `null`;
+# otherwise just return `value`.
+tryStringify = (value) ->
+  if value? then JSON.stringify value else value
+
 # Internationalization setup
 # --------------------------

@@ -169,18 +197,18 @@
     return no for key in keys when not localStorage.hasOwnProperty key
     yes

-  # Retrieve the value associated with the specified `keys` from
+  # Retrieve the value associated with the specified `key` from
   # `localStorage`.
-  # If the value is found, parse it as JSON before being returning it;
-  # otherwise return `undefined`.
-  get: (keys...) ->
-    if keys.length is 1
-      value = localStorage[keys[0]]
-      if value? then JSON.parse value else value
-    else
-      value = {}
-      value[key] = @get key for key in keys
-      value
+  # If the value is found, parse it as a JSON object before being returning
+  # it.
+  get: (key) ->
+    parts = dig localStorage, key
+    if parts[3]
+      value = parts[3][parts[4]]
+      # Ensure that the value is parsed if retrieved directly from
+      # `localStorage`.
+      value = tryParse value if parts[3] is parts[0]
+    value

   # Initialize the value of the specified key(s) in `localStorage`.
   # `keys` can either be a string for a single key (in which case
@@ -188,19 +216,20 @@
   # pairs.
   # If the value is currently `undefined`, assign the specified default value;
   # otherwise reassign itself.
-  init: (keys = {}, defaultValue) ->
+  init: (keys, defaultValue) ->
     switch typeof keys
       when 'object'
-        @set key, @get(key) ? defaultValue for own key, defaultValue of keys
+        @init key, defaultValue for own key, defaultValue of keys
       when 'string' then @set keys, @get(keys) ? defaultValue

   # For each of the specified `keys`, retrieve their value in `localStorage`
   # and pass it, along with the key, to the `callback` function provided.
-  # If `callback` returns a value that is not `undefined`, then that value will
-  # be assigned to the key in `localStorage`. This functionality is very useful
-  # when just manipulating existing values but can be used in any case.
+  # This functionality is very useful when just manipulating existing values.
   modify: (keys..., callback) ->
-    @set key, callback?(@get(key), key) ? @get key for key in keys
+    for key in keys
+      value = @get key
+      callback? value, key
+      @set key, value

   # Remove the specified `keys` from `localStorage`.
   # If only one key is specified then the current value of that key is returned
@@ -218,10 +247,10 @@
   # default value to it instead.
   rename: (oldKey, newKey, defaultValue) ->
     if @exists oldKey
-      @init newKey, @get oldKey
+      @set newKey, @get oldKey
       @remove oldKey
     else
-      @init newKey, defaultValue
+      @set newKey, defaultValue

   # Search `localStorage` for all keys that match the specified regular
   # expression.
@@ -235,12 +264,10 @@
   # key; otherwise transform it to a JSON string beforehand.
   set: (keys, value) ->
     switch typeof keys
-      when 'object'
-        for own key, value of keys
-          localStorage[key] = if value? then JSON.stringify value else value
+      when 'object' then @set key, value for own key, value of keys
       when 'string'
         oldValue = @get keys
-        localStorage[keys] = if value? then JSON.stringify value else value
+        localStorage[keys] = tryStringify value
         oldValue

 # Utilities setup


minor bug fixes and attempt at synchronization for multiple servers (not perfect yet)

--- src/jenkins-enhanced.coffee
+++ src/jenkins-enhanced.coffee
@@ -74,9 +74,13 @@
     @url = url
     @auth = auth
     @_querystring = require 'querystring'
+    @activeRequests = 0

   hasInitialized: ->
     @_hasListed
+
+  hasActiveRequests: ->
+    @activeRequests > 0

   getJobs: =>
     @_rootFolder.getJobs()
@@ -141,17 +145,16 @@

   getJobByName: (jobName, recursive=true) =>
     jobName = @_querystring.unescape(jobName).trim()
-    job = @_jobs.where(name: jobName)
-    return job if job.length > 0
+    jobs = @_jobs.where(name: jobName)
     # otherwise we must start searching the other folders
     if (recursive)
       for folder in @_folders
         job = folder.getJobByName(jobName)
-        return job if job and job.length > 0
-    null
+        jobs = jobs.concat(job)
+    jobs

   hasJobByName: (jobName, recursive=true) =>
-    @getJobByName(jobName, recursive) != null
+    @getJobByName(jobName, recursive).length > 0

   addFolder: (folder) =>
     @_hasListed = true
@@ -169,16 +172,15 @@

   getFolderByName: (folderName, recursive=true) =>
     folderName = @_querystring.unescape(folderName).trim()
-    folder = @_folders.where(name: folderName)
-    return folder if folder.length > 0
+    folders = @_folders.where(name: folderName)
     if (recursive)
       for folder in @_folders
         folder = folder.getFolderByName(folderName)
-        return folder if folder and folder.length > 0
-    null
+        folders = folder.concat(folder)
+    folders

   hasFolderByName: (folderName, recursive=true) =>
-    @getFolderByName(folderName, recursive) != null
+    @getFolderByName(folderName, recursive).length > 0

 class JenkinsJob
   name: null
@@ -208,6 +210,11 @@
     for server in @_servers
       return false if not server.hasInitialized()
     true
+
+  hasActiveRequests: =>
+    for server in @_servers
+      return true if server.hasActiveRequests()
+    false

   listServers: =>
     @_servers
@@ -293,6 +300,8 @@
   build: (buildWithEmptyParameters) =>
     return if not @_init(@build)
     job = @_getJob()
+    if not job
+      return
     server = @_serverManager.getServerByJobName(job.name)
     command = if buildWithEmptyParameters then "buildWithParameters" else "build"
     path = if @_params then "#{job.path}/buildWithParameters?#{@_params}" else "#{job.path}/#{command}"
@@ -313,6 +322,8 @@
   describe: =>
     return if not @_init(@describe)
     job = @_getJob()
+    if not job
+      return
     server = @_serverManager.getServerByJobName(job.name)
     if !server
       @msg.send "I couldn't find any servers with a job called #{job.name}.  Try `jenkins servers` to get a list."
@@ -337,6 +348,8 @@
   last: =>
     return if not @_init(@last)
     job = @_getJob()
+    if not job
+      return
     server = @_serverManager.getServerByJobName(job.name)
     path = "#{job.path}/lastBuild/api/json"
     if !server
@@ -345,7 +358,6 @@
     @_requestFactorySingle server, null, path, @_handleLast

   list: (isInit = false) =>
-    @_requestsRunning = 0
     @_requestFactory "api/json", if isInit then @_handleListInit else @_handleList

   listAliases: =>
@@ -391,7 +403,7 @@

   _makeRootFolderForServer: (items, server) =>
     response = ""
-    @_requestsRunning = 1
+    server.activeRequests = 1
     # make the default/root level folder
     rootFolder = server.getFolder()
     if rootFolder == null
@@ -401,19 +413,24 @@
     @_addJobsToFoldersList(items, server, rootFolder)

   _addJobsToFoldersList: (items, server, folder) =>
+    console.log("Active Requests? #{@_serverManager.hasActiveRequests()}")
     for item in items
       itemType = item._class
       if (itemType == 'com.cloudbees.hudson.plugins.folder.Folder')
         newFolder = new JenkinsFolder(item.name, "#{folder.path}/job/#{@_querystring.escape(item.name)}", folder.depth+1)
         folder.addFolder(newFolder)
-        @_requestsRunning++
+        server.activeRequests++
         @_requestFactorySingle server, newFolder, "#{newFolder.path}/api/json", @_handleNewFolder
       else
         newJob = new JenkinsJob(item.name, "#{folder.path}/job/#{@_querystring.escape(item.name)}", if item.color == "red" then "FAIL" else "PASS")
         folder.addJob(newJob)
     # count our current folder as processed
-    @_requestsRunning--
-    @_listJobs(server, server.getFolder(), true) if @_requestsRunning == 0
+    server.activeRequests--
+    # check if we are the last active instance
+    if (not @_serverManager.hasActiveRequests())
+      for server in @_serverManager.listServers()
+        @_listJobs(server, server.getFolder(), true)
+

   _listJobs: (server, folder, isRoot=false) =>
     response = ""
@@ -426,8 +443,9 @@
     for subFolder in folder.getFolders(false)
       response += "\t".repeat(subFolder.depth+1)+"Folder: #{subFolder.name}\n" if subFolder.name != ""
       response += @_listJobs(server, subFolder)
-    if isRoot and @_outputStatus
-      @send response
+    if isRoot
+      if @_outputStatus
+        @send response
       @_initComplete() if @_serverManager.hasInitialized()
     else
       return response
@@ -491,7 +509,7 @@
       if job and job.length > 0
         jobs = jobs.concat(job)
     if jobs.length > 1
-      @send "There are multiple jobs with that name, please use an id instead."
+      @send "There are multiple jobs with that name, please use an id from `jenkins list` instead."
     jobs[0] if jobs.length == 1

   # Switch the index with the job name
@@ -537,7 +555,6 @@
     try
       content = JSON.parse(body)
       @_addJobsToFoldersList content.jobs, server, folder
-      #@_initComplete() if @_serverManager.hasInitialized()
     catch error
       @send error

@@ -545,9 +562,8 @@
     if err
       @reply err
     else if 200 <= res.statusCode < 400 # Or, not an error code.
-      job     = @_getJob(true)
-      jobName = @_getJob(false)
-      @reply "(#{res.statusCode}) Build started for #{jobName} #{server.url}/job/#{job}"
+      job = @_getJob(false)
+      @reply "(#{res.statusCode}) Build started for #{job.name} #{server.url}/#{job.path}"
     else if 400 == res.statusCode
       @build true
     else
@@ -614,7 +630,6 @@
       content = JSON.parse(body)
       @_outputStatus = print
       @_makeRootFolderForServer content.jobs, server
-      #@_initComplete() if @_serverManager.hasInitialized()
     catch error
       @send error


Forgot to document Folder#move

Forgot to document Folder#move


clear parent id when new query starts

clear parent id when new query starts

--- app/assets/javascripts/admin_in_place_editor.js.coffee
+++ app/assets/javascripts/admin_in_place_editor.js.coffee
@@ -48,6 +48,7 @@

     $('.typeahead').typeahead
       source: (query, process) =>
+        $('#taxon_concept_parent_id').attr('value', null)
         designation_id = $('#taxon_concept_designation_id').attr('value')
         rank_id = $('#taxon_concept_rank_id').attr('value')
         $.get('http://localhost:3000/admin/taxon_concepts/autocomplete',


allow dragglable modal to display images

--- app/assets/javascripts/components/draggable-modal.cjsx
+++ app/assets/javascripts/components/draggable-modal.cjsx
@@ -42,6 +42,13 @@
     <Draggable x={x} y={y}>

+        {if @props.showCloseModal == 1
+

+
+

+        }
+
         { if @props.closeButton?

         }


fixes a bug in the days duration

fixes a bug in the days duration

--- app/assets/javascripts/models/expense_report.js.coffee
+++ app/assets/javascripts/models/expense_report.js.coffee
@@ -20,8 +20,8 @@
         86

       availableCountries: ->
-        [ "USA", "Great Britain", "Japan", "European Union", "Australia", "Canada",
-          "Denmark", "Norway", "South Africa", "Sweden", "Switzerland" ]
+        [ "USA", "Great Britain", "European Union", "Australia", "Canada",
+          "Denmark", "Norway", "Sweden", "Switzerland" ]

       total: ->
         sum = 0
@@ -66,7 +66,7 @@

       numberOfDays: ->
         if @startTime and @endTime
-          moment.duration(moment(@endTime) - moment(@startTime)).days() + 1
+          moment.duration(moment(@endTime) - moment(@startTime)).as("days") + 1
         else
           0
 ]


Updated shipping rate api call to ignore loading

--- app/assets/javascripts/sprangular/services/cart.coffee
+++ app/assets/javascripts/sprangular/services/cart.coffee
@@ -86,7 +86,7 @@
         state_id: options.stateId
         zipcode: options.zipcode

-      $http.get('/api/shipping_rates', {params: params, class: Sprangular.ShippingRate})
+      $http.get('/api/shipping_rates', {params: params, ignoreLoadingIndicator: true, class: Sprangular.ShippingRate})

     clear:                   -> @current.clear()
     totalQuantity:           -> @current.totalQuantity()


Add CSRF token to preview POST

Add CSRF token to preview POST

--- app/assets/javascripts/verblog/publish_tools.js.coffee
+++ app/assets/javascripts/verblog/publish_tools.js.coffee
@@ -182,6 +182,8 @@
       url: @url
       type: "POST"
       data: {}
+      headers:
+          "X-CSRF-Token": $("meta[name='csrf-token']").attr("content")
       dataType: "json"
       success: (r) =>
         console.log "got success of ", r


Document the storageKey option in Anon

Document the storageKey option in Anon

--- src/kissmetrics-anon.coffee
+++ src/kissmetrics-anon.coffee
@@ -107,11 +107,18 @@
 #
 # `apiKey` (String): Your Kissmetrics API key
 #
-# `options` *Optional* (Object): Specify which internal engine you want
-# to use: `'localStorage'` or `'cookie'`.
+# `options` *Optional* (Object):
+#
+#  * `storage`: Specify which internal engine you want to
+#    use: `'localStorage'` or `'cookie'`. Default is `'localStorage'`
+#  * `storageKey`: Specify what key you want the assigned ID to be
+#    stored under. Default is `'kissmetricsAnon'`
 #
 # ```
-# km = new AnonKissmetricsClient(API_KEY)
+# km = new AnonKissmetricsClient(API_KEY, {
+#   storage: 'cookie',
+#   storageKey: 'myKissmetricsAnon'
+# })
 # km.record('Visited signup form')
 # ```


Fix: よりバージョン判定範囲を広く close #23

Fix: よりバージョン判定範囲を広く close #23

--- src/core/util.coffee
+++ src/core/util.coffee
@@ -61,7 +61,11 @@
           else
             resolve(reg[1]+".0")
         else
-          reject("Error: Version Regexp Error (#{text})")
+          reg = /^\d+\.\d+/.exec(text)
+          if reg?
+            resolve(reg[0]+".0")
+          else
+            reject("Error: Version Regexp Error (#{text})")
       return
     ).catch( ->
       return readFile(path.join(require("./config").get("blitzPath"), "Data/version", "resources.txt"), "utf-8").then( (text) ->


Fixed bugs with copying documents from one collection to another causing updates to be rejected and initial insert IDs not to be mapped.

--- src/Collections.coffee
+++ src/Collections.coffee
@@ -171,18 +171,21 @@
         return if result == false
       dest.insert srcDoc, (err, result) -> if err then df.reject(err) else df.resolve(result)
       df.promise
+    # Collection2 may not allow inserting a doc into a collection with a predefined _id, so we
+    # store a map of src to dest IDs. If a copied doc is removed in the destination, this will
+    # still reference the source doc ID to this doc ID.
+    idMap = {}
+    # Default to the same ID if no mapping is found.
+    getDestId = (id) -> idMap[id] ? id
+    insertWithMap = (srcDoc) ->
+      id = getDestId(srcDoc._id)
+      return if dest.findOne(id)
+      insert(srcDoc).then (insertId) ->
+        idMap[srcDoc._id] = insertId

     @getCursor(src).forEach (doc) ->
-      return if dest.findOne(doc._id)
-      insertPromises.push(insert(doc))
+      insertPromises.push(insertWithMap(doc))
     if args.track
-      # Collection2 may not allow inserting a doc into a collection with a predefined _id, so we
-      # store a map of src to dest IDs. If a copied doc is removed in the destination, this will
-      # still reference the source doc ID to this doc ID.
-      idMap = {}
-      insertWithMap = (srcDoc) ->
-        insert(srcDoc).then (insertId) ->
-          idMap[srcDoc._id] = insertId
       if args.exclusive
         # Stop any existing copy.
         trackHandle = dest.trackHandle
@@ -190,13 +193,14 @@
       dest.trackHandle = @observe src,
         added: insertWithMap
         changed: (newDoc, oldDoc) ->
-          id = idMap[newDoc._id]
           # If the document doesn't exist in the destination, don't track changes from the source.
+          # Default to the same ID if no mapping is found.
+          id = getDestId(newDoc._id)
           if dest.findOne(id)
             dest.remove(id)
             insertWithMap(newDoc)
         removed: (oldDoc) ->
-          id = oldDoc._id
+          id = getDestId(oldDoc._id)
           dest.remove id, (err, result) ->
             return unless result == 1
             delete idMap[id]


gpodder for Windows and Linux

gpodder for Windows and Linux

--- src/coffee/clients.coffee
+++ src/coffee/clients.coffee
@@ -164,6 +164,12 @@
       title: 'Clementine'
       scheme: 'itpc://'
       icon: 'linux/clementine@2x.png'
+    },
+    {
+      title: 'gPodder'
+      scheme: 'gpodder://'
+      icon: 'linux/gpodder@2x.png'
+      install: 'http://gpodder.org/downloads'
     }
   ],
   osx: [
@@ -216,32 +222,32 @@

   windows7: [
     {
+      title: 'gPodder'
+      scheme: 'gpodder://'
+      icon: 'windows/gpodder@2x.png'
+      install: 'http://gpodder.org/downloads'
+    },
+    {
       title: 'iTunes'
       scheme: 'itpc://'
       icon: 'osx/itunes@2x.png'
       install: 'http://www.apple.com/itunes/'
-    }#,
-    #{
-      #title: 'gPodder'
-      #scheme: 'gpodder://'
-      #icon: 'windows/gpodder@2x.png'
-      #install: 'http://gpodder.org/downloads'
-    #},
+    }
   ]

   windows8: [
+    {
+      title: 'gPodder'
+      scheme: 'gpodder://'
+      icon: 'windows/gpodder@2x.png'
+      install: 'http://gpodder.org/downloads'
+    },
     {
       title: 'iTunes'
       scheme: 'itpc://'
       icon: 'osx/itunes@2x.png'
       install: 'http://www.apple.com/itunes/'
     },
-    #{
-      #title: 'gPodder'
-      #scheme: 'gpodder://'
-      #icon: 'windows/gpodder@2x.png'
-      #install: 'http://gpodder.org/downloads'
-    #},
     {
       title: 'Podscout'
       scheme: 'podscout://'
@@ -258,17 +264,17 @@
       #install: 'http://bringcast.com/'
     #},
     {
+      title: 'gPodder'
+      scheme: 'gpodder://'
+      icon: 'windows/gpodder@2x.png'
+      install: 'http://gpodder.org/downloads'
+    },
+    {
       title: 'iTunes'
       scheme: 'itpc://'
       icon: 'osx/itunes@2x.png'
       install: 'http://www.apple.com/itunes/'
     },
-    #{
-      #title: 'gPodder'
-      #scheme: 'gpodder://'
-      #icon: 'windows/gpodder@2x.png'
-      #install: 'http://gpodder.org/downloads'
-    #},
     {
       title: 'Podscout'
       scheme: 'podscout://'


Cleanup before build.

Cleanup before build.

--- src/commands/build.coffee
+++ src/commands/build.coffee
@@ -1,6 +1,8 @@
+fs         = require 'fs-extra'
 grunt      = require 'grunt'
 npmHelpers = require '../npm-helpers'

 exports.run = (opts) ->
   npmHelpers.runInstall false, ->
+    fs.removeSync 'public' if fs.existsSync 'public'
     grunt.tasks 'compile:dev'


new(landingPage): zoom in when selecting a city

--- src/components/App.coffee
+++ src/components/App.coffee
@@ -112,6 +112,7 @@
                 dispatch Actions.urlSetCenter
                   lat: city.lat
                   lng: city.lon
+                dispatch Actions.urlSetZoom 14
                 dispatch Actions.toggleLandingPage()
                 dispatch Actions.setSearchText ''
             onLogin: (data) ->


Changed attachnemnt links

Changed attachnemnt links

--- src/dockerhub.coffee
+++ src/dockerhub.coffee
@@ -58,9 +58,9 @@
       robot.messageRoom room, attachments:[
         color:       "good",
         title:       "DockerHub",
-        title_link:  data.repository.repo_url,
-        text:        "#{repo}:#{tag} was pushed",
-        fallback:    "DockerHub: #{repo}:#{tag} was pushed"
+        title_link:  "https://hub.docker.com/",
+        text:        "<#{data.repository.repo_url}|#{repo}:#{tag}> was pushed",
+        fallback:    "DockerHub: #{repo}:#{tag} was pushed\n#{data.repository.repo_url}"
       ]

     ## see https://docs.docker.com/docker-hub/repos/#webhook-chains


metadata doesn't include readme property, copy it form the package

--- lib/package-detail-view.coffee
+++ lib/package-detail-view.coffee
@@ -93,6 +93,9 @@
         @showErrorMessage()
       else
         @pack = packageData
+        # TODO: this should match Package.loadMetadata from core, but this is
+        # an acceptable hacky workaround
+        @pack.metadata = _.extend(@pack.metadata ? {}, @pack)
         @completeInitialzation()

   showLoadingMessage: ->


operations: ReplayArchive sorts results before capping

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -827,6 +827,14 @@
         async.waterfall [ (cb2) =>
             t.walkListenerArchive @req.sender, @req.start, @req.end, max, (results) =>
                 if sent < max
+                    results.sort (a, b) ->
+                        if a.updated < b.updated
+                            -1
+                        else if a.updated > b.updated
+                            1
+                        else
+                            0
+
                     results = results.slice(0, max - sent)
                     @sendNotification results
                     sent += results.length


Use node's crypto from fs.md5ForPath()

Use node's crypto from fs.md5ForPath()

--- src/stdlib/fs.coffee
+++ src/stdlib/fs.coffee
@@ -146,7 +146,8 @@
     traverse(rootPath, '', onFile, onDirectory)

   md5ForPath: (path) ->
-    $native.md5ForPath(path)
+    contents = nodeFs.readFileSync(path)
+    nodeRequire('crypto').createHash('md5').update(contents).digest('hex')

   resolve: (args...) ->
     extensions = args.pop() if _.isArray(_.last(args))


Remove deprecation about returning a disposable

--- src/pane-view.coffee
+++ src/pane-view.coffee
@@ -116,7 +116,6 @@

     if item.onDidChangeTitle?
       disposable = item.onDidChangeTitle(@activeItemTitleChanged)
-      deprecate 'Please return a Disposable object from your ::onDidChangeTitle method!' unless disposable?.dispose?
       @activeItemDisposables.add(disposable) if disposable?.dispose?
     else if item.on?
       disposable = item.on('title-changed', @activeItemTitleChanged)
@@ -124,7 +123,6 @@

     if item.onDidChangeModified?
       disposable = item.onDidChangeModified(@activeItemModifiedChanged)
-      deprecate 'Please return a Disposable object from your ::onDidChangeModified method!' unless disposable?.dispose?
       @activeItemDisposables.add(disposable) if disposable?.dispose?
     else if item.on?
       item.on('modified-status-changed', @activeItemModifiedChanged)


Remove comment

Remove comment

--- scripts/collections/content.coffee
+++ scripts/collections/content.coffee
@@ -29,8 +29,6 @@
       if attrs.mediaType
         mediaType = attrs.mediaType
         Medium = mediaTypes.type(mediaType)
-        # Include the `mediaType` in case models support multiple media types (like images).
-        #delete attrs.mediaType

         return new Medium(attrs)


Assign __env on generated items

Assign __env on generated items

--- src/core/generator.coffee
+++ src/core/generator.coffee
@@ -11,6 +11,7 @@
     for key, item of items
       if item instanceof ContentPlugin
         item.parent = root
+        item.__env = env
         item.__filename = 'generator'
         item.__plugin = generator
         root[key] = item


Fix #39 and sockjs/sockjs-protocol#33 - emit close immediately after an unexpected network close

--- src/transport.coffee
+++ src/transport.coffee
@@ -143,6 +143,9 @@
         return

     didTimeout: ->
+        if @to_tref
+            clearTimeout(@to_tref)
+            @to_tref = null
         if @readyState isnt Transport.CONNECTING and
            @readyState isnt Transport.OPEN and
            @readyState isnt Transport.CLOSING
@@ -210,12 +213,17 @@
         @setUp(@thingy)

     setUp: ->
-        @thingy_end_cb = () => @didClose(1006, "Connection closed")
+        @thingy_end_cb = () => @didAbort(1006, "Connection closed")
         @thingy.addListener('end', @thingy_end_cb)

     tearDown: ->
         @thingy.removeListener('end', @thingy_end_cb)
         @thingy_end_cb = null
+
+    didAbort: (status, reason) ->
+        session = @session
+        @didClose(status, reason)
+        session.didTimeout()

     didClose: (status, reason) ->
         if @thingy


transport: Fix typo

transport: Fix typo

--- src/transport.coffee
+++ src/transport.coffee
@@ -19,5 +19,5 @@
   return new transports[scheme].MessageBroker address, options

 # @module: Must have Client and MessageBroker constructors, implementing these interfaces
-export.register = (scheme, module) ->
+exports.register = (scheme, module) ->
   transports[scheme] = module


Fix Keyboard._onTab

Fix Keyboard._onTab

Also combine delete and insert into one instruction so it will be
emitted as such

--- src/modules/keyboard.coffee
+++ src/modules/keyboard.coffee
@@ -1,6 +1,7 @@
-_        = require('lodash')
-DOM      = require('../dom')
-Line     = require('../line')
+_      = require('lodash')
+DOM    = require('../dom')
+Line   = require('../line')
+Tandem = require('tandem-core')

 class Keyboard
@@ -78,9 +79,16 @@
     # Behavior according to Google Docs + Word
     # When tab on one line, regardless if shift is down, delete selection and insert a tab
     # When tab on multiple lines, indent each line if possible, outdent if shift is down
-    @quill.deleteText(range, { source: 'user' })
-    @quill.insertText(range.start, "\t", {}, { source: 'user' })
-    @quill.setSelection(range.start + 1, range.start.index + 1)
+    delta = Tandem.Delta.makeDelta({
+      startLength: @quill.getLength()
+      ops: [
+        { start: 0, end: range.start }
+        { value: "\t" }
+        { start: range.end, end: @quill.getLength() }
+      ]
+    })
+    @quill.updateContents(delta)
+    @quill.setSelection(range.start + 1, range.start + 1)

 module.exports = Keyboard


make "constructor" param optional

make "constructor" param optional

--- obj.coffee
+++ obj.coffee
@@ -1,13 +1,14 @@
 module.exports =

   # Return a function which creates a new object with the given prototype and initializes this object with the given
-  # constructor function.
+  # constructor function (if given).
   # Also copy optional `extend` object content to returned function.
   createConstructor: (prototype, constructor, extend) ->
-    # Create function which forwards to given constructor function.
-    F = (args) ->
-      constructor.apply @, args
-      @ # overwrite constructor return value
+    # Create function which forwards to given constructor function (if given).
+    F = if not constructor? then -> else
+      (args) ->
+        constructor.apply @, args
+        @ # overwrite constructor return value
     # Set functions prototype field.
     F.prototype = prototype
     # Create function which creates a new object with the given prototype and initializes with the given constructor.


only allow surface events to be pinned

only allow surface events to be pinned

--- vue/src/shared/services/ability_service.coffee
+++ vue/src/shared/services/ability_service.coffee
@@ -77,7 +77,7 @@
     @canAdministerDiscussion(thread)

   canPinEvent: (event) ->
-    !event.pinned && @canEditThread(event.discussion())
+    !event.pinned && event.isSurface() && @canEditThread(event.discussion())

   canUnpinEvent: (event) ->
     event.pinned && @canEditThread(event.discussion())


Add the period picker directive

Add the period picker directive

--- src/angular-moment-timepicker.coffee
+++ src/angular-moment-timepicker.coffee
@@ -32,7 +32,7 @@
       date.date(1)
       date.subtract(Math.abs(date.weekday()), 'days')
     compare: (a, b) ->
-      (moment(a).month()+1) - (moment(b).month() + 1)
+      (moment(a).month() + 1) - (moment(b).month() + 1)
     amount: 42
     line: moment.duration(7, 'day')
     step: moment.duration(1, 'day')
@@ -247,8 +247,16 @@
         if not scope.activeBefore? and not scope.activeAfter?
           step.active = false
         else
-          isBefore = if scope.activeBefore? then stepDate.isBefore(scope.activeBefore, scope.view) or stepDate.isSame(scope.activeBefore, scope.view) else true
-          isAfter = if scope.activeAfter? then stepDate.isAfter(scope.activeAfter, scope.view) or stepDate.isSame(scope.activeAfter, scope.view) else true
+          if scope.activeBefore?
+            isBefore = stepDate.isBefore(scope.activeBefore, scope.view) or stepDate.isSame(scope.activeBefore,
+                scope.view)
+          else
+            isBefore = true
+          if scope.activeAfter?
+            isAfter = stepDate.isAfter(scope.activeAfter, scope.view) or stepDate.isSame(scope.activeAfter, scope.view)
+          else
+            isAfter = true
+
           step.active = isBefore and isAfter
     )

@@ -271,10 +279,10 @@

       scope.steps = for i in [0..amount] by 1
         currentStep = {
-        past: step.compare(stepDate,scope.position) < 0
-        future: step.compare(stepDate,scope.position) > 0
-        formatted: moment(stepDate).format(step.format)
-        value: moment(stepDate).toDate()
+          past: step.compare(stepDate, scope.position) < 0
+          future: step.compare(stepDate, scope.position) > 0
+          formatted: moment(stepDate).format(step.format)
+          value: moment(stepDate).toDate()
         }
         stepDate.add(period)
         currentStep
@@ -342,7 +350,7 @@

   restrict: 'E'
   template: """
-

+

@@ -384,3 +392,125 @@

   }
 ]
+
+
+###
+    A directive that allow the selection of two datetime values.
+###
+module.directive 'periodDatetimePicker', ['dateTimePickerConfig', (defaultConfig)->
+  return {
+  scope:{
+    minDate: '=?'     # minimum selectionnable date.
+    maxDate: '=?'     # maximum selectionnable date.
+    view: '=?'
+    tokens: '&'
+    position: '=?'    # Default to ng-model.
+    from: "="
+    to: "="
+  }
+  replace:true
+  link:(scope, elm, attr)->
+
+    # The selected dates in the three datetimepicker.
+    scope.left = scope.middle = scope.right = null
+    scope.leftPos = scope.middlePos = scope.leftPos = moment().toDate() if not scope.position?
+    scope.hovered = null
+    scope.view = 'day'
+    scope.laa = scope.lab = scope.maa = scope.mab = scope.raa = scope.rab = null
+
+    scope.$watch('from + to + position', ->
+      return unless scope.from? and scope.to?
+
+      from = moment(scope.from)
+      to = moment(scope.to)
+
+      # Make sure we always start with the largest.
+      [from, to] = [to, from] if to < from
+
+      scope.rab = scope.mab = scope.lab = to.toDate()
+      scope.raa = scope.maa = scope.laa = from.toDate()
+    )
+
+    # Steps between the three directives, starting from the right.
+    viewsSteps =
+    minute:
+      duration: moment.duration(12, 'hour')
+      step: moment.duration(4, 'hour')
+    hour:
+      duration: moment.duration(24, 'hour')
+      step: moment.duration(8, 'hour')
+     #week:
+     #  duration:moment.duration(3, 'week')
+     #  step: moment.duration(7, 'day')
+    day:
+      duration: moment.duration(3, 'month')
+      step: moment.duration(1, 'month')
+    month:
+      duration: moment.duration(12, 'month')
+      step: moment.duration(4, 'month')
+    year:
+      duration: moment.duration(100, 'year')
+      step: moment.duration(4, 'month')
+
+    scope.$watch('position + view', ->
+      scope.leftPos = moment(moment(position) - (2 * viewsSteps[scope.view].step)).toDate()
+      scope.middlePos = moment(moment(position) - (1 * viewsSteps[scope.view].step)).toDate()
+      scope.rightPos = moment(moment(position) - (0 * viewsSteps[scope.view].step)).toDate()
+    )
+
+    selecting = null
+    changeRange = (newDate) ->
+      if not selecting?
+        scope.from = newDate
+        scope.to = null
+        selecting = scope.$watch('hover', (newDate) ->
+          scope.to = moment(newDate).toDate()
+        )
+      else
+        selecting()
+        selecting = null
+
+    scope.$watch('left', changeRange)
+    scope.$watch('middle', changeRange)
+    scope.$watch('right', changeRange)
+
+
+  restrict: 'E'
+  template: """
+

+

+

+

+

+      <moment-datetimepicker selected="left"
+                             position="leftPos"
+                             view="view"
+                             active-before="lab"
+                             active-after="laa"
+                             hovered="hover">
+      </moment-datetimepicker>
+

+

+      <moment-datetimepicker selected="right"
+                             position="middlePos"
+                             view="view"
+                             active-before="rab"
+                             active-after="raa"
+                             hovered="hover">
+

+

+      <moment-datetimepicker selected="middle"
+                             position="rightPos"
+                             view="view"
+                             active-before="mab"
+                             active-after="maa"
+                             hovered="hover">
+      </moment-datetimepicker>
+

+

+

+

+

+"""
+  }
+]


replace schemas with  content

replace schemas with  content

--- src/validator.coffee
+++ src/validator.coffee
@@ -193,8 +193,10 @@
       # we're in a recursive reference situation.
       # Ignore recursive references during this stage.
       if pointer.indexOf(uri + "/") != 0
-        schema.$ref = uri
-        if (schema = @resolve_uri(uri, scope))?
+        if (found_schema = @resolve_uri(uri, scope))?
+          delete schema.$ref
+          for k, v of found_schema
+            schema[k] = v
           @schema_references context, schema
         else
           # Store the unresolvable reference so we can try to resolve


Correct error.

Correct error.


removed "Todo" which is done

removed "Todo" which is done

--- src/art/engine/animation/persistant_animator.coffee
+++ src/art/engine/animation/persistant_animator.coffee
@@ -329,7 +329,6 @@
       continuous: t/f
       on: handlers

-      TODO:
       # added and removed animation values
       voidValue:  # both
       fromVoid:   # added animation


Fixed error where target_origin was set incorrectly [SJVC-17]

--- src/core/javascripts/controllers/pay_form.js.coffee
+++ src/core/javascripts/controllers/pay_form.js.coffee
@@ -54,15 +54,16 @@

   sendSubmittingEvent = () =>
-    origin = $location.protocol() + "://" + $location.host() + ":" + $location.port()
     referrer = $location.protocol() + "://" + $location.host()
     if $location.port()
       referrer += ":" + $location.port()
+    target_origin = $scope.referrer
+
     payload = JSON.stringify({
       'type': 'submitting',
       'message': referrer
     })
-    parent.postMessage(payload, origin)
+    parent.postMessage(payload, target_origin)

   submitPaymentForm = () =>
     payment_form = angular.element.find('form')


Trim `(edited)` from the ends of lines.

Trim `(edited)` from the ends of lines.

--- src/parsers/slackapp.coffee
+++ src/parsers/slackapp.coffee
@@ -39,5 +39,7 @@
     unless nick? and ts?
       throw new Error("Expected nick and timestamp line first.")

+    line = line.replace(/\s*\(edited\)$/, '')
+
     result.push "#{nick} [#{ts}] #{line}"
   result


module instance vars should be upper camel case

--- src/bumble-util.coffee
+++ src/bumble-util.coffee
@@ -5,11 +5,11 @@
 ###

-child_process = require "child_process"
-path = require 'path'
-fs = require 'fs'
+ChildProcess = require "child_process"
+Path = require 'path'
+Fs = require 'fs'
 _ = require 'underscore'
-moment = require 'moment'
+Moment = require 'moment'

 BStr = require 'bumble-strings'

@@ -28,7 +28,7 @@

   console.log("$ " + cmd) if options.echo
   try
-    out = child_process.execSync(cmd)
+    out = ChildProcess.execSync(cmd)
     process.stdout.write(out) if options.showOutput
   catch e
     console.error e if options.showOutput
@@ -39,7 +39,7 @@

 ###
-  Generic error handler that checks passed in error, from node fs functions for example,
+  Generic error handler that checks passed in error, from node Fs functions for example,
   and if there is an error, send it to console.error() and exit this process.
 ###
 handleError = (error) ->
@@ -85,9 +85,9 @@
   cmd += packageName

   packageExists = (
-    fs.existsSync("/usr/local/lib/node_modules/#{packageName}") ||
-    fs.existsSync("/opt/nodejs/current/lib/node_modules/#{packageName}") ||
-    fs.existsSync("./node_modules/#{packageName}")
+    Fs.existsSync("/usr/local/lib/node_modules/#{packageName}") ||
+    Fs.existsSync("/opt/nodejs/current/lib/node_modules/#{packageName}") ||
+    Fs.existsSync("./node_modules/#{packageName}")
   )
   unless packageExists
     if options.global
@@ -98,7 +98,7 @@
   Open a terminal tab (ONLY WORKS ON iterm2 or terminal apps on OSX)
 ###
 openTerminalTab = (cdPath = './', cmd='')->
-  cdPath = path.resolve(cdPath)
+  cdPath = Path.resolve(cdPath)
   console.log "opening terminal tab. maybe. to #{cdPath}. TERM_PROGRAM='#{process.env.TERM_PROGRAM}'"
   switch process.env.TERM_PROGRAM
     when 'iTerm.app'


:memo: Fix atom.workspace.open split parameter mismatch

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -394,7 +394,7 @@
   #     initially. Defaults to `0`.
   #   * `initialColumn` A {Number} indicating which column to move the cursor to
   #     initially. Defaults to `0`.
-  #   * `split` Either 'left', 'right', 'top' or 'bottom'.
+  #   * `split` Either 'left', 'right', 'up' or 'down'.
   #     If 'left', the item will be opened in leftmost pane of the current active pane's row.
   #     If 'right', the item will be opened in the rightmost pane of the current active pane's row.
   #     If 'up', the item will be opened in topmost pane of the current active pane's row.


Improve documentation for clarity.

Improve documentation for clarity.

--- src/server/db/amazon.coffee
+++ src/server/db/amazon.coffee
@@ -3,8 +3,8 @@
 # It uses Dynamo as the metadata store and it uses S3 for snapshot storage as
 # Dynamo objects are limited to 64KB in size.
 #
-# In order to use this backend you must require the 'aws2js' and the 'dynamo'
-# npm packages.
+# In order to use this backend you must require the 'aws2js', 'async' and the
+# 'dynamo' npm packages.
 #
 # Example usage:
 #
@@ -59,12 +59,11 @@
   # Public: Creates a new document.
   #
   # docName - The unique name of the new document.
-  # docData = {snapshot, type:typename, [meta]}
-  #
-  # FIXME: What should happen if it fails to save?
-  #
-  # Calls callback(true) if the document already exists. Calls callback() on
-  # success.
+  # docData - { snapshot:string, type:string, v:int, meta:string }
+  #
+  # Calls callback('Document already exists') if the document already exists.
+  # Calls callback(error) on failure.
+  # Calls callback() on success.
   @create = (docName, docData, callback) ->
     async.auto(
       write_metadata: (cb) ->
@@ -102,10 +101,11 @@
   # Public: Permanently deletes a document.
   #
   # docName - The name of the document to delete.
-  # dbMeta - ?
-  #
-  # Calls callback which takes a single argument which is null if something was
-  # deleted and the error message if something went wrong.
+  # dbMeta  - This argument is unused as it's unused in other storage engines
+  #
+  # Calls callback('Document does not exist') if no document exists.
+  # Calls callback(error) on failure.
+  # Calls callback(null) on success.
   @delete = (docName, dbMeta, callback) ->
     async.auto(
       list_snapshots: (cb) ->
@@ -202,9 +202,9 @@
   #
   # docName - The name of the document to retrieve.
   #
-  # data = {v, snapshot, type}. Snapshot == null and v = 0 if the document doesn't exist.
-  #
-  # Calls callback with (null, data) upon sucesss. Otherwise it calls callback with (error)
+  # Calls callback('Document does not exist') if no document exists.
+  # Calls callback(error) on failure.
+  # Calls callback(null, { v:int, snapshot:string, type:typename, meta:string }) on success.
   @getSnapshot = (docName, callback) ->
     async.auto(
       get_snapshot: (cb) ->
@@ -246,12 +246,14 @@
   # Public: Write new snapshot data to the database.
   #
   # docName - Name of document.
-  # docData - Document snapshot data. {snapshot:s, type:t, meta}
-  # dbMeta - ?
-  #
-  # The callback just takes an optional error.
+  # docData - { snapshot:string, type:typename, meta:string, v:int }
+  # dbMeta  - This argument is unused as it's unused in other storage engines
   #
   # This function has UNDEFINED BEHAVIOUR if you call append before calling create().
+  #
+  # Calls callback('Document already exists') if the document already exists.
+  # Calls callback(error) on failure.
+  # Calls callback() on success.
   @writeSnapshot = (docName, docData, dbMeta, callback) ->
     async.auto(
       write_metadata: (cb) ->
@@ -285,20 +287,15 @@
         callback?()
     )

-  # Public: Get all ops with version = start to version = end. Noninclusive.
-  # end is trimmed to the size of the document.
-  #
-  # If any documents are passed to the callback, the first one has v = start
-  # end can be null. If so, returns all documents from start onwards.
-  #
-  # Each document returned is in the form {op:o, meta:m, v:version}.
+  # Public: Get all operations between start and end noninclusive.
   #
   # docName - Name of the document
-  # start   - The noninclusive starting version.
-  # end     - The noninclusive ending version.
-  #
-  # Calls callback(null, data) on success and otherwise calls the callback with
-  # just the error.
+  # start   - The noninclusive starting version, must be less than end.
+  # end     - The noninclusive ending version, if null assumed to be maximum
+  #           value.
+  #
+  # Calls callback(error) on failure.
+  # Calls callback(null, [{ op:string, meta:string }]) on success.
   @getOps = (docName, start, end, callback) ->
     end = 2147483648 unless end?
     return callback("Start must be less than end", []) if start >= end
@@ -337,14 +334,17 @@

   # Public: Write an operation to a document.
   #
-  # opData = { op:the op to append, v:version, meta:optional }
-  # callback = callback when operation committed
+  # docName - Name of the document
+  # opData  - { op:string, v:int, meta:string }
   #
   # opData.v MUST be the subsequent version for the document.
   #
   # This function has UNDEFINED BEHAVIOUR if you call append before calling create().
-  # (its either that, or I have _another_ check when you append an op that the document already exists
-  # ... and that would slow it down a bit.)
+  # (its either that, or I have _another_ check when you append an op that the
+  # document already exists ... and that would slow it down a bit.)
+  #
+  # Calls callback(error) on failure.
+  # Calls callback() on success.
   @writeOp = (docName, opData, callback) ->
     async.auto(
       write_metadata: (cb) ->


Always open regular find bar, not last used

There are three find bars: The "regular" one, the "quick find" one and the
"quick find (link only)" one. Before, the VimFx `/` command opened the one
of those three that was used last. Now, the regular is always opened.

--- extension/packages/modes.coffee
+++ extension/packages/modes.coffee
@@ -47,7 +47,7 @@
   onEnter: (vim, storage, options) ->
     return unless findBar = utils.getRootWindow(vim.window)?.gBrowser.getFindBar()

-    findBar.open()
+    findBar.onFindCommand()
     findBar._findField.focus()
     findBar._findField.select()


test markdown in ping command

test markdown in ping command


Adjust integration spec.

Adjust integration spec.

--- src/spec/export.spec.coffee
+++ src/spec/export.spec.coffee
@@ -107,11 +107,11 @@
     expectedCSV =
       """
       productType,name,variantId
-      [a-z0-9\-]+,,1
+      myExportType,,1
       """
     @export.export template, file, (res) ->
       expect(res.status).toBe true
       expect(res.message).toBe 'Export done.'
       fs.readFile file, encoding: 'utf8', (err, content) ->
-        expect(content).toMatch expectedCSV
+        expect(content).toBe expectedCSV
         done()


do-the-git

do-the-git

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -179,9 +179,9 @@
   _render: (domain)->
     scale = exp(floor(log(16/domain.diagnal())/ln2)*ln2)

-    @save()
-    @beginPath()
-    self.transform().apply @context
+    @context.save()
+    @context.beginPath()
+    @transform().apply @context

     @context.moveTo 0, domain.y0
     @context.lineTo 0, domain.y1
@@ -189,10 +189,10 @@
     @context.moveTo domain.x0, 0
     @context.lineTo domain.x1, 0

-    @restore()
-    @lineWidth = 1
-    @strokeStyle = 'rgba(0,0,0,1)'
-    @stroke()
+    @context.restore()
+    @context.lineWidth = 1
+    @context.strokeStyle = 'rgba(0,0,0,1)'
+    @context.stroke()

 class Grid extends Layer
   constructor: (transforms)->
@@ -201,9 +201,9 @@
   _render: (domain)->
     scale = exp(floor(log(16/domain.diagnal())/ln2)*ln2)

-    @save()
-    @beginPath()
-    self.transform().apply @context
+    @context.save()
+    @context.beginPath()
+    @transform().apply @context

     for i in [floor(domain.x0*scale)..ceil(domain.x1*scale)]
       if i == 0
@@ -217,10 +217,10 @@
       @context.moveTo domain.x0, i/scale
       @context.lineTo domain.x1, i/scale

-    @restore()
-    @lineWidth = 1
-    @strokeStyle = 'rgba(0,0,0,0.125)'
-    @stroke()
+    @context.restore()
+    @context.lineWidth = 1
+    @context.strokeStyle = 'rgba(0,0,0,0.125)'
+    @context.stroke()

 class Fractal extends Layer

@@ -273,11 +273,17 @@
     expr = @expression
     valid = (v)-> v >= domain.y0 and v <= domain.y1

-    @begin()
     for s in [0..(1<<2)-1]
+      @context.save()
+      @context.beginPath()
+      self.transform().apply @context
+
       @_loop domain.x0, domain.x1, 1<<8, 1<<12, expr.bind(null, s), valid
-      @stroke @color, 3
-    @end()
+
+      @context.restore()
+      @context.lineWidth = 3
+      @context.strokeStyle = @color
+      @context.stroke()

 class Stack extends Element
   constructor: ->


fixed a empty string

fixed a empty string

--- src/coffee/util.coffee
+++ src/coffee/util.coffee
@@ -158,6 +158,6 @@
         dataName = name.replace( rName, "" )
         names = dataName.split("")
         names[0] = names[0].toLowerCase()
-        data[names.join('')] = if !isNaN val then Number val else val
+        data[names.join('')] = if !isNaN( val ) and val != '' then Number val else val
     data


JDomain::bind/unbind machine workaround for update, we need to fix this

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -256,11 +256,17 @@
         if err? then console.error err  unless err.code is 11000

+  # There is something wrong with instance level @update         FIXME ~ GG
+  # I'm using static update with instance @getId and it works
+  # but we need to look at this at some point, this is not correct
+
   bindMachine: (target, callback)->
-    @update $addToSet: machines: target, callback
+    JDomain.update {_id: @getId()}, $addToSet: machines: target, callback
+    # @update $addToSet: machines: target, callback

   unbindMachine: (target, callback)->
-    @update $pullAll: machines: [ target ], callback
+    JDomain.update {_id: @getId()}, $pullAll: machines: [ target ], callback
+    # @update $pullAll: machines: [ target ], callback

   bindMachine$: permit


StacksCustomViews: update default template, kloud will fill remaining

--- client/admin/lib/views/stacks/stackscustomviews.coffee
+++ client/admin/lib/views/stacks/stackscustomviews.coffee
@@ -34,15 +34,14 @@
     "provider": {
       "aws": {
         "access_key": "${var.access_key}",
-        "secret_key": "${var.secret_key}",
-        "region": "eu-central-1"
+        "secret_key": "${var.secret_key}"
       }
     },
     "resource": {
       "aws_instance": {
         "example": {
           "instance_type": "t2.micro",
-          "ami": "ami-936d9d93"
+          "ami": ""
         }
       }
     }


Wire up new plan button.

Wire up new plan button.

--- src/main/webapp/coffeescripts-hidden/manager.coffee
+++ src/main/webapp/coffeescripts-hidden/manager.coffee
@@ -9,3 +9,6 @@

   $('button.new-user').on 'click', ->
     document.location = "/admin/users/new"
+
+  $('button.new-plan').on 'click', ->
+    document.location = "/admin/plans/new"


LinkShell: styling and drop unnecessary parens

--- coffee/src/shells/link.shell.coffee
+++ coffee/src/shells/link.shell.coffee
@@ -28,7 +28,8 @@
   # Functions in this section are useful in dealing with
   # shells based on LinkShell

-  # Returns true if `link` matches pattern contained in array `validLinkPatterns`
+  # Returns true if `link` matches pattern contained in array
+  # `validLinkPatterns`
   # Returns false otherwise
   linkMatches: (link, validLinkPatterns) ->
     (_.find validLinkPatterns, (pattern) -> pattern.test link)?
@@ -42,7 +43,7 @@
       return LinkShell

     # parse link into a location object
-    location = acorn.util.parseUrl(link)
+    location = acorn.util.parseUrl link

     # filter out shell modules that don't derive from LinkShell
     shells = _.filter acorn.shells, (shell) ->
@@ -63,7 +64,7 @@
   # inheritence hierarchy.
   bestMatchingShell: (link) ->
     # obtain set of matching shells
-    matchingShells = LinkShell.matchingShells(link)
+    matchingShells = LinkShell.matchingShells link

     # reduce function to get the most specific shell (in terms of inheritance)
     reduceFn = (bestShell, shell) ->
@@ -71,7 +72,7 @@
         return bestShell
       else return shell

-    _.reduce(matchingShells, reduceFn, LinkShell)
+    _.reduce matchingShells, reduceFn, LinkShell

 # select functions above, attached to acorn namespace


Fix top level board discussion authors

Fix top level board discussion authors

--- app/talk/init.cjsx
+++ app/talk/init.cjsx
@@ -58,7 +58,7 @@
         boardsMeta = boards[0]?.getMeta()
         @setState {boards, boardsMeta, loading: false}
         boards.map (board) ->
-          author_ids.push board.latest_discussion.user_id
+          author_ids.push board.latest_discussion.latest_comment.user_id

         apiClient
           .type 'users'
@@ -83,13 +83,13 @@
                 prevState.author_roles[role.user_id].push role

   boardPreview: (data, i) ->
-    roles = @state.author_roles[data.latest_discussion.user_id]
+    roles = @state.author_roles[data.latest_discussion.latest_comment.user_id]
     roles ?= []
     <BoardPreview
       {...@props}
       key={i}
       data={data}
-      author={@state.authors[data.latest_discussion.user_id]}
+      author={@state.authors[data.latest_discussion.latest_comment.user_id]}
       roles={roles}
     />


Ensure position is a Point in Selection::selectToPosition

Fixes #1750

--- src/selection.coffee
+++ src/selection.coffee
@@ -1,4 +1,4 @@
-{Range} = require 'text-buffer'
+{Point, Range} = require 'text-buffer'
 {Emitter} = require 'emissary'
 {pick} = require 'underscore-plus'

@@ -141,6 +141,8 @@
   #
   # position - An instance of {Point}, with a given `row` and `column`.
   selectToScreenPosition: (position) ->
+    position = Point.fromObject(position)
+
     @modifySelection =>
       if @initialScreenRange
         if position.isLessThan(@initialScreenRange.start)


resize

resize

--- js/chrome.coffee
+++ js/chrome.coffee
@@ -101,6 +101,11 @@
     @elems.save.style.display = 'none'

     @sandbox = @elems.sandbox.contentWindow
+    window.addEventListener 'resize', @on.resize
+
+   @listen 'resize', ->
+    @elems.sandbox.style.width = "#{window.innerWidth}px"
+    @elems.sandbox.style.height = "#{window.innerHeight - 25}px"

    @listen 'print', ->
     @send 'print', {}


toolbar sidenote

toolbar sidenote

--- js/editor.coffee
+++ js/editor.coffee
@@ -13,20 +13,25 @@
      @div ".row", ->
       @div ".five.columns", ->
         @div ".toolbar", ->
-         @i ".fa.fa-header.fa-lg", on: {click: @$.on.header}
+         @i ".fa.fa-header", on: {click: @$.on.header}

-         @i ".fa.fa-bold.fa-lg", on: {click: @$.on.bold}
-         @i ".fa.fa-italic.fa-lg", on: {click: @$.on.italic}
-         @i ".fa.fa-link.fa-lg", on: {click: @$.on.link}
-         @i ".fa.fa-code.fa-lg", on: {click: @$.on.inlineCode}
-         @i ".fa.fa-camera.fa-lg", on: {click: @$.on.inlineMedia}
-         @i ".fa.fa-superscript.fa-lg", on: {click: @$.on.superscript}
-         @i ".fa.fa-subscript.fa-lg", on: {click: @$.on.subscript}
+         @i ".fa.fa-bold", on: {click: @$.on.bold}
+         @i ".fa.fa-italic", on: {click: @$.on.italic}
+         @i ".fa.fa-link", on: {click: @$.on.link}
+         @i ".fa.fa-code", on: {click: @$.on.inlineCode}
+         @i ".fa.fa-camera", on: {click: @$.on.inlineMedia}
+         @i ".fa.fa-superscript", on: {click: @$.on.superscript}
+         @i ".fa.fa-subscript", on: {click: @$.on.subscript}

-         @i ".fa.fa-table.fa-lg", on: {click: @$.on.table}
+         @i ".fa.fa-table", on: {click: @$.on.table}

-         @i ".fa.fa-indent.fa-lg", on: {click: @$.on.indent}
-         @i ".fa.fa-outdent.fa-lg", on: {click: @$.on.indent}
+         @i ".fa.fa-list-ol", on: {click: @$.on.listOl}
+         @i ".fa.fa-list-ul", on: {click: @$.on.listUl}
+
+         @i ".fa.fa-indent", on: {click: @$.on.indent}
+         @i ".fa.fa-outdent", on: {click: @$.on.outdent}
+
+         @i ".fa.fa-columns", on: {click: @$.on.sidenote}

         @$.elems.textarea = @textarea ".editor",
          autocomplete: "off"
@@ -57,14 +62,18 @@
     @editor.replaceSelection "#{b}#{s}#{e}"
     @editor.focus()

-   @listen 'header', ->
+   addSegment: (b) ->
     s = @editor.getSelection()
-    @editor.replaceSelection "\n##{s}\n"
+    @editor.replaceSelection "\n#{b}#{s}\n"
     {line} = @editor.getCursor()
     @editor.indentLine line - 1, 'prev'
     @editor.indentLine line, 'prev'
     @editor.indentLine line, 'add'
     @editor.focus()
+
+
+   @listen 'header', ->
+    @addSegment '#'

    @listen 'bold', -> @wrapSelection '**', '**'
    @listen 'italic', -> @wrapSelection '--', '--'
@@ -99,6 +108,20 @@
     for sel in sels
      for i in [sel.anchor.line..sel.head.line]
       @editor.indentLine i, 'subtract'
+
+   @listen 'listOl', -> @addSegment '- '
+   @listen 'listUl', -> @addSegment '* '
+
+   @listen 'sidenote', ->
+    s = @editor.getSelection()
+    @editor.replaceSelection "\n>>>\n#{s}"
+    {line} = @editor.getCursor()
+    @editor.indentLine line - 1, 'prev'
+    @editor.indentLine line, 'prev'
+    @editor.indentLine line, 'add'
+    @editor.focus()
+
+

    preview: ->


Tweak menu transitions to hide menu after transition.

--- js/flight.coffee
+++ js/flight.coffee
@@ -143,13 +143,16 @@
       window.setTimeout =>
         @translate(@mainMenu, "X", "-110%", "0.3s")
       ,10
+      window.setTimeout =>
+        @mainMenu.style.display = "none"
+      ,300
       @menuOpen = false
     else
-      @translate(@mainMenu, "X", "-100%", "0ms")
-      @mainMenu.style.display = "block"
+      @translate(@mainMenu, "X", "-110%", "0ms")
       window.setTimeout =>
         @translate(@mainMenu, "X", "0%", "0.3s")
       ,10
+      @mainMenu.style.display = "block"
       @menuOpen = true

   # Private: Close menu without transition


passport: support facebook

passport: support facebook

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -393,8 +393,8 @@
         auth_url = site_conf.auth
         dbg("auth_url=#{auth_url}")

-        init_passport_local = (cb) ->
-            dbg("init_passport_local")
+        init_local = (cb) ->
+            dbg("init_local")
             # Strategy: local email address / password login
             PassportStrategy = require('passport-local').Strategy

@@ -422,8 +422,8 @@

             cb()

-        init_passport_google = (cb) ->
-            dbg("init_passport_google")
+        init_google = (cb) ->
+            dbg("init_google")
             # Strategy: Google OAuth 2 -- https://github.com/jaredhanson/passport-google-oauth
             #
             # NOTE: The passport-recommend library passport-google uses openid2, which
@@ -466,8 +466,8 @@

                 cb()

-        init_passport_github = (cb) ->
-            dbg("init_passport_github")
+        init_github = (cb) ->
+            dbg("init_github")
             # Strategy: Github OAuth2 -- https://github.com/jaredhanson/passport-github
             PassportStrategy = require('passport-github').Strategy
             strategy = 'github'
@@ -499,7 +499,45 @@

                 cb()

-        async.parallel([init_passport_local, init_passport_google, init_passport_github], cb)
+
+        init_facebook = (cb) ->
+            dbg("init_facebook")
+            # Strategy: Facebook OAuth2 --
+            PassportStrategy = require('passport-facebook').Strategy
+            strategy = 'facebook'
+            get_conf strategy, (err, conf) ->
+                if err or not conf?
+                    cb(err)
+                    return
+                # Get these by going to https://developers.facebook.com/ and creating a new application.
+                # For that application, set the url to the site SMC will be served from.
+                # The Facebook "App ID" and is clientID and the Facebook "App Secret" is the clientSecret
+                # for oauth2, as I discovered by a lucky guess... (sigh).
+                #
+                # You must then put them in the database, via
+                #   update passport_settings set conf['clientID']='...'     where strategy='facebook';
+                #   update passport_settings set conf['clientSecret']='...' where strategy='facebook';
+
+                opts =
+                    clientID     : conf.clientID
+                    clientSecret : conf.clientSecret
+                    callbackURL  : "#{auth_url}/#{strategy}/return"
+                    enableProof  : false
+
+                verify = (accessToken, refreshToken, profile, done) ->
+                    console.log("#{strategy} auth: accessToken=",accessToken, " profile=", profile, "refreshToken=", refreshToken)
+                    done(undefined, {profile:profile})
+                passport.use(new PassportStrategy(opts, verify))
+
+                app.get "/auth/#{strategy}", passport.authenticate(strategy)
+
+                app.get "/auth/#{strategy}/return", passport.authenticate(strategy, {failureRedirect: '/auth/local'}), (req, res) ->
+                    console.log("/auth/#{strategy}/return")
+                    res.json(req.user)
+
+                cb()
+
+        async.parallel([init_local, init_google, init_github, init_facebook], cb)


bongo: remove unused arg

bongo: remove unused arg

--- node_modules_koding/bongo/lib/bongo.coffee
+++ node_modules_koding/bongo/lib/bongo.coffee
@@ -179,7 +179,7 @@
     switch type
       when 'instance'
         if event is 'messageBusEvent'
-          @publishEventToExchange event, params[0]
+          @publishEventToExchange params[0]
         else
           payload     = JSON.stringify params[0] or null
           routingKey  = getInstanceRoutingKey ctx, event
@@ -218,7 +218,7 @@

     return this

-  publishEventToExchange: (exchangeEvent, data) ->
+  publishEventToExchange: (data) ->

     return  unless @mqConfig


Bongo: registerModel->/s better handling multiple models from same file

--- node_modules_koding/bongo/lib/bongo.coffee
+++ node_modules_koding/bongo/lib/bongo.coffee
@@ -125,7 +125,7 @@
   defineModel: (name, model) ->

     unless model.addBongo? this
-      return
+      return null

     if @models[name]
       console.warn "Model #{name} defined multiple times!"
@@ -155,19 +155,23 @@
     return name

-  registerModel: (path) ->
-
-    cons = require path
-
+  registerModels: (path) ->
+
+    models = []
+    cons   = require path
+
+    # means that we've only one possible model candidate
+    # in provided path, return model definition.
     if cons.name? and ('function' is typeof cons)
-      @defineModel cons.name, cons
-
+      model = @defineModel cons.name, cons
+      models.push model  if model
     else
-      models = []
+      # there might be multiple models
       for own name, konstructor of cons
-        models.push @defineModel name, konstructor
-
-      models
+        model = @defineModel name, konstructor
+        models.push model  if model
+
+    return models

   findCode = (paths) ->
@@ -200,8 +204,8 @@
       debug "Loading #{Object.keys(cache).length} models..."

       Object.keys(cache).map (name) =>
-        @registerModel path.join @modelPaths[index], cache[name]
         debug 'Loading model:', name
+        @registerModels path.join @modelPaths[index], cache[name]

     .catch (err) =>

@@ -227,10 +231,15 @@
       findCode([_path]).map (codePath) =>

         Promise.try =>
-          model = @registerModel codePath
-          if model and not Array.isArray model
-            plainPath = codePath.replace _path, ''
-            cache[model] = plainPath
+          models    = @registerModels codePath
+          plainPath = codePath.replace _path, ''
+
+          if models and models.length > 0
+            debug models.join(', '), "model(s) found under:", plainPath
+            cache[model] = plainPath  for model in models
+
+          else
+            debug 'No model found under:', plainPath

         .catch (err) =>
           @emit 'error', \


Include cache stats in run script.

Include cache stats in run script.

--- script/run.coffee
+++ script/run.coffee
@@ -15,14 +15,14 @@

 m.on 'ready', ->
   console.log 'requesting', seed
-  m.request seed, console.log
+  m.request seed

 m.on 'quit', ->
   console.log 'quitting'

 count = 0
 rate = ->
-  console.log count
+  console.log(_.extend {keys_per_sec: count}, m.cache.stats)
   count = 0

 timer = setInterval rate, 1000
@@ -32,5 +32,5 @@
   # console.log key
   count++
   if key == seed
-    m.quit()
+    m.quit()
     clearInterval timer


Update highlights when grammar changes

Update highlights when grammar changes

--- lib/bracket-matcher-view.coffee
+++ lib/bracket-matcher-view.coffee
@@ -45,6 +45,9 @@
       @updateHighlights = true if @editor.getSoftWrap()

     @subscribe @editor, 'soft-wrap-changed', =>
+      @updateHighlights = true
+
+    @subscribe @editor, 'grammar-changed', =>
       @updateHighlights = true

     @subscribeToCursor()


path2poly undefined catch

path2poly undefined catch

--- src/geom/path2poly.coffee
+++ src/geom/path2poly.coffee
@@ -2,6 +2,7 @@

 module.exports = (path) ->

+  return [] unless path
   path = path.slice(1).slice(0,-1).split(/L|A/)
   poly = []


Update Settings.coffee

Update Settings.coffee

Co-authored-by: nate stemen <2a773820b1ffe416dfdb7571e38293ee2a4f683c@overleaf.com>

--- Settings.coffee
+++ Settings.coffee
@@ -38,7 +38,7 @@
 for file in possibleConfigFiles
 	if fs.existsSync(file)
 		if file.endsWith('.coffee')
-			console.warn "CoffeeScript settings file #{file}.coffee is deprecated, please convert to JavaScript"
+			console.warn "CoffeeScript settings file #{file} is deprecated, please convert to JavaScript"
 		console.log "Using settings from " + file
 		module.exports = merge(require(file), defaults)
 		settingsExist = true


Define snippets for React/react-native

Define snippets for React/react-native

* `rcont` generates the skeleton for a Redux container
* `rnss` generates the skeleton for a React Native stylesheet

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -19,3 +19,24 @@
 # If you are unfamiliar with CSON, you can read more about it in the
 # Atom Flight Manual:
 # https://atom.io/docs/latest/using-atom-basic-customization#cson
+
+'.source.js':
+  'Redux container':
+    'prefix': 'rcont'
+    'body': """
+            import { connect } from 'react-redux'
+
+            import $1 from '../components/$1'
+
+            export default connect()($1)
+
+            """
+  'Stylesheet (React Native)':
+    'prefix': 'rnss'
+    'body': """
+            import { StyleSheet } from 'react-native'
+
+            export default StyleSheet.create({
+            })
+
+            """


Update snippets.cson

Update snippets.cson

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -9,7 +9,10 @@
 '.text.html.basic':
   'a link':
     'prefix': 'alink'
-    'body': '$2'
+    'body': '$2'
+  'register link':
+    'prefix': 'registerLink'
+    'body': ' $2'
   'talk left':
     'prefix': 'tleft'
     'body': '

${1:メッセージ}

'


Remove old no-op line

Remove old no-op line

--- src/core/shapes.coffee
+++ src/core/shapes.coffee
@@ -175,7 +175,6 @@
     @x2 = args.x2 or 0
     @y2 = args.y2 or 0
     @strokeWidth = args.strokeWidth or 1
-    @strokeStyle = args.strokeStyle or null
     @color = args.color or 'black'
     @capStyle = args.capStyle or 'round'
     @endCapShapes = args.endCapShapes or [null, null]


getClass() checks CoffeeScript compatibility of a class before returning it.

--- src/utilities.coffee
+++ src/utilities.coffee
@@ -14,15 +14,21 @@
   getClass: ( value ) ->
     if value is null or value == ""
       # Special cases used to clear class-valued properties.
-      null
-    else if jQuery.isFunction value
-      value
+      return null
     else if jQuery.isPlainObject value
-      Control.sub value
-    else
-      classFn = window[ value ]
-      throw "Unable to find a class called #{value}." unless classFn
-      classFn
+      # Create a new class from the supplied Control JSON.
+      return Control.sub value
+
+    classFn = if jQuery.isFunction value
+      value # Value is already a class
+    else
+      window[ value ] # Look up class using value as a string class name.
+    throw "Unable to find a class called #{value}." unless classFn
+
+    # Ensure class is compatible before returning it.
+    if coffeeClassNeedsCompatibility classFn
+      makeCoffeeClassCompatible classFn
+    classFn

   # Return true if the given element is a control.


Move binding and subview destruction into their own methods

--- src/view/view.coffee
+++ src/view/view.coffee
@@ -146,7 +146,8 @@
     set: (key, html) ->
       @html = html
       @isBound = false
-      binding.die() for binding in @bindings
+      @destroyBindings()
+      @destroySubviews()

       @loadView(@node) if @node and html?
       @initializeBindings() if @bindImmediately
@@ -190,6 +191,12 @@
     @ready?()

   destroyBindings: ->
+    binding.die() for binding in @bindings
+    bindings = []
+
+  destroySubviews: ->
+    subview.die() for subview in @subviews.toArray()
+    @subviews.clear()

   baseForKeypath: (keypath) ->
     keypath.split('.')[0].split('|')[0].trim()
@@ -237,8 +244,8 @@
     if @_batman.events
       event.clearHandlers() for _, event of @_batman.events

-    binding.die() for binding in @bindings
-    subview.die() for subview in @subviews.toArray()
+    @destroyBindings()
+    @destroySubviews()

     @node = null


Don't recheck yieldNodes on _nodesChanged.

--- src/view/view.coffee
+++ src/view/view.coffee
@@ -51,6 +51,9 @@
     subview.set('superview', this)
     subview.fire('viewDidMoveToSuperview')

+    if (yieldName = subview.contentFor) and not subview.parentNode
+      yieldObject = Batman.DOM.Yield.withName(yieldName)
+      yieldObject.set('contentView', this)

     @observe('node', subview._nodesChanged)
     subview.observe('node', subview._nodesChanged)
@@ -78,19 +81,15 @@
     @superview?.subviews.remove(this)

   _nodesChanged: ->
+    return if not @node
     @initializeBindings() if @bindImmediately

-    if (yieldName = @contentFor) and not @parentNode
-      yieldObject = Batman.DOM.Yield.withName(yieldName)
-      yieldObject.set('contentView', this)
-
-    else
-      superviewNode = @superview.get('node')
-      parentNode = @parentNode
-      parentNode = Batman.DOM.querySelector(superviewNode, parentNode) if typeof parentNode is 'string'
-      parentNode = superviewNode if not parentNode
-
-      @addToParentNode(parentNode)
+    superviewNode = @superview.get('node')
+    parentNode = @parentNode
+    parentNode = Batman.DOM.querySelector(superviewNode, parentNode) if typeof parentNode is 'string'
+    parentNode = superviewNode if not parentNode
+
+    @addToParentNode(parentNode)

   addToParentNode: (parentNode) ->
     return if not @get('node')


Update note explaining the usage of the protocol

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -509,9 +509,12 @@
   #
   # Note that the opener will be called if and only if the URI is not already open
   # in the current pane. The searchAllPanes flag expands the search from the
-  # current pane to all panes. If the file path is converted from a standard path,
-  # e.g. /foo/bar/baz.html, to a URI with a fake protocol, e.g. custom://foo/bar/baz.html,
-  # it wouldn't conflict and the opener will be invoked.
+  # current pane to all panes. If you wish to open a view of a different type for
+  # a file that is already open, consider changing the protocol of the URI. For
+  # example, perhaps you wish to preview a rendered version of the file /foo/bar/baz.quux
+  # that is already open in a text editor view. You could signal this by calling
+  # {Workspace::open} on the URI quux-preview://foo/bar/baz.quux. Then your opener
+  # can check the protocol for quux-preview and only handle those URIs that match.
   addOpener: (opener) ->
     if includeDeprecatedAPIs
       packageName = @getCallingPackageName()


Fix screenshot bugs.

Fix screenshot bugs.

--- engine/render.coffee
+++ engine/render.coffee
@@ -951,23 +951,24 @@
             old_h = @height
             @width = size[0]
             @height = size[1]
-            vp_sizes = []
+            restore_vp = []
             for v in @viewports
+                {dest_buffer, rect} = v
                 if v.dest_buffer == @main_fb
+                    restore_vp.push {v, dest_buffer, rect}
                     v.dest_buffer = render_buffer
-                    vp_sizes.push v.rect
-                    v.rect = [v.rect[0]*x_ratio_render, v.rect[1]*y_ratio_render,
-                              v.rect[2]*x_ratio_render, v.rect[3]*y_ratio_render]
+                    v.rect = [rect[0]*x_ratio_render, rect[1]*y_ratio_render,
+                              rect[2]*x_ratio_render, rect[3]*y_ratio_render]
                     v.recalc_aspect()
             # render
             @draw_all()
             # restore viewports
             @width = old_w
             @height = old_h
-            for v in @viewports
-                if v.dest_buffer == render_buffer
-                    v.dest_buffer = @main_fb
-                    v.rect = vp_sizes.unshift()
+            for {v, dest_buffer, rect} in restore_vp
+                v.dest_buffer = dest_buffer
+                v.rect = rect
+                v.recalc_aspect()
             # resize/flip
             out_buffer.enable()
             filter = @filters.resize
@@ -980,7 +981,10 @@
             {gl} = @
             gl.readPixels 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(image_data.data.buffer)
             ctx.putImageData image_data, 0, 0
-            canvas.toBlob resolve, 'image/'+format, jpeg_quality
+            canvas.toBlob (blob) ->
+                document.body.removeChild canvas
+                resolve blob
+            , 'image/'+format, jpeg_quality
             return

     # @nodoc
@@ -1081,8 +1085,8 @@
                 for n in ob.data.num_indices
                     removed_polys += n
                 ob.visible = false
-                scene.mesh_passes[0].remove ob
-                scene.mesh_passes[1].remove ob
+                scene.mesh_passes[0] _,1 if (_ = scene.mesh_passes[0] ob)!=-1
+                scene.mesh_passes[1] _,1 if (_ = scene.mesh_passes[1] ob)!=-1
                 @removed_meshes.push ob
         return


Flip normals of meshes with negative scale.

--- engine/render.coffee
+++ engine/render.coffee
@@ -361,6 +361,9 @@
         if amesh.materials.length != amesh.material_names.length
             amesh.configure_materials()
             return
+
+        flip_normals = mesh.scale[0]*mesh.scale[1]*mesh.scale[2] < 0
+
         # Main routine for each submesh
         submesh_idx = -1

@@ -514,7 +517,12 @@
                 gl.uniformMatrix4fv mat.u_model_view_matrix, false, m4
                 mat3.multiply m3, @_world2cam3, mesh.normal_matrix
                 gl.uniformMatrix3fv mat.u_normal_matrix, false, m3
-                gl.drawElements data.draw_method, num_indices, gl.UNSIGNED_SHORT, 0
+                if flip_normals
+                    gl.frontFace 2304 # gl.CW
+                    gl.drawElements data.draw_method, num_indices, gl.UNSIGNED_SHORT, 0
+                    gl.frontFace 2305 # gl.CCW
+                else
+                    gl.drawElements data.draw_method, num_indices, gl.UNSIGNED_SHORT, 0
             if mirrors & 178
                 mat4.multiply m4, @_world2cam_mx, mesh2world
                 gl.uniformMatrix4fv mat.u_model_view_matrix, false, m4


Add mouseover with data type for col headers

--- lib/pg-hoff-results-view.coffee
+++ lib/pg-hoff-results-view.coffee
@@ -26,9 +26,10 @@
         compare = @getCompare(typeName, ascending, columnIndex)
         resultset.rows.sort compare

-    createTh: (text, resultsetIndex, columnIndex) ->
+    createTh: (col, resultsetIndex, columnIndex) ->
         th = document.createElement('th')
-        th.textContent = text
+        th.textContent = col.name
+        th.setAttribute 'title', col.type
         th.textContent += if @resultsets[resultsetIndex].columns[columnIndex].ascending then ' +' else ' -' ? ''

         th.onclick = =>
@@ -65,7 +66,7 @@
         if x.columns?
             col_tr = table.appendChild(document.createElement('tr'))
             for c, i in x.columns
-                col_tr.appendChild(@createTh(c.name, resultsetIndex, i))
+                col_tr.appendChild(@createTh(c, resultsetIndex, i))

         # Rows
         if x.rows?


add google lib in build

add google lib in build

--- GruntFile.coffee
+++ GruntFile.coffee
@@ -173,6 +173,7 @@
     requirejs:
       cms:
         options:
+          googlemaps: params: libraries: 'drawing'
           appDir: 'components/cms'
           baseUrl: 'vendor'
           fileExclusionRegExp: /^(server|spec)/


updates grunt tasks

updates grunt tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,4 +1,3 @@
-# config = require("./config.js")()
 nodemon = require "./nodemon.json"

 module.exports = (grunt) ->
@@ -15,24 +14,23 @@
 				options:
 					filter: "include",
 					tasks: [
-						"serve-dev",
-						"wiredep"
+						"serve-dev"
 					]
 					descriptions:
-						"serve-dev": "Boots up server and opens your default browser",
-						"wiredep": "Wire in bower dependencies"
+						"serve-dev": "Boots up server and opens your default browser"

 		# runs tasks concurrently
 		concurrent:
 			dev: [
-				"nodemon:dev"
+				"nodemon:dev",
+				"watch"
 			]
 			options:
 				logConcurrentOutput: true

-		# boots up nodemon for us
+		# boots up nodemon
 		nodemon:
 			dev:
 				scripts: "bin<%= path.sep %>www"
@@ -64,10 +62,81 @@
 		# wire bower dependencies
 		wiredep:
 			tasks:
+				directory: "bower_components"
 				src: [
 					"views<%= path.sep %>layout.jade"
 				]
-
+				cwd: './'
+				exclude: [
+					"bin/materialize.css"
+				]
+				ignorePath: /^(\.\.\/)/
+
+
+		# compile sass to css
+		sass:
+			dev:
+				options:
+					compress: false
+				files: [
+					"public<%= path.sep %>css<%= path.sep %>app.css" : "src<%= path.sep %>sass<%= path.sep %>materialize.scss",
+				]
+
+
+		# watches files and runs tasks when the files change
+		watch:
+			options:
+				livereload: true
+
+			sassfiles:
+				files: [
+					"src<%= path.sep %>sass<%= path.sep %>**<%= path.sep %>*.scss"
+				]
+				tasks: ['sass:dev']
+				options:
+					spawn: false
+
+			# images:
+			# 	files: ['src<%= path.sep %>images<%= path.sep %>**<%= path.sep %>*.*']
+			# 	tasks: ['clean:images', 'copy:images']
+			# 	#tasks: if config.env == 'dev' then ["clean:images", "copy:images"]
+			# 	options:
+			# 		spawn: false
+
+			# jadefiles:
+			# 	files: ["views<%= path.sep %>**<%= path.sep %>*.jade"]
+			# 	options:
+			# 		spawn: false
+
+			# jsfiles:
+			# 	files: [
+			# 		"routes<%= path.sep %>**<%= path.sep %>*.js",
+   #                  "config.js",
+   #                  "app-config.js"
+			# 	]
+			# 	options:
+			# 		spawn: false
+
+			# jsassets:
+			# 	files: [
+			# 		"src<%= path.sep %>js<%= path.sep %>*.js",
+			# 		"lib<%= path.sep %>**<%= path.sep %>*.js",
+			# 	]
+			# 	tasks: ['uglify:dev']
+			# 	options:
+			# 		spawn: false
+
+			# gruntfile:
+			# 	files: ["gruntfile.js"]
+			# 	tasks: ["jshint:gruntfile"]
+			# 	options:
+			# 		spawn: false
+
+			# mastercss:
+			# 	files: ["src<%= path.sep %>css<%= path.sep %>*.css"]
+			# 	tasks: ["cssmin:build"]
+			# 	options:
+			# 		spawn: false

 		# # automagically prefix our css
@@ -208,61 +277,7 @@
 		# 			"public<%= config.routes.writeAReview.css %>" : config.routes.writeAReview.styleAssets

-		# # watches files and runs tasks when the files change
-		# watch:
-		# 	options:
-		# 		livereload: true
-
-		# 	images:
-		# 		files: ['src<%= path.sep %>images<%= path.sep %>**<%= path.sep %>*.*']
-		# 		tasks: ['clean:images', 'copy:images']
-		# 		#tasks: if config.env == 'dev' then ["clean:images", "copy:images"]
-		# 		options:
-		# 			spawn: false
-
-		# 	jadefiles:
-		# 		files: ["views<%= path.sep %>**<%= path.sep %>*.jade"]
-		# 		options:
-		# 			spawn: false
-
-		# 	jsfiles:
-		# 		files: [
-		# 			"routes<%= path.sep %>**<%= path.sep %>*.js",
-  #                   "config.js",
-  #                   "app-config.js"
-		# 		]
-		# 		options:
-		# 			spawn: false
-
-		# 	jsassets:
-		# 		files: [
-		# 			"src<%= path.sep %>js<%= path.sep %>*.js",
-		# 			"lib<%= path.sep %>**<%= path.sep %>*.js",
-		# 		]
-		# 		tasks: ['uglify:dev']
-		# 		options:
-		# 			spawn: false
-
-		# 	lessfiles:
-		# 		files: [
-		# 			"src<%= path.sep %>less<%= path.sep %>**<%= path.sep %>*.less",
-		# 			"src<%= path.sep %>less<%= path.sep %>**<%= path.sep %>*.css"
-		# 		]
-		# 		tasks: ['less:dev']
-		# 		options:
-		# 			spawn: false
-
-		# 	gruntfile:
-		# 		files: ["gruntfile.js"]
-		# 		tasks: ["jshint:gruntfile"]
-		# 		options:
-		# 			spawn: false
-
-		# 	mastercss:
-		# 		files: ["src<%= path.sep %>css<%= path.sep %>*.css"]
-		# 		tasks: ["cssmin:build"]
-		# 		options:
-		# 			spawn: false
+

 		# # creates release branch
@@ -414,8 +429,7 @@
 	# grunt.registerTask("dev", ["string-replace:dev", "less:dev", "uglify:dev", "concurrent:backend"])
 	# grunt.registerTask("vetcss", ["clean:tmp", "less:vet", "postcss:vet", "csslint:tmp"])
 	grunt.registerTask("default", ["availabletasks"])
-	grunt.registerTask("serve-dev", ["concurrent:dev"])
-	# grunt.registerTask("wiredep")
+	grunt.registerTask("serve-dev", ["wiredep", "sass:dev", "concurrent:dev"])


stylus code in gruntfile

stylus code in gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -14,6 +14,26 @@
         files: {
           'build/js/main.js': 'angular/main.coffee', # 1:1 compile
           #'path/to/another.js': ['path/to/sources/*.coffee', 'path/to/more/*.coffee'] # compile and concat into single file
+        }
+      }
+    }
+
+    stylus: {
+      compile: {
+        options: {
+          paths: ['path/to/import', 'another/to/import']
+          urlfunc: 'embedurl' # use embedurl('test.png') in our code to trigger Data URI embedding
+          use: [
+            require('fluidity') # use stylus plugin at compile time
+          ]
+          import: [      #  @import 'foo', 'bar/moo', etc. into every .styl file
+            'foo'       #  that is compiled. These might be findable based on values you gave
+            'bar/moo'    #  to `paths`, or a plugin you added under `use`
+          ]
+        }
+        files: {
+          'path/to/result.css': 'path/to/source.styl', # 1:1 compile
+          'path/to/another.css': ['path/to/sources/*.styl', 'path/to/more/*.styl'] # compile and concat into single file
         }
       }
     }


Development workflow updates on Gruntfile

Enable Livereload on payload files.
Don't load minified payload on development.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -59,7 +59,9 @@
               match: 'payload_hash'
               replacement: ->
                 filename = grunt.file.readJSON('compiled/assets.json')['js/payload.js']
-                filename.replace '.js', '.min.js'
+                if grunt.config('env') isnt 'development'
+                  filename = filename.replace('.js', '.min.js')
+                filename
             }
             {
               match: 'base'
@@ -106,6 +108,8 @@
         tasks: ['karma:unit:run']

       payload:
+        options:
+          livereload: true
         files: [
           'config/settings/*.yml'
           'src/**/*.coffee'


Don't lint fixtures

Don't lint fixtures

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -8,7 +8,11 @@
           level: 'ignore'

       src: ['src/**/*.coffee']
-      test: ['spec/**/*.coffee']
+      test: [
+        'spec/*.coffee'
+        'spec/app/**/*.coffee'
+        'spec/stdlib/**/*.coffee'
+      ]

   grunt.loadNpmTasks('grunt-coffeelint')


chore: remove unused tasks

chore: remove unused tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -183,9 +183,6 @@
       s3:
         command: "s3cmd sync -rP --guess-mime-type --delete-removed --no-preserve --cf-invalidate --exclude '.DS_Store' <%= core.var.static_files %> <%= core.var.s3_bucket %>"

-      log:
-        command: "git log v<%= core.pkg.version %>..HEAD --reverse --format=%B | sed '/^$/d' | sed 's/^/- /'"
-
     concurrent:
       options:
         logConcurrentOutput: true
@@ -244,8 +241,5 @@
   # Sync image assets with `s3cmd`
   grunt.registerTask "s3", ["shell:s3"]

-  # Dump git log
-  grunt.registerTask "log", ["shell:log"]
-
   # Default task aka. build task
   grunt.registerTask "default", ["build"]


preserve quote style during js compression

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -53,6 +53,8 @@

     uglify:
       my_target:
+        options:
+          quoteStyle: 3
         files: [ {
           expand: true
           cwd: '_site/dist/js/'


Invoke grunt callback when shell test command completes

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -158,8 +158,9 @@
         options:
           stdout: true
           stderr: true
-          callback: (error, stdout, stderr) ->
+          callback: (error, stdout, stderr, callback) ->
             grunt.warn('Specs failed') if error?
+            callback()

   grunt.loadNpmTasks('grunt-coffeelint')
   grunt.loadNpmTasks('grunt-lesslint')


Fix defaults for 'fs'

Fix defaults for 'fs'

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -35,7 +35,7 @@
           resolve:
             extensions: ["", ".coffee", ".js"]
           node:
-            fs: "mock"
+            fs: "empty"
         ignores: [
           /bin\/coffee/
         ]


Added missing grunt-build-control config.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -44,6 +44,7 @@
         dir: 'prod'
         commit: true
         push: true
+        connectCommits: false
         message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
       pages:
         options:


Disable Recess until it supports LESS 1.6

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -247,7 +247,7 @@
     'jshint'
     'jsbeautifier'
     'coffeelint'
-    'recess'
+    #'recess' NOTE: Disabled until recess is upgraded to support LESS 1.6+
   ]

   # Dist


fix(Gruntfile.coffee): actually include the default template file in the dist/build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,6 +29,7 @@
     'dist': './_dist'
     'build': './_build'
     'src': './client'
+    'html': './client/html'
     'docs': './docs'
     'test': './test'

@@ -355,27 +356,27 @@
           }
           {
             expand: true
-            cwd: paths.build
+            cwd: paths.html
             src: ['*.html']
-            dest: "#{paths.dist}/<%= pkg.name %>-<%= pkg.version %>/"
-          }
-          {
-            expand: true
-            cwd: paths.build
+            dest: "#{paths.dist}/<%= pkg.name %>-<%= pkg.version %>/templates/"
+          }
+          {
+            expand: true
+            cwd: paths.html
             src: ['*.html']
-            dest: "#{paths.dist}/s3.<%= pkg.name %>-<%= pkg.version %>/"
-          }
-          {
-            expand: true
-            cwd: paths.build
+            dest: "#{paths.dist}/s3.<%= pkg.name %>-<%= pkg.version %>/templates/"
+          }
+          {
+            expand: true
+            cwd: paths.html
             src: ['*.html']
-            dest: "#{paths.dist}/jquery.<%= pkg.name %>-<%= pkg.version %>/"
-          }
-          {
-            expand: true
-            cwd: paths.build
+            dest: "#{paths.dist}/jquery.<%= pkg.name %>-<%= pkg.version %>/templates/"
+          }
+          {
+            expand: true
+            cwd: paths.html
             src: ['*.html']
-            dest: "#{paths.dist}/s3.jquery.<%= pkg.name %>-<%= pkg.version %>/"
+            dest: "#{paths.dist}/s3.jquery.<%= pkg.name %>-<%= pkg.version %>/templates/"
           }
         ]
       build:


Order.

Order.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -19,10 +19,12 @@
 	# Project configuration.
 	grunt.initConfig
 		nodeunit:
-			all: [
+			js: [
 				'tests_node/health-check.js'
+				'tests_node/*.js'
+			]
+			coffee: [
 				'tests_node/health-check.coffee'
-				'tests_node/*.js'
 				'tests_node/*.coffee'
 			]

@@ -129,7 +131,11 @@
 	# Basic tasks.
 	grunt.registerTask 'check', ['checkstrict', 'checklicense', 'coffeelint', 'jshint:all']
 	grunt.registerTask 'build', ['browserify', 'uglify']
-	grunt.registerTask 'test', ['jscoverage', 'coffeeCoverage', 'nodeunit:all', 'jscoverage_report']
+	grunt.registerTask 'test', [
+		'jscoverage', 'coffeeCoverage',    # order is important
+		'nodeunit:js', 'nodeunit:coffee',  # order is important
+		'jscoverage_report'
+	]
 	if grunt.option('target')?
 		grunt.config.set 'nodeunit.one', [ 'tests_node/'+grunt.option('target') ]


src/**/* to dist/**/*

src/**/* to dist/**/*

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -36,9 +36,9 @@
         coffee:
             dist:
                 expand: true
-                cwd: 'src/lib'
+                cwd: 'src'
                 src: ['**/*.coffee']
-                dest: 'dist/lib/'
+                dest: 'dist/'
                 ext: '.js'
                 extDot: 'first'
                 options:


configured Jade to output pretty HTML for template files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -40,6 +40,8 @@
           'templates/layout-3.html':      ['src/jade/layout-3.jade']
           'templates/layout-4.html':      ['src/jade/layout-4.jade']
           'templates/layout-5.html':      ['src/jade/layout-5.jade']
+        options:
+          pretty: true
       tests:
         files:
           'tests/test-buttons.html':      ['tests/src/jade/test-buttons.jade']


Move nginx config file location

Move nginx config file location

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -394,7 +394,7 @@
 			command.push(
 				"--maintainer", "ShareLaTeX <team@sharelatex.com>"
 				"--config-files", "/etc/sharelatex/settings.coffee"
-				"--config-files", "/etc/nginx/sites-enabled/sharelatex"
+				"--config-files", "/etc/nginx/conf.d/sharelatex.conf"
 				"--directories",  "/var/lib/sharelatex"
 				"--directories",  "/var/log/sharelatex"
 			)
@@ -452,7 +452,7 @@

 			command.push(
 				"package/config/settings.coffee=/etc/sharelatex/settings.coffee"
-				"package/nginx/sharelatex=/etc/nginx/sites-enabled/sharelatex"
+				"package/nginx/sharelatex=/etc/nginx/conf.d/sharelatex.conf"
 			)
 			console.log "fpm " + command.join(" ")
 			proc = spawn "fpm", command, stdio: "inherit"


Fixing a few bugs

Fixing a few bugs

Fixing bug in expression rendering in calculator dealing with
parentheses
fixed bug in equation builder where eq builder was not working
properly when rendered in page

--- support/spec_helpers.coffee
+++ support/spec_helpers.coffee
@@ -8,6 +8,7 @@
   else window.ttm.Logger.buildProduction(stringify_objects: false)
 class_mixer = ttm.class_mixer

+window.parseEntities = (str)-> $("<div>#{str}</div>").text()

 jasmine.getFixtures().findContainer = ->
   $(document.body).find("##{@containerId}")


Chages equation equality spec helper to use equation components toString Methods

--- support/spec_helpers.coffee
+++ support/spec_helpers.coffee
@@ -50,9 +50,9 @@
       @actual && @actual instanceof type

     toBeAnEqualExpressionTo: (other)->
-
       @message = ->
-        "Expected #{@actual}.isEqual(#{jasmine.pp other}) to be true"
+        # debugger
+        "Expected #{@actual.toString()} to be equal to #{other.toString()}"
       @actual && other && ttm.require('lib/math/expression_equality').isEqual(@actual, other)
   )


removed deprecated Mercury.bind function.

--- vendor/assets/javascripts/mercury/mercury.js.coffee
+++ vendor/assets/javascripts/mercury/mercury.js.coffee
@@ -60,11 +60,6 @@
     jQuery(top).trigger("mercury:#{eventName}", options)

-  bind: (eventName, callback) -> # todo: deprecated -- use 'on' instead
-    Mercury.deprecated('Mercury.bind is deprecated, use Mercury.on instead')
-    Mercury.on(eventName, callback)
-
-
   # Alerting and logging methods
   notify: (args...) ->
     window.alert(Mercury.I18n.apply(@, args))


:u6709::u7a7a: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -3,7 +3,7 @@
 remoteDependencies: [
   "//code.jquery.com/jquery-1.10.1.min.js"
   "//cdnjs.cloudflare.com/ajax/libs/coffee-script/1.6.3/coffee-script.min.js"
-  "http://strd6.github.io/tempest/javascripts/envweb-v0.4.6.js"
+  "http://strd6.github.io/tempest/javascripts/envweb-v0.4.7.js"
   "http://strd6.github.io/require/v0.2.2.js?"
 ]
 dependencies: