fix selector of button 'Update All' and 'Check for Updates'

--- lib/preferences-settings.coffee
+++ lib/preferences-settings.coffee
@@ -91,8 +91,8 @@

   @localizeUpdatesPanel: () =>
     PU.applySpecialHeading(@sv, @defS["heading-available-updates"]._label, 2, @defS["heading-available-updates"].value)
-    PU.applyTextWithOrg(@sv.querySelector('.update-all-button.btn-primary'), @defS.updates["check-updates"])
-    PU.applyTextWithOrg(@sv.querySelector('.update-all-button:not(.btn-primary)'), @defS.updates["update-all"])
+    PU.applyTextWithOrg(@sv.querySelector('.update-all-button.btn-primary'), @defS.updates["update-all"])
+    PU.applyTextWithOrg(@sv.querySelector('.update-all-button:not(.btn-primary)'), @defS.updates["check-updates"])
     PU.applyTextWithOrg(@sv.querySelector('.alert.icon-hourglass'),  @defS.updates["checking-updates"])
     PU.applyTextWithOrg(@sv.querySelector('.alert.icon-heart'),  @defS.updates["all-updated-message"])
     # TODO add failure message which is dynamic DOM node


Add test for ensuring fixedAmount is null unless explicitly specified

--- test/test.coffee
+++ test/test.coffee
@@ -84,12 +84,22 @@
       line.beneficiaries.should.containDeep [name: 'gabriele']
       line.beneficiaries.should.containDeep [name: 'daniele']

+    it 'should have a null fixed amount when no amount is specified', ->
+      result = parser.parse wallet
+      computedLines = brain.computeFromParsed result
+      line = computedLines[4]
+      (line.beneficiaries.filter((b) -> b.name == 'luca')[0].fixedAmount == null).should.be.true
+
+    it 'should have a null fixed amount when a modifier is specified', ->
+      result = parser.parse wallet
+      computedLines = brain.computeFromParsed result
+      line = computedLines[4]
+      (line.beneficiaries.filter((b) -> b.name == 'gabriele')[0].fixedAmount == null).should.be.true
+
     it 'should compute the fixed amount', ->
       result = parser.parse wallet
       computedLines = brain.computeFromParsed result
       line = computedLines[4]
-      line.beneficiaries.filter((b) -> b.name == 'luca')[0].fixedAmount.should.equal 0
-      line.beneficiaries.filter((b) -> b.name == 'gabriele')[0].fixedAmount.should.equal 0
       line.beneficiaries.filter((b) -> b.name == 'daniele')[0].fixedAmount.should.equal 10
       line.computing.totalFixedAmount.should.equal 10

@@ -117,7 +127,7 @@
       line = computedLines[7]
       line.computing.totalSpentAmount.should.equal 15 + 7 + 18

-    it 'should compute the owed amount', ->
+    it 'should compute the balance', ->
       result = parser.parse wallet
       computedLines = brain.computeFromParsed result
       line = computedLines[7]


Add notifications when turn on/off auto run

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -59,5 +59,7 @@
   toggleAutoRun: ->
     if atom.config.get('rubocop-auto-correct.autoRun')
       atom.config.set('rubocop-auto-correct.autoRun', false)
+      atom.notifications.addSuccess("Trun OFF, Auto Run")
     else
       atom.config.set('rubocop-auto-correct.autoRun', true)
+      atom.notifications.addSuccess("Trun ON, Auto Run")


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/text.coffee
+++ test/text.coffee
@@ -15,6 +15,6 @@
   equals text.I.y, -5

 test "text fades out", ->
-  ;
+  te

 module()


Test race with branches

Test race with branches

--- spec/Thenable.coffee
+++ spec/Thenable.coffee
@@ -866,6 +866,35 @@
           chai.expect(res).to.equal 15
           setTimeout ->
             chai.expect(called).to.equal 2
+            done()
+          , 1
+
+    describe 'with qualified race, branches  & return values', ->
+      it 'should resolve', (done) ->
+        called = 0
+        multiply = (multiplier, orig, data) ->
+          orig.tree 'a'
+          .deliver data
+          .then (c, d) ->
+            c.branch 'multiplied', (b, d) ->
+              called++
+              d * multiplier
+            c.branch 'divided', (b, d) ->
+              called++
+              d / multiplier
+        Root()
+        .deliver 5
+        .race [
+          multiply.bind @, 2
+          multiply.bind @, 3
+          multiply.bind @, 4
+        ], (result, results) ->
+          return result if result.value > 10
+          return false
+        .finally (c, res) ->
+          chai.expect(res).to.equal 15
+          setTimeout ->
+            chai.expect(called).to.equal 4
             done()
           , 1


Add pending state to spec helper.

Add pending state to spec helper.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -7,14 +7,17 @@
 {expect} = chai

-parse = (source, expectation) ->
-  result = null
+parse = (source, expectation, pending) ->
+  itFn = if pending then xit else it
+
   describe source, ->
-    it '✓ ok', ->
+    result = null
+
+    itFn '✓ ok', ->
       result = parser.parse source
       #console.log result
       expect(result).to.be.an 'object'
-    it '✓ matched', ->
+    itFn '✓ matched', ->
       # sort b/c order doesn't really matter
       for key, val of result
         if val.sort? then val.sort()


Make sure operations is fulfilled if no commands

--- tests/e2e.coffee
+++ tests/e2e.coffee
@@ -22,6 +22,16 @@
 			result += chunk
 		stream.on 'end', ->
 			resolve(result)
+
+wary.it 'should be fulfilled if no operations', {}, ->
+	configuration = operations.execute(RASPBERRY_PI, [])
+	promise = utils.waitStreamToClose(configuration)
+	m.chai.expect(promise).to.eventually.be.undefined
+
+wary.it 'should be fulfilled if operations is undefined', {}, ->
+	configuration = operations.execute(RASPBERRY_PI)
+	promise = utils.waitStreamToClose(configuration)
+	m.chai.expect(promise).to.eventually.be.undefined

 wary.it 'should be rejected if the command does not exist',
 	raspberrypi: RASPBERRY_PI


Removed abandoned comment

Removed abandoned comment

--- src/beans.coffee
+++ src/beans.coffee
@@ -152,8 +152,6 @@
       fs.watchFile file, {persistent: true, interval: 500}, (curr, prev) ->
         if curr.mtime.getTime() isnt prev.mtime.getTime()
           fn file
-
-# Return current time formatted as HH:mm:ss.

 # Compile one CoffeeScript file.
 # Existing event handlers are synchronously invoked in the process.


Fix running the build callback after all paths are compiled

--- src/beans.coffee
+++ src/beans.coffee
@@ -193,18 +193,31 @@

 # Compile all CoffeeScript sources for Node.
 buildNode = (info, watch, fn) ->
+  console.log info
+
+  # Calculate path count.
+  count = 0
+  count++ for _ of info.paths
+
+  # Iterate through, while counting compiled paths.
+  compiled = 0
   for sourcePath, targetPath of info.paths
     do (sourcePath, targetPath) ->
       withFiles path.join(sourcePath, '**/*.coffee'), (files) ->
         for file in files
           compile info, file, sourcePath, targetPath
-        fn?()
-        if watch
-          watchFiles files, (file) ->
-            try
-              compile info, file, sourcePath, targetPath
-            catch err
-              console.log err.stack
+
+        # Run the callback and watchers only after everything is compiled.
+        console.log 'compiled: ' + compiled
+        if ++compiled is count
+          console.log 'running callback'
+          fn?()
+          if watch
+            watchFiles files, (file) ->
+              try
+                compile info, file, sourcePath, targetPath
+              catch err
+                console.log err.stack

 # Use Stitch to create a browser bundle.
 bundle = (info) ->


Removed obsolete comment

Removed obsolete comment

--- src/Bootstrap.coffee
+++ src/Bootstrap.coffee
@@ -110,8 +110,6 @@
     cb()

   initTemplates: (cb) ->
-    # TODO Implement possibility for development environment to auto-load changes using gaze
-
     # find all templates and save their content in a hash
     files = []
     @metadata().templates = {}


Only compile app if electron-compile is there

--- src/build.coffee
+++ src/build.coffee
@@ -22,13 +22,16 @@
   compileDirs = (path.join(projectPath, dir) for dir in compileDirs)

   cmd = path.join(projectPath, 'node_modules', '.bin', 'electron-compile')
-  args = [
-    '--target', compileCacheDir
-  ].concat(compileDirs)
+  if fs.existsSync(cmd)
+    args = [
+      '--target', compileCacheDir
+    ].concat(compileDirs)

-  console.log 'Running', cmd, args
+    console.log 'Running', cmd, args

-  Utils.spawnCommand(cmd, args, callback)
+    Utils.spawnCommand(cmd, args, callback)
+  else
+    callback()

 packageApp = (options, callback) ->
   projectPath = Utils.getProjectPath()


stupid auth

stupid auth

--- app.coffee
+++ app.coffee
@@ -24,11 +24,15 @@
 app.post '/keyin', (req, res) ->
   twiml = new twilio.TwimlResponse()
   # console.log req
-  twiml
-  .say 'Success!'
-  .say "You entered #{req.body['Digits']}"
-  .play '/DTMF-6.mp3',
-    loop: 2
+  if req.body['Digits'] == '2674'
+    twiml
+    .say 'Welcome home YOLK'
+    .play '/DTMF-6.mp3',
+      loop: 2
+  else
+    twiml
+    .say 'Denied!!'
+    .say 'Good bye!'

   res.set 'Content-Type', 'text/xml'
   res.send new Buffer twiml.toString()


Add new tracker

Add new tracker

--- app.coffee
+++ app.coffee
@@ -1,6 +1,7 @@
 trackers = [
   [ 'wss://tracker.btorrent.xyz' ]
   [ 'wss://tracker.webtorrent.io' ]
+  [ 'wss://tracker.openwebtorrent.com' ]
 ]

 opts = {
@@ -268,4 +269,4 @@

 app.filter 'progress', ->
   (num) ->
-    (100 * num).toFixed(1) + '%'+    (100 * num).toFixed(1) + '%'


eat medicine for all human

eat medicine for all human

--- app.coffee
+++ app.coffee
@@ -155,6 +155,16 @@
 			if err then return next err
 			res.redirect '/admin'

+app.get '/admin/submitmedicineforallhumans', authorize('admin'), (req, res, next) ->
+	User = mongoose.model 'user'
+	User.find (err, users) ->
+		for user in users
+			u = UserFactory(user.toObject()).getInfo()
+			if u.role is 'human'
+				user.lastActionDate = new Date()
+				user.save();
+		res.send('ok')
+
 app.post '/human/submitMedicine', authorize('human'), (req, res, next) ->
 	code = req.body.code
 	Medicine = mongoose.model 'medicine'


* minor changed.

* minor changed.

--- src/cache.coffee
+++ src/cache.coffee
@@ -16,6 +16,7 @@
   hasLRU: LRUCache::has
   has: (id) ->hasOwnProperty.call(@_cache, id) or @hasLRU(id)
   deleteLRU: LRUCache::del
+  delLRU: LRUCache::del
   delFixed: (id)->
     result = @_cache[id]
     if result isnt undefined
@@ -25,35 +26,24 @@
       true
   deleteFixed: @::delFixed
   delete: (id) ->
-    result = @_cache[id]
-    if result isnt undefined
-      delete @_cache[id]
-    else
-      result = @_cacheLRU[id]
-      if result isnt undefined
-        delete @_cacheLRU[id]
-        @_lruQueue.delete(id) if @_lruQueue
-      else return false
-    @emit('del', id, result.value)
-    true
+    result = @delFixed(id)
+    result = @delLRU(id) if not result
+    result
   del: @::delete
   getFixed: (id) ->@_cache[id]
   # peek a id from LRU Cache, without updating the "recently used"-ness of the key
   peekLRU: LRUCache::peek
   peek: (id)->
     result = @_cache[id]
-    if result isnt undefined
-      result
-    else
-      peekLRU(id)
+    result = peekLRU(id) if result is undefined
+    result
   getLRU: LRUCache::get
   getFixed: (id)-> @_cache[id]
   get: (id)->
     result = @_cache[id]
-    if result isnt undefined
-      result
-    else
-      @getLRU(id)
+    result = @getLRU(id) if result is undefined
+    result
+
   setFixed: (id, value)->
     oldValue = @_cache[id]
     if oldValue isnt undefined


:memo: Add period

:memo: Add period

--- src/color.coffee
+++ src/color.coffee
@@ -52,11 +52,11 @@
         enumerable: true
         configurable: false

-  # Esssential: Returns a {String} in the form `'#abcdef'`
+  # Esssential: Returns a {String} in the form `'#abcdef'`.
   toHexString: ->
     "##{numberToHexString(@red)}#{numberToHexString(@green)}#{numberToHexString(@blue)}"

-  # Esssential: Returns a {String} in the form `'rgba(25, 50, 75, .9)'`
+  # Esssential: Returns a {String} in the form `'rgba(25, 50, 75, .9)'`.
   toRGBAString: ->
     "rgba(#{@red}, #{@green}, #{@blue}, #{@alpha})"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -43,7 +43,7 @@

     parsedColor = null

-    parsedColor =  parseRGB(color) || parseHex(color)
+    parsedColor = parseHex(color) || parseRGB(color) || lookup[color]

     alpha = parsedColor[3]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -1601,10 +1601,7 @@

     parsedColor = null

-    if lookup[color.toLowerCase().replace(/ /g, "")]
-      parsedColor = Color(lookup[color.toLowerCase().replace(/ /g, "")])
-    else
-      parsedColor = parseRGB(color)
+    parsedColor = parseRGB(color)

     alpha = parsedColor[3]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -1671,5 +1671,7 @@
   ]

   names.each (element) ->
-    lookup[element[1].toLowerCase().replace(/ /g, "")] = parseHex(element[0])
+    lookup[element[1].toLowerCase().replace(/ /g, "")] = parseHex(element[0])
+
+  console.log lookup
 )()


remove the redundant postfix of the event

--- library/gearbox/exchange.coffee
+++ library/gearbox/exchange.coffee
@@ -76,7 +76,7 @@
         assert this.$features[designate] = parameters
         logger.info n, identify.green, sfeature.green
         this.emit "feature", designate, parameters...
-        @broadcast "exchange-feature-x", arguments...
+        @broadcast "exchange-feature", arguments...

     # Declare the aligning of the current featured service. This
     # means the absolute numeric value of the service ordering as


Persist the doc, if there is one

Persist the doc, if there is one

--- library/nucleus/document.coffee
+++ library/nucleus/document.coffee
@@ -42,7 +42,7 @@
     validated = _.isFunction descriptor
     missing = "The #{descriptor} is not a descriptor"
     throw new Error missing unless validated
-    method.document = new Document
+    method.document ?= new Document
     method.document.descriptor = descriptor

 # Traverse all of the services that are registered with the router


fix bug in notification.coffee

fix bug in notification.coffee

--- app/assets/javascripts/application/services/notification.coffee
+++ app/assets/javascripts/application/services/notification.coffee
@@ -7,7 +7,7 @@
     event_name   = @element.data('event')

     channel = if AHP.channel(channel_name)
-                APH.channel(channel_name)
+                AHP.channel(channel_name)
               else
                 AHP.subscribe(channel_name)


Updates users

Updates users

--- coffee/game/users/source.coffee
+++ coffee/game/users/source.coffee
@@ -11,7 +11,7 @@
     profile_picture_src: 'de_niro.jpg',
     location: 'NYC',
     music: 'Eric Clapton, Rolling Stones',
-    likes: 'Pasta, taxis, Italy <3'
+    likes: 'Pasta, taxis, my wife'
   },
   {
     name: 'Ewan',


bootstrap-custom: removed unused functions

--- app/assets/javascripts/bootstrap-custom.js.coffee
+++ app/assets/javascripts/bootstrap-custom.js.coffee
@@ -1,7 +1,3 @@
-$ ->
-  $(".tabs").tabs()
-$ ->
-  $("a[rel=popover]").popover offset: 10
 $ ->
   $(".topbar-wrapper").dropdown()
 $ ->


OX-129 set branch point on initialization

--- app/assets/javascripts/geometries/skeleton.coffee
+++ app/assets/javascripts/geometries/skeleton.coffee
@@ -134,6 +134,8 @@
             @curIndex[index]++
         @routes[index].geometry.verticesNeedUpdate = true
         @nodes[index].geometry.verticesNeedUpdate = true
+    for branchPoint in @model.Route.branchStack
+      @setBranchPoint(true, branchPoint.id)
     @setActiveNode()

   setActiveNode : =>
@@ -160,12 +162,16 @@
       @activeNode.visible = false
     @flycam.hasChanged = true

-  setBranchPoint : (isBranchPoint) ->
+  setBranchPoint : (isBranchPoint, nodeID) ->
     colorActive = if isBranchPoint then COLOR_BRANCH_ACTIVE else COLOR_ACTIVE
     colorNormal = if isBranchPoint then COLOR_BRANCH else COLOR_NORMAL
-    @activeNode.material.color.setHex(colorActive)
-    if @activeNodeSphere
-      @activeNodeSphere.material.color.setHex(colorNormal)
+    if not nodeID? or nodeID == @model.Route.getActiveNodeId()
+      @activeNode.material.color.setHex(colorActive)
+      if @activeNodeSphere
+        @activeNodeSphere.material.color.setHex(colorNormal)
+    else
+      sphere = @getSphereFromId(nodeID)
+      sphere.material.color.setHex(colorNormal)
     @flycam.hasChanged = true

   setNodeRadius : (value) ->


Prevent ad_manager polling when the cards have been removed

--- app/assets/javascripts/lib/core/ad_manager.coffee
+++ app/assets/javascripts/lib/core/ad_manager.coffee
@@ -11,9 +11,12 @@

     adUnits: {}
     firstLoaded: false
+    polls: {}
+

     init : () ->

+      @addEvents()
       # GPT Boilerplate code
       window.googletag = window.googletag || {}
       googletag.cmd = googletag.cmd || []
@@ -110,7 +113,13 @@
         # Kick off polling where needed.
         for elId of toPoll
           if toPoll.hasOwnProperty(elId)
-            adManager.poll document.getElementById(elId), toPoll[elId]
+            adManager.poll document.getElementById(elId), toPoll[elId], elId
+
+
+    addEvents : ->
+      $("#js-card-holder").on ":cards/request :page/request", ->
+        for poll of adManager.polls
+          clearInterval(adManager.polls[poll])

     checkMpu : (adEl, iframe) ->
       if iframe.width() > 310
@@ -216,28 +225,26 @@
         e && e.preventDefault() && e.stopPropagation()

     # Abstract this polling functionality out for use in both checkMpu and hideEmpty
-    poll : (adEl, callback) ->
+    poll : (adEl, callback, id) ->
       count = 0
       maxPoll = 15000 # The maximum amount of milliseconds to poll for
       timeout = 250
-
       # Ugly but necessary. DOM Mutation events are deprecated, and there's not enough support for MutationObserver yet so we have to poll.
-      poll = window.setInterval ->
+      adManager.polls["poll" + id] = window.setInterval ->
         count++

         # Make sure we're not running this thing indefinitely
         if (count >= maxPoll/timeout)
-          window.clearInterval poll
+          window.clearInterval adManager.polls["poll" + id]
           return

         $(adEl).find('iframe').each ->
           iframe = $(this)
           contents = iframe.contents()
-
           # If something's been loaded into our ad element, we're good to go
           if adEl.style.display isnt 'none' and !!contents.find('body').html()

-            window.clearInterval poll
+            window.clearInterval adManager.polls["poll" + id]

             if not adManager.firstLoaded and window.lp and lp.fs and lp.fs.time
               window.lp.fs.time(
@@ -266,4 +273,4 @@
       iframe.style.width = "#{_config.unit[0]}px"
       iframe.style.height = "#{_config.unit[1]}px"
       s = document.getElementById(_target)
-      s.appendChild(iframe)+      s.appendChild(iframe)


force user card tooltips to close;

force user card tooltips to close;

force close if cursor is not known to be in a user card toolip and esc
was pressed. No need to distinguish between cards since only one
should be open at a time.

--- resources/assets/coffee/_classes/user-card.coffee
+++ resources/assets/coffee/_classes/user-card.coffee
@@ -22,6 +22,9 @@

   constructor: ->
     $(document).on 'mouseover', '.js-usercard', @onMouseOver
+    $(document).on 'keydown', @onKeyDown
+    $(document).on 'mouseenter', '.js-react--user-card-tooltip', @onMouseEnter
+    $(document).on 'mouseleave', '.js-react--user-card-tooltip', @onMouseLeave

   createTooltip: (el) =>
@@ -69,6 +72,18 @@
     $(el).qtip options

+  onKeyDown: (e) =>
+    $('.qtip--user-card').qtip('hide') if e.keyCode == 27 && !@inCard
+
+
+  onMouseEnter: () =>
+    @inCard = true
+
+
+  onMouseLeave: () =>
+    @inCard = false
+
+
   onMouseOver: (event) =>
     return if window.tooltipWithActiveMenu?
     # No user cards on mobile layout


DOMTokenList.replace() seems to be fairly "new"?

except it's been around for 2 years already...

--- resources/assets/coffee/_classes/user-card.coffee
+++ resources/assets/coffee/_classes/user-card.coffee
@@ -36,7 +36,8 @@

     # react should override the existing content after mounting
     card = $('#js-usercard__loading-template').children().clone()[0]
-    card.classList.replace 'js-react--user-card', 'js-react--user-card-tooltip'
+    card.classList.remove 'js-react--user-card'
+    card.classList.add 'js-react--user-card-tooltip'
     delete card.dataset.reactTurbolinksLoaded
     card.dataset.lookup = userId


No need to add bold for the class selector for removing events

--- app/assets/javascripts/lib/text_utility.js.coffee
+++ app/assets/javascripts/lib/text_utility.js.coffee
@@ -124,6 +124,6 @@
       gl.text._previousState = $thisTextarea.val()

   gl.text.removeListeners = () ->
-    $('js-md.btn-bold').off()
+    $('js-md').off()

 ) window


Send headers to s3.

Send headers to s3.

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -81,6 +81,10 @@
           $.ajax
             type: $uploadForm.data('callback-method')
             url: callback_url
+            headers: {
+              "Cache-Control":"max-age=315576000",
+              "access":"public_read"
+            }
             data: content
             beforeSend: ( xhr, settings )       -> $uploadForm.trigger( 'ajax:beforeSend', [xhr, settings] )
             complete:   ( xhr, status )         -> $uploadForm.trigger( 'ajax:complete', [xhr, status] )


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/approachable.coffee
+++ test/approachable.coffee
@@ -84,8 +84,7 @@
   4.times ->
     obj.update()

-  equals obj.I.cooldowns.health.value, 93, "health should continue of cool down when new cooldowns are added"
-
-  equals obj.I.cooldowns.turbo.value, 38, "turbo should cool down normally"
+  equals obj.I.health, 93, "health should continue of cool down when new cooldowns are added"
+  equals obj.I.turbo, 38, "turbo should cool down normally"

 module()


Scoping in debug log

Scoping in debug log

--- src/RedisPort.coffee
+++ src/RedisPort.coffee
@@ -201,7 +201,7 @@
 	#
 	pset: (p, data, cb) ->
 		p = @_cleanPath p
-		@client.set p, JSON.stringify(data), (error, result) ->
+		@client.set p, JSON.stringify(data), (error, result) =>
 			log.debug "#{@id}: set", p, result
 			cb? error


[Backport] Inject a missed dependencie

[Backport] Inject a missed dependencie

--- app/modules/history/history-lightbox/history-entry.directive.coffee
+++ app/modules/history/history-lightbox/history-entry.directive.coffee
@@ -17,10 +17,8 @@
 # File: comment.directive.coffee
 ###

-module = angular.module('taigaHistory')

 HistoryEntryDirective = (lightboxService) ->
-
     return {
         scope: {
             entry: "<"
@@ -28,4 +26,8 @@
         templateUrl:"history/history-lightbox/history-entry.html",
     }

-module.directive("tgHistoryEntry", HistoryEntryDirective)
+HistoryEntryDirective.$inject = [
+    "lightboxService"
+]
+
+angular.module('taigaHistory').directive("tgHistoryEntry", HistoryEntryDirective)


Camera: add set_projection_matrix

Camera: add set_projection_matrix

--- engine/camera.coffee
+++ engine/camera.coffee
@@ -94,6 +94,20 @@
             else
                 throw Error "Camera.sensor_fit must be
                     AUTO, HORIZONTAL, VERTICAL, COVER or CONTAIN."
+
+    set_projection_matrix: (matrix, adjust_aspect_ratio=false) ->
+        pm = mat4.copy @projection_matrix, matrix
+        if adjust_aspect_ratio
+            m_ratio = matrix.m00 / matrix.m05
+            # TODO: Use sensor_fit, we'll assume 'cover' for now
+            # TODO: it's expecting an inverse aspect ratio, probably
+            ratio_factor = m_ratio/@aspect_ratio
+            if ratio_factor > 1
+                mat4.scale pm, pm, vec3.new 1, ratio_factor, 1
+            else
+                mat4.scale pm, pm, vec3.new 1/ratio_factor, 1, 1
+        mat4.invert @projection_matrix_inv, @projection_matrix
+        @_calculate_culling_planes()

     update_projection: ->
         @_calculate_projection()


Render server errors

Render server errors

--- mixins/FormHandlerMixin.jsx.coffee
+++ mixins/FormHandlerMixin.jsx.coffee
@@ -14,9 +14,8 @@
   #       callback()
   #

-  # to review
-  # contextTypes:
-  #   errorResponseHandler: React.PropTypes.object.isRequired
+  contextTypes:
+    errorResponseHandler: React.PropTypes.object.isRequired

   propTypes:
     title:        React.PropTypes.string
@@ -61,14 +60,13 @@
         @onSubmit =>
           @setState submitting: false

-  # to review
-  # setServerErrors: (response) ->
-  #   if response.status == 422
-  #     if @isMounted()
-  #       @setState serverErrors: response.body, submitting: false
-  #   else
-  #     @setState(submitting: false)
-  #     @context.errorResponseHandler.resolve(response)
+  setServerErrors: (response) ->
+    if response.status == 422
+      if @isMounted()
+        @setState serverErrors: response.body, submitting: false
+    else
+      @setState(submitting: false)
+      @context.errorResponseHandler.resolve(response)

   clearForm: ->
     if @isMounted()


parse followable int (closes #196)

parse followable int (closes #196)

--- models/current_user.coffee
+++ models/current_user.coffee
@@ -53,21 +53,18 @@

   toggleFollow: (followable)->
     type = followable.get('base_class').toLowerCase() + 's'
-    id = followable.id
+    id = parseInt followable.id
     count = followable.get('follower_count')
-    console.log('count', count, followable)
     ids = @get("following_#{type}")

     isFollowing = _.include ids, id

     if isFollowing
       @set "following_#{type}", _.without @get("following_#{type}"), id
-      successEvent = "current_user:unfollowed"
       method = "DELETE"
       followable.set follower_count: count - 1
     else
       @push "following_#{type}", id
-      successEvent = "current_user:followed"
       method = "POST"
       followable.set follower_count: count + 1


fix graphite random walk

fix graphite random walk

--- examples/graphite_random_walk.coffee
+++ examples/graphite_random_walk.coffee
@@ -1,2 +1,3 @@
+colors = require 'colors'
 targets = (randomWalkFunction('test.' + i) for i in [1..9])
-graph targets, maxDataPoints: 200, d3_colors: lead.colors.brewer.Set3[11], lineWidth: 1.5, width: 1280, height: 720
+graph targets, maxDataPoints: 200, d3_colors: colors.brewer.Set3[11], lineWidth: 1.5, width: 1280, height: 720


Client: Activity: Feed:  Updated search input props

--- client/activity/lib/components/publicfeedpane/feedpanetabcontainer.coffee
+++ client/activity/lib/components/publicfeedpane/feedpanetabcontainer.coffee
@@ -57,7 +57,12 @@
         onClick={@bound 'showMostRecentMessages'}
         className={@getMostRecentTabClassNames()}>Most Recent</Link>
       <div>
-        <input className='FeedPane-searchInput' placeholder='Search...'/>
+        <input
+          value={ @state.query }
+          onChange={ @bound 'onChange' }
+          onKeyDown={ @bound 'onKeyDown' }
+          className='FeedPane-searchInput'
+          placeholder='Search...'/>
         <i className='FeedPane-searchIcon'/>
       </div>
     </div>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp.coffee
+++ src/clamp.coffee
@@ -1,7 +1,2 @@
 Clamp = (I={}, self) ->
-  Object.reverseMerge I,
-    x: 0
-    y: 0
-    width: 8
-    height: 8
-    collisionMargin: Point(0, 0)
+


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -468,14 +468,13 @@
     # modifies the color to be yellow
     magenta.hue(60)

-    # to see what they look like
-    for color, index in ['magenta']
-      canvas.drawRect
-        color: eval(color)
-        x: 20 + (60 * index)
-        y: 20 + (60 * index)
-        width: 60
-        height: 60
+    # to see what it looks like
+    canvas.drawRect
+      color: magenta
+      x: 50
+      y: 50
+      width: 80
+      height: 80
     </pre></code>

     @name hue


Лоадер должен всегда скрыватся

Лоадер должен всегда скрыватся

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -183,9 +183,9 @@

     fn = => after.call @, cid if cid is @current

+    @spinner.removeClass 'hide' if @spinner.hasClass 'hide'
+
     if not image.width or not image.height
-      @spinner.removeClass 'hide'
-
       element        = new Image()
       element.src    = image.source
       element.onload = =>


Remove initTouch method

Remove initTouch method

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -223,9 +223,6 @@

     return

-  initTouch: ->
-    return false
-
   ###
     Private methods
   ###


Added Opera 12.50 support

Added Opera 12.50 support

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -570,11 +570,30 @@
     prop = property.replace /-([a-z])/g, (g) -> g[1].toUpperCase()
     element.style[prop] = styles[property]

-translate = (element, s) ->
-  s = "translate3d(#{s}px, 0, 0)"
-  element.style.transform = s
-  element.style.WebkitTransform = s
-  element.style.msTransform = s
+translate = do ->
+  property = false
+  has3d = false
+
+  element = document.createElement 'div'
+  property = 'transform' if element.style.transform
+
+  prefixes = ['Webkit', 'Moz', 'O', 'ms']
+
+  if property is false
+    for prefix in prefixes when element.style["#{prefix}Transform"] isnt undefined
+      property = "#{prefix}Transform"
+
+  has3d = true if property is 'WebkitTransform'
+  if property isnt false
+    (element, s) ->
+      console.log property
+      console.log has3d
+      if has3d
+        s = "translate3d(#{s}px, 0, 0)"
+      else
+        s = "translateX(#{s}px)"
+      console.log s
+      element.style[property] = s


CodeMirror: small renaming

CodeMirror: small renaming

detach_ace → detach_cm

Signed-off-by: Thaddee Tyl <bf078aa284c515a655d62585a115c6ca630fa7b2@gmail.com>

--- src/client/cm.coffee
+++ src/client/cm.coffee
@@ -85,10 +85,10 @@
     suppress = false
     check()

-  @detach_ace = ->
+  @detach_cm = ->
     # TODO: can we remove the insert and delete event callbacks?
     editor.setOption 'onChange', null
-    delete @detach_ace
+    delete @detach_cm

   return


change constructor init; add docs

change constructor init; add docs

--- src/color/xyz.coffee
+++ src/color/xyz.coffee
@@ -1,42 +1,55 @@
+# XYZ and xyY color space implementation.

-class XYZ
-  constructor: (@X, @Y, @Z) ->
+# Imports
+# -------

-  # Convert XYZ color to xyY color
-  xyY: (T = new xyY) ->
+createConstructor = (require "../util/obj").createConstructor
+
+# XYZ prototype
+# -------------
+
+xyzPrototype =
+
+  # Convert the color from *XYZ* to *xyY* color space. Give optional *xyY* color `T` to store the result.
+  xyY: (T = do createXyy) ->
     T.x = @X / (@X + @Y + @Z)
     T.y = @Y / (@X + @Y + @Z)
     T.Y = @Y
     T

+  # Returns `true` if all components of the color are not `null` and not `undefined`.
   isDefined: ->
     @X? and @Y? and @Z?

+  # Returns a human readable string serialization of the color.
   toString: ->
     "X=#{@X}, Y=#{@Y}, Z=#{@Z}"

-class xyY
-  constructor: (@x, @y, @Y) ->
+# xyY prototype
+# -------------

-  # Convert xyY color to XYZ color
-  XYZ: (T = new XYZ) ->
+xyyPrototype =
+
+# Convert color from *xyY* to *XYZ* color space. Give optional *XYZ* color `T` to store the result.
+  XYZ: (T = do createXyz) ->
     T.X = @x * @Y / @y
     T.Y = @Y
     T.Z = (1 - @x - @y) * @Y / @y
     T

+  # Returns `true` if all components of the color are not `null` and not `undefined`.
   isDefined: ->
     @x? and @y? and @Y?

+  # Returns a human readable string serialization of the color.
   toString: ->
     "x=#{@x}, y=#{@y}, Y=#{@Y}"

-# public api
+# Public API
+# ----------
+
 module.exports =
-  XYZ: (X, Y, Z) ->
-    new XYZ X, Y, Z
-
-  xyY: (x, y, Y) ->
-    new xyY x, y, Y
+  XYZ: createXyz = createConstructor xyzPrototype, (@X, @Y, @Z) ->
+  xyY: createXyy = createConstructor xyyPrototype, (@x, @y, @Y) ->


改进对微信浏览器的判断方式

改进对微信浏览器的判断方式

--- src/core/base.coffee
+++ src/core/base.coffee
@@ -43,8 +43,9 @@
 		else
 			if (s = ua.match(/(android)\s+([\d.]+)/))
 				cola.os.android = parseFloat(s[1]) or -1
-				if(s = ua.match(/micromessenger\/([\d.]+)/)) then cola.browser.weixin = parseFloat(s[1]) or -1
 			else if (s = ua.match(/(windows)[\D]*([\d]+)/)) then cola.os.windows = parseFloat(s[1]) or -1
+
+		if (s = ua.match(/micromessenger\/([\d.]+)/)) then cola.browser.weixin = parseFloat(s[1]) or -1

 		cola.device.mobile = !!(`("ontouchstart" in window)` and ua.match(/(mobile)/))
 		cola.device.desktop = !cola.device.mobile


Fix: 言語ファイルのキャッシュを利用していなかったのを修正

Fix: 言語ファイルのキャッシュを利用していなかったのを修正

--- src/core/lang.coffee
+++ src/core/lang.coffee
@@ -12,7 +12,7 @@
  ###
 get = ->
   lang = config.get("lang")
-  if lang is _langname and !_table?
+  if lang is _langname and _table?
     return _table
   try
     _table = require("../lang/#{lang}.json")


Revert "Tests: Added deleteFile test."

Revert "Tests: Added deleteFile test."

This reverts commit 19c526bddb6429c9901e07ca3dedcbbf8009a476.

--- tests/src/ide/ide.coffee
+++ tests/src/ide/ide.coffee
@@ -35,22 +35,3 @@
     user = helpers.beginTest(browser)
     helpers.createFile(browser, user)
     browser.end()
-
-
-  deleteFile: (browser) ->
-
-    user         = helpers.beginTest(browser)
-    filename     = helpers.createFile(browser, user)
-    webPath      = '/home/' + user.username + '/Web'
-    fileSelector = "span[title='" + webPath + '/' + filename + "']"
-
-    browser
-      .waitForElementPresent     fileSelector, 20000
-      .click                     fileSelector
-      .click                     fileSelector + ' + .chevron'
-      .waitForElementVisible     'li.delete', 5000
-      .click                     'li.delete'
-      .waitForElementVisible     '.delete-container', 5000
-      .click                     '.delete-container button.clean-red'
-      .waitForElementNotPresent  fileSelector, 20000
-      .end()


Fix test

Fix test

--- test/test.coffee
+++ test/test.coffee
@@ -8,7 +8,6 @@
   it 'as callback', (done) ->
     parseJSON sampleJSON, done

-  it 'as promise', (done) ->
+  it 'as promise', ->
     parseJSON(sampleJSON).then (content) ->
       content.foo.should.be.equal 'bar'
-      done()


Added most stdlib function tests

Added most stdlib function tests

--- test/test.coffee
+++ test/test.coffee
@@ -16,6 +16,8 @@
   else
     [input.value]

+ip = (input) -> w.interpret w.parse input
+
 describe 'Whip', ->
   describe 'parser', ->
     it 'should lex a single atom', ->
@@ -46,88 +48,155 @@
   describe 'interpreter', ->
     describe 'lists', ->
       it 'should return list of strings', ->
-        ['hi', "mary", "rose"].should.eql w.interpret w.parse '("hi" "mary" "rose")'
+         ip('("hi" "mary" "rose")').should.eql ['hi', "mary", "rose"]

       it 'should return list of numbers', ->
-        [1, 2, 3].should.eql w.interpret w.parse '(1 2 3)'
+         ip('(1 2 3)').should.eql [1, 2, 3]

       it 'should return list of numbers in strings as strings', ->
-        ["1", "2", "3"].should.eql w.interpret w.parse '("1" "2" "3")'
+         ip('("1" "2" "3")').should.eql ["1", "2", "3"]

     describe 'atoms', ->
       it 'should return string atom', ->
-        'a'.should.eql w.interpret w.parse '"a"'
-
+         ip('"a"').should.eql 'a'

       it 'should return string with space atom', ->
-         'a b'.should.eql w.interpret w.parse '"a b"'
-
+          ip('"a b"').should.eql 'a b'

       it 'should return string with opening paren', ->
-        '(a'.should.eql w.interpret w.parse '"(a"'
-
+         ip('"(a"').should.eql '(a'

       it 'should return string with closing paren', ->
-        ')a'.should.eql w.interpret w.parse '")a"'
-
+         ip('")a"').should.eql ')a'

       it 'should return string with parens', ->
-        '(a)'.should.eql w.interpret w.parse '"(a)"'
-
+         ip('"(a)"').should.eql '(a)'

       it 'should return number atom', ->
-        123.should.eql w.interpret w.parse '123'
-
-
+         ip('123').should.eql 123

     describe 'invocation', ->
       it 'should run print on an int', ->
-        1.should.eql w.interpret w.parse '(print 1)'
-
+         ip('(print 1)').should.eql 1

       it 'should return first element of list', ->
-        1.should.eql w.interpret w.parse '(head (1 2 3))'
-
+         ip('(head (1 2 3))').should.eql 1

       it 'should return rest of list', ->
-        [2, 3].should.eql w.interpret w.parse '(tail (1 2 3))'
+         ip('(tail (1 2 3))').should.eql [2, 3]

     describe 'lambdas', ->
       it 'should return correct result when invoke lambda w no params', ->
-        [2].should.eql w.interpret w.parse '((lambda (tail (1 2))))'
-
+         ip('((lambda (tail (1 2))))').should.eql [2]

       it 'should return correct result for lambda that takes and returns arg', ->
-        1.should.eql w.interpret w.parse '((lambda (x) x) 1)'
-
+         ip('((lambda (x) x) 1)').should.eql 1

       it 'should return correct result for lambda that returns list of vars', ->
-        [1, 2].should.eql w.interpret w.parse '((lambda (x y) (x y)) 1 2)'
-
+         ip('((lambda (x y) (x y)) 1 2)').should.eql [1, 2]

       it 'should get correct result for lambda that returns list of lits + vars', ->
-        [0, 1, 2].should.eql w.interpret w.parse '((lambda (x y) (0 x y)) 1 2)'
-
+         ip('((lambda (x y) (0 x y)) 1 2)').should.eql [0, 1, 2]

       it 'should return correct result when invoke lambda w params', ->
-        1.should.eql w.interpret w.parse '((lambda (x) (head (x))) 1)'
+         ip('((lambda (x) (head (x))) 1)').should.eql 1

     describe 'let', ->
       it 'should eval inner expression w names bound', ->
-        [1, 2].should.eql w.interpret w.parse '(let ((x 1) (y 2)) (x y))'
-
+         ip('(let ((x 1) (y 2)) (x y))').should.eql [1, 2]

       it 'should not expose parallel bindings to each other', ->
         # Expecting undefined for y to be consistent with normal
         # identifier resolution in littleLisp.
-        [1, undefined].should.eql w.interpret w.parse '(let ((x 1) (y x)) (x y))'
+         ip('(let ((x 1) (y x)) (x y))').should.eql [1, undefined]

       it 'should accept empty binding list', ->
-        42.should.eql w.interpret w.parse '(let () 42)'
+         ip('(let () 42)').should.eql 42

     describe 'if', ->
       it 'should choose the right branch', ->
-        42.should.eql w.interpret w.parse '(if true 42 4711)'
-        4711.should.eql w.interpret w.parse '(if false 42 4711)'
+         ip('(if true 42 4711)').should.eql 42
+         ip('(if false 42 4711)').should.eql 4711
+
+    describe 'dictionaries', ->
+      it 'should generate dictionary with keys and values', ->
+        ip('{key:42 "other key":"value"}').should.eql {"key":42, "other key":"value"}
+
+describe 'stdlib', ->
+  describe 'arithmetic', ->
+    it 'should add two float lits', ->
+       ip('(+ 2.5 2.5)').should.eql 5
+
+    it 'should subtract two number lits', ->
+       ip('(- 10 5)').should.eql 5
+
+    it 'should multiply two number lits', ->
+       ip('(* 5 5)').should.eql 25
+
+    it 'should divide two number lits', ->
+       ip('(/ 25 5)').should.eql 5
+
+    it 'should modulo two number lits', ->
+       ip('(% 19 7)').should.eql 5
+
+    it 'should compare greatness of two number lits', ->
+      ip('(> 5 1)').should.be.true
+      ip('(< 5 1)').should.be.false
+
+    describe 'type coercion', ->
+      it 'should concatenate two strings', ->
+         ip('(+ "foo " "bar")').should.eql 'foo bar'
+
+      it 'should generate javascript-ruled string', ->
+         ip('(+ 5 "5")').should.eql '55'
+
+  describe 'boolean expressions', ->
+    it 'should evaluate not false to true', ->
+      ip('(! false)').should.be.true
+
+    it 'should evaluate true and false to false', ->
+      ip('(& true false)').should.not.be.true
+      ip('(& false true)').should.not.be.true
+
+    it 'should evaluate true or false to true', ->
+      ip('(^ true false)').should.be.true
+      ip('(^ false true)').should.be.true
+
+    describe 'equalities', ->
+      it 'should compare numbers as equal', ->
+        ip('(= 42 42)').should.be.true
+
+      it 'should compare strings as equal', ->
+        ip('(= "foo" "foo")').should.be.true
+
+      # TODO: Add support for comparing lists
+      it 'should not compare lists as equal', ->
+        ip('(= (1) (1))').should.not.be.true
+
+  describe 'indexing', ->
+    it 'should find value of list from index', ->
+      ip('(@ 1 (1 2 3))').should.eql 2
+
+    it 'should find attribute of dict from index', ->
+      ip('(@ "key1" {"key0":"foo" "key1":42 key2:false})').should.eql 42
+
+    it 'should find letter of string from index', ->
+      ip('(@ 3 "foobar")').should.eql 'b'
+
+    describe 'lengths', ->
+      it 'should get length of string', ->
+        ip('(_ "foobar")').should.eql 6
+
+      it 'should get length of list', ->
+        ip('(_ (1 2 3))').should.eql 3
+
+      it 'should not any get length of dict', ->
+        should.not.exist ip('(_ {0:0 1:1})')
+
+    describe 'slicing', ->
+      it 'should slice range list', ->
+        ip('(\\ (.. 1 100) -5)').should.eql [96, 97, 98, 99, 100]
+
+  # TODO: Haskell functions, map, and reduce functions


Update tests to include new livedb client param for wrapper.query*

--- test/test.coffee
+++ test/test.coffee
@@ -285,14 +285,14 @@
       opts = {poll}

       it 'returns a result it already applies to', (done) -> @create {x:5}, =>
-        @collection.query {'x':5}, opts, (err, emitter) =>
+        @collection.query @client, {'x':5}, opts, (err, emitter) =>
           expected = [docName:@docName, data:{x:5}, type:otTypes.json0.uri, v:1, c:@cName]
           assert.deepEqual emitter.data, expected
           emitter.destroy()
           done()

       it 'gets an empty result set when you query something with no results', (done) ->
-        @collection.query {'xyz':123}, opts, (err, emitter) ->
+        @collection.query @client, {'xyz':123}, opts, (err, emitter) ->
           assert.deepEqual emitter.data, []
           emitter.on 'diff', -> throw new Error 'should not have added results'

@@ -301,7 +301,7 @@
             done()

       it 'adds an element when it matches', (done) ->
-        @collection.query {'x':5}, opts, (err, emitter) =>
+        @collection.query @client, {'x':5}, opts, (err, emitter) =>
           emitter.on 'diff', (diff) =>
             assert.deepEqual diff, [
               index: 0
@@ -316,7 +316,7 @@

       #console.log util.inspect diff, colors:yes, depth:null
       it 'remove an element that no longer matches', (done) -> @create {x:5}, =>
-        @collection.query {'x':5}, opts, (err, emitter) =>
+        @collection.query @client, {'x':5}, opts, (err, emitter) =>
           emitter.on 'diff', (diff) =>
             assert.deepEqual diff, [type:'remove', index:0, howMany:1]

@@ -332,7 +332,7 @@
           @collection.submit @docName, v:1, op:[{p:['x'], od:5, oi:6}], (err, v) =>

       it 'removes deleted elements', (done) -> @create {x:5}, =>
-        @collection.query {'x':5}, opts, (err, emitter) =>
+        @collection.query @client, {'x':5}, opts, (err, emitter) =>
           assert.strictEqual emitter.data.length, 1

           emitter.on 'diff', (diff) =>
@@ -346,7 +346,7 @@
             throw new Error err if err

       it 'does not emit receive events to a destroyed query', (done) ->
-        @collection.query {'x':5}, opts, (err, emitter) =>
+        @collection.query @client, {'x':5}, opts, (err, emitter) =>
           emitter.on 'diff', -> throw new Error 'add called after destroy'

           emitter.destroy()
@@ -363,19 +363,19 @@
         @create2 '_p1', {x:5, i:1}, => @create2 '_p2', {x:5, i:2}, => @create2 '_p3', {x:5, i:3}, => callback()

       it 'respects limit queries', (done) ->
-        @collection.query {$query:{'x':5}, $orderby:{'i':1}, $limit:1}, {poll:true}, (err, emitter) ->
+        @collection.query @client, {$query:{'x':5}, $orderby:{'i':1}, $limit:1}, {poll:true}, (err, emitter) ->
           assert.strictEqual emitter.data.length, 1
           assert.strictEqual emitter.data[0].docName, '_p1'
           done()

       it 'respects skips', (done) ->
-        @collection.query {$query:{'x':5}, $orderby:{'i':1}, $limit:1, $skip:1}, {poll:true}, (err, emitter) ->
+        @collection.query @client, {$query:{'x':5}, $orderby:{'i':1}, $limit:1, $skip:1}, {poll:true}, (err, emitter) ->
           assert.strictEqual emitter.data.length, 1
           assert.strictEqual emitter.data[0].docName, '_p2'
           done()

       it 'will insert an element in the set', (done) ->
-        @collection.query {$query:{'x':5}, $orderby:{'i':1}}, {poll:true}, (err, emitter) =>
+        @collection.query @client, {$query:{'x':5}, $orderby:{'i':1}}, {poll:true}, (err, emitter) =>
           assert.equal emitter.data.length, 3

           emitter.on 'diff', (diff) =>
@@ -391,7 +391,7 @@
           @create2 '_p4', {x:5, i:1.5}

       it 'will remove an element from the set', (done) ->
-        @collection.query {$query:{'x':5}, $orderby:{'i':1}}, {poll:true}, (err, emitter) =>
+        @collection.query @client, {$query:{'x':5}, $orderby:{'i':1}}, {poll:true}, (err, emitter) =>
           emitter.once 'diff', (diff) ->
             assert.deepEqual diff, [type:'remove', index:0, howMany:1]
             emitter.once 'diff', (diff) ->
@@ -458,7 +458,7 @@

     describe 'extra data', ->
       it 'gets extra data in the initial result set', (done) ->
-        @testWrapper.query = (cName, query, callback) ->
+        @testWrapper.query = (client, cName, query, callback) ->
           callback null, {results:[], extra:{x:5}}

         @client.query 'internet', {x:5}, {backend:'test'}, (err, stream) =>
@@ -467,10 +467,10 @@

       it 'gets updated extra data when the result set changes', (done) ->
         x = 1
-        @testWrapper.query = (cName, query, callback) ->
+        @testWrapper.query = (client, cName, query, callback) ->
           callback null, {results:[], extra:{x:x++}}

-        @collection.query {x:5}, {backend:'test'}, (err, stream) =>
+        @collection.query @client, {x:5}, {backend:'test'}, (err, stream) =>
           assert.deepEqual stream.extra, {x:1}

           stream.on 'extra', (extra) ->


fix require

fix require

--- lib/Repl/ReplFormat.coffee
+++ lib/Repl/ReplFormat.coffee
@@ -8,6 +8,8 @@
         require conf_path
         # TODO: check cmd, args ... existe
         @cmd = cmd
+        console.log("format "+ cmd)
         @args = args
         @prompt = prompt
         @endSequence = endSequence
+        delete require.cache[require.resolve(conf_path)]


Allow welcome route to accept URL params

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -54,7 +54,7 @@
     /^\/reset(\/(\w|-)+)?$/
     /^\/confirm(\/(\w|-)+)?$/
     /^\/register(\/(\w|-)+)?$/
-    /^\/welcome(\/(\w|-)+)?$/
+    /^\/welcome((\/|\?)(.)+)?$/
   ]

   return if blacklist.isCurrentRouteBlacklisted()


Fixes welcome regex (closes #1257)

Fixes welcome regex (closes #1257)

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -54,7 +54,7 @@
     /^\/reset(\/\w+)?$/
     /^\/confirm(\/\w+)?$/
     /^\/register(\/\w+)?$/
-    /^\/welcome(\/\w+)?$/
+    /^\/welcome(\/.+)?$/
   ]

   return if blacklist.isCurrentRouteBlacklisted()


Remove subviews

Remove subviews

--- components/modalize/view.coffee
+++ components/modalize/view.coffee
@@ -42,5 +42,6 @@
     $('body').removeClass 'is-with-modal'

     @state 'close', =>
+      @subView?.remove?()
       @remove()
       callback?()


Repaired hashbang link in documentation

Repaired hashbang link in documentation

--- src/docco.coffee
+++ src/docco.coffee
@@ -166,7 +166,7 @@
   # Does the line begin with a comment?
   l.comment_matcher = new RegExp('^\\s*' + l.symbol + '\\s?')

-  # Ignore [hashbangs](http://en.wikipedia.org/wiki/Shebang_(Unix))
+  # Ignore [hashbangs](http://en.wikipedia.org/wiki/Shebang_(Unix\))
   # and interpolations...
   l.comment_filter = new RegExp('(^#![/]|^\\s*#\\{)')


Admins can see all queues

Admins can see all queues

--- controllers/queues.coffee
+++ controllers/queues.coffee
@@ -23,13 +23,14 @@
       socket.join queueKey

   index: (req, res, next) ->
-    # -- no params --
+    params =
+      include: includeParams
+    unless req.user.isAdmin
+      params.where = isPrivate: false

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


fix: Throw error when any additional path doesn't exist

Fix #378

--- src/dredd.coffee
+++ src/dredd.coffee
@@ -126,6 +126,12 @@
       glob globToExpand, (err, match) =>
         return globCallback(err) if err
         @configuration.files = @configuration.files.concat match
+        if match.length == 0
+          err = new Error("""\
+            API description document(s) not found on path: \
+            '#{globToExpand}'\
+          """)
+          return globCallback(err)
         globCallback()

     , (err) =>


updated use of example middleware to new format

--- new_project/server/rpc/demo.coffee
+++ new_project/server/rpc/demo.coffee
@@ -3,9 +3,9 @@
 # Define actions which can be called from the client using ss.rpc('demo.ACTIONNAME', param1, param2...)
 exports.actions = (req, res, ss) ->

-  # Example of pre-loading sessions into req.session using inbuilt middleware
-  # To use the 'example' custom middleware you'd append m.example.authenticated() to the array
+  # Example of pre-loading sessions into req.session using internal middleware
   req.use('session')
+  #req.use('example.authenticated') # Uncomment to use the middleware defined in server/middleware/example

   sendMessage: (message) ->
     if message && message.length > 0            # Check for blank messages


course: student/assignment note fields

course: student/assignment note fields

--- salvus/page/editor_course.cjsx
+++ salvus/page/editor_course.cjsx
@@ -24,17 +24,16 @@
 ###
 TODO:

-- [x] (1:30?) (0:23) add due date as a field to assignments:
-    - way to edit it (date selector...?)
-    - use it to sort assignments
+- [x] (1:30?) (0:45) add student/assignment note fields
+    - let enter/edit it in the students page
 - [ ] (0:15?) uniformly sort assignments everywhere, efficiently.
-- [ ] (1:30?) add student note field
-    - let enter/edit it in the students page
-    - show in the py and csv export
-- [ ] (2:00?) make everything look pretty
+- [ ] (2:00?) way to send an email to every student in the class (require some sort of premium account)
+- [ ] (6:00?) make everything look pretty
         - triangles for show/hide assignment info like for students
         - error messages in assignment page -- make hidable and truncate-able
         - escape to clear search boxes
+        - make textarea component that renders using markdown and submits using shift+enter...
+        - date picker for assignment due date

 - [ ] (3:00?) bug searching / testing / debugging
         - [ ] bug/race: when changing all titles/descriptions, some don't get changed.  I think this is because
@@ -45,8 +44,12 @@
         - [ ] (1:00?) (0:19+) fix bugs in opening directories in different projects using actions -- completely busted right now due to refactor of directory listing stuff....
 - [ ] (1:30?) #speed cache stuff/optimize for speed (?)
 - [ ] (0:45?) #unclear button in settings to update collaborators, titles, etc. on all student projects
+- [ ] (2:00?) automatically collect assignments on due date (?)

 DONE:
+- [x] (1:30?) (0:23) add due date as a field to assignments:
+    - way to edit it (date selector...?)
+    - use it to sort assignments
 - [x] (1:00?) (1:05) export all grades... to csv, excel file, python file, etc.?
 - [x] (0:30?) (0:03) course title should derive from filename first time.
 - [x] (1:00?) (0:55) grade: place to record the grade, display grade, etc.
@@ -318,6 +321,11 @@
             @configure_project_title(student_project_id, student_id)
             @configure_project_description(student_project_id, student_id)

+        set_student_note: (student, note) =>
+            student = store.get_student(student)
+            where      = {table:'students', student_id:student.get('student_id')}
+            @_update(set:{"note":note}, where:where)
+
         # Assignments
         add_assignment: (path) =>
             # Add an assignment to the course, which is defined by giving a directory in the project.
@@ -352,10 +360,16 @@
             grades[student.get('student_id')] = grade
             @_update(set:{grades:grades}, where:where)

-        set_due_date: (assignment, due_date) =>
+        _set_assignment_field: (assignment, name, val) =>
             assignment = store.get_assignment(assignment)
             where      = {table:'assignments', assignment_id:assignment.get('assignment_id')}
-            @_update(set:{due_date:due_date}, where:where)
+            @_update(set:{"#{name}":val}, where:where)
+
+        set_due_date: (assignment, due_date) =>
+            @_set_assignment_field(assignment, 'due_date', due_date)
+
+        set_assignment_note: (assignment, note) =>
+            @_set_assignment_field(assignment, 'note', note)

         # Copy the files for the given assignment_id from the given student to the
         # corresponding collection folder.
@@ -624,6 +638,9 @@
             # return student with given id if a string; otherwise, just return student (the input)
             if typeof(student)=='string' then @state.students?.get(student) else student

+        get_student_note: (student) =>
+            return @get_student(student)?.get('note')
+
         get_sorted_students: =>
             v = []
             @state.students.map (student, id) =>
@@ -637,6 +654,9 @@

         get_due_date: (assignment) =>
             return @get_assignment(assignment)?.get('due_date')
+
+        get_assignment_note: (assignment) =>
+            return @get_assignment(assignment)?.get('note')

         get_assignments: => @state.assignments

@@ -729,6 +749,8 @@
     getInitialState: ->
         more : false
         confirm_delete: false
+        editing_note: false
+        note        : ''

     render_student: ->
         if @state.more
@@ -827,11 +849,76 @@
                 </Col>
             </Row>

+    edit_note: (e) ->
+        e?.preventDefault()
+        @setState(editing_note:true, note:@props.student.get('note'))
+
+    save_note: (e) ->
+        e?.preventDefault()
+        @setState(editing_note:false)
+        @props.flux.getActions(@props.name).set_student_note(@props.student, @state.note)
+
+    render_note: ->
+        if @state.editing_note
+            <Row key='note'>
+                <Col xs=3>
+                    Notes about student -- (save):
+                </Col>
+                <Col xs=9>
+                    <form onSubmit={@save_note}>
+                        <Input ref="note_input"
+                            type = 'textarea'
+                            rows = 4
+                            placeholder = 'Notes about student (e.g., student id)'
+                            value = {@state.note}
+                            onChange ={=>@setState(note:@refs.note_input.getValue())}
+                            onKeyDown={(e)=>if e.keyCode==27 then @setState(editing_note:false)}
+                        />
+                    </form>
+                </Col>
+            </Row>
+
+        else
+            <Row key='note'>
+                <Col xs=3>
+                    Notes about student -- (edit):
+                </Col>
+                <Col xs=9>
+

+                        {@props.student.get('note')}
+

+                </Col>
+            </Row>
+
     render_more_info: ->
         # Info for each assignment about the student.
-        <Col key='more'>
-            {@render_assignments_info()}
-        </Col>
+        v = []
+        v.push <Row key='more'>
+                <Col md=12>
+                    {@render_assignments_info()}
+                </Col>
+            </Row>
+        v.push(@render_note())
+        return v
+
+    render_basic_info: ->
+        <Row key='basic'>
+            <Col md=4>
+

+                    {@render_student()}
+                    {@render_deleted()}
+

+            </Col>
+            <Col md=3>
+                {@render_project()}
+            </Col>
+            <Col md=3>
+                {@render_last_active()}
+            </Col>
+            <Col md=2>
+                {@render_delete_button()}
+            </Col>
+        </Row>

     render_deleted: ->
         if @props.student.get('deleted')
@@ -839,26 +926,10 @@

     render: ->
         <Row style={entry_style}>
-            <Col md=12 key='basic'>
-                <Row>
-                    <Col md=4>
-

-                            {@render_student()}
-                            {@render_deleted()}
-

-                    </Col>
-                    <Col md=3>
-                        {@render_project()}
-                    </Col>
-                    <Col md=3>
-                        {@render_last_active()}
-                    </Col>
-                    <Col md=2>
-                        {@render_delete_button()}
-                    </Col>
-                </Row>
-            </Col>
-            {@render_more_info() if @state.more}
+            <Col xs=12>
+                {@render_basic_info()}
+                {@render_more_info() if @state.more}
+            </Col>
         </Row>

 Students = rclass
@@ -1119,16 +1190,12 @@
         @props.flux.getActions(@props.name).set_grade(@props.assignment, @props.student, @state.grade)
         @setState(editing_grade:false)

-    grade_keydown: (e) ->
-        if e.keyCode == 27
-            @setState(grade:@props.grade, editing_grade:false)
-
     render_grade: ->
         if @state.editing_grade
             <form onSubmit={@save_grade}>
                 <Input autoFocus value={@state.grade} ref='grade_input' type='text' placeholder='Grade'
                        onChange={=>@setState(grade:@refs.grade_input.getValue())}
-                       onKeyDown={@grade_keydown}
+                       onKeyDown={(e)=>if e.keyCode == 27 then @setState(grade:@props.grade, editing_grade:false)}
                 />
             </form>
         else
@@ -1231,6 +1298,8 @@
         confirm_delete : false
         edit_due : false
         due_date : undefined
+        editing_note : false
+        note : ''

     edit_due_date: ->
         @setState(edit_due:true, due_date:@props.assignment.get('due_date'))
@@ -1239,10 +1308,6 @@
         e?.preventDefault()
         @props.flux.getActions(@props.name).set_due_date(@props.assignment, @state.due_date)
         @setState(edit_due:false)
-
-    due_date_keydown: (e) ->
-        if e.keyCode == 27
-            @setState(edit_due:false)

     render_due: ->
         if @state.edit_due
@@ -1250,7 +1315,7 @@
                 <Input autoFocus value={@state.due_date} ref='due_date_input'
                        type='text' placeholder='Due date'
                        onChange={=>@setState(due_date:@refs.due_date_input.getValue())}
-                       onKeyDown={@due_date_keydown}
+                       onKeyDown={(e)=>if e.keyCode==27 then @setState(edit_due:false)}
                 />
             </form>
         else
@@ -1259,6 +1324,47 @@
                 {"#{due_date}"} <Button onClick={=>@edit_due_date()}>Edit</Button>

+    edit_note: (e) ->
+        e?.preventDefault()
+        @setState(editing_note:true, note:@props.assignment.get('note'))
+
+    save_note: (e) ->
+        e?.preventDefault()
+        @setState(editing_note:false)
+        @props.flux.getActions(@props.name).set_assignment_note(@props.assignment, @state.note)
+
+    render_note: ->
+        if @state.editing_note
+            <Row key='note'>
+                <Col xs=3>
+                    Notes (not visible to students) -- save:
+                </Col>
+                <Col xs=9>
+                    <form onSubmit={@save_note}>
+                        <Input ref="note_input"
+                            type = 'textarea'
+                            rows = 4
+                            placeholder = 'Notes about this assignment'
+                            value = {@state.note}
+                            onChange ={=>@setState(note:@refs.note_input.getValue())}
+                            onKeyDown={(e)=>if e.keyCode==27 then @setState(editing_note:false)}
+                        />
+                    </form>
+                </Col>
+            </Row>
+
+        else
+            <Row key='note'>
+                <Col xs=3>
+                    Notes (not visible to students) -- edit:
+                </Col>
+                <Col xs=9>
+

+                        {@props.assignment.get('note')}
+

+                </Col>
+            </Row>
+
     render_more_header: ->
         <Row>
             <Col md=1>
@@ -1287,6 +1393,7 @@
                     <StudentListForAssignment flux={@props.flux} name={@props.name}
                         assignment={@props.assignment} students={@props.students}
                         user_map={@props.user_map} />
+                    {@render_note()}
                 </Panel>
             </Col>
         </Row>
@@ -1696,7 +1803,7 @@
         content = "Student Name,"
         content += (assignment.get('path') for assignment in assignments).join(',') + '\n'
         for student in store.get_sorted_students()
-            grades = (store.get_grade(assignment, student) for assignment in assignments).join(',')
+            grades = ("'#{store.get_grade(assignment, student) ? ''}'" for assignment in assignments).join(',')
             line = store.get_student_name(student) + "," + grades
             content += line + '\n'
         @write_file(@path('csv'), content)
@@ -1712,7 +1819,7 @@

         content += 'students = [\n'
         for student in store.get_sorted_students()
-            grades = ((store.get_grade(assignment, student) ? 'None') for assignment in assignments).join(',')
+            grades = (("'#{store.get_grade(assignment, student) ? ''}'") for assignment in assignments).join(',')
             line = "    {'name':'#{store.get_student_name(student)}', 'grades':[#{grades}]},"
             content += line + '\n'
         content += ']\n'


removing the registration of the message_handler. this is done in the native code.

--- src/bridges/JSCoreBridge.coffee
+++ src/bridges/JSCoreBridge.coffee
@@ -19,8 +19,3 @@
     #JSON.stringify(message)
     #no need to turn into string for iOS
     message
-
-  bindNativeBridge: ->
-    # register the handler when the native is ready
-    window.__JSCoreBridgeImpl.registerHandler steroids.nativeBridge.message_handler
-    super.bindNativeBridge()


handleResponse will now inform that the error came from giphy-api when it is the case.

--- src/giphy.coffee
+++ src/giphy.coffee
@@ -207,7 +207,7 @@
   handleResponse: (state, err, uriCreator) ->
     @log 'handleResponse:', state
     if err
-      @error state.msg, err
+      @error state.msg, "giphy-api Error: #{err}"
     else
       state.uri = uriCreator.call this
       @sendResponse state


Re-enable delete key to remove nodes. In lockdown mode, delete will only remove relationships between nodes.

--- src/code/mixins/app-view.coffee
+++ src/code/mixins/app-view.coffee
@@ -22,13 +22,16 @@

   addDeleteKeyHandler: (add) ->
     if add
-      deleteFunction = @props.graphStore.deleteSelected.bind @props.graphStore
+      if AppSettingsStore.store.settings.lockdown
+        deleteFunction = @props.graphStore.removeSelectedLinks.bind @props.graphStore
+      else
+        deleteFunction = @props.graphStore.deleteSelected.bind @props.graphStore
+
       $(window).on 'keydown', (e) ->
         # 8 is backspace, 46 is delete
         if e.which in [8, 46] and not $(e.target).is('input, textarea')
           e.preventDefault()
-          if not AppSettingsStore.store.settings.lockdown
-            deleteFunction()
+          deleteFunction()
     else
       $(window).off 'keydown'


Shortcuts: a fixme notice for the shortcuts map

--- client/ide/views/shortcutsview/shortcutsview.coffee
+++ client/ide/views/shortcutsview/shortcutsview.coffee
@@ -31,6 +31,10 @@
       @addSubView container

   getShortcuts: ->
+
+    # FIXME
+    # get these from KD.appClasses.IDE.options.keyBindings
+    # and sanitize them and keep this only in one place - SY
     'Workspace Shortcuts':
       'Split vertically'       : 'Ctrl-Alt-V'
       'Split horizontally'     : 'Ctrl-Alt-H'


variablesview: set readonly and listen events after editorView ready

--- client/stack-editor/lib/editor/variablesview.coffee
+++ client/stack-editor/lib/editor/variablesview.coffee
@@ -3,11 +3,11 @@
 remote                     = require 'app/remote'
 requirementsParser         = require 'app/util/stacks/requirementsparser'
 { yamlToJson, jsonToYaml } = require 'app/util/stacks/yamlutils'
+VariablesEditorView        = require './variableseditorview'
+isMine  = require 'app/util/isMine'
+isAdmin = require 'app/util/isAdmin'

-StackBaseEditorTabView     = require './stackbaseeditortabview'
-VariablesEditorView        = require './variableseditorview'
-
-module.exports = class VariablesView extends StackBaseEditorTabView
+module.exports = class VariablesView extends kd.View

   STATES    =
     INITIAL : 'You can define your custom variables,
@@ -40,12 +40,27 @@

     @setState 'INITIAL'

+    { stackTemplate } = @getData()
+    isMine = isAdmin() or isMine(stackTemplate)
+
     @editorView.ready =>
       @checkVariableChanges()
       @followVariableChanges()

       @checkStackTemplateChanges()
       @followStackTemplateChanges()
+      @setReadOnly()  unless isMine
+      @listenEditorEvents()
+
+
+  listenEditorEvents: ->
+    @on 'FocusToEditor', => @editorView.setFocus yes
+
+
+  setReadOnly: ->
+
+    @setClass 'isntMine'
+    @editorView.aceView.ace.editor.setReadOnly yes

   handleDataChange: ->


Use a forced update on resize

Use a forced update on resize

Given the amount of context leading to a resize and the work it would
imply to make detect and apply partial changes in these cases, it’s
safer to just redraw the whole canvas.

--- lib/minimap-element.coffee
+++ lib/minimap-element.coffee
@@ -151,7 +151,7 @@

     if @width isnt @clientWidth or @height isnt @clientHeight
       @measureHeightAndWidth()
-      @requestUpdate()
+      @requestForcedUpdate()

   measureHeightAndWidth: ->
     @height = @clientHeight


Change customizeProcessStdout to customizeConsole for List reporter

It had to customizeProcessStdout methods in the same class. Changing it to customizeConsole like the Spec reporter.

--- lib/mocha-phantomjs.coffee
+++ lib/mocha-phantomjs.coffee
@@ -189,7 +189,7 @@
       return if string is process.cursor.deleteLine or string is process.cursor.beginningOfLine
       console.log string

-  customizeProcessStdout: (options) ->
+  customizeConsole: (options) ->
     process.cursor.CRMatcher = /\u001b\[90m.*:\s\u001b\[0m/
     process.cursor.CRCleaner = (string) -> process.cursor.up + process.cursor.deleteLine + string + process.cursor.up + process.cursor.up
     origLog = console.log


cleanup, consistency

cleanup, consistency

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -19,10 +19,7 @@
       debug "#=== Start at #{new Date().toISOString()}"

     @withLock =>
-      # If we've started in visual-mode, and pushed operation with select method,
-      # set implicit Select operator as operator to modify selection with select
-      # method on target. Here target is Motion or TextObject.
-      # So use implicit Select operator as operator.
+      # Use implicit Select operator as operator.
       if @vimState.isVisualMode() and _.isFunction(op.select)
         debug "push INPLICIT Operators.Select"
         @stack.push(new Select(@vimState))
@@ -30,8 +27,7 @@
       debug "pushing <#{op.getKind()}>"
       @stack.push op

-      # If we've received an operator in visual mode, set inplict CurrentSelection TextObject
-      # as a target of operator.
+      # Operate on implicit CurrentSelection TextObject.
       if @vimState.isVisualMode() and op.isOperator()
         debug "push INPLICIT TextObjects.CurrentSelection"
         @stack.push(new CurrentSelection(@vimState))
@@ -41,10 +37,10 @@
       @vimState.ensureCursorIsWithinLine(cursor)

   process: ->
-    debug "-> @process(): start"
+    debug '-> @process(): start'

     if @stack.length > 2
-      throw "Must not happen"
+      throw 'Must not happen'

     if @stack.length is 2
       try
@@ -62,14 +58,14 @@
     unless @peekTop().isComplete()
       if @vimState.isNormalMode() and @peekTop().isOperator?()
         @inspect()
-        debug "-> @process(): activating: operator-pending-mode"
+        debug '-> @process(): activating: operator-pending-mode'
         @vimState.activateOperatorPendingMode()
       else
         debug "-> @process(): return: not <#{@peekTop().getKind()}>.isComplete()"
       return

     @inspect()
-    debug "-> @pop()"
+    debug '-> @pop()'
     op = @pop()
     @vimState.history.unshift(op) if op.isRecordable()
     debug " -> <#{op.getKind()}>.execute()"


fix query method

fix query method

--- src/index.coffee
+++ src/index.coffee
@@ -39,9 +39,14 @@

             @query: (opts, cb) ->
                 console.log("querying")
+
+                processData = (data) ->
+                    models = (_.extend(new InvisibleModel(), d) for d in data)
+                    cb(models)
+
                 http.request(
                         {path: "/invisible/#{@_modelName}/", method: "GET"},
-                        handleResponse(cb)).end()
+                        handleResponse(processData)).end()

             save: () ->
                 console.log("saving")


Add the `_send` method so tests can call the root request handler

--- src/index.coffee
+++ src/index.coffee
@@ -132,6 +132,10 @@
     if @_server.listening
     then callback()
     else @_server.once "listening", callback
+
+  # Used for testing.
+  _send: (req, res) ->
+    onRequest.call this, req, res

 type.defineStatics


fix arr for params

fix arr for params

--- src/index.coffee
+++ src/index.coffee
@@ -17,7 +17,7 @@
 Clay = (method, params, cb = -> null) ->
   if typeof params is 'function'
     cb = params
-    params = null
+    params = []

   # Normalize params to an array if passed an object
   if params? and Object::toString.call(params) is '[object Object]'


Add raw text wrapper

Add raw text wrapper

--- src/index.coffee
+++ src/index.coffee
@@ -46,7 +46,7 @@

   switch node.type
     # No child
-    when 'text'           then node.value
+    when 'text'           then rawTextWrapper node.value
     when 'escape'         then '\\'
     when 'break'          then $ 'br', {key}
     when 'horizontalRule' then $ 'hr', {key}
@@ -122,8 +122,12 @@
     else
       throw node.type + ' is unsuppoted node type. report to https://github.com/mizchi/md2react/issues'

+htmlWrapperComponent = null
+rawTextWrapper = null
+
 module.exports = (raw, options = {}) ->
   sanitize = options.sanitize ? true
+  rawValueWrapper = options.rawTextWrapper ? (text) -> text
   highlight = options.highlight ? (code, lang, key) ->
     $ 'pre', {key, className: 'code'}, [
       $ 'code', {key: key+'-_inner-code'}, code


Take out debugging dump

Take out debugging dump

--- src/index.coffee
+++ src/index.coffee
@@ -170,7 +170,6 @@
       scored = _.sortByOrder scored, ["score"], [false]
       # Take only top 20
       scored = scored.slice 0, 20
-      console.dir _.keys(scored[0])
       scored = _.map scored, (post) ->
         post.user.image_url = _.pick post.user.image_url, ["40px"]
         post.user = _.pick post.user, ["profile_url", "name", "image_url"]


Fixed bug

Fixed bug

--- src/index.coffee
+++ src/index.coffee
@@ -55,7 +55,7 @@
   #Get Release Type from options or by asking
   .then ->
     unless @options.release_type
-      askReleaseType().then (release_type) ->
+      askReleaseType().then (release_type) =>
         @options.release_type = release_type

   #Get Current Version


Fix Hipercard length

Fix Hipercard length

--- src/index.coffee
+++ src/index.coffee
@@ -23,7 +23,7 @@
       type: 'hipercard',
       pattern: /^(384100|384140|384160|60(?!11))/,
       format: defaultFormat,
-      length: [16],
+      length: [13, 16, 19],
       cvcLength: [3],
       luhn: true
   }


Validate the 'secure' option

Validate the 'secure' option

--- src/index.coffee
+++ src/index.coffee
@@ -15,6 +15,7 @@

 optionTypes =
   port: Number.Maybe
+  secure: Boolean.Maybe
   compress: Boolean.Maybe

 __DEV__ = process.env.NODE_ENV isnt "production"


Fix unnecessary space in objects

Fix unnecessary space in objects

--- src/index.coffee
+++ src/index.coffee
@@ -21,7 +21,7 @@
     stack.push [data, names]

     if toType(data) is 'function'
-      process.stdout.write toValue(data).value
+      process.stdout.write ' ' + toValue(data).value

     if level > 0
       process.stdout.write '\n'
@@ -32,11 +32,11 @@
       process.stdout.write theme.array.bracket '[' if data instanceof Array
       process.stdout.write theme.name(name)
       process.stdout.write theme.array.bracket ']' if data instanceof Array
-      process.stdout.write theme.dots(': ')
+      process.stdout.write theme.dots(':')

       stack.concat show value, level + 1, stack, names.slice(0).concat(name), max
   else
-    process.stdout.write toValue(data).value + '\n'
+    process.stdout.write ' ' + toValue(data).value + '\n'

   stack


use CIRCLE_PULL_REQUEST env var for Circle 2.0 compat (#16)

--- src/index.coffee
+++ src/index.coffee
@@ -53,7 +53,10 @@

         ENV.commitMessage = exec('git --no-pager log --pretty=format:"%s" -1').replace(/\\"/g, '\\\\"')
         # will either be a PR with a number or just a commit
-        ENV.prNumber = if process.env['CI_PULL_REQUEST'] then basename(process.env['CI_PULL_REQUEST']) else ''
+        # CI_PULL_REQUEST was deprecated in favor of CIRCLE_PULL_REQUEST in Circle 2.0
+        ENV.prNumber = if process.env['CIRCLE_PULL_REQUEST']
+            then basename(process.env['CIRCLE_PULL_REQUEST'])
+            else (if process.env['CI_PULL_REQUEST'] then basename(process.env['CI_PULL_REQUEST']) else '')
         ENV.githubDomain  = options.githubDomain ? 'api.github.com'
         return new Bot(ENV)

@@ -85,4 +88,4 @@
         else
             return @commentCommit(@env.sha1, body)

-module.exports = Bot+module.exports = Bot


test replace

test replace

--- src/index.coffee
+++ src/index.coffee
@@ -18,6 +18,7 @@

   '''
+  replace: true # default: true

 tick = ->
   app.decriment()


uniq: separate classes for sorted vs unsorted

--- lib/transforms/uniq.coffee
+++ lib/transforms/uniq.coffee
@@ -1,32 +1,37 @@
 _ = require 'underscore'
 {Transform} = require 'stream'

-class Uniq extends Transform
-  constructor: (@stream_opts, @sorted, @hash_fn) ->
+class SortedUniq extends Transform
+  constructor: (@stream_opts, @hash_fn) ->
     super @stream_opts
-    if _(@sorted).isFunction()
-      @hash_fn = @sorted
-      @sorted = false
-    else if _(@sorted).isObject() # Allow them to also pass in an options object
-      @hash_fn = @sorted.hash_fn
-      @sorted = @sorted.sorted
-    @hash_fn ?= String
-    @seen_map = {}
-    @seen_arr = []
+    @seen = []
     @seen_cnt = 0
   _transform: (obj, encoding, cb) =>
     hash = @hash_fn obj
-    send_obj = =>
-      @seen_cnt++
-      cb null, obj
-    if @sorted
-      return cb() if @seen_cnt and @seen_arr[@seen_cnt - 1] is hash
-      @seen_arr.push hash
-      send_obj()
-    else
-      return cb() if @seen_map[hash]
-      @seen_map[hash] = true
-      send_obj()
+    return cb() if @seen.length and _(@seen).last() is hash
+    @seen.push hash
+    cb null, obj
+
+class UnsortedUniq extends Transform
+  constructor: (@stream_opts, @hash_fn) ->
+    super @stream_opts
+    @seen = {}
+  _transform: (obj, encoding, cb) =>
+    hash = @hash_fn obj
+    return cb() if @seen[hash]
+    @seen[hash] = true
+    cb null, obj
+
+class Uniq
+  constructor: (stream_opts, sorted, hash_fn) ->
+    if _(sorted).isFunction() # For underscore-style arguments
+      hash_fn = sorted
+      sorted = false
+    else if _(sorted).isObject() # Allow them to also pass in an options object
+      hash_fn = sorted.hash_fn
+      sorted = sorted.sorted
+    hash_fn ?= String
+    return new (if sorted then SortedUniq else UnsortedUniq) stream_opts, hash_fn

 module.exports = (Understream) ->
   Understream.mixin Uniq, 'uniq'


corrige seletor da ref para seletor de sku

--- src/coffee/portal-sku-selector-product.coffee
+++ src/coffee/portal-sku-selector-product.coffee
@@ -10,11 +10,11 @@
 addSkuToCart = (sku) ->  true

 ref = $('.product-sku-selector-ref');
-
 ref.after('
');
 ref.remove();

 $(window).ready ->
+	ref = $('.product-sku-selector-ref');
 	ref.after('
');
 	ref.remove();


change mongo connection string temporaraly

--- services/docstore/config/settings.defaults.coffee
+++ services/docstore/config/settings.defaults.coffee
@@ -8,7 +8,7 @@
 			host: process.env['LISTEN_ADDRESS'] or "localhost"

 	mongo:
-		url: "mongodb://#{process.env['MONGO_HOST'] or '127.0.0.1'}/sharelatex"
+		url: "#{process.env['MONGO_CONNECTION_STRING'] or 'mongodb://127.0.0.1/sharelatex'}"

 	docstore:
 		healthCheck:


add comments

add comments

--- src/index.coffee
+++ src/index.coffee
@@ -60,4 +60,5 @@
 	box.rotation.x += 0.1
 	renderer.render scene, camera

+# start
 animate()


Support Dropbox preview links.

Support Dropbox preview links.

--- src/index.coffee
+++ src/index.coffee
@@ -190,11 +190,14 @@
                 $$.addToSaved(line)
                 $$.processSaved(line)

-    authenticationUrl = dbx.getAuthenticationUrl(location.href.split("#")[0], path)
-    authenticationLink = "#{authenticationUrl}"
+    makeAuthenticationUrl = (state) ->
+        dbx.getAuthenticationUrl(location.href.split("#")[0], state)
+
+    url = makeAuthenticationUrl(path)
+    authenticationLink = "#{url}"

     ensureDropboxToken = (state) =>
-        authenticationUrl = dbx.getAuthenticationUrl(location.href.split("#")[0], state)
+        authenticationUrl = makeAuthenticationUrl(state)

         promise = new Promise (resolve, reject) ->
             if not dbx.accessToken
@@ -222,27 +225,77 @@

             """

+
+    openDropboxChooserLink =
+        'Open Dropbox Chooser'
+
+    expose.launchDropBoxChooser = () ->
+        slog 'launchDropBoxChooser'
+        # Check access token first; this allows a smoother login flow UX
+        # (Likely reduce login screens to one time instead of two)
+        if dbx.accessToken
+            Dropbox.choose options =
+                # extensions: ['text', '.taskpaper', '.txt', '.ft']
+                success: (files) ->
+                    window.location.hash = files[0].link
+        else
+            window.location = makeAuthenticationUrl('CHOOSE')
+
+    loadDropboxPath = (path) ->
+        options =
+            path: path
+        dropboxApiReady = not dropboxAccessDenied
+        if dropboxApiReady
+            p1 = dbx.filesGetMetadata options
+            p1.then (metaData) =>
+                # source: http://stackoverflow.com/questions/190852
+                fileExtension = (fname) ->
+                    fname.substr((~-fname.lastIndexOf(".") >>> 0) + 2)
+
+                if metaData['.tag'] is 'folder'
+                    log "ERROR: #{path} is a folder. (Not supported)"
+                    history.pushState(null, null, "#BLANK")
+
+
+                    dropboxApiReady = false
+
+                textFileExtensions = ['', 'txt', 'taskpaper', 'ft']
+                fileExtension = fileExtension(metaData.name).toLowerCase()
+                if false and fileExtension not in textFileExtensions
+                    log "ERROR: File #{path} is not a text file (based on file extension)."
+                    history.pushState(null, null, "#BLANK")
+
+                    spy.fileExtension = fileExtension
+                    spy.name = metaData.name
+                    dropboxApiReady = false
+
+                if dropboxApiReady
+                    p2 = dbx.filesDownload options
+
+                    p2.then (fileData) =>
+                        reader = new FileReader()
+
+                        reader.addEventListener 'loadend', () ->
+                            # reader.result contains the contents of blob as a typed array
+                            stringResult = new TextDecoder('utf8').decode(reader.result)
+                            doc.setValue(stringResult)
+
+                        reader.readAsArrayBuffer(fileData.fileBlob)
+
+            p1.catch (error) =>
+                slog error
+                spy.error = error
+                switch error.status
+                    when 409  # Path not found
+                        log "ERROR: File #{path} not found on Dropbox."
+                        history.pushState(null, null, "#BLANK")
+                    else
+                        ensureDropboxToken(path).then () ->
+                            log "ERROR: #{error.error} (Status: #{error.status})"
+                            history.pushState(null, null, "#BLANK")
+
     if path is '/' or path is ''
         path = 'WELCOME'
-
-    openDropboxChooserLink =
-        'Open Dropbox Chooser'
-
-    expose.launchDropBoxChooser = () ->
-        slog 'launchDropBoxChooser'
-        # Get access token first so we can convert the link to a path later
-        ensureDropboxToken('CHOOSE').then () ->
-            Dropbox.choose options =
-                extensions: ['text', '.taskpaper', '.txt', '.ft']
-                success: (files) ->
-                    p1 = dbx.sharingGetSharedLinkFile options =
-                        url: files[0].link
-                    p1.then (fileData) ->
-                        window.location.hash = fileData.path_lower
-                    p1.catch (error) ->
-                        slog 'Error @sharingGetSharedLinkFile'
-                        slog error
-
     history.pushState(null, null, "##{path}")

     switch path
@@ -262,57 +315,22 @@

                     """
         else  # Dropbox
-            options =
-                path: path
-            dropboxApiReady = not dropboxAccessDenied
-            if dropboxApiReady
-                p1 = dbx.filesGetMetadata options
-                p1.then (metaData) =>
-                    # source: http://stackoverflow.com/questions/190852
-                    fileExtension = (fname) ->
-                        fname.substr((~-fname.lastIndexOf(".") >>> 0) + 2)
-
-                    if metaData['.tag'] is 'folder'
-                        log "ERROR: #{path} is a folder. (Not supported)"
+            # Shared link
+            if ///^https?://www.dropbox.com/s///.test(path)
+                p1 = dbx.sharingGetSharedLinkFile options =
+                    url: path
+                p1.then (fileData) ->
+                    window.location.hash = fileData.path_lower
+                p1.catch (error) ->
+                    slog 'Error @sharingGetSharedLinkFile'
+                    slog error
+                    ensureDropboxToken(path).then () ->
+                        log "ERROR: #{error.error} (Status: #{error.status})"
                         history.pushState(null, null, "#BLANK")
-
-
-                        dropboxApiReady = false
-
-                    textFileExtensions = ['', 'txt', 'taskpaper', 'ft']
-                    fileExtension = fileExtension(metaData.name).toLowerCase()
-                    if fileExtension not in textFileExtensions
-                        log "ERROR: File #{path} is not a text file (based on file extension)."
-                        history.pushState(null, null, "#BLANK")
-
-                        spy.fileExtension = fileExtension
-                        spy.name = metaData.name
-                        dropboxApiReady = false
-
-                    if dropboxApiReady
-                        p2 = dbx.filesDownload options
-
-                        p2.then (fileData) =>
-                            reader = new FileReader()
-
-                            reader.addEventListener 'loadend', () ->
-                                # reader.result contains the contents of blob as a typed array
-                                stringResult = new TextDecoder('utf8').decode(reader.result)
-                                doc.setValue(stringResult)
-
-                            reader.readAsArrayBuffer(fileData.fileBlob)
-
-                p1.catch (error) =>
-                    slog error
-                    spy.error = error
-                    switch error.status
-                        when 409  # Path not found
-                            log "ERROR: File #{path} not found on Dropbox."
-                            history.pushState(null, null, "#BLANK")
-                        else
-                            ensureDropboxToken(path).then () ->
-                                log "ERROR: #{error.error} (Status: #{error.status})"
-                                history.pushState(null, null, "#BLANK")
+            else
+                loadDropboxPath(path)
+
+


only inherit the schema (allow to add populate or limit fields in config)

--- src/index.coffee
+++ src/index.coffee
@@ -122,7 +122,7 @@
         for modelName, modelInfos of @config.schemas
             if (modelName is 'Basic')
                 throw "EurekaServer: 'Basic' is a reserved word and can not be used as model name"
-            models[modelName] = Model.extend(modelInfos)
+            models[modelName] = Model.extend({schema: modelInfos.schema})

         Database = require "archimedes/lib/#{@config.database.adapter}/database"
         db = new Database @config.database.config


Make mpc file before making resized versions

http://www.imagemagick.org/Usage/files/#mpc

--- src/index.coffee
+++ src/index.coffee
@@ -87,6 +87,16 @@

     return cb null, @meta

+Image.prototype.makeMpc = (cb) ->
+  @gm.write @src + '.mpc', (err) ->
+    return cb err if err
+
+    @gm = gm @src + '.mpc'
+
+    return cb null
+
+
+
 Image.prototype.resize = (version, cb) ->
   if version.original
     version.src = @src
@@ -150,9 +160,10 @@

 Image.prototype.exec = (cb) ->
   @getMeta (err) =>
-    return cb err if err
-    versions = JSON.parse(JSON.stringify(@config.versions))
-    async.mapLimit versions, @config.asyncLimit, @resizeAndUpload.bind(@), (err, versions) =>
+    @makeMpc (err) =>
       return cb err if err
-      return cb null, versions, @meta
+      versions = JSON.parse(JSON.stringify(@config.versions))
+      async.mapSeries versions, @resizeAndUpload.bind(@), (err, versions) =>
+        return cb err if err
+        return cb null, versions, @meta


Clarify comments.

Clarify comments.

--- src/index.coffee
+++ src/index.coffee
@@ -31,16 +31,16 @@

   {action, options} = tasks[name]

-  # If task action expects two arguments order is (options, callback).
+  # If task's action expects two arguments order is (options, callback).
   if action.length == 2
     action options, cb

-  # If task action expects a single argument named callback, cb, or done, or
+  # If task's action expects a single argument named callback, cb, or done, or
   # next it expects (callback) and no options object.
   else if /function \(callback|cb|done|next\)/.test action
     action cb

-  # Unspecified, or wants (options).
+  # Unspecified, or expects (options).
   else
     cb action options


cross-fade region: allow 0 fadeTime

cross-fade region: allow 0 fadeTime

--- core/app/backbone/factlink/cross_fade_region.coffee
+++ core/app/backbone/factlink/cross_fade_region.coffee
@@ -1,8 +1,6 @@
 Backbone.Factlink ||= {}

 class Backbone.Factlink.CrossFadeRegion extends Backbone.Marionette.Region
-
-  defaultFadeTime = 560

   crossFade: (newView) ->
     if @currentView
@@ -14,6 +12,10 @@

   resetFade: -> @$el?.stop().fadeOut(@_fadeTime(), => @reset())

-  _fadeTime: -> @options?.fadeTime || defaultFadeTime
+  _fadeTime: ->
+    if @options?.fadeTime?
+      @options.fadeTime
+    else
+      560

   onClose: -> @$el?.stop()


Update FileStoreHandlerTests

Update FileStoreHandlerTests

--- services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee
+++ services/web/test/UnitTests/coffee/FileStore/FileStoreHandlerTests.coffee
@@ -111,29 +111,56 @@

 	describe "getFileStream", ->
 		beforeEach ->
+			@query = {}
 			@request.returns(@readStream)

 		it "should get the stream with the correct params", (done)->
-			@handler.getFileStream @project_id, @file_id, {}, (err, stream)=>
+			@handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
 				@request.args[0][0].method.should.equal "get"
 				@request.args[0][0].uri.should.equal @handler._buildUrl()
 				done()

 		it "should get stream from request", (done)->
-			@handler.getFileStream @project_id, @file_id, {}, (err, stream)=>
+			@handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
 				stream.should.equal @readStream
 				done()

 		it "builds the correct url", (done)->
-			@handler.getFileStream @project_id, @file_id, {}, (err, stream)=>
+			@handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
 				@handler._buildUrl.calledWith(@project_id, @file_id).should.equal true
 				done()
-
+
 		it "should add an error handler", (done) ->
-			@handler.getFileStream @project_id, @file_id, {}, (err, stream)=>
+			@handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
 				stream.on.calledWith("error").should.equal true
 				done()
-
+
+		describe 'when range is specified in query', ->
+
+			beforeEach ->
+				@query = {'range': '0-10'}
+
+			it 'should add a range header', (done) ->
+				@handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
+					@request.callCount.should.equal 1
+					headers = @request.firstCall.args[0].headers
+					expect(headers).to.have.keys('range')
+					expect(headers['range']).to.equal 'bytes=0-10'
+					done()
+
+			describe 'when range is invalid', ->
+
+				['0-', '-100', 'one-two', 'nonsense'].forEach (r) =>
+
+					beforeEach ->
+						@query = {'range': "#{r}"}
+
+					it "should not add a range header for '#{r}'", (done) ->
+						@handler.getFileStream @project_id, @file_id, @query, (err, stream)=>
+							@request.callCount.should.equal 1
+							headers = @request.firstCall.args[0].headers
+							expect(headers).to.not.have.keys('range')
+							done()

 	describe "copyFile", ->


Uniquify IDs in SVG documents

Uniquify IDs in SVG documents

This prevents collisions when inserting multiple SVGs that use the same ID.

Closes GH-3

--- src/index.coffee
+++ src/index.coffee
@@ -34,9 +34,11 @@
       onLoad: PropTypes.func
       onError: PropTypes.func
       supportTest: PropTypes.func
+      uniquifyIDs: PropTypes.bool
     getDefaultProps: ->
       wrapper: span
       supportTest: isSupportedEnvironment
+      uniquifyIDs: true
     getInitialState: ->
       status: Status.PENDING
     componentDidMount: ->
@@ -77,9 +79,12 @@
     render: ->
       (@props.wrapper
         className: @getClassName()
-        dangerouslySetInnerHTML: __html: @state.loadedText if @state.loadedText
+        dangerouslySetInnerHTML: __html: @processSVG(@state.loadedText) if @state.loadedText
         @renderContents()
       )
+    processSVG: (svgText) ->
+      if @props.uniquifyIDs then uniquifyIDs svgText, getComponentID this
+      else svgText
     renderContents: ->
       switch @state.status
         when Status.UNSUPPORTED then @props.children
@@ -111,6 +116,60 @@

 isSupportedEnvironment = once ->
   (window?.XMLHttpRequest or window?.XDomainRequest) and supportsInlineSVG()
+
+# Replaces the IDs in the provided string with document-unique IDs. This is
+# obviously very naive, but is intended to provide a good-enough solution
+# without adding too much overhead as would, for example, parsing as XML. See
+# GH-3.
+
+uniquifyIDs = do ->
+  mkAttributePattern = (attr) -> "(?:(?:\\s|\\:)#{ attr })"
+  idPattern =
+    ///
+    (?:                                    # Match ID declarations:
+      (
+        #{ mkAttributePattern 'id' }       #   The start of an attribute named "id"
+      )
+      ="
+        ([^"]+)                            #   Our attribute value—match anything that isn't a quote.
+      "                                    #   The end of the attribute
+    )
+    |                                      # ...OR...
+    (?:                                    # Match IRI references:
+      (
+        #{ mkAttributePattern 'href' }
+        |
+        #{ mkAttributePattern 'role' }
+        |
+        #{ mkAttributePattern 'arcrole' }
+      )
+      ="                                   #   The start of an attribute
+        \#                                 #   A literal # indicating this is an IRI reference
+        ([^"]+)                            #   Our attribute value—match anything that isn't a quote.
+      "                                    #   The end of the attribute
+    )
+    |                                      # ...OR...
+    (?:                                    # Match FuncIRI references
+      ="                                   #   The start of an attribute
+        url\(                              #   The beginning of a FuncIRI
+          \#                               #   A literal # indicating this is an IRI reference
+          ([^\)]+)                         #   The id—match anything that isn't a closing paren
+        \)                                 #   The end of the FuncIRI
+      "                                    #   The end of the attribute
+    )
+    ///g
+  (svgText, svgID) ->
+    uniquifyID = (id) -> "#{ id }___#{ svgID }"
+    svgText.replace idPattern, (m, p1, p2, p3, p4, p5) ->
+      if p2 then "#{ p1 }=\"#{ uniquifyID p2 }\""
+      else if p3 then "=\"url(##{ uniquifyID p3 })\""
+      else if p5 then "#{ p4 }=\"##{ uniquifyID p4 }\""
+
+# Gets a unique ID for the given component, for use in the id attribute.
+
+getComponentID = do ->
+  clean = (str) -> str.toString().replace /[^a-zA-Z0-9]/g, '_'
+  (component) -> "#{ clean component._rootNodeID }__#{ clean component._mountDepth }"

 # Errors


fix menu where it was using an incorrect variable

--- modules/menu/main/index.coffee
+++ modules/menu/main/index.coffee
@@ -247,7 +247,7 @@
         ddNode = hx.select(node)
         if node.scrollTop < node.scrollHeight - node.clientHeight
           ddNode.style('width', ddNode.width() + hx.scrollbarSize() + 'px')
-          if @dropdown.alignments[2] is 'r'
+          if @dropdown._.alignments[2] is 'r'
             ddNode.style('left', Math.max(0, ddNode.box().left - hx.scrollbarSize()) + 'px')

     selection = hx.select(@selector)


make sure a builder is filled with at least all sync tags on builder.ready

--- src/dt-jquery.coffee
+++ src/dt-jquery.coffee
@@ -167,12 +167,11 @@
         el._jquery_manip    ?= cancelable_and_retrivable_callbacks()
         el._jquery_done     ?= deferred_callbacks()
         parent._jquery_done ?= deferred_callbacks()
-        for tag in [parent, el]
-            cb = tag._jquery_done.callback()
-            if tag is that.builder
-                cb()
-            else
-                tag.ready(cb)
+
+        ecb = el._jquery_done.callback()
+        pcb = parent._jquery_done.callback()
+        if el is el.builder then ecb() else el.ready(ecb)
+        if parent is parent.builder then pcb() else parent.ready(pcb)

         el._jquery_insert ?= singlton_callback el, ->
             that.fn.add(@parent, this)
@@ -183,8 +182,11 @@

         el._jquery_parent_done ?= singlton_callback el, ->
             return if removed this
-            if @parent is that.builder
-                if @parent._jquery_insert is true
+            if @parent is @parent.builder
+                bool = (not @parent.parent? or
+                           (@parent.parent is @parent.parent?.builder and # FIXME recursive?
+                            @parent.parent?._jquery_done is true))
+                if bool and @parent._jquery_insert is true
                     that.animation.push(@_jquery_insert)
                 else
                     @_jquery_insert?()
@@ -194,6 +196,7 @@
         parent._jquery_done(el._jquery_parent_done)

     onreplace: (oldtag, newtag) ->
+        return if removed(oldtag) or removed(newtag)
         newtag._jquery_parent_done ?= oldtag._jquery_parent_done
         newtag._jquery_insert      ?= oldtag._jquery_insert
         newtag._jquery_done        ?= oldtag._jquery_done