job reload change

job reload change

--- server/job.coffee
+++ server/job.coffee
@@ -145,7 +145,6 @@
         return job_result.remove if _.isEmpty(this.queryParams) then '*' else this.queryParams
       else if this.urlParams.which is 'processes'
         res = job_process.remove if _.isEmpty(this.queryParams) then '*' else this.queryParams
-        job_process.insert _id: 'STUCK', repeat: true, function: 'API.job.stuck', priority: 1, group: 'API.job.stuck', limit: 900000
         return res
       else if this.urlParams.which is 'processing'
         return job_processing.remove if _.isEmpty(this.queryParams) then '*' else this.queryParams
@@ -421,16 +420,11 @@
   Meteor.setTimeout (() -> future.return()), Math.floor(Math.random()*interval+1)
   future.wait()
   API.log {msg: 'Starting job runner with interval ' + interval, _cid: process.env.CID, _appid: process.env.APP_ID, function: 'API.job.start', level: 'debug'}
-  # create a repeating limited stuck check process with id 'STUCK' so that it can check for stuck jobs
-  # multiple machines trying to create it won't matter because they will just overwrite each other, eventually only one process will run
   job_limit.remove '*'
-  job_process.remove 'STUCK'
-  job_process.remove 'TEST'
-  job_processing.remove 'STUCK'
-  job_processing.remove 'TEST'
-  job_result.remove 'STUCK'
-  job_result.remove 'TEST'
-  job_process.insert _id: 'STUCK', repeat: true, function: 'API.job.stuck', priority: 8000, group: 'STUCK', limit: 1800000 # half-hourly stuck check
+  API.job.reload() # TODO this will be risky if multiple cluster machines try to run it - add a limit checker or something so it only runs on the first machine
+  #job_process.remove 'TEST'
+  #job_processing.remove 'TEST'
+  #job_result.remove 'TEST'
   #job_process.insert _id: 'TEST', repeat: true, function: 'API.test', priority: 8000, group: 'TEST', limit: 86400000 # daily system test
   API.job._iid ?= Meteor.setInterval API.job.next, interval

@@ -442,28 +436,11 @@
   # note that processes already processing will keep going, but no new ones will start
   job_limit.insert _id: 'PAUSE'

-API.job.stuck = (p) ->
-  if p._id is 'STUCK' # the first stuck check after a system restart will have this as ID, so check for hung processes
-    if job_processing.find 'NOT _id:STUCK AND NOT _id:TEST AND createdAt:<' + p.createdAt
-      API.job.reload(if job_processing.find('NOT _id:STUCK AND NOT _id:TEST AND createdAt:>=' + p.createdAt) then 'createdAt:<' + p.createdAt else '*')
-  else if job_processing.count('*') is 0 and job_process.count('*') isnt 0
-    API.log {msg:'Job processing seems to be stuck, there are processes waiting but none running', notify:true, level:'WARN'}
-  st = API.job.status()
-  try
-    if p.previous and job_result.get p.previous
-      previous = API.job.result p.previous
-      if st.jobs.count isnt 0 and previous.jobs.count is st.jobs.count and previous.jobs.oldest?._id is st.jobs.oldest?._id and previous.jobs.newest?._id is st.jobs.newest?._id and previous.jobs.done is st.jobs.done and st.jobs.done isnt st.jobs.count
-        # if there are jobs, and previous jobs count matches current, and previous oldest and newest job IDs match current,
-        # and amount of jobs done is the same, and amount of jobs done is not all jobs, then something is wrong, send a warning
-        API.log {msg:'Job processing seems to be stuck, job amounts have not changed since last check', notify:true, level:'WARN'}
-      else if previous.processing.count is st.processing.count and previous.processing.oldest._id is st.processing.oldest._id and previous.processing.newest._id is st.processing.newest._id
-        API.log {msg:'Job processing seems to be stuck, processing amounts and oldest and newest have not changed since last check', notify:true, level:'WARN'}
-      else if st.jobs.done isnt st.jobs.count and st.processing.count is 0
-        API.log {msg:'Job processing seems to be stuck, there are jobs not done but no processes running', notify:true, level:'WARN'}
-  # TODO could trigger a reload if it seems processing jobs are just not getting done - can be done en masse or per job not done
-  return st
-
-API.job.status = (filter='NOT group:STUCK AND NOT group:TEST') ->
+API.job.stuck = () ->
+  # TODO add a stuck checker
+  return false
+
+API.job.status = (filter='NOT group:TEST') ->
   res =
     running: API.job.running()
     jobs:
@@ -504,7 +481,7 @@
   else
     job_processing.each q, ((proc) ->
       ret += 1
-      if proc.group isnt 'STUCK' and not job_result.get(proc._id)? and not job_process.get(proc._id)?
+      if not job_result.get(proc._id)? and not job_process.get(proc._id)?
         proc.reloaded ?= []
         proc.reloaded.push proc.createdAt
         reloads.push proc


Update # Use 1.5 multiplier to calculate thread count

--- server/app.coffee
+++ server/app.coffee
@@ -45,7 +45,8 @@
     .catch (err)->
       console.error err

-  threadCount = require('os').cpus().length
+  cpuCount = require('os').cpus().length
+  threadCount = Math.ceil cpuCount * 1.5
   for i in [1..threadCount]
     cluster.fork()


FIX: Make grade icon responsive to new input grades

--- src/app/common/partials/directives/grade-icon.coffee
+++ src/app/common/partials/directives/grade-icon.coffee
@@ -7,8 +7,10 @@
   scope:
     inputGrade: '=?grade'
   controller: ($scope, gradeService) ->
-    $scope.grade = if _.isString($scope.inputGrade) then gradeService.grades.indexOf($scope.inputGrade) else $scope.inputGrade
-    $scope.gradeText = (grade) ->
-      if $scope.grade? then gradeService.grades[$scope.grade] or "Grade"
-    $scope.gradeLetter = (grade) ->
-      gradeService.gradeAcronyms[$scope.gradeText(grade)] or 'G'
+    $scope.$watch 'inputGrade', (newGrade) ->
+      return unless newGrade?
+      $scope.grade = if _.isString($scope.inputGrade) then gradeService.grades.indexOf($scope.inputGrade) else $scope.inputGrade
+      $scope.gradeText = (grade) ->
+        if $scope.grade? then gradeService.grades[$scope.grade] or "Grade"
+      $scope.gradeLetter = (grade) ->
+        gradeService.gradeAcronyms[$scope.gradeText(grade)] or 'G'


BY now closes implicit calls.

BY now closes implicit calls.

--- rewriter.coffee
+++ rewriter.coffee
@@ -345,7 +345,7 @@
 IMPLICIT_BLOCK   = ['->', '=>', '{', '[', ',']

 # Tokens that always mark the end of an implicit call for single-liners.
-IMPLICIT_END     = ['POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'LOOP', 'TERMINATOR', 'INDENT']
+IMPLICIT_END     = ['POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY', 'LOOP', 'TERMINATOR', 'INDENT']

 # Single-line flavors of block expressions that have unclosed endings.
 # The grammar can't disambiguate them, so we insert the implicit indentation.


Workaround for failed reconnections.

Workaround for failed reconnections.

--- app/assets/javascripts/reports.js.coffee
+++ app/assets/javascripts/reports.js.coffee
@@ -382,6 +382,7 @@
         faye_url = '/live'
         faye_url = 'http://localhost:3001/live' if document.location.hostname == "localhost" or document.location.hostname == "127.0.0.1"
         window.faye = new Faye.Client(faye_url, timeout: 60)
+        window.faye.disable("websocket")

         window.pings_missed = 0
         window.faye.subscribe("/ping", (message) =>
@@ -395,6 +396,8 @@
                         $(".ping-fail-overlay").css("display":"block")
                 ), 1000)

+        #window.faye.on('transport:down', () -> console.log("transport:down"))
+        #window.faye.on('transport:down', () -> console.log("transport:up"))

         window.listController = new ListController(api: window.API)
         window.reportController = new ReportController(api: window.API)


Cambio del formato del DatePicker

Cambio del formato del DatePicker

Se ha cambiado a que muestre primero los dias, luego los meses y por ultimo los año

--- app/assets/javascripts/surveys.js.coffee
+++ app/assets/javascripts/surveys.js.coffee
@@ -4,7 +4,7 @@

 activado=false
 evaluar=()-> if $('#survey_deadline')? and $('#survey_deadline').datepicker? then activar() else $("#survey_deadline").hover(activar)
-activar=() -> if activado is false then $('#survey_deadline').datepicker() activado = true
+activar=() -> if activado is false then $('#survey_deadline').datepicker({ dateFormat: 'dd/mm/yy' }) activado = true

 # si se recarga la pagina
 $(document).ready(evaluar)


Make sure freehand tools only appear if enabled

--- app/classifier/tasks/generic-editor.cjsx
+++ app/classifier/tasks/generic-editor.cjsx
@@ -122,9 +122,13 @@
                         Type{' '}
                         <select name="#{@props.taskPrefix}.#{choicesKey}.#{index}.type" value={choice.type} onChange={handleChange}>
                           {for toolKey of drawingTools
-                            <option key={toolKey} value={toolKey}>{toolKey}</option> unless toolKey in ["grid"]}
+                            <option key={toolKey} value={toolKey}>{toolKey}</option> unless toolKey in ["grid", "freehandLine", "freehandShape"]}
                           {if @canUse("grid")
                             <option key="grid" value="grid">grid</option>}
+                          {if @canUse("freehandLine")
+                            <option key="freehandLine" value="freehandLine">freehand line</option>}
+                          {if @canUse("freehandShape")
+                            <option key="freehandShape" value="freehandShape">freehand shape</option>}
                         </select>
                       </AutoSave>
                     </div>


Edited fields for Xbox Controller

Edited fields for Xbox Controller

--- app/client/components/GamepadItem.coffee
+++ app/client/components/GamepadItem.coffee
@@ -70,8 +70,6 @@
                 12
                 13
                 14
-                15
-                16

                 Value
@@ -90,8 +88,6 @@
                 {values.buttons[12]}
                 {values.buttons[13]}
                 {values.buttons[14]}
-                {values.buttons[15]}
-                {values.buttons[16]}

@@ -101,6 +97,8 @@
                 1
                 2
                 3
+                4
+                5

                 Value
@@ -108,6 +106,8 @@
                 {values.axes[1]}
                 {values.axes[2]}
                 {values.axes[3]}
+                {values.axes[4]}
+                {values.axes[5]}

         </Modal.Body>


revert branch

revert branch

--- src/dropzone.coffee
+++ src/dropzone.coffee
@@ -392,7 +392,10 @@
           if file.status == Dropzone.UPLOADING
             Dropzone.confirm @options.dictCancelUploadConfirmation, => @removeFile file
           else
-            Dropzone.confirm @options.dictRemoveFileConfirmation, => @removeFile file
+            if @options.dictRemoveFileConfirmation
+              Dropzone.confirm @options.dictRemoveFileConfirmation, => @removeFile file
+            else
+              @removeFile file

         removeLink.addEventListener "click", removeFileEvent for removeLink in file.previewElement.querySelectorAll("[data-dz-remove]")

@@ -1412,7 +1415,7 @@
 # The default implementation just uses `window.confirm` and then calls the
 # appropriate callback.
 Dropzone.confirm = (question, accepted, rejected) ->
-  if !question || window.confirm question
+  if window.confirm question
     accepted()
   else if rejected?
     rejected()


Only show sub comment add when logged in

--- core/app/backbone/views/sub_comments/react_sub_comments.coffee
+++ core/app/backbone/views/sub_comments/react_sub_comments.coffee
@@ -70,7 +70,8 @@
       R.div className: 'sub-comments',
         @model().map (sub_comment) =>
           ReactSubComment(model: sub_comment)
-        ReactSubCommentsAdd(addToCollection: @model())
+        if Factlink.Global.signed_in
+          ReactSubCommentsAdd(addToCollection: @model())

 window.ReactSubComments = React.createClass
   render: ->


Removing `.only` directive from test to run them all

--- tests/tests/acceptance/conditional.coffee
+++ tests/tests/acceptance/conditional.coffee
@@ -12,7 +12,7 @@
   browser: "code = 'BROWSER'"
   universal: "code = 'UNIVERSAL'"

-describe.only '[acceptance] conditional compilation', ->
+describe '[acceptance] conditional compilation', ->

   it 'should compile app with ENV=node', ->


Update crane.coffee

Update crane.coffee

--- app/lib/field-guide-content/crane.coffee
+++ app/lib/field-guide-content/crane.coffee
@@ -1,6 +1,6 @@
 module.exports =
   description: '''
-    There are two crane species in Gorongosa: grey crowned crane (Balearica regulorum) and wattled crane (Bugeranus carunculatus). The grey crowned crane has a pale gray neck, which contrasts sharply with its black and white wings, crest of golden feathers on top of its head, and bright red pouch that hangs below its throat. Its head is black, with a large patch of bare white skin on the cheeks and a red spot above. Its legs and feet are black. Males tend to be slightly larger than females but are otherwise indistinguishable.
+    There are two crane species in Gorongosa: grey crowned crane (Balearica regulorum) and wattled crane (Bugeranus carunculatus). The grey crowned crane has a pale gray neck, which contrasts sharply with its black and white wings, crest of golden feathers on top of its head, and bright red pouch that hangs below its throat. Its head is black, with a large patch of bare white skin on the cheeks and a red spot above. Its legs and feet are black. Males tend to be slightly larger than females but are otherwise indistinguishable.

     The wattled crane is a large bird with a long white neck and breast. The underparts are black, while the body is gray. They have a black “cap” on top of their head, bare red facial skin, and long wattles that bear white feathers and give rise to their common name.


concatenating names to 30 chars

concatenating names to 30 chars

--- dkobo/koboform/static/js/xlform/XLF.model.utils.coffee
+++ dkobo/koboform/static/js/xlform/XLF.model.utils.coffee
@@ -56,7 +56,8 @@
       collection.parseable = false

 XLF.sluggifyLabel = (str, other_names=[])->
-  attempt_base = str.replace(/\s+$/, "")
+  attempt_base = str.slice(0,30)
+                  .replace(/\s+$/, "")
                   .replace(/^\s+/, "")
                   .replace(/\s/g, "_")
                   .replace(/\W/g, "")


HBase lifecycle example

HBase lifecycle example

--- actions/hdp/lifecycle.coffee
+++ actions/hdp/lifecycle.coffee
@@ -197,12 +197,14 @@
   hbase_regionserver_start: (ctx, callback) ->
     {hbase_user, hbase_conf_dir} = ctx.config.hdp
     ctx.execute
+      # su -l hbase -c "/usr/lib/hbase/bin/hbase-daemon.sh --config /etc/hbase/conf start regionserver"
       cmd: "su -l #{hbase_user} -c \"/usr/lib/hbase/bin/hbase-daemon.sh --config #{hbase_conf_dir} start regionserver\""
     , (err, stoped) ->
       callback err, stoped
   hbase_regionserver_stop: (ctx, callback) ->
     {hbase_user, hbase_conf_dir} = ctx.config.hdp
     ctx.execute
+      # su -l hbase -c "/usr/lib/hbase/bin/hbase-daemon.sh --config /etc/hbase/conf stop regionserver"
       cmd: "su -l #{hbase_user} -c \"/usr/lib/hbase/bin/hbase-daemon.sh --config #{hbase_conf_dir} stop regionserver\""
     , (err, stoped) ->
       callback err, stoped


Set a failsafe max percentage

Set a failsafe max percentage

--- app/scripts/directives/horizontal.coffee
+++ app/scripts/directives/horizontal.coffee
@@ -57,6 +57,8 @@
     @scope.$watch('percentage', @updatePercentage)

   updatePercentage: =>
+    @scope.percentage = 1 if @scope.percentage > 1
+
     idealdx = (Math.floor(Math.log(@scope.value) / Math.log(1000))) * 0.012
     idealRatio = idealdx + (Math.floor(Math.log(@scope.value) / Math.log(10)) + 1) * 0.024
     @width = if @scope.percentage > idealRatio then @scope.percentage * @maxWidth else idealRatio * @maxWidth


add placeholder selection

add placeholder selection

--- lib/atomizr.coffee
+++ lib/atomizr.coffee
@@ -256,7 +256,7 @@
       atom.notifications.addError("Atomizr", detail: e, dismissable: true)
       return

-    output =
+    output =
       ".source": data

     # Write to editor
@@ -268,6 +268,13 @@
     unless atom.config.get('atomizr.warnAboutMissingScope') is false
       atom.notifications.addWarning("Atomizr", detail: "Could not determine scope automatically, using placeholder", dismissable: false)

+      # Select placeholder scope
+      rangeStart = 1
+      rangeEnd = ".source".length + rangeStart
+      range = [[1, rangeStart], [1, rangeEnd]];
+
+      editor.selectMarker(editor.markBufferRange(range, {invalidate: 'never'}))
+
   vsCodeToSubl: ->
     editor = atom.workspace.getActiveTextEditor()
     unless editor?
@@ -288,6 +295,13 @@

     unless atom.config.get('atomizr.warnAboutMissingScope') is false
       atom.notifications.addWarning("Atomizr", detail: "Could not determine scope automatically, using placeholder", dismissable: false)
+
+      # Select placeholder scope
+      rangeStart = 12
+      rangeEnd = "source".length + rangeStart
+      range = [[2, rangeStart], [2, rangeEnd]];
+
+      editor.selectMarker(editor.markBufferRange(range, {invalidate: 'never'}))

   # Convert Atom snippet format (CSON to JSON, or vice versa)


KDRouter dom exception fix

KDRouter dom exception fix

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -132,7 +132,7 @@

     if shouldPushState
       method = if replaceState then 'replaceState' else 'pushState'
-      history[method] objRef, '', path
+      history[method] objRef, path, path

     for edge in frag
       if node[edge]


reactivity: exception fix when switching from channel to IDE

--- client/activity/lib/flux/chatinput/getters.coffee
+++ client/activity/lib/flux/chatinput/getters.coffee
@@ -408,7 +408,8 @@
   commandsQuery stateId
   ActivityFluxGetters.selectedChannelThread
   (allCommands, query, selectedChannelThread) ->
-    return immutable.List()  if disabledFeatures.indexOf('commands') > -1
+    if disabledFeatures.indexOf('commands') > -1 or not selectedChannelThread
+      return immutable.List()

     ignoredFeatures  = []
     selectedChannel  = selectedChannelThread.get('channel').toJS()


ComputeController: fetchPlans without provider info, fallback to free plan if an error occurs while fetching plans

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -259,11 +259,14 @@
     if @plans
       kd.utils.defer => callback @plans
     else
-      remote.api.ComputeProvider.fetchPlans
-        provider: "koding"
-      , (err, plans)=>
-          if err? then kd.warn err
-          else @plans = plans
+      remote.api.ComputeProvider.fetchPlans (err, plans)=>
+        # If there is an error at least return a simple plan
+        # which includes only 'free' plan
+        if err? or not plans?
+          kd.warn err
+          callback { free: total: 1, alwaysOn: 0, storage: 3 }
+        else
+          @plans = plans
           callback plans

@@ -294,6 +297,10 @@
     @fetchUserPlan (plan)=> @fetchPlans (plans)=>
       @fetchUsage { provider }, (err, usage)=>
         @fetchRewards { unit: 'GB' }, (err, reward)->
+          # If there is an invalid plan set for user
+          # or plans failed to fetch, then fallback to 'free' plan
+          plan = 'free'  unless plans[plan]?
+
           callback err, { plan, plans, usage, reward }


client/env: simplify machine filter with predicate

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -103,9 +103,7 @@

   getMachineWithPredicate: (fn, predicate) ->

-    filtered = @getAllMachines().filter predicate
-
-    if filtered.length then filtered[0] else null
+    @getAllMachines().filter(predicate)[0]

   fetchMachine: (identifier, callback) ->


work in progress, combining request invitation for koding and usual groups... no email notification for admins when access requested by non-user. approval by admin does not work for non-users

--- client/landing/app/MainApp/login/loginview.coffee
+++ client/landing/app/MainApp/login/loginview.coffee
@@ -217,21 +217,22 @@
         @hide()

   doRequest:(formData)->
-
-    KD.remote.api.JInvitationRequest.create formData, (err, result)=>
-
-      if err
-        msg = if err.code is 11000 then "This email was used for a request before!"
-        else "Something went wrong, please try again!"
-        new KDNotificationView
-          title     : msg
-          duration  : 2000
-      else
-        @requestForm.reset()
-        @requestForm.email.hide()
-        @requestForm.button.hide()
-        @$('.flex-wrapper').addClass 'expanded'
-      @requestForm.button.hideLoader()
+    {entryPoint} = KD.config
+    slug = if entryPoint?.type is 'group' and entryPoint.slug\
+           then entryPoint.slug else 'koding'
+    KD.remote.cacheable slug, (err, [group])=>
+      group.requestAccess formData, (err)=>
+        if err
+          warn err
+          new KDNotificationView
+            title     : 'Something went wrong, please try again!'
+            duration  : 2000
+        else
+          @requestForm.reset()
+          @requestForm.email.hide()
+          @requestForm.button.hide()
+          @$('.flex-wrapper').addClass 'expanded'
+        @requestForm.button.hideLoader()

   showHeadBanner:(message, callback)->
     @headBannerMsg = message


Sort collection

Sort collection

--- client/lanes/models/query/CollectionResult.coffee
+++ client/lanes/models/query/CollectionResult.coffee
@@ -4,6 +4,10 @@
         @collection.on('add remove reset', =>
             @query.trigger('change', @query)
         )
+        @collection.on('sort', =>
+            @query.changeCount += 1
+        )
+
         this

     rowAt: (index, options = {}) ->
@@ -35,6 +39,7 @@
     ensureLoaded: ->
         @collection.ensureLoaded().then(@)

+    reset: Lanes.emptyFn

     rowRepresentation: (rowNum) ->
         @modelAt(rowNum)
@@ -42,25 +47,19 @@
     valueForField: (rowNum, field) ->
         @modelAt(rowNum)[field.id]

+    fieldToSortValue:
+        any:    (v) -> v
+        bigdec: (v) -> parseFloat(v)
+
     _updateSort: ->
         field = @query.sortField
         asc = @query.sortAscending
-
-
+        f2v = @fieldToSortValue[field.type] || @fieldToSortValue.any
         @collection.comparator = field.sortBy or (a, b) ->
-            a = a[field.id]; b = b[field.id]
-            result = if a < b then -1 else if b > a then 1 else 0
+            a = f2v(a[field.id]); b = f2v(b[field.id])
+            result = if a < b then -1 else if a > b then 1 else 0
             if asc then result else result * -1
-
-
-    #     @sort =
-    #         asc: asc, index: @query.fields.indexOf(field)
-    #         visibleIndex: @query.fields.visible.indexOf(field)
-    #     @collection.comparator = (a, b) ->
-    #         sort = field.comparator(a, b)
-    #         if asc then sort else sort * -1
-    #     @query.trigger('sort')
-    #     @collection.sort()
+        @collection.sort()

 Object.defineProperties Lanes.Models.Query.CollectionResult.prototype,
     length:


Update test.coffee

Update test.coffee

--- test/test.coffee
+++ test/test.coffee
@@ -23,20 +23,20 @@
     expect(metaMap.config).to.have.property("email")

 describe "getConcepts([corpus]): analzye text array", () ->
-  @timeout(30000)
-  it "works in vanilla mode", ( done ) ->
+  @timeout(99999)
+  it "works in vanilla mode", () ->
     fAnalysis = metaMap.getConcepts("Definition and classification of chronic kidney disease")
     fElem = fAnalysis.then((arr) -> arr[0])
     tests = []
     tests.push expect(fAnalysis).to.eventually.be.not.empty
     tests.push expect(fElem).to.eventually.have.property "Utterances"
     tests.push expect(fElem).to.eventually.have.deep.property "Utterances.Utterance.Phrases"
-    BPromise.all(tests).then( () -> done() )
-  it "accepts options", ( done ) ->
+    return BPromise.all(tests)
+  it "accepts options", () ->
     fRes = metaMap.getConcepts("Definition and classification of chronic kidney disease",{r: ["AIR","AOD"]})
     fElem = fRes.then((arr) -> arr[0])
     tests = []
     tests.push expect(fRes).to.eventually.be.not.empty
     tests.push expect(fElem).to.eventually.have.property "Utterances"
     tests.push expect(fElem).to.eventually.have.deep.property "Utterances.Utterance.Phrases"
-    BPromise.all(tests).then( () -> done() )
+    return BPromise.all(tests)


Hide bid status when the auction is closed

--- components/artwork_item/views/sale_artwork.coffee
+++ components/artwork_item/views/sale_artwork.coffee
@@ -28,9 +28,9 @@

     if @sale
       if @sale.has 'auctionState'
-        @setBidButtonState()
+        @setupAuctionState()
       else
-        @listenTo @sale, 'change:auctionState', @setBidButtonState
+        @listenTo @sale, 'change:auctionState', @setupAuctionState

   contactSeller: (e) ->
     e.preventDefault()
@@ -53,7 +53,11 @@
         copy        : 'Sign up to bid'
         redirectTo  : @sale.redirectUrl @model

-  setBidButtonState: ->
+  setupAuctionState: ->
+    # Possibly hide the bid status
+    @$('.artwork-item-auction-bid-status').hide() if @sale.isClosed()
+
+    # Set button state
     $button   = @$('.artwork-item-bid')
     state     = @sale.bidButtonState @currentUser, @model
     $button.


Update saucelabs platforms

Update saucelabs platforms


added tagging task on release

added tagging task on release

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -38,6 +38,7 @@
         tasks: ['jasmine']

     exec:
+      tagRelease: 'git tag v<%= pkg.version %>'
       publish: 'git push origin master && git push --tags && npm publish'
       commitRelease: 'git commit bower.json easy-date.min.js package.json -m "Release v<%= pkg.version %>"'

@@ -60,6 +61,6 @@
   grunt.registerTask 'default', [ 'test' ]

   grunt.registerTask 'build', [ 'test', 'bump-only', 'uglify']
-  grunt.registerTask 'release', ['exec:commitRelease', 'exec:publish']
+  grunt.registerTask 'release', ['exec:commitRelease', 'exec:tagRelease', 'exec:publish']

   return


Add option to custom task

Add option to custom task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -63,6 +63,7 @@
                     title: "Codo-codo Documentation"
                     extra: [ "LICENSE-MIT" ]
                     undocumented: yes
+                    stats: no
                 src: [
                     "test/fixtures/default"
                     "test/fixtures/custom/**/*.coffee"


Add desktop and mobile Safari, and Android browser to tested browsers

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -98,6 +98,12 @@
           ,
             browserName: 'firefox'
           ,
+            browserName: 'safari'
+          ,
+            browserName: 'ipad'
+          ,
+            browserName: 'android'
+          ,
             browserName: 'internet explorer'
             platform: 'WIN8'
             version: '10'


Use proper js test folder.

Use proper js test folder.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,7 +29,7 @@

     clean:
       default: "lib"
-      test: "spec"
+      test: "test"

     coffee:
       options:
@@ -75,9 +75,9 @@
         stderr: true
         failOnError: true
       coverage:
-        command: "istanbul cover jasmine-node --captureExceptions spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
+        command: "istanbul cover jasmine-node --captureExceptions test && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
       jasmine:
-        command: "jasmine-node --captureExceptions spec"
+        command: "jasmine-node --captureExceptions test"

   # load plugins that provide the tasks defined in the config
   grunt.loadNpmTasks "grunt-coffeelint"


Remove reference to non-existent run-core-specs task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -172,7 +172,7 @@

   grunt.registerTask('compile', ['coffee', 'prebuild-less', 'cson'])
   grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint'])
-  grunt.registerTask('test', ['shell:kill-atom', 'run-core-specs', 'run-specs'])
+  grunt.registerTask('test', ['shell:kill-atom', 'run-specs'])
   grunt.registerTask('ci', ['lint', 'update-atom-shell', 'build', 'set-development-version', 'test'])
   grunt.registerTask('deploy', ['partial-clean', 'update-atom-shell', 'build', 'codesign'])
   grunt.registerTask('docs', ['markdown:guides', 'build-docs'])


use new task name for grunt bower

use new task name for grunt bower

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -12,7 +12,7 @@

   grunt.registerTask('default',['build'])

-  grunt.registerTask('build',['bower_install', 'bower:configure', 'coffee:compile', 'bower_css', 'jade:compile'])
+  grunt.registerTask('build',['bower_install', 'bowerRequirejs', 'coffee:compile', 'bower_css', 'jade:compile'])
   grunt.registerTask('dev',['default', 'connect','watch'])

   grunt.registerTask 'bower_install', 'Install bower modules', ->
@@ -50,7 +50,7 @@
         dest: 'public/js/',
         ext: '.js'

-    bower:
+    bowerRequirejs:
       configure:
         rjsConfig: 'public/js/bower.js'


Clarify comment.

Clarify comment.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -62,10 +62,11 @@
         ]
         dest: '_public/javascripts/lib'

+      # Since the non-minimized Bootstrap module references the
+      # CSS map, the map must be colocated with the stylesheets.
+      # The CSS style files themselves are copied to the destination
+      # in the concat:css task.
       css:
-        # Note: Since the non-minimized Bootstrap module
-        # references the CSS map, the map must be colocated
-        # with the stylesheets.
         expand: true
         flatten: true
         cwd: 'bower_components/'


Explicitly list freedom file

Explicitly list freedom file

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -328,13 +328,12 @@
           dest: devBuildPath,
           onlyIf: 'modified'
         }]
+
       devGenericCore:
         files: [{
           nonull: true,
-          expand: true,
-          cwd: 'src/generic_core/dev_build/',
-          src: ['*'],
-          dest: devBuildPath + '/generic_core',
+          src: 'src/generic_core/dev_build/freedom-module.json',
+          dest: devBuildPath + '/generic_core/freedom-module.json',
           onlyIf: 'modified'
         }]

@@ -807,8 +806,8 @@
         src: [
           'src/**/*.ts',
           '!src/**/*.d.ts',
-          '!src/lib/build-tools/**/*.ts',
-          '!src/integration/**/*.ts',
+          '!src/lib/build-tools/**/*',
+          '!src/integration/**/*',
           '!src/**/*.core-env.ts',
           '!src/**/*.core-env.spec.ts'
         ]


chore( formatting )

chore( formatting )

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,381 +1,390 @@
 mountFolder = (connect, dir)->
-	return connect.static(require('path').resolve(dir))
+  return connect.static(require('path').resolve(dir))

 fileHTMLRewriter = ({regex, snippet})->
-	excludeList = [".woff", ".js", ".css", ".ico"]
-
-	acceptsHtmlExplicit = (req)->
-		accept = req.headers["accept"]
-		return false unless accept
-		return (~accept.indexOf("html"))
-
-	isExcluded = (req)->
-		url = req.url
-		excluded = false
-		return true unless url
-
-		excludeList.forEach (exclude)->
-			if ~url.indexOf(exclude)
-				excluded = true
-		return excluded
-
-	return (req, res, next)->
-		write = res.write
-
-		# Select just html file
-		if !acceptsHtmlExplicit(req) or isExcluded(req)
-      		return next()
-
-		res.write = (string, encoding)->
-			body = if string instanceof Buffer then string.toString() else string
-			body = body.replace regex, snippet
-
-			if string instanceof Buffer
-				string = new Buffer(body)
-			else
-				string = body
-
-			unless this.headerSent
-				this.setHeader 'content-length', Buffer.byteLength(body)+snippet.lenght
-				this._implicitHeader()
-
-			write.call(res, string, encoding)
-
-		next()
+  excludeList = [".woff", ".js", ".css", ".ico"]
+
+  acceptsHtmlExplicit = (req)->
+    accept = req.headers["accept"]
+    return false unless accept
+    return (~accept.indexOf("html"))
+
+  isExcluded = (req)->
+    url = req.url
+    excluded = false
+    return true unless url
+
+    excludeList.forEach (exclude)->
+      if ~url.indexOf(exclude)
+        excluded = true
+    return excluded
+
+  return (req, res, next)->
+    write = res.write
+
+    # Select just html file
+    if !acceptsHtmlExplicit(req) or isExcluded(req)
+          return next()
+
+    res.write = (string, encoding)->
+      body = if string instanceof Buffer then string.toString() else string
+      body = body.replace regex, snippet
+
+      if string instanceof Buffer
+        string = new Buffer(body)
+      else
+        string = body
+
+      unless this.headerSent
+        this.setHeader 'content-length', Buffer.byteLength(body)+snippet.lenght
+        this._implicitHeader()
+
+      write.call(res, string, encoding)
+
+    next()

 module.exports = (grunt)->

-	grunt.loadNpmTasks('grunt-contrib-clean')
-	grunt.loadNpmTasks('grunt-contrib-coffee')
-	grunt.loadNpmTasks('grunt-contrib-compass')
-	grunt.loadNpmTasks('grunt-contrib-less')
-	grunt.loadNpmTasks('grunt-contrib-concat')
-	grunt.loadNpmTasks('grunt-contrib-connect')
-	grunt.loadNpmTasks('grunt-contrib-copy')
-	grunt.loadNpmTasks('grunt-contrib-cssmin')
-	grunt.loadNpmTasks('grunt-contrib-htmlmin')
-	grunt.loadNpmTasks('grunt-contrib-imagemin')
-	grunt.loadNpmTasks('grunt-contrib-uglify')
-	grunt.loadNpmTasks('grunt-contrib-requirejs')
-	grunt.loadNpmTasks('grunt-contrib-watch')
-	grunt.loadNpmTasks('grunt-open')
-	grunt.loadNpmTasks('grunt-usemin')
-	grunt.loadNpmTasks('grunt-mocha')
-	grunt.loadNpmTasks('grunt-coffeecov')
-
-	# configurable paths
-	yeomanConfig = {
-		app: 'app'
-		src: 'src'
-		dist: 'dist'
-		test: 'test'
-
-		tmp: '.tmp'
-		tmp_dist: '.tmp-dist'
-
-		server_port: 9000
-		livereload_port: 35729
-	}
-
-	try
-		yeomanConfig.app = require('./bower.json').appPath || yeomanConfig.app
-	catch e
-
-	#
-	# Grunt configuration:
-	#
-	# https://github.com/cowboy/grunt/blob/master/docs/getting_started.md
-	#
-	grunt.initConfig
-
-		# Project configuration
-		# ---------------------
-		yeoman: yeomanConfig
-		watch:
-			options:
-				interrupt: true
-
-			coffee:
-				files: ['<%= yeoman.src %>/coffee/{,**/}*.coffee']
-				tasks: ['coffee:dist']
-
-			compass:
-				files: ['<%= yeoman.src %>/sass/{,**/}*.{scss,sass}']
-				tasks: ['compass:server']
-
-			files:
-				files: [
-					'<%= yeoman.tmp %>/{,**/}*.{css,js}'
-					'<%= yeoman.app %>/{,**/}*.html'
-					'<%= yeoman.app %>/css/{,**/}*.css'
-					'<%= yeoman.app %>/js/{,**/}*.js'
-					'<%= yeoman.app %>/images/{,**/}*.{png,jpg,jpeg}'
-					'!<%= yeoman.app %>/components/**'
-				]
-				tasks: []
-				options:
-					livereload: yeomanConfig.livereload_port
-
-		connect:
-			server:
-				options:
-					port: yeomanConfig.server_port
-					hostname: '0.0.0.0'
-					middleware: (connect)->
-						return [
-							require('connect-livereload')(port: yeomanConfig.livereload_port)
-							mountFolder(connect, yeomanConfig.tmp)
-							mountFolder(connect, yeomanConfig.app)
-						]
-			dist:
-				options:
-					port: yeomanConfig.server_port + 1
-					hostname: '0.0.0.0'
-					middleware: (connect)->
-						return [
-							mountFolder(connect, yeomanConfig.dist)
-						]
-
-			test:
-				options:
-					port: yeomanConfig.server_port + 2
-					hostname: '0.0.0.0'
-					middleware: (connect)->
-						return [
-							require('connect-livereload')(port: yeomanConfig.livereload_port)
-							fileHTMLRewriter
-								snippet: [
-									"<!-- Test snippet -->",
-									"<script src=\"components/mocha/mocha.js\"></script>",
-									"<link rel=\"stylesheet\" href=\"components/mocha/mocha.css\">",
-									"<script>",
-									"    window.is_test = true;",
-									"</script>",
-									""
-								].join('\n')
-								regex: /<!-- Test snippet -->/
-							mountFolder(connect, yeomanConfig.test)
-							mountFolder(connect, yeomanConfig.tmp)
-							mountFolder(connect, yeomanConfig.app)
-						]
-
-		open:
-			server:
-				path: 'http://localhost:<%= connect.server.options.port %>'
-			dist:
-				path: 'http://localhost:<%= connect.dist.options.port %>'
-			test:
-				path: 'http://localhost:<%= connect.test.options.port %>'
-
-		clean:
-			dist: ['<%= yeoman.dist %>']
-			tmp: ['<%= yeoman.tmp %>']
-			tmp_dist: ['<%= yeoman.tmp_dist %>']
-			components: ['<%= yeoman.dist %>/components']
-			templates: ['<%= yeoman.dist %>/templates']
-			spec: ['<%= yeoman.dist %>/js/spec']
-
-		coffee:
-			dist:
-				expand: true
-				cwd: '<%= yeoman.src %>/coffee/'
-				src: ['**/*.coffee']
-				dest: '<%= yeoman.tmp %>/js'
-				ext: '.js'
-
-		coffeecov:
-			options:
-				path: 'relative'
-			dist:
-				src: '<%= yeoman.src %>/coffee/app'
-				dest: '<%= yeoman.tmp %>/js/app'
-
-		compass:
-			options:
-				sassDir: '<%= yeoman.src %>/sass'
-				cssDir: '<%= yeoman.tmp %>/css'
-				imagesDir: '<%= yeoman.app %>/images'
-				javascriptsDir: '<%= yeoman.app %>/js'
-				fontsDir: './css/fonts'
-				importPath: ['<%= yeoman.app %>/components']
-				relativeAssets: true
-
-			dist:
-				options:
-					force: true
-					outputStyle: 'compressed'
-					environment: 'production'
-			server:
-				options:
-					debugInfo: true
-
-		less:
-			server:
-				options:
-					dumpLineNumbers: 'all'
-				files:
-    				'<%= yeoman.tmp %>/css/all-less.css' : '<%= yeoman.app %>/components/bootstrap/less/{bootstrap,responsive}.less'
-
-			dist:
-				options:
-					compress: true
-					yuicompress: true
-				files:
-    				'<%= yeoman.tmp %>/css/all-less.css' : '<%= yeoman.app %>/components/bootstrap/less/{bootstrap,responsive}.less'
-
-		mocha:
-			all:
-				options:
-					mocha:
-						ignoreLeaks: true
-
-					urls: ['http://localhost:<%= connect.test.options.port %>/']
-					run: false
-					reporter: 'mocha-phantom-coverage-reporter'
-					timeout: 60000
-
-		copy:
-			dist:
-				files: [
-					{ expand: true, cwd: '<%= yeoman.tmp %>/', src: ['**'], dest: '<%= yeoman.tmp_dist %>/' }
-					{ expand: true, cwd: '<%= yeoman.app %>/', src: ['**'], dest: '<%= yeoman.tmp_dist %>/' }
-				]
-
-		useminPrepare:
-			html: '<%= yeoman.tmp_dist %>/index.html'
-			options:
-				dest: '<%= yeoman.dist %>'
-
-		usemin:
-			html: ['<%= yeoman.dist %>/{,*/}*.html']
-			css: ['<%= yeoman.dist %>/css/{,*/}*.css']
-			options:
-				dirs: ['<%= yeoman.dist %>']
-
-		imagemin:
-			dist:
-				files: [{
-					expand: true,
-					cwd: '<%= yeoman.app %>/images'
-					src: '{,*/}*.{png,jpg,jpeg}'
-					dest: '<%= yeoman.dist %>/images'
-				}]
-
-		htmlmin:
-			dist:
-				# options:
-				#   removeCommentsFromCDATA: true
-				#   # https://github.com/yeoman/grunt-usemin/issues/44
-				#   collapseWhitespace: true
-				#   collapseBooleanAttributes: true
-				#   removeAttributeQuotes: true
-				#   removeRedundantAttributes: true
-				#   useShortDoctype: true
-				#   removeEmptyAttributes: true
-				#   removeOptionalTags: true
-
-				files: [{
-					expand: true,
-					cwd: '<%= yeoman.app %>',
-					src: ['*.html', 'templates/*.html'],
-					dest: '<%= yeoman.dist %>'
-				}]
-
-		uglify:
-			dist:
-				files: [{
-					expand: true,
-					cwd: '<%= yeoman.dist %>/js',
-					src: '**/*.js',
-					dest: '<%= yeoman.dist %>/js'
-				}]
-
-		requirejs:
-			compile:
-				options:
-					# no minification, is done by the min task
-					baseUrl: 'js/'
-					appDir: './<%= yeoman.tmp_dist %>/'
-					dir: './<%= yeoman.dist %>/'
-
-					wrap: true
-
-					removeCombined: true
-					keepBuildDir: true
-
-					inlineText: true
-					mainConfigFile: '<%= yeoman.tmp_dist %>/js/main.js'
-
-					optimize: "none"
-
-					modules: [
-						{ name: 'app/vendors', exclude: [] }
-						{ name: 'app/app', exclude: ['app/vendors'] }
-						{ name: 'main', exclude: ['config', 'app/app', 'app/vendors'] }
-					]
-
-	grunt.registerTask('test', [
-		'coffee:dist'
-		'coffeecov:dist'
-		'compass:server'
-		'less:server'
-		'connect:test'
-		'mocha'
-	])
-
-	grunt.registerTask('server', [
-		'coffee:dist'
-		'compass:server'
-		'less:server'
-		'connect:server'
-		'open:server'
-		'watch'
-	])
-
-	grunt.registerTask('server-test', [
-		'coffee:dist'
-		'coffeecov:dist'
-		'compass:server'
-		'less:server'
-		'connect:test'
-		'open:test'
-		'watch'
-	])
-
-	grunt.registerTask('server-dist', [
-		'connect:dist'
-		'open:dist'
-		'watch:files'
-	])
-
-	grunt.registerTask('compile', [
-		'coffee:dist'
-		'compass:server'
-		'less:dist'
-	])
-
-	grunt.registerTask('build', [
-		'clean:dist'
-		'clean:tmp'
-		'clean:tmp_dist'
-		'coffee'
-		'compass:dist'
-		'less:dist'
-		'copy:dist'
-		'connect:test'
-		'mocha'
-		'requirejs:compile'
-		'useminPrepare'
-		'imagemin'
-		'htmlmin'
-		'concat'
-		'usemin'
-		'cssmin'
-		'clean:tmp_dist'
-		'clean:components'
-		'clean:templates'
-		'clean:spec'
-		'uglify'
-	])
-
-	grunt.registerTask('default', ['build'])
+  grunt.loadNpmTasks('grunt-contrib-clean')
+  grunt.loadNpmTasks('grunt-contrib-coffee')
+  grunt.loadNpmTasks('grunt-contrib-compass')
+  grunt.loadNpmTasks('grunt-contrib-less')
+  grunt.loadNpmTasks('grunt-contrib-concat')
+  grunt.loadNpmTasks('grunt-contrib-connect')
+  grunt.loadNpmTasks('grunt-contrib-copy')
+  grunt.loadNpmTasks('grunt-contrib-cssmin')
+  grunt.loadNpmTasks('grunt-contrib-htmlmin')
+  grunt.loadNpmTasks('grunt-contrib-imagemin')
+  grunt.loadNpmTasks('grunt-contrib-uglify')
+  grunt.loadNpmTasks('grunt-contrib-requirejs')
+  grunt.loadNpmTasks('grunt-contrib-watch')
+  grunt.loadNpmTasks('grunt-open')
+  grunt.loadNpmTasks('grunt-usemin')
+  grunt.loadNpmTasks('grunt-mocha')
+  grunt.loadNpmTasks('grunt-coffeecov')
+
+  # configurable paths
+  yeomanConfig = {
+    app: 'app'
+    src: 'src'
+    dist: 'dist'
+    test: 'test'
+
+    tmp: '.tmp'
+    tmp_dist: '.tmp-dist'
+
+    server_port: 9000
+    livereload_port: 35729
+  }
+
+  try
+    yeomanConfig.app = require('./bower.json').appPath || yeomanConfig.app
+  catch e
+
+  #
+  # Grunt configuration:
+  #
+  # https://github.com/cowboy/grunt/blob/master/docs/getting_started.md
+  #
+  grunt.initConfig
+
+    # Project configuration
+    # ---------------------
+    yeoman: yeomanConfig
+    watch:
+      options:
+        interrupt: true
+
+      coffee:
+        files: ['<%= yeoman.src %>/coffee/{,**/}*.coffee']
+        tasks: ['coffee:dist']
+
+      compass:
+        files: ['<%= yeoman.src %>/sass/{,**/}*.{scss,sass}']
+        tasks: ['compass:server']
+
+      files:
+        files: [
+          '<%= yeoman.tmp %>/{,**/}*.{css,js}'
+          '<%= yeoman.app %>/{,**/}*.html'
+          '<%= yeoman.app %>/css/{,**/}*.css'
+          '<%= yeoman.app %>/js/{,**/}*.js'
+          '<%= yeoman.app %>/images/{,**/}*.{png,jpg,jpeg}'
+          '!<%= yeoman.app %>/components/**'
+        ]
+        tasks: []
+        options:
+          livereload: yeomanConfig.livereload_port
+
+    connect:
+      server:
+        options:
+          port: yeomanConfig.server_port
+          hostname: '0.0.0.0'
+          middleware: (connect)->
+            return [
+              require('connect-livereload')(port: yeomanConfig.livereload_port)
+              mountFolder(connect, yeomanConfig.tmp)
+              mountFolder(connect, yeomanConfig.app)
+            ]
+      dist:
+        options:
+          port: yeomanConfig.server_port + 1
+          hostname: '0.0.0.0'
+          middleware: (connect)->
+            return [
+              mountFolder(connect, yeomanConfig.dist)
+            ]
+
+      test:
+        options:
+          port: yeomanConfig.server_port + 2
+          hostname: '0.0.0.0'
+          middleware: (connect)->
+            return [
+              require('connect-livereload')(port: yeomanConfig.livereload_port)
+              fileHTMLRewriter
+                snippet: [
+                  "<!-- Test snippet -->",
+                  "<script src=\"components/mocha/mocha.js\"></script>",
+                  "<link rel=\"stylesheet\" href=\"components/mocha/mocha.css\">",
+                  "<script>",
+                  "    window.is_test = true;",
+                  "</script>",
+                  ""
+                ].join('\n')
+                regex: /<!-- Test snippet -->/
+              mountFolder(connect, yeomanConfig.test)
+              mountFolder(connect, yeomanConfig.tmp)
+              mountFolder(connect, yeomanConfig.app)
+            ]
+
+    open:
+      server:
+        path: 'http://localhost:<%= connect.server.options.port %>'
+      dist:
+        path: 'http://localhost:<%= connect.dist.options.port %>'
+      test:
+        path: 'http://localhost:<%= connect.test.options.port %>'
+
+    clean:
+      dist: ['<%= yeoman.dist %>']
+      tmp: ['<%= yeoman.tmp %>']
+      tmp_dist: ['<%= yeoman.tmp_dist %>']
+      components: ['<%= yeoman.dist %>/components']
+      templates: ['<%= yeoman.dist %>/templates']
+      spec: ['<%= yeoman.dist %>/js/spec']
+
+    coffee:
+      dist:
+        expand: true
+        cwd: '<%= yeoman.src %>/coffee/'
+        src: ['**/*.coffee']
+        dest: '<%= yeoman.tmp %>/js'
+        ext: '.js'
+
+    coffeecov:
+      options:
+        path: 'relative'
+      dist:
+        src: '<%= yeoman.src %>/coffee/app'
+        dest: '<%= yeoman.tmp %>/js/app'
+
+    compass:
+      options:
+        sassDir: '<%= yeoman.src %>/sass'
+        cssDir: '<%= yeoman.tmp %>/css'
+        imagesDir: '<%= yeoman.app %>/images'
+        javascriptsDir: '<%= yeoman.app %>/js'
+        fontsDir: './css/fonts'
+        importPath: ['<%= yeoman.app %>/components']
+        relativeAssets: true
+
+      dist:
+        options:
+          force: true
+          outputStyle: 'compressed'
+          environment: 'production'
+      server:
+        options:
+          debugInfo: true
+
+    less:
+      server:
+        options:
+          dumpLineNumbers: 'all'
+        files:
+            '<%= yeoman.tmp %>/css/all-less.css' : '<%= yeoman.app %>/components/bootstrap/less/{bootstrap,responsive}.less'
+
+      dist:
+        options:
+          compress: true
+          yuicompress: true
+        files:
+            '<%= yeoman.tmp %>/css/all-less.css' : '<%= yeoman.app %>/components/bootstrap/less/{bootstrap,responsive}.less'
+
+    mocha:
+      all:
+        options:
+          mocha:
+            ignoreLeaks: true
+
+          urls: ['http://localhost:<%= connect.test.options.port %>/']
+          run: false
+          reporter: 'mocha-phantom-coverage-reporter'
+          timeout: 60000
+
+    copy:
+      dist:
+        files: [
+          { expand: true, cwd: '<%= yeoman.tmp %>/', src: ['**'], dest: '<%= yeoman.tmp_dist %>/' }
+          { expand: true, cwd: '<%= yeoman.app %>/', src: ['**'], dest: '<%= yeoman.tmp_dist %>/' }
+
+        ]
+      fontDist:
+        files: [
+          { expand: true, cwd: '<%= yeoman.app %>/components/font-awesome/fonts/', src: ['**'], dest: '<%= yeoman.dist %>/components/font-awesome/fonts/' }
+          # {'<%= yeoman.dist %>': '<%= yeoman.app %>/components/font-awesome/fonts/*'}
+        ]
+
+
+    useminPrepare:
+      html: '<%= yeoman.tmp_dist %>/index.html'
+      options:
+        dest: '<%= yeoman.dist %>'
+
+    usemin:
+      html: ['<%= yeoman.dist %>/{,*/}*.html']
+      css: ['<%= yeoman.dist %>/css/{,*/}*.css']
+      options:
+        dirs: ['<%= yeoman.dist %>']
+
+    imagemin:
+      dist:
+        files: [{
+          expand: true,
+          cwd: '<%= yeoman.app %>/images'
+          src: '{,*/}*.{png,jpg,jpeg}'
+          dest: '<%= yeoman.dist %>/images'
+        }]
+
+    htmlmin:
+      dist:
+        # options:
+        #   removeCommentsFromCDATA: true
+        #   # https://github.com/yeoman/grunt-usemin/issues/44
+        #   collapseWhitespace: true
+        #   collapseBooleanAttributes: true
+        #   removeAttributeQuotes: true
+        #   removeRedundantAttributes: true
+        #   useShortDoctype: true
+        #   removeEmptyAttributes: true
+        #   removeOptionalTags: true
+
+        files: [{
+          expand: true,
+          cwd: '<%= yeoman.app %>',
+          src: ['*.html', 'templates/*.html'],
+          dest: '<%= yeoman.dist %>'
+        }]
+
+    uglify:
+      dist:
+        files: [{
+          expand: true,
+          cwd: '<%= yeoman.dist %>/js',
+          src: '**/*.js',
+          dest: '<%= yeoman.dist %>/js'
+        }]
+
+    requirejs:
+      compile:
+        options:
+          # no minification, is done by the min task
+          baseUrl: 'js/'
+          appDir: './<%= yeoman.tmp_dist %>/'
+          dir: './<%= yeoman.dist %>/'
+
+          wrap: true
+
+          removeCombined: true
+          keepBuildDir: true
+
+          inlineText: true
+          mainConfigFile: '<%= yeoman.tmp_dist %>/js/main.js'
+
+          optimize: "none"
+
+          modules: [
+            { name: 'app/vendors', exclude: [] }
+            { name: 'app/app', exclude: ['app/vendors'] }
+            { name: 'main', exclude: ['config', 'app/app', 'app/vendors'] }
+          ]
+
+  grunt.registerTask('test', [
+    'coffee:dist'
+    'coffeecov:dist'
+    'compass:server'
+    #'less:server'
+    'connect:test'
+    'mocha'
+  ])
+
+  grunt.registerTask('serve', [
+    'coffee:dist'
+    'compass:server'
+    #'less:server'
+    'connect:server'
+    'open:server'
+    'watch'
+  ])
+
+  grunt.registerTask('serve-test', [
+    'coffee:dist'
+    'coffeecov:dist'
+    'compass:server'
+    #'less:server'
+    'connect:test'
+    'open:test'
+    'watch'
+  ])
+
+  grunt.registerTask('serve-dist', [
+    'connect:dist'
+    'open:dist'
+    'watch:files'
+  ])
+
+  grunt.registerTask('compile', [
+    'coffee:dist'
+    'compass:server'
+    #'less:dist'
+  ])
+
+  grunt.registerTask('build', [
+    'clean:dist'
+    'clean:tmp'
+    'clean:tmp_dist'
+    'coffee'
+    'compass:dist'
+    #'less:dist'
+    'copy:dist'
+
+    'connect:test'
+    # 'mocha'
+    'requirejs:compile'
+    'useminPrepare'
+    'imagemin'
+    'htmlmin'
+    'concat'
+    'usemin'
+    'cssmin'
+    'clean:tmp_dist'
+    'clean:components'
+    'clean:templates'
+    'clean:spec'
+    'uglify'
+    'copy:fontDist'
+  ])
+
+  grunt.registerTask('default', ['build'])


remove tests

remove tests

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -90,6 +90,25 @@
                     exclude: [
                       'bower_components/reveal.js/plugin/leap'
                       'bower_components/reveal.js/plugin/leap/leap.js'
+                      'bower_components/reveal.js/test'
+                      'bower_components/reveal.js/test/examples'
+                      'bower_components/reveal.js/test/examples/assets'
+                      'bower_components/reveal.js/test/examples/assets/image1.png'
+                      'bower_components/reveal.js/test/examples/assets/image2.png'
+                      'bower_components/reveal.js/test/examples/barebones.html'
+                      'bower_components/reveal.js/test/examples/embedded-media.html'
+                      'bower_components/reveal.js/test/examples/math.html'
+                      'bower_components/reveal.js/test/examples/slide-backgrounds.html'
+                      'bower_components/reveal.js/test/qunit-1.12.0.css'
+                      'bower_components/reveal.js/test/qunit-1.12.0.js'
+                      'bower_components/reveal.js/test/test-markdown-element-attributes.html'
+                      'bower_components/reveal.js/test/test-markdown-element-attributes.js'
+                      'bower_components/reveal.js/test/test-markdown-slide-attributes.html'
+                      'bower_components/reveal.js/test/test-markdown-slide-attributes.js'
+                      'bower_components/reveal.js/test/test-markdown.html'
+                      'bower_components/reveal.js/test/test-markdown.js'
+                      'bower_components/reveal.js/test/test.html'
+                      'bower_components/reveal.js/test/test.js'
                     ]
                 src: [
                     'css/**'


Fixed multiline banner

Fixed multiline banner

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,10 +1,12 @@
 # global config:true, file:true, task:true, module: true

-banner_template = '/*!
+banner_template = """
+/*!
 * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= pkg.homepage ? " * " + pkg.homepage : "" %>
 * Date: <%= grunt.template.today("m/d/yyyy") %>
 * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>
-*/'
+*/
+"""

 module.exports = (grunt) ->
   grunt.initConfig


Gruntfile: 'watch' also does 'assemble'

Gruntfile: 'watch' also does 'assemble'

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -23,6 +23,9 @@
       cssmin:
         files: ['dist/css/bootstrap.css']
         tasks: ['cssmin:minify']
+      assemble:
+        files: ['pages/*.html', 'pages/examples/*']
+        tasks: ['assemble']
     cssmin:
       minify:
         expand: true


Remove duplicate task prereq

Remove duplicate task prereq

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -37,4 +37,4 @@
   grunt.registerTask 'clean', -> require('rimraf').sync('lib')
   grunt.registerTask('lint', ['coffeelint'])
   grunt.registerTask('default', ['coffee', 'lint'])
-  grunt.registerTask('test', ['default', 'coffeelint:test', 'shell:test'])
+  grunt.registerTask('test', ['default', 'shell:test'])


fix typo

fix typo

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1300,7 +1300,7 @@
 ]

 # Builds all code, including the "dist" build, but skips
-# linging and testing which can both be annoying and slow.
+# linting and testing which can both be annoying and slow.
 # jshint is here because catches hard syntax errors, etc.
 taskManager.add 'build', [
   'exec:rmIosBuild'


Fix ngtemplate base

Fix ngtemplate base

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -114,7 +114,7 @@
         src:        '<%= dirs.dist %>/app/<%= files.html %>'
         dest:       '<%= dirs.dist %>/app/js/app.templates.js'
         options:
-          base:     '<%= dirs.client %>'
+          base:     '<%= dirs.dist %>'

     mincss:
       app:


mocha timeout to 5 seconds (travis delay)

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -53,7 +53,7 @@
     mochacli:
       options:
         compilers: ['coffee:coffee-script']
-        timeout: 3000
+        timeout: 5000
         ignoreLeaks: false
         ui: 'bdd'
         reporter: 'spec'


remove extraneous logging

remove extraneous logging

--- static/coffee/renderers/glyph/circle.coffee
+++ static/coffee/renderers/glyph/circle.coffee
@@ -51,7 +51,7 @@
         if isNaN(@sx[i] + @sy[i] + @radius[i])
           continue
         ctx.beginPath()
-        ctx.arc(@sx[i], @sy[i], @radius[i], 0, 2*Math.PI*2, false)
+        ctx.arc(@sx[i], @sy[i], @radius[i], 0, 2*Math.PI, false)
         ctx.fill()

     if @do_stroke
@@ -60,7 +60,7 @@
         if isNaN(@sx[i] + @sy[i] + @radius[i])
           continue
         ctx.beginPath()
-        ctx.arc(@sx[i], @sy[i], @radius[i], 0, 2*Math.PI*2, false)
+        ctx.arc(@sx[i], @sy[i], @radius[i], 0, 2*Math.PI, false)
         ctx.stroke()

   _full_path: (ctx) ->
@@ -69,7 +69,7 @@
         continue

       ctx.beginPath()
-      ctx.arc(@sx[i], @sy[i], @radius[i], 0, 2*Math.PI*2, false)
+      ctx.arc(@sx[i], @sy[i], @radius[i], 0, 2*Math.PI, false)

       if @do_fill
         @glyph_props.fill_properties.set(ctx, @data[i])


+ before/after/beforeEach/after annotations to hook

--- src/features.coffee
+++ src/features.coffee
@@ -36,6 +36,16 @@
 #       i++
 #   return this

+has_annotation = (annotations, name, language)->
+  try name = language.localise(name)
+  regexp = new RegExp('^' + name + '$', 'i')
+  result = false
+  for k,v of annotations
+    result = regexp.test(k) and v
+    break if result
+  # console.log 'has_annotation', name, annotations, result
+  result
+
 resolveDirFromRoot = (aDir, aRootDir)->
   if isArray aDir
     aDir = for v in aDir
@@ -117,8 +127,8 @@
       , aLangName
       , /^([^\.\-].*)[\-\.]lib[s]?\.(js|coffee)$/

-  # before/after, beforeEach/afterEach
-  processHook: (aFeature, yadda, context)->
+  # before/after, beforeEach/afterEach hooks
+  processHook: (aFeature, yadda, context, lang)->
     yaddaRun = Promise.promisify(yadda.run)
     vHooks =
       before: []
@@ -128,20 +138,27 @@
     scenarios = aFeature.scenarios
     for scenario, i in scenarios
       for k, v of vHooks
-        if scenario.annotations[k]
+        if has_annotation scenario.annotations, k, lang
           v.push scenario
           scenarios[i] = undefined
-          break
+          # make one scenario could be before and after too.
+          # break
     for k,v of vHooks
       if v.length
         for scenario in v
-          container[k] scenario.title, ->
-            Promise.mapSeries scenario.steps, (step)->
-              testScope.context = context.ctx
-              yaddaRun.call yadda, step
+          unless has_annotation scenario.annotations, 'pending', lang
+            # console.log 'before def', k, scenario.title
+            ((scenario, hook)->
+              # before/after '', ->
+              container[hook] scenario.title, ->
+                # console.log 'def', hook, scenario.title
+                Promise.mapSeries scenario.steps, (step)->
+                  # console.log scenario.title, step
+                  testScope.context = context.ctx
+                  yaddaRun.call yadda, step
+            )(scenario, k)
+    # remove the scenarios for hooks
     cleanArray.call scenarios
-
-

   # run a feature file.
   runFile: (aFile, aLangName)->
@@ -172,7 +189,7 @@
       # yadda = Yadda.createInstance libraries.concat(steps), context
       yadda = Yadda.createInstance libraries, context

-      @processHook feature, yadda, context
+      @processHook feature, yadda, context, aLang
       # add the describe to each scenario
       scenarios feature.scenarios, (scenario)->
         # add the it to each step


Remove tinymce editor instances before initializing

We have several places where we need to reinit tinymce editor instances. Sometimes they are already initlialized. Instead of manually removing them in all of these places, we remove the instances before initializing it.

This should fix lot of UI issues, where the tinymce does not get initialiazed correctly. For instance if an element has expanded nested elements, but is itself collapsed.

--- app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee
+++ app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee
@@ -45,7 +45,11 @@
   #   - Editor id that should be initialized.
   #
   initEditor: (id) ->
-    textarea = $("#tinymce_#{id}")
+    editor_id = "tinymce_#{id}"
+    textarea = $("##{editor_id}")
+    editor = tinymce.get(editor_id)
+    # remove editor instance, if already initialized
+    editor.remove() if editor
     if textarea.length == 0
       Alchemy.log_error "Could not initialize TinyMCE for textarea#tinymce_#{id}!"
       return


update wiki page to page

update wiki page to page

fixes COMMS-1263

Test Plan:
- tests pass
- words changed from wiki page to page

Change-Id: I180894cc49ea2c8cec15415f3405408a571a371d
Reviewed-on: https://gerrit.instructure.com/155462
Reviewed-by: Venk Natarajan <f9eab59b292314637a3c917fea4742741e36a19b@instructure.com>
Tested-by: Jenkins
Product-Review: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
QA-Review: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>

--- app/coffeescripts/views/wiki/WikiPageDeleteDialog.coffee
+++ app/coffeescripts/views/wiki/WikiPageDeleteDialog.coffee
@@ -32,7 +32,7 @@
   class WikiPageDeleteDialog extends DialogFormView
     setViewProperties: false
     wrapperTemplate: -> '

'
-    template: -> I18n.t 'delete_confirmation', 'Are you sure you wish to delete this wiki page?'
+    template: -> I18n.t 'delete_confirmation', 'Are you sure you wish to delete this page?'

     @optionProperty 'wiki_pages_path'
     @optionProperty 'focusOnCancel'


fix failing spec

fix failing spec

--- spec/app/text-mate-grammar-spec.coffee
+++ spec/app/text-mate-grammar-spec.coffee
@@ -251,7 +251,7 @@
       it "correctly parses a method. (regression)", ->
         grammar = TextMateBundle.grammarForFilePath("hello.c")
         {tokens, ruleStack} = grammar.tokenizeLine("if(1){m()}")
-        expect(tokens[5]).toEqual value: "m", scopes: ["source.objc++", "meta.block.c", "meta.function-call.c", "support.function.any-method.c"]
+        expect(tokens[5]).toEqual value: "m", scopes: ["source.c", "meta.block.c", "meta.function-call.c", "support.function.any-method.c"]

       it "correctly parses nested blocks. (regression)", ->
         grammar = TextMateBundle.grammarForFilePath("hello.c")


Fix compensation amount calculation.

Fix compensation amount calculation.

--- kirppu/static_src/js/checkout/vendorcompensation.coffee
+++ kirppu/static_src/js/checkout/vendorcompensation.coffee
@@ -77,7 +77,7 @@
     table = Templates.render("item_report_table",
       caption: "Compensated Items"
       items: items
-      sum: _.reduce(@compensableItems, ((acc, item) -> acc + item.price), 0)
+      sum: _.reduce(items, ((acc, item) -> acc + item.price), 0)
       topSum: true
     )
     @itemDiv.empty().append(table)


elektron: filter offline machines in kd list

--- elektron/src/trayicon.coffee
+++ elektron/src/trayicon.coffee
@@ -211,6 +211,8 @@
       return  if @setFailed err

       menu = []
+
+      result = result.filter (machine) -> machine.machineStatus isnt 1

       result.forEach (machine) =>
         # { team, teamUrl } = parseTeam machine.teams


Fix bug, fix https://github.com/poooi/poi/issues/296

--- views/components/ship/parts/panebody.cjsx
+++ views/components/ship/parts/panebody.cjsx
@@ -90,7 +90,7 @@
     continue if shipId == -1
     ship = _ships[shipId]
     for itemId, slotId in ship.api_slot
-      continue if itemId == -1
+      continue unless itemId != -1 && _slotitems[itemId]?
       item = _slotitems[itemId]
       # Basic tyku
       if item.api_type[3] in [6, 7, 8]
@@ -120,7 +120,7 @@
     ship = _ships[shipId]
     shipSaku += ship.api_sakuteki[0]
     for itemId, slotId in ship.api_slot
-      continue if itemId == -1
+      continue unless itemId != -1 && _slotitems[itemId]?
       item = _slotitems[itemId]
       switch item.api_type[3]
         when 9
@@ -154,7 +154,7 @@
     ship = _ships[shipId]
     shipPureSaku = ship.api_sakuteki[0]
     for itemId, slotId in ship.api_slot
-      continue if itemId == -1
+      continue unless itemId != -1 && _slotitems[itemId]?
       item = _slotitems[itemId]
       shipPureSaku -= item.api_saku
       switch item.api_type[3]


fix #117: add get_data to graphite

fix #117: add get_data to graphite

--- src/graphite.coffee
+++ src/graphite.coffee
@@ -148,6 +148,9 @@
           Q.reject reason
         promise

+    fn 'get_data', 'Fetches Graphite metric data', (args...) ->
+      @value graphite.get_data graphite.args_to_params {args, default_options: @options()}
+
     context_vars: -> dsl.define_functions {}, function_names

     init: ->


Tell you how to add the role if you're missing it.

--- src/greeting.coffee
+++ src/greeting.coffee
@@ -58,6 +58,7 @@
       true
     else
       msg.reply "I can't do that, you're not a #{GREETING_ROLE}!"
+      msg.reply "Ask your admin to run '#{robot.name}: #{msg.message.user.name} has #{GREETING_ROLE} role' so you can."
       false

   robot.respond /greet (\S+) with (.+)/i, (msg) ->


Refactor

Refactor

--- datastores.coffee
+++ datastores.coffee
@@ -3,18 +3,17 @@

 add_datastores = ({apps, data_dir}, c) ->
+  fs.mkdirSync(data_dir) if not fs.existsSync(data_dir)
+  _add_leveldb apps, data_dir
+  c null

-  # dirs
+
+_add_leveldb = (apps, data_dir) ->
   leveldb_dir = "#{data_dir}/leveldb"
-  fs.mkdirSync(data_dir) if not fs.existsSync(data_dir)
   fs.mkdirSync(leveldb_dir) if not fs.existsSync(leveldb_dir)
-
-  # db
   leveldb_client = new LevelDBClient leveldb_dir
   for app in apps
     app.db = new LevelDBWrapper leveldb_client, {prefix: "#{app.slug}:"}

-  c null
-

 module.exports = {add_datastores}


Flesh out register specs

Flesh out register specs

Now it covers:
  a
  +
  *
  _

I have a pending spec for %, since I'm not sure how to setup the editor
variable to behave correctly.

--- spec/prefixes-spec.coffee
+++ spec/prefixes-spec.coffee
@@ -50,6 +50,17 @@
         expect(editor.getText()).toBe 'three'

   describe "Register", ->
+    describe "the a register", ->
+      it "saves a value for future reading", ->
+        vimState.setRegister('a', text: 'new content')
+        expect(vimState.getRegister("a").text).toEqual 'new content'
+
+      it "overwrites a value previously in the register", ->
+        vimState.setRegister('a', text: 'content')
+        vimState.setRegister('a', text: 'new content')
+        expect(vimState.getRegister("a").text).toEqual 'new content'
+
+
     describe "the * register", ->
       describe "reading", ->
         it "is the same the system clipboard", ->
@@ -62,3 +73,40 @@

         it "overwrites the contents of the system clipboard", ->
           expect(atom.clipboard.read()).toEqual 'new content'
+
+    # FIXME: once linux support comes out, this needs to read from
+    # the correct system clipboard. For now it behaves just like the * register
+    describe "the + register", ->
+      describe "reading", ->
+        it "is the same the system clipboard", ->
+          expect(vimState.getRegister('*').text).toEqual 'initial clipboard content'
+          expect(vimState.getRegister('*').type).toEqual 'character'
+
+      describe "writing", ->
+        beforeEach ->
+          vimState.setRegister('*', text: 'new content')
+
+        it "overwrites the contents of the system clipboard", ->
+          expect(atom.clipboard.read()).toEqual 'new content'
+
+    describe "the _ register", ->
+      describe "reading", ->
+        it "is always the empty string", ->
+          expect(vimState.getRegister("_").text).toEqual ''
+
+      describe "writing", ->
+        it "throws away anything written to it", ->
+          vimState.setRegister('_', text: 'new content')
+          expect(vimState.getRegister("_").text).toEqual ''
+
+    # FIXME: @editor.getUri() is returning undefined. Not sure how to setup
+    # the @editor instance in test most.
+    xdescribe "the % register", ->
+      describe "reading", ->
+        it "returns the filename of the current editor", ->
+          expect(vimState.getRegister('%').text).toEqual 'fjdka'
+
+      describe "writing", ->
+        it "throws away anything written to it", ->
+          vimState.setRegister('%', "new content")
+          expect(vimState.getRegister('%').text).toEqual 'fjdka'


Add test for nested settings

Add test for nested settings

--- spec/settings-spec.coffee
+++ spec/settings-spec.coffee
@@ -1,13 +1,23 @@
-Settings = require '../lib/settings.coffee'
+Settings = require '../lib/settings'

 describe "Settings", ->

   describe ".load(settings)", ->
-    it "Loads the settings provided", ->
+    it "Loads the settings provided if they are flat", ->
       settings = new Settings()
       settings.load({"foo.bar.baz": 42})

       expect(atom.config.get("foo.bar.baz")).toBe 42
+
+    it "Loads the settings provided if they are an object", ->
+      settings = new Settings()
+      expect(atom.config.get('foo.bar.baz')).toBe undefined
+      settings.load({
+        foo:
+          bar:
+            baz: 42
+      })
+      expect(atom.config.get('foo.bar.baz')).toBe 42

   describe ".load(settings) with a 'scope' option", ->
     it "Loads the settings for the scope", ->


Use `atom.activatePackage` directly in specs

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -12,13 +12,13 @@
     window.rootView = new RootView
     rootView.open('sample.js')

-    packageWithSnippets = window.loadPackage("package-with-snippets")
+    packageWithSnippets = atom.loadPackage("package-with-snippets")
     spyOn(atom, "getLoadedPackages").andCallFake ->
       textMatePackages = window.textMatePackages.filter (pack) -> /package-with-a-cson-grammar|test|textmate-package|javascript/.test(pack.name)
       textMatePackages.concat([packageWithSnippets])

     spyOn(require("snippets/lib/snippets"), 'loadAll')
-    window.loadPackage("snippets")
+    atom.activatePackage("snippets")

     editor = rootView.getActiveView()
     editSession = rootView.getActivePaneItem()


Redo search on filter delete

Redo search on filter delete

--- web/site/search.coffee
+++ web/site/search.coffee
@@ -560,7 +560,7 @@
     $scope.$on('$routeUpdate',() ->
       $scope.query = $location.search().query
       $scope.searchBoxQuery = $scope.query
-      # $scope.search()
+      $scope.search()
     )

@@ -591,6 +591,7 @@
       rmindex = $scope.selectedMetrics.remove(metric)
       $scope.allMetrics.push(metric.metric)
       $scope.allMetrics.sort((a,b) -> a.id - b.id)
+      $scope.search()
       return

     # Code for the initial load


test ctrl-c resetting command mode

test ctrl-c resetting command mode

--- spec/vim-mode-spec.coffee
+++ spec/vim-mode-spec.coffee
@@ -83,6 +83,14 @@
         expect(vimState.opStack.length).toBe 1

         keydown('escape', element: editor[0])
+        expect(vimState.opStack.length).toBe 0
+
+    describe "the ctrl-c keybinding", ->
+      it "clears the operator stack", ->
+        keydown('d', element: editor[0])
+        expect(vimState.opStack.length).toBe 1
+
+        editor.trigger keydownEvent('ctrl-c')
         expect(vimState.opStack.length).toBe 0

     describe "the i keybinding", ->


Bugging the user to upgrade: /exams

Bugging the user to upgrade: /exams

--- apis/v0.coffee
+++ apis/v0.coffee
@@ -6,112 +6,123 @@

 studentsRouter = etabits.express.Router()
 studentsRouter.use (req, res, next)->
-	etabits.svu.Student.restoreFromToken req.query.token, (err, stud)->
-		return next(err) if err
+  etabits.svu.Student.restoreFromToken req.query.token, (err, stud)->
+    return next(err) if err

-		#console.log(stud)
-		req.studentObject = stud
-		#console.log req.studentObject.doc
-		req.studentObject.doc.lastActivity = new Date()
-		req.on 'end', ()->
-			++req.studentObject.doc.actionsCounter
-			req.studentObject.doc.save()
-		next()
+    #console.log(stud)
+    req.studentObject = stud
+    #console.log req.studentObject.doc
+    req.studentObject.doc.lastActivity = new Date()
+    req.on 'end', ()->
+      ++req.studentObject.doc.actionsCounter
+      req.studentObject.doc.save()
+    next()

 dataFixers = {
-	courses: (c)->
-		{
-			name: c.Course
+  courses: (c)->
+    {
+      name: c.Course

-		}
+    }
 }

 studentsRouter.get '/explore/classes', (req, res)->
-	opts = {
-		pid: parseInt(req.query.pid)
-		tid: parseInt(req.query.tid)
-		cid: parseInt(req.query.cid)
-	}
+  opts = {
+    pid: parseInt(req.query.pid)
+    tid: parseInt(req.query.tid)
+    cid: parseInt(req.query.cid)
+  }

-	req.studentObject.get 'explore_classes', opts, (err, data)->
-		return next(err) if err
+  req.studentObject.get 'explore_classes', opts, (err, data)->
+    return next(err) if err

-		debug("Got #{data.length} data array for #{req.studentObject.studentId}/explore_classes")
+    debug("Got #{data.length} data array for #{req.studentObject.studentId}/explore_classes")

-		res.json({success: true, data: data})
+    res.json({success: true, data: data})

 studentsRouter.get '/explore/courses', (req, res)->
-	req.studentObject.get 'progtermcourse', {pid: parseInt(req.query.pid)}, (err, data)->
-		return next(err) if err
+  req.studentObject.get 'progtermcourse', {pid: parseInt(req.query.pid)}, (err, data)->
+    return next(err) if err

-		debug("Got #{data.courses.length} data array for #{req.studentObject.studentId}/courses")
+    debug("Got #{data.courses.length} data array for #{req.studentObject.studentId}/courses")

-		res.json({success: true, data: data.courses})
+    res.json({success: true, data: data.courses})

 studentsRouter.get '/explore/progterm', (req, res)->
-	req.studentObject.get 'progtermcourse', {}, (err, data)->
-		return next(err) if err
+  req.studentObject.get 'progtermcourse', {}, (err, data)->
+    return next(err) if err

-		debug("Got #{data.length} data array for #{req.studentObject.studentId}/#{req.params.section}")
-		#data = data.map(dataFixers[req.params.section])
-		finalData = []
-		for t in data.terms
-			#console.log t
-			continue if not t.code.match(/1(?:4|5)$/)
-			for p in data.programmes
-				continue if -1==[8, 7].indexOf(p.id)
-				finalData.push {
-					program: p
-					term: t
-				}
-		res.json({success: true, data: finalData})
+    debug("Got #{data.length} data array for #{req.studentObject.studentId}/#{req.params.section}")
+    #data = data.map(dataFixers[req.params.section])
+    finalData = []
+    for t in data.terms
+      #console.log t
+      continue if not t.code.match(/1(?:4|5)$/)
+      for p in data.programmes
+        continue if -1==[8, 7].indexOf(p.id)
+        finalData.push {
+          program: p
+          term: t
+        }
+    res.json({success: true, data: finalData})

 studentsRouter.get '/:section(exams|results|classes)', (req, res, next)->
-	#console.log req.params
-	req.studentObject.get req.params.section, {}, (err, data)->
-		return next(err) if err
-		debug("Got #{data.length} data array for #{req.studentObject.studentId}/#{req.params.section}")
-		#data = data.map(dataFixers[req.params.section])
-		res.json({success: true, data: data})
+  #console.log req.params
+  req.studentObject.get req.params.section, {}, (err, data)->
+    return next(err) if err
+    debug("Got #{data.length} data array for #{req.studentObject.studentId}/#{req.params.section}")
+    #data = data.map(dataFixers[req.params.section])
+    if 'exams' == req.params.section
+
+      data.unshift {
+        "course": {
+          "code": "UPD513"
+        },
+        "date": "2015-02-01T23:59:59.999Z",
+        "telecenter": {
+          "name": "You are using an old version. UPGRADE NOW!\n Use the link in the login screen, or this link: bit.ly/SVUHelper"
+        }
+      }
+    res.json({success: true, data: data})

 v0 = etabits.express.Router()
 v0.post '/login', etabits.jsonMiddleware, (req, res, next)->
-	context = {}
-	context.deviceType = 'a'
-	context.description = "Android App Old (<=v274)"
-	etabits.svu.Student.login req.body.stud_id, req.body.password, context, (err, data)->
-		console.error(err) if err
-
-		return res.send({success: false, errorMessage: 'Bad Login'}) if err
-		res.send {
-			success: true
-			token: data.doc.sessionToken
-			classes: data.classes
-		}
+  context = {}
+  context.deviceType = 'a'
+  context.description = "Android App Old (<=v274)"
+  etabits.svu.Student.login req.body.stud_id, req.body.password, context, (err, data)->
+    console.error(err) if err
+
+    return res.send({success: false, errorMessage: 'Bad Login'}) if err
+    res.send {
+      success: true
+      token: data.doc.sessionToken
+      classes: data.classes
+    }

 cfg = {}
 fs.readFile "cfg.json", 'utf-8', (err, data)->
-	try
-		cfg = JSON.parse(data)
-		#console.log cfg
-	catch e
-		console.log e
+  try
+    cfg = JSON.parse(data)
+    #console.log cfg
+  catch e
+    console.log e
 v0.get '/hello', (req, res)->
-	message = cfg.messageBase
-
-	if parseInt(req.query.versionCode) < cfg.minVersionCode
-		message += cfg.messageUpdate
-	else
-		message += cfg.messageOther
-	###
-	message += '

-	Important Note:
When the SVU website is DOWN (NOT AVAILABLE, has errors, etc.), the application will stop working too (of course).'
-	###
-	res.send {
-		success: true
-		newsHTML: message
-	}
+  message = cfg.messageBase
+
+  if parseInt(req.query.versionCode) < cfg.minVersionCode
+    message += cfg.messageUpdate
+  else
+    message += cfg.messageOther
+  ###
+  message += '

+  Important Note:
When the SVU website is DOWN (NOT AVAILABLE, has errors, etc.), the application will stop working too (of course).'
+  ###
+  res.send {
+    success: true
+    newsHTML: message
+  }

 v0.use '/student', studentsRouter


Enable defineProperty

Enable defineProperty

--- bokehjs/src/coffee/core/has_props.coffee
+++ bokehjs/src/coffee/core/has_props.coffee
@@ -28,13 +28,13 @@
         # XXX: should be an error, but Backbone.Model.url must be removed first
         console.log("attempted to redefine attribute '#{this.name}.#{name}'")

-      #Object.defineProperty(this.prototype, name, {
-      #  get: ()      -> this.get(name)
-      #  set: (value) -> this.set(name, value)
-      #}, {
-      #  configurable: false
-      #  enumerable: true
-      #})
+      Object.defineProperty(this.prototype, name, {
+        get: ()      -> this.get(name)
+        set: (value) -> this.set(name, value)
+      }, {
+        configurable: false
+        enumerable: true
+      })

       props = _.clone(this.prototype.props)
       props[name] = prop


Throw an exception when attempting to redefine an attribute

--- bokehjs/src/coffee/core/has_props.coffee
+++ bokehjs/src/coffee/core/has_props.coffee
@@ -17,9 +17,8 @@
         if this.prototype.props[name]?
           throw new Error("attempted to redefine property '#{this.name}.#{name}'")

-        if this.prototype[name]?
-          # XXX: should be an error, but Backbone.Model.url must be removed first
-          console.log("attempted to redefine attribute '#{this.name}.#{name}'")
+        if this.prototype[name]? and name != "url" # TODO: remove when we drop backbone
+          throw new Error("attempted to redefine attribute '#{this.name}.#{name}'")

         Object.defineProperty(this.prototype, name, {
           get: ()      -> this.get(name)


show error on initialization failure

show error on initialization failure

--- app/app.coffee
+++ app/app.coffee
@@ -218,6 +218,15 @@
       Notebook.run first_cell
     }

+# TODO style, copy
+InitializationFailureModal = React.createClass
+  render: ->
+    footer = React.DOM.button {onClick: @props.dismiss}, 'OK'
+    exports.ModalComponent {title: 'Lead Failed to Start Properly', footer},
+      React.DOM.p {},
+        "An error occurred while starting lead. More details may be available in the browser's console. Some features might not be available. Try reloading this page."
+      React.DOM.p {style: marginTop: '1em'}, "Message: ", @props.error
+
 BuilderAppComponent = React.createClass
   displayName: 'BuilderAppComponent'
   render: ->
@@ -300,9 +309,11 @@

   # TODO handle errors, timeouts
   initializationPromise = Modules.init_modules(module_names)
+  initializationPromise.fail (e) ->
+    console.error 'Failure initializing modules', e
+    exports.pushModal handler: InitializationFailureModal, props: error: e

   React.renderComponent routesComponent, target
-  initializationPromise.done()

 encodeNotebookValue = (value) ->


improve weeks publish preparation

improve weeks publish preparation

--- imports/ui/components/shiftsHeader/server/weeks.coffee
+++ imports/ui/components/shiftsHeader/server/weeks.coffee
@@ -17,11 +17,13 @@
 		start: $gte: moment((v1 + v2) / 2 - Math.abs(v1 - v2) / 2).isoWeekday(1).format('YYYYDDDD')

 	Weeks.find(filter || {}).observeChanges
-		added: (id, fields) =>
-			fields.existing = true
-
-			@added "testdata", id, fields
-		changed: (id, fields) ->
-
-			@changed "testdata", id, fields
-		removed: (id) -> @removed '', id
+		added: (id, doc) =>
+			@added 'shiftsHeader.weeks', id,
+				_id: id
+				date: doc.date
+		changed: (id, doc) ->
+			@changed 'shiftsHeader.weeks', id,
+				_id: id
+				date: doc.date
+		removed: (id) ->
+			@removed 'shiftsHeader.weeks', id


fix incorrect parameter passing to setBindOptions

--- app/controllers/state.coffee
+++ app/controllers/state.coffee
@@ -76,9 +76,9 @@
         console.log 'NT: ', new_tool

         if _.isUndefined tool.bind_options.params
-          new_tool.setBindOptions tool.bind_options.source, tool.bind_options.type, tool.bind_options.process
+          new_tool.setBindOptions tool.bind_options.source, tool.bind_options.process
         else
-          new_tool.setBindOptions tool.bind_options.source, tool.bind_options.type, tool.bind_options.params
+          new_tool.setBindOptions tool.bind_options.source, tool.bind_options.params

         new_tool.receiveData tool.data


create action can contain complete story

--- client/lib/revision.coffee
+++ client/lib/revision.coffee
@@ -15,6 +15,7 @@
       when 'create'
         if journalEntry.item.title?
           revTitle = journalEntry.item.title
+          revStory = journalEntry.item.story || []
       when 'add'
         if (afterIndex = revStoryIds.indexOf journalEntry.after) != -1
           revStory.splice(afterIndex+1,0,journalEntry.item)


Revert "Get rid of blatantly awful commands like 'annoy me'"

This reverts commit 872c968d8c601cd53662a7b5bb636a8fb2f37756.

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -8,50 +8,71 @@
 #
 #   These are from the scripting documentation: https://github.com/github/hubot/blob/master/docs/scripting.md

-# module.exports = (robot) ->
+module.exports = (robot) ->

   robot.hear /badger/i, (res) ->
     res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"

-#   robot.respond /open the (.*) doors/i, (res) ->
-#     doorType = res.match[1]
-#     if doorType is "pod bay"
-#       res.reply "I'm afraid I can't let you do that."
-#     else
-#       res.reply "Opening #{doorType} doors"
+  robot.respond /open the (.*) doors/i, (res) ->
+    doorType = res.match[1]
+    if doorType is "pod bay"
+      res.reply "I'm afraid I can't let you do that."
+    else
+      res.reply "Opening #{doorType} doors"

   robot.hear /I like pie/i, (res) ->
     res.emote "makes a freshly baked pie"

-#  lulz = ['lol', 'rofl', 'lmao']
+  lulz = ['lol', 'rofl', 'lmao']

-#   robot.respond /lulz/i, (res) ->
-#     res.send res.random lulz
+  robot.respond /lulz/i, (res) ->
+    res.send res.random lulz

-#   robot.topic (res) ->
-#     res.send "#{res.message.text}? That's a Paddlin'"
+  robot.topic (res) ->
+    res.send "#{res.message.text}? That's a Paddlin'"

-#   enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
-#   leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
+  enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
+  leaveReplies = ['Are you still there?', 'Target lost', 'Searching']

-#   robot.enter (res) ->
-#     res.send res.random enterReplies
-#   robot.leave (res) ->
-#     res.send res.random leaveReplies
+  robot.enter (res) ->
+    res.send res.random enterReplies
+  robot.leave (res) ->
+    res.send res.random leaveReplies

-#   answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
+  answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING

-#   robot.respond /what is the answer to the ultimate question of life/, (res) ->
-#     unless answer?
-#       res.send "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
-#       return
-#     res.send "#{answer}, but what is the question?"
+  robot.respond /what is the answer to the ultimate question of life/, (res) ->
+    unless answer?
+      res.send "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
+      return
+    res.send "#{answer}, but what is the question?"

-#   robot.respond /you are a little slow/, (res) ->
-#     setTimeout () ->
-#       res.send "Who you calling 'slow'?"
-#     , 60 * 1000
+  robot.respond /you are a little slow/, (res) ->
+    setTimeout () ->
+      res.send "Who you calling 'slow'?"
+    , 60 * 1000
+
+  annoyIntervalId = null
+
+  robot.respond /annoy me/, (res) ->
+    if annoyIntervalId
+      res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
+      return
+
+    res.send "Hey, want to hear the most annoying sound in the world?"
+    annoyIntervalId = setInterval () ->
+      res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
+    , 1000
+
+  robot.respond /unannoy me/, (res) ->
+    if annoyIntervalId
+      res.send "GUYS, GUYS, GUYS!"
+      clearInterval(annoyIntervalId)
+      annoyIntervalId = null
+    else
+      res.send "Not annoying you right now, am I?"
+

   robot.router.post '/hubot/chatsecrets/:room', (req, res) ->
     room   = req.params.room


Wait until HAVE_FUTURE_DATA to remove loading UI

--- scripts/players.coffee
+++ scripts/players.coffee
@@ -43,7 +43,7 @@
     updateMedia()

     media.addEventListener(event, (e) ->
-        if media.readyState > 0
+        if media.readyState >= 3 # HAVE_FUTURE_DATA (we can play now)
             updateMedia()
     , false) for event in ['progress', 'timeupdate', 'pause', 'playing', 'seeked', 'ended']


More forceful markdown enabling

More forceful markdown enabling

--- scripts/pudding.coffee
+++ scripts/pudding.coffee
@@ -196,6 +196,8 @@
         fields: fields
         mrkdwn_in: ['fields', 'pretext', 'title']
       username: robot.name
+      mrkdwn_in: ['fields', 'pretext', 'title']
+      mrkdwn: true

     robot.emit 'slack.attachment', payload


Handle new response format from the posts endpoint

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -172,7 +172,7 @@
   reportFeedClick: (postId, callback) ->
     @ajaxPost("#{@scheme}://posts.gocarrot.com/#{postId}/clicks", {clicking_user_id: @udid, sig: "s"},
       (jqXHR) =>
-        response = $.parseJSON(jqXHR.responseText);
+        response = $.parseJSON(jqXHR.responseText).response;
         if response.cascade && response.cascade.method == "sendRequest"
           @sendRequest(response.cascade.arguments.request_id, response.cascade.arguments.opts)
         callback(response) if callback
@@ -213,7 +213,7 @@
   acceptRequest: (requestId, callback) ->
     @ajaxPost("#{@scheme}://posts.gocarrot.com/requests/#{requestId}/clicks", {clicking_user_id: @udid, sig: "s"},
       (jqXHR) =>
-        response = $.parseJSON(jqXHR.responseText);
+        response = $.parseJSON(jqXHR.responseText).response;
         if response.cascade && response.cascade.method == "sendRequest"
           @sendRequest(response.cascade.arguments.request_id, response.cascade.arguments.opts)
         callback(response) if callback


Pass the responseJSON to callbacks too...

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -59,7 +59,7 @@
   ajaxPost: (url, data, callback) ->
     if @request
         @request.post(url, {'form':data}, (error, response, body) =>
-          callback(response.statusCode) if callback
+          callback(response.statusCode, response.responseJSON) if callback
           response.end
         )
     else


Now that display_name is available from API response, remove AJAX call that retrieves it.

--- app/partials/owned-card.cjsx
+++ app/partials/owned-card.cjsx
@@ -59,9 +59,7 @@

{@props.resource.display_name}

           {if !@props.skipOwner
-            <PromiseRenderer promise={@props.resource.get('owner')}>{ (owner) ->
-
{owner?.display_name ? 'LOADING'}

-            }</PromiseRenderer>}
+
{@props.resource.links.owner.display_name}
}
           {
{@props.resource.description}
 if @props.resource.description?}
           {
 Private
 if @props.resource.private}
           <button type="button" tabIndex="-1" className="standard-button card-button"><Translate content={"#{@props.translationObjectName}.button"} /></button>


Client: Activity: Updated updateMessage method to trim input value

--- client/activity/lib/components/chatlistitem/index.coffee
+++ client/activity/lib/components/chatlistitem/index.coffee
@@ -250,7 +250,7 @@

     return @deletePostButtonHandler()  if @state.isDeleting

-    return @setState isDeleting: yes  unless @state.editInputValue
+    return @setState isDeleting: yes  unless @state.editInputValue.trim()

     name  = @props.channelName
     value = @state.editInputValue


StackTemplateListController: Make fetcherMethod optional

--- client/app/lib/stacks/stacktemplatelistcontroller.coffee
+++ client/app/lib/stacks/stacktemplatelistcontroller.coffee
@@ -23,7 +23,7 @@
     options.scrollView        = no

     options.noItemFoundText  ?= 'You currently have no stack template'
-    options.fetcherMethod     = (query, options, callback) =>
+    options.fetcherMethod    ?= (query, options, callback) =>

       queue = [
         (next) ->


Cleaning requests parse method bug fix

Cleaning requests parse method bug fix

--- SingularityUI/app/collections/RequestsCleaning.coffee
+++ SingularityUI/app/collections/RequestsCleaning.coffee
@@ -7,7 +7,7 @@
     url: "#{ env.SINGULARITY_BASE }/#{ constants.api_base }/requests/queued/cleanup"

     parse: (requests) ->
-        _.each requests, (requestIdString) =>
+        _.each requests, (requestIdString, i) ->
             request = {}
             request.id = requestIdString
             requests[i] = request


Workspace: Added getValues method to get multiple editor value in EditorPane.

--- client/Workspace/panes/editorpane.coffee
+++ client/Workspace/panes/editorpane.coffee
@@ -51,8 +51,17 @@
       @tabView.addPane pane
       @editors[name] = editor  if name

+  # use this for single editor
   getValue: ->
     return  @ace.editor.getSession().getValue()
+
+  # use this for multiple editor tabs
+  getValues: ->
+    data = {}
+    for name, editorInstance of @editors
+      data[name] = editorInstance.editor.getValue()
+
+    return data

   pistachio: ->
     single   = "{{> @ace}}"


Don't render tables when objects are empty.

--- community/browser/app/scripts/directives/neoTable.coffee
+++ community/browser/app/scripts/directives/neoTable.coffee
@@ -41,6 +41,7 @@
           unbind()

         json2html = (obj) ->
+          return '' unless Object.keys(obj).length
           html  = "

"
           html += "
#{k}	#{cell2html(v)}
" for own k, v of obj
           html += "

"


adds more unit tests

adds more unit tests

--- js/test/search/SearchKeySerializer-test.coffee
+++ js/test/search/SearchKeySerializer-test.coffee
@@ -122,5 +122,14 @@
         key = createUint8Array({ size : 1000, generator: (i) -> i % 256 })
         testKey( key )

+      it 'should encrypt and deserialize a large array containing high byte value', ->
+        key = createUint8Array({ size : 1000, generator: (i) -> 255 })
+        testKey( key )

+      it 'should encrypt and deserialize a large array containing low byte value', ->
+        key = createUint8Array({ size : 1000, generator: (i) -> 0 })
+        testKey( key )

+      it 'should encrypt and deserialize a large array containing random byte values', ->
+        key = createUint8Array({ size : 1000, generator: (i) -> (Math.random() * (256 - 0) + 0) })
+        testKey( key )


defaultVhostFiles fix

defaultVhostFiles fix

--- config/kite.sharedHosting.vagrant.coffee
+++ config/kite.sharedHosting.vagrant.coffee
@@ -16,7 +16,7 @@
   usersPath             : '/Users/'
   vhostDir              : 'Sites'
   suspendDir            : '/var/www/suspended_vhosts/'
-  defaultVhostFiles     : nodePath.join cwd, '..', 'sharedHosting', 'defaultVhostFiles'
+  defaultVhostFiles     : nodePath.join cwd, 'kites', 'sharedHosting', 'defaultVhostFiles'
   freeUsersGroup        : 'freeusers'
   liteSpeedUser         : 'lsws'
   defaultDomain         : 'koding.com'


Prevent more than one scrape from occurring at a time

--- gameserver/gameserver.coffee
+++ gameserver/gameserver.coffee
@@ -160,12 +160,13 @@
     constructor: ->
         @client = new pg.Client('tcp://postgres:pgpass1@localhost/cgf')
         @client.connect()
+        @scrapes = []

     get: (profile_url, cb) =>
         this._getFromDB(profile_url, (err, profile) =>
             return cb(err) if err
             return cb(null, profile) if profile isnt null
-            this._scrape(profile_url, (err, profile) =>
+            this._queueScrape(profile_url, (err, profile) =>
                 return cb(err) if err
                 this._putInDB(profile_url, profile)
                 cb(null, profile)
@@ -194,6 +195,17 @@
             text: 'INSERT INTO profiles (url, region, name, league) VALUES ($1, $2, $3, $4)',
             values: [profile_url, profile.region, profile.name, profile.league]
         })
+
+    _queueScrape: (profile_url, cb) =>
+        @scrapes.push({profile_url: profile_url, cb: cb})
+        return if @scrapes.length > 1
+        this._scrape(profile_url, @_queuedScrapeCB)
+
+    _queuedScrapeCB: (err, data) =>
+        s = @scrapes.shift() # this should have just finished
+        s.cb(err, data)
+        if @scrapes.length > 0 # still more to go
+            this._scrape(@scrapes[0].profile_url, @_queuedScrapeCB)

     _scrape: (profile_url, cb) =>
         error = 'Bad profile URL or an error occurred, try again.'


Don't read files starting with `_`. Closes #215.

--- src/brunch.coffee
+++ src/brunch.coffee
@@ -38,16 +38,22 @@
   directories = ['app', 'vendor'].map (dir) -> sysPath.join rootPath, dir

   fileList = new fs_utils.SourceFileList
-
+
   helpers.loadPlugins config, (error, plugins) ->
     return logger.error error if error?
+    ignored = /^_/
     start = null
     addToFileList = (isPluginHelper) -> (path) ->
       start = Date.now()
       logger.log 'debug', "File '#{path}' was changed"
+      return fileList.resetTimer() if ignored.test sysPath.basename path
       compiler = plugins.filter(isCompilerFor path)[0]
       return unless compiler
       fileList.add {path, compiler, isPluginHelper}
+
+    removeFromFileList = (path) ->
+      return fileList.resetTimer() if ignored.test sysPath.basename path
+      fileList.remove path

     plugins.forEach (plugin) ->
       return unless plugin.include?
@@ -61,11 +67,7 @@
     watcher = (new fs_utils.FileWatcher)
       .add(directories)
       .on('change', addToFileList no)
-      .on('remove', (path) -> fileList.remove path)
-    assetWatcher = (new fs_utils.FileWatcher)
-      .add(assetDirectories)
-      .on('change', fileList.resetTimer)
-      .on('remove', fileList.resetTimer)
+      .on('remove', removeFromFileList)
     fileList.on 'resetTimer', -> writer.write fileList
     writer.on 'write', (result) ->
       assetPath = sysPath.join rootPath, 'app', 'assets'
@@ -73,9 +75,7 @@
         logger.error "Asset compilation failed: #{error}" if error?
         logger.info "compiled."
         logger.log 'debug', "compilation time: #{Date.now() - start}ms"
-        unless persistent
-          watcher.close()
-          assetWatcher.close()
+        watcher.close() unless persistent
         callback null, result
     watcher


Fix: create separate request for each action in multiAction

--- src/client.coffee
+++ src/client.coffee
@@ -378,11 +378,11 @@
 			cb = retry
 			retry = 0

-		req =
-			regionAction: []
-
 		result = []
 		async.each Object.keys(multiActions), (serverName, done) =>
+			req =
+				regionAction: []
+
 			for region, actions of multiActions[serverName]
 				operations = []


Emit open event after hello, not before

Emit open event after hello, not before

--- src/client.coffee
+++ src/client.coffee
@@ -99,8 +99,6 @@
     else
       @ws = new WebSocket @socketUrl
       @ws.on 'open', =>
-        @emit 'open'
-        @connected = true
         @_connAttempts = 0
         @_lastPong = Date.now()

@@ -321,6 +319,7 @@
       when "hello"
         # connected really really
         @connected = true
+        @emit 'open'

       when "presence_change"
         # find user by id and change their presence


Remove minimum cost adjustment display

Remove minimum cost adjustment display

--- app/assets/javascripts/price_policy.js.coffee
+++ app/assets/javascripts/price_policy.js.coffee
@@ -25,13 +25,6 @@
       $inputElement.data "original-value", $inputElement.val() if $inputElement.val()
       $inputElement.val(if isDisabled then "" else $inputElement.data("original-value"))
       $inputElement.prop "disabled", isDisabled
-
-  deriveAdjustedCost = (unadjustedCost, usageSubsidyString)->
-    usageSubsidy = parseFloat usageSubsidyString
-    if isFiniteAndPositive(usageSubsidy)
-      (unadjustedCost * (1.0 - usageSubsidy)).toFixed 2
-    else
-      unadjustedCost

   getMasterUsageRate = ->
     rate = parseFloat $("input.master_usage_cost.usage_rate").val()
@@ -63,9 +56,8 @@
       desiredClass = RegExp.$1
       $("span.#{desiredClass}").each ->
         $costElement = $(this)
-        cost = deriveAdjustedCost(o.value, $costElement.data("usageSubsidy"))
-        $costElement.html(cost)
-        $costElement.siblings("input[type=hidden].#{desiredClass}").val(cost)
+        $costElement.html(o.value)
+        $costElement.siblings("input[type=hidden].#{desiredClass}").val(o.value)

   $(".can_purchase").change(->
     toggleGroupFields $(this)


Apply minimum cost subsidy only to its own policy row

--- app/assets/javascripts/price_policy.js.coffee
+++ app/assets/javascripts/price_policy.js.coffee
@@ -46,6 +46,7 @@

   refreshCosts = ->
     $(".master_minimum_cost").each (index, element)-> setInternalCost element
+    $("input[type=hidden].usage_cost").val(getMasterUsageRate())

   updateUsageSubsidy = (usageAdjustmentElement)->
     usageAdjustment = getUsageAdjustment usageAdjustmentElement
@@ -60,10 +61,11 @@
   setInternalCost = (o)->
     if o.className.match /master_(\S+_cost)/
       desiredClass = RegExp.$1
-      $spanElements = $("span.#{desiredClass}")
-      cost = deriveAdjustedCost o.value, $spanElements.data("usageSubsidy")
-      $spanElements.html cost
-      $("input[type=hidden].#{desiredClass}").val cost
+      $("span.#{desiredClass}").each ->
+        $costElement = $(this)
+        cost = deriveAdjustedCost(o.value, $costElement.data("usageSubsidy"))
+        $costElement.html(cost)
+        $costElement.siblings("input[type=hidden].#{desiredClass}").val(cost)

   $(".can_purchase").change(->
     toggleGroupFields $(this)


TL-299: Edit profile image is not working

--- app/coffeescripts/util/ProximityLoader.coffee
+++ app/coffeescripts/util/ProximityLoader.coffee
@@ -55,7 +55,7 @@
     constructor: (el, options = {}) ->
       @options = _.extend({}, @defaultOptions, options)
       @_cacheElements(el)
-      @deferred = $.Deferred().then(@_loadScript)
+      @deferred = $.Deferred().done(@_loadScript)
       # fail if the given element doesn't exist
       return unless @$el.length
       @_attachEvents()


removed logging

removed logging

--- test/tests/babel_bridge/indent_blocks_parsing.coffee
+++ test/tests/babel_bridge/indent_blocks_parsing.coffee
@@ -42,7 +42,7 @@

   test "simple expression", ->
     p = MyParser.parse "false"
-    log "simple expression": p.plainObjects
+    "simple expression": p.plainObjects

   test "simple block", ->
     p = MyParser.parse """
@@ -54,7 +54,7 @@
        indent1
       false
       """
-    log "simple expression": p.plainObjects
+    "simple expression": p.plainObjects

 suite "BabelBridge.Parser.indent block parsing.CaffeineScriptObjectNotation", ->

@@ -183,7 +183,7 @@
         """

     test "nested object expression", ->
-      log MyParser.parse """
+      MyParser.parse """
         hi:    123
         there:
           one: 123