Get CPU size from payment plan

Get CPU size from payment plan

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -69,8 +69,8 @@
     if not currentGroup or currentGroup is 'koding' then "koding~#{KD.nick()}"
     else currentGroup

-  createGroupVM:(type='personal', callback)->
-    defaultVMOptions = {cpu : 1, disk : 1, ram : 1}
+  createGroupVM:(type='personal', cpuSize, callback)->
+    defaultVMOptions = {cpu : cpuSize, disk : 1, ram : 1}
     group = KD.singletons.groupsController.getCurrentGroup()

     group.fetchBundle (err, bundle)->
@@ -115,7 +115,9 @@

     # Take this to a better place, possibly to payment controller.
-    makePayment = (type, planCode, callback)->
+    makePayment = (type, plan, callback)->
+      planCode = plan.code
+      cpuSize  = plan.item.meta.cpu
       if type is 'shared'
         group = KD.singletons.groupsController.getCurrentGroup()
         group.checkPayment (err, payments)->
@@ -139,7 +141,7 @@
                   if err
                     onError err
                   else
-                    vmController.createGroupVM type, vmCreateCallback
+                    vmController.createGroupVM type, cpuSize, vmCreateCallback
                     onSuccess()
                     callback()
               paymentModal.on "KDModalViewDestroyed", -> vmController.emit "PaymentModalDestroyed"
@@ -152,7 +154,7 @@
           KD.remote.api.JRecurlyPlan.getPlanWithCode planCode, (err, plan)->
             plan.subscribe {}, (err, subscription)->
               return cb err  if cb and err
-              vmController.createGroupVM type, vmCreateCallback
+              vmController.createGroupVM type, cpuSize, vmCreateCallback
               cb?()
         KD.remote.api.JRecurlyPlan.getUserAccount (err, account)->
           if err
@@ -176,7 +178,7 @@
       else
         KD.singletons.finderController.mountVm vm.name
         vmController.emit 'VMListChanged'
-      paymentModal.destroy()
+      paymentModal?.destroy()

     group = KD.singletons.groupsController.getGroupSlug()

@@ -221,7 +223,7 @@
                 form                = modal.modalTabs.forms["Create VM"]
                 {personal, shared}  = form.buttons

-                makePayment formData.type, @paymentPlans[formData.host].code, ->
+                makePayment formData.type, @paymentPlans[formData.host], ->
                   modal.destroy()
               buttons               :
                 personal            :


fix: Data editor save/update errors are not displayed


diversify filtered select to work as autocomplete

--- lib/assets/javascripts/directives/filteredselect.js.coffee
+++ lib/assets/javascripts/directives/filteredselect.js.coffee
@@ -24,50 +24,34 @@
       collection   = $parse(match[8])
       modelValue   = match[1].replace(match[5] + '.','')
       viewValue    = if match[2] then match[2].replace(match[5] + '.','') else modelValue
-      template     = angular.element("

")
-      search       = angular.element "<input type='search' placeholder='Search'>"
-      tempHolder   = angular.element("

")
       viewValueFn  = $parse(viewValue || modelValue)
       modelValueFn = $parse(modelValue || viewValue)
-      tempHolder.addClass('bottom') if element.hasClass('bottom')
-      template.bind 'mousewheel DOMMouseScroll', (event)->
-        delta = event.wheelDelta or event.originalEvent and event.originalEvent.wheelDelta or -event.detail
-        bottomOverflow = template[0].scrollTop + template.outerHeight() - template[0].scrollHeight >= 0
-        topOverflow = template[0].scrollTop <= 0
-        if delta < 0 and bottomOverflow or delta > 0 and topOverflow
-          event.preventDefault()
-      scrollStart = null
-      scrollTop   = null
-      template.bind 'touchstart', (event)->
-        event.stopPropagation()
-        scrollStart = event.originalEvent.touches[0].clientY
-      template.bind 'touchmove', (event) ->
-        event.stopPropagation()
-        scroll = event.originalEvent.touches[0].clientY
-        scrollStart = scroll unless scrollStart
-        if scroll > scrollStart
-          event.preventDefault() if template.scrollTop() == 0
-        else if scroll < scrollStart
-          newScrollTop = template.scrollTop()
-          event.preventDefault() if scrollTop == newScrollTop
-          scrollTop = newScrollTop
-        scrollStart = scroll
-        return true
-      template.bind 'touchend', ->
-        event.stopPropagation()
-        scrollStart = null
-      body = angular.element('body')
-      body.bind 'keydown', (event) ->
-        tempHolder.removeClass('active') if event.keyCode == 27
-      tempHolder.append search
-      tempHolder.append template
-      body.append tempHolder
+      template     = angular.element("

")
+      if isMobile = typeof attrs.ngMobile != 'undefined'
+        search       = angular.element "<input type='search' placeholder='Search'>"
+        tempHolder   = angular.element("

")
+        tempHolder.addClass('bottom') if element.hasClass('bottom')
+        body = angular.element(document.body)
+        body.bind 'keydown', (event) ->
+          tempHolder.removeClass('active') if event.keyCode == 27
+        tempHolder.append search
+        tempHolder.append template
+        body.append tempHolder
+      else
+        span         = angular.element ""
+        search       = angular.element "<input class='autocomplete' type='search' placeholder='" + attrs.placeholder + "'>"
+        tempHolder   = angular.element "
"
+        span.append search
+        span.append tempHolder
+        element.append span
+        tempHolder.append template
+
       ulHeight = ->
         full = window.innerHeight
         full = full/3 if element.hasClass('bottom')
         full - search[0].offsetHeight + 'px'
-      template.css('height',ulHeight())
-      scope.$watch collection, (newVal,oldVal) ->
+      template.css('height',ulHeight()) if isMobile
+      scope.$watchCollection collection, (newVal,oldVal) ->
         return if newVal == oldVal
         setInitialValue()
         buildTemplate()
@@ -77,7 +61,7 @@
       search.bind 'input', ->
         buildTemplate()
       buildTemplate = ->
-        list= []
+        template.empty()
         obj={}
         obj[viewValue] = search.val() || ''
         return unless collection(scope)
@@ -86,30 +70,44 @@
           [filterType,value] = filter.replace(/\s+/,'').split(':')
           filteredList = $filter(filterType)(filteredList, value)
         for item in filteredList
-          li = angular.element '
' + viewValueFn(item) + '
'
+          if isMobile
+            li = angular.element '
' + viewValueFn(item) + '
'
+          else
+            li = angular.element '' + viewValueFn(item) + ''
+          ip = angular.element '<input type="hidden">'
+          ip.val(modelValueFn(item))
+          li.append(ip)
           li.bind 'mousedown', ($event)->
-            updateValue($event.target.attributes['ng-model-value'].value)
-          list.push li
-        template.empty()
-        template.append(list)
+            updateValue($event.target.children[0].value)
+          template.append(li)
       setInitialValue = ->
-        unless model = ngModel.$modelValue
-          view = attrs.placeholder
-          element.css('color','rgba(0,0,0,0.4)')
+        unless isMobile
+          obj = {}
+          obj[modelValue] = ngModel.$modelValue
+          list = $filter('filter')(collection(scope), obj,true)
+          viewScope = list[0] if list
+          search.val(if viewScope then viewValueFn(viewScope) else '')
         else
-          element.css('color','')
-          obj = {}
-          obj[modelValue] = model
-          list = $filter('filter')(collection(scope), obj)
-          viewScope = list[0] if list
-          view = if viewScope then viewValueFn(viewScope) else attrs.placeholder
-        element.html('')
-        element.html(view)
+          unless model = ngModel.$modelValue
+            view = attrs.placeholder
+            element.css('color','rgba(0,0,0,0.4)')
+          else
+            element.css('color','')
+            obj = {}
+            obj[modelValue] = model
+            list = $filter('filter')(collection(scope), obj,true)
+            viewScope = list[0] if list
+            view = if viewScope then viewValueFn(viewScope) else attrs.placeholder
+          element.html('')
+          element.html(view)
+
       setInitialValue()
       buildTemplate()
       updateValue = (model) ->
         ngModel.$setViewValue(model)
         tempHolder.removeClass('active')
+        $timeout ->
+          setInitialValue() unless isMobile
       done = false
       fieldset = element
       until done
@@ -117,17 +115,18 @@
         unless done = typeof fieldset[0] == 'undefined'
           done = fieldset[0].tagName == 'FIELDSET'
       element.parent('fieldset')
-      element.bind 'mousedown', (event)->
-        return if tempHolder.hasClass('active')
-        return if attrs.disabled || $parse(attrs.ngDisabled)(scope)
-        if fieldset
-          ngdis = if fieldset[0].attributes.ngDisabled then fieldset[0].attributes.ngDisabled.value else ''
-          return if fieldset[0].attributes.disabled || $parse(ngdis)(scope)
-        search.val('')
-        buildTemplate()
-        tempHolder.css('top',event.clientY)
-        tempHolder.css('transition','none')
-        $timeout ->
-          tempHolder.css('transition','')
-          tempHolder.css('top','')
-          tempHolder.addClass('active')
+      if isMobile
+        element.bind 'mousedown', (event)->
+          return if tempHolder.hasClass('active')
+          return if attrs.disabled || $parse(attrs.ngDisabled)(scope)
+          if fieldset.length > 0
+            ngdis = if fieldset[0].attributes.ngDisabled then fieldset[0].attributes.ngDisabled.value else ''
+            return if fieldset[0].attributes.disabled || $parse(ngdis)(scope)
+          search.val('')
+          buildTemplate()
+          tempHolder.css('top',event.clientY)
+          tempHolder.css('transition','none')
+          $timeout ->
+            tempHolder.css('transition','')
+            tempHolder.css('top','')
+            tempHolder.addClass('active')


WritingComponent cleaning

WritingComponent cleaning

--- pub/talk/src/js/components/WritingComponent.coffee
+++ pub/talk/src/js/components/WritingComponent.coffee
@@ -6,6 +6,68 @@
 StationActions  = require '../actions/StationActions.coffee'
 StationStore    = require '../stores/StationStore.coffee'
 Member          = require './MemberComponent.coffee'
+
+SHIPSHAPE = ///
+^~?(                              #preamble
+   [a-z]{3}                       # galaxy
+ | [a-z]{6}(-[a-z]{6}){0,3}       # star - moon
+ |   [a-z]{6}(-[a-z]{6}){3}       # comet
+   (--[a-z]{6}(-[a-z]{6}){3})+    #
+)$                                #postamble
+///
+PO = '''
+dozmarbinwansamlitsighidfidlissogdirwacsabwissib
+rigsoldopmodfoglidhopdardorlorhodfolrintogsilmir
+holpaslacrovlivdalsatlibtabhanticpidtorbolfosdot
+losdilforpilramtirwintadbicdifrocwidbisdasmidlop
+rilnardapmolsanlocnovsitnidtipsicropwitnatpanmin
+ritpodmottamtolsavposnapnopsomfinfonbanporworsip
+ronnorbotwicsocwatdolmagpicdavbidbaltimtasmallig
+sivtagpadsaldivdactansidfabtarmonranniswolmispal
+lasdismaprabtobrollatlonnodnavfignomnibpagsopral
+bilhaddocridmocpacravripfaltodtiltinhapmicfanpat
+taclabmogsimsonpinlomrictapfirhasbosbatpochactid
+havsaplindibhosdabbitbarracparloddosbortochilmac
+tomdigfilfasmithobharmighinradmashalraglagfadtop
+mophabnilnosmilfopfamdatnoldinhatnacrisfotribhoc
+nimlarfitwalrapsarnalmoslandondanladdovrivbacpol
+laptalpitnambonrostonfodponsovnocsorlavmatmipfap
+
+zodnecbudwessevpersutletfulpensytdurwepserwylsun
+rypsyxdyrnuphebpeglupdepdysputlughecryttyvsydnex
+lunmeplutseppesdelsulpedtemledtulmetwenbynhexfeb
+pyldulhetmevruttylwydtepbesdexsefwycburderneppur
+rysrebdennutsubpetrulsynregtydsupsemwynrecmegnet
+secmulnymtevwebsummutnyxrextebfushepbenmuswyxsym
+selrucdecwexsyrwetdylmynmesdetbetbeltuxtugmyrpel
+syptermebsetdutdegtexsurfeltudnuxruxrenwytnubmed
+lytdusnebrumtynseglyxpunresredfunrevrefmectedrus
+bexlebduxrynnumpyxrygryxfeptyrtustyclegnemfermer
+tenlusnussyltecmexpubrymtucfyllepdebbermughuttun
+bylsudpemdevlurdefbusbeprunmelpexdytbyttyplevmyl
+wedducfurfexnulluclennerlexrupnedlecrydlydfenwel
+nydhusrelrudneshesfetdesretdunlernyrsebhulryllud
+remlysfynwerrycsugnysnyllyndyndemluxfedsedbecmun
+lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes
+'''
+
+Audience = recl
+  displayName: "Audience"
+  onKeyDown: (e) ->
+    if e.keyCode is 13
+      e.preventDefault()
+      setTimeout () ->
+          $('#writing').focus()
+        ,0
+      return false
+  render: ->
+    div {
+          id:"audi"
+          className:"audi valid-#{@props.valid}"
+          contentEditable:true
+          @onKeyDown
+          onBlur:@props.onBlur
+        }, @props.audi.join(" ")

 module.exports = recl
   set: ->
@@ -26,20 +88,21 @@
     s.ludi = _.without s.ludi, window.util.mainStationPath window.urb.user
     s

-  getInitialState: -> @stateFromStore()
+  getInitialState: -> _.extend @stateFromStore(), length:0, lengthy: false

   typing: (state) ->
     if @state.typing[@state.station] isnt state
       StationActions.setTyping @state.station,state

-  _blur: ->
+  onBlur: ->
     @$writing.text @$writing.text()
     MessageActions.setTyping false
     @typing false

-  _focus: ->
+  onFocus: ->
     MessageActions.setTyping true
     @typing true
+    @cursorAtEnd

   addCC: (audi) ->
     listening = @state.config[window.util.mainStation(window.urb.user)].sources
@@ -62,22 +125,14 @@
       audi = @state.audi
     audi = @addCC audi
     MessageActions.sendMessage @$writing.text().trim(),audi
-    @$length.text "0/62"
     @$writing.text('')
+    @setState length:0
     @set()
     @typing false

-  _audiKeyDown: (e) ->
-    if e.keyCode is 13
-      e.preventDefault()
-      setTimeout () ->
-          $('#writing').focus()
-        ,0
-      return false
-
-  _writingKeyUp: (e) ->
+  onKeyUp: (e) ->
     if not window.urb.util.isURL @$writing.text()
-      @$length.toggleClass('valid-false',(@$writing.text().length > 62))
+      @setState lengthy: (@$writing.text().length > 62)
     # r = window.getSelection().getRangeAt(0).cloneRange()
     # @$writing.text @$writing.text()
     # setTimeout =>
@@ -87,7 +142,7 @@
     #     console.log r
     #   ,0

-  _writingKeyDown: (e) ->
+  onKeyDown: (e) ->
     if e.keyCode is 13
       txt = @$writing.text()
       e.preventDefault()
@@ -95,10 +150,10 @@
            window.urb.util.isURL @$writing.text() )
         @sendMessage()
       return false
-    @_input()
+    @onInput()
     @set()

-  _input: (e) ->
+  onInput: (e) ->
     text   = @$writing.text()
     length = text.length
     # geturl = new RegExp [
@@ -113,11 +168,10 @@
     #   for url in urls
     #     length -= url.length
     #     length += 10
-    @$length.text "#{length}/62"
-
-  _setFocus: -> @$writing.focus()
+    @setState {length}

   _validateAudiPart: (a) ->
+    a = a.trim()
     # if a[0] isnt "~"
     #   return false
     if a.indexOf("/") isnt -1
@@ -127,9 +181,9 @@
       ship = _a[0]
     else
       ship = a
-    if ship.length < 3
-      return false
-    return true
+
+    return (SHIPSHAPE.test ship) and
+      _.all (ship.match /[a-z]{3}/g), (a)-> -1 isnt PO.indexOf a

   _validateAudi: ->
     v = $('#audi').text()
@@ -138,23 +192,17 @@
       return true
     if v.length < 5 # zod/a is shortest
       return false
-    v = v.split " "
-    for a in v
-      a = a.trim()
-      valid = @_validateAudiPart(a)
-    valid
+    _.all (v.split /\ +/), @_validateAudiPart

   _setAudi: ->
     valid = @_validateAudi()
     StationActions.setValidAudience valid
     if valid is true
-      v = $('#audi').text()
-      if v.length is 0 then v = window.util.mainStationPath window.urb.user
-      v = v.split " "
-      for k,_v of v
-        if _v[0] isnt "~" then v[k] = "~#{_v}"
-      StationActions.setAudience v
-      v
+      stan = $('#audi').text() || window.util.mainStationPath window.urb.user
+      stan = (stan.split /\ +/).map (v)->
+        if v[0] is "~" then v else "~"+v
+      StationActions.setAudience stan
+      stan
     else
       false

@@ -178,12 +226,11 @@
     StationStore.addChangeListener @_onChangeStore
     MessageStore.addChangeListener @_onChangeStore
     @$el = $ @getDOMNode()
-    @$length = $('#length')
     @$writing = $('#writing')
     @$writing.focus()
     if @get()
       @$writing.text @get()
-      @_input()
+      @onInput()
     @interval = setInterval =>
         @$el.find('.time').text @getTime()
       , 1000
@@ -210,25 +257,14 @@
     div {className:k}, [
       (div {className:"attr"}, [
         (React.createElement Member, iden)
-        (div {
-          id:"audi"
-          className:"audi valid-#{@state.valid}"
-          contentEditable:true
-          onKeyDown: @_audiKeyDown
-          onBlur:@_setAudi
-          }, audi.join(" "))
+        (React.createElement Audience, {audi,valid:@state.valid, onBlur:@_setAudi})
         (div {className:"time"}, @getTime())
       ])
       (div {
           id:"writing"
           contentEditable:true
-          onFocus: @_focus
-          onBlur: @_blur
-          onInput: @_input
-          onPaste: @_input
-          onKeyDown: @_writingKeyDown
-          onKeyUp: @_writingKeyUp
-          onFocus: @cursorAtEnd
+          onPaste: @onInput
+          @onInput, @onFocus, @onBlur, @onKeyDown, @onKeyUp
         }, "")
-      div {id:"length"}, "0/62"
+       (div {id:"length",className:("valid-false" if @state.lengthy)}, "#{@state.length}/62")
       ]


Use fs.resolveOnLoadPath() to find ctags executable

--- src/packages/symbols-view/lib/tag-generator.coffee
+++ src/packages/symbols-view/lib/tag-generator.coffee
@@ -1,6 +1,7 @@
 Point = require 'point'
 $ = require 'jquery'
 BufferedProcess = require 'buffered-process'
+fs = require 'fs-utils'

 module.exports =
 class TagGenerator
@@ -17,7 +18,7 @@
   generate: ->
     deferred = $.Deferred()
     tags = []
-    command = require.resolve('ctags')
+    command = fs.resolveOnLoadPath('ctags')
     args = ['--fields=+KS', '-nf', '-', @path]
     stdout = (lines) =>
       for line in lines.split('\n')


remove some logging

remove some logging

--- server/helpers/localization_manager.coffee
+++ server/helpers/localization_manager.coffee
@@ -30,7 +30,6 @@

     # execute polyglot.t, for server-side localization
     t: (key, params = {}) ->
-        console.log "NOW", key, params, @polyglot
         return @polyglot?.t key, params

     render: (name, options, callback) ->


Crawlable: removed topics/activity headers

--- server/lib/crawler/staticpages/feed.coffee
+++ server/lib/crawler/staticpages/feed.coffee
@@ -113,7 +113,7 @@

             <meta itemprop="mainContentOfPage" content="true"/>
-
Latest activities

+
Latest activities

             <meta itemprop="itemListOrder" content="Descending" />
       """
   else if contentType is "Topics"
@@ -121,7 +121,7 @@
       """

           <meta itemprop="mainContentOfPage" content="true"/>
-
Latest topics

+
Latest topics

           <meta itemprop="itemListOrder" content="Descending" />

       """
@@ -148,8 +148,8 @@

-      #{tag.counts?.post ? 0}Posts
-      #{tag.counts?.followers ? 0}Followers
+      #{tag.counts?.post ? 0} Posts
+      #{tag.counts?.followers ? 0} Followers

#{tag.body ? ''}


Add more tests

Add more tests

--- test/unit/controllers/projectsControllerSpec.coffee
+++ test/unit/controllers/projectsControllerSpec.coffee
@@ -73,6 +73,56 @@
         issue_statuses: [],
         issue_types: [],
     }
+    users: [
+        {
+            id: 1,
+            username: "admin",
+            first_name: "",
+            last_name: "",
+            full_name: "admin",
+            email: "admin@taiga.io",
+            is_active: true,
+        },
+        {
+            id: 2,
+            username: "user-0",
+            first_name: "Marina",
+            last_name: "Medina",
+            full_name: "Marina Medina",
+            email: "ducimus@maiores.net",
+            is_active: true,
+        }
+    ]
+    permissions: [
+        {
+            id: 90,
+            name: "Can add issue",
+            codename: "add_issue"
+        },
+        {
+            id: 91,
+            name: "Can change issue",
+            codename: "change_issue"
+        }
+    ]
+    roles: [
+        {
+            id: 4,
+            name: "Back",
+            permissions: [90, 91],
+            computable: true,
+            project: 1,
+            order: 40
+        },
+        {
+            id: 5,
+            name: "Product Owner",
+            permissions: [90, 91],
+            computable: false,
+            project: 1,
+            order: 50
+        }
+    ]
 }

 describe "projectsController", ->
@@ -175,7 +225,7 @@
             httpBackend = $httpBackend
             httpBackend.whenGET(APIURL+"/sites").respond(200, {test: "test"})
             httpBackend.whenGET("#{APIURL}/resolver?project=test").respond(200, {project: 1})
-            httpBackend.whenGET("#{APIURL}/projects/1?").respond(200, FIXTURES["project"])
+            httpBackend.whenGET("#{APIURL}/projects/1?").respond(200, FIXTURES.project)
             httpBackend.whenPATCH("#{APIURL}/projects/1", {name:"New name"}).respond(
                                                              202, {detail: "success"})
             httpBackend.whenPATCH("#{APIURL}/projects/1", {total_milestones: "Error"}).respond(
@@ -244,10 +294,8 @@
             })
             httpBackend = $httpBackend
             httpBackend.whenGET(APIURL+"/sites").respond(200, {test: "test"})
-            httpBackend.whenGET("#{APIURL}/resolver?project=test").respond(200, {
-                project: 1
-            })
-            httpBackend.whenGET("#{APIURL}/projects/1?").respond(200, FIXTURES["project"])
+            httpBackend.whenGET("#{APIURL}/resolver?project=test").respond(200, {project: 1})
+            httpBackend.whenGET("#{APIURL}/projects/1?").respond(200, FIXTURES.project)
             httpBackend.flush()
         ))

@@ -274,3 +322,146 @@

         it "should be actived", ->
             expect(ctrl.isActive("values")).to.be.true
+
+
+    describe "ProjecAdminMilestonesController", ->
+        httpBackend = null
+        scope = null
+        ctrl = null
+
+        beforeEach(inject(($rootScope, $controller, $httpBackend) ->
+            scope = $rootScope.$new()
+            routeParams = {
+                pslug: "test"
+            }
+            ctrl = $controller("ProjectAdminMilestonesController", {
+                $scope: scope,
+                $routeParams: routeParams,
+            })
+            httpBackend = $httpBackend
+            httpBackend.whenGET(APIURL+"/sites").respond(200, {test: "test"})
+            httpBackend.whenGET("#{APIURL}/resolver?project=test").respond(200, {project: 1})
+            httpBackend.whenGET("#{APIURL}/projects/1?").respond(200, FIXTURES.project)
+            httpBackend.flush()
+        ))
+
+        afterEach ->
+            httpBackend.verifyNoOutstandingExpectation()
+            httpBackend.verifyNoOutstandingRequest()
+
+        it "should have section projects", ->
+            expect(ctrl.section).to.be.equal("admin")
+
+        it "should have a title", ->
+            expect(ctrl.getTitle()).to.be.equal("common.admin-panel")
+
+        it "should change the location", ->
+            sinon.spy(ctrl.location, "url")
+
+            ctrl.goTo("milestones")
+
+            ctrl.location.url.should.have.been.calledOnce
+            ctrl.location.url.should.have.been.calledWith("/project/test/admin/milestones")
+
+        it "should set the breadcrumb", ->
+            expect(ctrl.rootScope.pageBreadcrumb).to.be.lengthOf(2)
+
+        it "should be actived", ->
+            expect(ctrl.isActive("milestones")).to.be.true
+
+
+    describe "ProjecAdminMembershipsController", ->
+        httpBackend = null
+        scope = null
+        ctrl = null
+
+        beforeEach(inject(($rootScope, $controller, $httpBackend) ->
+            scope = $rootScope.$new()
+            routeParams = {
+                pslug: "test"
+            }
+            ctrl = $controller("ProjectAdminMembershipsController", {
+                $scope: scope,
+                $routeParams: routeParams,
+            })
+            httpBackend = $httpBackend
+            httpBackend.whenGET(APIURL+"/sites").respond(200, {test: "test"})
+            httpBackend.whenGET("#{APIURL}/resolver?project=test").respond(200, {project: 1})
+            httpBackend.whenGET("#{APIURL}/projects/1?").respond(200, FIXTURES.project)
+            httpBackend.whenGET("#{APIURL}/users?project=1").respond(200, FIXTURES.users)
+            httpBackend.whenGET("#{APIURL}/roles?project=1").respond(200, FIXTURES.roles)
+            httpBackend.flush()
+        ))
+
+        afterEach ->
+            httpBackend.verifyNoOutstandingExpectation()
+            httpBackend.verifyNoOutstandingRequest()
+
+        it "should have section projects", ->
+            expect(ctrl.section).to.be.equal("admin")
+
+        it "should have a title", ->
+            expect(ctrl.getTitle()).to.be.equal("common.admin-panel")
+
+        it "should change the location", ->
+            sinon.spy(ctrl.location, "url")
+
+            ctrl.goTo("memberships")
+
+            ctrl.location.url.should.have.been.calledOnce
+            ctrl.location.url.should.have.been.calledWith("/project/test/admin/memberships")
+
+        it "should set the breadcrumb", ->
+            expect(ctrl.rootScope.pageBreadcrumb).to.be.lengthOf(2)
+
+        it "should be actived", ->
+            expect(ctrl.isActive("memberships")).to.be.true
+
+
+    describe "ProjecAdminRolesController", ->
+        httpBackend = null
+        scope = null
+        ctrl = null
+
+        beforeEach(inject(($rootScope, $controller, $httpBackend) ->
+            scope = $rootScope.$new()
+            routeParams = {
+                pslug: "test"
+            }
+            ctrl = $controller("ProjectAdminRolesController", {
+                $scope: scope,
+                $routeParams: routeParams,
+            })
+            httpBackend = $httpBackend
+            httpBackend.whenGET(APIURL+"/sites").respond(200, {test: "test"})
+            httpBackend.whenGET("#{APIURL}/resolver?project=test").respond(200, {project: 1})
+            httpBackend.whenGET("#{APIURL}/projects/1?").respond(200, FIXTURES.project)
+            httpBackend.whenGET("#{APIURL}/users?project=1").respond(200, FIXTURES.users)
+            httpBackend.whenGET("#{APIURL}/permissions").respond(200, FIXTURES.permissions)
+            httpBackend.whenGET("#{APIURL}/roles?project=1").respond(200, FIXTURES.roles)
+            httpBackend.flush()
+        ))
+
+        afterEach ->
+            httpBackend.verifyNoOutstandingExpectation()
+            httpBackend.verifyNoOutstandingRequest()
+
+        it "should have section projects", ->
+            expect(ctrl.section).to.be.equal("admin")
+
+        it "should have a title", ->
+            expect(ctrl.getTitle()).to.be.equal("common.admin-panel")
+
+        it "should change the location", ->
+            sinon.spy(ctrl.location, "url")
+
+            ctrl.goTo("roles")
+
+            ctrl.location.url.should.have.been.calledOnce
+            ctrl.location.url.should.have.been.calledWith("/project/test/admin/roles")
+
+        it "should set the breadcrumb", ->
+            expect(ctrl.rootScope.pageBreadcrumb).to.be.lengthOf(2)
+
+        it "should be actived", ->
+            expect(ctrl.isActive("roles")).to.be.true


added comments

added comments

--- services/clsi/app/coffee/UrlFetcher.coffee
+++ services/clsi/app/coffee/UrlFetcher.coffee
@@ -20,8 +20,9 @@

 		logger.log url:url, filePath: filePath, "started downloading url to cache"
 		urlStream = request.get({url: url, timeout: oneMinute})
-		urlStream.pause()
+		urlStream.pause() # stop data flowing until we are ready

+		# attach handlers before setting up pipes
 		urlStream.on "error", (error) ->
 			logger.error err: error, url:url, filePath: filePath, "error downloading url"
 			callbackOnce(error or new Error("Something went wrong downloading the URL #{url}"))
@@ -33,6 +34,7 @@
 			if res.statusCode >= 200 and res.statusCode < 300
 				fileStream = fs.createWriteStream(filePath)

+				# attach handlers before setting up pipes
 				fileStream.on 'error', (error) ->
 					logger.error err: error, url:url, filePath: filePath, "error writing file into cache"
 					fs.unlink filePath, (err) ->
@@ -48,7 +50,7 @@
 					logger.log url:url, filePath: filePath, "piping into filestream"

 				urlStream.pipe(fileStream)
-				urlStream.resume()
+				urlStream.resume() # now we are ready to handle the data
 			else
 				logger.error statusCode: res.statusCode, url:url, filePath: filePath, "unexpected status code downloading url to cache"
 				# https://nodejs.org/api/http.html#http_class_http_clientrequest


call afterScroll callback when scrolling immediately

--- shared/src/components/scroll-to-mixin.cjsx
+++ shared/src/components/scroll-to-mixin.cjsx
@@ -85,6 +85,7 @@

     if options.immediate is true
       win.scroll(0, endPos)
+      @_onAfterScroll(el, options)
       return

     startPos  = win.pageYOffset


Change default star size to 250px

Change default star size to 250px

--- src/modules/app/maker/makerController.coffee
+++ src/modules/app/maker/makerController.coffee
@@ -9,7 +9,7 @@
     stroke: '#e4a91a',
     skew: 0,
     randomness: 0,
-    size: 100
+    size: 250
   }

   $scope.updateBoxStyle = ->


Removed a console.log

Removed a console.log

--- src/spine/modules/dom_inflator.coffee
+++ src/spine/modules/dom_inflator.coffee
@@ -4,7 +4,6 @@
     ((@filters ||= {})['before_prepare'] ||= []).push 'inflateFromDOM'

   inflateFromDOM: ->
-    console.log 'inflateFromDOM!'
     return unless typeof @['deactivateAndKillOrphans'] == 'function'

     classNames = []


disable concurrent error above thresh -- it seems to make things worse

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -113,7 +113,7 @@
             warning  : 30          # display warning and stop using connection if run takes this many seconds or more
             error    : 10*60       # kill any query that takes this long (and corresponding connection)
             concurrent_warn  : 500  # if number of concurrent outstanding db queries exceeds this number, put a concurrent_warn message in the log.
-            concurrent_error : 1000  # if nonzero, and this many queries at once, any query instantly fails!
+            concurrent_error : 0  # if nonzero, and this many queries at once, any query instantly fails!
             mod_warn : 2           # display MOD_WARN warning in log if any query modifies at least this many docs
             cache_expiry  : 15000  # expire cached queries after this many milliseconds (default: 15s)
             cache_size    : 250    # cache this many queries; use @...query.run({cache:true}, cb) to cache result for a few seconds


Fix indentation

Fix indentation

--- review_front/app/components/permission-cell.coffee
+++ review_front/app/components/permission-cell.coffee
@@ -8,16 +8,17 @@
   permission: (() ->
     user = @get('user')
     project = @get('project')
-    permission = user.get('permissions').find((item) -> item.get('project.id') == project.get('id'))
+    permission = user.get('permission').find (item) ->
+      item.get('project.id') == project.get('id')
     permission ||= @createPermission(user, project)
   ).property('project', 'user')

   createPermission: (user, project) ->
     permission = @store.createRecord('permission', {
-        allowed: false
-        user: user
-        project: project
-      })
+      allowed: false
+      user: user
+      project: project
+    })

   actions:
     grantAccess: (permission)->


fixed typo

fixed typo

--- src/scripts/githooks/coreos_restart/service.coffee
+++ src/scripts/githooks/coreos_restart/service.coffee
@@ -3,7 +3,7 @@
 #=================================================================================
 # This file contains helper functions related to managing deployment configuration.
 {join} = require "path"
-{async, read, write} = require "fairmont"
+{async, read, write, merge} = require "fairmont"
 {render} = require "mustache"

 {pull_configuration, get_dirs, make_key, get_branch_name} = require "./helpers"


changed image preview to lowercase it so .PNG will preview

--- services/web/public/coffee/file-view/FileView.coffee
+++ services/web/public/coffee/file-view/FileView.coffee
@@ -9,7 +9,7 @@
 		id: "fileViewArea"

 		render: () ->
-			extension = @model.get("name").split(".").pop()
+			extension = @model.get("name").split(".").pop().toLowerCase()
 			image = (["jpg", "jpeg", "png", "gif", "eps", "pdf"].indexOf(extension) != -1)
 			html = Mustache.to_html(@template, {
 				name: @model.get("name")


Use single quotes.

Use single quotes.

--- src/spec/integration/integration-order.spec.coffee
+++ src/spec/integration/integration-order.spec.coffee
@@ -126,7 +126,7 @@
         expect(orderUpdated2.returnInfo[0].items[0].paymentState).toEqual orderNew2.returnInfo[0].items[0].paymentState
         done()

-  it "should sync delivery items", (done) ->
+  it 'should sync delivery items', (done) ->

     orderNew = JSON.parse(JSON.stringify(@order))

@@ -147,7 +147,7 @@

       done()

-  it "should sync parcel items of a delivery", (done) ->
+  it 'should sync parcel items of a delivery', (done) ->

     orderNew = JSON.parse(JSON.stringify(@order))


Fixing PlanningBoardSpec

Fixing PlanningBoardSpec

--- roadmapplanningboard/PlanningBoardSpec.coffee
+++ roadmapplanningboard/PlanningBoardSpec.coffee
@@ -87,32 +87,30 @@

   it 'should render with a backlog column', ->
-    @board.render(Ext.getBody())
-    backlogColumn = @board.getColumns()[0]
+    @board.render('testDiv')
+    @waitForComponentReady(@board).then =>
+      backlogColumn = @board.getColumns()[0]

-    expect(backlogColumn).toBeTruthy()
-    expect(backlogColumn.getColumnHeader().getHeaderValue()).toBe "Backlog"
+      expect(backlogColumn).toBeTruthy()
+      expect(backlogColumn.getColumnHeader().getHeaderValue()).toBe "Backlog"

   it 'should have three visible planning columns', ->
-    @board.render(Ext.getBody())
+    @board.render('testDiv')
+    @waitForComponentReady(@board).then =>

-    expect(@board.getColumns()[1].getColumnHeader().getHeaderValue()).toBe "Q1"
-    expect(@board.getColumns()[2].getColumnHeader().getHeaderValue()).toBe "Q2"
-    expect(@board.getColumns()[3].getColumnHeader().getHeaderValue()).toBe "Future Planning Period"
+      expect(@board.getColumns()[1].getColumnHeader().getHeaderValue()).toBe "Q1"
+      expect(@board.getColumns()[2].getColumnHeader().getHeaderValue()).toBe "Q2"
+      expect(@board.getColumns()[3].getColumnHeader().getHeaderValue()).toBe "Future Planning Period"

   it 'should have features in the appropriate columns', ->
-    @board.render(Ext.getBody())
+    @board.render('testDiv')
+    @waitForComponentReady(@board).then =>

-    deferred = Ext.create 'Deft.Deferred'
-    _this = this
-    @board.on 'load', ->
-      expect(_this.board.getColumns()[1].getCards().length).toBe 3
-      expect(_this.board.getColumns()[2].getCards().length).toBe 1
-      expect(_this.board.getColumns()[3].getCards().length).toBe 0
-      expect(_this.board.getColumns().length).toBe(4)
-      deferred.resolve()
+      expect(this.board.getColumns()[1].getCards().length).toBe 3
+      expect(this.board.getColumns()[2].getCards().length).toBe 1
+      expect(this.board.getColumns()[3].getCards().length).toBe 0
+      expect(this.board.getColumns().length).toBe(4)

-    deferred.promise
   # 3 + backlog

   it 'should be correctly configured with stores from deft', ->
@@ -120,22 +118,14 @@
     expect(@board.planStore).toBeTruthy()

   it 'should have appropriate plan capacity range', ->
-    @board.render(Ext.getBody())
-    deferred = Ext.create 'Deft.Deferred'
-    _this = this
-
-    @board.on 'load', ->
-      expect(_this.board.getColumns()[1].getPlanRecord().get('lowCapacity')).toBe 2
-      expect(_this.board.getColumns()[1].getPlanRecord().get('highCapacity')).toBe 8
-      expect(_this.board.getColumns()[2].getPlanRecord().get('lowCapacity')).toBe 3
-      expect(_this.board.getColumns()[2].getPlanRecord().get('highCapacity')).toBe 30
-      expect(_this.board.getColumns()[3].getPlanRecord().get('lowCapacity')).toBe 15
-      expect(_this.board.getColumns()[3].getPlanRecord().get('highCapacity')).toBe 25
-
-      deferred.resolve()
-
-    deferred.promise
-
+    @board.render('testDiv')
+    @waitForComponentReady(@board).then =>
+      expect(this.board.getColumns()[1].getPlanRecord().get('lowCapacity')).toBe 2
+      expect(this.board.getColumns()[1].getPlanRecord().get('highCapacity')).toBe 8
+      expect(this.board.getColumns()[2].getPlanRecord().get('lowCapacity')).toBe 3
+      expect(this.board.getColumns()[2].getPlanRecord().get('highCapacity')).toBe 30
+      expect(this.board.getColumns()[3].getPlanRecord().get('lowCapacity')).toBe 15
+      expect(this.board.getColumns()[3].getPlanRecord().get('highCapacity')).toBe 25

   # it 'should have a blank roadmap with 3 plans', ->
   #   newboard = Ext.create 'Rally.apps.roadmapplanningboard.PlanningBoard',
@@ -167,15 +157,15 @@
   #   testBoard.destroy()

   it 'should set isRightmostColumn flag in last column only', ->
-    @board.render(Ext.getBody())
+    @board.render('testDiv')
+    @waitForComponentReady(@board).then =>

-    expect(@board.getColumns()[0].isRightmostColumn).not.toBeTruthy()
-    expect(@board.getColumns()[1].isRightmostColumn).not.toBeTruthy()
-    expect(@board.getColumns()[2].isRightmostColumn).not.toBeTruthy()
-    expect(@board.getColumns()[3].isRightmostColumn).toBeTruthy()
+      expect(@board.getColumns()[0].isRightmostColumn).not.toBeTruthy()
+      expect(@board.getColumns()[1].isRightmostColumn).not.toBeTruthy()
+      expect(@board.getColumns()[2].isRightmostColumn).not.toBeTruthy()
+      expect(@board.getColumns()[3].isRightmostColumn).toBeTruthy()

   it 'attribute should be set to empty', ->
-    @board.render(Ext.getBody())
-    expect(@board.attribute == '').toBeTruthy()
-
-
+    @board.render('testDiv')
+    @waitForComponentReady(@board).then =>
+      expect(@board.attribute == '').toBeTruthy()


Refactor syntax at the AggregateEntity

Refactor syntax at the AggregateEntity

--- src/aggregate_entity.coffee
+++ src/aggregate_entity.coffee
@@ -9,7 +9,7 @@

 class AggregateEntity

-  _.extend @prototype, MixinSetGet::
+  _.extend @::, MixinSetGet::

   constructor: (name, @_props = {}) ->
     @_entityName        = name


Refactor TentStatus.Collection

Refactor TentStatus.Collection

--- lib/assets/javascripts/collection.js.coffee
+++ lib/assets/javascripts/collection.js.coffee
@@ -21,19 +21,6 @@
     @fetch(next_params, _.extend({ append: true }, options))

   fetch: (params = {}, options = {}) =>
-    complete = (res, xhr) =>
-      models = null
-      if xhr.status in [200...300]
-        models = @fetchSuccess(params, options, res, xhr)
-        options.success?(models, res, xhr, params, options)
-        @trigger('fetch:success', models, res, xhr, params, options)
-        # success
-      else
-        options.failure?(res, xhr, params, options)
-        @trigger('fetch:failure', res, xhr, params, options)
-      options.complete?(models, res, xhr, params, options)
-      @trigger('fetch:complete', models, res, xhr, params, options)
-
     params = _.extend {
       entity: TentStatus.config.current_user.entity
       types: [@constructor.model.post_type]
@@ -43,7 +30,21 @@
     params.types = [params.types] unless _.isArray(params.types)
     params.types = _.map params.types, (type) => (new TentClient.PostType type).toURIString()

-    TentStatus.tent_client.post.list(params: params, callback: complete)
+    TentStatus.tent_client.post.list(params: params, callback: ((res, xhr) => @fetchComplete(params, options, res, xhr)))
+
+  fetchComplete: (params, options, res, xhr) =>
+    console.log('fetch complete', res, xhr.status)
+    models = null
+    if xhr.status == 200
+      # success
+      models = @fetchSuccess(params, options, res, xhr)
+      options.success?(models, res, xhr, params, options)
+      @trigger('fetch:success', models, res, xhr, params, options)
+    else
+      options.failure?(res, xhr, params, options)
+      @trigger('fetch:failure', res, xhr, params, options)
+    options.complete?(models, res, xhr, params, options)
+    @trigger('fetch:complete', models, res, xhr, params, options)

   fetchSuccess: (params, options, res, xhr) =>
     @pagination = _.extend({


odroid-xu4.coffee: Updating links to getting started guide

Changelog-entry: Update links to getting started guide from resin.io to balena.io
Signed-off-by: Gareth Davies <2f9181a725196cd0a5e07c6a6e161d83fcc71157@balena.io>

--- odroid-xu4.coffee
+++ odroid-xu4.coffee
@@ -12,9 +12,9 @@

 	instructions: commonImg.instructions
 	gettingStartedLink:
-		windows: 'http://docs.resin.io/#/pages/installing/gettingStarted-ODROID-XU4.md#windows'
-		osx: 'http://docs.resin.io/#/pages/installing/gettingStarted-ODROID-XU4.md#on-mac-and-linux'
-		linux: 'http://docs.resin.io/#/pages/installing/gettingStarted-ODROID-XU4.md#on-mac-and-linux'
+		windows: 'https://www.balena.io/docs/learn/getting-started/odroid-xu4/nodejs/'
+		osx: 'https://www.balena.io/docs/learn/getting-started/odroid-xu4/nodejs/'
+		linux: 'https://www.balena.io/docs/learn/getting-started/odroid-xu4/nodejs/'
 	supportsBlink: true

 	options: [ networkOptions.group ]


Adds 3 seconds (block times) to expiration time #296

--- dl/src/chain/config.coffee
+++ dl/src/chain/config.coffee
@@ -1,3 +1,3 @@
 module.exports =
     address_prefix: "GPH"
-    expire_in_secs: 12
+    expire_in_secs: 15


Update TokenizerFactory.coffee

Update TokenizerFactory.coffee

Support typescript react files (tsx).

--- src/tokenizer/TokenizerFactory.coffee
+++ src/tokenizer/TokenizerFactory.coffee
@@ -7,7 +7,7 @@

   LANGUAGES:
     javascript: ['js', 'es', 'es6']
-    typescript: ['ts']
+    typescript: ['ts', 'tsx']
     jsx: ['jsx']
     haxe: ['hx', 'hxml']
     coffeescript: ['coffee']


Delete unused param

Delete unused param

--- src/app/undo-manager.coffee
+++ src/app/undo-manager.coffee
@@ -28,7 +28,7 @@
       @clear()
       throw e

-  transact: (fn) ->
+  transact: ->
     isNewTransaction = not @currentTransaction?
     @currentTransaction ?= []
     isNewTransaction


update to new metrics api

update to new metrics api

--- services/contacts/app/coffee/ContactManager.coffee
+++ services/contacts/app/coffee/ContactManager.coffee
@@ -31,14 +31,8 @@
 			return callback(error) if error?
 			callback null, user?.contacts

-metrics.timeAsyncMethod(
-	ContactManager, 'touchContact',
-	'ContactManager.touchContact',
-	logger
-)
-
-metrics.timeAsyncMethod(
-	ContactManager, 'getContacts',
-	'ContactManager.getContacts',
-	logger
-)
+[
+	'touchContact',
+	'getContacts',
+].map (method) ->
+	metrics.timeAsyncMethod(ContactManager, method, 'mongo.ContactManager', logger)


Fixes address issue for guest users

Fixes address issue for guest users

--- app/assets/javascripts/sprangular/controllers/checkout/addresses.coffee
+++ app/assets/javascripts/sprangular/controllers/checkout/addresses.coffee
@@ -7,8 +7,9 @@
   $scope.user = Account.user
   $scope.submitted = false

-  $scope.shippingAddresses = $scope.user.addresses.slice()
-  $scope.billingAddresses = $scope.user.addresses.slice()
+  unless Account.isGuest
+    $scope.shippingAddresses = $scope.user.addresses.slice()
+    $scope.billingAddresses = $scope.user.addresses.slice()

   $scope.$watch 'order.state', (state) ->
     $scope.done = _.contains(['confirm', 'payment', 'delivery'], state)


payment: use unminified accounting.js

payment: use unminified accounting.js

I had to hack accounting.js because of retarded AMD

--- client/includes.coffee
+++ client/includes.coffee
@@ -3,6 +3,7 @@
   # Libs
   "libs/async.js",
   "libs/md5-min.js",
+  "libs/accounting.js",

   # --- Application ---
   "app/MainApp/utils.coffee"


removed some debugging info

removed some debugging info

--- lib/omnisharp-atom/features/lib/completion-provider.coffee
+++ lib/omnisharp-atom/features/lib/completion-provider.coffee
@@ -28,15 +28,13 @@
                 renderLabelAsHtml:true,
                 label: renderLabel(item)
                 } for item in completions)
-            console.log completions
             resolve(suggestions)

     renderLabel = (item) ->
-      #we don't have icons for these types at the moment, skip.
       if item.Kind is 'NamedType' or item.Kind is 'Parameter' then return
-      #we're returning the high DPI ones for now and scaling down as they look the best.
+
       return '<img height="16px" width="16px" src="atom://omnisharp-atom/styles/icons/autocomplete_' + item.Kind.toLowerCase()  + '@3x.png" /> '
-    #currently unused, item.DisplayText has angle brackets for generics which break when we're in HTML mode
+
     htmlEscape = (str) ->
       return String(str)
             .replace(/&/g, '&')


Removed event.stopPropagation from form event handlers.

--- client/settings.coffee
+++ client/settings.coffee
@@ -27,9 +27,7 @@
 # Username settings
 Template.settingsUsername.events =
   'submit form#set-username': (event, template) ->
-    event.stopPropagation()
     event.preventDefault()
-    usernameReadyForValidation = true
     usernameFormMessages.resetMessages()
     username = $('#username').val()
     try
@@ -44,7 +42,6 @@
     return # Make sure CoffeeScript does not return anything

   'blur input#username': (event, template) ->
-    event.stopPropagation()
     usernameReadyForValidation = usernameFieldModified
     username = $('#username').val()
     validateUsername username, 'username'
@@ -64,7 +61,7 @@
     return # Make sure CoffeeScript does not return anything

 Template.settings.usernameExists = ->
-  !Meteor.person?().user?.username
+  !!Meteor.person?().user?.username

 Template.settingsUsername.messageOnField = (field, options) ->
   field = null unless options


Removed extra ValidationError argument.

Removed extra ValidationError argument.

--- client/settings.coffee
+++ client/settings.coffee
@@ -165,7 +165,7 @@

   try
     # We check this manually because changePassword error throws global 'Match failed' error if current password is empty string
-    throw new ValidationError 400, "Incorrect password.", 'current-password' unless currentPassword
+    throw new ValidationError "Incorrect password.", 'current-password' unless currentPassword
     Accounts.changePassword currentPassword, newPassword, (error) ->
       formError = new ValidationError (error.reason or error.toString() or "Unknown error"), 'current-password' if error
       callback formError


groups: merge gone wrong, re-added addCustomRole

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -49,7 +49,7 @@
         'updatePermissions', 'fetchMembers', 'fetchRoles', 'fetchMyRoles'
         'fetchUserRoles','changeMemberRoles','canOpenGroup', 'canEditGroup'
         'fetchMembershipPolicy','modifyMembershipPolicy','requestInvitation'
-        'fetchReadme', 'setReadme'
+        'fetchReadme', 'setReadme', 'addCustomRole'
       ]
     schema          :
       title         :
@@ -324,7 +324,19 @@
   createRole: permit 'grant permissions'
     success:(client, formData, callback)->
       JGroupRole = require './role'
-      JGroupRole.create {title : formData.title}, callback
+      JGroupRole.create
+        title           : formData.title
+        isConfigureable : formData.isConfigureable or no
+      , callback
+
+  addCustomRole: permit 'grant permissions'
+    success:(client,formData,callback)->
+      @createRole client,formData, (err,role)=>
+        console.log err,role
+        unless err
+          @addRole role, callback
+        else
+          callback err, null

   modify: permit
     advanced : [


Update open-weather-map.coffee

Update open-weather-map.coffee

--- src/open-weather-map.coffee
+++ src/open-weather-map.coffee
@@ -7,6 +7,7 @@
 # Configuration:
 #   HUBOT_OPEN_WEATHER_MAP_APIKEY = required API key from http://openweathermap.org/faq#error401
 #   HUBOT_OPEN_WEATHER_MAP_URL (optional)
+#   HUBOT_OPEN_WEATHER_MAP_FIND_COORDINATES (optional)
 #   HUBOT_OPEN_WEATHER_MAP_FORECAST_URL (optional)
 #   HUBOT_OPEN_WEATHER_MAP_DEFAULT_CITIES (optional)
 #   HUBOT_OPEN_WEATHER_MAP_UNITS (optional) imperial, metric
@@ -26,7 +27,6 @@
 weatherURL = process.env.HUBOT_OPEN_WEATHER_MAP_URL or 'http://api.openweathermap.org/data/2.5/weather?q='
 findWeatherCoordinatesURL = process.env.HUBOT_OPEN_WEATHER_MAP_FIND_COORDINATES or 'http://api.openweathermap.org/data/2.5/find?q='
 forecastURL = process.env.HUBOT_OPEN_WEATHER_MAP_FORECAST_URL or 'http://api.openweathermap.org/data/2.5/onecall?'
-coordinates = process.env.HUBOT_OPEN_WEATHER_MAP_DEFAULT_COORDINATES
 cities     = process.env.HUBOT_OPEN_WEATHER_MAP_DEFAULT_CITIES
 apiKey     = process.env.HUBOT_OPEN_WEATHER_MAP_APIKEY
 units      = process.env.HUBOT_OPEN_WEATHER_MAP_UNITS or 'imperial'


@dmosher -> @davemo

@dmosher -> @davemo

--- tasks/server.coffee
+++ tasks/server.coffee
@@ -2,7 +2,7 @@
 Task: server
 Description: server static files and proxy API (server-side) requests from another port
 Dependencies: grunt
-Contributor: @dmosher, @searls
+Contributor: @davemo, @searls

 Configuration:
 "base" - the path from which to serve static assets from (this should almost always be left to the default value of "generated")


add update files to virus total reports

add update files to virus total reports

--- tasks/upload.coffee
+++ tasks/upload.coffee
@@ -70,7 +70,7 @@
           if _.includes(item, 'Windows_Setup')
             body += link({name: 'Windows Setup', link: GLOBAL.vtReports[item]})

-          if _.includes(item, '.Windows.')
+          if _.includes(item, '.WIN.')
             body += link({name: 'Windows ZIP', link: GLOBAL.vtReports[item]})

           if _.includes(item, 'OSX')
@@ -78,6 +78,12 @@

           if _.includes(item, 'asar')
             body += link({name: 'update.asar', link: GLOBAL.vtReports[item]})
+
+          if _.includes(item, 'u_osx')
+            body += link({name: 'u_osx.tar.gz', link: GLOBAL.vtReports[item]})
+
+          if _.includes(item, 'u_win')
+            body += link({name: 'u_win.tar.gz', link: GLOBAL.vtReports[item]})

         body += [


removing log statement

removing log statement

--- jsapp/xlform_model_view/view.row.coffee
+++ jsapp/xlform_model_view/view.row.coffee
@@ -110,8 +110,6 @@
       for [key, val] in @model.attributesArray()
         if key in ["name", "label", "_isRepeat", "appearance"]
           new $viewRowDetail.DetailView(model: val, rowView: @).renderInRowView(@)
-        else
-          log "don't create rowDetailView", key
       @

   class RowView extends BaseRowView


docs(doc): Removed unused functions on ExampleController

--- docs/doc.coffee
+++ docs/doc.coffee
@@ -279,24 +279,6 @@
           "Original air date": "May 7, 1986"
         }
       ]
-      $scope.selectedModels = []
-      $scope.unselectAll    = ->
-        $scope.selectedModels = []
-        return
-
-      $scope.selectAll = ->
-        $scope.selectedModels = $scope.macGyverSeasonOne.slice(0)
-        return
-
-      $scope.selectRandom = ->
-        length = $scope.macGyverSeasonOne.length
-        models = []
-        for i in [1..(length / 2)]
-          index = Math.floor Math.random() * length
-          model = $scope.macGyverSeasonOne[index]
-          models.push model unless model in models
-
-        $scope.selectedModels = models
   ]

 window.prettyPrint and prettyPrint()


Don't end drag session if mousedown

Don't end drag session if mousedown

--- framer/Extras/TouchEmulator.coffee
+++ framer/Extras/TouchEmulator.coffee
@@ -166,6 +166,8 @@
 		@endMultiTouch()

 	mouseout: (event) =>
+
+		return if @isMouseDown

 		fromElement = event.relatedTarget or event.toElement


removed '/' from api route

removed '/' from api route

--- routes/api/v2/apps.coffee
+++ routes/api/v2/apps.coffee
@@ -86,7 +86,7 @@
       if app then lib.foundJson @response, 200, formatApp app
       else lib.notFound @response

-    @route '/api/v2/apps',
+    @route 'api/v2/apps',
       where: 'server'
       path: '/api/v2/apps'
     .get ->


Fix syntax error in adopt script

Fix syntax error in adopt script

--- scripts/adopt.coffee
+++ scripts/adopt.coffee
@@ -22,7 +22,10 @@
       return callback "#{util.display(greyhound)} has already been adopted 😝"

     info.category = 'adopted'
-    info.doa = if doa? new Date(doa) else util.nowDate()
+    info.doa = if doa?
+      new Date(doa)
+    else
+      util.nowDate()
     site.dumpGreyhound greyhound, info, bio, callback

 module.exports = (robot) ->


opt: benchmark

opt: benchmark

--- benchmark/template.coffee
+++ benchmark/template.coffee
@@ -9,36 +9,30 @@

 module.exports = (name, Promise) ->

-	resolveCount = 0
+	countDown = 10 ** 5
 	start = Date.now()
-	taskSpan = 1

 	checkEnd = ->
-		if Date.now() - start >= 1000 * 3
-			mem = process.memoryUsage()
-			for k of mem
-				mem[k] = Math.floor(mem[k] / 1024 / 1024) + 'mb'
+		return if --countDown

-			console.log """
-			#{cs.cyan kit._.padRight(name, 15)}
-			    resolve count: #{cs.green resolveCount}
-			     memory usage: #{JSON.stringify mem}
-			"""
+		time = Date.now() - start

-			process.exit()
+		mem = process.memoryUsage()
+		for k of mem
+			mem[k] = Math.floor(mem[k] / 1024 / 1024) + 'mb'
+
+		console.log """
+		#{cs.cyan kit._.padRight(name, 15)}
+		    	     time: #{cs.green time}
+		     memory usage: #{JSON.stringify mem}
+		"""

 	asyncTask = ->
 		new Promise (resolve) ->
-			setTimeout ->
-				resolve()
-			, taskSpan
+			resolve()
 		.then ->
-			resolveCount++
+			checkEnd()

-			asyncTask()
-			asyncTask()
-
-			checkEnd()
-			return
-
-	asyncTask()
+	i = countDown
+	while i--
+		asyncTask()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/array_extensions.coffee
+++ test/array_extensions.coffee
@@ -1,7 +1,7 @@
 module "Array"

 test "#average", ->
-  [1, 3, 5, 7].average()
+  e[1, 3, 5, 7].average()

 test "#compact", ->
   a = [0, 1, undefined, 2, null, 3, '', 4]


Every message in all topics will be published to public

--- client/landing/app/Applications/Chat.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Chat.kdapplication/AppController.coffee
@@ -5,6 +5,7 @@
 ###
 class Chat12345 extends AppController
   {mq} = bongo
+  PUBLIC = 'public'

   constructor:(options = {}, data)->
     options.view = new ChatView
@@ -23,7 +24,7 @@
     super name : 'Chat'#, type : 'background'

   loadView:(mainView)->
-    @joinChannel 'public'
+    @joinChannel PUBLIC

   joinChannel: (name) ->
     return @channels[name] if @channels[name]
@@ -46,6 +47,8 @@
     channel.view.on "ChatMessageSent", (messageBody) =>
       @parseMessageForChannels messageBody
       @broadcastOwnMessage channel, messageBody
+      if name isnt PUBLIC
+        @broadcastOwnMessage @channels[PUBLIC], messageBody

     @broadcaster.on channelName, (msg) ->
       channel.messageReceived msg


delete example script

delete example script

--- test/hello-world_test.coffee
+++ test/hello-world_test.coffee
@@ -1,19 +0,0 @@
-chai = require 'chai'
-sinon = require 'sinon'
-chai.use require 'sinon-chai'
-
-expect = chai.expect
-
-describe 'hello-world', ->
-  beforeEach ->
-    @robot =
-      respond: sinon.spy()
-      hear: sinon.spy()
-
-    require('../src/hello-world')(@robot)
-
-  it 'registers a respond listener', ->
-    expect(@robot.respond).to.have.been.calledWith(/hello/)
-
-  it 'registers a hear listener', ->
-    expect(@robot.hear).to.have.been.calledWith(/orly/)


complete 100% coverage again

complete 100% coverage again

--- test/phabs_feeds_test.coffee
+++ test/phabs_feeds_test.coffee
@@ -370,7 +370,7 @@
                 'attachments': {
                   'projects': {
                     'projectPHIDs': [
-                      'PHID-PROJ-123456ss7'
+                      'PHID-PROJ-1234567'
                     ]
                   }
                 }


lines: improve tests

lines: improve tests

--- test/properties.lines.coffee
+++ test/properties.lines.coffee
@@ -47,7 +47,7 @@
     "",1974,8.8392926E7,"","
     "",1974,8.8392926E7,"",""
     """, (err, data) ->
-      err.message.should.match /Quoted field not terminated at line 4/
+      err.message.should.eql "Quoted field not terminated at line 6"
       (data == undefined).should.be.true
       next()

@@ -59,7 +59,7 @@
       "  1974    8.8392926E7 ""t "
       "  1974    8.8392926E7 "t ""
     """, quote: '"', escape: '"', delimiter: "\t", (err, data) ->
-      err.message.should.match /Invalid closing quote at line 3/
+      err.message.should.eql 'Invalid closing quote at line 3; found " " instead of delimiter "\\t"'
       (data == undefined).should.be.true
       next()


ipython: experimenting with different reconnect times

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -4545,7 +4545,7 @@
                             # stuff in SMC, not sync!
                             websocket_reconnect = () =>
                                 @nb?.kernel?.start_channels()
-                            @_reconnect_interval = setInterval(websocket_reconnect, 5000)
+                            @_reconnect_interval = setInterval(websocket_reconnect, 60000)

                             @status()
                             cb()


remove flips

remove flips

--- site/examples.coffee
+++ site/examples.coffee
@@ -19,4 +19,4 @@

     $(this).grafxShake();

-    $(this).grafxFlipIn().delay(500).grafxFlipOut();+    # $(this).grafxFlipIn().delay(500).grafxFlipOut();


Allow single answer matching

Allow single answer matching

--- commands/yomama.coffee
+++ commands/yomama.coffee
@@ -622,7 +622,7 @@
 yoMama = (theme, respond) ->
     if theme?
         jk = jokes.filter((j) -> j.indexOf(theme) >= 0)
-        respond brain.anyOf (if jk.length > 1 then jk else jokes)
+        respond brain.anyOf (if jk.length > 0 then jk else jokes)
     else
         respond brain.anyOf jokes


Add spec for when singleTrailingNewline is false

--- src/packages/whitespace/spec/whitespace-spec.coffee
+++ src/packages/whitespace/spec/whitespace-spec.coffee
@@ -41,8 +41,7 @@
     [originalConfigValue] = []
     beforeEach ->
       originalConfigValue = config.get("whitespace.singleTrailingNewline")
-      config.set("whitespace.singleTrailingNewline", true)
-      config.update()
+      expect(originalConfigValue).toBe true

     afterEach ->
       config.set("whitespace.singleTrailingNewline", originalConfigValue)
@@ -72,3 +71,12 @@
       editor.insertText "\n"
       editor.getBuffer().save()
       expect(editor.getText()).toBe "\n"
+
+    it "does not add trailing newline if singleTrailingNewline is false", ->
+      config.set("whitespace.singleTrailingNewline", false)
+      config.update()
+
+      editor.insertText "no trailing newline"
+      editor.getBuffer().save()
+      expect(editor.getText()).toBe "no trailing newline"
+


checkpoint for broken code debugging

checkpoint for broken code debugging

--- src/smc-webapp/profile.cjsx
+++ src/smc-webapp/profile.cjsx
@@ -180,7 +180,7 @@

                 if p.project_id == @props.project_id
                     for user in p.users
-                        console.log('user', JSON.stringify(p), user)
+                        console.log('user', JSON.stringify(p), JSON.stringify(@props.user_map.get(user.account_id)?.toJS()), user)
                         [event, most_recent] = @_find_most_recent(user)
                         if user.account_id in users
                             if most_recent > users[user.account_id]['most_recent']


Tokenize empty lines with a changed indent level synchronously

--- src/tokenized-buffer.coffee
+++ src/tokenized-buffer.coffee
@@ -157,32 +157,29 @@
     end = oldRange.end.row
     delta = newRange.end.row - oldRange.end.row

-    @invalidateSurroundingChangedWhitespaceRows(start, end)
     @updateInvalidRows(start, end, delta)
     previousEndStack = @stackForRow(end) # used in spill detection below
     newTokenizedLines = @buildTokenizedLinesForRows(start, end + delta, @stackForRow(start - 1))
     _.spliceWithArray(@tokenizedLines, start, end - start + 1, newTokenizedLines)
+
+    start = @retokenizeWhitespaceRowsIfIndentLevelChanged(start - 1, -1)
+    end = @retokenizeWhitespaceRowsIfIndentLevelChanged(newRange.end.row + 1, 1) - delta
+
     newEndStack = @stackForRow(end + delta)
-
     if newEndStack and not _.isEqual(newEndStack, previousEndStack)
       @invalidateRow(end + delta + 1)

     @emit "changed", { start, end, delta, bufferChange: e }

-  invalidateSurroundingChangedWhitespaceRows: (startRow, endRow) ->
-    # Indent levels might change when something else in the buffer changes.
-    # If they do, invalidate those rows.
-    @invalidateChangedWhitespaceRows(startRow - 1, -1)
-    @invalidateChangedWhitespaceRows(endRow + 1, 1)
-
-  invalidateChangedWhitespaceRows: (row, increment) ->
+  retokenizeWhitespaceRowsIfIndentLevelChanged: (row, increment) ->
     line = @tokenizedLines[row]
-    if line?.isOnlyWhitespace() and @indentLevelForRow(row) != line.indentLevel
+    if line?.isOnlyWhitespace() and @indentLevelForRow(row) isnt line.indentLevel
       while line?.isOnlyWhitespace()
-        @invalidateRow(row)
+        @tokenizedLines[row] = @buildTokenizedTokenizedLineForRow(row, @stackForRow(row - 1))
         row += increment
         line = @tokenizedLines[row]
-    return
+
+    row - increment

   buildTokenizedLinesForRows: (startRow, endRow, startingStack) ->
     ruleStack = startingStack


Updated transactions unit testing script to allow for filter testing

--- test/integration/transactionsAPITests.coffee
+++ test/integration/transactionsAPITests.coffee
@@ -140,7 +140,6 @@

 			startDate = "2014-06-09T00:00:00.000Z"
 			endDate = "2014-06-10T00:00:00.000Z"
-			console.log("/transactions?status=Processing&filterPage=0&filterLimit=10&startDate="+startDate+"&endDate="+endDate)
 			it "should call getTransactions with filter paramaters ", (done) ->
 				Transaction.count {}, (err, countBefore) ->
 					tx = new Transaction transactionData


whoops accidentally stopped deleting added files!

--- test/support/add-file.coffee
+++ test/support/add-file.coffee
@@ -6,4 +6,4 @@
   p = path.resolve(process.cwd(), subPath)
   fs.writeFileSync(p, contents)
   after ->
-    # fs.unlinkSync(p)
+    fs.unlinkSync(p)


Add trim_trailing_whitespace fixtures to fixtures test helper

--- test/support/fixtures.coffee
+++ test/support/fixtures.coffee
@@ -14,3 +14,6 @@
 	'end_of_line/lf.txt': readFixture 'end_of_line/lf.txt'
 	'end_of_line/crlf.txt': readFixture 'end_of_line/crlf.txt'
 	'end_of_line/none.txt': readFixture 'end_of_line/none.txt'
+	'trim_trailing_whitespace/space.txt': readFixture 'trim_trailing_whitespace/space.txt'
+	'trim_trailing_whitespace/tab.txt': readFixture 'trim_trailing_whitespace/tab.txt'
+	'trim_trailing_whitespace/none.txt': readFixture 'trim_trailing_whitespace/none.txt'


mousing over participating users doesn't trigger reasons overlay anymore

Former-commit-id: 45fb715a3ce7d46486623c48cd8ed324d3752f6a [formerly 95f5ede9efd6dfab7df96f4254a56b387f94055b] [formerly 5d33391aa1d0817019fe4d9514b215615f5dafc8 [formerly 656c1f452440a3ca8ea2822d94125d200c339eb0]] [formerly 05e0bed1707721bd403ace076ea8b54ef80ed401 [formerly cb0272ec757decb491735c2c4ad662686da295a7] [formerly c0122fc1f96e8fe09ce540e6f80028ca75918a80 [formerly c0122fc1f96e8fe09ce540e6f80028ca75918a80 [formerly d232f37b7ba7ad11981d0d0cfdd4d3b12ade16b7]]]]
Former-commit-id: 4ea4dabe95fa44075a4c1e065f35f31145a675ae [formerly e9859ccea213878d41dd34047f2e7263f29918eb] [formerly 235596765db37139c85340b8f145530d9635b4d6 [formerly 4e9a2f7cb340f709cf39d43f62d86957e10a8717]]
Former-commit-id: 52d9478142556bb4bdbb6e19eacfce746af7ccb7 [formerly 7a61c2f540c0449ac0f02beb4a90ea7a1749dd81]
Former-commit-id: c13a581d1ef8b02f1041c4bb15880e7949dea21b
Former-commit-id: eab4ebf0225855e1d6ff2408c68a844abb417861

--- @client/modules/franklin/proposal/reasons_view.coffee
+++ @client/modules/franklin/proposal/reasons_view.coffee
@@ -81,14 +81,14 @@

     events :
       'click .points_list_region' : 'reasonsClicked'
-      'click .participating_users_region' : 'reasonsClicked'
+      # 'click .participating_users_region' : 'reasonsClicked'
       'click .reasons_footer_region' : 'reasonsClicked'
       'mouseenter .points_list_region' : 'showViewResults'
       'mouseleave .points_list_region' : 'hideViewResults'
       'mouseenter .reasons_footer_region' : 'showViewResults'
       'mouseleave .reasons_footer_region' : 'hideViewResults'
-      'mouseenter .participating_users_region' : 'showViewResults'
-      'mouseleave .participating_users_region' : 'hideViewResults'
+      # 'mouseenter .participating_users_region' : 'showViewResults'
+      # 'mouseleave .participating_users_region' : 'hideViewResults'
       'mouseenter .community_point' : 'logPointView'


Don't change selection when inserting replacement text

The browser is typically inserting over where you previously typed, not where you're currently typing.

--- src/trix/controllers/level_2_input_controller.coffee
+++ src/trix/controllers/level_2_input_controller.coffee
@@ -250,7 +250,7 @@
     insertReplacementText: ->
       @delegate?.inputControllerWillPerformTyping()
       @withTargetDOMRange ->
-        @responder?.insertString(@event.dataTransfer.getData("text/plain"))
+        @responder?.insertString(@event.dataTransfer.getData("text/plain"), updatePosition: false)

     insertText: ->
       @delegate?.inputControllerWillPerformTyping()


make clean_crontabs handle asynchronicity

--- bin/clean_crontabs.coffee
+++ bin/clean_crontabs.coffee
@@ -1,18 +1,25 @@
 #!/usr/bin/env coffee

+process.env.NODE_ENV = 'cron'
+
 mongoose = require 'mongoose'
-_ = require 'underscore'
+async = require 'async'
 mongoose.connect process.env.CU_DB

 {Dataset} = require 'model/dataset'

+CLI = false
+
 main = (TheDataset) ->
   TheDataset.findToBeDeleted (err, datasets)->
-    _.each datasets, (dataset) ->
-      dataset.cleanCrontab()
+    async.each datasets, (dataset, cb) ->
+      dataset.cleanCrontab cb
+    , (err) ->
+      console.warn(err) if err?
+      process.exit() if CLI?

 if require.main == module
+  CLI = true
   main(Dataset)
-  process.exit()

 exports.main = main


add better tree view toggle

add better tree view toggle

--- home/.atom/keymap.cson
+++ home/.atom/keymap.cson
@@ -31,15 +31,19 @@
 #
 # '.vim-mode.insert-mode':

-# Character Navigation
 'atom-text-editor.vim-mode:not(.insert-mode)':
+  # Character Navigation
   'c':   'core:move-up'
   't':   'core:move-down'
   'h':   'core:move-left'
   'n':   'core:move-right'

-'atom-text-editor.vim-mode.normal-mode':
-  ';': 'ex-mode:open' # shortcut for : (so you don't have to type shift)
+  # Tree View Toggle
+  '\\':   'tree-view:toggle-focus'
+
+  # shortcut for : (so you don't have to type shift)
+  ';': 'ex-mode:open'
+

 ###############################################
 # To not forget the joy of NERDTree's shortcuts
@@ -66,6 +70,7 @@
   't':   'core:move-down'
   'h':   'tree-view:collapse-directory'
   'n':   'tree-view:open-selected-entry'
+  '\\':  'tree-view:toggle-focus'

 'atom-workspace atom-text-editor':


PluginSetting change to CollapsiblePanel

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -7,7 +7,7 @@
 npm = require 'npm'
 semver = require 'semver'
 {$, $$, _, React, ReactBootstrap, FontAwesome, ROOT, PluginManager} = window
-{Grid, Col, Row, Input, Alert, Button, ButtonGroup, Label, Collapse, Well, OverlayTrigger, Tooltip, Overlay, Popover} = ReactBootstrap
+{Grid, Col, Row, Input, Alert, Button, ButtonGroup, Label, Collapse, Well, OverlayTrigger, Tooltip, Panel} = ReactBootstrap
 {config} = window
 shell = require 'shell'
 {dialog} = remote.require 'electron'
@@ -20,7 +20,33 @@
   render: ->
     React.createElement @props.plugin.settingsClass

+CollapsiblePanel = React.createClass
+  getInitialState: ->
+    expanded: @props.expanded
+    hide: !@props.expanded
+
+  componentWillReceiveProps: (nextProps) ->
+    transitionTime = @props.transitionTime || 400
+    if @props.expanded && !nextProps.expanded
+      @setState {expanded: false}
+      _.delay (=> @setState {hide: true}), transitionTime
+    else if !@props.expanded && nextProps.expanded
+      @setState {hide: false}
+      _.defer (=> @setState {expanded: true})
+
+  render: ->
+    style = Object.assign {}, @props.style,
+      if @state.hide then {display: 'none'} else {}
+    <Panel {...@props} style={style} />
+
 InstalledPlugin = React.createClass
+  getInitialState: ->
+    settingOpen: false
+
+  toggleSettingPop: ->
+    @setState
+      settingOpen: !@state.settingOpen
+
   render: ->
     plugin = @props.plugin
     index = @props.index
@@ -68,7 +94,7 @@
                 if plugin.settingsClass?
                   <Button ref="setting-btn"
                           bsStyle='primary' bsSize='xs' style={width: 'calc(100% / 3)'}
-                          onClick={_.partial @props.toggleSettingPop, plugin.name}>
+                          onClick={@toggleSettingPop}>
                     <FontAwesome name='gear' />
                     {__ 'Settings'}
                   </Button>
@@ -119,15 +145,10 @@
             </ButtonGroup>
             {
               if plugin.settingsClass?
-                <Overlay show={@props.settingPopOpen}
-                         onHide={_.partial @props.toggleSettingPop, plugin.name, false}
-                         rootClose={true}
-                         target={=> ReactDOM.findDOMNode @refs['setting-btn']}
-                         placement='top'>
-                  <Popover id="#{plugin.name}-setting-pop">
-                    <PluginSettingWrap plugin={plugin} />
-                  </Popover>
-                </Overlay>
+                <CollapsiblePanel collapsible expanded={@state.settingOpen}
+                  style={margin: 0} >
+                  <PluginSettingWrap plugin={plugin} />
+                </CollapsiblePanel>
             }
           </div>
         </Col>
@@ -190,7 +211,6 @@
     mirrors: {}
     plugins: []
     uninstalledPluginSettings: []
-    settingPopOpen: {}
     updatingAll: false
     reloading: false
     advanced: false
@@ -237,16 +257,6 @@
     config = yield PluginManager.selectConfig(state ,null, null)
     @setState
       config: config
-  toggleSettingPop: (name, state) ->
-    settingPopOpen = @state.settingPopOpen
-    if state?
-      settingPopOpen[name] = state
-    else
-      if settingPopOpen[name]?
-        settingPopOpen[name] = !settingPopOpen[name]
-      else
-        settingPopOpen[name] = true
-    @setState {settingPopOpen}
   handleAdvancedShow: ->
     advanced = !@state.advanced
     @setState {advanced}
@@ -559,10 +569,8 @@
             plugin={plugin}
             handleClickAuthorLink={@handleClickAuthorLink}
             handleUpdate={@handleUpdate}
-            toggleSettingPop={@toggleSettingPop}
             handleEnable={@handleEnable}
             handleRemove={@handleRemove}
-            settingPopOpen={@state.settingPopOpen[plugin.name]}
             />
       }
       {


Fix test for IE8; childNodes is not an array

--- tests/batman/view/loop_rendering_test.coffee
+++ tests/batman/view/loop_rendering_test.coffee
@@ -154,7 +154,7 @@
   )

   helpers.render source, false, {playerScores}, (node, view) ->
-    for childNode in Array::slice.call(node.childNodes)
+    for childNode in node.childNodes
       equal childNode.className,  'present'
       equal parseInt(childNode.innerHTML, 10), playerScores.get(childNode.id)
     QUnit.start()
@@ -188,7 +188,7 @@
     crono: 10

   helpers.render source, false, {playerScores}, (node, view) ->
-    for childNode in Array::slice.call(node.childNodes)
+    for childNode in node.childNodes
       equal childNode.className,  'present'
       equal parseInt(childNode.innerHTML, 10), playerScores[childNode.id]
     QUnit.start()
@@ -221,7 +221,7 @@
         deepEqual names, ['a', 'b', 'c', 'd', 'e']

 getVals = (node) ->
-  Array::slice.call(node.childNodes).map (n) -> parseInt(n.innerHTML)
+  parseInt(child.innerHTML, 10) for child in node.childNodes

 asyncTest 'it should stop previous ongoing renders if items are removed', ->
   getSet = (seed) -> new Batman.Set(seed, seed+1, seed+2)


Support LV and child IREP dump

Support LV and child IREP dump

--- src/editor/mrbviewer.coffee
+++ src/editor/mrbviewer.coffee
@@ -127,41 +127,65 @@
     switch r.section_identify
       when "IREP"
         r.rite_version = String.fromCharCode.apply(null, h.subarray(0, 4))
-        r.record_size = _bin_to_u32_be(h.subarray(4, 8))
-        r.number_of_locals = _bin_to_u16_be(h.subarray(8, 10))
-        r.number_of_regs = _bin_to_u16_be(h.subarray(10, 12))
-        r.number_of_child_ireps = _bin_to_u16_be(h.subarray(12, 14))
-        n = _bin_to_u32_be(h.subarray(14, 18))
-        pad = (4 - (offset + 18) % 4) % 4
-        r.iseq = {number_of_opcodes: n, padding_bytes: pad, opcodes: []}
-        i = 18 + pad
-        while n > 0
-          c = _bin_to_u32_be(h.subarray(i, i + 4))
-          [c1,c2] = @_decodeRiteInst(c)
-          c1 = c1.replace("\t", " ") + "                                        "
-          r.iseq.opcodes.push("#{c1.substring(0, 20)}# #{c2}")
+        @_loadIrepRecord(r, h.subarray(4), offset + 4)
+      when "LVAR"
+        r.syms_len = _bin_to_u32_be(h.subarray(0, 4))
+        r.record = []
+        i = 4
+        n = 0
+        while n < r.syms_len
+          l = _bin_to_u16_be(h.subarray(i, i + 2))
+          i += 2
+          r.record.push(
+            {symbol: String.fromCharCode.apply(null, h.subarray(i, i + l))}
+          )
+          i += l
+          n += 1
+        while i < r.section_size
+          n = _bin_to_u16_be(h.subarray(i, i + 2))
+          r.record[n]?.lv = _bin_to_u16_be(h.subarray(i + 2, i + 4)) if n < 0xffff
           i += 4
-          n -= 1
-        n = _bin_to_u32_be(h.subarray(i, i + 4))
-        i += 4
-        r.pool = {length_of_pool: n, values: []}
-        while n > 0
-          n -= 1
-        n = _bin_to_u32_be(h.subarray(i, i + 4))
-        i += 4
-        r.syms = {number_of_syms: n, symbols: []}
-        while n > 0
-          len = _bin_to_u16_be(h.subarray(i, i + 2))
-          i += 2
-          if len == 0xffff
-            r.syms.symbols.push(null)
-          else
-            r.syms.symbols.push(String.fromCharCode.apply(null, h.subarray(i, i + len)))
-            i += (len + 1)
-          n -= 1
-        null
     offset += r.section_size
-    [r, offset]
+    return [r, offset]
+
+  _loadIrepRecord: (r, h, offset) ->
+    r.record_size = _bin_to_u32_be(h.subarray(0, 4))
+    r.number_of_locals = _bin_to_u16_be(h.subarray(4, 6))
+    r.number_of_regs = _bin_to_u16_be(h.subarray(6, 8))
+    r.number_of_child_ireps = _bin_to_u16_be(h.subarray(8, 10))
+    n = _bin_to_u32_be(h.subarray(10, 14))
+    pad = (4 - (offset + 14) % 4) % 4
+    r.iseq = {number_of_opcodes: n, padding_bytes: pad, opcodes: []}
+    i = 14 + pad
+    while n > 0
+      c = _bin_to_u32_be(h.subarray(i, i + 4))
+      [c1,c2] = @_decodeRiteInst(c)
+      c1 = c1.replace("\t", " ") + "                                        "
+      r.iseq.opcodes.push("#{c1.substring(0, 20)}# #{c2}")
+      i += 4
+      n -= 1
+    n = _bin_to_u32_be(h.subarray(i, i + 4))
+    i += 4
+    r.pool = {length_of_pool: n, values: []}
+    while n > 0
+      n -= 1
+    n = _bin_to_u32_be(h.subarray(i, i + 4))
+    i += 4
+    r.syms = {number_of_syms: n, symbols: []}
+    while n > 0
+      len = _bin_to_u16_be(h.subarray(i, i + 2))
+      i += 2
+      if len == 0xffff
+        r.syms.symbols.push(null)
+      else
+        r.syms.symbols.push(String.fromCharCode.apply(null, h.subarray(i, i + len)))
+        i += (len + 1)
+      n -= 1
+    n = 0
+    while n < r.number_of_child_ireps
+      @_loadIrepRecord(r["child_#{n}"] = {}, h.subarray(i), offset + i)
+      n += 1
+    return

   _decodeRiteInst: (code) ->
     op = (code) & 0x7f
@@ -213,8 +237,10 @@
         return ["OP_GETMCNST\t#{a}, #{bx}", "R(#{a}) := R(#{a+1})::Syms(#{bx})"]
       when 0x14
         return ["OP_SETMCNST\t#{a}, #{bx}", "R(#{a+1})::Syms(#{bx}) := R(#{a})"]
-      # when 0x15 => OP_GETUPVAR
-      # when 0x16 => OP_SETUPVAR
+      when 0x15
+        return ["OP_GETUPVAR\t#{a}, #{b}, #{c}", "R(#{a}) := uvget(#{b},#{c})"]
+      when 0x16
+        return ["OP_SETUPVAR\t#{a}, #{b}, #{c}", "uvset(#{b},#{c},R(#{a})"]
       when 0x17
         return ["OP_JMP\t#{sbx}", "pc+=#{sbx}"]
       when 0x18
@@ -261,7 +287,10 @@
       # when 0x26 => OP_ENTER
       # when 0x27 => OP_KARG
       # when 0x28 => OP_KDICT
-      # when 0x29 => OP_RETURN
+      when 0x29
+        return ["OP_RETURN\t#{a}, #{b}", "return R(#{a}) [OP_R_#{
+          ["NORMAL", "BREAK", "RETURN"][b]
+        }]"] if b <= 2
       # when 0x2a => OP_TAILCALL
       # when 0x2b => OP_BLKPUSH
       when 0x2c


Minor formatting :cop:

Minor formatting :cop:

--- web/site/src/javascript/status/StatusDrawer.coffee
+++ web/site/src/javascript/status/StatusDrawer.coffee
@@ -16,16 +16,16 @@

 emotify_comf = (comf) ->
-  return '😶'               if not comf
+  return '😶' if not comf
   return '😫' if 1   <= comf <  1.3
-  return '😒'      if 1.3 <  comf <  1.7
-  return '😐'         if 1.7 <  comf <  2.3
-  return '😉'        if 2.3 <  comf <  2.7
-  return '😄'   if 2.7 <  comf <= 3
+  return '😒' if 1.3 <  comf <  1.7
+  return '😐' if 1.7 <  comf <  2.3
+  return '😉' if 2.3 <  comf <  2.7
+  return '😄' if 2.7 <  comf <= 3
   return '😡'
 comfort_rating_to_desc = (comf) ->
   text = do ->
-    return 'unrated'               if not comf
+    return 'unrated'            if not comf
     return 'very uncomfortable' if 1   <= comf <  1.3
     return 'uncomfortable'      if 1.3 <  comf <  1.7
     return 'indecisive'         if 1.7 <  comf <  2.3


wording, once more :bulb:

wording, once more :bulb:

--- templates/pages/home.coffee
+++ templates/pages/home.coffee
@@ -1,10 +1,10 @@
 module.exports = ($) ->
 	return '
-
Keep up with what you care about.

+
shout for everyone

-
shout allows you to subscribe to groups you are interested in.

+
Keep up with things you are enthusiastic about. Participate when and where participation is needed.

-
A group\'s host can keep all subscribers up to date by sending messages in realtime.

+
Use shout to subscribe to groups you are interested. Receive notifications from their organizers, on your phone and in real time.

@@ -18,8 +18,13 @@

-
Or keep interested people informed.

+
Featured groups:
+	bln-refugees-help,
+	festivals-germany
+

-
Use shout to reach, inform and coordinate them immediatly.

+
shout for organizers

+
+
Inform and coordinate people immediatly. Reach and motivate others enthusiastic about what you care about.

 create a group'


chore(watch): fix watching of new and deleted files

Remove prefixed (./) from globbing patterns.
New and deleted files are not picked up with the aforementioned prefix.
Removing the prefix works.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,15 +1,15 @@
 {APP_NAME, BOWER_COMPONENTS, SCRIPTS, STYLES} = require './config.coffee'

-BOWER_DIRECTORY          = './.components/'
-CHANGELOG_FILE           = './CHANGELOG.md'
+BOWER_DIRECTORY          = '.components/'
+CHANGELOG_FILE           = 'CHANGELOG.md'
 COMPONENTS_DIRECTORY     = "#{BOWER_DIRECTORY}_/"
 DEV_PORT                 = 8181
-DIST_DIRECTORY           = './dist/'
-DOCS_DIRECTORY           = './docs/'
+DIST_DIRECTORY           = 'dist/'
+DOCS_DIRECTORY           = 'docs/'
 SCRIPTS_MIN_FILE         = 'scripts.min.js'
-SRC_DIRECTORY            = './src/'
+SRC_DIRECTORY            = 'src/'
 STYLES_MIN_FILE          = 'styles.min.css'
-TEMP_DIRECTORY           = './.temp/'
+TEMP_DIRECTORY           = '.temp/'
 VENDOR_DIRECTORY         = 'vendor/'
 VENDOR_FONTS_DIRECTORY   = "#{VENDOR_DIRECTORY}fonts/"
 VENDOR_SCRIPTS_DIRECTORY = "#{VENDOR_DIRECTORY}scripts/"


typo fix

typo fix

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -76,7 +76,7 @@

   styles = styles.pipe(CSSmin()) if production
   styles = styles.pipe gulp.dest paths.styles.destination
-  styles = stypes.pipe livereload() unless production
+  styles = styles.pipe livereload() unless production
   styles

 gulp.task 'assets', ->


Modify gulp task

Modify gulp task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -24,7 +24,14 @@
   .pipe(minifyCss())
   .pipe(gulp.dest('lib/'))

-gulp.task 'js', ['ts'], ->
+gulp.task 'gfi', ['html', 'css', 'ts'], ->
+  gulp.src('lib/**/*.js')
+  .pipe(gfi({
+      '/* STYLES */': 'lib/growthmessage/styles/styles.css'
+    }))
+  .pipe(gulp.dest('lib/'));
+
+gulp.task 'js', ['gfi'], ->
   browserify
     entries: ['./lib/index.js']
     extensions: ['.js']
@@ -33,12 +40,9 @@
   .pipe(source('growthbeat.js'))
   .pipe(gulp.dest('./'))

-gulp.task 'gfi', ['html', 'css', 'js'], ->
-  gulp.src('lib/**/*.js')
-  .pipe(gfi({
-      '/* STYLES */': 'lib/growthmessage/styles/styles.css'
-    }))
-  .pipe(gulp.dest('lib/'));
+gulp.task 'clean', shell.task [
+  'rm -rf lib/*'
+]

 gulp.task 'uglify', () ->
   gulp.src('growthbeat.js')
@@ -46,5 +50,5 @@
   .pipe(rename({extname: '.min.js'}))
   .pipe(gulp.dest('./'))

-gulp.task 'build', ['gfi']
+gulp.task 'build', ['js']
 gulp.task 'default', ['build']


fix(gulp): load gulp from app's node_modules if present

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,8 +1,14 @@
 # rapid sequence
 # ==============
 module.exports = (options={}) ->
+	# try to load gulp from app's node_modules directory
+	# if it isn't there, load it from rapid-build's node_modules
+	# ==========================================================
+	try
+		gulp = require '../gulp'
+	catch e
+		gulp = require 'gulp'
 	rbDir     = __dirname
-	gulp      = require 'gulp'
 	bootstrap = require("#{rbDir}/bootstrap")()
 	config    = require("#{rbDir}/config") rbDir, options
 	tasks     = require("#{config.req.init}/tasks") gulp, config


add missing client param in updateUnregisteredUserAccount

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -249,7 +249,7 @@
       # update the user with empty data

       user.update modifier, updateUnregisteredUserAccount({
-        user, username, toBeDeletedUsername
+        user, username, toBeDeletedUsername, client
       }, callback)

@@ -707,7 +707,7 @@

   updateUnregisteredUserAccount = (options, callback) ->

-    { username : usernameAfterDelete, toBeDeletedUsername, user } = options
+    { username : usernameAfterDelete, toBeDeletedUsername, user, client } = options

     return (err, docs) ->
       return callback err  if err?


social: send group name as part of register event

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1602,7 +1602,10 @@
       ->
         subject             = Tracker.types.START_REGISTER
         { username, email } = user
-        Tracker.track username, { to : email, subject }, { pin }
+
+        opts = { pin, email, group: client.context.group }
+        Tracker.track username, { to : email, subject }, opts
+
         queue.next()

       ->


oauth: switch unset to have 1 in values

oauth: switch unset to have 1 in values

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -848,7 +848,7 @@
       return callback err

   unlinkOAuths: (callback)->
-    @update $unset: {foreignAuth:"", foreignAuthType:""}, callback
+    @update $unset: {foreignAuth:1, foreignAuthType:1}, callback

   @persistOauthInfo: (username, clientId, callback)->
     @extractOauthFromSession clientId, (err, foreignAuthInfo, session)=>
@@ -880,7 +880,7 @@
     @update {username}, $set: query, callback

   @clearOauthFromSession: (session, callback)->
-    session.update $unset: {foreignAuth: "", foreignAuthType:""}, callback
+    session.update $unset: {foreignAuth:1, foreignAuthType:1}, callback

   @copyPublicOauthToAccount: (username, {foreignAuth, foreignAuthType}, callback)->
     JAccount.one {"profile.nickname":username}, (err, account)->


[Banning] implemented user unblocking feature

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -263,7 +263,7 @@
           """
         callback createKodingError message
       else
-        user.update {$set: status: 'unconfirmed'}, callback
+        user.unblock callback
     else
       callback null

@@ -806,6 +806,17 @@
         JUser.emit "UserBlocked", @
         return callback err

+  unblock:(callback)->
+    @update
+      $set            :
+        status        : 'unconfirmed',
+        blockedUntil  : new Date()
+    , (err) =>
+      return callback err if err
+
+      JUser.emit "UserUnblocked", @
+      return callback err
+
   @copyOauthFromSessionToUser: (username, clientId, callback)->
     JSession.one {clientId: clientId}, (err, session) =>
       if err then callback err


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -2,7 +2,7 @@

 test "should exist", ->
   obj = GameObject()
-
+  obj.include()

 module()


Refactor tests

Refactor tests

--- test/dom-events.spec.coffee
+++ test/dom-events.spec.coffee
@@ -9,6 +9,15 @@
     el.innerHTML = html
     el[key] = value for key, value of attrs
     return el
+
+do_not_call = (event) ->
+    assert.ok false, "Function shouldn't be called"
+
+class Number.Counter
+    constructor: (@index = 0) ->
+    valueOf: -> @index
+    toString: -> @index.toString()
+    step: => ++@index

 suite 'DOMEvents', ->

@@ -24,67 +33,60 @@
     test 'remove nothing', ->
         div = makeElement('div')
         DOMEvents.removeListener div, 'foo'
-        assert true
+        assert.ok true

     test 'remove listener', (done) ->
         div = makeElement('div')
-        count = 0
-        foo = (event) -> assert false, "Foo"
-        bar = (event) -> count++
+        counter = new Number.Counter

-        DOMEvents.addListener div, 'foo', foo
-        DOMEvents.addListener div, 'bar', bar
+        DOMEvents.addListener div, 'foo', do_not_call
+        DOMEvents.addListener div, 'bar', counter.step
         DOMEvents.removeListener div, 'foo'

         DOMEvents.trigger div, 'bar'
         DOMEvents.trigger div, 'foo'

         setTimeout ->
-            assert.equal count, 1
+            assert.equal counter, 1
             done()
         , 0

     test 'remove selector', (done) ->
         div = makeElement('div')
-        count = 0
-        foo = -> assert false
-        bar = -> count++
+        counter = new Number.Counter

-        DOMEvents.addListener div, 'click', 'div', bar
-        DOMEvents.addListener div, 'click', 'a', foo
+        DOMEvents.addListener div, 'click', 'div', counter.step
+        DOMEvents.addListener div, 'click', 'a', do_not_call
         DOMEvents.removeListener div, 'click', 'a'

         DOMEvents.trigger div, 'click'

         setTimeout ->
-            assert.equal count, 1
+            assert.equal counter, 1
             done()
         , 0

     test 'remove handler', (done) ->
         div = makeElement('div')
-        count = 0
-        foo = (event) -> assert false, "Foo"
-        bar = (event) -> count++
+        counter = new Number.Counter

-        DOMEvents.addListener div, 'click', false, foo
-        DOMEvents.addListener div, 'click', false, bar
-        DOMEvents.removeListener div, '*', false, foo
+        DOMEvents.addListener div, 'click', false, do_not_call
+        DOMEvents.addListener div, 'click', false, counter.step
+        DOMEvents.removeListener div, '*', false, do_not_call

         DOMEvents.trigger div, 'click'

         setTimeout ->
-            assert.equal count, 1
+            assert.equal counter, 1
             done()
         , 0

     test 'remove all', (done) ->
         div = makeElement('div')
-        foo = (event) -> assert false, "Foo"

-        DOMEvents.addListener div, 'click', false, foo
-        DOMEvents.addListener div, 'focus', false, foo
-        DOMEvents.addListener div, 'blur', false, foo
+        DOMEvents.addListener div, 'click', false, do_not_call
+        DOMEvents.addListener div, 'focus', false, do_not_call
+        DOMEvents.addListener div, 'blur', false, do_not_call
         DOMEvents.removeListener div, '*'

         DOMEvents.trigger div, 'click focus blur'
@@ -94,9 +96,9 @@
     test 'delegate', (done) ->
         list = $('.list').get(0)
         item = $('.a').get(0)
-        count = 0
+        counter = new Number.Counter
         fn = (event) ->
-            count++
+            counter.step()
             assert.equal event.currentTarget, document
             assert.equal event.target, item

@@ -107,7 +109,7 @@
         DOMEvents.trigger document, 'click'

         setTimeout ->
-            assert.equal count, 1
+            assert.equal counter, 1
             done()
         , 0

@@ -136,9 +138,8 @@

     test 'Rye off', (done) ->
         itens = $('.list li')
-        foo = (event) -> assert false, "Foo"

-        itens.on 'blur', foo
+        itens.on 'blur', do_not_call
         itens.off 'blur'
         itens.trigger 'blur'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -18,7 +18,7 @@
     x: 5
     y: 10

-  obj4
+  obj4 = engine.

   indexSum = 0


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -26,7 +26,7 @@
     else if index is 1
       equals obj, obj2
     else if index is 2
-      equals obj, o
+      equals obj, obj

 test "#find", ->
   engine = Engine()


Change delay method to async as it conflicts with Q's delay

--- test/q-proxy.coffee
+++ test/q-proxy.coffee
@@ -14,14 +14,14 @@
     self: -> object
     value: -> "ok"
     child: -> child
-    delay: -> Q.delay(0).then -> child
+    async: -> Q.delay(0).then -> child
     error: -> throw new Error()

 mapping =
   s: "self"
   v: "value"
   c: "child"
-  d: "delay"
+  a: "async"
   e: "error"

 codes = [""].concat Object.keys(mapping)
@@ -50,16 +50,16 @@
       components = c.split("")
       methods = components.map (c) -> mapping[c]
       identifier = "proxy." + methods.join("().") + "()"
-      # no error, delay, error
-      if c.match(/^[^e]*d.*e.*$/)
+      # no error, async, error
+      if c.match(/^[^e]*a.*e.*$/)
         it "#{identifier} should be rejected", ->
           fnForMethods(proxy, methods)().should.be.rejected
-      # no error or value, delay, no error
-      else if c.match(/^[^e]*d[^e]*$/)
+      # no error or value, async, no error
+      else if c.match(/^[^e]*a[^e]*$/)
         it "#{identifier} should resolve to 'ok'", ->
           fnForMethods(proxy, methods)().should.eventually.equal("ok")
-      # no delay or value, error
-      else if c.match(/^[^d]*e/)
+      # no async or value, error
+      else if c.match(/^[^a]*e/)
         it "#{identifier} should throw", ->
           fnForMethods(proxy, methods).should.throw()
       else


Make sure services are totally stopped before unregistering

This was causing some problems with the browser tests as Qs defer
behavior is slightly different between node.js and the browser.

--- test/service.coffee
+++ test/service.coffee
@@ -1,3 +1,4 @@
+# Some nonsense to make these tests run in both browser and node environments
 if typeof require != 'undefined'
   chai = require 'chai'
   chaiAsPromised = require 'chai-as-promised'
@@ -92,7 +93,7 @@
       @onStopCalled = false
       service.onStop = => @onStopCalled = true
       Services.register('TestService', service)
-    afterEach -> Services.unregister('TestService')
+    afterEach -> Services.stop().then -> Services.unregister('TestService')

     it 'allows all services to be stopped', (done) ->
       Services.start('TestService')


text area support added

text area support added

--- vendor/assets/javascripts/aced-api.js.coffee
+++ vendor/assets/javascripts/aced-api.js.coffee
@@ -21,15 +21,27 @@
       ,
       conf: (options) ->
         unless @.data('aced_editor')
-          $.error "Element isn't ace editor"
-          return @
+          return $.error "Element isn't ace editor"

         editor = @.data('aced_editor')
+        session = editor.getSession()
+
         if options['theme']
           editor.setTheme "ace/theme/#{options['theme']}"
+
         if options['mode']
           editorMode = require("ace/mode/#{options['mode']}").Mode
-          editor.getSession().setMode(new editorMode())
+          session.setMode(new editorMode())
+
+        if options['content']
+          session.setValue options['content']
+
+        if options['on_change']
+          if typeof options['on_change'] == 'function'
+            session.on 'change', options['on_change']
+          else
+            $.error 'Argument must be a function'
+
         @
     }

@@ -41,4 +53,62 @@
       else
         $.error 'No method error'

+#----------------  aced_ta - textarea tag features  ---
+
+    ta_methods = {
+      init: (options) ->
+        #check this (must be textarea)
+        if not @.is('textarea')
+          return $.error('Selected tag is not textarea')
+
+        options == {} unless options
+
+        #random id
+        id_num = 1
+        id_num = $.random(1000) while $("#aced_editor_#{id_num}").length > 0
+
+        div = $('

')
+
+        div.attr 'id', "aced_editor_#{id_num}"
+
+        style = ''
+        css = ''
+
+        if options['class']
+          css += " #{options['class']} "
+
+        if options['style']
+          style += " #{options['style']} "
+
+        if options['normalize'] == 'yes'
+          style += " position: relative; margin-left: 0; "
+
+        if options['rows_to_px']
+          height = parseInt(@.attr('rows')) * parseInt(options['rows_to_px'])
+          style += " height: #{height}px; "
+
+        div.attr 'class', css
+        div.attr 'style', style
+
+        @.before div
+        @.hide()
+
+        ta = $(@)
+        callback = (event) ->
+          ta.val div.data('aced_editor').getSession().getValue()
+
+        div.aced 'init', $.extend(options, {on_change: callback})
+
+        @.data 'aced_editor_div', div
+        @.data 'aced_editor', div.data('aced_editor')
+    }
+
+    $.fn.aced_ta = (method) ->
+      if ta_methods[method]
+        return ta_methods[ method ].apply @, Array.prototype.slice.call( arguments, 1 )
+      else if typeof method == 'object' or not method
+        return ta_methods.init.apply @, arguments
+      else
+        $.error 'No method error'
+
 )(jQuery)


Space and enter trigger next slide as well.

--- vendor/javascripts/prez-controller.js.coffee
+++ vendor/javascripts/prez-controller.js.coffee
@@ -45,18 +45,20 @@
     prevSlide: -> @changeSlideBy -1
     end: -> @window.close()

+    KEY_ENTER = 13
+    KEY_SPACE = 32
     KEY_LEFT = 37
     KEY_RIGHT = 39

     @handlers:
         keyDown: (e) ->
-            return if $(e.target).is("input, textarea, select, option")
+            return if $(e.target).is("button, input, textarea, select, option")

             switch e.which
                 when KEY_LEFT
                     e.preventDefault()
                     Prez.current?.prevSlide()
-                when KEY_RIGHT
+                when KEY_ENTER, KEY_SPACE, KEY_RIGHT
                     e.preventDefault()
                     Prez.current?.nextSlide()


OX-187 performance testing on server

OX-187 performance testing on server

--- app/assets/javascripts/model/binary/pullqueue.coffee
+++ app/assets/javascripts/model/binary/pullqueue.coffee
@@ -161,8 +161,17 @@
     @bucketCount += bucketCount

     if @bucketCount == 1000
+      console.timeEnd "pull"
       console.log @roundTripTimes
-      console.timeEnd "pull"
+
+      sum = 0
+      for t in @roundTripTimes
+        sum += t
+
+      avg = sum / @roundTripTimes.length
+      console.log sum, @roundTripTimes.length
+      console.log "ping - min:", _.min(@roundTripTimes), "max:", _.max(@roundTripTimes), "avg:", avg
+
     #if @roundTripTime? and @bucketTime?
     #  @roundTripTime = (1 - @ROUND_TRIP_TIME_SMOOTHER) * @roundTripTime + @ROUND_TRIP_TIME_SMOOTHER * roundTripTime
     #  @bucketTime = (1 - @BUCKET_TIME_SMOOTHER) * @bucketTime + @BUCKET_TIME_SMOOTHER * (new Date() - @lastReceiveTime) / bucketCount


BF-201 recursion example

BF-201 recursion example

--- app/assets/javascripts/model/binary/pullqueue.coffee
+++ app/assets/javascripts/model/binary/pullqueue.coffee
@@ -7,7 +7,7 @@
 PullQueue =

   # Constants
-  PULL_DOWNLOAD_LIMIT : 10
+  PULL_DOWNLOAD_LIMIT : 100

   queue : []
   pullLoadingCount : 0
@@ -46,6 +46,9 @@
   insert : (bucket, zoomStep, priority) ->

     queue = @queue
+
+    if priority > 100 and zoomStep < 3
+      @insert([bucket[0] >> 1, bucket[1] >> 1, bucket[2] >> 1], zoomStep+1, priority-100)

     # Buckets with negative priority are not loaded
     return unless priority >= 0


mongo fetch dbs fix

mongo fetch dbs fix

--- kites/databases/mongodbApi.coffee
+++ kites/databases/mongodbApi.coffee
@@ -35,7 +35,6 @@
     id.substr 0, length

   getDbUsers : (dbName,callback)->
-    log.debug 'does call'

     db = new mongo.Db dbName, @server, native_parser: false
     db.open (err,db)=>
@@ -51,7 +50,6 @@
               usernames = []
               for user in items
                 usernames.push user.user
-              console.log "miki",usernames
               callback usernames

   fetchDatabaseList :(options, callback)->
@@ -88,11 +86,9 @@
                   users_dbs.forEach (users_db, index)=>
                     queue.push =>
                       @getDbUsers users_db,(usersArray)=>
-                        log.debug "x",users_db,index,usersArray
                         dbInfo.push { dbName: users_db, dbUser: usersArray }
                         queue.next()
                   queue.push ->
-                    log.debug 'info FINALLY', dbInfo
                     callback null, dbInfo
                   daisy queue

@@ -227,11 +223,13 @@

 options =
    username : "gokment"
-   dbName    : "dlskdlskdlsk"
-
-
-mongoDB.fetchDatabaseList options,(res)->
-  console.log res
+   dbName    : "dlskdldskdlskd1111skdlsk"
+
+#mongoDB.createDatabase options,(err,res)->
+#  console.log err,res
+
+mongoDB.fetchDatabaseList options,(err,res)->
+  console.log err,res
 #dbs =  ['gokment_c219e4c7','gokment_df539bb7']
 #for db in dbs
 #  do (db)->