Groups: VM not deleted bug is fixed

Groups: VM not deleted bug is fixed

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -467,12 +467,12 @@
               return callback err  if err
               JVM.ensureDomainSettings {account, vm, type, nickname, groupSlug}
               if type is 'group'
-                @addVmUsers vm, group, ->
+                @addVmUsers user, vm, group, ->
                   callback null, vm
               else
                 callback null, vm

-  @addVmUsers = (vm, group, callback)->
+  @addVmUsers = (user, vm, group, callback)->
     # todo - do this operation in batches
     selector =
       sourceId    : group.getId()
@@ -506,7 +506,8 @@
             vmUsers = []

             vmUsers = sourceIds.map (rec)->
-              { id: rec.sourceId, sudo: yes }
+              owner = if rec.sourceId.equals user.getId() then yes else no
+              { id: rec.sourceId, sudo: yes, owner }

             return vm.update {
               $set: users: vmUsers
@@ -638,63 +639,46 @@
     JVM.removeRelatedDomains this
     super callback

-  @deleteVM = (vm, callback)->
-    if vm.planCode is 'free'
-      vm.remove callback
-    else
-      JPaymentSubscription.one
-        planCode : vm.subscriptionCode
-        $or      : [
-          {status: 'active'}
-          {status: 'canceled'}
-        ]
-      , (err, subscription)->
-        if err or not subscription
-          return callback { message: 'Unable to update subscription.' }
-
-        if subscription.status is 'canceled'
-          vm.remove callback
-        else
-          JPaymentPack.one { planCode: vm.planCode }, (err, pack) ->
-            return callback err  if err
-
-            subscription.credit pack, (err) ->
-              return callback err  if err
-
-              vm.remove callback
+  credit: (group, callback)->
+    group.fetchSubscription (err, subscription)=>
+      return callback err  if err
+      return callback new KodingError 'Group subscription not found'  unless subscription
+
+      if subscription.status is 'canceled'
+        @remove callback
+      else
+        subscription.creditPack tag : "vm", (err) =>
+          return callback err  if err
+          @remove callback

   @removeByHostname = secure (client, hostnameAlias, callback)->
     {delegate} = client.connection

-    delegate.fetchUser (err, user) =>
-      return callback err  if err
-      return callback { message: "user not found" }  unless user
+    fetchVmByHostname delegate, hostnameAlias, (err, vm) ->
+      return callback err  if err
+      return callback new KodingError 'No such VM'  unless vm
+
+      [{ id: groupId }] = vm.groups
+      JGroup = require './group'
+      JGroup.one { _id: groupId }, (err, group)->
+        return callback err  if err
+        return callback new KodingError "Group not found"  unless group
+        JPermissionSet.checkPermission client, "delete vms", group, (err, hasPermission)->
+          return callback err  if err
+          return callback new KodingError "You do not have permission to delete this vm"  unless hasPermission
+          if vm.planCode is 'free' then vm.remove callback
+          else vm.credit group, callback
+
+  fetchVmByHostname = (account, hostnameAlias, callback) ->
+    account.fetchUser (err, user) =>
+      return callback err  if err
+      return callback new KodingError "user not found"  unless user

       selector =
         hostnameAlias : hostnameAlias
         users         : { $elemMatch: id: user.getId(), owner: yes }

-      JVM.one selector, (err, vm) =>
-        return callback err  if err
-        return callback new KodingError 'No such VM'  unless vm
-
-        delegate.hasTarget vm, 'owner', (err, hasTarget) =>
-          return callback err  if err
-
-          if hasTarget
-            @deleteVM vm, callback
-          else
-            [{ id: groupId }] = vm.groups
-
-            JGroup = require './group'
-            JGroup.one { _id: groupId }, (err, group)=>
-              return callback err  if err
-
-              JPermissionSet.checkPermission client, "delete vms", group,
-              (err, hasPermission)=>
-                return callback err  if err
-
-                @deleteVM vm, callback  if hasPermission
+      JVM.one selector, callback

   @addVm = ({ account, target, user, sudo, groups, groupSlug
              type, planCode, planOwner, webHome, uid }, callback)->


Tests: Disabled paidaccount tests.

Tests: Disabled paidaccount tests.

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -85,9 +85,9 @@
     { name: 'environments vm' }
   ]

-  [
-    { name: 'environments paidaccount' }
-  ]
+  # [
+  #   { name: 'environments paidaccount' }
+  # ]

   [
     { name: 'environments domain' }


Test/Myteam update parallel sets

Test/Myteam update parallel sets

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -21,7 +21,7 @@

 module.exports = [

-  # [
+  [
   #   { name: 'dashboard myaccount' }
   #   { name: 'dashboard stacks' }
   #   { name: 'dashboard virtualmachines' }
@@ -31,12 +31,12 @@
   #   { name: 'dashboard onboarding' }
   #   { name: 'dashboard sidebar' }
   #   { name: 'dashboard teambilling' }
-  # ]
+  ]

-  [
+  # [
     # { name: 'teams teams' }
     # { name: 'unittests' }
-  ]
+  # ]

   # [
   #   { name: 'ide file' }


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -44,7 +44,7 @@
       period: period
       lastFired: I.age

-
+  # TODO: Move this into a more
   sendOrApply: (fn) ->
     if typeof fn is "function"
       fn.apply(self)


Properly reload masonry once image uploaded.

--- app/assets/javascripts/images.js.coffee
+++ app/assets/javascripts/images.js.coffee
@@ -1,17 +1,13 @@
-window.setupMasonry = ->
+setupMasonry = ->
   $imagesContainer = $('#images')
   $window = $(window)

   $imagesContainer.masonry
     itemSelector: '.image'
-    isAnimated: true
     isResizable: true

-  $imagesContainer.find("img").load ->
-    $imagesContainer.masonry "reload"
-
   $imagesContainer.imagesLoaded ->
-    $imagesContainer.masonry "reload"
+    $imagesContainer.masonry('reload')
     $window.scroll()

   imageSize = 300
@@ -26,13 +22,12 @@
       containerWidth = imageSize * (columns - 1) - 15 + 40

     $imagesContainer.width(containerWidth)
-    $imagesContainer.masonry "reload"
+    $imagesContainer.masonry('reload')

   $window.resize()

 window.tmpl = (id) ->
-  console.log(id)
   if id is 'template-upload'
     return (data) -> JST['templates/file'](data)

@@ -87,7 +82,6 @@

   $imagesContainer = $('#images')

-
   $('#toggle_selection').click (e) ->
     e.preventDefault()
     if $(this).hasClass('active')
@@ -106,12 +100,13 @@
     downloadTemplateId: false
     uploadTemplate: (data) ->
       for file in data.files
-        window.file = file
         JST['templates/file'](file: file, formatFileSize: data.formatFileSize)
     done: (e, data) ->
       image = JST['templates/image'](image: data.result.image, id: data.result.id)
-      $imagesContainer.append(image)
+      $imagesContainer.prepend(image)
       data.context.find('.progress').replaceWith("Success")
+      $imagesContainer.imagesLoaded ->
+        $imagesContainer.masonry('reload')
     fail: (e, data) ->
       response = JSON.parse(data.xhr().response)
       error = "Error #{response.errors}	"


Remove splash.js.coffee

Remove splash.js.coffee

--- app/assets/javascripts/splash.js.coffee
+++ app/assets/javascripts/splash.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/


Polyfill Event API for IE/phantomjs compatablity

--- app/assets/javascripts/task_list.coffee
+++ app/assets/javascripts/task_list.coffee
@@ -94,6 +94,15 @@
     el = el.parentNode
   el

+createEvent = (eventName, detail) ->
+  if typeof Event == 'function'
+    event = new Event eventName, {bubbles: true, cancelable: true}
+    event.detail = detail
+  else
+    event = document.createEvent 'CustomEvent'
+    event.initCustomEvent eventName, true, true, detail
+  event
+
 class TaskList
   constructor: (@el, method) ->
     @container = closest @el, 'js-task-list-container'
@@ -112,9 +121,7 @@
       forEach (checkbox) ->
         checkbox.disabled = false
       @container.classList.add 'is-task-list-enabled'
-      event = new Event 'tasklist:enabled',
-        cancelable: true
-        bubbles: true
+      event = createEvent 'tasklist:enabled'
       @container.dispatchEvent event

   disable: ->
@@ -125,9 +132,7 @@
     forEach (checkbox) ->
       checkbox.disabled = true
     @container.classList.remove('is-task-list-enabled')
-    event = new Event 'tasklist:disabled',
-      cancelable: true
-      bubbles: true
+    event = createEvent 'tasklist:disabled'
     @container.dispatchEvent event

   # Updates the field value to reflect the state of item.
@@ -137,24 +142,16 @@
     checkboxes = @container.querySelectorAll('.task-list-item-checkbox')
     index = 1 + NodeArray(checkboxes).indexOf item

-    changeEvent = new Event 'tasklist:change',
-      cancelable: true
-      bubbles: true
-    changeEvent.detail =
+    changeEvent = createEvent 'tasklist:change',
       index: index
       checked: item.checked
     @field.dispatchEvent changeEvent

     unless changeEvent.defaultPrevented
       @field.value = TaskList.updateSource(@field.value, index, item.checked)
-      changeEvent = new Event 'change',
-        cancelable: true
-        bubbles: true
+      changeEvent = createEvent 'change'
       @field.dispatchEvent changeEvent
-      changedEvent = new Event 'tasklist:changed',
-        cancelable: true
-        bubbles: true
-      changedEvent.detail =
+      changedEvent = createEvent 'tasklist:changed',
         index: index
         checked: item.checked
       @field.dispatchEvent changedEvent


updated

updated

--- app/assets/javascripts/downloads.js.coffee
+++ app/assets/javascripts/downloads.js.coffee
@@ -24,11 +24,35 @@
   # コードをロード
   load_codes = ->
     codes = JSON.parse(localStorage.getItem("codes")) || []
-
+    return
+
   # コードを保存
   save_codes = ->
     localStorage.setItem("codes", JSON.stringify(codes))
+    return

+  # 全銘柄名取得
+  resolve_all_stock_names = ->
+    for code in codes
+      if !stock_names[code]
+        get_stock_name(code)
+        break
+    return
+
+  # 銘柄取得
+  get_stock_name = (code) ->
+    $.ajax
+      type: "GET"
+      url: "/stocks/name/#{code}"
+      dataType: "json"
+      success: (data, status, xhr) ->
+        name = "(不明)"
+        name = data.name if data.name
+        stock_names[code] = name
+        $("#stock_name_#{code}").text(name)
+        resolve_all_stock_names()
+    return
+
   # View
   render = ->
     tbody = $("#rows")
@@ -36,11 +60,20 @@
     for code in codes
       tr = $("

")
       tbody.append(tr)
-      tr.append($("	").text(code))
-      tr.append($("	"))
+
+      $("	").text(code).appendTo(tr)
+
+      td = $("	").appendTo(tr)
+      if stock_names[code]
+        td.text(stock_names[code])
+
       del = $("<button class='btn btn-danger'>削除</button>")
-      del.on "click", ->
-        on_remove_code(code)
+      # code の値をイベントハンドラ内で使用するクロージャにする
+      do (code) ->
+        del.on "click", ->
+            on_remove_code(code)
+          return
+        return
       tr.append($("	").append(del))
     return

@@ -52,20 +85,24 @@
       if add_code(code)
         save_codes()
         render()
+        resolve_all_stock_names()
     else
       alert("コードは4桁の整数で入力してください")
-
+    return
+
   # コード削除
   on_remove_code = (code) ->
     if remove_code(code)
       save_codes()
       render()
-
+    return
+
   # OFX ダウンロード
   download_ofx = ->
     url = "/downloads/ofx?codes=" + codes.join(",")
     location.href = url
-
+    return
+
   # イベントハンドラ設定
   $("#add_code").on "click", on_add_code

@@ -81,4 +118,5 @@

   # 初期化
   load_codes()
+  resolve_all_stock_names()
   render()


Workaround iframe phone bug in IframeSaver

[#89024392] [#89602814]

--- app/assets/javascripts/iframe-saver.coffee
+++ app/assets/javascripts/iframe-saver.coffee
@@ -28,7 +28,13 @@
     if (@put_url or @get_url)
       IFrameSaver.instances.push @

-    phone_answered = () =>
+    @already_setup = false
+    phone_answered = =>
+      # Workaround IframePhone problem - phone_answered cabllack can be triggered multiple times:
+      # https://www.pivotaltracker.com/story/show/89602814
+      return if @already_setup
+      @already_setup = true
+
       @iframePhone.addListener 'setLearnerUrl', (learner_url) =>
         @learner_url = learner_url
       @iframePhone.addListener 'interactiveState', (interactive_json) =>


BF-187 Bug fix in preloading

BF-187 Bug fix in preloading

--- app/assets/javascripts/model/binary.coffee
+++ app/assets/javascripts/model/binary.coffee
@@ -127,14 +127,14 @@
       console.time "ping"

       @positionBucket = [position[0] >> (5 + zoomStep), position[1] >> (5 + zoomStep), position[2] >> (5 + zoomStep)]
-      buckets   = @getBucketArray(@positionBucket, @TEXTURE_SIZE >> (6 + zoomStep), @TEXTURE_SIZE >> 6, 0).concat(
+      buckets   = @getBucketArray(@positionBucket, @TEXTURE_SIZE >> 6, @TEXTURE_SIZE >> 6, 0).concat(
                   @getBucketArray(@positionBucket, @TEXTURE_SIZE >> 6, 0, @TEXTURE_SIZE >> 6),
                   @getBucketArray(@positionBucket, 0, @TEXTURE_SIZE >> 6, @TEXTURE_SIZE >> 6))
       # Buckets of zoom step 3 so that there won't be any black
       @positionBucket3 = [position[0] >> (5 + 3), position[1] >> (5 + 3), position[2] >> (5 + 3)]
-      buckets3  = @getBucketArray(@positionBucket3, @TEXTURE_SIZE >> (6 + zoomStep), @TEXTURE_SIZE >> (6 + zoomStep), 0).concat(
-                  @getBucketArray(@positionBucket3, @TEXTURE_SIZE >> (6 + zoomStep), 0, @TEXTURE_SIZE >> (6 + zoomStep)),
-                  @getBucketArray(@positionBucket3, 0, @TEXTURE_SIZE >> (6 + zoomStep), @TEXTURE_SIZE >> (6 + zoomStep)))
+      buckets3  = @getBucketArray(@positionBucket3, @TEXTURE_SIZE >> (6 + 3 - zoomStep), @TEXTURE_SIZE >> (6 + 3 - zoomStep), 0).concat(
+                  @getBucketArray(@positionBucket3, @TEXTURE_SIZE >> (6 + 3 - zoomStep), 0, @TEXTURE_SIZE >> (6 + 3 - zoomStep)),
+                  @getBucketArray(@positionBucket3, 0, @TEXTURE_SIZE >> (6 + 3 - zoomStep), @TEXTURE_SIZE >> (6 + 3 - zoomStep)))

       Cube.extendByBucketAddressExtent6(
         @positionBucket[0] - (@TEXTURE_SIZE >> 6), @positionBucket[1] - (@TEXTURE_SIZE >> 6), @positionBucket[2] - (@TEXTURE_SIZE >> 6),
@@ -162,6 +162,8 @@
       index = buckets.length
       level = 0

+      console.time "queue 1"
+      console.log "Buckets 1: " + buckets3.length
       if zoomStep != 3            # don't do this if you need to load the lowest resolution anyway
         for coordinate in [0..2]
           i = [0, 0, 0]
@@ -171,8 +173,10 @@
               if b
                 priority = Math.max(Math.abs(b[0] - @positionBucket3[0]), Math.abs(b[1] - @positionBucket3[1]), Math.abs(b[2] - @positionBucket3[2]))
                 PullQueue.insert [b[0] + i[0], b[1] + i[1], b[2] + i[2]], 3, priority + Math.abs(indexValue)*buckets3.length
-
+      console.timeEnd "queue 1"
+
       i = buckets.length * preloading.length
+      console.log "Buckets 2: " + buckets.length
       while i--
         index--
         if buckets[index]


Update swimlane counter on Swimlane redraw

--- app/assets/javascripts/swimlanes.js.coffee
+++ app/assets/javascripts/swimlanes.js.coffee
@@ -26,10 +26,17 @@
   close: -> @_el.toggleClass('closed')

   redraw: ->
+    @update_story_counter()
+
     if ! @stories().length
       @_el.addClass('empty')
     else
       @_el.removeClass('empty')
+
+  update_story_counter: ->
+      counter = @_el.find('.swimlane-header .info')
+      stories = @_el.find('.story').size()
+      counter.text("#{stories} issue#{'s' if stories > 1}")

   # Private methods
   _set_attr_readers: (type) ->


registerEvent wont make js fail if libraries not available

--- app/assets/javascripts/ux-events.js.coffee
+++ app/assets/javascripts/ux-events.js.coffee
@@ -1,3 +1,6 @@
 @registerEvent = (eventName,options) ->
-  mixpanel.track(eventName);
-  Intercom('trackEvent',eventName);
+  try
+    mixpanel.track(eventName)
+    Intercom('trackEvent',eventName)
+  catch err
+    console.error(err)


Link menu updates.

Link menu updates.

--- app/assets/javascripts/web/_site.js.coffee
+++ app/assets/javascripts/web/_site.js.coffee
@@ -2362,23 +2362,23 @@
         form.submit()

       hideLinkActions = (url) ->
-        if url of feedbin.linkActions
-          tooltip = feedbin.linkActions[url].tooltip
-          feedbin.linkActions[url].linkMenuTimer = setTimeout ( ->
-            if url of feedbin.linkActions && !tooltip.is(':hover')
-              tooltip.addClass('hide')
-              tooltip.removeClass('open')
-              feedbin.linkActions[url].linkMenuCleanup = setTimeout ->
+          hideTooltip = ->
+            if url of feedbin.linkActions
+              tooltip = feedbin.linkActions[url].tooltip
+              if url of feedbin.linkActions && !tooltip.is(':hover')
+                tooltip.addClass('hide')
+                tooltip.removeClass('open')
                 tooltip.remove()
                 feedbin.linkActions[url].popper.destroy() if feedbin.linkActions[url].popper
                 delete feedbin.linkActions[url]
-              , 150
-          ), 350
+
+          feedbin.linkActions[url].linkMenuTimer = setTimeout hideTooltip, 350

       showLinkActions = (url, link) ->
         if url of feedbin.linkActions && !feedbin.linkActions[url].visible
           tooltip = feedbin.linkActions[url].tooltip
           $('body').append(tooltip)
+          tooltip.removeClass('hide')
           position = link[0].getClientRects()
           lastLine = position[position.length - 1]
           offset = (lastLine.width + tooltip.outerWidth()) * -1
@@ -2392,7 +2392,6 @@

           feedbin.linkActions[url].visible = true
           feedbin.linkActions[url].popper = new Popper(link, tooltip, options)
-          tooltip.removeClass('hide')

       $(document).on 'mouseleave', '[data-behavior~=link_actions]', (event) ->
         hideLinkActions($(@).data('url'))


Configure the BASE_PATH for WYMEditor globally

--- app/assets/javascripts/wymeditor.js.coffee
+++ app/assets/javascripts/wymeditor.js.coffee
@@ -1,8 +1,9 @@
 #= require wymeditor/jquery.wymeditor.js
 #= require_self

+WYMeditor.BASE_PATH = '/assets/wymeditor/'
+
 $ ->
   $(':input.wymeditor').wymeditor
-    basePath: '/assets/wymeditor/'
     updateSelector: 'form:has(:input.wymeditor)'
     updateEvent: 'submit'


Added EMMI.Client test

Added EMMI.Client test

--- tests/jasmine/server/unit/patientsMethods.coffee
+++ tests/jasmine/server/unit/patientsMethods.coffee
@@ -19,6 +19,12 @@
     lastName: "McTestington"
     mrn: "1234512345"
     surgicalPathologyNumbers: [ "111", "222", "333"]
+
+emmi_result =
+    result:
+        firstname: "Adolphus"
+        lastname: "McTestington"
+        mrn: "1234512345"

 describe "Patients", ->

@@ -138,4 +144,16 @@
             Meteor.call "lookupPatient", {}, (err, resp) ->
                 expect(err.error).not.toBe 401
                 expect(Meteor.user).toHaveBeenCalled()
-                expect(Roles.userIsInRole).toHaveBeenCalled()+                expect(Roles.userIsInRole).toHaveBeenCalled()
+
+        it "looks up a patient using an EMMI.Client", ->
+            spyOn Meteor, "user"
+                .and.returnValue tech
+            spyOn Roles, "userIsInRole"
+                .and.returnValue true
+            clientStub = getPatient: jasmine.createSpy("getPatient").and.returnValue emmi_result
+            spyOn EMMI, "Client"
+                .and.callFake -> clientStub
+            Meteor.call "lookupPatient", "1234", (err, resp) ->
+                expect(err).toBeNull()
+                expect(clientStub.getPatient).toHaveBeenCalledWith "1234"


Reset avatar before uploading to prevent caching issues

--- server/methods/setAvatarFromService.coffee
+++ server/methods/setAvatarFromService.coffee
@@ -32,6 +32,7 @@
 				throw new Meteor.Error('invalid-avatar-url', '[methods] setAvatarFromService -> url service -> invalid content-type')

 			ars = RocketChatFile.bufferToStream new Buffer(result.content, 'binary')
+			RocketChatFileAvatarInstance.deleteFile "#{user.username}.jpg"
 			aws = RocketChatFileAvatarInstance.createWriteStream "#{user.username}.jpg", result.headers['content-type']
 			aws.on 'end', Meteor.bindEnvironment ->
 				Meteor.setTimeout ->
@@ -46,6 +47,7 @@
 		{image, contentType} = RocketChatFile.dataURIParse dataURI

 		rs = RocketChatFile.bufferToStream new Buffer(image, 'base64')
+		RocketChatFileAvatarInstance.deleteFile encodeURIComponent("#{user.username}.jpg")
 		ws = RocketChatFileAvatarInstance.createWriteStream encodeURIComponent("#{user.username}.jpg"), contentType
 		ws.on 'end', Meteor.bindEnvironment ->
 			Meteor.setTimeout ->


Move clicks to beforeEach.

Move clicks to beforeEach.

--- spec/javascripts/thumb_selector_spec.coffee
+++ spec/javascripts/thumb_selector_spec.coffee
@@ -47,11 +47,12 @@
     expect($('.enable-some-selected')).not.toHaveClass('disabled')

   describe 'adding to a source image set', ->
+    beforeEach ->
+      $('#div1').click()
+      $('#div3').click()
+
     describe 'when successful', ->
       it 'redirects to the set', ->
-        $('#div1').click()
-        $('#div3').click()
-
         spyOn $, 'attr'

         spyOn($, 'ajax').andCallFake (url, params) ->
@@ -63,9 +64,6 @@

     describe 'when the response is forbidden', ->
       it 'shows the user an error message', ->
-        $('#div1').click()
-        $('#div3').click()
-
         spyOn($, 'ajax').andCallFake (url, params) ->
           params.error({status: 403}, '')

@@ -75,9 +73,6 @@

     describe 'when the response is another error', ->
       it 'shows the user an error message', ->
-        $('#div1').click()
-        $('#div3').click()
-
         spyOn($, 'ajax').andCallFake (url, params) ->
           params.error({status: 500}, 'some error')


replace 'downPane' to 'lowerPane'

replace 'downPane' to 'lowerPane'

--- spec/pane-resize-handle-element-spec.coffee
+++ spec/pane-resize-handle-element-spec.coffee
@@ -98,13 +98,13 @@

     it "drag the horizontal element, the size of other vertical pane will not change", ->
       upperPane = container.getActivePane()
-      downPane = upperPane.splitDown()
+      lowerPane = upperPane.splitDown()

       [upperRightPane, upperLeftPane] = [upperPane.splitRight(), upperPane]
       upperPane = upperLeftPane.getParent()

-      [downRightPane, downLeftPane] = [downPane.splitRight(), downPane]
-      downPane = downLeftPane.getParent()
+      [lowerRightPane, lowerLeftPane] = [lowerPane.splitRight(), lowerPane]
+      lowerPane = lowerLeftPane.getParent()

       horizontalResizeElements = containerElement.querySelectorAll('atom-pane-resize-handle.horizontal')
       expect(horizontalResizeElements.length).toBe(2)
@@ -112,11 +112,11 @@
       expectCloseTo = (element, scale) ->
         expect(element.getFlexScale()).toBeCloseTo(scale, 0.1)

-      expectPaneScale = (upper, down, upperLeft, upperRight, downLeft, downRight) ->
+      expectPaneScale = (upper, lower, upperLeft, upperRight, lowerLeft, lowerRight) ->
         paneScales = [
-          [upperPane, upper], [downPane, down], [upperLeftPane, upperLeft],
-          [upperRightPane, upperRight], [downLeftPane, downLeft],
-          [downRightPane, downRight]
+          [upperPane, upper], [lowerPane, lower], [upperLeftPane, upperLeft],
+          [upperRightPane, upperRight], [lowerLeftPane, lowerLeft],
+          [lowerRightPane, lowerRight]
         ]
         expectCloseTo(e[0], e[1]) for e in paneScales

@@ -156,11 +156,11 @@
       resizeElementMove(resizeElement, getElementWidth(resizeElement.previousSibling) / 2)
       expect(leftPane.getFlexScale()).toBeCloseTo(0.5, 0.1)

-      downPane = leftPane.splitDown()   # dynamically split pane, pane's flexScale will become to 1
+      lowerPane = leftPane.splitDown()   # dynamically split pane, pane's flexScale will become to 1
       expect(leftPane.getFlexScale()).toBeCloseTo(1, 0.1)
       expect(leftPane.getParent().getFlexScale()).toBeCloseTo(0.5, 0.1)

-      downPane.close() # dynamically close pane, the pane's flexscale will recorver to origin value
+      lowerPane.close() # dynamically close pane, the pane's flexscale will recorver to origin value
       expect(leftPane.getFlexScale()).toBeCloseTo(0.5, 0.1)


Add some comments for app.Storage.

Add some comments for app.Storage.

--- app/client/js/storage.coffee
+++ app/client/js/storage.coffee
@@ -10,6 +10,15 @@
     @extends {este.labs.storage.Base}
   ###
   constructor: (@songsStore) ->
+    @stores = [@songsStore]
+    @fetchStores()
+    @listenStores()
+
+  ###*
+    @type {Array.<goog.events.Listenable>}
+    @protected
+  ###
+  stores: null

   ###*
     @override
@@ -27,4 +36,24 @@
           # console.log 'Store updated.'
           @songsStore.song = @songsStore.songByRoute route
       else
-        super route, routes+        super route, routes
+
+  ###*
+    NOTE(steida): Now plain browser localStorage is used to store and retrieve
+    app state snapshot. In future, we can leverage:
+    - http://git.yathit.com/ydn-db/wiki/Home
+    - https://github.com/swannodette/mori
+    - https://github.com/benjamine/jsondiffpatch
+    PATTERN(steida): This should be one place to change/sync app state. The goal
+    is to use http://en.wikipedia.org/wiki/Persistent_data_structure
+    with all its benefits.
+    @protected
+  ###
+  fetchStores: ->
+
+  ###*
+    @protected
+  ###
+  listenStores: ->
+    # for store in @stores
+    #   store.listen


allowing X for removing tokens

allowing X for removing tokens

--- vendor/assets/javascripts/token-text-area.coffee
+++ vendor/assets/javascripts/token-text-area.coffee
@@ -184,13 +184,16 @@
     @input.find("[data-id='#{id}']").remove()
     @saveEquation()

-  fillFromEquation: (equation, items) ->
+  fillFromEquation: (equation, items, removeButton=false) ->
     html = equation.replace(/\#(\w+)\#/g, (dirtyId) ->
       id = dirtyId.replace(/\#/g, '')
       foundItem = items.filter( (item) -> item.id == id )[0]
       return '' unless foundItem
       name = foundItem.name
-      return ' ' + name + ' '
+      if removeButton
+        return ' ' + name + ' '
+      else
+        return ' ' + name + ' '
     )
     if @options.operators
       for op in @options.operators
@@ -241,7 +244,8 @@
       node.className = 'token'
       node.contentEditable = false
       node.setAttribute('data-id', id)
-      node.innerHTML = name
+      if @options.removeButton
+        node.innerHTML = '' + name + ''

     @range.insertNode(node)

@@ -283,6 +287,11 @@
     #     $(this).replaceWith('@' + $(this).text() + '@')

     equation = equation.text()
+
+    self = @
+    $('.remove-filter').click( () ->
+      self.removeItem($(@).data('id'))
+    )

     # Check with server to find if expression is valid.
     @options.onChange(equation) if @options.onChange


menu/clear: Add Shapes item.

menu/clear: Add Shapes item.

--- Oghma/menu/clear.coffee
+++ Oghma/menu/clear.coffee
@@ -26,7 +26,8 @@
   Ext.create( 'Oghma.Ext.Menu',
     listeners:
       beforeshow: ->
-        @child( '#dice' ).setDisabled( O.tableverse.dice.empty() )
+        @child( '#dice'   ).setDisabled( O.tableverse.dice.empty()   )
+        @child( '#shapes' ).setDisabled( O.tableverse.shapes.empty() )
     items: [
       {
         text:    'Dice'
@@ -38,6 +39,16 @@
               die.remove()
           )
       },
+      {
+        text: 'Shapes'
+        id:   'shapes'
+        handler: ( item, e ) =>
+          all = e.shiftKey
+          O.tableverse.shapes.each( ( shape ) ->
+            if all || O.i_own( shape )
+              shape.remove()
+          )
+      }
       '-',
       {
         text: "Hold shift for all (vs. mine)."


LinkShell: bug fix in validation

LinkShell: bug fix in validation

--- coffee/src/shells/link.shell.coffee
+++ coffee/src/shells/link.shell.coffee
@@ -85,7 +85,7 @@
 class LinkShell.Model extends Shell.Model

   validate: (attrs) =>
-    unless (attrs? or attrs.link == '')
+    if attrs.link? and attrs.link != ''
       unless LinkShell.linkMatches attrs.link, @module.validLinkPatterns
         ValueError 'link', 'doesn\'t match valid link patterns for this shell.'


Catch markdown parsing errors and return unparsed input Closes #1300

--- app/components/markdown.cjsx
+++ app/components/markdown.cjsx
@@ -61,7 +61,11 @@
       markdownIt.render input

   render: ->
-    html = @replaceSymbols(@emojify(@markdownify(@props.children ? @props.content)))
+    try
+      html = @replaceSymbols(@emojify(@markdownify(@props.children ? @props.content)))
+    catch e
+      console.log e.message
+      html = @props.children ? @props.content

     React.createElement @props.tag,
       className: "markdown #{@props.className ? ''}"


chat: do nothing if a user is offline

chat: do nothing if a user is offline

--- client/activity/lib/models/collaborationchannelparticipants.coffee
+++ client/activity/lib/models/collaborationchannelparticipants.coffee
@@ -54,7 +54,9 @@
       @emitChange()  if emitEvent

-  setVideoSelectedParticipant: (nickname) ->
+  setVideoSelectedParticipant: (nickname, isOnline) ->
+
+    return  unless isOnline

     @state.selectedParticipant = nickname


Parse the topic name.

Parse the topic name.

--- src/scoutapp-awssns.coffee
+++ src/scoutapp-awssns.coffee
@@ -61,10 +61,11 @@
           console.log str

     else
-      isAlert = data.Subject?.match /ALARM/
-      color   = if isAlert then "danger" else "good"
-      prefix  = if isAlert then "Alert" else "Back to Normal"
-      emoji   = if isAlert then ":warning:" else ":thumbsup:"
+      topicName = data.TopicArn?.split(":").slice(-1)
+      isAlert   = data.Subject?.match /ALARM/
+      color     = if isAlert then "danger" else "good"
+      prefix    = if isAlert then "Alert" else "Back to Normal"
+      emoji     = if isAlert then ":warning:" else ":thumbsup:"

       fields = [
         title: data.Subject
@@ -77,7 +78,7 @@
       robot.emit 'slack-attachment',
         message:
           room:       room
-          username:   'aws'
+          username:   topicName
           icon_emoji: emoji
         content:
           text:     ''


(- re) Small fixes to the Execution bones

- more concise create-and-super pattern
- no reason to store that an Alien is, well, alien

--- Paws/Source/Paws.coffee
+++ Paws/Source/Paws.coffee
@@ -94,8 +94,7 @@
       @alien.native = this

    clone: (to) ->
-      to ?= new Label
-      super to
+      super (to ?= new Label)
       to.alien = @alien
       to.alien.native
       return to
@@ -115,7 +114,7 @@
       @      .push Thing.pair 'locals', @locals.responsible()

 paws.Alien = Alien = class Alien extends Execution
-   constructor: constructify(return:@) (@bits...) -> @alien = yes
+   constructor: constructify(return:@) (@bits...) ->

 paws.Native = Native = class Native extends Execution
    constructor: constructify(return:@) (@position) -> @stack = new Array


(- new API) Make all Things into EventEmitters

--- Paws/Source/data.coffee
+++ Paws/Source/data.coffee
@@ -3,17 +3,23 @@

 uuid = require 'uuid'
 util = require 'util'
+{EventEmitter} = require 'events'
+
+{debugging} = require('./additional.coffee')

 module.exports =
    Paws = new Object

-{debugging} = require('./additional.coffee')
 debugging.inject Paws
+

 # Core data-types
 # ---------------
-Paws.Thing = Thing = parameterizable class Thing
+
+#---
+# XXX: I'm not sure of the consequences of making all Things into EventEmitters ...
+Paws.Thing = Thing = parameterizable class Thing extends EventEmitter
    constructor: constructify(return:@) (elements...)->
       @id = uuid.v4()
       @metadata = new Array


logging

logging

--- assets/js/student.coffee
+++ assets/js/student.coffee
@@ -23,10 +23,10 @@
         false

 dollarCharacterConsideredHarmful = () ->
-        $("#scratch").append $("
"+
+        $("#scratch").append $("
"+
                                 "Sorry, but for security reasons the dollar character is not allowed"+
                                 "
")
-                      .show 'blind', 1000
+        $('.alert-error').show 'blind', 1000
         return

 $('#scratch').keyup ->


Add 'spell' command to wordnik script

Add 'spell' command to wordnik script

--- src/scripts/wordnik.coffee
+++ src/scripts/wordnik.coffee
@@ -32,3 +32,22 @@

           msg.send reply

+  robot.respond /spell(?: me)? (.*)/i, (msg) ->
+    if not process.env.WORDNIK_API_KEY?
+      msg.send "Missing WORDNIK_API_KEY env variable."
+      return
+
+    word = msg.match[1]
+
+    msg.http("http://api.wordnik.com/v4/word.json/#{escape(word)}?includeSuggestions=true")
+      .header('api_key', process.env.WORDNIK_API_KEY)
+      .get() (err, res, body) ->
+        wordinfo = JSON.parse(body)
+        if wordinfo.canonicalForm
+          msg.send "\"#{word}\" is a word."
+        else if not wordinfo.suggestions
+          console.log wordinfo.suggestions
+          msg.send "No suggestions for \"#{word}\" found."
+        else
+          list = wordinfo.suggestions.join(', ')
+          msg.send "Suggestions for \"#{word}\": #{list}"


a little more confirmation of working functionality in the integration test

--- jsapp/test/unit/xlform/integration.tests.coffee
+++ jsapp/test/unit/xlform/integration.tests.coffee
@@ -229,4 +229,9 @@
         # there should be one "first-level row": the group
         firstLevelRows = @div.find('.survey-editor__list > .survey__row')
         expect(firstLevelRows.length).toBe(1)
+
+        # split is apart again
+        @div.find('.js-delete-group').addClass('js-force-delete-group').eq(0).click()
+        firstLevelRows = @div.find('.survey-editor__list > .survey__row')
+        expect(firstLevelRows.length).toBe(2)
         ``


change default path for certificates

change default path for certificates

--- router.coffee
+++ router.coffee
@@ -16,8 +16,8 @@
 # Server.key and Server.cert should be regenerated for each installation
 options =
     https:
-        key: fs.readFileSync('./server.key', 'utf8'),
-        cert: fs.readFileSync('./server.crt', 'utf8')
+        key: fs.readFileSync('/home/cozy/server.key', 'utf8'),
+        cert: fs.readFileSync('/home/cozy/server.crt', 'utf8')

 # Proxy server that uses route table defined earlier


add non-osx shortcuts

add non-osx shortcuts

--- keymaps/atom-eval.cson
+++ keymaps/atom-eval.cson
@@ -7,5 +7,9 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-'atom-text-editor[data-grammar="source coffee"]:not([mini])':
+'.platform-darwin atom-text-editor[data-grammar="source coffee"]:not([mini])':
   'cmd-enter': 'atom-eval:evaluate'
+
+'.platform-linux atom-text-editor[data-grammar="source coffee"]:not([mini]),
+ .platform-win32 atom-text-editor[data-grammar="source coffee"]:not([mini])':
+  'ctrl-enter': 'atom-eval:evaluate'


keymaps

keymaps

--- keymaps/atom-lupa.cson
+++ keymaps/atom-lupa.cson
@@ -10,5 +10,9 @@
 'atom-workspace':
   'cmd-.': 'atom-lupa:toggle'

+# TODO don't duplicate selector
 '.lupa-structure .lupa-entity':
   'enter': 'atom-lupa:lupa-structure-enter'
+
+'.lupa-structure':
+  'escape': 'atom-lupa:lupa-structure-esc'


event metadata work - still not working

event metadata work - still not working

--- lib/sensu-dashboard/assets/javascripts/models/metadata/events.coffee
+++ lib/sensu-dashboard/assets/javascripts/models/metadata/events.coffee
@@ -14,3 +14,8 @@
       @set {warning: events.where({status: 1}).length}
       @set {critical: events.where({status: 2}).length}
       @set {unknown: events.where({status: 3}).length} # TODO: check for all status codes that are not 1 or 2
+
+    parse: (response) ->
+      console.log 'here'
+      response.events = SensuDashboard.Events
+      return response


cistrans: a bunch of improvements

cistrans: a bunch of improvements

  - Initial choice of a probe to show LOD score

  - When you pick an eQTL, it first chooses the marker with highest
    LOD for an initial pheno vs geno plot

  - Ensure that point colors are updated in pheno vs geno plot

--- cistrans/cistrans.coffee
+++ cistrans/cistrans.coffee
@@ -218,11 +218,13 @@
      .attr("x", (left[0] + right[0])/2)
      .attr("y", bottom[0] + pad.bottom*0.75)
      .attr("fill", titlecolor)
+     .attr("text-anchor", "middle")
   axislabels.append("text")
      .text("Position (cM)")
      .attr("x", (left[1] + right[1])/2)
      .attr("y", bottom[1] + pad.bottom*0.75)
      .attr("fill", titlecolor)
+     .attr("text-anchor", "middle")
   xloc = left[0] - pad.left*0.65
   yloc = (top[0] + bottom[0])/2
   axislabels.append("text")
@@ -294,8 +296,16 @@
     svg.selectAll(".probe_data").remove()
     d3.select("text#pxgtitle").text("")
     svg.selectAll(".plotPXG").remove()
-    # max lod
-    maxlod = d3.max(probe_data.lod)
+
+    # find marker with maximum LOD score
+    maxlod = -1
+    maxlod_marker = null
+    for m in data.markers
+      lod = probe_data.lod[data.pmark[m].index]
+      if maxlod < lod
+        maxlod = lod
+        maxlod_marker = m
+
     # y-axis scale
     lodcurve_yScale = d3.scale.linear()
                         .domain([0, maxlod*1.05])
@@ -323,6 +333,7 @@
          .attr("y", (d) -> lodcurve_yScale(d))
          .attr("x", left[1] - pad.left*0.1)
          .style("text-anchor", "end")
+         .attr("fill", labelcolor)
     yaxis.append("line")
          .attr("y1", lodcurve_yScale(5))
          .attr("y2", lodcurve_yScale(5))
@@ -345,6 +356,7 @@
             .attr("class", "thickline")
             .attr("stroke", darkblue)
             .style("pointer-events", "none")
+            .attr("fill", "none")

     # title
     titletext = probe_data.probe
@@ -409,7 +421,7 @@
        .enter()
        .append("circle")
        .attr("class", "probe_data")
-       .attr("id", (td) -> "circle#{td}")
+       .attr("id", (td) -> "marker_#{td}")
        .attr("cx", (td) -> chrLowXScale[data.pmark[td].chr](data.pmark[td].pos_cM))
        .attr("cy", (td) -> lodcurve_yScale(probe_data.lod[data.pmark[td].index]))
        .attr("r", bigRad)
@@ -432,7 +444,7 @@
                   plotPXG td
               else
                   markerClick[lastMarker] = 0
-                  d3.select("#circle#{lastMarker}").attr("opacity", 0).attr("fill",purple).attr("stroke","none")
+                  d3.select("circle#marker_#{lastMarker}").attr("opacity", 0).attr("fill",purple).attr("stroke","none")
                   revPXG td
               lastMarker = td
               markerClick[td] = 1
@@ -525,6 +537,7 @@

     pxgYscale = null
     pxgXaxis = svg.append("g").attr("class", "probe_data").attr("id", "pxg_xaxis")
+    pxgYaxis = svg.append("g").attr("class", "probe_data").attr("class", "plotPXG").attr("id", "pxg_yaxis")
     meanmarks = svg.append("g").attr("id", "pxgmeans").attr("class", "probe_data")

     plotPXG = (marker) ->
@@ -532,7 +545,6 @@
                      .domain([d3.min(probe_data.pheno),
                               d3.max(probe_data.pheno)])
                      .range([bottom[2]-pad.inner, top[2]+pad.inner])
-      pxgYaxis = svg.append("g").attr("class", "probe_data").attr("class", "plotPXG").attr("id", "pxg_yaxis")
       pxgticks = pxgYscale.ticks(8)
       pxgYaxis.selectAll("empty")
          .data(pxgticks)
@@ -552,6 +564,7 @@
          .attr("y", (d) -> pxgYscale(d))
          .attr("x", left[2] - pad.left*0.1)
          .style("text-anchor", "end")
+         .attr("fill", labelcolor)

       # calculate group averages
       chr = data.pmark[marker].chr
@@ -599,7 +612,7 @@
               g = data.geno[marker][i]
               return pink if g < 0
               darkGray)
-           .attr("stroke", (d,i) ->
+          .attr("stroke", (d,i) ->
                g = data.geno[marker][i]
                return purple if g < 0
                "black")
@@ -654,6 +667,28 @@
               if(data.pmark[marker].chr is "X")
                 return pxgXscaleX(sx*2+g-1)+jitter[i]
               pxgXscaleA(sx*3+g-1)+jitter[i])
+         .attr("fill", (d,i) ->
+              g = data.geno[marker][i]
+              return pink if g < 0
+              darkGray)
+         .attr("stroke", (d,i) ->
+               g = data.geno[marker][i]
+               return purple if g < 0
+               "black")
+         .attr("stroke-width", (d,i) ->
+               g = data.geno[marker][i]
+               return "2" if g < 0
+               "1")
+
+    # initially select the marker with maximum LOD
+    lastMarker = maxlod_marker
+    markerClick[lastMarker] = 1
+    d3.select("circle#marker_#{lastMarker}").attr("opacity", 1).attr("fill",altpink).attr("stroke",purple)
+    pos = data.pmark[lastMarker].pos_cM
+    chr = data.pmark[lastMarker].chr
+    title = "#{lastMarker} (chr #{chr}, #{onedig(pos)} cM)"
+    d3.select("text#pxgtitle").text(title)
+    plotPXG(lastMarker)

   chrindex = {}
   for c,i in data.chrnames
@@ -665,7 +700,7 @@
              .data(data.peaks)
              .enter()
              .append("circle")
-             .attr("class", (d) -> "probe#{d.probe}")
+             .attr("class", (d) -> "probe_#{d.probe}")
              .attr("cx", (d) -> chrXScale[d.chr](d.pos_cM))
              .attr("cy", (d) -> chrYScale[data.probes[d.probe].chr](data.probes[d.probe].pos_cM))
              .attr("r", peakRad)
@@ -673,7 +708,7 @@
              .attr("fill", (d) -> return if(chrindex[d.chr] % 2 is 0) then darkblue else darkgreen)
              .attr("opacity", (d) -> Zscale(d.lod))
              .on "mouseover", (d) ->
-                 d3.selectAll("circle.probe#{d.probe}")
+                 d3.selectAll("circle.probe_#{d.probe}")
                                 .attr("r", bigRad)
                                 .attr("fill", pink)
                                 .attr("stroke", darkblue)
@@ -681,7 +716,7 @@
                                 .attr("opacity", 1)
                  eqtltip.call(this,d)
              .on "mouseout", (d) ->
-                 d3.selectAll("circle.probe#{d.probe}")
+                 d3.selectAll("circle.probe_#{d.probe}")
                                 .attr("r", peakRad)
                                 .attr("fill", (d) -> return if(chrindex[d.chr] % 2 is 0) then darkblue else darkgreen)
                                 .attr("stroke", "none")
@@ -690,6 +725,8 @@
              .on "click", (d) ->
                  d3.json("data/probe_data/probe#{d.probe}.json", draw_probe)

+  # initial set of LOD curves at the bottom
+  d3.json("data/probe_data/probe517761.json", draw_probe)

   # black borders
   for j of left


Try to clean up all open handles

Try to clean up all open handles

--- src/WebSocket.coffee
+++ src/WebSocket.coffee
@@ -27,22 +27,33 @@
       it 'should succeed', (done) ->
         @timeout 4000
         tries = 10
-        startServer ->
+        startServer (err) ->
+          return done err if err
           client = new WebSocketClient
-          client.on 'connect', (conn) ->
+          onConnected = (conn) ->
             connection = conn
             connection.setMaxListeners(1000)
+            client.removeListener 'connectFailed', onFailed
             done()
-          client.on 'connectFailed', (err) ->
+          onFailed = (err) ->
             tries--
-            chai.expect(tries, 'Connection retry limit must not be exceeded').to.be.above 0
-            setTimeout(
-              ->
-                client.connect address, 'noflo'
-              200
-            )
+            unless tries
+              client.removeListener 'connect', onConnected
+              done err
+              return
+            setTimeout ->
+              client.connect address, 'noflo'
+            , 100
+          client.once 'connect', onConnected
+          client.on 'connectFailed', onFailed
           client.connect address, 'noflo'
-    after stopServer
+    after (done) ->
+      unless connection
+        stopServer done
+        return
+      connection.once 'close', ->
+        stopServer done
+      connection.close()

     send = (protocol, command, payload) ->
       payload = {} unless payload
@@ -679,7 +690,7 @@
         done = ->
     (done) ->
       return done() unless child
-      child.on 'exit', ->
+      child.once 'exit', ->
         done()
       child.kill 'SIGTERM'
     host


Fix expects to make compatible with NoFlo 0.8

--- src/WebSocket.coffee
+++ src/WebSocket.coffee
@@ -86,6 +86,8 @@
           if data.command is 'started'
             delete data.payload.time
             delete expected.payload.time
+            delete data.payload.running
+            delete expected.payload.running
           if data.command is 'stopped'
             delete data.payload.time
             delete expected.payload.time
@@ -594,17 +596,17 @@
             protocol: 'network'
             command: 'disconnect'
             payload:
-               id: 'DATA -> IN Hello()'
-               graph: 'bar'
-               tgt: { node: 'Hello', port: 'in' }
-          ,
-            protocol: 'network'
-            command: 'disconnect'
-            payload:
                id: 'Hello() OUT -> IN World()'
                graph: 'bar'
                src: { node: 'Hello', port: 'out' }
                tgt: { node: 'World', port: 'in' }
+          ,
+            protocol: 'network'
+            command: 'disconnect'
+            payload:
+               id: 'DATA -> IN Hello()'
+               graph: 'bar'
+               tgt: { node: 'Hello', port: 'in' }
           ]
           receive expects, done, true
           send 'network', 'start',


hubot abstract me (like a bossk)

hubot abstract me (like a bossk)

--- scripts/google-images.coffee
+++ scripts/google-images.coffee
@@ -6,25 +6,18 @@
 #                      parameters to try to return an animated GIF instead.
 module.exports = (robot) ->
   robot.hear /(image|img)( me)? (.*)/i, (response) ->
-    imagery = response.match[3]
-    imgurl  = 'http://ajax.googleapis.com/ajax/services/search/images?' +
-              'v=1.0&rsz=8&q=' +escape(imagery)
-
-    response.fetch imgurl, (res) ->
-      images = JSON.parse(res.body)
-      images = images.responseData.results
-      image  = response.random images
-
-      response.send image.unescapedUrl + "#.png"
+    imageMe response.match[3]

   robot.hear /animate me (.*)/i, (response) ->
-    imagery = response.match[1]
-    imgurl  = 'http://ajax.googleapis.com/ajax/services/search/images?' +
-              'v=1.0&rsz=8&as_filetype=gif&q=animted%20' +escape(imagery)
+    imageMe "animated #{response.match[1]}"

-    response.fetch imgurl, (res) ->
-      images = JSON.parse(res.body)
+imageMe = (res, query) ->
+  res.http('http://ajax.googleapis.com/ajax/services/search/images')
+    .query(v: "1.0", rsz: '8', q: imagery)
+    .get() (err, res, body) ->
+      images = JSON.parse(body)
       images = images.responseData.results
-      image  = response.random images
+      image  = res.random images

-      response.send image.unescapedUrl + "#.png"
+      res.send image.unescapedUrl + "#.png"
+


Genrated the id of created cart items

Genrated the id of created cart items

--- spec/javascripts/support/factory.js.coffee
+++ spec/javascripts/support/factory.js.coffee
@@ -12,7 +12,7 @@

   create: (name, config)->
     object = Factory.attributeFor(name, config)
-    object.id = Factory.generateId(name) unless object.id
+    object.id = Factory.generateId(name)

     opts = Factory.factories[name]['opts']
     klass = opts['class']


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -116,25 +116,25 @@
     path.transition()
         .duration(1000)
         #.attrTween("d", pieTween)
-        #.attrTween("d", (d,i) ->
-        #    #console.log("d",d)
-        #    #console.log("i",i)
-        #    i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
-        #    return (t) ->
-        #        b = i(t)
-        #        return arc(b)
-        #)
+        .attrTween("d", (d,i) ->
+            #console.log("d",d)
+            #console.log("i",i)
+            i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
+            return (t) ->
+                b = i(t)
+                return arc(b)
+        )

     #@path.exit().transition().duration(300).attrTween('d', that.removePieTween).remove()
     path.exit()
         .transition()
         .duration(300)
-        #.attrTween("d", (d,i) ->
-        #    i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
-        #    return (t) ->
-        #        b = i(t)
-        #        return arc(b)
-        #)
+        .attrTween("d", (d,i) ->
+            i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
+            return (t) ->
+                b = i(t)
+                return arc(b)
+        )
         .remove()

     labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -192,7 +192,7 @@
         if !data
           return

-        piedata = data[0]
+        piedata = data[0].label
         console.log("update pie", piedata)

         $(@node).children(".title").text($(@node).attr("data-title"))


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -93,20 +93,23 @@
         #d.innerRadius = (radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
         #d.innerRadius = 0 #(radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
         #d.outerRadius = radiuso
-        "translate(" + arc.centroid(d) + ")")
+        #"translate(" + arc.centroid(d) + ")")

-        #c = arc.centroid(d)
-        #x = c[0]
-        #y = c[1]
-        #h = Math.sqrt(x*x + y*y)
-        #r = 0
-        #if x <= 0
-        #    r = -labelRadius
-        #else
-        #    r = labelRadius
+        c = arc.centroid(d)
+        x = c[0]
+        y = c[1]
+        mx = 1.2
+        my = .8
+        h = Math.sqrt(x*x + y*y)
+        r = 0
+        if x <= 0
+            r = -labelRadius
+        else
+            r = labelRadius
         #return "translate(" + (x/h * labelRadius) + "," + (y/h * labelRadius) + ")"
         #return "translate(" + r + "," + y + ")"#(y/h * labelRadius) + ")"
-        #)
+        return "translate(" + r*mx + "," + y*my + ")"#(y/h * labelRadius) + ")"
+        )

         .attr('fill', "#fff")
         .attr("text-anchor", "middle").text((d, i) ->


Sort builds on builds/PRs/branches tabs

Sort builds on builds/PRs/branches tabs

--- assets/scripts/app/controllers/repo.coffee
+++ assets/scripts/app/controllers/repo.coffee
@@ -4,6 +4,12 @@
   init: ->
     @_super.apply this, arguments
     Ember.run.later(@updateTimes.bind(this), Travis.INTERVALS.updateTimes)
+    @set 'builds', Em.ArrayProxy.create(Em.SortableMixin,
+      isLoadedBinding: 'content.isLoaded'
+      sortProperties: ['number']
+      sortAscending: false
+      content: []
+    )

   updateTimes: ->
     if builds = @get('builds')
@@ -32,15 +38,15 @@

   viewBuilds: ->
     @connectTab('builds')
-    @_bind('builds', 'repo.builds')
+    @_bind('builds.content', 'repo.builds')

   viewPullRequests: ->
     @connectTab('pull_requests')
-    @_bind('builds', 'repo.pullRequests')
+    @_bind('builds.content', 'repo.pullRequests')

   viewBranches: ->
     @connectTab('branches')
-    @_bind('builds', 'repo.branches')
+    @_bind('builds.content', 'repo.branches')

   viewEvents: ->
     @connectTab('events')
@@ -73,4 +79,4 @@

   _unbind: ->
     binding.disconnect(this) for binding in @bindings
-    @bindings.length = 0
+    @bindings.clear()


fix dumb typo

fix dumb typo

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -1492,7 +1492,7 @@
         async.parallel([
             (cb) =>
                 # touch accounts table
-                @_touch_table('accounts', opts.account_id, cb)
+                @_touch_account(opts.account_id, cb)
             (cb) =>
                 if not opts.project_id?
                     cb(); return


vm: fix call to `JPaymentSubscription#debit` in `JVM::createSharedVm`

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -371,7 +371,7 @@

           JPaymentPack.one tags: "vm", (err, pack) =>
             return callback err  if err or not pack
-            subscription.debit pack, 1, (err) =>
+            subscription.debit {pack}, 1, (err) =>
               return callback err  if err
               @createVm {
                 type      : "group"


hub/sign-in throttling: large default values

--- src/smc-hub/sign-in.coffee
+++ src/smc-hub/sign-in.coffee
@@ -23,7 +23,7 @@
 # these parameters are per group and per hub!
 bottleneck_opts      =
     minTime        : process_env_int('THROTTLE_SIGN_IN_MINTIME', 100)
-    maxConcurrent  : process_env_int('THROTTLE_SIGN_IN_CONCURRENT', 2)
+    maxConcurrent  : process_env_int('THROTTLE_SIGN_IN_CONCURRENT', 10)
 limit_group          = new Bottleneck.Group(bottleneck_opts)

 sign_in_fails =
@@ -98,7 +98,7 @@
     key = opts.client.ip_address

     done_cb = () ->
-        opts.logger?.info("sign_in group '#{key}' done")
+        opts.logger?.debug("sign_in(group='#{key}'): done")

     limit_group.key(key).submit(_sign_in, opts, done_cb)

@@ -252,7 +252,7 @@
     key = opts.client.ip_address

     done_cb = () ->
-        opts.logger?.info("sign_in_using_auth_token group '#{key}' done")
+        opts.logger?.debug("sign_in_using_auth_token(group='#{key}'): done")

     limit_group.key(key).submit(_sign_in_using_auth_token, opts, done_cb)


Atom config updates

Atom config updates

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -11,6 +11,7 @@
       "linter-jshint"
       "symbols-view"
       "screen-send"
+      "linter-coffeelint"
     ]
     ignoredNames: [
       ".bundle"
@@ -27,12 +28,12 @@
     ]
   editor:
     fontFamily: "Fira Code"
-    fontSize: 13
+    fontSize: 18
     invisibles: {}
     showIndentGuide: true
     tabType: "soft"
   "elm-format":
-    binary: "/Users/andrewhao/.nodenv/shims/elm-format"
+    binary: "/Users/andrewhao/personal/prejoinin/node_modules/.bin/elm-format"
   elmjutsu:
     autocompleteDescriptionDisplay: "markdown"
     autocompleteEnabled: true
@@ -41,6 +42,9 @@
     globalAutocompleteEnabled: true
     hyperclickEnabled: true
     sidekickPosition: "right"
+  "exfmt-atom": {}
+  "git-blame":
+    columnWidth: 281
   "go-plus": {}
   iex:
     colors: {}


LogLines tracks the difference in content length between requests

--- SingularityUI/app/collections/LogLines.coffee
+++ SingularityUI/app/collections/LogLines.coffee
@@ -38,6 +38,8 @@
             error

     parse: (result) =>
+        @offsetDifference = result.offset - @offset
+
         @offset = offset = result.offset

         # split on newlines


Add existential check

Add existential check

--- app/pages/project/index.cjsx
+++ app/pages/project/index.cjsx
@@ -181,7 +181,7 @@
     if @props.location.query?.workflow? and @canFetchWorkflowByQuery(project, @props.user)
       selectedWorkflowID = @props.location.query.workflow
       unless preferences?.preferences.selected_workflow is selectedWorkflowID
-        @handlePreferencesChange('preferences.selected_workflow', selectedWorkflowID) if preferences
+        @handlePreferencesChange('preferences.selected_workflow', selectedWorkflowID) if preferences?
     else if preferences?.preferences.selected_workflow?
       selectedWorkflowID = preferences?.preferences.selected_workflow
     else if preferences?.settings?.workflow_id?


Remove timeout for testing

Remove timeout for testing

--- app/pages/project/index.cjsx
+++ app/pages/project/index.cjsx
@@ -260,12 +260,6 @@
   componentDidMount: ->
     @_boundForceUpdate = @forceUpdate.bind this
     @fetchProjectData @props.params.owner, @props.params.name, @props.user
-      .then =>
-        # For testing! Simulating a new assignment coming in after some time.
-        setTimeout ( =>
-          console.log('set selected workfow')
-          @state.preferences.update "preferences.selected_workflow": '2333'
-        ), 5000

   componentWillReceiveProps: (nextProps) ->
     {owner, name} = nextProps.params


Add the Elixir tdoc snippet

Add the Elixir tdoc snippet

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -67,6 +67,13 @@
   'Let One-Line':
     prefix: 'letd'
     body: 'let :$1, do: $0'
+  '@typedoc':
+    prefix: 'tdoc'
+    body: '''
+      @typedoc """
+      $0
+      """
+    '''
 '.source.ruby':
   Before:
     prefix: 'bef'


Add Liquid include snippet

Add Liquid include snippet

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -716,6 +716,10 @@
     'prefix': 'assign'
     'body': '{% assign ${1:name} = ${2:value} %}'

+  'include':
+    'prefix': 'include'
+    'body': '{% include ${1:filename}.html $2%}'
+
   'if':
     'prefix': 'if'
     'body': '{% if ${1:condition} %}$2'


Disable console log

Disable console log

--- app/scripts/lib/Level.coffee
+++ app/scripts/lib/Level.coffee
@@ -300,7 +300,7 @@
   loadAssets: (name, assetObject) ->
     @entityAssets ?= {}
     return @entityAssets[name].promise if @entityAssets[name]
-    console.log 'loading', name
+    #console.log 'loading', name
     d = WhenJS.defer()
     @entityAssets[name] =
       assets: assetObject


Cargamos la previsualización cuando carga la página.

--- app/scripts/live_form.coffee
+++ app/scripts/live_form.coffee
@@ -3,28 +3,32 @@

   $('input[name=preview]').remove()

+  send_form = ->
+    $('form.pagina').ajaxSubmit(
+      beforeSend: ->
+        $('#preview').html('
Cargando..
')
+      success: (data) ->
+        $('head').append($('#css', data).html())
+        $('#preview').html($('#preview', data).html())
+        $('#sidebar').remove()
+        $('#sidebar', data).prependTo('#extra')
+        sources = data.match(/script src="([^"]+)"/g)
+        if sources
+          for source in sources
+            script = document.createElement('script')
+            script.setAttribute "type","text/javascript"
+            script.setAttribute "src", source.replace("script src=", "").replace(/"/g, "")
+            document.getElementsByTagName("head")[0].appendChild(script)
+
+        imagenes_quitables()
+      data: { preview: true }
+    )
+
   $('input[type=text], textarea', $('form.pagina')).typeWatch(
-    callback: ->
-      $('form.pagina').ajaxSubmit(
-        beforeSend: ->
-          $('#preview').html('
Cargando..
')
-        success: (data) ->
-          $('head').append($('#css', data).html())
-          $('#preview').html($('#preview', data).html())
-          $('#sidebar').remove()
-          $('#sidebar', data).prependTo('#extra')
-          sources = data.match(/script src="([^"]+)"/g)
-          if sources
-            for source in sources
-              script = document.createElement('script')
-              script.setAttribute "type","text/javascript"
-              script.setAttribute "src", source.replace("script src=", "").replace(/"/g, "")
-              document.getElementsByTagName("head")[0].appendChild(script)
-
-          imagenes_quitables()
-        data: { preview: true }
-      )
+    callback:       -> send_form()
     wait:           1000
     highlight:      false
     captureLenght:  1
   )
+
+  send_form()


fixed bug-maxBuffer

fixed bug-maxBuffer

--- hn.coffee
+++ hn.coffee
@@ -17,17 +17,29 @@
 setInterval check, 3000

 loadPosts = (url) ->
- child_process.execFile 'coffee', ['./child.coffee', cnt, errCnt, cCnt, cErrCnt, url], {}, (err, stdout, stderr) ->
-  console.log stdout
-  arr = stderr.split '\n'
-  n = arr.length
-  last = arr[n-1]
-  last = arr[n-2] if last is ''
-  nums = last.split ' '
-  cnt = parseInt nums[0]
-  errCnt = parseInt nums[1]
-  cCnt = parseInt nums[2]
-  cErrCnt = parseInt nums[3]
-  nextUrl = nums[4]
+ process = child_process.execFile(
+   'coffee'
+   ['./child.coffee', cnt, errCnt, cCnt, cErrCnt, url]
+   maxBuffer: 100*1024*1024
+   (err, stdout, stderr) ->
+    console.error "Process error: ", err

-  console.error line for line in arr
+    arr = stderr.split '\n'
+    n = arr.length
+    last = arr[n-1]
+    last = arr[n-2] if last is ''
+    nums = last.split ' '
+    cnt = parseInt nums[0]
+    errCnt = parseInt nums[1]
+    cCnt = parseInt nums[2]
+    cErrCnt = parseInt nums[3]
+    nextUrl = nums[4]
+ )
+
+ process.stdout.on 'data', (data) ->
+  data = data.substr 0, data.length-1 if data[data.length-1] is '\n'
+  console.log data
+
+ process.stderr.on 'data', (data) ->
+  data = data.substr 0, data.length-1 if data[data.length-1] is '\n'
+  console.error data


linting

linting

--- cURLCodeGenerator.coffee
+++ cURLCodeGenerator.coffee
@@ -135,7 +135,6 @@

     @generate = (context, requests, options) ->
         self.options = (options || {}).inputs || {}
-        console.log('@@@@', JSON.stringify(options, null, 2))

         curls = requests.map((request) ->
             return self.generateRequest(request)


singleton Header class

singleton Header class

--- app/static/coffee/all.coffee
+++ app/static/coffee/all.coffee
@@ -142,18 +142,21 @@
         ]

 class Header
-
     #a list to be passed to the link generator function
-    links: [
-        {title: "Gnome", url: "/home"}
-        {title: "Group Browse", url: "/gbrowse"}
-        {title: "Group Analyses", url: "/groups"}
-        {title: "VF and AMR", url: "/factors"}
-        {title: "Meta", url: "/meta"}
-    ]
-
-    controller: ->
-
+    model: () =>
+        @links = [
+            {title: "Gnome", url: "/home"}
+            {title: "Group Browse", url: "/gbrowse"}
+            {title: "Group Analyses", url: "/groups"}
+            {title: "VF and AMR", url: "/factors"}
+            {title: "Meta", url: "/meta"}
+        ]
+        return
+    constructor: () ->
+        @model()
+    @get: () =>
+    	@instance ?= new @()
+    	return @instance
     view: ->
         m("div", {class:'container-fluid'}, [
             m("nav", {class:'navbar navbar-default navbar-fixed-top', role:'navigation'}, [
@@ -170,10 +173,10 @@
                 ])
             ])
         ])
-
+#header = Header.get()

 #Singleton?
-header = new Header()
+
 meta = new App
 home = new Home


Move constants to follow the requirements of client

--- client.coffee
+++ client.coffee
@@ -15,6 +15,47 @@
 string2compact = require('string2compact')
 utils = require './utils'

+#
+# Constants
+#
+
+BOOTSTRAP_NODES = [
+  'router.bittorrent.com:6881'
+  'router.utorrent.com:6881'
+  'dht.transmissionbt.com:6881'
+]
+BOOTSTRAP_TIMEOUT = 10000
+K = 20
+# number of nodes per bucket
+MAX_CONCURRENCY = 6
+# α from Kademlia paper
+ROTATE_INTERVAL = 5 * 60 * 1000
+# rotate secrets every 5 minutes
+SECRET_ENTROPY = 160
+# entropy of token secrets
+SEND_TIMEOUT = 2000
+MESSAGE_TYPE =
+  QUERY: 'q'
+  RESPONSE: 'r'
+  ERROR: 'e'
+ERROR_TYPE =
+  GENERIC: 201
+  SERVER: 202
+  PROTOCOL: 203
+  METHOD_UNKNOWN: 204
+LOCAL_HOSTS =
+  4: []
+  6: []
+interfaces = os.networkInterfaces()
+for i of interfaces
+  j = 0
+  while j < interfaces[i].length
+    face = interfaces[i][j]
+    if face.family == 'IPv4'
+      LOCAL_HOSTS[4].push face.address
+    if face.family == 'IPv6'
+      LOCAL_HOSTS[6].push face.address
+    j++

 ###*
 # A DHT client implementation. The DHT is the main peer discovery layer for BitTorrent,
@@ -23,6 +64,12 @@
 ###

 class DHT extends EventEmitter
+
+  # These constants will also be exported
+  @ERROR_TYPE = ERROR_TYPE
+  @K = K
+  @MESSAGE_TYPE = MESSAGE_TYPE
+
   constructor: (opts) ->
     if !(@ instanceof DHT)
       return new DHT(opts)
@@ -107,46 +154,6 @@
         @_bootstrap BOOTSTRAP_NODES
     @on 'ready', ->
       @_debug 'emit ready'
-
-module.exports = DHT
-
-BOOTSTRAP_NODES = [
-  'router.bittorrent.com:6881'
-  'router.utorrent.com:6881'
-  'dht.transmissionbt.com:6881'
-]
-BOOTSTRAP_TIMEOUT = 10000
-K = module.exports.K = 20
-# number of nodes per bucket
-MAX_CONCURRENCY = 6
-# α from Kademlia paper
-ROTATE_INTERVAL = 5 * 60 * 1000
-# rotate secrets every 5 minutes
-SECRET_ENTROPY = 160
-# entropy of token secrets
-SEND_TIMEOUT = 2000
-MESSAGE_TYPE = module.exports.MESSAGE_TYPE =
-  QUERY: 'q'
-  RESPONSE: 'r'
-  ERROR: 'e'
-ERROR_TYPE = module.exports.ERROR_TYPE =
-  GENERIC: 201
-  SERVER: 202
-  PROTOCOL: 203
-  METHOD_UNKNOWN: 204
-LOCAL_HOSTS =
-  4: []
-  6: []
-interfaces = os.networkInterfaces()
-for i of interfaces
-  j = 0
-  while j < interfaces[i].length
-    face = interfaces[i][j]
-    if face.family == 'IPv4'
-      LOCAL_HOSTS[4].push face.address
-    if face.family == 'IPv6'
-      LOCAL_HOSTS[6].push face.address
-    j++

 ###*
 # Start listening for UDP messages on given port.
@@ -956,4 +963,4 @@
   args[0] = '[' + utils.idToHexString(@nodeId).substring(0, 7) + '] ' + args[0]
   debug.apply null, args

-
+module.exports = DHT


Remove round progress bar (fixes #8)

Remove round progress bar (fixes #8)

And fix a small bug with getting location while not available.

--- client.coffee
+++ client.coffee
@@ -162,7 +162,7 @@
 						border: 0
 						boxShadow: "0 3px 10px 0 rgba(0, 0, 0, 1)"
 						marginBottom: '-25px'
-						overflowX: 'hidden'
+						#overflowX: 'hidden'
 					Dom.div !->
 						Dom.style
 							height: "25px"
@@ -175,9 +175,9 @@
 							filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#4d000000',GradientType=0 )"
 							backgroundColor: nextColor
 							zIndex: "10"
-							_borderRadius: '12.5px'
-							padding: '0 13px 0 13px'
-							marginLeft: '-13px'
+							#_borderRadius: '12.5px'
+							#padding: '0 13px 0 13px'
+							#marginLeft: '-13px'
 						Dom._get().style.width = dbPercentage + "%"
 						log "dbPercentage after balancing = ", dbPercentage
 						Dom._get().style.transition = "width " + time + "ms linear"
@@ -489,7 +489,7 @@
 					Dom.onTap !->
 						Db.local.set('currentSetupPage', 'setup2')
 			renderMap()
-			if !placedBeacons and mapReady()
+			if !placedBeacons and mapReady() and location?
 				location = window.beaconCurrentLocation.getLatLng()
 				one = L.latLng(location.lat+0.01,location.lng-0.02)
 				two = L.latLng(location.lat-0.01,location.lng+0.02)


Improve rendering for unsatisfiable goals

--- admin/static/coffee/resolve_issues.coffee
+++ admin/static/coffee/resolve_issues.coffee
@@ -81,7 +81,7 @@
                 async: false

             # rerender issue view (just the issues, not the whole thing)
-            window.app.resolve_issues.render_issues()
+            #window.app.resolve_issues.render_issues()

             # notify the user that we succeeded
             $('#user-alert-space').append @alert_tmpl
@@ -165,15 +165,15 @@
             ack_expectations_to_send = {}

             for datacenter in @datacenters_with_issues
-                replica_affinities_to_send[datacenter.datacenter_id] = datacenter.num_machines
-
-                ack_expectations_to_send[datacenter.datacenter_id] = @namespace.get('ack_expectations')[datacenter.datacenter_id]
-                if ack_expectations_to_send[datacenter.datacenter_id] > replica_affinities_to_send[datacenter.datacenter_id]
-                    ack_expectations_to_send[datacenter.datacenter_id] = replica_affinities_to_send[datacenter.datacenter_id]
-
-                if @namespace.get('primary_uuid') is datacenter.datacenter_id # Since primary is a replica, we lower it by 1 if we are dealing with the primary datacenter
-                    replica_affinities_to_send[datacenter.datacenter_id]--
-
+                if datacenter.num_replicas > datacenter.num_machines
+                    replica_affinities_to_send[datacenter.datacenter_id] = datacenter.num_machines
+
+                    ack_expectations_to_send[datacenter.datacenter_id] = @namespace.get('ack_expectations')[datacenter.datacenter_id]
+                    if ack_expectations_to_send[datacenter.datacenter_id] > replica_affinities_to_send[datacenter.datacenter_id]
+                        ack_expectations_to_send[datacenter.datacenter_id] = replica_affinities_to_send[datacenter.datacenter_id]
+
+                    if @namespace.get('primary_uuid') is datacenter.datacenter_id # Since primary is a replica, we lower it by 1 if we are dealing with the primary datacenter
+                        replica_affinities_to_send[datacenter.datacenter_id]--

             $.ajax
                 processData: false
@@ -345,40 +345,15 @@

             namespace = namespaces.get(@model.get('namespace_id'))

-            # Check primary
-            num_replicas = namespace.get('replica_affinities')[namespace.get('primary_uuid')]
-            if not num_replicas? # replica affinities may be missing for new namespaces
-                num_replicas = 0
-            num_replicas++ # The master is also a replica
-            num_machines = DataUtils.get_datacenter_machines(namespace.get('primary_uuid')).length
-            if num_machines < num_replicas
-                json.datacenters_with_issues.push
-                    datacenter_id: namespace.get('primary_uuid')
-                    datacenter_name: datacenters.get(namespace.get('primary_uuid')).get('name')
-                    num_replicas: num_replicas
-                    num_machines: num_machines
-                primary_replica_count = namespace.get('replica_affinities')[namespace.get('primary_uuid')]
-                if not primary_replica_count? # Replica affinities may be missing for new namespaces
-                    primary_replica_count = 0
-
-                if json.datacenters_with_issues[json.datacenters_with_issues.length-1].num_machines > primary_replica_count-1 # -1 for the primary
-                    json.datacenters_with_issues[json.datacenters_with_issues.length-1].change_ack = true
-
-            # Check secondaries
-            secondary_affinities = {}
-            _.each namespace.get('replica_affinities'), (num_replicas, id) =>
-                if id != namespace.get('primary_uuid') and num_replicas > 0
-                    num_machines = DataUtils.get_datacenter_machines(id).length
-                    if num_machines < num_replicas
-                        json.datacenters_with_issues.push
-                            datacenter_id: id
-                            datacenter_name: datacenters.get(id).get('name')
-                            num_replicas: num_replicas
-                            num_machines: num_machines
-
-                        if DataUtils.get_ack_expectations(namespace.get('id'), id) > json.datacenters_with_issues[json.datacenters_with_issues.length-1].num_machines
-                            json.datacenters_with_issues[json.datacenters_with_issues.length-1].change_ack = true
-
+            for datacenter_id of @model.get('replica_affinities')
+                number_replicas = if datacenter_id is @model.get('primary_datacenter') then @model.get('replica_affinities')[datacenter_id]+1 else @model.get('replica_affinities')[datacenter_id]
+
+                if number_replicas > @model.get('actual_machines_in_datacenters')[datacenter_id]
+                    json.datacenters_with_issues.push
+                        datacenter_id: datacenter_id
+                        datacenter_name: datacenters.get(datacenter_id).get('name')
+                        num_replicas: number_replicas
+                        num_machines: @model.get('actual_machines_in_datacenters')[datacenter_id]

             @.$el.html _template(json)
             # bind resolution events


Complicate the grunt file with per-action build commands (js/css/etc)

--- Web/gruntfile.coffee
+++ Web/gruntfile.coffee
@@ -2,13 +2,14 @@
 module.exports = (grunt) ->
 	grunt.initConfig
 		pkg: grunt.file.readJSON('package.json')
-		sass: main:
-			files:
-				'dist/style.css': 'css/rohpod.scss'
+		sass:
+			css:
+				src:  'css/rohpod.scss'
+				dest: 'build/css/rohpod.css'
 		copy:
 			js:
 				src:  'js/*.js'
-				dest: 'dist/'
+				dest: 'build/'
 				expand:  true
 				flatten: true
 			img:
@@ -16,12 +17,18 @@
 				dest: 'dist/'
 				expand:  true
 				flatten: true
-			jslib:
-				src:  'build/jslibs.min.js'
-				dest: 'dist/jslibs.min.js'
+			# jslib:
+			# 	src:  'build/jslibs.min.js'
+			# 	dest: 'dist/jslibs.min.js'
 			index:
 				src:  'index.htm'
+				dest: 'build/'
+			deploy:
+				src:  'build/*'
 				dest: 'dist/'
+				expand: true
+				flatten: true
+				filter: 'isFile'
 		concat:
 			jslib:
 				src:  'jslib/*.min.js'
@@ -32,20 +39,43 @@

 		myth:
 			css:
-				src:  'build/rohpod.css'
-				dest: 'dist/style.css'
-
+				src:  'build/css/rohpod.css'
+				dest: 'build/style.css'

 	grunt.loadNpmTasks 'grunt-contrib-sass'
 	grunt.loadNpmTasks 'grunt-contrib-copy'
 	grunt.loadNpmTasks 'grunt-contrib-concat'
 	grunt.loadNpmTasks 'grunt-contrib-clean'
 	grunt.loadNpmTasks 'grunt-myth'
-	grunt.registerTask 'default', () ->
+
+	grunt.registerTask 'default', [
+		'setup'
+		'clean:dist'
+		'css'
+		'js'
+		'templates'
+		'misc'
+		'copy:deploy'
+	]
+
+	grunt.registerTask 'setup', () ->
 		fs.mkdirSync 'build' unless fs.existsSync 'build'
 		grunt.task.run 'concat:jslib' unless fs.existsSync 'build/jslibs.min.js'
-		grunt.task.run [
-			'clean:dist'
-			'copy'
-			# 'sass'
-		]
+
+	grunt.registerTask 'css', [
+		'sass:css'
+		'myth:css'
+	]
+
+	grunt.registerTask 'js', [
+		'copy:js'
+	]
+
+	grunt.registerTask 'templates', [
+		'copy:index'
+	]
+
+	grunt.registerTask 'misc', [
+		'copy:img'
+	]
+


Remove 'jQuery' from calendar.js.coffee.

--- app/assets/javascripts/calendar.js.coffee
+++ app/assets/javascripts/calendar.js.coffee
@@ -103,10 +103,10 @@
     str += "
"
     str += "
"

-    jQuery("#calendar").get(0).innerHTML = str
+    $("#calendar").get(0).innerHTML = str

-    jQuery("#calendar_year").val(@selectedMonth.year)
-    jQuery("#calendar_month").val(@selectedMonth.month)
+    $("#calendar_year").val(@selectedMonth.year)
+    $("#calendar_month").val(@selectedMonth.month)

     if call && @callback
       @callback.call()


Fix star loading with turbolinks

Fix star loading with turbolinks

--- app/assets/javascripts/food_trucks.coffee
+++ app/assets/javascripts/food_trucks.coffee
@@ -13,5 +13,5 @@
     # Replace the numerical value with stars
     $(this).html $span

-$ ->
+document.addEventListener 'turbolinks:load', ->
   $('span.stars').stars()


hotfix/#1101 Karte in offer.show wird nicht angezeigt

--- app/assets/javascripts/gmaps/setup.coffee
+++ app/assets/javascripts/gmaps/setup.coffee
@@ -8,3 +8,6 @@

   initialize: -> # callback for when maps script is loaded from google
     Clarat.GMaps.loaded = true
+    # init map if there is a canvas
+    if document.getElementById('map-canvas')
+      Clarat.GMaps.presenter = new Clarat.GMaps.Presenter()


:pencil2: added background map

:pencil2: added background map

--- app/assets/javascripts/widgets/map.coffee
+++ app/assets/javascripts/widgets/map.coffee
@@ -1,18 +1,23 @@
 class Map
   constructor: () ->
-    layer_id = $('#sheetdata').data("layer")
-    @map = L.mapbox.map('map', "https://s3.amazonaws.com/maptiles.nypl.org/#{layer_id}/#{layer_id}spec.json",
+    @map = L.mapbox.map('map', 'nypllabs.g6ei9mm0',
       zoomControl: false
       scrollWheelZoom: false
       touchZoom: false
-      animate: false
-      attributionControl: true
+      animate: true
+      attributionControl: false
       minZoom: 12
       maxZoom: 21
       dragging: true
       tileLayer: # added this because maptiles.nypl does not support retina yet
         detectRetina: false
     )
+
+    layer_id = $('#sheetdata').data("layer")
+    @overlay = L.mapbox.tileLayer("https://s3.amazonaws.com/maptiles.nypl.org/#{layer_id}/#{layer_id}spec.json",
+      zIndex: 2
+      detectRetina: false # added this because maptiles.nypl does not support retina yet
+    ).addTo(@map)

     L.control.zoom(
       position: 'bottomleft'


Don't add already added song.

Don't add already added song.

--- app/client/js/react/pages/editsong.coffee
+++ app/client/js/react/pages/editsong.coffee
@@ -71,6 +71,10 @@
         @addSong()

       addSong: ->
+        if store.contains song
+          alert 'Song with such name and artist already exists.'
+          return
+
         errors = store.add song
         # TODO: Consider React helper for that.
         if errors.length


small change to update status

small change to update status

--- src/chat/chat.coffee
+++ src/chat/chat.coffee
@@ -184,20 +184,18 @@
     @context.winList.add win
     win

-  updateStatus: (status) ->
-    unless status
-      statusList = []
-      nick = @context.currentWindow.conn?.irc.nick ? @previousNick
-      away = @context.currentWindow.conn?.irc.away
-      channel = @context.currentWindow.target
-      topic = @context.currentWindow.conn?.irc.channels[channel]?.topic
-      statusList.push "[#{nick}]" if nick
-      statusList.push "(away)" if away
-      statusList.push "#{channel}" if channel
-      statusList.push "- #{topic}" if topic
-      statusList.push 'Welcome!' if statusList.length is 0
-      status = statusList.join ' '
-    $('#status').text(status)
+  updateStatus: ->
+    statusList = []
+    nick = @context.currentWindow.conn?.irc.nick ? @previousNick
+    away = @context.currentWindow.conn?.irc.away
+    channel = @context.currentWindow.target
+    topic = @context.currentWindow.conn?.irc.channels[channel]?.topic
+    statusList.push "[#{nick}]" if nick
+    statusList.push "(away)" if away
+    statusList.push "#{channel}" if channel
+    statusList.push "- #{topic}" if topic
+    statusList.push 'Welcome!' if statusList.length is 0
+    $('#status').text(statusList.join ' ')

   switchToWindowByIndex: (winNum) ->
       winNum = 10 if winNum is 0


debugging key name.

debugging key name.

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -20,7 +20,7 @@
       sensor: "1910x1080 @30fps"
       fov: "90 degrees diagonal with auto focus lens."
       latency: "127.7ms"
-      'audio recording': 'Camera built-in stereo microphone.'
+      audio_recording: 'Camera built-in stereo microphone.'
     cartlabel: "World camera: high resolution"
     cartSpecs: "Max resolution: 1910x1080 @30fps. FOV 90 degrees diagonal with auto focus lens. 120ms latency"
     dimensions: 0
@@ -42,7 +42,7 @@
       sensor: "1920x1080 @30fps, 1280x720 @60fps, 640x480 @120fps"
       fov: "100 degrees diagonal. Lenses are exchangeable: Headset ships with 60deg and 100 deg FOV lenses."
       latency: "5.7ms"
-      'audio recording': 'via host device microphone.'
+      audio_recording: 'via host device microphone.'
     cartlabel: "World camera: high speed"
     cartSpecs: "Variable Resolution: 1920x1080 @30fps, 1280x720 @60fps, 640x480 @120fps. FOV 100 degrees diagonal. Lenses are exchangeable: Headset ships with 60deg and 100 deg FOV lenses. 5.7ms latency."
     dimensions: 0


activity: stop "click" event of "Show More"

--- client/Main/utils.coffee
+++ client/Main/utils.coffee
@@ -54,6 +54,7 @@
       location.reload(true)

   showMoreClickHandler:(event)->
+    __utils.stopDOMEvent event
     $trg = $(event.target)
     more = "span.collapsedtext a.more-link"
     less = "span.collapsedtext a.less-link"


improved to deliver better performace javascript

--- app/assets/javascripts/app/transactions.coffee
+++ app/assets/javascripts/app/transactions.coffee
@@ -103,7 +103,9 @@
     # Used click instead of click because IE
     $(id).find("input").click( ->
       sum = 0
-      sum += 1 * $(k).siblings("label").find("span").data("rate") for k in $(self.conf.taxes_id).find("input:checkbox:checked")
+      $inputs = $(self.conf.taxes_id).find("input:checked")
+      #sum += 1 * $(k).siblings("label").find("span").data("rate") for k in $inputs
+      sum += 1 * $(k.nextSibling).find("span").data("rate") for k in $inputs

       $(self.conf.taxes_percentage_id).html(_b.ntc(sum)).data("val", sum)
       self.calculate_taxes()


collision

collision

--- src/collision.coffee
+++ src/collision.coffee
@@ -92,7 +92,7 @@
     radius: 10

   Collision.rayCircle(Point(0, 0), Point(1, 0), circle)
-  => true
+  # => true
   </pre></code>

   @name rayCircle


fix broken expunge

fix broken expunge

--- server/models/mailbox.coffee
+++ server/models/mailbox.coffee
@@ -267,12 +267,10 @@
                     (cb) -> imap.addFlags uids, '\\Deleted', cb
                     (cb) -> imap.expunge uids, cb
                     (cb) -> imap.closeBox cb
-                    (cb) -> Message.safeRemoveAllFromBox box.id, (err) ->
-                        if err
-                            log.error """
-                                fail to remove msg of box #{box.id}""", err
-                        # loop anyway
-                        cb()
+                    (cb) ->
+                        removal = new MessagesRemovalByMailbox
+                            mailboxID: @id
+                        removal.run cb
                 ], cbRelease
         , callback

@@ -288,7 +286,10 @@

 class TestMailbox extends Mailbox
     imap_expungeMails: (callback) =>
-        Message.safeRemoveAllFromBox @id, callback
+        removal = new MessagesRemovalByMailbox
+            mailboxID: @id
+
+        removal.run callback

 module.exports = Mailbox
 require('./model-events').wrapModel Mailbox
@@ -298,6 +299,7 @@
 _ = require 'lodash'
 async = require 'async'
 mailutils = require '../utils/jwz_tools'
+MessagesRemovalByMailbox = require '../processes/message_remove_by_mailbox'
 {Break, NotFound} = require '../utils/errors'
 {FETCH_AT_ONCE} = require '../utils/constants'


bugfix iPhone

bugfix iPhone

--- app/assets/javascripts/static_pages.js.coffee
+++ app/assets/javascripts/static_pages.js.coffee
@@ -39,7 +39,7 @@
 		$(".menu").toggleClass "menu-open-browser"
 		$('.siteContainer').toggleClass "site-open-browser"
 	$('body').css "position", "relative"
-	$('body').css "overflow", "auto"
+	$('.siteContainer').css "overflow", "auto"
 	$(".menu").data "state", "closed"

 openMenu = (e) ->
@@ -47,7 +47,7 @@
 		$(".menu").toggleClass "menu-open-mobile"
 		$('.siteContainer').toggleClass "site-open-mobile"
 		$('body').css "position", "fixed"
-		$('body').css "overflow", "hidden"
+		$('.siteContainer').css "overflow", "hidden"
 	else
 		$(".menu").toggleClass "menu-open-browser"
 		$('.siteContainer').toggleClass "site-open-browser"


Fix homepage specs

Fix homepage specs

--- apps/home/test/client.coffee
+++ apps/home/test/client.coffee
@@ -83,9 +83,8 @@
     benv.teardown()

   beforeEach ->
-    { @init } = @mod = benv.requireWithJadeify resolve(__dirname, '../client/index.coffee'), [
+    { @init, @HomeView } = @mod = benv.requireWithJadeify resolve(__dirname, '../client/index.coffee'), [
       'featuredLinksTemplate'
-      'featuredArtworksTemplate'
       'featuredShowsTemplate'
       'featuredPostsTemplate'
       'featuredArtistsTemplate'
@@ -93,59 +92,26 @@
     @mod.__set__ 'HeroUnitView', ->
     sinon.stub Backbone.history, 'start'
     sinon.stub Backbone, 'sync'
+    sinon.stub @HomeView::, 'renderArtworks'
     @init()

   afterEach ->
     Backbone.sync.restore()
     Backbone.history.start.restore()
-
-  context 'with a user', ->
-
-    beforeEach (done) ->
-      user = new CurrentUser(fabricate 'user', lab_features: ["Suggested Artworks"])
-      sinon.stub(user, 'hasSuggestions').returns true
-      CurrentUser = @mod.__get__ 'CurrentUser'
-      sinon.stub(CurrentUser, 'orNull').returns user
-      benv.render resolve(__dirname, '../templates/index.jade'), {
-          heroUnits: new HeroUnits([
-            fabricate 'site_hero_unit'
-            fabricate 'site_hero_unit'
-            fabricate 'site_hero_unit'
-          ]).models
-          featuredLinks: new FeaturedLinks([
-            fabricate 'featured_link'
-          ]).models
-
-          user: user
-          sd: {}
-        }, =>
-          @init()
-          done()
-
-    afterEach ->
-      CurrentUser.orNull.restore()
-
-    it 'renders suggested artworks', ->
-      Backbone.sync.args[4][2].success [fabricate('artwork', title: 'Foobaz')]
-      $('body').html().should.containEql "Foobaz"
-
-  it 'renders featured artworks', ->
-    Backbone.sync.args[0][2].success [fabricate 'set']
-    _.last(Backbone.sync.args)[2].success [fabricate 'artwork', title: "Foo At Bar"]
-    $('body').html().should.containEql "Foo At Bar"
+    @HomeView::renderArtworks.restore()

   it 'renders featured show', ->
-    Backbone.sync.args[1][2].success [fabricate 'set']
+    Backbone.sync.args[0][2].success [fabricate 'set']
     _.last(Backbone.sync.args)[2].success [fabricate 'show', name: "Fooshow At Bar"]
     $('body').html().should.containEql "Fooshow At Bar"

   it 'renders featured posts', ->
-    Backbone.sync.args[2][2].success [fabricate 'set']
+    Backbone.sync.args[1][2].success [fabricate 'set']
     _.last(Backbone.sync.args)[2].success [fabricate 'post', title: "Retrospect on Andy Foobar"]
     $('body').html().should.containEql "Retrospect on Andy Foobar"

   it 'renders links', ->
-    Backbone.sync.args[2][2].success [fabricate 'set']
+    Backbone.sync.args[1][2].success [fabricate 'set']
     _.last(Backbone.sync.args)[2].success [fabricate 'post', title: "Retrospect on Andy Foobar"]
     $('.home-featured-post-link').first().html().should.containEql '<a>'


allow gradezilla total columns to be rearranged

This allows assignment group total columns and the total column to be
rearranged by the user. It also removes the "move to front/end"
functionality as that will be implemented different in a later
patchset. Also, a later patchset will handle persisting the order.

fixes CNVS-35470

test plan:
 - Have a course with multiple assignments, assignment groups, and
   shows the total.
 - Load gradezilla.
 - Drag assignments, assignment group totals, and the total column
   until your hand gets tired.
 - Ensure all of those items move multiple times

Change-Id: Ie84d75484262be63469f7d94e200e8d789ce4658
Reviewed-on: https://gerrit.instructure.com/105848
Reviewed-by: Jeremy Neander <20252aad5efdc666f083d82ab63ed0f9f72a043e@instructure.com>
Tested-by: Jenkins
Reviewed-by: Derek Bender <d24d8a7ee520e34b1cec38800225d9f36b552df7@instructure.com>
QA-Review: Anju Reddy <851202533fb1f0b615e4547ba226c76996d060dd@instructure.com>
Product-Review: Christi Wruck

--- app/coffeescripts/gradezilla/Gradebook.coffee
+++ app/coffeescripts/gradezilla/Gradebook.coffee
@@ -156,8 +156,7 @@
         @options.settings['show_concluded_enrollments'] == "true"
       @showInactiveEnrollments =
         @options.settings['show_inactive_enrollments'] == "true"
-      @totalColumnInFront = UserSettings.contextGet 'total_column_in_front'
-      @numberOfFrozenCols = if @totalColumnInFront then 2 else 1
+      @numberOfFrozenCols = 1
       @gradingPeriods = GradingPeriodsApi.deserializePeriods(@options.active_grading_periods)
       if @options.grading_period_set
         @gradingPeriodSet = GradingPeriodSetsApi.deserializeSet(@options.grading_period_set)
@@ -479,8 +478,6 @@
       else
         @storeCustomColumnOrder()

-      @fixColumnReordering()
-
     reorderCustomColumns: (ids) ->
       $.ajaxJSON(@options.reorder_custom_columns_url, "POST", order: ids)

@@ -506,8 +503,6 @@
       columns.sort @makeColumnSortFn(newSortOrder)
       columns.splice(0, 0, frozen...)
       @grid.setColumns(columns)
-
-      @fixColumnReordering()

     makeColumnSortFn: (sortOrder) =>
       fn = switch sortOrder.sortType
@@ -574,7 +569,6 @@
     handleAssignmentMutingChange: (assignment) =>
       @renderAssignmentColumnHeader(assignment.id)
       @setAssignmentWarnings()
-      @fixColumnReordering()
       @buildRows()

     handleAssignmentGroupWeightChange: (assignment_group_options) =>
@@ -590,11 +584,6 @@
     handleSubmissionsDownloading: (assignmentId) =>
       @getAssignment(assignmentId).hasDownloadedSubmissions = true
       @renderAssignmentColumnHeader(assignmentId)
-
-    moveTotalColumn: =>
-      @totalColumnInFront = not @totalColumnInFront
-      UserSettings.contextSet 'total_column_in_front', @totalColumnInFront
-      window.location.reload()

     # filter, sort, and build the dataset for slickgrid to read from, then
     # force a full redraw
@@ -837,30 +826,6 @@

     unhighlightColumns: () =>
       @$grid.find('.hovered-column').removeClass('hovered-column')
-
-    # this is a workaroud to make it so only assignments are sortable but at the same time
-    # so that the total and final grade columns don't dissapear after reordering columns
-    fixColumnReordering: =>
-      $headers = $('#gradebook_grid .container_1').find('.slick-header-columns')
-      originalItemsSelector = $headers.sortable 'option', 'items'
-      onlyAssignmentColsSelector = '> *:not([id*="assignment_group"]):not([id*="total_grade"]):not([id*=student])'
-      (makeOnlyAssignmentsSortable = ->
-        $headers.sortable 'option', 'items', onlyAssignmentColsSelector
-        $notAssignments = $(originalItemsSelector, $headers).not($(onlyAssignmentColsSelector, $headers))
-        $notAssignments.data('sortable-item', null)
-      )()
-      originalStopFn = $headers.sortable 'option', 'stop'
-      (fixupStopCallback = ->
-        $headers.sortable 'option', 'stop', (event, ui) ->
-          # we need to set the items selector back to the default because slickgrid's 'stop'
-          # function relies on it to re-render correctly.  if not it will render without the
-          # assignment group and final grade columns
-          $headers.sortable 'option', 'items', originalItemsSelector
-          returnVal = originalStopFn.apply(this, arguments)
-          makeOnlyAssignmentsSortable() # set it back
-          fixupStopCallback() # originalStopFn re-creates sortable widget so we need to re-fix
-          returnVal
-      )()

     minimizeColumn: ($columnHeader) =>
       columnDef = $columnHeader.data('column')
@@ -1392,14 +1357,11 @@
           minWidth: columnWidths.total.min
           maxWidth: columnWidths.total.max
           width: totalWidth
-          cssClass: if @totalColumnInFront then 'meta-cell' else 'total-cell'
+          cssClass: 'total-cell'
           type: 'total_grade'
           neverSort: true

-        if @totalColumnInFront
-          @parentColumns.push total_column
-        else
-          @aggregateColumns.push total_column
+        @aggregateColumns.push total_column

       $widthTester.remove()


Fix broken hero import error

Fix broken hero import error


Fix the koding-counter

Fix the koding-counter

--- node_modules_koding/koding-counter/lib/koding-counter/index.coffee
+++ node_modules_koding/koding-counter/lib/koding-counter/index.coffee
@@ -9,7 +9,7 @@
     @collectionName = collectionName ? 'counters'
     @offset         = offset ? 0

-  wrap = (callback) -> (err, doc)-> callback err, doc?.seq
+  wrap = (callback) -> (err, doc)-> callback err, (doc?.seq - 1) or 0

   getCollection: -> @db.collection @collectionName


IDE: Call selectWorkspace method on sidebar directly.

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -14,6 +14,11 @@
         loadIDE { machine, workspace }
       else
         routeToLastWorkspace()
+
+
+  selectWorkspaceOnSidebar = (data) ->
+    mainView = KD.getSingleton 'mainView'
+    mainView.activitySidebar.selectWorkspace data

   getMachineByUId = (uid) ->
@@ -33,7 +38,7 @@
         app.workspaceData     = workspace

         appManager.tell 'IDE', 'mountMachineByMachineUId', machineUId
-        KD.getSingleton('mainView').emit 'WorkspaceSelected', data
+        selectWorkspaceOnSidebar data

     return fallback()  unless ideApps?.instances

@@ -49,7 +54,7 @@

     if ideInstance
       appManager.showInstance ideInstance
-      KD.getSingleton('mainView').emit 'WorkspaceSelected', data
+      selectWorkspaceOnSidebar data
     else
       fallback()


Use hype permission attribute for button state

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -125,7 +125,8 @@
                   text: if userAlreadyHyped then osu.trans('beatmaps.hype.button_done') else osu.trans('beatmaps.hype.button')
                   icon: 'fas fa-bullhorn'
                   props:
-                    disabled: userAlreadyHyped
+                    disabled: !@props.beatmapset.current_user_attributes.can_hype
+                    title: @props.beatmapset.current_user_attributes?.can_hype_reason
                     onClick: @focusHypeInput

           if mapCanBeNominated || mapIsQualified


silent mode

silent mode

--- server.coffee
+++ server.coffee
@@ -13,13 +13,14 @@

 #####

-CHAT_OFF = 'nochat' in process.argv
+config.nochat ?= 'nochat' in process.argv
+config.silent ?= 'silent' in process.argv

 sendChatMessage = (message) ->

     console.log "sending message: \n#{message}"

-    if CHAT_OFF then return
+    if config.nochat then return

     chatURL = "http://partychat-hooks.appspot.com/post/p_ngvimtvm?" + qs.stringify({ message }).replace(/\'/g, '%27')

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

 #####

-sendChatMessage 'Hello!'
+if not config.silent then sendChatMessage 'Hello!'

 server = http.createServer (req, res) ->


e2e render logic up

e2e render logic up

--- server.coffee
+++ server.coffee
@@ -25,7 +25,7 @@
 	contexts:

 		# Should we render on the server-side?
-		render: true
+		render: not process.env['E2E']?

 		# Context timeout in milliseconds.
 		timeout: 1000 * 60 * 5


combineCanvases() supports arbitrary number of canvases. Sorry Blake.

--- src/core/util.coffee
+++ src/core/util.coffee
@@ -22,13 +22,18 @@
     window.addEventListener 'orientationchange', resize
     resize()

-  combineCanvases: (a, b) ->
+  combineCanvases: (canvases...) ->
     c = document.createElement('canvas')
-    c.width = Math.max(a.width, b.width)
-    c.height = Math.max(a.height, b.height)
+    c.width = canvases[0].width
+    c.height = canvases[0].height
+    for canvas in canvases
+      c.width = Math.max(canvas.width, c.width)
+      c.height = Math.max(canvas.height, c.height)
     ctx = c.getContext('2d')
-    ctx.drawImage(a, 0, 0)
-    ctx.drawImage(b, 0, 0)
+
+    for canvas in canvases
+      ctx.drawImage(canvas, 0, 0)
+      ctx.drawImage(canvas, 0, 0)
     c

   renderShapes: (shapes, bounds, scale=1, canvas=null) ->


Adding a try/catch around the bindAll stuff for better error reporting

--- src/core/view.coffee
+++ src/core/view.coffee
@@ -208,7 +208,10 @@
 bindEventHandlers = (events={})->
   for eventSignature, handler of events
     if _.isString(handler)
-      _.bindAll @, handler
+      try
+        _.bindAll @, handler
+      catch e
+        console.log "Error binding to handler - #{handler} = #{e} #{@identifier()}"

 Luca.View.deferrableEvent = "reset"


Catalog offering may be undefined

Catalog offering may be undefined

--- src/course/item.cjsx
+++ src/course/item.cjsx
@@ -14,7 +14,7 @@
     routeData = navigation.getDataByView('task')
     return null unless course.isRegistered()

-    category = course.catalog_offering_identifier.toLowerCase()
+    category = course.catalog_offering_identifier?.toLowerCase() or 'unknown'
     link = interpolate bookUrlPattern, {cnxUrl, ecosystem_book_uuid}

     <BS.ListGroupItem


Update express.coffee

Update express.coffee

--- app/templates/mvc-coffee/config/express.coffee
+++ app/templates/mvc-coffee/config/express.coffee
@@ -23,6 +23,11 @@
   env = process.env.NODE_ENV || 'development'
   app.locals.ENV = env;
   app.locals.ENV_DEVELOPMENT = env == 'development'
+  <% if(options.viewEngine == 'swig'){ %>
+  	if app.locals.ENV == 'development'
+  		app.set 'view cache', false
+  		swig.setDefaults cache: false
+  <% } %>

   # app.use(favicon(config.root + '/public/img/favicon.ico'));
   app.use logger 'dev'


JCredential: remove old shareWith method and use instance level for client requests

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -262,60 +262,19 @@
       else
         callback new KodingError "Target does not support credentials."

+
   # .share can be used like this:
   #
   # JCredentialInstance.share { user: yes, owner: no, target: "gokmen"}, cb
   #                                      group or user slug -> ^^^^^^

-  shareWith: permit
+  shareWith$: permit

     advanced: [
       { permission: 'update credential', validateWith: Validators.own }
     ]

-    success: (client, options, callback)->
-
-      { delegate } = client.connection
-      { target, user, owner } = options
-      user ?= yes
-
-      # Owners cannot unassign them from a credential
-      # Only another owner can unassign any other owner
-      if delegate.profile.nickname is target
-        return callback null
-
-      setPermissionFor = (target, callback)=>
-
-        Relationship.remove {
-          targetId : @getId()
-          sourceId : target.getId()
-        }, (err)=>
-
-          if user
-            as = if owner then 'owner' else 'user'
-            target.addCredential this, { as }, (err)-> callback err
-          else
-            callback err
-
-      JName.fetchModels target, (err, result)=>
-
-        if err or not result
-          return callback new KodingError "Target not found."
-
-        { models } = result
-        [ target ] = models
-
-        if target instanceof JUser
-          target.fetchOwnAccount (err, account)=>
-            if err or not account
-              return callback new KodingError "Failed to fetch account."
-            setPermissionFor account, callback
-
-        else if target instanceof JGroup
-          setPermissionFor target, callback
-
-        else
-          callback new KodingError "Target does not support credentials."
+    success: JCredential::shareWith

   delete: permit 'delete credential',