Hide mark details unless frame numbers match Fixes #3132

--- app/classifier/tasks/drawing/markings-renderer.cjsx
+++ app/classifier/tasks/drawing/markings-renderer.cjsx
@@ -53,6 +53,11 @@
                 continue

               toolDescription = taskDescription.tools[mark.tool]
+
+              if parseInt(mark.frame) is parseInt(@props.frame)
+                {details} = toolDescription
+              else
+                details = null

               toolEnv =
                 containerRect: @props.containerRect
@@ -65,7 +70,7 @@
               toolProps =
                 classification: @props.classification
                 mark: mark
-                details: toolDescription.details
+                details: details
                 color: toolDescription.color
                 size: toolDescription.size


Convert seconds to milliseconds for setTimeout + do not emit two errors when the handshake fails

--- drivers/javascript/src/net.coffee
+++ drivers/javascript/src/net.coffee
@@ -13,7 +13,7 @@
     DEFAULT_HOST: 'localhost'
     DEFAULT_PORT: 28015
     DEFAULT_AUTH_KEY: ''
-    DEFAULT_TIMEOUT: 20
+    DEFAULT_TIMEOUT: 20 # In seconds

     constructor: (host, callback) ->
         if typeof host is 'undefined'
@@ -275,12 +275,12 @@
                         @buffer = bufferSlice(@buffer, i)
                         status_str = String.fromCharCode.apply(null, new Uint8Array status_buf)

+                        handshake_complete = true
                         if status_str == "SUCCESS"
                             # We're good, finish setting up the connection
                             @rawSocket.on 'data', (buf) =>
                                 @_data(toArrayBuffer(buf))

-                            handshake_complete = true
                             @emit 'connect'
                             return
                         else
@@ -297,7 +297,7 @@
             if not handshake_complete
                 @rawSocket.destroy()
                 @emit 'error', new RqlDriverError "Handshake timedout"
-        ), @timeout)
+        ), @timeout*1000)

     close: () ->


(fix) Fix navigation problems.

(fix) Fix navigation problems.

--- app/scripts/pages/login/login.coffee
+++ app/scripts/pages/login/login.coffee
@@ -6,9 +6,6 @@

 class ViewModel
   constructor: ->
-    if app.isLoggedIn()
-      app.goto '/'
-
     @pin = ko.observable 'ABC-DEF'

     @samlData = ko.observable({})
@@ -36,7 +33,7 @@
       if app.user().pseudonym().indexOf('Nameless Nobody') == 0
         app.goto 'register'
       else
-        if localStorage.getItem('post-login-redirect') == '/login'
+        if localStorage.getItem('post-login-redirect') == 'login'
             app.goto '/'
         else
           app.goto (localStorage.getItem('post-login-redirect') || '/')


require that CU_BOX_SERVER is on https

require that CU_BOX_SERVER is on https

--- server/code/model/box.coffee
+++ server/code/model/box.coffee
@@ -91,7 +91,7 @@
   @endpoint: (server, name) ->
     proto_server = "https://#{server}"
     if process.env.CU_BOX_SERVER
-      proto_server = "http://#{process.env.CU_BOX_SERVER}"
+      proto_server = "https://#{process.env.CU_BOX_SERVER}"
     return "#{proto_server}/#{name}"

   @findAllByUser: (shortName, callback) ->


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -233,7 +233,7 @@

         path = pathGroup.selectAll("path.pie")
             #.data(data)
-            .data(pie(d))
+            .data(pie)

         path.enter().append("path")
             .attr("class", "pie")


Agent pie: test globalizing variables

Agent pie: test globalizing variables

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -3,7 +3,8 @@
     @accessor 'data'

     #@oldPieData: ''
-    #@container: ''
+    @container: ''
+    @pie: ''

     ready: ->
         # Remove any previous svg


Agent: agent pie font size adjustment

Agent: agent pie font size adjustment

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -180,7 +180,7 @@
         labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->
             p1.startAngle - p2.startAngle
         ))
-        labels.enter().append('text').attr('text-anchor', 'middle').attr('font-size', radiusi * .2 + 'px').attr('filter','url(#dropshadow)')
+        labels.enter().append('text').attr('text-anchor', 'middle').attr('font-size', radiusi * .3 + 'px').attr('filter','url(#dropshadow)')
         #labels.enter().append('text')
             #.attr('text-anchor', (d) ->
                 #rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -432,7 +432,7 @@
         return
     $(@node).children("svg").remove();
     width = 750
-    height = 350
+    height = 400
     radius = Math.min(width, height) / 2
     radiuso = 135
     radiusi = 90


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -41,7 +41,7 @@
           defs = svg.append('defs')
         marker = defs.select('marker#circ')
         if marker.empty()
-          defs.append('marker').attr('id', 'circ').attr('markerWidth', 12).attr('markerHeight', 12).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3
+          defs.append('marker').attr('id', 'circ').attr('markerWidth', 44).attr('markerHeight', 44).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3

         #Create/select <g> elements to hold the different types of graphics
         #and keep them in the correct drawing order


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -248,5 +248,10 @@
             else
                 'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy

+        totaltickets = 0
+        for pd in dataSet
+            totaltickets += pd.value
+        console.log totaltickets
+
         oldPieData = piedata
         return


Remove config

Remove config

--- atom.symlink/projects.cson
+++ atom.symlink/projects.cson
@@ -1,62 +0,0 @@
-dotfiles:
-  title: "dotfiles"
-  paths: [
-    "/home/local/ANT/daniimms/ssd/workspaces/dotfiles"
-    "/home/local/ANT/daniimms/workspaces/tyr-tools-old"
-    "/home/local/ANT/daniimms/workspaces/tyr-tools-private"
-  ]
-  icon: "icon-tools"
-"depot tools":
-  title: "depot tools"
-  icon: "icon-tools"
-  paths: [
-    "/home/local/ANT/daniimms/tools/depot_tools"
-  ]
-amazon_infra:
-  title: "amazon_infra"
-  icon: "icon-pulse"
-  paths: [
-    "/home/local/ANT/daniimms/ssd/workspaces/infra"
-  ]
-"chromium-linux":
-  title: "Chromium linux"
-  icon: "icon-globe"
-  paths: [
-    "/home/local/ANT/daniimms/ssd/workspaces/chromium-linux/src"
-  ]
-"chromium-android":
-  title: "Chromium android"
-  icon: "icon-globe"
-  paths: [
-    "/home/local/ANT/daniimms/ssd/workspaces/chromium-android/src"
-  ]
-"slate-android":
-  title: "slate-android"
-  icon: "icon-globe"
-  paths: [
-    "/home/local/ANT/daniimms/ssd/workspaces/slate-android/src"
-  ]
-"slate-android-amazon":
-  title: "slate-android → amazon"
-  icon: "icon-globe"
-  paths: [
-    "/home/local/ANT/daniimms/ssd/workspaces/slate-android/src/amazon"
-  ]
-"slate-linux":
-  title: "slate-linux"
-  icon: "icon-globe"
-  paths: [
-    "/home/local/ANT/daniimms/ssd/workspaces/slate-linux/src"
-  ]
-"slate-linux-amazon":
-  title: "slate-linux → amazon"
-  icon: "icon-globe"
-  paths: [
-    "/home/local/ANT/daniimms/ssd/workspaces/slate-linux/src/amazon"
-  ]
-"slate-linux-v44-amazon":
-  title: "slate-linux-v44 → amazon"
-  icon: "icon-globe"
-  paths: [
-    "/home/local/ANT/daniimms/ssd/workspaces/slate-linux-v44/src/amazon"
-  ]


 Merge branch 'master.removevm' into 'master'

vm: fix variable collision

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -626,8 +626,11 @@
         return callback err  if err

         subscription = freeSubscription = null
-        for subscription in subscriptions
-          freeSubscription = subscription  if "nosync" in subscription.tags
+        for item in subscriptions
+          if "nosync" in item.tags
+            freeSubscription = item
+          else
+            subscription = item

         kallback subscription or freeSubscription
     else


fix bug where tooltip would disappear if a child element gained focus

--- @client/tooltip.coffee
+++ @client/tooltip.coffee
@@ -29,7 +29,8 @@

 toggle_tooltip = (e) ->
-  if e.target.getAttribute('data-tooltip')
+  tooltip_el = e.target.closest('[data-tooltip]')
+  if tooltip_el?
     tooltip = fetch('tooltip')
     if tooltip.coords
       clear_tooltip()
@@ -37,17 +38,19 @@
       show_tooltip(e)

 show_tooltip = (e) ->
-  if e.target.getAttribute('data-tooltip')
-    name = e.target.getAttribute('data-tooltip')
+  tooltip_el = e.target.closest('[data-tooltip]')
+  if tooltip_el?
+    name = tooltip_el.getAttribute('data-tooltip')
     tooltip = fetch 'tooltip'
-    tooltip.coords = $(e.target).offset()
-    tooltip.coords.left += e.target.offsetWidth / 2
+    tooltip.coords = $(tooltip_el).offset()
+    tooltip.coords.left += tooltip_el.offsetWidth / 2
     tooltip.tip = name
     save tooltip
     e.preventDefault()

 hide_tooltip = (e) ->
-  if e.target.getAttribute('data-tooltip')
+  tooltip_el = e.target.closest('[data-tooltip]')
+  if tooltip_el?
     clear_tooltip()

 document.addEventListener "click", toggle_tooltip


fixed identation

fixed identation

--- API/src/scraper.coffee
+++ API/src/scraper.coffee
@@ -64,12 +64,12 @@
                     for url in urls
                         p.push get_details url
                     Promise.all p
-                .catch (err)
+                .catch (err) ->
                     r err
                 .then (values) ->
                     cache.set('tiere', values, 60*60*24)
                     f values
-                .catch err
+                .catch err ->
                     r err
         else
             f values
@@ -115,7 +115,7 @@
             res.json pets
         .catch (err) ->
             console.error err
-            res.status(500).json(err);
+            res.status(500).json(err)

 # Return a random pet
 app.get '/random', (req, res) ->
@@ -124,7 +124,7 @@
             res.json pet
         .catch (err) ->
             console.error err
-            res.status(500).json(err);
+            res.status(500).json(err)

 server = app.listen 3000, 'localhost', ->
     host = server.address().address


Fix missing local button variable in stop spying error

--- app/views/admin/MainAdminView.coffee
+++ app/views/admin/MainAdminView.coffee
@@ -61,6 +61,7 @@
   clearQueryParams: -> window.history.pushState({}, '', document.location.href.split('?')[0])

   stopSpying: ->
+    button = @$('#stop-spying-btn')
     me.stopSpying({
       success: -> document.location.reload()
       error: ->


Fix Date/DateTime bug.

Fix Date/DateTime bug.

--- Src/coffeescript/cql-execution/src/cql-exec.coffee
+++ Src/coffeescript/cql-execution/src/cql-exec.coffee
@@ -281,6 +281,10 @@

   exec: (ctx) ->
     new DT.DateTime(@execArgs(ctx)...)
+
+class DateTimeFunctionRef extends DateFunctionRef
+  constructor: (json) ->
+    super

 # Comparisons
 class Greater extends Expression


Adjust orders integration spec

Adjust orders integration spec

--- src/spec/integration/integration-order.spec.coffee
+++ src/spec/integration/integration-order.spec.coffee
@@ -16,33 +16,35 @@

     # get a tax category required for setting up shippingInfo (simply returning first found)
     @sync._client.taxCategories.save(taxCategoryMock())
-    .then (taxCategory) =>
-      @taxCategory = taxCategory
+    .then (result) =>
+      @taxCategory = result.body
       @sync._client.zones.save(zoneMock())
-    .then (zone) =>
-      @zone = zone
+    .then (result) =>
+      @zone = result.body
       @sync._client.shippingMethods.save(shippingMethodMock(@zone, @taxCategory))
-    .then (shippingMethod) =>
-      @shippingMethod = shippingMethod
+    .then (result) =>
+      @shippingMethod = result.body
       @sync._client.productTypes.save(productTypeMock())
-    .then (productType) =>
-      @productType = productType
-      @sync._client.products.save(productMock(productType))
-    .then (product) =>
-      @product = product
-      @sync._client.orders.import(orderMock(@shippingMethod, product, @taxCategory))
-    .then (order) =>
-      @order = order
-      done()
-    .fail (error) -> done(error)
+    .then (result) =>
+      @productType = result.body
+      @sync._client.products.save(productMock(@productType))
+    .then (result) =>
+      @product = result.body
+      @sync._client.orders.import(orderMock(@shippingMethod, @product, @taxCategory))
+    .then (result) =>
+      @order = result.body
+      done()
+    .fail (error) ->
+      console.log error
+      done(error)

   afterEach (done) ->

     # TODO: delete order (not supported by API yet)
     @sync._client.products.byId(@product.id).delete(@product.version)
-    .then (product) =>
+    .then (result) =>
       @sync._client.productTypes.byId(@productType.id).delete(@productType.version)
-    .then (productType) -> done()
+    .then (result) -> done()
     .fail (error) -> done(error)
     .fin ->
       @product = null
@@ -106,20 +108,20 @@
     @sync.buildActions(orderNew, @order).update()
     .then (result) =>
       orderUpdated = result.body
-      orderNew2 = _.deepClone orderUpdated
-
-      orderNew2.returnInfo[0].items[0].shipmentState = 'BackInStock'
-      orderNew2.returnInfo[0].items[0].paymentState = 'Refunded'
+      @orderNew2 = _.deepClone orderUpdated
+
+      @orderNew2.returnInfo[0].items[0].shipmentState = 'BackInStock'
+      @orderNew2.returnInfo[0].items[0].paymentState = 'Refunded'

       # update returnInfo status
-      @sync.buildActions(orderNew2, orderUpdated).update()
-    .then (result) ->
+      @sync.buildActions(@orderNew2, orderUpdated).update()
+    .then (result) =>
       expect(result.statusCode).toBe 200
       orderUpdated2 = result.body

       expect(orderUpdated2).toBeDefined()
-      expect(orderUpdated2.returnInfo[0].items[0].shipmentState).toEqual orderNew2.returnInfo[0].items[0].shipmentState
-      expect(orderUpdated2.returnInfo[0].items[0].paymentState).toEqual orderNew2.returnInfo[0].items[0].paymentState
+      expect(orderUpdated2.returnInfo[0].items[0].shipmentState).toEqual @orderNew2.returnInfo[0].items[0].shipmentState
+      expect(orderUpdated2.returnInfo[0].items[0].paymentState).toEqual @orderNew2.returnInfo[0].items[0].paymentState
       done()
     .fail (error) -> done(error)

@@ -156,7 +158,7 @@
       }]]

     @sync.buildActions(orderNew, @order).update()
-    .then (result) ->
+    .then (result) =>
       expect(result.statusCode).toBe 200
       orderUpdated = result.body

@@ -181,7 +183,7 @@

       # sync first parcel
       @sync.buildActions(orderNew2, orderUpdated).update()
-    .then (result) ->
+    .then (result) =>
       expect(result.statusCode).toBe 200
       orderUpdated2 = result.body


removed toggle button test

removed toggle button test

--- test/components/learning-guide/chapter.spec.coffee
+++ test/components/learning-guide/chapter.spec.coffee
@@ -14,14 +14,14 @@
       onPractice: sinon.spy()
     }

-  it 'renders expanded and can be toggled', ->
-    Testing.renderComponent( Chapter, props: @props ).then ({dom}) ->
-      expect(_.toArray(dom.classList)).to.include('expanded')
-      expect(dom.querySelector('.view-toggle').textContent).to.equal('View Less')
-      Testing.actions.clickButton(dom, '.view-toggle')
-      expect(_.toArray(dom.classList)).to.include('collapsed')
-      expect(_.toArray(dom.classList)).to.not.include('expanded')
-      expect(dom.querySelector('.view-toggle').textContent).to.equal('View More')
+  # it 'renders expanded and can be toggled', ->
+  #   Testing.renderComponent( Chapter, props: @props ).then ({dom}) ->
+  #     expect(_.toArray(dom.classList)).to.include('expanded')
+  #     expect(dom.querySelector('.view-toggle').textContent).to.equal('View Less')
+  #     Testing.actions.clickButton(dom, '.view-toggle')
+  #     expect(_.toArray(dom.classList)).to.include('collapsed')
+  #     expect(_.toArray(dom.classList)).to.not.include('expanded')
+  #     expect(dom.querySelector('.view-toggle').textContent).to.equal('View More')

   it 'reports how many problems were worked', ->
     Testing.renderComponent( Chapter, props: @props ).then ({dom}) ->


Press Enter key to navigate

Press Enter key to navigate

--- views/components/settings/parts/navigator-bar.cjsx
+++ views/components/settings/parts/navigator-bar.cjsx
@@ -44,7 +44,7 @@
     webview.reload()
   handlePressEnter: (e) ->
     if e.keyCode is 13
-	     @handleNavigate()
+      @handleNavigate()
   handleSetHomepage: ->
     config.set 'poi.homepage', @state.navigateUrl
   componentDidMount: ->


Checks for undefined when inserting autocomplete into textarea

--- app/assets/javascripts/gfm_auto_complete.js.coffee
+++ app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -35,7 +35,7 @@

       $.fn.atwho.default.callbacks.filter(query, data, searchKey)
     beforeInsert: (value) ->
-      if value.indexOf('undefined')
+      if value.indexOf('undefined') is 1
         @at
       else
         value


Semantic class name change on map infobox.

--- app/assets/javascripts/lib/maps/lodging_map.coffee
+++ app/assets/javascripts/lib/maps/lodging_map.coffee
@@ -78,7 +78,7 @@
             whiteSpace: "nowrap"
             width: "auto"
           closeBoxURL: ''
-          content: '

Location

'+@args.lodgingLocation+' · interesting places nearby

'
+          content: '

Location

'+@args.lodgingLocation+' · interesting places nearby

'
           disableAutoPan: true
           maxWidth: 350
           zIndex: 50


EDSC-354: Fix serialization of granule queries

--- app/assets/javascripts/models/data/query.js.coffee
+++ app/assets/javascripts/models/data/query.js.coffee
@@ -237,7 +237,7 @@
         extend(true, params, component.params())
       params

-    serialize: -> @_writeComponents(@_serialized)
+    serialize: -> @_writeComponents(@_serialized, {})
     _computeParams: => @_writeComponents(@_components)
     _computeGlobalParams: => @_writeComponents(@_propagated)
     _computeOwnParams: => @_writeComponents(@_components, {})


invitations: allow sending multiple invites

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -1,4 +1,7 @@
 jraphical = require 'jraphical'
+Bongo     = require "bongo"
+
+{secure, daisy, Base} = Bongo

 module.exports = class JInvitation extends jraphical.Module

@@ -27,7 +30,7 @@
     sharedMethods   :
       instance      : ['modifyMultiuse', 'remove']
       static        : ['inviteFriend', 'byCode', 'suggestCode', 'createMultiuse',
-                       'createForResurrection']
+                       'createForResurrection', 'createMultiForResurrection']
     sharedEvents    :
       static        : []
       instance      : []
@@ -218,24 +221,37 @@
             return callback err  if err
             groupObj.addInvitation invite, callback

+  @createMultiForResurrection = permit 'send invitations',
+    success: (client, usernames, callback)->
+      if typeof usernames is String
+        return callback {"Usernames should be an array"}
+
+      daisy queue = usernames.map (username) =>
+        => @_createForResurrection client, username, callback
+
+      queue.push -> callback null
+
   @createForResurrection = permit 'send invitations',
-    success: ({connection:{delegate}}, username, callback)->
-      JUser = require './user'
-      JUser.one {username}, (err, user)=>
-        return callback err  if err or !user
-
-        {email} = user
-        code    = @generateInvitationCode "group", email, "resurrection"
-        invite  = new JInvitation {
-          code
-          email
-          type       : "multiuse"
-          group      : "resurrection"
-          origin     : ObjectRef(delegate)
-          maxUses    : 100
-          createdFor : username
-        }
-        invite.save callback
+    success: (client, username, callback)->
+      @_createForResurrection client, username, callback
+
+  @_createForResurrection : ({connection:{delegate}}, username, callback)->
+    JUser = require './user'
+    JUser.one {username}, (err, user)=>
+      return callback err  if err or !user
+
+      {email} = user
+      code    = @generateInvitationCode "group", email, "resurrection"
+      invite  = new JInvitation {
+        code
+        email
+        type       : "multiuse"
+        group      : "resurrection"
+        origin     : ObjectRef(delegate)
+        maxUses    : 100
+        createdFor : username
+      }
+      invite.save callback

   @suggestCode: permit 'send invitations',
     success: (client, callback, tries=0)->


arranged captcha function

arranged captcha function

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1130,8 +1130,12 @@
           queue.next()

       =>
-        @verifyRecaptcha recaptcha, {foreignAuthType, slug}, (err)->
-          return callback err  if err
+        if KONFIG.recaptcha.enabled
+          @verifyRecaptcha recaptcha, { foreignAuthType, slug }, (err)->
+            return callback err  if err
+            queue.next()
+
+        else
           queue.next()

       =>
@@ -1680,23 +1684,23 @@
    * @param {function} callback
   ###
   @verifyRecaptcha = (response, params, callback) ->
+
+    { url, secret }           = KONFIG.recaptcha
     { foreignAuthType, slug } = params
-    { url, secret, enabled }  = KONFIG.recaptcha
-
-    return callback null  unless enabled
+
     return callback null  if foreignAuthType is 'github'

     # TODO: temporarily disable recaptcha for groups
     if slug? and slug isnt 'koding'
       return callback null

-    request.post url, {form:{response, secret}}, (err, res, raw)->
+    request.post url, { form: { response, secret } }, (err, res, raw) ->
       if err
         console.log "Recaptcha: err validation captcha: #{err}"

-      if !err && res.statusCode == 200
+      if not err and res.statusCode is 200
         try
-          if JSON.parse(raw)["success"]
+          if JSON.parse(raw)['success']
             return callback null
         catch e
           console.log "Recaptcha: parsing response failed. #{raw}"


User: check for first name and last name if they are empty, put username as firstname

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -637,6 +637,9 @@
     { nickname : oldUsername } = account.profile
     { username, email, firstName, lastName,
       agree, inviteCode, referrer } = userFormData
+
+    if not firstName or firstName is "" then firstName = username
+    if not lastName then lastName = ""

     # only unreigstered accounts can be "converted"
     if account.status is "registered"


fix spectrogram axis locations

fix spectrogram axis locations

--- bokehjs/demo/spectrogram/coffee/spectrogram.coffee
+++ bokehjs/demo/spectrogram/coffee/spectrogram.coffee
@@ -209,8 +209,8 @@
       dims: [options.width, options.height]
       xrange: [0, NGRAMS]
       yrange: [0, MAX_FREQ]
-      xaxes: "min"
-      yaxes: "min"
+      xaxes: "below"
+      yaxes: "left"
       xgrid: false
       ygrid: false
       tools: false
@@ -348,8 +348,8 @@
       dims: [options.width, options.height]
       xrange: [options.x0, options.x1]
       yrange: [options.y0, options.y1]
-      xaxes: "min"
-      yaxes: "min"
+      xaxes: "below"
+      yaxes: "left"
       xgrid: false
       tools: false
     }


reactivity: embed payload tests

reactivity: embed payload tests

--- client/activity/lib/flux/test/messagesstore.coffee
+++ client/activity/lib/flux/test/messagesstore.coffee
@@ -270,3 +270,75 @@
       message = storeState.get messageId
       expect(message.get '__isEditing').toEqual no

+
+  describe '#handleEditMessageEmbedPayloadSuccess', ->
+
+    messageId       = 'test'
+    body            = 'Hello World'
+    clientRequestId = 'testclient'
+    message         = MessageCollectionHelpers.createFakeMessage messageId, body
+    embedPayload    = { link_url : 'http://www.test.ccom', link_embed : { body : 'test' } }
+
+    it 'skips message __editedPayload property update when message isn\'t in edit mode', ->
+
+      reactor.dispatch actionTypes.CREATE_MESSAGE_SUCCESS, { clientRequestId, message }
+      reactor.dispatch actionTypes.EDIT_MESSAGE_EMBED_PAYLOAD_SUCCESS, { messageId, embedPayload }
+
+      storeState = reactor.evaluate ['messages']
+      message = storeState.get messageId
+
+      expect(message.get '__editedPayload').to.be.undefined
+
+    it 'updates message __editedPayload property with a new embed payload when message is in edit mode', ->
+
+      reactor.dispatch actionTypes.CREATE_MESSAGE_SUCCESS, { clientRequestId, message }
+      reactor.dispatch actionTypes.SET_MESSAGE_EDIT_MODE, { messageId }
+      reactor.dispatch actionTypes.EDIT_MESSAGE_EMBED_PAYLOAD_SUCCESS, { messageId, embedPayload }
+
+      storeState = reactor.evaluate ['messages']
+      message = storeState.get messageId
+
+      expect(message.get('__editedPayload').toJS()).to.eql embedPayload
+
+
+  describe '#handleEditMessageEmbedPayloadFail', ->
+
+    messageId       = 'test'
+    body            = 'Hello World'
+    clientRequestId = 'testclient'
+    message         = MessageCollectionHelpers.createFakeMessage messageId, body
+    embedPayload    = { link_url : 'http://www.test.ccom', link_embed : { body : 'test' } }
+
+    it 'clears embed payload in __editedPayload property', ->
+
+      reactor.dispatch actionTypes.CREATE_MESSAGE_SUCCESS, { clientRequestId, message }
+      reactor.dispatch actionTypes.SET_MESSAGE_EDIT_MODE, { messageId }
+      reactor.dispatch actionTypes.EDIT_MESSAGE_EMBED_PAYLOAD_SUCCESS, { messageId, embedPayload }
+      reactor.dispatch actionTypes.EDIT_MESSAGE_EMBED_PAYLOAD_FAIL, { messageId }
+
+      storeState = reactor.evaluate ['messages']
+      message = storeState.get messageId
+
+      expect(message.getIn ['__editedPayload', 'link_url']).to.be.undefined
+      expect(message.getIn ['__editedPayload', 'link_embed']).to.be.undefined
+
+
+  describe '#handleResetEditedMessagePayload', ->
+
+    messageId       = 'test'
+    body            = 'Hello World'
+    clientRequestId = 'testclient'
+    message         = MessageCollectionHelpers.createFakeMessage messageId, body
+    embedPayload    = { link_url : 'http://www.test.ccom', link_embed : { body : 'test' } }
+
+    it 'It clears message __editedPayload property', ->
+
+      reactor.dispatch actionTypes.CREATE_MESSAGE_SUCCESS, { clientRequestId, message }
+      reactor.dispatch actionTypes.EDIT_MESSAGE_EMBED_PAYLOAD_SUCCESS, { messageId, embedPayload }
+      reactor.dispatch actionTypes.RESET_EDITED_MESSAGE_PAYLOAD, { messageId }
+
+      storeState = reactor.evaluate ['messages']
+      message = storeState.get messageId
+
+      expect(message.has '__editedPayload').to.be.false
+


statusbar: change default state of button to loading

--- client/ide/lib/views/statusbar/idestatusbar.coffee
+++ client/ide/lib/views/statusbar/idestatusbar.coffee
@@ -20,6 +20,7 @@
     @on 'ShowAvatars',          @bound 'showAvatars'
     @on 'ParticipantLeft',      @bound 'dimParticipantAvatar'
     @on 'ParticipantJoined',    @bound 'addParticipantAvatar'
+    @on 'CollaborationLoading', @bound 'handleCollaborationLoading'
     @on 'CollaborationEnded',   @bound 'handleCollaborationEnded'
     @on 'CollaborationStarted', @bound 'handleCollaborationStarted'
     @on 'ParticipantWatched',   @bound 'decorateWatchedAvatars'
@@ -42,10 +43,11 @@

     @addSubView @share = new CustomLinkView
       href     : "#{kd.singletons.router.getCurrentPath()}/share"
-      title    : 'Share'
+      title    : 'Loading'
       cssClass : 'share fr hidden'
       click    : (event) ->
         kd.utils.stopDOMEvent event
+        return  if @hasClass 'loading'
         appManager.tell 'IDE', 'showChat'  unless collabDisabled

     @addSubView @avatars = new KDCustomHTMLView cssClass : 'avatars fr'
@@ -120,12 +122,22 @@
     @avatars.show()

+  handleCollaborationLoading: ->
+
+    @share.setClass 'loading'
+    @share.updatePartial 'Loading'
+
+
   handleCollaborationEnded: ->

+    @share.unsetClass 'loading'
     @share.updatePartial 'Share'
     @avatars.destroySubViews()

   handleCollaborationStarted: ->

+    @share.unsetClass 'loading'
     @share.updatePartial 'Chat'
+
+


Reply-To header was not properly handled, refs #249

--- server/models/message.coffee
+++ server/models/message.coffee
@@ -572,7 +572,7 @@
     mail.normSubject = mailutils.normalizeSubject mail.subject if mail.subject

     # @TODO, find and parse from mail.headers ?
-    mail.replyTo = []
+    mail.replyTo ?= []
     mail.cc ?= []
     mail.bcc ?= []
     mail.to ?= []


For shortened URLs, do not consider case.

This ensures that URLs which differ only in their case are considered
duplicates, hence one is dropped.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -137,6 +137,7 @@
         url = decodeURI @url
       catch
         @url
+    url = url.toLowerCase()
     for [ filter, replacements ] in @stripPatterns
       if new RegExp(filter).test url
         for replace in replacements


Search completion; prevent vomnibar flicker.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -469,7 +469,11 @@
             continuation = cont if cont?
             if @completers.length <= ++completersFinished
               shouldRunContinuation = continuation? and not @mostRecentQuery
-              onComplete @prepareSuggestions(queryTerms, suggestions), keepAlive: shouldRunContinuation
+              # We don't post results immediately if there are none, and we're going to run a continuation
+              # (ie. a SearchEngineCompleter).  This prevents hiding the vomnibar briefly before showing it
+              # again, which looks ugly.
+              unless shouldRunContinuation and suggestions.length == 0
+                onComplete @prepareSuggestions(queryTerms, suggestions), keepAlive: shouldRunContinuation
               # Allow subsequent queries to begin.
               @filterInProgress = false
               if shouldRunContinuation


Removed workaround with invalid role_ids. Improved code layout.

--- app/assets/javascripts/app/controllers/signup.coffee
+++ app/assets/javascripts/app/controllers/signup.coffee
@@ -44,7 +44,7 @@
       @params.login = @params.email

     @params.signup = true
-    @params.role_ids = [0]
+    @params.role_ids = []
     @log 'notice', 'updateAttributes', @params
     user = new App.User
     user.load(@params)
@@ -54,7 +54,7 @@
     )
     if errors
       @log 'error new', errors
-      @formValidate( form: e.target, errors: errors )
+      @formValidate(form: e.target, errors: errors)
       @formEnable(e)
       return false


keyup event works best

keyup event works best

Pricing model changes (Task #73569)

--- app/assets/javascripts/app/manage_instruments.coffee
+++ app/assets/javascripts/app/manage_instruments.coffee
@@ -2,7 +2,7 @@
   target = '.edit_instrument #instrument_min_reserve_mins'

   if $(target).length
-    $(target).change ->
+    $(target).keyup ->
       interval = $('#instrument_reserve_interval').val()
       color = if $(this).val() % interval == 0 then 'black' else 'red'
       $(this).css 'color', color


confirm on enter

confirm on enter

--- app/assets/javascripts/bootstrap-data-confirm.coffee
+++ app/assets/javascripts/bootstrap-data-confirm.coffee
@@ -56,6 +56,10 @@
           )
       )
     .end()
+
+    .on('keypress', (e) ->
+      $('.modal-footer a:last').trigger('click') if e.keyCode == 13 # Enter Key Code
+    )

     .on("hidden", -> $(@).remove())


Add console pushState

Add console pushState

--- app/assets/javascripts/garage/docs/console.js.coffee
+++ app/assets/javascripts/garage/docs/console.js.coffee
@@ -69,6 +69,9 @@
       cache: false,
       data: buildData($('.parameters')),
       dataType: 'json',
+      complete: ->
+        queryString = $.param({'location': $('#location').val(), 'method': $('#method').val()})
+        history.pushState('', '', "#{location.pathname}?#{queryString}")
       success: (data, textStatus, xhr) ->
         $('#api-headers').text("#{xhr.status} #{xhr.statusText}\n" + xhr.getAllResponseHeaders())
         $('#api-response').html buildHyperlinks(JSON.stringify(data, undefined, 2))


Don't even try to register socket in unit tests.

--- app/coffee/modules/packages/comm/socket/index.coffee
+++ app/coffee/modules/packages/comm/socket/index.coffee
@@ -9,16 +9,19 @@
 	'$rootScope', 'config'
 	($rootScope, config) ->

+		service = {}
+
 # Be aware: this will throw in unit tests because [global].io won't be
 # available. It must be mocked out.
-
+
+		return service if 'unit' is config.get 'testMode'
 		socket = io.connect()

 # We have to queue emits while not initialized to keep things robust.

 		initializedQueue = []
 		socket.on 'initialized', =>
-			@emit.apply this, args for args in initializedQueue
+			service.emit.apply this, args for args in initializedQueue

 # Might as well make sure everything's working fine.

@@ -26,15 +29,15 @@

 # Connection and disconnection.

-		@connect = -> socket.socket.connect()
+		service.connect = -> socket.socket.connect()

-		@connected = -> socket.socket.connected
+		service.connected = -> socket.socket.connected

-		@disconnect = -> socket.disconnect()
+		service.disconnect = -> socket.disconnect()

 # Proxy for Socket::on, handles invocation of Scope::$apply.

-		@on = (eventName, callback) ->
+		service.on = (eventName, callback) ->

 			debugListeners["on-#{eventName}"] ?= (->
 				socket.on eventName, (data) ->
@@ -48,8 +51,8 @@

 # Proxy for Socket::emit, handles invocation of Scope::$apply.

-		@emit = (eventName, data, callback) ->
-			return initializedQueue.push arguments unless @connected()
+		service.emit = (eventName, data, callback) ->
+			return initializedQueue.push arguments unless service.connected()

 			if config.get 'debugging'
 				console.debug "sent: #{eventName}, #{JSON.stringify data, null, '  '}"
@@ -64,9 +67,9 @@
 				emitArguments = arguments
 				$rootScope.$apply -> callback.apply socket, emitArguments

-		$rootScope.$on 'debugLog', (error) => @emit 'debugLog', error
+		$rootScope.$on 'debugLog', (error) -> service.emit 'debugLog', error

-		return
+		service

 ]


Add tracking to assistant

Add tracking to assistant

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -50,6 +50,7 @@

 misc = require('smc-util/misc')
 misc_page = require('./misc_page')
+{analytics_event} = require('./tracker')

 # Ensure CodeMirror is available and configured
 require('./codemirror/codemirror')
@@ -1490,6 +1491,7 @@
             )
         else
             @examples_dialog.show(lang)
+        analytics_event('editor_assistant', misc.get_extension(@filename), lang)
         @examples_dialog.set_handler(@example_insert_handler)

     example_insert_handler: (insert) =>


webapp/examples: header nav bar spacing and brackets for cjsx

--- src/smc-webapp/examples.cjsx
+++ src/smc-webapp/examples.cjsx
@@ -378,11 +378,23 @@

     render_nav: ->
         entries = @props.nav_entries ? []
-
+
             {
                 entries.map (key) ->
                     name = lang2name(key)
-                    <NavItem key={key} eventKey={key} title={name}>{name}</NavItem>
+                    <NavItem
+                        key      = {key}
+                        eventKey = {key}
+                        title    = {name}
+                    >
+                        {name}
+                    </NavItem>
             }

@@ -390,9 +402,9 @@
         <FormGroup>
             <InputGroup className = 'webapp-examples-search'>
                 <FormControl
-                    ref         = 'search'
-                    type        = 'text'
-                    placeholder = 'Search'
+                    ref         = {'search'}
+                    type        = {'text'}
+                    placeholder = {'Search'}
                     value       = {@props.search_str ? ''}
                     onKeyUp     = {@handle_search_keyup}
                     onChange    = {@search}
@@ -407,18 +419,19 @@

     render: ->
         return null if (not @props.lang?) or (not @props.lang_select?)
+        show_lang_nav = @props.lang_select and not @props.unknown_lang
         <Row>
-            <Col sm={3}>
+            <Col sm={if show_lang_nav then 2 else 5}>

                     <Icon name={exports.ICON_NAME} />
                     <Space/>
                     {lang2name(@props.lang) if not @props.lang_select} Assistant

             </Col>
-            <Col sm={5}>
-                {@render_nav() if @props.lang_select and not @props.unknown_lang}
+            <Col sm={if show_lang_nav then 7 else 4}>
+                {@render_nav() if show_lang_nav}
             </Col>
-            <Col sm={3}>
+            <Col sm={2}>
                 {@render_search() if not @props.unknown_lang}
             </Col>
         </Row>
@@ -468,7 +481,7 @@
         if not list?
             list = []
         # don't use ListGroup & ListGroupItem with onClick, because then there are div/buttons (instead of ul/li) and layout is f'up
-

+

         {
             list.map (name, idx) =>
                 click  = @category_selection.bind(@, level, idx)
@@ -484,7 +497,7 @@

     render_search_results: ->
         ss = @props.search_str
-

+

         {
             @props.hits.map (hit, idx) =>
                 [lvl1, lvl2, lvl3, title, descr, inDescr] = hit
@@ -506,7 +519,8 @@
                     onClick      = {click}
                 >
                     {lvl1} → {lvl2} →
-                    {' '}{ if snippet?.length > 0}
+                    {' '}
+                    { if snippet?.length > 0}

         }

@@ -516,7 +530,7 @@
         if not @props.data?
             <Row key='top'>
                 <Col sm={8} smOffset={4}>
-

+

<Loading />

@@ -536,10 +550,10 @@
     render_bottom: ->
         <Row key='bottom'>
             <Col sm={6}>
-
{@props.code}

+
{@props.code}

             </Col>
             <Col sm={6}>
-                <Panel ref='descr' className='webapp-examples-descr'>
+                <Panel ref={'descr'} className={'webapp-examples-descr'}>
                     <Markdown value={@props.descr} />
                 </Panel>
             </Col>
@@ -556,7 +570,7 @@
             <Col sm={12}>
                 Selected language {@props.lang} has no data.
                 You can help by contributing more content at{' '}
-
+
                     {'sagemathinc/cocalc-assistant'}
                 .
             </Col>


add max all button and set initial values to 0 and add back in the max buttons

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -696,11 +696,38 @@
                 limit = if limits[name] > 0 then 1 else 0
                 current_value = current[name] ? limit
             else
+                current_value = 0
+            state["upgrade_#{name}"] = misc.round2(current_value * factor)
+            upgrades = {}
+
+        return state
+
+    max_upgrades : ->
+        state =
+            upgrading : true
+
+        current = @props.upgrades_you_applied_to_this_project
+
+        # how much upgrade you have used between all projects
+        used_upgrades = @props.upgrades_you_applied_to_all_projects
+
+        # how much unused upgrade you have remaining
+        remaining = misc.map_diff(@props.upgrades_you_can_use, used_upgrades)
+
+        # maximums you can use, including the upgrades already on this project
+        limits = misc.map_sum(current, remaining)
+
+        for name, data of @props.quota_params
+            factor = data.display_factor
+            if name == 'network' or name == 'member_host'
+                limit = if limits[name] > 0 then 1 else 0
+                current_value = current[name] ? limit
+            else
                 current_value = current[name] ? limits[name]
             state["upgrade_#{name}"] = misc.round2(current_value * factor)
             upgrades = {}

-        return state
+        @setState(state)

     show_upgrade_quotas : ->
         @setState(upgrading : true)
@@ -734,7 +761,10 @@
         >
             Max
         </Button>
-
+
+    render_addon : (misc, name, display_unit, limit) ->
+
{"#{misc.plural(2,display_unit)}"} {@render_max_button(name, limit)}

+
     render_upgrade_row : (name, data, remaining=0, current=0, limit=0) ->
         if not data?
             return
@@ -769,7 +799,7 @@
                             checked  = {val > 0}
                             label    = {label}
                             onChange = {=>@setState("upgrade_#{name}" : if @refs["upgrade_#{name}"].getChecked() then 1 else 0)}
-                            />
+                        />
                     </form>
                 </Col>
             </Row>
@@ -813,7 +843,7 @@
                         value      = {val}
                         bsStyle    = {bs_style}
                         onChange   = {=>@setState("upgrade_#{name}" : @refs["upgrade_#{name}"].getValue())}
-
+                        addonAfter = {@render_addon(misc, name, display_unit, limit)}
                     />
                     {label}
                 </Col>
@@ -868,6 +898,15 @@

<Icon name='arrow-circle-up' /> Adjust your project quota contributions

                 Adjust your contributions to the quotas on this project (disk space, memory, cores, etc.).  The total quotas for this project are the sum of the contributions of all collaborators and the free base quotas.
+

+                    <Button
+                        bsSize  = 'xsmall'
+                        onClick = {=>@max_upgrades()}
+                        style   = {padding:'0px 5px'}
+                    >
+                        Max all the upgrades
+                    </Button>
+

                 <Row>
                     <Col md=6>


Add creator to COmment

Add creator to COmment

--- core/app/backbone/models/comment.coffee
+++ core/app/backbone/models/comment.coffee
@@ -1,9 +1,7 @@
 class window.Comment extends Backbone.Model

+  templateHelpers: =>
+    creator: @creator
+
   creator: ->
-    new User
-        "id": "50a2171100abf04ed9000038",
-        "name": "tomdev",
-        "username": "tomdev",
-        "gravatar_hash": "87d45547dd789eb527a74568044af70a",
-        "authority": 1.6
+    new User(@get('created_by'))


Remove old error from form

Remove old error from form

--- app/assets/javascripts/add-playlist-option.js.coffee
+++ app/assets/javascripts/add-playlist-option.js.coffee
@@ -40,6 +40,9 @@
   $('#playlist_title').val("")
   $('#playlist_comment').val("")
   $('#playlist_visibility_private').prop('checked', true)
+  #remove any possible old errors
+  $('#title_error').remove()
+  $('#playlist_title').parent().removeClass('has-error')

   $('#add-playlist-modal').modal('show')
   return true


Remove math.coffee because Google took down the backend.

https://github.com/github/hubot/pull/625

--- scripts/math.coffee
+++ scripts/math.coffee
@@ -1,22 +0,0 @@
-# Description:
-#   Allows Hubot to do mathematics.
-#
-# Commands:
-#   hubot math me <expression> - Calculate the given expression.
-#   hubot convert me <expression> to <units> - Convert expression to given units.
-module.exports = (robot) ->
-  robot.respond /(calc|calculate|calculator|convert|math|maths)( me)? (.*)/i, (msg) ->
-    msg
-      .http('https://www.google.com/ig/calculator')
-      .query
-        hl: 'en'
-        q: msg.match[3]
-      .headers
-        'Accept-Language': 'en-us,en;q=0.5',
-        'Accept-Charset': 'utf-8',
-        'User-Agent': "Mozilla/5.0 (X11; Linux x86_64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
-      .get() (err, res, body) ->
-        # Response includes non-string keys, so we can't use JSON.parse here.
-        json = eval("(#{body})")
-        msg.send json.rhs || 'Could not compute.'
-


Use console.info into cozyMails.logInfo

Use console.info into cozyMails.logInfo

--- client/app/utils/api_utils.coffee
+++ client/app/utils/api_utils.coffee
@@ -276,7 +276,7 @@
         xhr.open 'POST', 'activity', true
         xhr.setRequestHeader "Content-Type", "application/json;charset=UTF-8"
         xhr.send JSON.stringify(data)
-        console.log message
+        console.info message

     # Log every Flux action (only in development environment)
@@ -341,4 +341,3 @@
     # clear action logs
     clearLogs: ->
         window.cozyMails.debugLogs = []
-


go to category after product save

go to category after product save

--- app/assets/javascripts/controllers/product.js.coffee
+++ app/assets/javascripts/controllers/product.js.coffee
@@ -1,3 +1,6 @@
 Stocks.ProductController = Ember.Controller.extend
   actions:
-    save: () -> @get('model').save()+    save: () ->
+      model = @get('model')
+      model.save()
+      @transitionToRoute 'category', model.get('category')


uploadRate() changes result max once per second after last progress update. To prevent infinite digest.

--- app/assets/javascripts/directives/uploader.js.coffee
+++ app/assets/javascripts/directives/uploader.js.coffee
@@ -101,7 +101,7 @@
                   ),
                   ((currentProgress) =>
                     @current.progress = currentProgress
-                    @rateWindow.push([ Date.now(), @progress.loaded + @current.progress.loaded ])
+                    @rateWindow.push([ now = Date.now(), now, @progress.loaded + @current.progress.loaded ])
                   )
                 ).
                 finally(=>
@@ -137,7 +137,8 @@
           if @rateWindow.length >= 2 # rate = 0 if there aren't 2 entries to compare
             first = @rateWindow[0]
             last = @rateWindow[@rateWindow.length - 1]
-            return (last[1] - first[1]) / ((now - first[0]) / 1000)
+            last[1] = now if now - last[1] > 1000 # return value changes max once per second after last progress update, to prevent infinite digest
+            return Math.max((last[2] - first[2]) / ((last[1] - first[0]) / 1000), 0)

         0


Add firetracker to adapters list

Add firetracker to adapters list

--- app/assets/javascripts/embeditor/embeditor.js.coffee
+++ app/assets/javascripts/embeditor/embeditor.js.coffee
@@ -15,7 +15,7 @@
         # 'storify'       : 'Embedly'
         'coveritlive'   : 'CoverItLive'
         # 'rebelmouse'    : 'RebelMouse'
-        # 'firetracker'   : 'FireTracker'
+        'firetracker'   : 'FireTracker'
         # 'twitter'       : 'Embedly'
         'instagram'     : 'Embedly'
         'soundcloud'    : 'Embedly'


EDSC-586 avoid overwriting observables in dataset.js.coffee

--- app/assets/javascripts/models/data/dataset.js.coffee
+++ app/assets/javascripts/models/data/dataset.js.coffee
@@ -142,13 +142,18 @@
       if attributes && @granuleQueryLoaded()
         @granuleQuery.attributes.definitions(attributes)

-      this[key] = value for own key, value of jsonObj
-
       @hasAtomData(jsonObj.short_name?)
-      @gibs = ko.observable(jsonObj.gibs ? @gibs?())
-      @opendap = ko.observable(jsonObj.opendap ? @opendap?())
+      @_setObservable('gibs', jsonObj)
+      @_setObservable('opendap', jsonObj)
+      @_setObservable('modaps', jsonObj)
       @nrt = jsonObj.collection_data_type == "NEAR_REAL_TIME"
-      @modaps = ko.observable(jsonObj.modaps ? @modaps?())
       @granuleCount(jsonObj.granule_count)

+      for own key, value of jsonObj
+        this[key] = value unless ko.isObservable(this[key])
+
+    _setObservable: (prop, jsonObj) =>
+      this[prop] ?= ko.observable(undefined)
+      this[prop](jsonObj[prop] ? this[prop]())
+
   exports = Dataset


Fix: also remove body class

Fix: also remove body class

--- app/assets/javascripts/modules/cookie_warning.coffee
+++ app/assets/javascripts/modules/cookie_warning.coffee
@@ -7,6 +7,7 @@

     $('.JS-Cookie-warning--close').on 'click', ->
       $('.JS-Cookie-warning').remove()
+      $('body').removeClass 'hasCookieWarning'
       createCookie 'userAcceptsCookies', 'true', 89 # valid for 89 days

 $(document).ready cookieWarning


perf update (:not is not dom-native) [ci skip]

--- app/assets/javascripts/rails_admin/ra.widgets.coffee
+++ app/assets/javascripts/rails_admin/ra.widgets.coffee
@@ -124,7 +124,7 @@

     # ckeditor

-    $('form [data-richtext=ckeditor]:not(.ckeditored)').each ->
+    $('form [data-richtext=ckeditor]').not('.ckeditored').each ->
       window.CKEDITOR_BASEPATH = '/assets/ckeditor/'
       options = $(this).data('options')
       if not window.CKEDITOR


Added logic to load credit card in user model

--- app/assets/javascripts/sprangular/models/user.coffee
+++ app/assets/javascripts/sprangular/models/user.coffee
@@ -1,6 +1,13 @@
 class Sprangular.User
   init: ->
+    @creditCards = []
     @_mergeAddressLists()
+
+    for paymentSource in @payment_sources
+      card = new Sprangular.CreditCard
+      card.init(paymentSource)
+
+      @creditCards.push(card)

   _mergeAddressLists: ->
     addresses = []


revert to 0.13.0

revert to 0.13.0

--- natal.coffee
+++ natal.coffee
@@ -20,7 +20,7 @@
 camelRx         = /([a-z])([A-Z])/g
 projNameRx      = /\$PROJECT_NAME\$/g
 projNameHyphRx  = /\$PROJECT_NAME_HYPHENATED\$/g
-rnVersion       = '0.14.2'
+rnVersion       = '0.13.0'
 rnPackagerPort  = 8081
 podMinVersion   = '0.38.2'
 process.title   = 'natal'


String#isString

String#isString

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -55,6 +55,17 @@
   this.replace(/_id$/, "").replace(/_/g, " ").capitalize()

 ###*
+Returns true.
+
+@name isString
+@methodOf String#
+@type Boolean
+@returns true
+###
+String::isString = ->
+  true
+
+###*
 Parse this string as though it is JSON and return the object it represents. If it
 is not valid JSON returns the string itself.


Profile when available

Profile when available

--- benchmark/benchmark.coffee
+++ benchmark/benchmark.coffee
@@ -18,9 +18,11 @@
   lines = _.shuffle(lines)
   count++

+console.profile?('span-skip-list')
 start = Date.now()
 for offsets in offsetsToInsert
   offsetIndex.spliceArray('rows', 0, offsets.length, offsets)
 time = Date.now() - start
+console.profileEnd?('span-skip-list')

 console.log "Inserting #{lines.length * times} lines took #{time}ms (#{Math.round(lines.length * times / time)} lines/ms)"


[Email Notifitcation] Added recipient field into addActivities method instead of null, if it is not set, set it to null

--- workers/social/lib/social/traits/notifying.coffee
+++ workers/social/lib/social/traits/notifying.coffee
@@ -19,12 +19,13 @@
     JUser    = require '../models/user'

     actor = contents[contents.actorType]
-    {origin} = contents
+    {origin, recipient} = contents
+    recipient or= null

     createActivity = =>
       if contents.relationship?
         relationship = new Relationship contents.relationship
-        CBucket.addActivities relationship, origin, actor, null, (err)->
+        CBucket.addActivities relationship, origin, actor, recipient, (err)->
           console.err err if err

     sendNotification = =>


refactor expectAccessDenied helper function

--- workers/social/testhelper/index.coffee
+++ workers/social/testhelper/index.coffee
@@ -157,13 +157,13 @@
   daisy queue

-expectAccessDenied = (done, caller, callee, args...) ->
+expectAccessDenied = (caller, callee, args..., callback) ->

   withDummyClient ({ client }) ->

     kallback = (err) ->
       expect(err?.message).to.be.equal 'Access denied'
-      done()
+      callback()

     if    args.length > 0
     then  caller[callee] client, args..., kallback


"Back to Top" button only visible when document is big enough

--- client/index.coffee
+++ client/index.coffee
@@ -17,6 +17,7 @@
 $ ->

 	$('.navSection').not('#projects').hide()
+	$('.btnTop').hide()

 	$.getJSON '/md/' + encodeURIComponent(ABOUT_URL), (data) ->
 		console.log data
@@ -60,6 +61,7 @@
 			$img.load (e) ->
 				$(this).parent().appendTo $projects
 				$(this).fadeIn()
+				checkBtnTopOffset()

 			$project.attr 'title', project.name

@@ -89,6 +91,7 @@
 		$('.project').filter(".#{filter}").fadeIn(600)
 		$('.filterButton').removeClass 'active'
 		$button.addClass 'active'
+		checkBtnTopOffset()

 	$('.navButton').click (e) ->
 		$button = $(e.target)
@@ -97,7 +100,14 @@
 		$button.addClass 'active'
 		$('.navSection').hide()
 		$("##{section}").fadeIn()
+		checkBtnTopOffset()

 	$('.btnTop').click ->
 		$(document).scrollTop 0

+	checkBtnTopOffset = ->
+		if $(window).height() > $('.footer').offset().top
+			$('.btnTop').hide()
+		else
+			$('.btnTop').show()
+


Client: Activity: Updated  onChange event for readable code

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -198,7 +198,8 @@

     matchResult = value.match /^@(.*)/

-    if matchResult then query = matchResult[1] else query = value
+    query = value
+    query = matchResult[1]  if matchResult

     { channel, user  } = ActivityFlux.actions


Workspace: Added missing id attribute to CodeMirror stylesheet to prevent fetching it again.

--- client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
@@ -64,13 +64,15 @@
     @setEditorMode()

   setEditorTheme: ->
-    unless document.getElementById "codemirror-ambiance-style"
-      link       = document.createElement "link"
-      link.rel   = "stylesheet"
-      link.type  = "text/css"
-      link.href  = "#{cdnRoot}/theme/ambiance.css"
-      document.head.appendChild link
-      @codeMirrorEditor.setOption "theme", "ambiance"
+    return if document.getElementById "codemirror-ambiance-style"
+    link       = document.createElement "link"
+    link.rel   = "stylesheet"
+    link.type  = "text/css"
+    link.href  = "#{cdnRoot}/theme/ambiance.css"
+    link.id    = "codemirror-ambiance-style"
+    document.head.appendChild link
+
+    @codeMirrorEditor.setOption "theme", "ambiance"

   setEditorMode: ->
     {file} = @getOptions()


Video controls mobile

Video controls mobile

--- apps/editorial_features/components/eoy/client.coffee
+++ apps/editorial_features/components/eoy/client.coffee
@@ -13,10 +13,6 @@
 module.exports.EoyView = class EoyView extends Backbone.View

   el: $('body')
-  events:
-    'click .video-controls': 'playVideo'
-    'click .scroller__items section[data-section="0"]': 'hintScroll'
-    'loadedmetadata .video': 'videoControls'

   initialize: ->
     $('.scroller__items section').attr('data-state', 'closed')
@@ -30,6 +26,9 @@
     new SuperArticleView el: $('body'), article: @article
     @loadBody = _.once @deferredLoadBody
     @watchScrolling()
+    $('.video-controls').on 'click', @playVideo
+    $('.scroller__items section[data-section="0"]').on 'click', @hintScroll
+    $('.video').on 'loadedmetadata', @videoControls
     $('.scroller').fadeIn 500, =>
       @loadBody()
       @smoothAnchorScroll()


Save target element and offset so we can later reposition on command

--- js-library/app/js/jail_iframe/classes/icon_buttons.coffee
+++ js-library/app/js/jail_iframe/classes/icon_buttons.coffee
@@ -1,5 +1,7 @@
 class IconButton
   constructor: (options) ->
+    @_targetElement = options.targetElement
+    @_targetOffset = options.targetOffset
     @$el = $ """
       <factlink-icon-button>
         <factlink-icon-button-bubble>
@@ -18,13 +20,16 @@
       mouseleave: options.onmouseleave
     @$el.on 'click', options.onclick

-    @_tether = new Tether
-      element: @$el[0]
-      target: options.targetElement
-      attachment: 'top left'
-      targetAttachment: 'top right'
-      classPrefix: 'factlink-tether'
-      targetOffset: options.targetOffset || '0 0'
+
+    @_tether = new Tether(@_tether_options())
+
+  _tether_options: () ->
+    element: @$el[0]
+    target: @_targetElement
+    attachment: 'top left'
+    targetAttachment: 'top right'
+    classPrefix: 'factlink-tether'
+    targetOffset: @_targetOffset || '0 0'

   destroy: ->
     @_tether.destroy()


regex tweaks

regex tweaks

--- lib/build.coffee
+++ lib/build.coffee
@@ -109,7 +109,7 @@
       if(atom.workspace.getActiveTextEditor().buffer?.file?)
         isWin = /^win/.test(process.platform)
         path = atom.workspace.getActiveTextEditor().buffer.file.path
-        projectPath = if isWin then @root+'\\src\\\\' else @root+'/src/'
+        projectPath = if isWin then @root+'\\src.' else @root+'/src/'
         console.log('projectPath :'+projectPath)
         pathHeRegex = ///#{projectPath}///
         console.log('path :'+path)


tweaks

tweaks

--- lib/build.coffee
+++ lib/build.coffee
@@ -109,9 +109,8 @@
       if(atom.workspace.getActiveTextEditor().buffer?.file?)
         isWin = /^win/.test(process.platform)
         path = atom.workspace.getActiveTextEditor().buffer.file.path
-        projectPath = if isWin then @root+'\\src\\' else @root+'/src'
+        projectPath = if isWin then @root+'\\src\\' else @root+'/src/'
         console.log('projectPath : '+projectPath)
-        pathHeRegex = ///#{projectPath}///
         console.log('path : '+path)
         console.log('projectPath : '+projectPath)
         console.log('path.startsWith(projectPath) = '+path.startsWith(projectPath))


Revert cache

Revert cache

--- lib/cache.coffee
+++ lib/cache.coffee
@@ -1,12 +1,9 @@
-_ = require './util'
-stats = require('./stats').getChildClient('cache')
+_ = require 'underscore'

 class Cache

   constructor: (opts = {}) ->
     { @max_items } = opts
-    @lru_size = 0
-    @sticky_size = 0
     unless @max_memory || @max_items
       throw new Error "Must specify some kind of size limit"
     @reset()
@@ -16,49 +13,55 @@
     @map = {}
     @head = new CacheItem
     @head.next = @head.prev = @head
+    @stats =
+      lru_items: 0
+      sticky_items: 0
+      lru_hits: 0
+      sticky_hits: 0
+      misses: 0
+      added: 0
+      removed: 0

   add: (key, value, sticky = false) ->
     return item.get() if item = (@sticky[key] || @map[key])
     item = new CacheItem key, value
-    stats.increment 'add'
     if sticky
+      @stats.sticky_items++
       @sticky[key] = item
-      @sticky_size++
       return item.get()
     @shrink()
-    @lru_size++
     item.add_after @head
+    @stats.lru_items++
+    @stats.added++
     @map[key] = item
-    @gauge_size()
     item.get()

   remove: (key) ->
     if item = @map[key]
       delete @map[key]
       item.remove()
-      stats.increment 'remove'
-      @lru_size--
-      @gauge_size()
+      @stats.lru_items--
+      @stats.removed++
       item.value

   get: (key) ->
     if (item = @sticky[key]) != undefined
-      stats.increment 'hit'
+      @stats.sticky_hits++
       item.get()
     else if item = @map[key]
       item.hits++
-      stats.increment 'hit'
+      @stats.lru_hits++
       unless item == @head.next
         item.remove()
         item.add_after @head
       item.get()
     else
-      stats.increment 'miss'
+      @stats.misses++
       undefined

   shrink: ->
     while true
-      break if @max_items && (@lru_size < @max_items)
+      break if @max_items && (@stats.lru_items < @max_items)
       @remove @head.prev.key

   keys: ->
@@ -66,10 +69,6 @@
     while (item = next) != @head
       next = item.next
       item.key
-
-  gauge_size: ->
-    stats.gauge 'size.lru', @lru_size
-    stats.gauge 'size.sticky', @sticky_size

 class CacheItem


Disable avatar click since it leads to Google+ that's not working anymore.

--- src/ui/views/messages.coffee
+++ src/ui/views/messages.coffee
@@ -205,7 +205,7 @@

 drawMessageAvatar = (u, sender, viewstate, entity) ->
     div class: 'sender-wrapper', ->
-        a href:linkto(u.cid), title: sender, {onclick}, class:'sender', ->
+        a title: sender, class:'sender', ->
             drawAvatar(u.cid, viewstate, entity)
         span sender


Update de-DE.coffee

Update de-DE.coffee

typo

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -620,7 +620,7 @@
     Wir starteten CodeCombat um Lernen dieses Gefühl der Zauberkraft in ihren Fingerspitzen zu geben während sie getippten Code benutzen." # {change}
     mission_description_2: "Wie sich herausstellt, können sie dadurch auch schneller lernen. Sehr viel schneller.
     Es ist mehr wie eine Unterhaltung als das Lesen einer Bedienungsanleitung. Wir wollen diese Unterhaltung in jede Schule zu
-    jedem Schüler bringen, weil jeder sollte die Chance haben die Magie des Programmierens zu erlernen." # {change}
+    jedem Schüler bringen, weil jeder die Chance haben sollte die Magie des Programmierens zu erlernen." # {change}
     team_title: "Triff das CodeCombat Team"
     team_values: "Wir legen Wert auf einen offenen und respektvollen Dialog, wo die besten Ideen gewinnen. Unsere Entscheidungen basieren auf Benutzerforschung und unser Fortschritt richtet sich danach ihnen handfeste Resultate zu liefern. Jeder packt mit an von unserem Geschäftsführer bis hin zu unseren  Github-Unterstützern, weil wir Wert auf Zuwachs und Lernen in unserem Team legen."
     nick_title: "Mitbegründer, Geschäftsführer" # {changed}


a `Comic#mime()` method return the right mimetype

--- lib/comic.coffee
+++ lib/comic.coffee
@@ -20,6 +20,18 @@
     redis.zadd [@key(), @time, @url], (err, res) ->
       # TODO: Check success and pass loading through Comic.at
       cb(err, this)
+
+  # Return the mime type of the image
+  mime: ->
+    ext = @url[-3..].toLowerCase()
+    return 'image/' +
+      if ext in ['jpeg', 'jpg']
+        'jpeg'
+      else
+        ext
+
+    base += 'png' if @url[-3..].toLowerCase() == 'png'
+    base += 'png'

   # Populate `prev` and `next` if possible, then invoke callback
   # The new properties will be populated with timestamps, not comic objects


cleaned up and optimized fix for #1354 (d91ccd4003fea41b5a03312662d17e8d64c7e402)

--- nodes.coffee
+++ nodes.coffee
@@ -1349,12 +1349,13 @@
   invert: NEGATE

   compileNode: (o) ->
-    isArray = @array instanceof Value and @array.isArray()
-    splat   = obj for obj in @array.base.objects when obj instanceof Splat if isArray
-    if isArray and not splat
-      @compileOrTest o
-    else
-      @compileLoopTest o
+    if @array instanceof Value and @array.isArray()
+      for obj in @array.base.objects when obj instanceof Splat
+        hasSplat = yes
+        break
+      # `compileOrTest` only if we have an array literal with no splats
+      return @compileOrTest o unless hasSplat
+    @compileLoopTest o

   compileOrTest: (o) ->
     [sub, ref] = @object.cache o, LEVEL_OP


Fixes #1055 -- disallow invalid keys in object literals ... but allow them where the implicit object is being used as a class defn' or a destructuring

--- nodes.coffee
+++ nodes.coffee
@@ -882,7 +882,7 @@
     return [@makeCode(if @front then '({})' else '{}')] unless props.length
     if @generated
       for node in props when node instanceof Value
-        throw new Error 'cannot have an implicit value in an implicit object'
+        throw new SyntaxError 'cannot have an implicit value in an implicit object'
     idt         = o.indent += TAB
     lastNoncom  = @lastNonComment @properties
     answer = []
@@ -894,6 +894,8 @@
       else
         ',\n'
       indent = if prop instanceof Comment then '' else idt
+      if prop instanceof Assign and prop.variable instanceof Value and prop.variable.hasProperties()
+        throw new SyntaxError 'Invalid object key'
       if prop instanceof Value and prop.this
         prop = new Assign prop.properties[0].name, prop, 'object'
       if prop not instanceof Comment
@@ -1002,9 +1004,9 @@
         func = assign.value
         if base.value is 'constructor'
           if @ctor
-            throw new Error 'cannot define more than one constructor in a class'
+            throw new SyntaxError 'cannot define more than one constructor in a class'
           if func.bound
-            throw new Error 'cannot define a constructor as a bound function'
+            throw new SyntaxError 'cannot define a constructor as a bound function'
           if func instanceof Code
             assign = @ctor = func
           else
@@ -1237,7 +1239,7 @@
     # Disallow conditional assignment of undefined variables.
     if not left.properties.length and left.base instanceof Literal and
            left.base.value != "this" and not o.scope.check left.base.value
-      throw new Error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been defined."
+      throw new SyntaxError "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been defined."
     if "?" in @context then o.isExistentialEquals = true
     new Op(@context[...-1], left, new Assign(right, @value, '=') ).compileToFragments o


now self-compiling array/object/range comprehensions.

--- nodes.coffee
+++ nodes.coffee
@@ -36,8 +36,6 @@
 exports.ParentheticalNode : -> @name: this.constructor.name; @values: arguments
 exports.IfNode            : -> @name: this.constructor.name; @values: arguments

-exports.Expressions.wrap  : (values) -> @values: values
-
 # Some helper functions

 # Tabs are two spaces for pretty printing.
@@ -71,8 +69,10 @@

 # Merge objects.
 merge: (src, dest) ->
-  (dest[key]: val) for key, val of src
-  dest
+  fresh: {}
+  (fresh[key]: val) for key, val of src
+  (fresh[key]: val) for key, val of dest
+  fresh

 # Do any of the elements in the list pass a truth test?
 any: (list, test) ->
@@ -494,8 +494,8 @@
   # part of a comprehension, slice, or splice.
   # TODO: This generates pretty ugly code ... shrink it.
   compile_array: (o) ->
-    body: Expressions.wrap(new LiteralNode 'i')
-    arr:  Expressions.wrap(new ForNode(body, {source: (new ValueNode(this))}, 'i'))
+    body: Expressions.wrap([new LiteralNode('i')])
+    arr:  Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, 'i')])
     (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)

 }
@@ -571,9 +571,9 @@
   wrap: (array, expressions) ->
     expr: expressions.unwrap()
     return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only()
-    Expressions.wrap(new CallNode(
+    Expressions.wrap([new CallNode(
       new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr]
-    ))
+    )])

 }

@@ -581,9 +581,9 @@
 ClosureNode: exports.ClosureNode: {

   wrap: (expressions, statement) ->
-    func: new ParentheticalNode(new CodeNode([], Expressions.wrap(expressions)))
+    func: new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])))
     call: new CallNode(new ValueNode(func, new AccessorNode(new LiteralNode('call'))), [new LiteralNode('this')])
-    if statement then Expressions.wrap(call) else call
+    if statement then Expressions.wrap([call]) else call

 }

@@ -871,42 +871,119 @@

 }

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+# The replacement for the for loop is an array comprehension (that compiles)
+# into a for loop. Also acts as an expression, able to return the result
+# of the comprehenion. Unlike Python array comprehensions, it's able to pass
+# the current index of the loop as a second parameter.
+ForNode: exports.ForNode: inherit Node, {
+
+  constructor: (body, source, name, index) ->
+    @body:    body
+    @name:    name
+    @index:   index or null
+    @source:  source.source
+    @filter:  source.filter
+    @step:    source.step
+    @object:  !!source.object
+    [@name, @index]: [@index, @name] if @object
+    @children: [@body, @source, @filter]
+    this
+
+  top_sensitive: ->
+    true
+
+  compile_node: (o) ->
+    top_level:      del(o, 'top') and not o.returns
+    range:          @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length
+    source:         if range then @source.base else @source
+    scope:          o.scope
+    name:           @name and @name.compile(o)
+    index:          @index and @index.compile(o)
+    name_found:     name and scope.find(name)
+    index_found:    index and scope.find(index)
+    body_dent:      @idt(1)
+    rvar:           scope.free_variable() unless top_level
+    svar:           scope.free_variable()
+    ivar:           if range then name else index or scope.free_variable()
+    var_part:       ''
+    body:           Expressions.wrap([@body])
+    if range
+      index_var:    scope.free_variable()
+      source_part:  source.compile_variables(o)
+      for_part:     index_var + '=0, ' + source.compile(merge(o, {index: ivar, step: @step})) + ', ' + index_var + '++'
+    else
+      index_var:    null
+      source_part:  svar + ' = ' + @source.compile(o) + ';\n' + @idt()
+      step_part:    if @step then ivar + ' += ' + @step.compile(o) else ivar + '++'
+      for_part:     if @object then ivar + ' in ' + svar else ivar + ' = 0; ' + ivar + ' < ' + svar + '.length; ' + step_part
+      var_part:     body_dent + name + ' = ' + svar + '[' + ivar + '];\n' if name
+    set_result:     if rvar then @idt() + rvar + ' = []; ' else @idt()
+    return_result:  rvar or ''
+    body:           ClosureNode.wrap(body, true) if top_level and @contains (n) -> n instanceof CodeNode
+    body:           PushNode.wrap(rvar, body) unless top_level
+    if o.returns
+      return_result: 'return ' + return_result
+      del o, 'returns'
+      body:         new IfNode(@filter, body, null, {statement: true}) if @filter
+    else if @filter
+      body:         Expressions.wrap([new IfNode(@filter, body)])
+    if @object
+      o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true)
+      call: new CallNode(
+        new ValueNode(new LiteralNode('__hasProp'), [new AccessorNode(new LiteralNode('call'))])
+        [new LiteralNode(svar), new LiteralNode(ivar)]
+      )
+      body: Expressions.wrap([new IfNode(
+        call, Expressions.wrap([body]), null, {statement: true}
+      )])
+    return_result:  '\n' + @idt() + return_result + ';' unless top_level
+    body:           body.compile(merge(o, {indent: body_dent, top: true}))
+    vars:           if range then name else name + ', ' + ivar
+    set_result + source_part + 'for (' + for_part + ') {\n' + var_part + body + '\n' + @idt() + '}\n' + @idt() + return_result
+
+}
+
+statement ForNode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


Fixes #1328

Fixes #1328

--- nodes.coffee
+++ nodes.coffee
@@ -907,9 +907,6 @@
   constructor: (@variable, @value, @context, options) ->
     @param = options and options.param

-  # Matchers for detecting class/method names
-  METHOD_DEF: /^(?:(\S+)\.prototype\.|\S+?)?\b([$A-Za-z_][$\w\x7f-\uffff]*)$/
-
   children: ['variable', 'value']

   assigns: (name) ->
@@ -935,7 +932,7 @@
         o.scope.add name, 'var'
       else
         o.scope.find name
-    if @value instanceof Code and match = @METHOD_DEF.exec name
+    if @value instanceof Code and match = METHOD_DEF.exec name
       @value.name  = match[2]
       @value.klass = match[1] if match[1]
     val = @value.compile o, LEVEL_LIST
@@ -1802,6 +1799,7 @@

 IDENTIFIER = /^[$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]*$/
 SIMPLENUM  = /^[+-]?\d+$/
+METHOD_DEF = /^(?:([$A-Za-z_][$\w\x7f-\uffff]*)\.prototype\.)?([$A-Za-z_][$\w\x7f-\uffff]*)$/

 # Is a literal value a string?
 IS_STRING = /^['"]/


unused js model

unused js model

--- app/assets/javascripts/models/agent.js.coffee
+++ app/assets/javascripts/models/agent.js.coffee
@@ -1,4 +0,0 @@
-
-namespace 'Bixby.model', (exports, top) ->
-
-  class exports.Agent extends Stark.Model


fix pjax inner container support

fix pjax inner container support

--- app/assets/javascripts/ab_admin/core/pjax.js.coffee
+++ app/assets/javascripts/ab_admin/core/pjax.js.coffee
@@ -6,17 +6,19 @@
       e.preventDefault()
       container = $(this).data('pjax-container')
       unless container
-        table = $(this).closest('table.index_table')
+        table = $(this).closest('table.index_table:not(#list)')
         if table[0]
           container = "##{table.attr('id')}"
         else
           container = '[data-pjax-container]'

+      main_pjax_wrap = container == '[data-pjax-container]'
       $.pjax
         container: container
         url: $(this).data('href') || $(this).attr('href')
         timeout: 50000
-        push: container == '#list'
+        push: main_pjax_wrap
+        scrollTo: main_pjax_wrap
     else if $(this).data('href')
       e.preventDefault()
       window.location = $(this).data('href')


fix XSS

fix XSS

--- app/assets/javascripts/pirates/codegui.coffee
+++ app/assets/javascripts/pirates/codegui.coffee
@@ -277,7 +277,7 @@

   @updateQueueSize = () ->
     watchlist = @get()
-    @_$size.html watchlist.length
+    @_$size.text watchlist.length

   @get = () ->
     watchlist = []


Finished the post categry treeview

Finished the post categry treeview

--- app/assets/javascripts/bank_train/post-level.coffee
+++ app/assets/javascripts/bank_train/post-level.coffee
@@ -95,7 +95,6 @@

   bind_events: ->
     that = this
-
     # 创建岗位
     # 点击创建按钮设置模态框（弹出模态框）
     @$elm.on "click",".post-crt", ->
@@ -108,7 +107,6 @@
         window.modal_dialog.set_body( msg.body )
       .error (msg) ->
         console.log(msg)
-
     # 点击提交表单(创建)
     window.modal_dialog.get_modal_dialog().on "submit",".modal-body .page-posts-form-new .simple_form",(event) ->
       event.preventDefault()
@@ -129,15 +127,12 @@
         that.set_success_im(msg)
       .error (msg) =>
         that.set_failure_im(msg)
-
     # 点击头 关闭模态框按钮 通过data 设置@global = false
     window.modal_dialog.get_modal_dialog().on "click", ".modal-content .modal-header .close", =>
       @global = jQuery(".modal-content .modal-header .close").data("global")
-
     # 点击脚 关闭模态框按钮 通过data 设置@global = false
     window.modal_dialog.get_modal_dialog().on "click", ".modal-content .modal-footer .close-modal", =>
       @global = jQuery(".modal-content .modal-footer .close-modal").data("global")
-
     # 点击 业务种类按钮 设置业务种类树（显示树 隐藏表单）
     window.modal_dialog.get_modal_dialog().on "click", ".modal-content .modal-body .post_business_categories .bsns_btn", =>
       if @global is false
@@ -176,7 +171,6 @@
         jQuery(".modal-content .modal-body .simple_form").addClass("hide")
         jQuery(".modal-content .modal-body .tree").removeClass("hide")
         jQuery(".modal-content .modal-body .tree-button").removeClass("hide")
-
     # 点击树中的确定按钮，隐藏树 显示表单
     window.modal_dialog.get_modal_dialog().on "click", ".modal-content .modal-body .tree-button", =>
       jQuery(".modal-content .modal-body .tree").addClass("hide")
@@ -197,7 +191,6 @@
         window.modal_dialog.set_body( msg.body )
       .error (msg) ->
         console.log(msg)
-
     # 点击提交表单(修改)
     window.modal_dialog.get_modal_dialog().on "submit",".modal-body .page-posts-form-edit .simple_form", ->
       post_id = $(this).closest(".page-posts-form-edit").attr("data-post-id")
@@ -283,7 +276,6 @@
         window.modal_dialog.set_body( msg.body )
       .error (msg) ->
         console.log(msg)
-
     # 提交表单(创建)
     window.modal_dialog.get_modal_dialog().on "submit",".modal-body .page-levels-new .simple_form", (event) ->
       event.preventDefault()
@@ -309,7 +301,6 @@
         window.modal_dialog.set_body( msg.body )
       .error (msg) ->
         console.log(msg)
-
     # 提交表单(修改)
     window.modal_dialog.get_modal_dialog().on "submit",".modal-body .page-levels-edit .simple_form", (event) ->
       level_id = $( this ).closest(".page-levels-edit").attr("data-level-id")


added guard to nested model #1126

added guard to nested model #1126

--- app/assets/javascripts/libs/nested_obj_model.coffee
+++ app/assets/javascripts/libs/nested_obj_model.coffee
@@ -8,7 +8,7 @@
     _.reduce(
       attributes
       (value, attribute) ->
-        value[attribute]
+        return value?[attribute]
       valueObj)


channel binding refactoring

channel binding refactoring

--- app/assets/javascripts/pirates/socketHandler.coffee
+++ app/assets/javascripts/pirates/socketHandler.coffee
@@ -52,18 +52,20 @@
     super "operations"
     @lifeTime = 0
     @operationQueue = []
-    @channel.bind "line", (data) =>
-      @operationQueue.push new Operation("line", data)
-    @channel.bind "left", (data) =>
-      @operationQueue.push new Operation("left", data)
-    @channel.bind "right", (data) =>
-      @operationQueue.push new Operation("right", data)
-    @channel.bind "move", (data) =>
-      @operationQueue.push new Operation("move", data)
-    @channel.bind "addBuoy", (data) =>
-      @operationQueue.push new Operation("addBuoy", data)
-    @channel.bind "done", (data) =>
-      @operationQueue.push new Operation("done", data)
+    @operations = [
+      'left',
+      'right',
+      'move',
+      'addBuoy',
+      'line',
+      'done'
+    ]
+
+    #bind operations for the operations channel
+    for op in @operations
+      @channel.bind op, (data) =>
+        @operationQueue.push new Operation(op, data)
+

   highlightLine: (line) ->
     if @lastLine?


add a framerate chart on lighting test, because why not?

--- app/assets/jax/controllers/lighting.js.coffee
+++ app/assets/jax/controllers/lighting.js.coffee
@@ -3,6 +3,7 @@
 SQ2_2 = Math.sqrt(2) / 2
 Jax.Controller.create "lighting",
   index: ->
+    @world.addObject new Jax.Framerate
     @teapot = @world.addObject new Jax.Model
       mesh: new Jax.Mesh.Teapot(size: 10)
       position: [0, 0, -25]


Fixes #3242 - Reporting does not update ticket list when the ticket count is the same (frontend cache bug).

--- app/assets/javascripts/app/controllers/report.coffee
+++ app/assets/javascripts/app/controllers/report.coffee
@@ -117,11 +117,11 @@
         dataNew[key] = value
       @ui.storeParams()

-    if !@lastNewData
-      @lastNewData = {}
-
-    return if @lastNewData && JSON.stringify(dataNew) is JSON.stringify(@lastNewData)
-    @lastNewData = dataNew
+    if !@lastParams
+      @lastParams = {}
+
+    return if @lastParams && JSON.stringify(@params) is JSON.stringify(@lastParams)
+    @lastParams = $.extend(true, {}, @params)

     @draw(dataNew)
     t = new Date


Removing unnecesary anyComputableRole

Removing unnecesary anyComputableRole

--- app/coffee/modules/admin/third-parties.coffee
+++ app/coffee/modules/admin/third-parties.coffee
@@ -46,7 +46,6 @@

         @scope.sectionName = "Github" #i18n
         @scope.project = {}
-        @scope.anyComputableRole = true

         promise = @.loadInitialData()

@@ -63,8 +62,6 @@
         return @rs.projects.get(@scope.projectId).then (project) =>
             @scope.project = project
             @scope.$emit('project:loaded', project)
-            @scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable))
-
             return project

     loadInitialData: ->
@@ -97,8 +94,6 @@

         @scope.sectionName = "Gitlab" #i18n
         @scope.project = {}
-        @scope.anyComputableRole = true
-
         promise = @.loadInitialData()

         promise.then () =>
@@ -117,8 +112,6 @@
         return @rs.projects.get(@scope.projectId).then (project) =>
             @scope.project = project
             @scope.$emit('project:loaded', project)
-            @scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable))
-
             return project

     loadInitialData: ->
@@ -151,8 +144,6 @@

         @scope.sectionName = "Bitbucket" #i18n
         @scope.project = {}
-        @scope.anyComputableRole = true
-
         promise = @.loadInitialData()

         promise.then () =>
@@ -171,8 +162,6 @@
         return @rs.projects.get(@scope.projectId).then (project) =>
             @scope.project = project
             @scope.$emit('project:loaded', project)
-            @scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable))
-
             return project

     loadInitialData: ->


Clean up code

Clean up code

--- updaters/prefixes.coffee
+++ updaters/prefixes.coffee
@@ -176,17 +176,13 @@
             props:  ['cursor']
             # Chrome added this 3.0, but is flagged as starting in 4 because
             # that was when the grab/grabbing prefixed property was added
-            browsers: browsers.push ["chrome 3"]
+            browsers: browsers.push ['chrome 3']
     prefix 'grab', 'grabbing',
             props:  ['cursor']
-            # Firefox didn't unprefix the grab/grabbing cursors till v27, but the
-            # caniuse data flagged it as clean in 24 because the zoom-in/zoom-out
-            # version was dropped.
-            browsers: browsers.push [
-              "ff 24"
-              "ff 25"
-              "ff 26"
-            ]
+            # Firefox didn't unprefix the grab/grabbing cursors till v27,
+            # but the caniuse data flagged it as clean in 24 because
+            # the zoom-in/zoom-out version was dropped.
+            browsers: browsers.push ['ff 24', 'ff 25', 'ff 26']

   # Sticky position
   @feature 'css-sticky', (browsers) =>


Try different route to fix issue.

Try different route to fix issue.

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -200,9 +200,9 @@

     reportError: (id) ->
         result = ""
-        while result != null and result == ""
+        while !result? and result == ""
             result = window.prompt("Enter reason for reporting this entry:")
-        if result != null
+        if result?
             query = $.ajax "/businesses/report/" + id, {type: "POST", data: {
                 reason: result
             }}


removed timeout from requireJS configuration

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -1,6 +1,8 @@
 require.config

   baseUrl : "/assets/javascripts"
+
+  waitSeconds : 0

   paths :
     "jquery" : "libs/jquery-1.8.3.min"


OX-187 performance testing on server

OX-187 performance testing on server

--- app/assets/javascripts/model/binary/pullqueue.coffee
+++ app/assets/javascripts/model/binary/pullqueue.coffee
@@ -27,16 +27,18 @@
     @bucketCount = 0
     @roundTripTimes = []
     @tests = [
-      { batchSize: 1, batchLimit: 1, socket: @getLoadBucketWebsocket(), desc: "Websocket 1/1" }
-      { batchSize: 100, batchLimit: 1, socket: @getLoadBucketWebsocket(), desc: "Websocket 100/1" }
-      { batchSize: 1, batchLimit: 100, socket: @getLoadBucketWebsocket(), desc: "Websocket 1/100" }
+#      { batchSize: 1, batchLimit: 1, socket: @getLoadBucketAjax(), desc: "Ajax 1/1" }
+#      { batchSize: 100, batchLimit: 1, socket: @getLoadBucketAjax(), desc: "Ajax 100/1" }
+#      { batchSize: 1, batchLimit: 100, socket: @getLoadBucketAjax(), desc: "Ajax 1/100" }
+#      { batchSize: 10, batchLimit: 10, socket: @getLoadBucketAjax(), desc: "Ajax 10/10" }
+#      { batchSize: 5, batchLimit: 5, socket: @getLoadBucketAjax(), desc: "Ajax 5/5" }
+
+      { batchSize: 1, batchLimit: 10, socket: @getLoadBucketWebsocket(), desc: "Websocket 1/10" }
+      { batchSize: 2, batchLimit: 10, socket: @getLoadBucketWebsocket(), desc: "Websocket 2/10" }
       { batchSize: 10, batchLimit: 10, socket: @getLoadBucketWebsocket(), desc: "Websocket 10/10" }
-      { batchSize: 5, batchLimit: 5, socket: @getLoadBucketWebsocket(), desc: "Websocket 5/5" }
-      { batchSize: 1, batchLimit: 1, socket: @getLoadBucketAjax(), desc: "Ajax 1/1" }
-      { batchSize: 100, batchLimit: 1, socket: @getLoadBucketAjax(), desc: "Ajax 100/1" }
-      { batchSize: 1, batchLimit: 100, socket: @getLoadBucketAjax(), desc: "Ajax 1/100" }
-      { batchSize: 10, batchLimit: 10, socket: @getLoadBucketAjax(), desc: "Ajax 10/10" }
-      { batchSize: 5, batchLimit: 5, socket: @getLoadBucketAjax(), desc: "Ajax 5/5" }
+      { batchSize: 1, batchLimit: 7, socket: @getLoadBucketWebsocket(), desc: "Websocket 1/7" }
+      { batchSize: 2, batchLimit: 7, socket: @getLoadBucketWebsocket(), desc: "Websocket 2/7" }
+      { batchSize: 10, batchLimit: 7, socket: @getLoadBucketWebsocket(), desc: "Websocket 10/7" }
     ]

   fillQueue : ->


front-end refactoring #65

front-end refactoring #65

--- app/assets/javascripts/oxalis/view/mergemodal.coffee
+++ app/assets/javascripts/oxalis/view/mergemodal.coffee
@@ -75,9 +75,9 @@

-            Explorativs
-

-

+            Explorativs
+

+

                 <button class="btn btn-primary" id="explorative-merge">Merge</button>

@@ -92,7 +92,7 @@
     "task"        : ".task"
     "tasktype"    : ".task-type"
     "project"     : ".project"
-    "explorativs" : ".explorativs"
+    "explorative" : ".explorative"

   events :
     "click #task-merge"               : "mergeTask"
@@ -107,7 +107,7 @@
     "task"                 : ".task"
     "tasktype"             : ".task-type"
     "project"              : ".project"
-    "explorativs"          : ".explorativs"
+    "explorative"          : ".explorative"
     "uploadAndExploreForm" : "#upload-and-explore-form"
     "formSpinnerIcon"      : "#form-spinner-icon"
     "formUploadIcon"       : "#form-upload-icon"
@@ -149,7 +149,7 @@
       @task       .show(@taskSelectionView)
       @tasktype   .show(@taskTypeSelectionView)
       @project    .show(@projectSelectionView)
-      @explorativs.show(@explorativSelectionView)
+      @explorative.show(@explorativSelectionView)
     )

@@ -185,7 +185,7 @@

   mergeExplorative : ->

-    explorativId = @ui.explorativs.find("select :selected").val()
+    explorativId = @ui.explorative.find("select :selected").val()
     url = "/annotations/Explorational/#{explorativId}/merge/#{@_model.tracingType}/#{@_model.tracingId}"
     @merge(url)


Indicatorate EDE feeds

Indicatorate EDE feeds

--- indicatorators/ede.coffee
+++ indicatorators/ede.coffee
@@ -1,5 +1,6 @@
 request = require('request')
 fs      = require('fs')
+_       = require('underscore')

 EDE_API_URL = "http://geodev.grid.unep.ch/api"

@@ -14,7 +15,26 @@
     unless @data?
       throw "You must call fetchDataFromService before indicatorating"

-    console.log @data
+    valueField = @indicatorDefinition.valueField
+
+    outputRows = []
+
+    for row in @data
+      value = row[valueField]
+      continue unless value?
+      text = @calculateIndicatorText(value)
+      outputRows.push(_.extend(row, text: text))
+
+    return @data
+
+  calculateIndicatorText: (value) ->
+    value = parseFloat(value)
+    ranges = @indicatorDefinition.ranges
+
+    for range in ranges
+      return range.message if value > range.minValue
+
+    return "Error: Value #{value} outside expected range"

   makeGetUrl: ->
     "#{EDE_API_URL}/countries/#{@countryCode}/variables/#{@variableId}"


Indicate number of comments in discussion header

--- app/assets/javascripts/views/question_view.js.coffee
+++ app/assets/javascripts/views/question_view.js.coffee
@@ -39,7 +39,7 @@
           )

         @div class: 'span4', =>
-          @h5 'Discussion'
+          @h5 'Discussion', outlet: 'discussionHeader'
           @subview 'discussion', new Views.DiscussionView(question.comments())

   initialize: (@question) ->
@@ -77,6 +77,10 @@
     @subscriptions.add @question.votes().onInsert => @updateShowAllVotesLink()
     @subscriptions.add @question.votes().onRemove => @updateShowAllVotesLink()
     @updateShowAllVotesLink()
+
+    @subscriptions.add @question.comments().onInsert => @updateDiscussionHeader()
+    @subscriptions.add @question.comments().onRemove => @updateDiscussionHeader()
+    @updateDiscussionHeader()

     unless @question.creator() == Models.User.getCurrent()
       @editButton.hide()
@@ -196,6 +200,13 @@
     @collectiveVote.find('.answer').each ->
       $(this).view().updateRankIndicator()

+  updateDiscussionHeader: ->
+    count = @question.comments().size()
+    if count == 1
+      @discussionHeader.text("1 Comment")
+    else
+      @discussionHeader.text("#{count} Comments")
+
   remove: (selector, keepData) ->
     super
     unless keepData


Switch legislation/votes jobs to use next day.

--- jobs/poll-gga.coffee
+++ jobs/poll-gga.coffee
@@ -10,6 +10,10 @@
   msUntilNextWeek = ->
     startOfNextWeek = moment().startOf('week').add('weeks', 1)
     startOfNextWeek.diff(moment(), 'milliseconds')
+
+  msUntilNextDay = ->
+    startOfNextDay = moment().startOf('day').add('days', 1)
+    startOfNextDay.diff(moment(), 'milliseconds')

   msUntilNextHour = ->
     startOfNextHour = moment().startOf('hour').add('hours', 1)
@@ -56,31 +60,31 @@
   jobs.process 'poll legislation', (job, callback) ->
     db.collection("sessions").findOne {current: true}, (err, currentSession) ->
       if err
-        jobs.create('poll legislation').delay(msUntilNextHour()).save()
+        jobs.create('poll legislation').delay(msUntilNextDay()).save()
         callback(err)
         return

       if ! currentSession?
-        jobs.create('poll legislation').delay(msUntilNextHour()).save()
+        jobs.create('poll legislation').delay(msUntilNextDay()).save()
         callback("No current session found.")
         return

       jobs.create('import legislation for session', session: currentSession).save()
-      jobs.create('poll legislation').delay(msUntilNextHour()).save()
+      jobs.create('poll legislation').delay(msUntilNextDay()).save()
       callback()

   jobs.process 'poll votes', (job, callback) ->
     db.collection("sessions").findOne {current: true}, (err, currentSession) ->
       if err
-        jobs.create('poll votes').delay(msUntilNextHour()).save()
+        jobs.create('poll votes').delay(msUntilNextDay()).save()
         callback(err)
         return

       if ! currentSession?
-        jobs.create('poll votes').delay(msUntilNextHour()).save()
+        jobs.create('poll votes').delay(msUntilNextDay()).save()
         callback("No current session found.")
         return

       jobs.create('import all votes for session', session: currentSession).save()
-      jobs.create('poll votes').delay(msUntilNextHour()).save()
+      jobs.create('poll votes').delay(msUntilNextDay()).save()
       callback()


adding debug

adding debug

--- src/whitelist-manager.coffee
+++ src/whitelist-manager.coffee
@@ -1,8 +1,8 @@
-_                    = require 'lodash'
-async                = require 'async'
-ListChecker          = require 'meshblu-list-checker'
-DeviceTransmogrifier = require 'meshblu-device-transmogrifier'
-
+_                     = require 'lodash'
+async                 = require 'async'
+ListChecker           = require 'meshblu-list-checker'
+DeviceTransmogrifier  = require 'meshblu-device-transmogrifier'
+debug                 = require("debug")("meshblu-core-manager-whitelist")
 class WhitelistManager
   constructor: ({@datastore,@uuidAliasResolver}) ->

@@ -31,6 +31,7 @@
       owner: true
       uuid: true

+    debug "checking if #{toUuid} can #{field} to #{fromUuid}"
     @uuidAliasResolver.resolve toUuid, (error, toUuid) =>
       return callback error if error?
       @datastore.findOne {uuid: toUuid}, projection, (error, toDevice) =>


Update all text editor styles in one batch

This allows DOM mutation observers to detect changes to the global
styles.

--- src/workspace-element.coffee
+++ src/workspace-element.coffee
@@ -12,7 +12,6 @@

   createdCallback: ->
     @subscriptions = new CompositeDisposable
-    @initializeGlobalTextEditorStyleSheet()
     @initializeContent()
     @observeScrollbarStyle()
     @observeTextEditorFontConfig()
@@ -25,10 +24,6 @@
   detachedCallback: ->
     @subscriptions.dispose()
     @model.destroy()
-
-  initializeGlobalTextEditorStyleSheet: ->
-    atom.styles.addStyleSheet('atom-text-editor {}', sourcePath: 'global-text-editor-styles')
-    @globalTextEditorStyleSheet = document.head.querySelector('style[source-path="global-text-editor-styles"]').sheet

   initializeContent: ->
     @classList.add 'workspace'
@@ -54,9 +49,20 @@
           @classList.add("scrollbars-visible-when-scrolling")

   observeTextEditorFontConfig: ->
-    @subscriptions.add atom.config.observe 'editor.fontSize', @setTextEditorFontSize.bind(this)
-    @subscriptions.add atom.config.observe 'editor.fontFamily', @setTextEditorFontFamily.bind(this)
-    @subscriptions.add atom.config.observe 'editor.lineHeight', @setTextEditorLineHeight.bind(this)
+    @updateGlobalTextEditorStyleSheet()
+    @subscriptions.add atom.config.onDidChange 'editor.fontSize', @updateGlobalTextEditorStyleSheet.bind(this)
+    @subscriptions.add atom.config.onDidChange 'editor.fontFamily', @updateGlobalTextEditorStyleSheet.bind(this)
+    @subscriptions.add atom.config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this)
+
+  updateGlobalTextEditorStyleSheet: ->
+    styleSheetSource = """
+      atom-text-editor {
+        font-size: #{atom.config.get('editor.fontSize')}px;
+        font-family: #{atom.config.get('editor.fontFamily')};
+        line-height: #{atom.config.get('editor.lineHeight')};
+      }
+    """
+    atom.styles.addStyleSheet(styleSheetSource, sourcePath: 'global-text-editor-styles')

   createSpacePenShim: ->
     WorkspaceView ?= require './workspace-view'
@@ -86,20 +92,6 @@
     this

   getModel: -> @model
-
-  setTextEditorFontSize: (fontSize) ->
-    @updateGlobalEditorStyle('font-size', fontSize + 'px')
-
-  setTextEditorFontFamily: (fontFamily) ->
-    @updateGlobalEditorStyle('font-family', fontFamily)
-
-  setTextEditorLineHeight: (lineHeight) ->
-    @updateGlobalEditorStyle('line-height', lineHeight)
-
-  updateGlobalEditorStyle: (property, value) ->
-    editorRule = @globalTextEditorStyleSheet.cssRules[0]
-    editorRule.style[property] = value
-    atom.themes.emitter.emit 'did-update-stylesheet', @globalTextEditorStyleSheet

   handleFocus: (event) ->
     @model.getActivePane().activate()


Fix adding levels to campaigns

Fix adding levels to campaigns

--- app/views/editor/campaign/CampaignEditorView.coffee
+++ app/views/editor/campaign/CampaignEditorView.coffee
@@ -158,7 +158,7 @@
     index = 0
     for levelOriginal, campaignLevel of campaignLevels
       level = @levels.findWhere({original: levelOriginal})
-      if level.get('campaignIndex') isnt index
+      if level and level.get('campaignIndex') isnt index
         level.set('campaignIndex', index)
       campaignLevel.campaignIndex = index
       index += 1


shells.Registry: clone doesn't for classes.

--- coffee/src/shells/registry.coffee
+++ coffee/src/shells/registry.coffee
@@ -24,7 +24,7 @@
     # populate shellModule with default properties from Shell
     _.each acorn.shells.Shell, (value, key) ->
       unless shellModule[key]?
-        shellModule[key] = _.clone value
+        class shellModule[key] extends value

     # validate shellModule's Model and View properties
     class_properties = [ 'Model', 'ContentView', 'SummaryView', 'EditView']


Set start panel with flight.launch()

Set start panel with flight.launch()

--- js/routers/AppRouter.coffee
+++ js/routers/AppRouter.coffee
@@ -3,6 +3,9 @@
   routes:
     '': 'homePage'
     'panel/:id': 'goToPanel'
+
+  initialize: ->
+    flight.launch("#panel-1")

   homePage: ->
     @view = new app.Views.Home


load factlink earlier

load factlink earlier

--- app/js/loader/loader_common.coffee
+++ app/js/loader/loader_common.coffee
@@ -32,7 +32,7 @@
     content && el.appendChild(content)
     el

-  onReady = ->
+  whenHasBody = ->
     # Add styles
     style_tag = mkEl 'style', null, document.createTextNode(style_code)
     document.getElementsByTagName('head')[0].appendChild style_tag
@@ -43,7 +43,7 @@
     # Wrapper for increased CSS specificity
     outerWrapperEl = mkEl 'div', 'factlink-containment-wrapper', jslib_jail_iframe

-    document.body.appendChild outerWrapperEl
+    document.body.insertBefore(outerWrapperEl, document.body.firstChild)

     load_time_before_jail = new Date().getTime()

@@ -63,11 +63,15 @@
     jslib_jail_doc.documentElement.appendChild(script_tag)

     root = jslib_jail_iframe.contentWindow.FactlinkJailRoot
+    $ = jslib_jail_iframe.contentWindow.$
     root.perf.add_existing_timing_event 'factlink_loader_start', factlink_loader_start_timestamp
     root.perf.add_existing_timing_event 'before_jail', load_time_before_jail
     root.perf.add_timing_event 'after_jail'

-    root.core_loaded_promise.done ->
+
+    root.core_loaded_promise
+    .then( -> root.ready_promise)
+    .then ->
       #called from jail-iframe when core iframe is ready.
       for name in methods
         do (name) ->
@@ -82,11 +86,15 @@
         window.FACTLINK[methodCall.name](methodCall.arguments...)
       storedMethodCalls = undefined

-  if /^(interactive|complete)$/.test(document.readyState)
-    onReady()
-  else
-    document.addEventListener('DOMContentLoaded',onReady);

+  tryToInit = (i) -> ->
+    if document.body
+      whenHasBody()
+    else
+      console.log 'waiting #{i}...'
+      setTimeout i, tryToInit(i+1)
+
+  tryToInit(1)()

 jslib_jail_code = __INLINE_JS_PLACEHOLDER__
 style_code = __INLINE_CSS_PLACEHOLDER__