FIX: flushCache now really flushes everything ALL 840 PASSINg

--- source/Art/Engine/Core/Drawing/DrawCacheManager.coffee
+++ source/Art/Engine/Core/Drawing/DrawCacheManager.coffee
@@ -77,11 +77,17 @@
     super
     @_currentFrameNumber = 0
     @_maxCacheByteSize = 64*1024*1024 # 128 megs
+    @_bitmapsCreated = 0
+
+    @_initCache()
+
+  _initCache: ->
     @_cacheByteSize = 0
+    @_cacheBitmaps = new Map
+
+    @_unusedCacheBitmaps = []
     @_unusedCacheByteSize = 0
-    @_cachedBitmaps = new Map
-    @_bitmapsCreated = 0
-    @_unusedCacheBitmaps = []
+

   # EFFECT: all cached bitmaps are released
   # OUT: {bytesReleased, bitmapsReleased}
@@ -96,12 +102,12 @@
     bitmapsReleased = 0
     bytesReleased = 0

-    @_cachedBitmaps.forEach (cachedBitmap, element) =>
-      if cachedBitmap = mapRemove @_cachedBitmaps, element
-        cachedBitmap.elementDoneWithCacheBitmap()
-        bitmapsReleased++
-        bytesReleased += cachedBitmap.getByteSize()
-        @_cacheByteSize -= cachedBitmap.getByteSize()
+    @_cacheBitmaps.forEach (cachedBitmap, element) =>
+      cachedBitmap.elementDoneWithCacheBitmap()
+      bitmapsReleased++
+      bytesReleased += cachedBitmap.getByteSize()
+
+    @_initCache()

     # @_validateCacheByteSize "flushCache done"

@@ -115,8 +121,10 @@
   @getCacheInfo: -> DrawCacheManager.singleton.getCacheInfo()
   getCacheInfo: ->
     {
-      bitmapsCached: @_cachedBitmaps.size
-      averageByteSize: @cacheByteSize / @_cachedBitmaps.size | 0
+      cacheBitmaps:      @_cacheBitmaps.size
+      unusedCacheBitmaps: @_unusedCacheBitmaps.length
+      @bitmapsCreated
+      @maxCacheByteSize
       @cacheByteSize
       @unusedCacheByteSize
     }
@@ -126,12 +134,12 @@
   validateCacheByteSize: (context) ->
     # log "_validateCacheByteSize #{context} _cacheByteSize: #{@_cacheByteSize}"
     # disabled for now; seems OK August 19, 2017
-    cachedBitmaps = []
+    cacheBitmaps = []
     unusedCacheByteSize = 0
     cacheByteSize = 0
-    @_cachedBitmaps.forEach (bitmap, element) ->
+    @_cacheBitmaps.forEach (bitmap, element) ->
       cacheByteSize += bitmap.byteSize
-      cachedBitmaps.push {bitmap, element:element.inspectedName}
+      cacheBitmaps.push {bitmap, element:element.inspectedName}

     for b in @_unusedCacheBitmaps
       unusedCacheByteSize += b.byteSize
@@ -145,7 +153,7 @@
         context:            context
         tracked:            {@cacheByteSize, @unusedCacheByteSize}
         actual:             {cacheByteSize, unusedCacheByteSize}
-        cachedBitmaps:      cachedBitmaps.length
+        cacheBitmaps:      cacheBitmaps.length
         unusedCacheBitmaps: @_unusedCacheBitmaps.length

       throw new Error "bad _cacheByteSize"
@@ -157,12 +165,12 @@
     }

-  @getter "cacheByteSize unusedCacheByteSize",
+  @getter "cacheByteSize unusedCacheByteSize maxCacheByteSize bitmapsCreated",
     currentFrameNumber: -> @_currentFrameNumber
     recycleableSortedCacheBitmaps: ->
       recyclable = []
       currentFrameNumber = @_currentFrameNumber
-      @_cachedBitmaps.forEach (v, k) ->
+      @_cacheBitmaps.forEach (v, k) ->
         recyclable.push v if v.lastFrameUsed < currentFrameNumber - 1

       recyclable.sort (a, b) -> a.lastFrameUsed - b.lastFrameUsed
@@ -170,7 +178,7 @@
   # called by Element#_clearDrawCache
   doneWithCacheBitmap: (element) ->
     # @_validateCacheByteSize "doneWithCacheBitmap start"
-    if cachedBitmap = mapRemove @_cachedBitmaps, element
+    if cachedBitmap = mapRemove @_cacheBitmaps, element
       cachedBitmap.elementDoneWithCacheBitmap()
       byteSize = cachedBitmap.getByteSize()
       # console.error "doneWithCacheBitmap recycling for #{cachedBitmap.element?.inspectedName} bitmap = #{cachedBitmap.bitmap.size}"
@@ -181,7 +189,7 @@

   # called by element every time the draw-cache is used
   useDrawCache: (element) ->
-    @_cachedBitmaps.get(element)?.use @_currentFrameNumber
+    @_cacheBitmaps.get(element)?.use @_currentFrameNumber

   # called every time a new element drawCache is created
   # OUT: a clear bitmap (filled with pixel with color: #0000)
@@ -205,7 +213,7 @@
   _recycleInUseCacheBitmap: (element, size) ->
     # @_validateCacheByteSize "_recycleInUseCacheBitmap start"
     if recyclableCacheBitmap = @_findRecycleableCacheBitmap size
-      @_cachedBitmaps.set element, mapRemove @_cachedBitmaps, recyclableCacheBitmap.element
+      @_cacheBitmaps.set element, mapRemove @_cacheBitmaps, recyclableCacheBitmap.element

       # log "found recyclableCacheBitmap for #{size}, recyclableCacheBitmap.lastFrameUsed = #{recyclableCacheBitmap.lastFrameUsed}, _currentFrameNumber = #{@_currentFrameNumber}"
       getGlobalEpochCycle().logEvent "recycleUsedCacheBitmap", "recycleUsedCacheBitmap"
@@ -229,7 +237,7 @@

   _addCacheBitmap: (element, cacheBitmap) ->
     # @_validateCacheByteSize "_addCacheBitmap start"
-    @_cachedBitmaps.set element, cacheBitmap
+    @_cacheBitmaps.set element, cacheBitmap
     @_cacheByteSize += cacheBitmap.getByteSize()
     # @_validateCacheByteSize "_addCacheBitmap done"

@@ -247,7 +255,7 @@

   # used for internal validation, uncomment body to help debug
   _validateCacheByteSize: (context) ->
-    # @validateCacheByteSize context
+    @validateCacheByteSize context

   canUseBitmap: canUseBitmap = (cachedBitmap, requestedSize) ->
     if cachedBitmap?
@@ -272,8 +280,8 @@
   # or undefined
   _findRecycleableCacheBitmap: (size) ->
     currentFrameNumber = @_currentFrameNumber
-    valuesIterator = @_cachedBitmaps.values()
-    while !(vi = @_cachedBitmaps.next()).done
+    valuesIterator = @_cacheBitmaps.values()
+    while !(vi = @_cacheBitmaps.next()).done
       cachedBitmap = vi.value
       compareSize = cachedBitmap.size
       if cachedBitmap.lastFrameUsed < currentFrameNumber - 1 && canUseBitmap cachedBitmap, size
@@ -292,7 +300,7 @@
     byteSize = byteSizeFromSize size
     reduceToAtLeast = @_maxCacheByteSize - size
     for cachedBitmap in @recycleableSortedCacheBitmaps
-      if cachedBitmap = mapRemove @_cachedBitmaps, cachedBitmap.element
+      if cachedBitmap = mapRemove @_cacheBitmaps, cachedBitmap.element
         cachedBitmap.elementDoneWithCacheBitmap()
         @_cacheByteSize -= cachedBitmap.getByteSize()
         break if @_cacheByteSize <= reduceToAtLeast


local_hub: clean up (=fix bugs) locking and restart code related to console server

--- salvus/local_hub.coffee
+++ salvus/local_hub.coffee
@@ -186,9 +186,29 @@
         delete ports[type]

 # try to restart the console server and get port where it is listening
+CONSOLE_SERVER_MAX_STARTUP_TIME_S = 10   # 10 seconds
+
+_restarting_console_server = false
+_restarted_console_server  = 0   # time when we last restarted it
 restart_console_server = (cb) ->   # cb(err)
+    dbg = (m) -> winston.debug("restart_console_server: #{misc.to_json(m)}")
+
+    if _restarting_console_server
+        dbg("hit lock -- already restarting console server")
+        cb("already restarting console server")
+        return
+
+    t = new Date() - _restarted_console_server
+    if t <= CONSOLE_SERVER_MAX_STARTUP_TIME_S*1000
+        err = "restarted console server #{t}ms ago -- still waiting for it to start"
+        dbg(err)
+        cb(err)
+        return
+
+    _restarting_console_server = true
+    dbg("restarting the daemon")
+
     port_file = abspath("#{DATA}/console_server.port")
-    dbg = (m) -> winston.debug("restart_console_server: #{m}")
     port = undefined
     async.series([
         (cb) ->
@@ -205,10 +225,7 @@
                 cb          : cb
         (cb) ->
             dbg("wait a little to see if #{port_file} appears, and if so read it and return port")
-            t = misc.walltime()
             f = (cb) ->
-                if misc.walltime() - t > 5  # give up
-                    cb(); return
                 fs.exists port_file, (exists) ->
                     if not exists
                         cb(true)
@@ -223,9 +240,15 @@
                                 catch error
                                     cb('reading port corrupt')
             misc.retry_until_success
-                f  : f
-                cb : cb
+                f        : f
+                max_time : 7000
+                cb       : cb
     ], (err) =>
+        _restarting_console_server = false
+        _restarted_console_server = new Date()
+        dbg("finished trying to restart console_server")
+        if err
+            dbg("ERROR: #{err}")
         cb(err, port)
     )

@@ -273,106 +296,129 @@
                 else
                     @_new_session(client_socket, mesg, port, session?.history)

-    _new_session: (client_socket, mesg, port, history, cnt) =>
-        if not cnt?
-            cnt = 0
+    _get_console_server_socket: (port, cb) =>
+        socket = undefined
+        f = (cb) =>
+            misc_node.connect_to_locked_socket
+                port  : port
+                token : secret_token
+                cb    : (err, _socket) =>
+                    if err
+                        cb(err)
+                    else
+                        socket = _socket
+                        cb()
+        async.series([
+            (cb) =>
+                misc.retry_until_success
+                    f        : f
+                    max_time : 5000
+                    cb       : (err) =>
+                        cb()  # ignore err on purpose -- no err sets socket
+            (cb) =>
+                if socket?
+                    cb(); return
+                forget_port('console')
+                restart_console_server (err, _port) =>
+                    if err
+                        cb(err)
+                    else
+                        port = _port
+                        cb()
+            (cb) =>
+                if socket?
+                    cb(); return
+                misc.retry_until_success
+                    f        : f
+                    max_time : 5000
+                    cb        : cb
+        ], (err) =>
+            cb(undefined, socket)
+        )
+
+    _new_session: (client_socket, mesg, port, history) =>
         winston.debug("_new_session: defined by #{json(mesg)}")
         # Connect to port CONSOLE_PORT, send mesg, then hook sockets together.
-        misc_node.connect_to_locked_socket
-            port  : port
-            token : secret_token
-            cb : (err, console_socket) =>
-                if err
-                    winston.debug("_new_session - error connecting to locked console socket -- #{err}")
-                    forget_port('console')
-                    if cnt >= 3
-                        # too many tries -- give up
-                        client_socket.write_mesg('json', message.error(id:mesg.id, error:"local_hub -- TOO MANY problems connecting to console server."))
-                        winston.debug("_new_session: console server denied connection too many times")
-                        return
-                    winston.debug("_new_session -- not too many (=#{cnt}) tries -- try to restart console server and try again.")
-                    restart_console_server (err, port) =>
-                        if err or not port?
-                            # even restarting console server failed
-                            client_socket.write_mesg('json', message.error(id:mesg.id, error:"local_hub -- Problem connecting to console server."))
-                            winston.debug("_new_session: console server denied connection")
-                        else
-                            @_new_session(client_socket, mesg, port, history, cnt+1)
-                        return
-                    return
-                # Request a Console session from console_server
-                misc_node.enable_mesg(console_socket)
-                console_socket.write_mesg('json', mesg)
-                # Read one JSON message back, which describes the session
-                console_socket.once 'mesg', (type, desc) =>
-                    if not history?
-                        history = new Buffer(0)
-                    client_socket.write_mesg('json', {desc:desc, history:history.toString()})  # in future, history could be read from a file
-                    # Disable JSON mesg protocol, since it isn't used further
-                    misc_node.disable_mesg(console_socket)
-                    misc_node.disable_mesg(client_socket)
-
-                    session =
-                        socket  : console_socket
-                        desc    : desc,
-                        status  : 'running',
-                        clients : [client_socket],
-                        history : history
-                        session_uuid : mesg.session_uuid
-                        project_id   : mesg.project_id
-
-                    # Connect the sockets together.
-
-                    # receive data from the user (typing at their keyboard)
-                    client_socket.on 'data', (data) ->
-                        activity()
-                        if not console_socket.writable
-                            winston.debug("WARNING: console client socket not writable")
-                        else
-                            console_socket.write(data)
-
-                    session.amount_of_data = 0
-                    session.last_data = misc.mswalltime()
-
-                    # receive data from the pty, which we push out to the user (via global hub)
-                    console_socket.on 'data', (data) ->
-
-                        # every 2 ms we reset the burst data watcher.
-                        tm = misc.mswalltime()
-                        if tm - session.last_data >= 2
-                            session.amount_of_data = 0
-                        session.last_data = tm
-
-                        if session.amount_of_data > 200000
-                            # We just got more than 200000 characters of output in <= 2 ms, so truncate it.
-                            # I had a control-c here, but it was EVIL (and useless), so do *not* enable this.
-                            #      console_socket.write(String.fromCharCode(3))
-                            # client_socket.write('[...]')
-                            data = '[...]'
-
-                        session.history += data
-                        session.amount_of_data += data.length
-                        n = session.history.length
-                        if n > 200000
-                            session.history = session.history.slice(session.history.length - 100000)
-
-                        # Never push more than 20000 characters at once to client hub, since that could overwhelm...
-                        if data.length > 20000
-                            data = "[...]"+data.slice(data.length-20000)
-
-                        if not console_socket.writable
-                            winston.debug("WARNING: console client socket not writable")
-                        else
-                            client_socket.write(data)
-
-                    @_sessions[mesg.session_uuid] = session
-
-                console_socket.on 'end', () =>
-                    winston.debug("console session #{mesg.session_uuid} ended")
-                    session = @_sessions[mesg.session_uuid]
-                    if session?
-                        session.status = 'done'
-                    client_socket.end()
+        @_get_console_server_socket port, (err, console_socket) =>
+            if err
+                m = "_new_session: console server failed to connect -- #{err}"
+                winston.debug(m)
+                client_socket.write_mesg('json', message.error(id:mesg.id, error:m))
+                return
+            # Request a Console session from console_server
+            misc_node.enable_mesg(console_socket)
+            console_socket.write_mesg('json', mesg)
+            # Read one JSON message back, which describes the session
+            console_socket.once 'mesg', (type, desc) =>
+                if not history?
+                    history = new Buffer(0)
+                # in future, history could be read from a file
+                client_socket.write_mesg('json', {desc:desc, history:history.toString()})
+                # Disable JSON mesg protocol, since it isn't used further
+                misc_node.disable_mesg(console_socket)
+                misc_node.disable_mesg(client_socket)
+
+                session =
+                    socket  : console_socket
+                    desc    : desc,
+                    status  : 'running',
+                    clients : [client_socket],
+                    history : history
+                    session_uuid : mesg.session_uuid
+                    project_id   : mesg.project_id
+
+                # Connect the sockets together.
+
+                # receive data from the user (typing at their keyboard)
+                client_socket.on 'data', (data) ->
+                    activity()
+                    if not console_socket.writable
+                        winston.debug("WARNING: console_socket not writable")
+                    else
+                        console_socket.write(data)
+
+                session.amount_of_data = 0
+                session.last_data = misc.mswalltime()
+
+                # receive data from the pty, which we push out to the user (via global hub)
+                console_socket.on 'data', (data) ->
+
+                    # every 2 ms we reset the burst data watcher.
+                    tm = misc.mswalltime()
+                    if tm - session.last_data >= 2
+                        session.amount_of_data = 0
+                    session.last_data = tm
+
+                    if session.amount_of_data > 200000
+                        # We just got more than 200000 characters of output in <= 2 ms, so truncate it.
+                        # I had a control-c here, but it was EVIL (and useless), so do *not* enable this.
+                        #      console_socket.write(String.fromCharCode(3))
+                        # client_socket.write('[...]')
+                        data = '[...]'
+
+                    session.history += data
+                    session.amount_of_data += data.length
+                    n = session.history.length
+                    if n > 200000
+                        session.history = session.history.slice(session.history.length - 100000)
+
+                    # Never push more than 20000 characters at once to client hub, since that could overwhelm...
+                    if data.length > 20000
+                        data = "[...]"+data.slice(data.length-20000)
+
+                    if not client_socket.writable
+                        winston.debug("WARNING: client_socket not writable")
+                    else
+                        client_socket.write(data)
+
+                @_sessions[mesg.session_uuid] = session
+
+            console_socket.on 'end', () =>
+                winston.debug("console session #{mesg.session_uuid} ended")
+                session = @_sessions[mesg.session_uuid]
+                if session?
+                    session.status = 'done'
+                client_socket.end()

     # Return object that describes status of all Console sessions
     info: (project_id) =>
@@ -408,11 +454,9 @@
         return
     t = new Date() - _restarted_sage_server
     if t <= SAGE_SERVER_MAX_STARTUP_TIME_S*1000
-        f = () ->
-            err = "restarted sage server #{t}ms ago -- still waiting for it to start"
-            dbg(err)
-            cb(err)
-        setTimeout(cb, 3000)
+        err = "restarted sage server #{t}ms ago -- still waiting for it to start"
+        dbg(err)
+        cb(err)
         return

     _restarting_sage_server = true


Fix frame-focus detection.

Fix frame-focus detection.

In b05276ed8264e5a71f20a7068690ba2a414ee6d8, I inadvertently moved the
focus handler from window to document.  As a consequence, detectFocus
(now registerFocus) wasn't being called when expected.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -209,7 +209,7 @@
     for type in [ "keydown", "keypress", "keyup", "click", "focus", "blur", "mousedown" ]
       do (type) -> installListener window, type, (event) -> handlerStack.bubbleEvent type, event
     installListener document, "DOMActivate", (event) -> handlerStack.bubbleEvent 'DOMActivate', event
-    installListener document, "focus", detectFocus
+    installListener window, "focus", registerFocus
     installedListeners = true
     FindModeHistory.init()

@@ -229,7 +229,7 @@
 #
 # The backend needs to know which frame has focus.
 #
-detectFocus = ->
+registerFocus = ->
   # settings may have changed since the frame last had focus
   settings.load()
   chrome.runtime.sendMessage({ handler: "frameFocused", frameId: frameId })


Fix edit blocker css when scrolling

Fix edit blocker css when scrolling

This change keeps the blocker message and background blocker from scrolling.

--- app/assets/javascripts/check-author-idle.coffee
+++ app/assets/javascripts/check-author-idle.coffee
@@ -13,8 +13,8 @@

     blockerHTML = $ [
       '
',
-        '

',
-        '
',
+        '

',
+        '
',
           'Your login session has timed out.  Reload this page to log back in and continue editing.'
         '
',
       '
'


select-up on long lines for input history

--- app/assets/javascripts/console-ish-input.coffee
+++ app/assets/javascripts/console-ish-input.coffee
@@ -3,7 +3,7 @@
 # by default slack won't go to previous commands with 'up', just to the front
 # of the line. Fix that if there's no newlines in the message.
 input.bind "keydown", (e) ->
-  return if e.altKey || e.ctrlKey
+  return if e.altKey || e.ctrlKey || e.shiftKey
   return if input.val().indexOf("\n") != -1

   if (e.which == TS.utility.keymap.up && input.getCursorPosition() >= 1 && !e.movedHistory)


Adjusted zoom level when only displaying a single marker.

--- app/assets/javascripts/gmaps4histvest.js.coffee
+++ app/assets/javascripts/gmaps4histvest.js.coffee
@@ -40,12 +40,15 @@
         related_locations = []
         for marker in @markers
             related_locations.push marker if marker.belongs_to_current_topic
-        if related_locations.length > 0
+        console.log related_locations
+        if related_locations.length > 1
             @extend_bounds_with_objects(related_locations)
+            @set_center(@bounds_object.getCenter())
+            @my_map.fitBounds(@bounds_object)
         else
-            @extend_bounds_with_objects(@markers)
-        @set_center(@bounds_object.getCenter())
-        @my_map.fitBounds(@bounds_object)
+            @extend_bounds_with_objects(related_locations)
+            @set_center(@bounds_object.getCenter())
+            @set_zoom(16)

     bind_fancybox: ->
         _this = this;


cleanup

cleanup

--- app/assets/javascripts/lib/nypl_progress.coffee
+++ app/assets/javascripts/lib/nypl_progress.coffee
@@ -113,17 +113,8 @@
     )

   createLayerToggle: (layer) ->
-    console.log @layer_id, layer.id
     selected = if (@layer_id==layer.id) then 'selected=\"selected\"' else ''
     "<option id=\"layer_toggle_#{layer.id}\" #{selected} value=\"#{layer.id}\" data-bbox=\"#{layer.bbox}\" data-tileset=\"#{layer.tilejson}\">#{layer.name}, #{layer.year}</option>"
-
-  activateLayerToggle: (layer_id) ->
-    p = @
-    $("#layer_toggle_#{layer_id}").on("click", (e) ->
-      # console.log "click:", e.layer
-      e.preventDefault()
-      p.toggleLayer(e)
-    )

   toggleLayer: (id) ->
     @map.removeLayer(@markers) if @markers


Fixes ui tab paging links. Implements remote links and remote modal link handler.

--- app/assets/javascripts/voluntary/base.js.coffee
+++ app/assets/javascripts/voluntary/base.js.coffee
@@ -26,7 +26,9 @@
     event.preventDefault()

     $.get($(this).attr('href'), (data) ->
-      $($('.ui-tabs-panel[style*="display: block"]')[0]).html(data)
+      panel = $('.ui-tabs-panel[style*="display: block"]')[0]
+      panel = $('.ui-tabs-panel')[0] unless panel
+      $(panel).html(data)
     ).fail (jqXHR, textStatus, errorThrown) ->
       json = null

@@ -67,4 +69,26 @@
     });

   $( ".datepicker" ).each (k, v) ->
-    $(v).datepicker({ dateFormat: "yy-mm-dd", changeYear: true, yearRange: "c-100:c+10" });+    $(v).datepicker({ dateFormat: "yy-mm-dd", changeYear: true, yearRange: "c-100:c+10" });
+
+  $(document.body).on "click", ".remote_links", (event) ->
+    $this = $(this)
+
+    $.ajax(url: $this.attr('href'), type: "GET", dataType: "html").success (data) ->
+      $($this.data("replace")).html(data)
+
+    event.preventDefault()
+
+  $(document.body).on "click", ".remote_modal_link", (event) ->
+    $this = $(this)
+
+    $.ajax(url: $this.attr('href'), type: "GET", dataType: "html").success (data) ->
+      $('#bootstrap_modal').html(data)
+      $('#bootstrap_modal').modal('show')
+
+    event.preventDefault()
+
+  $(document.body).on "click", "#close_bootstrap_modal_button", (event) ->
+    $('#bootstrap_modal').modal('hide')
+    event.preventDefault()
+


Disable HTML5 routes for now.

Disable HTML5 routes for now.


Corrected file saving on create

Corrected file saving on create

--- server/controllers/shopmanagement/deals.coffee
+++ server/controllers/shopmanagement/deals.coffee
@@ -31,7 +31,7 @@
 exports.create = (req, res) ->
   shopId = req.session.currentShop.shopId
   dealId = req.params.dealId
-
+
   DealCategory.find().sort('name').exec( (err, categories ) ->
     res.render 'pages/shopmanagement/deals/create',
       {
@@ -54,11 +54,16 @@
   format = type.split("/")
   if format[1] is "jpg" or format[1] is "jpeg" or format[1] is "png" or format[1] is "gif"
     fs.rename req.files.image.path, path, (err) ->
-
+
       Shop.findOne({_id: shopId}).exec (err, shop) ->

         if shop
-          splittednewname = (file.path).split("/")
+
+          if file.path.indexOf('/') > -1
+            splittednewname = (file.path).split('/')
+          else
+            splittednewname = (file.path).split('\\')
+
           image = "/upload/" + splittednewname[splittednewname.length-1]
           deal = new Deal
             shop: shop
@@ -80,7 +85,7 @@
             else
               console.log err
               res.redirect 'shopmanagement/deals/list'
-
+
    else
     fs.unlink file.path
     res.render "shopmanagement/deals/edit",
@@ -89,9 +94,9 @@
 exports.edit = (req, res) ->
   shopId = req.session.currentShop.shopId
   dealId = req.params.dealId
-
+
   Deal.findOne({_id: req.params.dealId}).exec( (err, deal) ->
-
+
     DealCategory.find().sort('name').exec( (err, categories ) ->
       res.render 'pages/shopmanagement/deals/create',
         {
@@ -104,7 +109,7 @@
         }
       )
   )
-
+
 exports.editDo = (req, res) ->
   shopId = req.session.currentShop.shopId
   dealId = req.params.dealId
@@ -116,7 +121,7 @@

   if format[1] is "jpg" or format[1] is "jpeg" or format[1] is "png" or format[1] is "gif"
     fs.rename req.files.image.path, path, (err) ->
-
+

       Deal.findOne({_id: req.body.id}).exec (err, deal) ->

@@ -151,12 +156,12 @@
               else
                 console.log err
                 res.redirect 'shopmanagement/deals/list'
-
+
   else
     fs.unlink file.path
-    res.render "shopmanagement/deals/edit",
+    res.render "page/shopmanagement/deals/edit",
     {
-      errorMsg: 'El formato debe ser jpg, png o gif',
+      errorType: 1,
       deal: dealId,
       currentShop: req.session.currentShop
     }
@@ -164,9 +169,9 @@
 exports.delete = (req, res) ->
   shopId = req.session.currentShop.shopId
   dealId = req.params.dealId
-
+
   Deal.findOne({_id: req.params.dealId}).exec (err, deal) ->
-
+
     res.render 'pages/shopmanagement/deals/delete',
       {
         deal: deal,


added --node flag for passing options through directly to node

--- command.coffee
+++ command.coffee
@@ -27,6 +27,7 @@

 # The list of all the valid option flags that `coffee` knows how to handle.
 SWITCHES = [
+  [      '--node [ARGS]',     'pass arguments through to the node binary']
   ['-c', '--compile',         'compile to JavaScript and save as .js files']
   ['-i', '--interactive',     'run an interactive CoffeeScript REPL']
   ['-o', '--output [DIR]',    'set the directory for compiled JavaScript']
@@ -60,12 +61,13 @@
 # `--` will be passed verbatim to your script as arguments in `process.argv`
 exports.run = ->
   parseOptions()
-  return usage()                              if opts.help
-  return version()                            if opts.version
-  return require './repl'                     if opts.interactive
-  return compileStdio()                       if opts.stdio
-  return compileScript null, sources[0]       if opts.eval
-  return require './repl'                     unless sources.length
+  return execl()                         if opts.node
+  return usage()                         if opts.help
+  return version()                       if opts.version
+  return require './repl'                if opts.interactive
+  return compileStdio()                  if opts.stdio
+  return compileScript null, sources[0]  if opts.eval
+  return require './repl'                unless sources.length
   separator = sources.indexOf '--'
   flags = []
   if separator >= 0
@@ -192,6 +194,28 @@
 # The compile-time options to pass to the CoffeeScript compiler.
 compileOptions = (fileName) -> {fileName, bare: opts.bare or opts['no-wrap']}

+# Start up a new node instance with the arguments in opts.node passed to node,
+# preserving the other options
+execl = ->
+  nodeArgs = opts.node.split /\s+/
+  files = opts.arguments
+  flags = []
+  args = []
+  if (idx = files.indexOf '--') > -1
+    flags = ['--', files[(idx+1)..]...]
+    files[idx..] = []
+  for name, value of opts
+    # the three magic values listed below are the long-name of the argument that causes
+    # this function to be invoked, a generated option, and the non-option arguments
+    continue if !value or name in ['node','run','arguments']
+    args.push '--' + name
+    args.push value unless value is true
+  spawn process.execPath,
+    [nodeArgs..., process.argv[1], args..., files..., flags...]
+    cwd: process.cwd()
+    env: process.env
+    customFds: [0, 1, 2]
+
 # Print the `--help` usage message and exit. Deprecated switches are not
 # shown.
 usage = ->


use author attribution to enforce cursor location

--- src/modules/cursor.coffee
+++ src/modules/cursor.coffee
@@ -3,8 +3,8 @@

 _applyDelta = (delta) ->
   _.defer( =>
-    delta.apply((index, text) =>
-      this.shiftCursors(index, text.length)
+    delta.apply((index, text, formatting) =>
+      this.shiftCursors(index, text.length, formatting['author'])
     , (index, length) =>
       this.shiftCursors(index, -1 * length)
     , (index, length, name, value) =>
@@ -104,16 +104,14 @@
       _applyDelta.call(this, delta)
     )

-  shiftCursors: (index, length) ->
+  shiftCursors: (index, length, authorId) ->
     _.each(@cursors, (cursor, id) =>
-      return unless cursor?
-      if (cursor.index >= index)
-        if length > 0
-          _setCursor.call(this, cursor.userId, cursor.index + length, cursor.name, cursor.color)   # Insert
-        else
-          # Delete needs to handle special case: start|cursor|end vs normal case: start|end|cursor
-          _setCursor.call(this, cursor.userId, cursor.index + Math.max(length, index - cursor.index), cursor.name, cursor.color)
+      if cursor and cursor.index > index
+        # Needs to handle special case: start|cursor|end vs normal case: start|end|cursor
+        _setCursor.call(this, cursor.userId, cursor.index + Math.max(length, index - cursor.index), cursor.name, cursor.color)
     )
+    cursor = @cursors[authorId]
+    _setCursor.call(this, cursor.userId, index + length, cursor.name, cursor.color) if cursor?

   setCursor: (userId, index, name, color) ->
     cursor = _setCursor.call(this, userId, index, name, color)


nicer tests for `hifo.lowest` and `hifo.highest`

--- test/index.coffee
+++ test/index.coffee
@@ -2,6 +2,8 @@
 assert =	require 'assert'

 hifo =		require '../src/index.js'
+
+x = Math.random()

@@ -13,26 +15,52 @@

 	describe 'lowest', () ->

-		it 'should sort numeric values correctly', () ->
-			sort = hifo.lowest()
-			assert 0 > sort 4, 5
-			assert 0 < sort -1, -2
+		context '0-ary sort', ->

-		it 'should sort object values correctly', () ->
-			sort = hifo.lowest 'test'
-			assert 0 > sort {test: 4}, {test: 5}
-			assert 0 < sort {test: -1}, {test: -2}
+			it 'should sort numeric values correctly', () ->
+				sort = hifo.lowest()
+				assert 0 > sort  4,  5
+				assert 0 < sort -1, -2
+
+		context '1-ary sort', ->
+
+			it 'should sort object values correctly', () ->
+				sort = hifo.lowest 'test'
+				assert 0 > sort {test:  4}, {test:  5}
+				assert 0 < sort {test: -1}, {test: -2}
+
+		context '2-ary sort', ->
+
+			it 'should sort object values correctly', () ->
+				sort = hifo.lowest 'foo', 'bar'
+				assert 0 > sort {foo:  4},          {foo:  5}
+				assert 0 > sort {foo:  1, bar:  4}, {foo:  1, bar:  5}
+				assert 0 < sort {foo: -1},          {foo: -2}
+				assert 0 < sort {foo:  1, bar: -1}, {foo:  1, bar: -2}

 	describe 'highest', () ->

-		it 'should sort numeric values correctly', () ->
-			sort = hifo.highest()
-			assert 0 < sort 4, 5
-			assert 0 > sort -1, -2
+		context '0-ary sort', ->

-		it 'should sort object values correctly', () ->
-			sort = hifo.highest 'test'
-			assert 0 < sort {test: 4}, {test: 5}
-			assert 0 > sort {test: -1}, {test: -2}
+			it 'should sort numeric values correctly', () ->
+				sort = hifo.highest()
+				assert 0 < sort  4,  5
+				assert 0 > sort -1, -2
+
+		context '1-ary sort', ->
+
+			it 'should sort object values correctly', () ->
+				sort = hifo.highest 'test'
+				assert 0 < sort {test:  4}, {test:  5}
+				assert 0 > sort {test: -1}, {test: -2}
+
+		context '2-ary sort', ->
+
+			it 'should sort object values correctly', () ->
+				sort = hifo.highest 'foo', 'bar'
+				assert 0 < sort {foo:  4},          {foo:  5}
+				assert 0 < sort {foo:  1, bar:  4}, {foo:  1, bar:  5}
+				assert 0 > sort {foo: -1},          {foo: -2}
+				assert 0 > sort {foo:  1, bar: -1}, {foo:  1, bar: -2}


nginx: choose event mechanism based on operating system

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -144,6 +144,11 @@
 module.exports.create = (KONFIG, environment)->
   workers = KONFIG.workers

+  event_mechanism = switch process.platform
+    when 'darwin' then 'use kqueue;'
+    when 'linux'  then 'use epoll;'
+    else ''
+
   config = """
   worker_processes #{if environment is "dev" then 1 else 16};
   master_process #{if environment is "dev" then "off" else "on"};
@@ -158,8 +163,7 @@
   events {
     worker_connections  1024;
     multi_accept on;
-    # epoll is only valid for linux environments
-    use #{if environment is 'dev' then 'kqueue' else 'epoll'};
+    #{event_mechanism}
   }

   # start http


Get closer to passing the workflow spec

Get closer to passing the workflow spec

--- test/mocha.coffee
+++ test/mocha.coffee
@@ -23,10 +23,3 @@
 # override default to be "lockdown" instead of "dryrun", otherwise we run into rate limits pretty soon
 # run "NOCK_BACK_MODE=record mocha path/to/your/test.coffee" manually to record API responses
 global.nock.back.setMode(process.env.NOCK_BACK_MODE or "lockdown")
-
-#global.mockKnex = require "mock-knex"
-#global.mockKnex.setAdapter "knex@0.8"
-#global.mockKnex.mock global.knex
-#
-#global.knexTracker = global.mockKnex.getTracker()
-#global.knexTracker.install()


chore(test): unset MONGO_URI env var

chore(test): unset MONGO_URI env var

--- test/suite.coffee
+++ test/suite.coffee
@@ -10,6 +10,10 @@
 data.områder  = require './data/områder.json'
 data.steder   = require './data/steder.json'
 data.turer    = require './data/turer.json'
+
+# Unset environment variable MONGO_URI to use test values for DB connection
+
+process.env.MONGO_URI = undefined

 # For some reason NodeJS or Mocha caches the object array but still tries to run
 # the Object to ObjectID convertion. This results in new ObjectIDs for every run


chore: log error also

chore: log error also

--- app/config/app.coffee
+++ app/config/app.coffee
@@ -11,6 +11,7 @@

   # From http://git.io/HPjymw
   Ember.RSVP.configure 'onerror', (e) ->
+    Ember.Logger.error(e)
     Ember.Logger.error(e.message)
     Ember.Logger.error(e.stack)


Remove store mixin

Remove store mixin

--- app/data/store.coffee
+++ app/data/store.coffee
@@ -10,54 +10,10 @@

   constructor: (options = {}) ->
     @items = {}
-    @mixInto = @_generateMixIntoMethod()

     super

     dispatcher.register this
-
-  _generateMixIntoMethod: ->
-    # Here's a shortcut mixin to keep React components state up to date with stores' properties.
-    # To indicate that the store's `foo` should be passed as the component's state's `bar`:
-    # React.createClass({
-    #   mixins: [store.mixInto({foo: bar})]
-    # });
-
-    store = this
-
-    (stateProperties) ->
-      changeCallbackName = "updateStateWithStore_#{Math.random().toString().split('.')[1]}"
-
-      getCurrentState = ->
-        if typeof stateProperties is 'function'
-          state = stateProperties.call this
-
-        else
-          state = {}
-
-          if typeof stateProperties is 'string'
-            state[stateProperties] = store
-
-          else
-            for storeProperty, stateProperty of stateProperties
-              state[stateProperty] = store[storeProperty]
-
-        state
-
-      Mixin =
-        getInitialState: ->
-          getCurrentState.call this
-
-        componentWillMount: ->
-          store.listen @[changeCallbackName]
-
-        componentWillUnmount: ->
-          store.stopListening @[changeCallbackName]
-
-      Mixin[changeCallbackName] = ->
-        @setState getCurrentState.call this
-
-      Mixin

   create: (instance) ->
     if @type?


Dagre - conditionally deconstructs Cytoscape.js graph

--- app/coffee/modules/lib/views/dagre/index.coffee
+++ app/coffee/modules/lib/views/dagre/index.coffee
@@ -48,7 +48,7 @@
   # onBeforeDestroy
   # Destroyes the Cytoscape.js graph
   onBeforeDestroy: ->
-    return @cy.destroy()
+    return @cy?.destroy()

 # # # # #


Draw minAxis debug from center of body A

--- app/js/physics.coffee
+++ app/js/physics.coffee
@@ -119,7 +119,7 @@

   drawDebug: (display) ->
     if minAxis = @debug.minAxis
-      Utils.debugLine display, [0,0], minAxis, "#F6F"
+      Utils.debugLine display, minAxis.from, minAxis.to, "#F6F"
     if ref = @debug.reference
       Utils.debugLine display, ref.from, ref.to, "#F66"
     if inc = @debug.incident
@@ -269,7 +269,8 @@
     if Vec.dotProduct(dir, minAxis) < 0
       minAxis = Vec.invert minAxis # separation axis is always A->B

-    @debug.minAxis = minAxis
+    @debug.minAxis = from: @position, to: Vec.add @position, minAxis
+    minAxis

   projectionInterval: (axis) ->
     Utils.projectionInterval @vertices(), axis


Helpful help menu, with links to issues etc.

--- app/metal/menu.coffee
+++ app/metal/menu.coffee
@@ -7,27 +7,23 @@

 refresh_menu = ->
   mac = (process.platform == "darwin")
+  repo_url = "https://github.com/itchio/itchio-app"
+  open_url = (url) ->
+    require("shell").openExternal url

   menus = {
     file: {
       label: "File"
       submenu: [
         {
-          label: "itch.io version #{require("app").getVersion()}"
-          click: ->
-            require("shell").openExternal "https://github.com/itchio/itchio-app"
-        }
-        {
           label: "Close Window"
-          accelerator: if mac then "Command+W" else "Alt+F4"
-          click: ->
-            require("app").main_window?.hide()
+          accelerator: "CmdOrCtrl+W"
+          click: -> require("app").main_window?.hide()
         }
         {
           label: "Quit"
-          accelerator: if mac then "Command+Q" else "Ctrl+Q"
-          click: ->
-            AppActions.quit()
+          accelerator: "CmdOrCtrl+Q"
+          click: -> AppActions.quit()
         }
       ]
     }
@@ -37,8 +33,47 @@
       submenu: [
         {
           label: "Log out"
-          click: ->
-            AppActions.logout()
+          click: -> AppActions.logout()
+        }
+      ]
+    }
+
+    help: {
+      label: "Help"
+      submenu: [
+        {
+          label: "View itch.io Terms"
+          click: -> open_url "https://itch.io/docs/legal/terms"
+        }
+        {
+          label: "View License"
+          click: -> open_url "#{repo_url}/blob/master/LICENSE"
+        }
+        {
+          label: "Version #{require("app").getVersion()}"
+          enabled: false
+        }
+        {
+          label: "Check for Update"
+          click: -> console.log "check for update: stub"
+        }
+        {
+          type: "separator"
+        }
+        {
+          label: "Report Issue"
+          click: -> open_url "#{repo_url}/issues/new"
+        }
+        {
+          label: "Search Issue"
+          click: -> open_url "#{repo_url}/search?type=Issues"
+        }
+        {
+          type: "separator"
+        }
+        {
+          label: "Release Notes"
+          click: -> open_url "#{repo_url}/releases"
         }
       ]
     }
@@ -48,6 +83,8 @@

   if AppStore.get_current_user()
     template.push menus.account
+
+  template.push menus.help

   # gotcha: buildFromTemplate mutates its argument - calling it
   # twice with the same argument throws 'Invalid menu template'


arrivals: Add pics to open PRs if it exist

This basically allows people to post a new greyhound and then add extra
pics to the pull request before it is merged.

--- scripts/arrivals.coffee
+++ scripts/arrivals.coffee
@@ -85,6 +85,13 @@
           user: gitUser
         res.reply "Adding new pic for #{capitalize(greyhound)}! 🖼\n" +
                   "Hang on a sec..."
-        git.update addPic, greyhound, picUrl, gitOpts, (update) ->
-          res.reply update
+        git.pullrequest gitOpts.branch, (pr, err) ->
+          if err?
+            return res.reply err
+          if pr?
+            git.append addPic, greyhound, picUrl, gitOpts, (append) ->
+              res.reply append
+          else
+            git.update addPic, greyhound, picUrl, gitOpts, (update) ->
+              res.reply update
   )


deneme

deneme

--- scripts/filtered.coffee
+++ scripts/filtered.coffee
@@ -1,4 +1,4 @@
 module.exports = (robot) ->

    robot.hear /cmon/i, (res) ->
-     res.send "Goals? cmon? #{res.message.text}? WE DON'T NEED NO STINKIN BADGERS"+     res.send "Goals? cmon? {res.message.text}? WE DON'T NEED NO STINKIN BADGERS"


Remove unused variable

Remove unused variable

--- spec/background-tips-spec.coffee
+++ spec/background-tips-spec.coffee
@@ -6,7 +6,7 @@
 # or `fdescribe`). Remove the `f` to unfocus the block.

 describe "BackgroundTips", ->
-  [workspaceElement, backgroundTips, backgroundTipsView] = []
+  [workspaceElement, backgroundTipsView] = []

   BackgroundTipsView::DisplayDuration = 5
   BackgroundTipsView::FadeDuration = 1


:lipstick: specs

:lipstick: specs

--- spec/underscore-plus-spec.coffee
+++ spec/underscore-plus-spec.coffee
@@ -1,7 +1,7 @@
 _ = require '../src/underscore-plus'

 describe "underscore extensions", ->
-  describe "_.adviseBefore", ->
+  describe "::adviseBefore(object, methodName, advice)", ->
     [object, calls] = []

     beforeEach ->
@@ -21,7 +21,7 @@
       object.method(1, 2, 3)
       expect(calls).toEqual []

-  describe "_.endsWith", ->
+  describe "::endsWith(string, ending)", ->
     it "returns whether the given string ends with the given suffix", ->
       expect(_.endsWith("test.txt", ".txt")).toBeTruthy()
       expect(_.endsWith("test.txt", "txt")).toBeTruthy()
@@ -31,7 +31,7 @@
       expect(_.endsWith("test.txt", ".tx")).toBeFalsy()
       expect(_.endsWith("test.txt", "test")).toBeFalsy()

-  describe "_.camelize(string)", ->
+  describe "::camelize(string)", ->
     it "converts `string` to camel case", ->
       expect(_.camelize("corey_dale_johnson")).toBe "coreyDaleJohnson"
       expect(_.camelize("corey-dale-johnson")).toBe "coreyDaleJohnson"
@@ -39,14 +39,14 @@
       expect(_.camelize("coreyDaleJohnson")).toBe "coreyDaleJohnson"
       expect(_.camelize("CoreyDaleJohnson")).toBe "CoreyDaleJohnson"

-  describe "_.dasherize(string)", ->
+  describe "::dasherize(string)", ->
     it "converts `string` to use dashes", ->
       expect(_.dasherize("corey_dale_johnson")).toBe "corey-dale-johnson"
       expect(_.dasherize("coreyDaleJohnson")).toBe "corey-dale-johnson"
       expect(_.dasherize("CoreyDaleJohnson")).toBe "corey-dale-johnson"
       expect(_.dasherize("corey-dale-johnson")).toBe "corey-dale-johnson"

-  describe "_.underscore(string)", ->
+  describe "::underscore(string)", ->
     it "converts `string` to use underscores", ->
       expect(_.underscore('')).toBe ''
       expect(_.underscore(null)).toBe ''
@@ -64,7 +64,7 @@
       expect(_.underscore("CoreyDaleJohnson")).toBe "corey_dale_johnson"
       expect(_.underscore("corey_dale_johnson")).toBe "corey_dale_johnson"

-  describe "spliceWithArray(originalArray, start, length, insertedArray, chunkSize)", ->
+  describe "::spliceWithArray(originalArray, start, length, insertedArray, chunkSize)", ->
     describe "when the inserted array is smaller than the chunk size", ->
       it "splices the array in place", ->
         array = ['a', 'b', 'c']
@@ -77,7 +77,7 @@
         _.spliceWithArray(array, 1, 1, ['v', 'w', 'x', 'y', 'z'], 2)
         expect(array).toEqual ['a', 'v', 'w', 'x', 'y', 'z', 'c']

-  describe "_.humanizeEventName(eventName)", ->
+  describe "::humanizeEventName(eventName)", ->
     describe "when no namespace exists", ->
       it "undasherizes and capitalizes the event name", ->
         expect(_.humanizeEventName('nonamespace')).toBe 'Nonamespace'
@@ -88,7 +88,7 @@
         expect(_.humanizeEventName('space:final-frontier')).toBe 'Space: Final Frontier'
         expect(_.humanizeEventName('star-trek:the-next-generation')).toBe 'Star Trek: The Next Generation'

-  describe "_.humanizeKeystroke(keystroke)", ->
+  describe "::humanizeKeystroke(keystroke)", ->
     it "replaces single keystroke", ->
       expect(_.humanizeKeystroke('cmd-O')).toEqual '⌘⇧O'
       expect(_.humanizeKeystroke('cmd-shift-up')).toEqual '⌘⇧↑'
@@ -116,7 +116,7 @@
       expect(_.humanizeKeystroke(null)).toEqual null
       expect(_.humanizeKeystroke('')).toEqual ''

-  describe "_.deepExtend(objects...)", ->
+  describe "::deepExtend(objects...)", ->
     it "copies all key/values from each object into a new object", ->
       first =
         things:
@@ -147,7 +147,7 @@
             first: 1
             second: 2

-  describe "_.isSubset(potentialSubset, potentialSuperset)", ->
+  describe "::isSubset(potentialSubset, potentialSuperset)", ->
     it "returns whether the first argument is a subset of the second", ->
       expect(_.isSubset([1, 2], [1, 2])).toBeTruthy()
       expect(_.isSubset([1, 2], [1, 2, 3])).toBeTruthy()
@@ -155,7 +155,7 @@
       expect(_.isSubset([], [])).toBeTruthy()
       expect(_.isSubset([1, 2], [2, 3])).toBeFalsy()

-  describe '_.isEqual(a, b)', ->
+  describe '::isEqual(a, b)', ->
     it 'returns true when the elements are equal, false otherwise', ->
       expect(_.isEqual(null, null)).toBe true
       expect(_.isEqual('test', 'test')).toBe true
@@ -207,7 +207,7 @@
       expect(-> _.isEqual({}, {}, "junk")).not.toThrow()
       expect(-> _.isEqual({}, {}, [], "junk")).not.toThrow()

-  describe ".capitalize(word)", ->
+  describe "::capitalize(word)", ->
     it "capitalizes the word", ->
       expect(_.capitalize('')).toBe ''
       expect(_.capitalize(null)).toBe ''
@@ -215,8 +215,7 @@
       expect(_.capitalize('Github')).toBe 'GitHub'
       expect(_.capitalize('test')).toBe 'Test'

-
-  describe ".dasherize(word)", ->
+  describe "::dasherize(word)", ->
     it "dasherizes the word", ->
       expect(_.dasherize('')).toBe ''
       expect(_.dasherize(null)).toBe ''
@@ -224,7 +223,7 @@
       expect(_.dasherize('a_b')).toBe 'a-b'
       expect(_.dasherize('test')).toBe 'test'

-  describe ".uncamelcase(string)", ->
+  describe "::uncamelcase(string)", ->
     it "uncamelcases the string", ->
       expect(_.uncamelcase('')).toBe ''
       expect(_.uncamelcase(null)).toBe ''


Add support for streaming byte ranges

Add support for streaming byte ranges

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -176,8 +176,8 @@
                     rangeRequest = true

                     # Force into integers
-                    requestStart    = rangeVals[1]
-                    requestEnd      = rangeVals[2]
+                    requestStart    = rangeVals[1] - 0
+                    requestEnd      = rangeVals[2] - 0 or undefined

                     console.log "requested start, end is", requestStart, requestEnd

@@ -185,7 +185,7 @@
                     rangeEnd    = if (requestEnd    <= fend)    then requestEnd     else fend
                     console.log "rangeStart, rangeEnd, rangeRequest is", rangeStart, rangeEnd, rangeRequest

-                    length = (rangeEnd - rangeStart + 1)
+                    length = (rangeEnd - rangeStart)

             # What is the actual length of content being sent back?
             console.log "actual length is", length
@@ -218,8 +218,16 @@

                 # now set up our file read as a stream
                 console.log "creating read stream. #{@listeners} active downloads."
-                rstream = fs.createReadStream filename, bufferSize:256*1024
-
+                readStreamOpts = bufferSize: 256*1024
+
+                # If this is a range request, only deliver that range of bytes
+                if rangeRequest
+                    console.log "Sending byte range #{rangeStart}-#{rangeEnd}"
+                    readStreamOpts['start'] = rangeStart
+                    readStreamOpts['end']   = rangeEnd
+
+                console.log "read stream opts are", readStreamOpts
+                rstream = fs.createReadStream filename, readStreamOpts
                 rstream.pipe res, end:false

                 rstream.on "end", =>


improve style by removing unnecessary parentheses

--- src/angular-bacon.coffee
+++ src/angular-bacon.coffee
@@ -6,7 +6,7 @@
             scope[watchMethod] watchExp, (newValue) ->
                 bus.push newValue
             , objectEquality
-            scope.$on '$destroy', () -> bus.end()
+            scope.$on '$destroy', -> bus.end()
             initialValue = scope.$eval(watchExp)
             if typeof initialValue != "undefined"
                 bus.toProperty(initialValue)


Fix timout code

Fix timout code

--- src/app/code/code.coffee
+++ src/app/code/code.coffee
@@ -16,7 +16,7 @@
       setTimeout ->
         if self.run
           self.loop()
-      ,10000
+      ,60000
     ).catch(->
       if self.run
         self.loop()


Rename path variable to entryPath

Rename path variable to entryPath

--- src/app/directory.coffee
+++ src/app/directory.coffee
@@ -40,17 +40,17 @@
   getEntries: ->
     directories = []
     files = []
-    for path in fsUtils.list(@path)
+    for entryPath in fsUtils.list(@path)
       try
-        stat = fs.lstatSync(path)
+        stat = fs.lstatSync(entryPath)
         symlink = stat.isSymbolicLink()
-        stat = fs.statSync(path) if symlink
+        stat = fs.statSync(entryPath) if symlink
       catch e
         continue
       if stat.isDirectory()
-        directories.push(new Directory(path, symlink))
+        directories.push(new Directory(entryPath, symlink))
       else if stat.isFile()
-        files.push(new File(path, symlink))
+        files.push(new File(entryPath, symlink))

     directories.concat(files)


Avoid crash when missing connection request creds

--- lib/api-functions.coffee
+++ lib/api-functions.coffee
@@ -78,10 +78,10 @@
     if res.statusCode == 401 and res.headers['www-authenticate']?
       authHeader = auth.parseAuthHeader(res.headers['www-authenticate'])
       if authHeader.method is 'Basic'
-        options.headers = {'Authorization' : auth.basic(username, password)}
+        options.headers = {'Authorization' : auth.basic(username or '', password or '')}
       else if authHeader.method is 'Digest'
         options.headers = {
-          'Authorization' : auth.digest(username, password, options.path, 'GET', null, authHeader)
+          'Authorization' : auth.digest(username or '', password or '', options.path, 'GET', null, authHeader)
         }

       request = http.get(options, (res) ->


separate ics calendar data from the data url

--- lib/calendar_urls.coffee
+++ lib/calendar_urls.coffee
@@ -61,7 +61,7 @@

     href

-  icsCalendarUrl: ->
+  icsCalendarData: ->
     startTime = formatTime @get('start_at')
     endTime = formatTime @get('end_at')
     description = @get('description')?.replace(/(\r\n|\n|\r)/gm, "")
@@ -79,7 +79,7 @@
       'END:VEVENT',
       'END:VCALENDAR'].join('\n');

-    data = 'data:text/calendar;charset=utf8,' + data
-
+  icsCalendarUrl: ->
+    data = 'data:text/calendar;charset=utf8,' + @icsCalendarData()
     href = encodeURI(data)
     href


commandstream: Support new NetworkStop message

--- lib/commandstream.coffee
+++ lib/commandstream.coffee
@@ -217,6 +217,10 @@
   index += writeCmd(buffer, index, cmdFormat.commands.StartNetwork.id)
   return index

+commands.network.stop = (payload, buffer, index) ->
+  index += writeCmd(buffer, index, cmdFormat.commands.StopNetwork.id)
+  return index
+
 # The following are MicroFlo specific, not part of FBP runtime protocol
 commands.microflo.configuredebug = (payload, buffer, index) ->
   debugLevel = payload.level
@@ -263,6 +267,12 @@
     payload:
       running: true
       started: true
+  return m
+responses.NetworkReset = () ->
+  m =
+    protocol: "graph"
+    command: "clear"
+    payload: {}
   return m

 responses.NodeAdded = (componentLib, graph, cmdData) ->


Manually combine reducers (no longer combineReducers())

--- src/chromeExtension/reducers/appReducer.coffee
+++ src/chromeExtension/reducers/appReducer.coffee
@@ -1,6 +1,13 @@
-Redux = require 'redux'
-cx = require './cxReducer'
-stories = require './storyReducer'
-settings = require './settingsReducer'
+timm        = require 'timm'
+cx          = require './cxReducer'
+stories     = require './storyReducer'
+settings    = require './settingsReducer'

-module.exports = Redux.combineReducers {cx, stories, settings}
+reducer = (state = {}, action) ->
+  nextState = timm.merge state,
+    cx:       cx        state.cx,       action
+    stories:  stories   state.stories,  action
+    settings: settings  state.settings, action
+  nextState
+
+module.exports = reducer


Fix Set usage

Fix Set usage

--- lib/editor/result.coffee
+++ lib/editor/result.coffee
@@ -131,8 +131,8 @@
     @decoration = @editor.decorateMarker @marker, mark
     if @type == 'inline'
       ed = @editor
-      if not resultEditorRegistry.hasOwnProperty ed.id
-        resultEditorRegistry[ed.id] = true
+      if not resultEditorRegistry.has ed.id
+        resultEditorRegistry.add ed.id
         # create new editor specific result animation method
         listener = debounce((->
           res = layers[ed.id].getMarkers().filter((m) -> m.result?).map((m) -> m.result)


add a base path to all directory actions

--- lib/ewg/generator.coffee
+++ lib/ewg/generator.coffee
@@ -6,19 +6,24 @@
 util     = require 'util'

 class Generator
-  constructor: (@name, configPath, @gulp) ->
+  constructor: (@name, configPath, @gulp, @basePath = '') ->
     @config = new Config(configPath, @reGenerate).config
-    @dest   = @gulp.dest
     @watch  = @gulp.watch

   if:                 gulpif
-  changed:            changed
   log:       (msg) => log.info("#{@name}: ", msg)
   taskName: (name) => "#{@name}:#{name}"
   task: (name, cb) => @gulp.task(@taskName(name), cb)

   src: (src) =>
-    @gulp.src(src)
+    @gulp.src("#{@basePath}/#{src}")
+
+  changed: (src) =>
+    changed("#{@basePath}/#{src}")
+
+  dest: (dest) =>
+    @gulp.dest("#{@basePath}/#{dest}")
+

   isRepetitive:   => @config.hasOwnProperty 'repetitive'


Home: campaign removed by status

Home: campaign removed by status

--- client/Intro/home.coffee
+++ client/Intro/home.coffee
@@ -48,8 +48,8 @@

     @campaignContainer = new KDCustomHTMLView

-    # if KD.campaign?.status
-    @campaignContainer.addSubView new TBCampaignHomePageView {}, KD.campaign
+    if KD.campaign?.status
+      @campaignContainer.addSubView new TBCampaignHomePageView {}, KD.campaign

   show:->


remove map initialisation from analysis.coffee.js

--- app/assets/javascripts/analysis/analysis.js.coffee
+++ app/assets/javascripts/analysis/analysis.js.coffee
@@ -1,52 +1,5 @@
 initializeMap = () ->
-  baseMap = L.tileLayer('http://tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png', {maxZoom: 17})
-  baseSatellite =  new L.BingLayer("ApZALeudlU-OTm7Me2qekFHrstBXNdv3hft6qy3ZeTQWD6a460-QqCQyYnDigINc", {type: "Aerial", maxZoom: 19})
-
-  map = L.map 'map_analysis',
-    center: [24.5, 54]
-    zoom: 9
-    layers: [baseSatellite]
-    minZoom: 8
-    maxZoom: 19
-
-  baseMaps = {}
-  baseMaps[polyglot.t('analysis.map')] = baseMap
-  baseMaps[polyglot.t('analysis.satellite')] = baseSatellite
-
-  overlayMaps = {}
-  habitats = {
-    mangrove: '#008b00'
-    seagrass: '#9b1dea'
-    saltmarsh: '#007dff'
-    algal_mat: '#ffe048'
-    other: '#1dcbea'
-  }
-
-  _.each habitats, (polygon_fill, habitat) ->
-    query = "SELECT * FROM bc_#{habitat} WHERE toggle = true AND (action <> 'delete' OR action IS NULL)"
-    style = """
-      #bc_#{habitat} {
-        line-color: #FFF;
-        line-width: 0.5;
-        polygon-fill: #{polygon_fill};
-        polygon-opacity: 0.4
-      }
-    """
-    url   = "http://carbon-tool.cartodb.com/tiles/bc_#{habitat}/{z}/{x}/{y}.png?sql=#{query}&style=#{style}"
-
-    prettyName = polyglot.t("analysis.#{habitat}")
-    overlayMaps[prettyName] = L.tileLayer(url).addTo(map)
-
-  L.control.layers(baseMaps, overlayMaps).addTo(map)
-
-  attribution = L.control.attribution(
-    position: 'bottomright'
-    prefix: ''
-  )
-  attribution.addAttribution('Developed for the Abu Dhabi Blue Carbon Demonstration Project')
-  attribution.addTo(map)
-
-  return map
+  new AnalysisMap().map

 initializePica = (map) ->
   window.pica = new Pica.Application(


Re-fixes column sorting

Re-fixes column sorting

--- app/assets/javascripts/housing_locations.js.coffee
+++ app/assets/javascripts/housing_locations.js.coffee
@@ -22,11 +22,11 @@
     "columnDefs": [
       {
         "orderable": false,
-        "targets": [6,7,8]
+        "targets": [6,7,8,9]
       },
       {
         "searchable": false,
-        "targets": [2,3,4,5,6,7,8]
+        "targets": [2,3,4,5,6,7,8,9]
       }
     ],
     "dom": '<"wrapper"ftpr>'


adding some logging statements to see if I know what I'm doing at all

--- app/assets/javascripts/mercury/regions/full.coffee
+++ app/assets/javascripts/mercury/regions/full.coffee
@@ -246,12 +246,14 @@
       # replace snippet contents to be an identifier
       if filterSnippets then for element, index in container.find('[data-snippet]')
         element = jQuery(element)
-        if element.attr('wrap_content') != 'true'
-          if snippet = Mercury.Snippet.find(element.data("snippet"))
-            Mercury.log("adding data to snippet #{snippet.identity}: #{element.html()}")
-            snippet.data = element.html()
-          element.html("[#{element.data("snippet")}/#{element.data("version")}]")
-          element.attr({contenteditable: null, 'data-version': null})
+        if snippet = Mercury.Snippet.find(element.data("snippet"))
+          Mercury.log("adding data to snippet #{snippet.identity}: #{element.html()}")
+          snippet.data = element.html()
+        console.log('this is where this is happening!')
+        console.log('what is wrap_content? '+element.attr('wrap_content'))
+        console.log('what is inner html? '+element.html())
+        element.html("[#{element.data("snippet")}/#{element.data("version")}]")
+        element.attr({contenteditable: null, 'data-version': null})
         if saving && Mercury.config.serverParser.radius
           element.attr({'class': null})
           remove_attrs = []


added requires_key prop

added requires_key prop

--- app/assets/javascripts/models/repository.js.coffee
+++ app/assets/javascripts/models/repository.js.coffee
@@ -6,6 +6,7 @@
     params: [ { name: "repo", set_id: true } ]

     Backprop.create_strings @, "name", "uri", "branch", "public_key"
+    Backprop.create @, "requires_key", Boolean

   class exports.RepoList extends Stark.Collection
     model: exports.Repo


Add in the option to pass the position of the zoom control for the map

--- app/assets/javascripts/modules/maps/base.js.coffee
+++ app/assets/javascripts/modules/maps/base.js.coffee
@@ -21,7 +21,7 @@

         config = @$mapContainer.data()

-        @map = @createMap(@$mapContainer.attr('id'))
+        @map = @createMap(@$mapContainer.attr('id'), @$mapContainer.data('zoom-position'))

         unless config.scrollWheelZoom?
           @map.scrollWheelZoom.disable()
@@ -42,10 +42,10 @@
           Interactive.listen(@map)
           ProtectedAreaOverlay.render(@map, config)

-      createMap: (id) ->
+      createMap: (id, position = "bottomright") ->
         L.mapbox.map(
           id, 'unepwcmc.l8gj1ihl', CONFIG
-        ).addControl(L.control.zoom(position: 'bottomright'))
+        ).addControl(L.control.zoom(position: position))

       updateMap: (ids) ->
         @shownIds = ids


fix unit id used when deleting an ilo

fix unit id used when deleting an ilo

--- src/app/units/partials/directives/unit-contexts.coffee
+++ src/app/units/partials/directives/unit-contexts.coffee
@@ -289,7 +289,7 @@
       $scope.editILO()

     $scope.deleteILO = (ilo) ->
-      IntendedLearningOutcome.delete { id: ilo.id, unit_id: ilo.unit_id },
+      IntendedLearningOutcome.delete { id: ilo.id, unit_id: $scope.unit.id },
         (response) ->
           $scope.unit.ilos = _.without $scope.unit.ilos, ilo
           alertService.add("info", "ILO #{ilo.id} was deleted successfully", 2000)


Hotfix: fix to Mouvuino integration on HTTPS

--- client/src/app/io.coffee
+++ client/src/app/io.coffee
@@ -858,15 +858,20 @@

     # TODO: check for errors
     # TODO: make host and port configurable
-    ws = new WebSocket("ws://localhost:53141/")
-    ws.onopen = ->
-      ws.send("l")
-      state.connected = true
-    ws.onmessage = (event) ->
-      # Expecting data like "l 6360 -8796 -16152 -371 218 -147"
-      tokens = event.data.split(" ")
-      state.a = _.map(tokens[1..3], parseFloat)
-      state.g = _.map(tokens[4..6], parseFloat)
+    ws = null
+    try
+      scheme = if window.location.protocol is "https:" then "wss" else "ws"
+      ws = new WebSocket("#{scheme}://localhost:53141/")
+      ws.onopen = ->
+        ws.send("l")
+        state.connected = true
+      ws.onmessage = (event) ->
+        # Expecting data like "l 6360 -8796 -16152 -371 218 -147"
+        tokens = event.data.split(" ")
+        state.a = _.map(tokens[1..3], parseFloat)
+        state.g = _.map(tokens[4..6], parseFloat)
+    catch e
+      console.error("Cannot initialize WebSocket for Mouvino")

     return {
       provideData: -> return global: state


Exclude lower priority suggestions

Exclude lower priority suggestions

--- lib/love-provider.coffee
+++ lib/love-provider.coffee
@@ -6,7 +6,7 @@
     disableForSelector: '.source.lua .comment, .source.lua .string'

     inclusionPriority: 10
-    excludeLowerPriority: false
+    excludeLowerPriority: true

     # Required: Return a promise, an array of suggestions, or null.
     getSuggestions: ( { editor, bufferPosition, scopeDescriptor, prefix, activatedManually } ) ->


Rename button to Discard Changes

Rename button to Discard Changes

--- lib/changes/status-list-view.coffee
+++ lib/changes/status-list-view.coffee
@@ -251,7 +251,7 @@
       message: message
       detailedMessage: details
       buttons:
-        "Move to Trash": =>
+        "Discard Changes": =>
           shell.moveItemToTrash(localPath) if exists
           @git.forceCheckoutPath(path).then =>
             @base.trigger("index-updated")


Rename variables for clarity

Rename variables for clarity

--- lib/changes/status-list-view.coffee
+++ lib/changes/status-list-view.coffee
@@ -109,26 +109,26 @@

   appendUnstaged: (status) ->
-    unstagedSummaryView = new FileSummaryElement
+    unstagedSummaryElement = new FileSummaryElement
     # XXX: confusing use of status to mean two things here
     unstagedSummary = new FileSummary
       file: status
       status: 'unstaged'
       git: @changesView.model.git

-    unstagedSummaryView.initialize(unstagedSummary)
-    @unstagedNode.appendChild(unstagedSummaryView)
+    unstagedSummaryElement.initialize(unstagedSummary)
+    @unstagedNode.appendChild(unstagedSummaryElement)

   appendStaged: (status) ->
-    stagedSummaryView = new FileSummaryElement
+    stagedSummaryElement = new FileSummaryElement
     # XXX: confusing use of status to mean two things here
     stagedSummary = new FileSummary
       file: status
       status: 'staged'
       git: @changesView.model.git

-    stagedSummaryView.initialize(stagedSummary)
-    @stagedNode.appendChild(stagedSummaryView)
+    stagedSummaryElement.initialize(stagedSummary)
+    @stagedNode.appendChild(stagedSummaryElement)

   setIndices: ->
     for entry, idx in @getAllEntries()


Add 'cover' option for images

Add 'cover' option for images

--- lib/mixins/images.coffee
+++ lib/mixins/images.coffee
@@ -51,16 +51,26 @@
       else
         h = bh
         w = bh * ip
-
+
+    else if options.cover
+      [bw, bh] = options.cover
+      bp = bw / bh
+      ip = image.width / image.height
+      if ip > bp
+        h = bh
+        w = bh * ip
+      else
+        w = bw
+        h = bw / ip
+
+    if options.fit or options.cover
       if options.align is 'center'
         x = x + bw / 2 - w / 2
-
       else if options.align is 'right'
         x = x + bw - w

       if options.valign is 'center'
         y = y + bh / 2 - h / 2
-
       else if options.valign is 'bottom'
         y = y + bh - h


改为调用Util中的创建模块方法

改为调用Util中的创建模块方法

--- lib/module/module.coffee
+++ lib/module/module.coffee
@@ -48,46 +48,18 @@
     console.log options
     info = options.moduleInfo
     filePath = pathM.join info.modulePath,info.moduleId
-    configFilePath = pathM.join filePath,desc.moduleConfigFileName
-    configFile = new File(configFilePath)
-    configFileContent = Util.formatModuleConfigToObj(info)
-    entryFilePath = pathM.join filePath,info.mainEntry
-    entryFile = new File(entryFilePath)
-    htmlString = Util.getIndexHtmlCore()
-    isProject = options.isChameleonProject
-    configFile.create()
-      .then (isSuccess) =>
-        console.log isSuccess
-        if isSuccess is yes
-          configFile.setEncoding('utf8')
-          console.log 'CreateModule Success'
-          cb = (err) =>
-            console.log err
-          Util.writeJson(configFilePath,configFileContent,cb)
-          entryFile.create()
-        else
-          console.log 'CreateModule error'
-      .then (isSuccess) =>
-        if isSuccess is yes
-          entryFile.writeSync(htmlString)
-          @addProjectModule info
-          @openTreeView filePath
-          alert desc.createModuleSuccess
-          @chameleonBox.closeView()
-      # .finally =>
-        # console.log 'CreateModule Success',@
+    params = @formatOptions options
+    Util.createModule params, (err) =>
+      return console.error err if err?
+      @addProjectModule info
+      @openTreeView filePath
+      alert desc.createModuleSuccess
+      @chameleonBox.closeView()
+

   CreateSimpleModule: (options) ->
     console.log 'SimpleModule'
-    info = options.moduleInfo
-    moduleConfig = Util.formatModuleConfigToObj info
-    params =
-      builderConfig: []
-      moduleConfig: moduleConfig
-      moduleInfo:
-        identifier: info.moduleId
-        moduleName: info.moduleName
-        modulePath: info.modulePath
+    params = @formatOptions options
     # console.log params
     Builder.activate(params);
     @chameleonBox.closeView()
@@ -139,6 +111,16 @@
         @modalPanel.item.children(".loading-mask").remove()
     Util.getRepo(desc.getFrameworkPath(), config.repoUri, success) #没有，执行 git clone，成功后执行第二步

+  formatOptions: (options) ->
+    info = options.moduleInfo
+    moduleConfig = Util.formatModuleConfigToObj info
+    params =
+      builderConfig: []
+      moduleConfig: moduleConfig
+      moduleInfo:
+        identifier: info.moduleId
+        moduleName: info.moduleName
+        modulePath: info.modulePath

   editModuleConfig: (config,info) ->
     config.template = config.identifier


Put the time in the front matter, closes #29

--- lib/new-post-view.coffee
+++ lib/new-post-view.coffee
@@ -50,8 +50,22 @@
     title = @generateDateString() + "-" + titleName unless draft
     return title

-  generateDateString: (currentTime = new Date())->
-    return currentTime.getFullYear() + "-" + ("0" + (currentTime.getMonth() + 1)).slice(-2) + "-" + ("0" + currentTime.getDate()).slice(-2)
+  generateDateString: (currentTime = new Date(), showTime = false)->
+    string = currentTime.getFullYear() +
+      "-" +
+      ("0" + (currentTime.getMonth() + 1)).slice(-2) +
+      "-" +
+      ("0" + currentTime.getDate()).slice(-2)
+
+    if showTime
+      string += " " +
+      ("0" + currentTime.getHours()).slice(-2) +
+      ":" +
+      ("0" + currentTime.getMinutes()).slice(-2) +
+      ":" +
+      ("0" + currentTime.getSeconds()).slice(-2)
+
+    return string

   onConfirm: (title) ->
     draft = !!@draftCheckbox.prop('checked')
@@ -71,7 +85,7 @@
         if endsWithDirectorySeparator
           @showError("File names must not end with a '/' character.")
         else
-          fs.writeFileSync(pathToCreate, @fileContents(title, @generateDateString()))
+          fs.writeFileSync(pathToCreate, @fileContents(title, @generateDateString(new Date(), true)))
           #atom.project.getRepo()?.getPathStatus(pathToCreate)
           atom.workspace.open(pathToCreate)
           @destroy()


Improve max-min for coffee

Improve max-min for coffee

--- code/math-max-min.coffee
+++ code/math-max-min.coffee
@@ -1,4 +1,4 @@
-console.log Math.min.apply(Math, [5, 10])
+console.log Math.min.apply(@, [5, 10])
 # 5
-console.log Math.max.apply(Math, [4, 7])
+console.log Math.max.apply(@, [4, 7])
 # 7


Fixed issue for modified/unmodified files

--- lib/code-peek.coffee
+++ lib/code-peek.coffee
@@ -64,8 +64,21 @@
     regExp = SupportedFiles.getFunctionRegExpForFileType(fileType, functionName)

     atom.workspace.scan(regExp, {paths: ["*.#{fileType}"]}, (matchingFile) =>
+      initialLine = 0
+
+      # HACK I literally have no idea why this is happening. If the file has
+      # unsaved changes, we get a range object. If it does not, we get an array
+      # for the range.
+      if matchingFile.matches[0].range? and
+        matchingFile.matches[0].range.start? and
+        matchingFile.matches[0].range.start.row?
+          initialLine = matchingFile.matches[0].range.start.row
+      else
+        initialLine = matchingFile.matches[0].range[0][0]
+
+      console.log "initial line is #{initialLine}"
       @matchingFiles.push(new FileInfo(matchingFile.filePath,
-        matchingFile.matches[0].range[0][0]))
+        initialLine))
     ).then =>
       if @matchingFiles.length is 0
         atom.notifications.addWarning("Could not find function \


log more

log more

--- lib/converter.coffee
+++ lib/converter.coffee
@@ -34,7 +34,7 @@
 exports.convert = (req, res) ->
 	upload = req.files.upload

-	console.log(req.files)
+	console.log(req)

 	settings = getConvertSettings(req)


remove colorspace argument. not really needed as far as i can tell

--- lib/converter.coffee
+++ lib/converter.coffee
@@ -8,11 +8,11 @@
 	'pdf:jpg':
 		'input': 'pdf'
 		'output': 'jpg'
-		'args': '-density 600 -colorspace RGB'
+		'args': '-density 600'
 	'pdf:png':
 		'input': 'pdf'
 		'output': 'png'
-		'args': '-density 600 -colorspace RGB'
+		'args': '-density 600'

 getConvertSettings = (req) ->


Increment index on each iteration

Increment index on each iteration

--- lib/directory.coffee
+++ lib/directory.coffee
@@ -73,8 +73,9 @@
     index = 0
     for entry in @directory.getEntries() when not @isPathIgnored(entry.path)
       name = entry.getBaseName()
-      newEntries.push([entry, index++]) if not @entries.hasOwnProperty(name)
+      newEntries.push([entry, index]) if not @entries.hasOwnProperty(name)
       delete removedEntries[name]
+      index++

     for name, entry of removedEntries
       @entries[name]?.destroy()


More tests for array position specific generators.

--- tests.coffee
+++ tests.coffee
@@ -88,7 +88,7 @@
           if fields.body and fields.subdocument?.body
             tags.push "tag-#{ tags.length }-prefix-#{ fields.body.toLowerCase() }-#{ fields.subdocument.body.toLowerCase() }-suffix"
           if fields.body and fields.nested and _.isArray fields.nested
-            for nested in fields.nested
+            for nested in fields.nested when nested.body
               tags.push "tag-#{ tags.length }-prefix-#{ fields.body.toLowerCase() }-#{ nested.body.toLowerCase() }-suffix"
           [fields._id, tags]
       ]
@@ -3691,9 +3691,13 @@
         'tag-6-prefix-foobar-nestedfoobar-suffix'
       ]

-    Persons.remove @person2Id,
-      expect (error) =>
-        test.isFalse error, error?.toString?() or error
+    Posts.update @postId,
+      $set:
+        'subdocument.body': 'SubdocumentFooBarZ'
+    ,
+      expect (error, res) =>
+        test.isFalse error, error?.toString?() or error
+        test.isTrue res

     # Sleep so that observers have time to update documents
     Meteor.setTimeout expect(), 500
@@ -3708,6 +3712,731 @@
         _id: @person1._id
         username: @person1.username
       subscribers: [
+        _id: @person2._id
+      ,
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ]
+      reviewers: [
+        _id: @person2._id
+        username: @person2.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ]
+      subdocument:
+        person:
+          _id: @person2._id
+          username: @person2.username
+        persons: [
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ]
+        slug: 'subdocument-prefix-foobar-subdocumentfoobarz-suffix'
+        body: 'SubdocumentFooBarZ'
+      nested: [
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBar'
+      slug: 'prefix-foobar-subdocumentfoobarz-suffix'
+      tags: [
+        'tag-0-prefix-foobar-subdocumentfoobarz-suffix'
+        'tag-1-prefix-foobar-nestedfoobar-suffix'
+        'tag-2-prefix-foobar-nestedfoobar-suffix'
+        'tag-3-prefix-foobar-nestedfoobar-suffix'
+        'tag-4-prefix-foobar-nestedfoobar-suffix'
+        'tag-5-prefix-foobar-nestedfoobar-suffix'
+        'tag-6-prefix-foobar-nestedfoobar-suffix'
+      ]
+
+    Posts.update @postId,
+      $set:
+        'nested.0.body': 'NestedFooBarZ'
+    ,
+      expect (error, res) =>
+        test.isFalse error, error?.toString?() or error
+        test.isTrue res
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), 500
+,
+  (test, expect) ->
+    @post = Posts.findOne @postId,
+      transform: null # So that we can use test.equal
+
+    test.equal @post,
+      _id: @postId
+      author:
+        _id: @person1._id
+        username: @person1.username
+      subscribers: [
+        _id: @person2._id
+      ,
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ]
+      reviewers: [
+        _id: @person2._id
+        username: @person2.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ]
+      subdocument:
+        person:
+          _id: @person2._id
+          username: @person2.username
+        persons: [
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ]
+        slug: 'subdocument-prefix-foobar-subdocumentfoobarz-suffix'
+        body: 'SubdocumentFooBarZ'
+      nested: [
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobarz-suffix'
+        body: 'NestedFooBarZ'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBar'
+      slug: 'prefix-foobar-subdocumentfoobarz-suffix'
+      tags: [
+        'tag-0-prefix-foobar-subdocumentfoobarz-suffix'
+        'tag-1-prefix-foobar-nestedfoobarz-suffix'
+        'tag-2-prefix-foobar-nestedfoobar-suffix'
+        'tag-3-prefix-foobar-nestedfoobar-suffix'
+        'tag-4-prefix-foobar-nestedfoobar-suffix'
+        'tag-5-prefix-foobar-nestedfoobar-suffix'
+        'tag-6-prefix-foobar-nestedfoobar-suffix'
+      ]
+
+    Posts.update @postId,
+      $set:
+        'nested.4.body': 'NestedFooBarA'
+    ,
+      expect (error, res) =>
+        test.isFalse error, error?.toString?() or error
+        test.isTrue res
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), 500
+,
+  (test, expect) ->
+    @post = Posts.findOne @postId,
+      transform: null # So that we can use test.equal
+
+    test.equal @post,
+      _id: @postId
+      author:
+        _id: @person1._id
+        username: @person1.username
+      subscribers: [
+        _id: @person2._id
+      ,
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ]
+      reviewers: [
+        _id: @person2._id
+        username: @person2.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ]
+      subdocument:
+        person:
+          _id: @person2._id
+          username: @person2.username
+        persons: [
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ]
+        slug: 'subdocument-prefix-foobar-subdocumentfoobarz-suffix'
+        body: 'SubdocumentFooBarZ'
+      nested: [
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobarz-suffix'
+        body: 'NestedFooBarZ'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobara-suffix'
+        body: 'NestedFooBarA'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBar'
+      slug: 'prefix-foobar-subdocumentfoobarz-suffix'
+      tags: [
+        'tag-0-prefix-foobar-subdocumentfoobarz-suffix'
+        'tag-1-prefix-foobar-nestedfoobarz-suffix'
+        'tag-2-prefix-foobar-nestedfoobar-suffix'
+        'tag-3-prefix-foobar-nestedfoobar-suffix'
+        'tag-4-prefix-foobar-nestedfoobar-suffix'
+        'tag-5-prefix-foobar-nestedfoobara-suffix'
+        'tag-6-prefix-foobar-nestedfoobar-suffix'
+      ]
+
+    Posts.update @postId,
+      $set:
+        'nested.3.body': null
+    ,
+      expect (error, res) =>
+        test.isFalse error, error?.toString?() or error
+        test.isTrue res
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), 500
+,
+  (test, expect) ->
+    @post = Posts.findOne @postId,
+      transform: null # So that we can use test.equal
+
+    test.equal @post,
+      _id: @postId
+      author:
+        _id: @person1._id
+        username: @person1.username
+      subscribers: [
+        _id: @person2._id
+      ,
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ]
+      reviewers: [
+        _id: @person2._id
+        username: @person2.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ]
+      subdocument:
+        person:
+          _id: @person2._id
+          username: @person2.username
+        persons: [
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ]
+        slug: 'subdocument-prefix-foobar-subdocumentfoobarz-suffix'
+        body: 'SubdocumentFooBarZ'
+      nested: [
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobarz-suffix'
+        body: 'NestedFooBarZ'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: null
+        body: null
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobara-suffix'
+        body: 'NestedFooBarA'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBar'
+      slug: 'prefix-foobar-subdocumentfoobarz-suffix'
+      tags: [
+        'tag-0-prefix-foobar-subdocumentfoobarz-suffix'
+        'tag-1-prefix-foobar-nestedfoobarz-suffix'
+        'tag-2-prefix-foobar-nestedfoobar-suffix'
+        'tag-3-prefix-foobar-nestedfoobar-suffix'
+        'tag-4-prefix-foobar-nestedfoobara-suffix'
+        'tag-5-prefix-foobar-nestedfoobar-suffix'
+      ]
+
+    Posts.update @postId,
+      $unset:
+        'nested.2.body': ''
+    ,
+      expect (error, res) =>
+        test.isFalse error, error?.toString?() or error
+        test.isTrue res
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), 500
+,
+  (test, expect) ->
+    @post = Posts.findOne @postId,
+      transform: null # So that we can use test.equal
+
+    test.equal @post,
+      _id: @postId
+      author:
+        _id: @person1._id
+        username: @person1.username
+      subscribers: [
+        _id: @person2._id
+      ,
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ]
+      reviewers: [
+        _id: @person2._id
+        username: @person2.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ]
+      subdocument:
+        person:
+          _id: @person2._id
+          username: @person2.username
+        persons: [
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ]
+        slug: 'subdocument-prefix-foobar-subdocumentfoobarz-suffix'
+        body: 'SubdocumentFooBarZ'
+      nested: [
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobarz-suffix'
+        body: 'NestedFooBarZ'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: null
+        body: null
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobar-nestedfoobara-suffix'
+        body: 'NestedFooBarA'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBar'
+      slug: 'prefix-foobar-subdocumentfoobarz-suffix'
+      tags: [
+        'tag-0-prefix-foobar-subdocumentfoobarz-suffix'
+        'tag-1-prefix-foobar-nestedfoobarz-suffix'
+        'tag-2-prefix-foobar-nestedfoobar-suffix'
+        'tag-3-prefix-foobar-nestedfoobara-suffix'
+        'tag-4-prefix-foobar-nestedfoobar-suffix'
+      ]
+
+    Posts.update @postId,
+      $set:
+        'body': 'FooBarZ'
+    ,
+      expect (error, res) =>
+        test.isFalse error, error?.toString?() or error
+        test.isTrue res
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), 500
+,
+  (test, expect) ->
+    @post = Posts.findOne @postId,
+      transform: null # So that we can use test.equal
+
+    test.equal @post,
+      _id: @postId
+      author:
+        _id: @person1._id
+        username: @person1.username
+      subscribers: [
+        _id: @person2._id
+      ,
+        _id: @person2._id
+      ,
+        _id: @person3._id
+      ]
+      reviewers: [
+        _id: @person2._id
+        username: @person2.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ,
+        _id: @person3._id
+        username: @person3.username
+      ]
+      subdocument:
+        person:
+          _id: @person2._id
+          username: @person2.username
+        persons: [
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person2._id
+          username: @person2.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ,
+          _id: @person3._id
+          username: @person3.username
+        ]
+        slug: 'subdocument-prefix-foobarz-subdocumentfoobarz-suffix'
+        body: 'SubdocumentFooBarZ'
+      nested: [
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobarz-nestedfoobarz-suffix'
+        body: 'NestedFooBarZ'
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobarz-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: null
+        body: null
+      ,
+        required:
+          _id: @person2._id
+          username: @person2.username
+        optional:
+          _id: @person2._id
+          username: @person2.username
+        slug: 'nested-prefix-foobarz-nestedfoobara-suffix'
+        body: 'NestedFooBarA'
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobarz-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBarZ'
+      slug: 'prefix-foobarz-subdocumentfoobarz-suffix'
+      tags: [
+        'tag-0-prefix-foobarz-subdocumentfoobarz-suffix'
+        'tag-1-prefix-foobarz-nestedfoobarz-suffix'
+        'tag-2-prefix-foobarz-nestedfoobar-suffix'
+        'tag-3-prefix-foobarz-nestedfoobara-suffix'
+        'tag-4-prefix-foobarz-nestedfoobar-suffix'
+      ]
+
+    Persons.remove @person2Id,
+      expect (error) =>
+        test.isFalse error, error?.toString?() or error
+
+    # Sleep so that observers have time to update documents
+    Meteor.setTimeout expect(), 500
+,
+  (test, expect) ->
+    @post = Posts.findOne @postId,
+      transform: null # So that we can use test.equal
+
+    test.equal @post,
+      _id: @postId
+      author:
+        _id: @person1._id
+        username: @person1.username
+      subscribers: [
         _id: @person3._id
       ]
       reviewers: [
@@ -3726,39 +4455,35 @@
           _id: @person3._id
           username: @person3.username
         ]
-        slug: 'subdocument-prefix-foobar-subdocumentfoobar-suffix'
-        body: 'SubdocumentFooBar'
+        slug: 'subdocument-prefix-foobarz-subdocumentfoobarz-suffix'
+        body: 'SubdocumentFooBarZ'
       nested: [
         required:
           _id: @person3._id
           username: @person3.username
         optional: null
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
       ,
         required:
           _id: @person3._id
           username: @person3.username
         optional: null
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ,
-        required:
-          _id: @person3._id
-          username: @person3.username
-        optional:
-          _id: @person3._id
-          username: @person3.username
-        slug: 'nested-prefix-foobar-nestedfoobar-suffix'
-        body: 'NestedFooBar'
-      ]
-      body: 'FooBar'
-      slug: 'prefix-foobar-subdocumentfoobar-suffix'
+        slug: null
+        body: null
+      ,
+        required:
+          _id: @person3._id
+          username: @person3.username
+        optional:
+          _id: @person3._id
+          username: @person3.username
+        slug: 'nested-prefix-foobarz-nestedfoobar-suffix'
+        body: 'NestedFooBar'
+      ]
+      body: 'FooBarZ'
+      slug: 'prefix-foobarz-subdocumentfoobarz-suffix'
       tags: [
-        'tag-0-prefix-foobar-subdocumentfoobar-suffix'
-        'tag-1-prefix-foobar-nestedfoobar-suffix'
-        'tag-2-prefix-foobar-nestedfoobar-suffix'
-        'tag-3-prefix-foobar-nestedfoobar-suffix'
+        'tag-0-prefix-foobarz-subdocumentfoobarz-suffix'
+        'tag-1-prefix-foobarz-nestedfoobar-suffix'
       ]

     Persons.remove @person3Id,
@@ -3782,13 +4507,13 @@
       subdocument:
         person: null
         persons: []
-        slug: 'subdocument-prefix-foobar-subdocumentfoobar-suffix'
-        body: 'SubdocumentFooBar'
+        slug: 'subdocument-prefix-foobarz-subdocumentfoobarz-suffix'
+        body: 'SubdocumentFooBarZ'
       nested: []
-      body: 'FooBar'
-      slug: 'prefix-foobar-subdocumentfoobar-suffix'
+      body: 'FooBarZ'
+      slug: 'prefix-foobarz-subdocumentfoobarz-suffix'
       tags: [
-        'tag-0-prefix-foobar-subdocumentfoobar-suffix'
+        'tag-0-prefix-foobarz-subdocumentfoobarz-suffix'
       ]

     Persons.remove @person1Id,


Fix cmd-c cmd-f quick sequence from altering the text

There is currently a very annoying bug in atom where if you are quick to search, it will override the search with the previous value. See https://github.com/atom/find-and-replace/issues/758 for the bug report and repro steps.

Looking at the blame, it looks like the behavior to replace the text was there since the very early days but in 2014, a check was added to make sure that it doesn't override it if the text is the same as it would make the cursor jump ( https://github.com/atom/find-and-replace/issues/121 ).

I don't understand why when the result of a search comes back, you want to modify the input text. I do think this is a bug and the behavior should be to just leave the text as is and when that new text is finished searching it'll update the search results. So, this removes the entire update logic.

Test Plan:
Do the repro steps of https://github.com/atom/find-and-replace/issues/758

Before this commit:

![](http://g.recordit.co/01zDNyuFcN.gif)

After this commit:

![](http://g.recordit.co/EGwg0ZVaE0.gif)

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -313,9 +313,6 @@
     else
       @clearMessage()

-    if @model.getFindOptions().findPattern isnt @findEditor.getText()
-      @findEditor.setText(@model.getFindOptions().findPattern)
-
   findError: (error) =>
     @setErrorMessage(error.message)

@@ -426,7 +423,9 @@
     if editor?.getSelectedText?
       findPattern = editor.getSelectedText() or editor.getWordUnderCursor()
       findPattern = Util.escapeRegex(findPattern) if @model.getFindOptions().useRegex
-      @search(findPattern) if findPattern
+      if findPattern
+        @findEditor.setText(findPattern)
+        @search()

   findNextSelected: =>
     @setSelectionAsFindPattern()


Rm console.log

Rm console.log

--- lib/installed-packages-panel.coffee
+++ lib/installed-packages-panel.coffee
@@ -77,7 +77,6 @@
   loadPackages: ->
     packagesWithUpdates = {}
     @packageManager.getOutdated().then (packages) =>
-      console.log packages
       for {name, latestVersion} in packages
         packagesWithUpdates[name] = latestVersion
       @displayPackageUpdates(packagesWithUpdates)