#923 - Value in calculate question can't be edited and doesn't save properly

--- jsapp/xlform_model_view/view.row.coffee
+++ jsapp/xlform_model_view/view.row.coffee
@@ -7,6 +7,7 @@
         'cs!xlform/view.utils',
         'cs!xlform/view.choices',
         'cs!xlform/view.rowDetail',
+        'cs!xlform/model.utils',
         ], (
             Backbone,
             $,
@@ -16,6 +17,7 @@
             $viewUtils,
             $viewChoices,
             $viewRowDetail,
+            $modelUtils,
             )->
   class BaseRowView extends Backbone.View
     tagName: "li"
@@ -72,8 +74,12 @@
       @cardSettingsWrap = @$('.card__settings').eq(0)
       @defaultRowDetailParent = @cardSettingsWrap.find('.card__settings__fields--question-options').eq(0)
       for [key, val] in @model.attributesArray()
-        new $viewRowDetail.DetailView(model: val, rowView: @).renderInRowView(@)
-
+        view = new $viewRowDetail.DetailView(model: val, rowView: @)
+        if key == 'label' and @model.get('type').get('value') == 'calculate'
+          view.model = @model.get('calculation')
+          @model.finalize()
+          val.set('value', '')
+        view.renderInRowView(@)

       @
     add_row_to_question_library: (evt) ->


everybody should see scores after voting

--- controllers/index.coffee
+++ controllers/index.coffee
@@ -114,7 +114,7 @@
   res.redirect '/'

 app.get '/scores', (req, res, next) ->
-  return next(401) unless req.user?.admin
+  return next(401) unless app.enabled('winners') or req.user?.admin
   Team.sortedByScore (error, teams) ->
     return next error if error
     res.render2 'index/scores', teams: teams


fix a bug

fix a bug

--- lib/build/android-fastBuild-view.coffee
+++ lib/build/android-fastBuild-view.coffee
@@ -18,7 +18,7 @@

       @div class: 'row', =>
         @div class: 'col-xs-3', =>
-          @img class: 'icon', click: 'onClickIcon', outlet: 'icon'
+          @img class: 'icon', outlet: 'icon'

         @div class: 'col-xs-9', =>

@@ -61,6 +61,7 @@
     result = scheme: "#{@scheme.val()}"
     return result

+
   iconSrcPath:->
     if process.platform is "win32"
       {icon: url.parse(this.icon[0].src).path.replace("/","")}


Add optional node-formidable dependency to parse post requests

--- lib/frontend/http_middleware/api.coffee
+++ lib/frontend/http_middleware/api.coffee
@@ -41,6 +41,37 @@

 # PRIVATE

+# Alternative form parser
+if SS.config.api.form_parser.enabled
+
+  form_parser = require('formidable')
+
+  # Creating form process func
+  process_form = (request, cb)->
+
+    data = {fields:[], files:[], errors:[]}
+    form = new form_parser.IncomingForm()
+
+    #Formiblade config
+    form.maxFieldsSize = SS.config.api.form_parser.maxFieldsSize
+    form.keepExtensions = SS.config.api.form_parser.keepExtensions
+    form.encoding = SS.config.api.form_parser.encoding
+    form.uploadDir = SS.config.api.form_parser.uploadDir
+
+    form.on 'field', (field, value)->
+      data.fields.push [field, value]
+
+    form.on 'file', (file, value)->
+      data.files.push [file, value]
+
+    form.on 'error', (error)->
+      data.errors.push [error]
+
+    form.on 'end', ->
+      cb(data)
+
+    form.parse request
+
 # Process an API Request
 process = (request, response, url, actions) ->

@@ -50,11 +81,10 @@

     # Check format is supported
     throw 'Invalid output format. Supported formats: ' + formatters.keys().join(', ') unless formatters.keys().include(format)
-
+
     post_data = ''
-    request.on 'data', (chunk) -> post_data += chunk.toString()
-    request.on 'end', ->

+    generate_reply = ->
       # Generate request for back end and send
       obj = {responder: 'server', method: actions.join('.'), params: params}
       obj.post = post_data if post_data.length > 0
@@ -62,6 +92,18 @@
       # Execute the request and deliver the response once it returns
       rpc.send obj, (result) ->
         reply(result, response, format)
+
+    if !SS.config.api.form_parser.enabled
+
+      request.on 'data', (chunk) -> post_data += chunk.toString()
+      request.on 'end', ->
+        generate_reply()
+
+    else
+      process_form request, (data)->
+        post_data = JSON.stringify(data)
+        generate_reply()
+

   catch e
     server.showError(response, e)


Improve performance

Improve performance

--- lib/videojs-HTML5-niconicoplayer.coffee
+++ lib/videojs-HTML5-niconicoplayer.coffee
@@ -138,12 +138,15 @@
         if not player.paused()
           seconds = player.currentTime()
           scrollCommentTime seconds
-          updateComment()
       , 100
+
+      setInterval ->
+        updateComment()
+      , 33

       layoutComment = (chat, chatIndex) ->
         vpos = player.currentTime()
-        videoWidth = videoEl.offsetWidth
+        videoWidth = player.width()

         if vpos - settings.commentPostTime < chat.vpos / 100 < vpos + settings.commentPreTime
           scrollTime = (vpos + settings.commentPreTime) - chat.vpos / 100
@@ -183,6 +186,7 @@

           commentEl.dataset.line = line
           commentEl.dataset.index = chatIndex
+          commentEl.dataset.width = commentWidth
           commentEl.style.top = line * settings.commentHeight + 'px'
           commentEl.style.left = commentOffset + 'px'
           lineEndTimes[index] = chat.vpos / 100 + settings.commentPostTime
@@ -202,7 +206,7 @@

       updateComment = ->
         vpos = player.currentTime()
-        videoWidth = videoEl.offsetWidth
+        videoWidth = player.width()

         removalPendingElements = []

@@ -212,7 +216,7 @@
             removalPendingElements.push commentEl
           else
             scrollTime = (vpos + settings.commentPreTime) - chat.vpos / 100
-            commentWidth = commentEl.offsetWidth
+            commentWidth = parseInt commentEl.dataset.width
             scrollPath = videoWidth + commentWidth

             scrollOffset = scrollPath / settings.commentTime * scrollTime


More Rack frameworks

More Rack frameworks

--- lib/assets/javascripts/unpoly/protocol.coffee
+++ lib/assets/javascripts/unpoly/protocol.coffee
@@ -22,7 +22,7 @@

 - [Ruby on Rails](/install/rails)
 - [Roda](https://github.com/adam12/roda-unpoly)
-- [Rack](https://github.com/adam12/rack-unpoly) (Sinatra, Padrino)
+- [Rack](https://github.com/adam12/rack-unpoly) (Sinatra, Padrino, Hanami, Cuba, ...)
 - [Phoenix](https://elixirforum.com/t/unpoly-a-framework-like-turbolinks/3614/15) (Elixir)


Correct indentation error.

Correct indentation error.

--- lib/connection/connection-statemachine.coffee
+++ lib/connection/connection-statemachine.coffee
@@ -99,7 +99,7 @@
         if socket
           socket.destroy()

-         client.emit('end')
+        client.emit('end')

         '@exit'


Really respect the limit of the mongo database.

--- packages/rocketchat-importer-test/main.coffee
+++ packages/rocketchat-importer-test/main.coffee
@@ -1,10 +1,8 @@
-###
-# Invite is a named function that will replace /invite commands
-# @param {Object} message - The message object
-###
 if Meteor.isClient
 	Test = undefined
 else
+	aTest = new RocketChat.importTool.TestImporter
+
 	class Test
 		constructor: () ->
 			console.log 'Importer Test initialized'


fix error rooms

fix error rooms

--- app/assets/javascripts/rooms.coffee
+++ app/assets/javascripts/rooms.coffee
@@ -6,8 +6,8 @@

 $ ->
   $("#modal-submit").click ->
-    if $("#room_topic").val() is ""
-      $.bootstrapGrowl "You have write TOPIC",
+    if $("#room_topic").val() is "" or $("#room_name").val() is ""
+      $.bootstrapGrowl "You don't filed in requered fields",
         type: "success"
         offset:
           from: "top"


Format

Format

basically I prefer horizontally long code than vertically too long code

--- app/assets/javascripts/erd/erd.js.coffee
+++ app/assets/javascripts/erd/erd.js.coffee
@@ -138,20 +138,14 @@
     type_span = $("<span/>", class: 'column_type_text unsaved')
       .append(type)

-    li_node = $("<li/>", class: 'column unsaved')
-      .append(name_span)
-      .append(" ")
-      .append(type_span)
+    li_node = $("<li/>", class: 'column unsaved').append(name_span).append(" ").append(type_span)

     target.hide()
       .parent()
       .siblings('.columns')
-      .find('ul')
-      .append(li_node)
+      .find('ul').append(li_node).end()
       .end()
-      .end()
-      .find('a.add_column')
-      .show()
+      .find('a.add_column').show()

   handle_change_column_type: (ev) =>
     ev.preventDefault()
@@ -166,12 +160,8 @@
       @upsert_change 'alter_column', model, column, type, to

     target.hide()
-      .siblings('.column_type_text')
-      .text(to)
-      .show()
-      .addClass('unsaved')
-      .parents('.column')
-      .addClass('unsaved')
+      .siblings('.column_type_text').text(to).show().addClass('unsaved')
+      .parents('.column').addClass('unsaved')

   handle_rename_column: (ev) =>
     ev.preventDefault()
@@ -185,11 +175,8 @@
       @upsert_change 'rename_column', model, column, column, to

     target.hide()
-      .siblings('.column_name_text')
-      .text(to)
-      .show()
-      .parents('.column')
-      .addClass('unsaved')
+      .siblings('.column_name_text').text(to).show()
+      .parents('.column').addClass('unsaved')

   handle_rename_model: (ev) =>
     ev.preventDefault()
@@ -202,10 +189,7 @@
       @upsert_change 'rename_model', model, '', model, to

     target.hide()
-      .siblings('.model_name_text')
-      .text(to)
-      .show()
-      .addClass('unsaved')
+      .siblings('.model_name_text').text(to).show().addClass('unsaved')

   handle_add_column_click: (ev) =>
     ev.preventDefault()
@@ -217,17 +201,10 @@
       return false

     target.hide()
-      .next('form')
-      .show()
-      .find('a.cancel')
-      .show()
-      .end()
-      .find('input[name=type]')
-      .val('string')
-      .end()
-      .find('input[name=name]')
-      .val('')
-      .focus()
+      .next('form').show()
+      .find('a.cancel').show().end()
+      .find('input[name=type]').val('string').end()
+      .find('input[name=name]').val('').focus()

   handle_cancel_click: (ev) =>
     ev.preventDefault()
@@ -239,10 +216,8 @@
       return false

     target.hide()
-      .parent('form')
-      .hide()
-      .prev('a.add_column, span, div')
-      .show()
+      .parent('form').hide()
+      .prev('a.add_column, span, div').show()

   handle_text_elem_click: (ev) =>
@@ -255,14 +230,9 @@
       return false

     target.hide()
-      .next('form')
-      .show()
-      .find('a.cancel')
-      .show()
-      .end()
-      .find('input[name=to]')
-      .val(text)
-      .focus()
+      .next('form').show()
+      .find('a.cancel').show().end()
+      .find('input[name=to]').val(text).focus()

   handle_remove_model_click: (ev) =>
     ev.preventDefault()
@@ -305,10 +275,8 @@
       return false

     target.hide()
-      .next('div')
-      .show()
-      .find('#close_migration')
-      .show()
+      .next('div').show()
+      .find('#close_migration').show()

   handle_close_migration_click: (ev) =>
@@ -323,10 +291,8 @@
       return false

     target.hide()
-      .parent()
-      .hide()
-      .prev('div')
-      .show()
+      .parent().hide()
+      .prev('div').show()

 $ ->
   window.erd = new ERD('erd', $('#erd'), window.raw_edges)


Fix history clear

Fix history clear

--- app/assets/javascripts/history.js.coffee
+++ app/assets/javascripts/history.js.coffee
@@ -20,6 +20,9 @@

         btn.unbind(event).addClass('has-popover')
         btn.popover(options).popover('show')
+
+  $('.history-query-clear').click ->
+    $('.history-query').val('').closest('form').submit()

   $('.result-delete-button').on 'ajax:success', (e, data, status, xhr) ->
     $(this).closest('tr').fadeOut 500, ->


Allow to click on qiuck search results

Allow to click on qiuck search results

--- app/assets/javascripts/pages/menu.coffee
+++ app/assets/javascripts/pages/menu.coffee
@@ -35,7 +35,8 @@

   search.blur ->
     form.removeClass('focused')
-    quick.hide()
+    after '100ms', ->
+      quick.hide()
     app.search.lastSearch = null

   search.bind 'keyup change search', (e) ->


Add files to sidebar when uploaded

Add files to sidebar when uploaded

--- app/assets/javascripts/uploads.js.coffee
+++ app/assets/javascripts/uploads.js.coffee
@@ -37,9 +37,11 @@
       if $.inArray(extension, image_exts) > -1
         insertTextAtCaret($(sv_post_body), image_thumbnail_markdown(data.orig_name, src.url, src.thumb.url) + "\n")
         insertTextAtCaret($(en_post_body), image_thumbnail_markdown(data.orig_name, src.url, src.thumb.url) + "\n")
+        addToFileList(data.orig_name, src.url)
       else
         insertTextAtCaret($(sv_post_body), link_markdown(data.orig_name, src.url) + "\n")
         insertTextAtCaret($(en_post_body), link_markdown(data.orig_name, src.url) + "\n")
+        addToFileList(data.orig_name, src.url)

 handle_file_error = (data) ->
   data.label.text I18n.t('unsupported_file_format')
@@ -59,6 +61,12 @@
   oldText = $elem.val()
   $elem.val(oldText.substring(0, caretPos) + text + oldText.substring(caretPos))

+addToFileList = (fileName, url) ->
+  content = "
" + fileName + ""
+  content += "<button class='button tiny copy-file-link' data-markdown='" + link_markdown(fileName, url) + "'>Copy link
"
+  #content = fileName + "
"
+  $("#file-list").html(content)
+
 image_markdown = (title, url) ->
   "![#{title}](#{url})"


Reuse the get annotations code

Reuse the get annotations code

--- app/assets/javascripts/welcome.js.coffee
+++ app/assets/javascripts/welcome.js.coffee
@@ -81,14 +81,10 @@

     for stock in $scope.stored_stocks
       if stock.checked is true
-        console.log stock.ticker
         checked_tickers.push stock.ticker

-    $http.get('/annotations.json?order=asc')
-      .success (data) ->
-        displayChart(checked_tickers, data)
-      .error (data) ->
-        console.log "Failed to get data."
+    $scope.sendToDisplay(checked_tickers)
+
     # Try the localstorage feature below
     localStorage.setItem("list", checked_tickers)

@@ -101,11 +97,15 @@
       for stock in $scope.stored_stocks
         if stock.ticker in storedListArray is true
           stock.checked = true
-      $http.get('/annotations.json?order=asc')
-        .success (data) ->
-          displayChart(storedListArray, data)
-        .error (data) ->
-          console.log "Failed to get data."
+
+      $scope.sendToDisplay(storedListArray)
+
+  $scope.sendToDisplay = (tickers) ->
+    $http.get('/annotations.json?order=asc')
+      .success (data) ->
+        displayChart(tickers, data)
+      .error (data) ->
+        console.log "Failed to get data."

   $scope.loadStocks()


rotating arcs

rotating arcs

--- app/assets/javascripts/welcome.js.coffee
+++ app/assets/javascripts/welcome.js.coffee
@@ -13,6 +13,36 @@
   $.each words, (i) ->
     cells = words[i].cells
     $.each cells, (j) ->
+      last = null
+      if j > 0
+        last = cells[j - 1]
+      next = null
+      if j < cells.length - 1
+        next = cells[j + 1]
       cell = cells[j]
       div = $ 'tr:eq(' + cell.y + ') td:eq(' + cell.x + ') > *'
-      div.append '
<svg height="' + diameter + '" width="' + diameter + '"><circle cx="' + radius + '" cy="' + radius + '" r="' + radius + '" /></svg>
'+      deg = null
+      if j == 0
+        deg = getDirection(cell, next)
+      if j == cells.length - 1
+        deg = getDirection(cell, last)
+      if deg != null
+        div.append '
<svg height="' + diameter + 'px" width="' + diameter + 'px" viewBox="0 0 100 100"><path d="M0,50 a50,50 0 0,0 100,0" fill="none" stroke="black" stroke-width="3" /></svg>
'
+
+getDirection = (cell1, cell2) ->
+  if cell1.x == cell2.x && cell1.y - 1 == cell2.y
+    return 0
+  if cell1.x + 1 == cell2.x && cell1.y - 1 == cell2.y
+    return 1
+  if cell1.x + 1 == cell2.x && cell1.y == cell2.y
+    return 2
+  if cell1.x + 1 == cell2.x && cell1.y + 1 == cell2.y
+    return 3
+  if cell1.x == cell2.x && cell1.y + 1 == cell2.y
+    return 4
+  if cell1.x - 1 == cell2.x && cell1.y + 1 == cell2.y
+    return 5
+  if cell1.x - 1 == cell2.x && cell1.y == cell2.y
+    return 6
+  if cell1.x - 1 == cell2.x && cell1.y - 1 == cell2.y
+    return 7


increase column spacing divisor

increase column spacing divisor

--- app/assets/javascripts/zvg/column.coffee
+++ app/assets/javascripts/zvg/column.coffee
@@ -332,10 +332,10 @@
     total_column_count  += d.values.length for d in @_data

     series_set = d3.set([])
-    series_set.add(@survey_title_to_series_name[d.key])for d,i in @_data
+    series_set.add(@survey_title_to_series_name[d.key]) for d,i in @_data
     series_count = series_set.values().length

-    @column_spacing      = (@width - @x_offset)/(total_column_count + @_data.length + series_count)
+    @column_spacing      = (@width - @x_offset)/(total_column_count + @_data.length + ((series_count - 1) * 1.5))
     @column_padding      = 0.1 * @column_spacing
     @series_padding      = @column_spacing / 2
     current_x            = @x_offset # allow for scale on left
@@ -358,5 +358,6 @@
       .domain([0...maxCount])
       .rangeRoundBands([0, @column_spacing * maxCount], 0.1)
     series_padding_sum = d3.max([0, series_count - 1]) * @series_padding
+
     if @column_band.rangeBand() < @minimum_column_width
       @widen_chart(d3.max([@width, current_x]) - @x_offset + 100 + series_padding_sum)


Fix sessionStorage initialization code

Fix sessionStorage initialization code

With previous version of this code sessionStorage was never initialized
properly. This breaks travis become.

--- app/initializers/storage.coffee
+++ app/initializers/storage.coffee
@@ -19,8 +19,8 @@
   try
     # firefox will not throw error on access for sessionStorage var,
     # you need to actually get something from session
-    sessionStorage.getItem('foo')
-    storage = sessionStorage
+    window.sessionStorage.getItem('foo')
+    storage = window.sessionStorage
   catch err
     storage = Storage.create()


Fix clinicalEncounters existence check.

Fix clinicalEncounters existence check.

--- app/javascripts/dateline.coffee
+++ app/javascripts/dateline.coffee
@@ -3,27 +3,24 @@

   dateline.factory 'VisitDateline', ['ObjectHelper', 'Chart', (ObjectHelper, Chart) ->
     # A helper function to calculate the effective treatment dates.
-    # If there is not a begin date, then this function returns an
-    # empty array. Otherwise, this function returns a [begin, end]
-    # array, where:
-    # * begin is the moment begin date integer
+    # This function returns a [start, end] array, where:
+    # * start is the moment start date integer
     # * end is the moment end date integer
     # The default end date is today.
     #
+    # TODO - depict a treatment with no end date as an arrow.
+    #
     # @param treatment the treatment object
-    # @returns the [begin, end] array
+    # @returns the [start, end] array
     treatmentSpan = (treatment) ->
-      if treatment.start_date?
-        begin = treatment.start_date.valueOf()
-        endDate = treatment.end_date or moment()
-        end = endDate.valueOf()
-        [begin, end]
-      else
-        []
+      start = treatment.start_date.valueOf()
+      endDate = treatment.end_date or moment()
+      end = endDate.valueOf()
+      [start, end]

     # Decorates the dateline as follows:
     # * Add the session hyperlinks, encounter dates and treatment
-    #   begin-end bars.
+    #   start-end bars.
     # * Rotate the date x-axis tick labels
     #
     # Note: The session hyperlinks are ui-sref attributes. However,
@@ -90,8 +87,6 @@
           callback(a) if callback

       # Inserts an SVG bar for each treatment above the timeline.
-      # The bar is added if and only if the treatment has a
-      # begin date.
       #
       # @param xAxisNode the chart SVG x axis element
       # @param config the chart configuration
@@ -105,26 +100,24 @@
         # The scaling factor.
         factor = dateline.width / (high - low)
         for trt in subject.treatments
-          trtSpan = treatmentSpan(trt)
-          if _.any(trtSpan)
-            [begin, end] = trtSpan
-            left = (begin - low) * factor
-            width = (end - begin) * factor
-            # Allow for a 6-pixel minimum width.
-            if not width
-              left = left - 3
-              width = 6
-            # Place the new bar rectangle element in the DOM
-            # before the x axis element.
-            bar = parent.insert('svg:rect', -> xAxisNode)
-            # Set the bar dimensions.
-            bar.attr('height', "#{ TREATMENT_BAR_HEIGHT }em")
-            bar.attr('width', width)
-            # Set the bar style.
-            bar.classed("qi-dateline-#{ trt.treatment_type.toLowerCase() }", true)
-            # Position the bar.
-            bar.attr('x', left)
-            bar.attr('y', "-#{ TREATMENT_BAR_HEIGHT }em")
+          [start, end] = treatmentSpan(trt)
+          left = (start - low) * factor
+          width = (end - start) * factor
+          # Allow for a 6-pixel minimum width.
+          if not width
+            left = left - 3
+            width = 6
+          # Place the new bar rectangle element in the DOM
+          # before the x axis element.
+          bar = parent.insert('svg:rect', -> xAxisNode)
+          # Set the bar dimensions.
+          bar.attr('height', "#{ TREATMENT_BAR_HEIGHT }em")
+          bar.attr('width', width)
+          # Set the bar style.
+          bar.classed("qi-dateline-#{ trt.treatment_type.toLowerCase() }", true)
+          # Position the bar.
+          bar.attr('x', left)
+          bar.attr('y', "-#{ TREATMENT_BAR_HEIGHT }em")

       # Inserts a marker for each encounter above the timeline.
       #
@@ -176,7 +169,7 @@
           p = parent.insert('p', -> svgNode)
           p.text('Treatments: ')
           p.classed({'col-md-offset-5': true, 'font-size: small': true})
-          # Sort by begin date.
+          # Sort by start date.
           sorted = _.sortBy(trts, (trt) -> trt.start_date.valueOf())
           # The treatment labels.
           labels = _.uniq(trt.treatment_type for trt in sorted)
@@ -193,14 +186,14 @@

         addEncounterLegend = (parent, svgNode) ->
           # Bail if no encounters are displayed.
-          if _.any(subject.clinicalEncounters)
-            return
+          encs = subject.clinicalEncounters
+          return if not encs.length
           # Place the legend line.
           p = parent.insert('p', -> svgNode)
           p.text('Encounters: ')
           p.classed({'col-md-offset-5': true, 'font-size: small': true})
-          # Sort by begin date.
-          sorted = _.sortBy(subject.clinicalEncounters, (enc) -> enc.date.valueOf())
+          # Sort by start date.
+          sorted = _.sortBy(encs, (enc) -> enc.date.valueOf())
           # The encounter labels.
           labels = _.uniq(enc._cls for enc in sorted)
           for label in labels


badge defs

badge defs

--- app/lib/BadgeDefinitions.coffee
+++ app/lib/BadgeDefinitions.coffee
@@ -4,26 +4,26 @@
     url: 'badges/seed.png'
     description: "You earn this badge for transcribing 10 Hebernium records"
     awardText:  "You earned this badge for transcribing 10 Hebernium records"
-    condition: (details)-> details.user.project.classification_count >= 1
+    condition: (details)-> details.user.project.classification_count == 1
     collection: 'Herbarium'

   sprout :
     url: 'badges/seedling.png'
     description: "You earn this badge for transcribing 20 Hebernium records"
     awardText:  "You earned this badge for transcribing 20 Hebernium records"
-    condition: (details)-> details.user.project.classification_count > 10
+    condition: (details)-> details.user.project.classification_count == 10
     collection: 'Herbarium'

   seedling :
     url: 'badges/sapling.png'
     description: "You earn this badge for transcribing 50 Hebernium records"
     awardText:  "You earned this badge for transcribing 50 Hebernium records"
-    condition: (details)-> details.user.project.classification_count > 50
+    condition: (details)-> details.user.project.classification_count == 50
     collection: 'Herbarium'

   sapling :
     url: 'badges/tree.png'
     description: "You earn this badge for transcribing 100 Hebernium records"
     awardText:  "You earned this badge for transcribing 100 Hebernium records"
-    condition: (details)-> details.user.project.classification_count > 100
+    condition: (details)-> details.user.project.classification_count == 100
     collection: 'Herbarium'


[Backport] Task #3538: Remove tribe_git from the history

--- app/coffee/modules/common/history.coffee
+++ app/coffee/modules/common/history.coffee
@@ -28,7 +28,7 @@

 IGNORED_FIELDS = {
     "userstories.userstory": [
-        "watchers", "kanban_order", "backlog_order", "sprint_order", "finish_date"
+        "watchers", "kanban_order", "backlog_order", "sprint_order", "finish_date", "tribe_gig"
     ],
     "tasks.task": [
         "watchers", "us_order", "taskboard_order"


don't disable preventDefault() for kylmenu links

refs: CNVS-14966

Test Plan:
  - Using screenreader/keyboard-only navigation
  - Go to the modules page (/courses/##/modules)
  - open the cog menu on module
  - hit space on the "delete" option
  - the page should not reload

this test plan is just one of many things that were
broken by not being able to .preventDefault()
on kyleMenu options.

@acannon, was there some reason that we needed to
force not being able to .preventDefault()?
see: https://developer.mozilla.org/en-US/docs/Web/API/Event.initEvent

Change-Id: Ibca3ed9fe8fc199e25c75313159d10d0563165a1
Reviewed-on: https://gerrit.instructure.com/40952
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Clare Strong <40d6bb3ed7fef516c73274131d5a837c16de74fd@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/jquery.kylemenu.coffee
+++ app/coffeescripts/jquery.kylemenu.coffee
@@ -64,7 +64,7 @@
         e.preventDefault()
         el = $target[0]
         event = document.createEvent 'MouseEvent'
-        event.initEvent 'click', true, false
+        event.initEvent 'click', true, true
         el.dispatchEvent event
       @close()


Fix issue zooniverse/ubret#24

Fix issue zooniverse/ubret#24

--- app/collections/external_subjects.coffee
+++ app/collections/external_subjects.coffee
@@ -22,7 +22,9 @@

   processParams: =>
     params = new Array
-    params.push "#{key}=#{value}" for key, value of @params when value isnt ''
+    for key, value of @params when value isnt ''
+      value = value.split('\n').join(' ') if key is 'query'
+      params.push "#{key}=#{value}"
     params.join("&")

 module.exports = ExternalSubjectCollection


Netease vendor purchased schema added to Prepaids

--- app/schemas/models/prepaid.schema.coffee
+++ app/schemas/models/prepaid.schema.coffee
@@ -15,6 +15,9 @@
   properties: { type: 'object' }
   exhausted: { type: 'boolean' }
   startDate: c.stringDate()
+  vendorPurchased: c.object {},
+    gems: {type: 'number', description: 'Overwrites the number of gems given by Product when the prepaid is redeemed'}
+    expires: c.stringDate({description: 'Date the prepaid expires and cannot be redeemed'})
   endDate: c.stringDate()
   includedCourseIDs: c.array({ description: 'courseIDs that this prepaid includes access to' }, c.objectId())
   joiners: c.array {title: 'Teachers this Prepaid is shared with'},


Declutter upload notifications

Declutter upload notifications

--- app/scripts/controllers/documents.coffee
+++ app/scripts/controllers/documents.coffee
@@ -168,6 +168,7 @@
           break

     uploadDocument = (name, content) ->
+      $scope.$parent.clearAlerts()
       $scope.$parent.alert("Uploading and importing #{name}...")
       doc_type = 'plaintext'
       if name.slice(-3) == 'xml'


Refactoring

Refactoring

--- client/Ace/ace.coffee
+++ client/Ace/ace.coffee
@@ -400,8 +400,8 @@
       @editor.setOptions
         enableBasicAutocompletion: value
         enableSnippets: value
-    return  unless save
-    @appStorage.setValue 'enableAutocomplete', value
+
+    @appStorage.setValue 'enableAutocomplete', value  if save

   gotoLine: (lineNumber) ->
     @editor.gotoLine lineNumber


render vote button differently depending on whether user is logged in and whether they have previously voted for the band

--- assets/coffee/components/VoteButton.cjsx
+++ assets/coffee/components/VoteButton.cjsx
@@ -16,12 +16,22 @@
       .set('Accept', 'application/json')
       .end (error, res) =>
         # TODO handle errors better
-        return console.error error if error?
-        return console.log JSON.parse(res.text)
+        if error?
+          return console.error error
+        else
+          return console.log JSON.parse(res.text)

   render: ->
-

-      <button onClick={@voteForBand}>Vote!</button>
-

+    if @props.loggedIn
+      unless @props.userHasVotedFor
+

+          <button onClick={@voteForBand}>Vote!</button>
+

+      else
+

+    else
+

+        Sign in to vote!
+

 module.exports = VoteButton


Fix the log view for finished logs

Fix the log view for finished logs

--- assets/scripts/app/views/log.coffee
+++ assets/scripts/app/views/log.coffee
@@ -165,9 +165,9 @@

     didInsertElement: ->
       job = @get('job')
-      if job && !job.get('isFinished')
+      if job
         job.get('log').fetch()
-        job.subscribe()
+        job.subscribe() if !job.get('isFinished')

     willDestroyElement: ->
       job = @get('job')


i hope this finally works as intended

i hope this finally works as intended

--- client/js/main.coffee
+++ client/js/main.coffee
@@ -17,9 +17,6 @@
     @route 'user', 'user'
     @route 'user/:user', 'user'

-    if window.ga?
-      @on 'route', -> window.ga 'send', 'pageview'
-
   user: (user) ->
     console?.log 'route:user'

@@ -27,6 +24,8 @@
     path = '/' + path
     getContent(path).then (results) ->
       {album, picture} = results
+
+      window.ga 'send', 'pageview', path if window.ga?

       if picture
         pictureView.setModel(picture).render()


Remove obsolete AMIs

Remove obsolete AMIs

--- scripts/test-instance/instance-ami.coffee
+++ scripts/test-instance/instance-ami.coffee
@@ -3,10 +3,6 @@

   # Ordered by creation date
   list: [
-    'ami-10465678'
-    'ami-3e415256'
-    'ami-bc5e4cd4'
-    'ami-193fd572'
     'ami-a7986fcc'
     'ami-b920d7d2'
   ]


mathjax flag bug fix

mathjax flag bug fix

--- client/message.coffee
+++ client/message.coffee
@@ -87,9 +87,9 @@
   tex2jax: ->
     history = Template.instance().history.get() ? @
     if history.format in mathjaxFormats
+      'tex2jax'
+    else
       ''
-    else
-      'tex2jax'
   body: ->
     history = Template.instance().history.get() ? @
     body = history.body


reporter: fix jsonp reporter.

reporter: fix jsonp reporter.

--- src/reporters/jsonp.coffee
+++ src/reporters/jsonp.coffee
@@ -18,11 +18,10 @@
   head = document.getElementsByTagName('head')[0]
   script = document.createElement('script')
   script.src = url
-  removeScript -> head.removeChild(script)
-  script.onload = removeTag
-  script.onerror = removeTag
-
-  head.appendChild(script_tag);
+  removeScript = -> head.removeChild(script)
+  script.onload = removeScript
+  script.onerror = removeScript
+  head.appendChild(script)

 module.exports = report


resource_loader

--- src/resource_loader.coffee
+++ src/resource_loader.coffee
@@ -13,8 +13,8 @@
     Return the url for a particular asset.

-       ResourceLoader.urlFor("images", "player")
-    => This returns the url for the file "player.png" in your images directory.
+    ResourceLoader.urlFor("images", "player")
+    # => This returns the url for the file "player.png" in your images directory.

     @name urlFor


Do not use project name when searching for apps/instances.

--- client/version.coffee
+++ client/version.coffee
@@ -1 +1 @@
-@version = '4.0.7'
+@version = '4.0.8'


Fix Users Collection path

Fix Users Collection path

--- public/src/app/Collection/Users.coffee
+++ public/src/app/Collection/Users.coffee
@@ -1,6 +1,6 @@

 class Users extends Backbone.Collection
   model: User
-  url: "api/users"
+  url: "/api/users"
   initialize: ->
     console.log 'Users Collection initialize'


formally do the **easy** parts of rewriting storage.coffee to use postgres

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -39,12 +39,9 @@
 NOTES:
   - save_BUP always saves to GCS as well, so there is no save_GCS.
 ###
+require('coffee-cache')

 BUCKET = 'smc-projects-bup'  # if given, will upload there using gsutil rsync
-DB = ['db0', 'db1', 'db2', 'db3', 'db4', 'db5']  # default database nodes
-
-#if process.env.USER != 'root'
-#    console.warn("WARNING: many functions in storage.coffee will not work if you aren't root!")

 {join}      = require('path')
 fs          = require('fs')
@@ -59,12 +56,18 @@
 misc        = require('smc-util/misc')
 {defaults, required} = misc

+postgres = require('./postgres')
+
 # Set the log level
 winston.remove(winston.transports.Console)
 winston.add(winston.transports.Console, {level: 'debug', timestamp:true, colorize:true})

 exclude = () ->
     return ("--exclude=#{x}" for x in misc.split('.sage/cache .sage/temp .trash .Trash .sagemathcloud .smc .node-gyp .cache .forever .snapshots *.sage-backup'))
+
+get_db = (cb) ->
+    db = postgres.db()
+    db.connect(cb : (err) => cb?(err, db))

 # Low level function that save all changed files from a compute VM to a local path.
 # This must be run as root.
@@ -128,13 +131,16 @@

 get_storage = (project_id, database, cb) ->
     dbg = (m) -> winston.debug("get_storage(project_id='#{project_id}'): #{m}")
-    database.table('projects').get(project_id).pluck(['storage']).run (err, x) ->
-        if err
-            cb(err)
-        else if not x?
-            cb("no such project")
-        else
-            cb(undefined, x.storage?.host)
+    database.get_project
+        project_id : project_id
+        columns    : ['storage']
+        cb         : (err, x) ->
+            if err
+                cb(err)
+            else if not x?
+                cb("no such project")
+            else
+                cb(undefined, x.storage?.host)

 get_host_and_storage = (project_id, database, cb) ->
     dbg = (m) -> winston.debug("get_host_and_storage(project_id='#{project_id}'): #{m}")
@@ -143,35 +149,39 @@
     async.series([
         (cb) ->
             dbg("determine project location info")
-            database.table('projects').get(project_id).pluck(['storage', 'host']).run (err, x) ->
-                if err
-                    cb(err)
-                else if not x?
-                    cb("no such project")
-                else
-                    host    = x.host?.host
-                    storage = x.storage?.host
-                    if not host
-                        cb("project not currently open on a compute host")
+            database.get_project
+                project_id : project_id
+                columns    : ['storage', 'host']
+                cb         : (err, x) ->
+                    if err
+                        cb(err)
+                    else if not x?
+                        cb("no such project")
                     else
-                        cb()
+                        host    = x.host?.host
+                        storage = x.storage?.host
+                        if not host
+                            cb("project not currently open on a compute host")
+                        else
+                            cb()
         (cb) ->
             if storage?
                 cb()
                 return
             dbg("allocate storage host")
-            database.table('storage_servers').pluck('host').run (err, x) ->
-                if err
-                    cb(err)
-                else if not x? or x.length == 0
-                    cb("no storage servers in storage_server table")
-                else
-                    # TODO: could choose based on free disk space
-                    storage = misc.random_choice((a.host for a in x))
-                    database.set_project_storage
-                        project_id : project_id
-                        host       : storage
-                        cb         : cb
+            database._query
+                query : "SELECT host FROM storage_servers"
+                cb    : postgres.all_results 'host', (err, hosts) ->
+                    if err
+                        cb(err)
+                    else if not hosts? or hosts.length == 0
+                        cb("no storage servers in storage_server table")
+                    else
+                        storage = misc.random_choice(hosts)
+                        database.set_project_storage
+                            project_id : project_id
+                            host       : storage
+                            cb         : cb
     ], (err) ->
         cb(err, {host:host, storage:storage})
     )
@@ -218,7 +228,7 @@

 To save(=rsync over) everything modified in the last week:

-s = require('smc-hub/storage'); require('smc-hub/rethink').rethinkdb(hosts:['db0'],pool:1,cb:(err,db)->s.save_SNAPSHOT_age(database:db, age_m:60*24*7, cb:console.log))
+s.save_SNAPSHOT_age(database:db, age_m:60*24*7, cb:console.log)

 ###
 exports.save_SNAPSHOT_age = (opts) ->
@@ -599,7 +609,7 @@
 ###
 Must run as root:

-db = require('smc-hub/rethink').rethinkdb(hosts:['db0'],pool:1); s = require('smc-hub/storage');
+s = require('smc-hub/storage');

 # make sure everything not touched in a year has a backup as recorded in the database...
 # (except use limit to only do that many)
@@ -766,7 +776,13 @@
                 cb         :cb
         (cb) ->
             dbg("recording successful backup in database")
-            opts.database.table('projects').get(opts.project_id).update(last_backup: new Date()).run(cb)
+            opts.database._query
+                query : "UPDATE projects"
+                set   :
+                    last_backup: new Date()
+                where :
+                    'project_id :: UUID = $' : opts.project_id
+                cb    : cb
     ], (err) -> opts.cb(err))

 copy_BUP_to_GCS = (opts) ->
@@ -1083,12 +1099,9 @@
     db = undefined
     async.series([
         (cb) ->
-            require('./rethink').rethinkdb
-                hosts : DB
-                pool  : 1
-                cb    : (err, x) ->
-                    db = x
-                    cb(err)
+            get_db (err, x) ->
+                db = x
+                cb(err)
         (cb) ->
             exports.save_BUP_age
                 database                 : db
@@ -1111,22 +1124,23 @@
     async.series([
         (cb) ->
             dbg("get projects with no assigned storage")
-            database.table('projects').filter((row)->row.hasFields({storage:true}).not()).pluck('project_id').run (err, v) ->
-                dbg("get #{v?.length} projects")
-                projects = v; cb(err)
-        (cb) ->
-            database.table('storage_servers').pluck('host').run (err, v) ->
-                if err
+            database._query
+                query : "SELECT project_id FROM projects WHERE storage IS NULL"
+                cb    : postgres.all_results 'project_id', (err, v) ->
+                    dbg("get #{v?.length} projects")
+                    projects = v
                     cb(err)
-                else
-                    dbg("got hosts: #{misc.to_json(v)}")
-                    hosts = (x.host for x in v)
-                    cb()
+        (cb) ->
+            database._query
+                query : "SELECT host FROM storage_servers"
+                cb    : postgres.all_results 'host', (err, v) ->
+                    dbg("get #{v?.length} storage_servers")
+                    hosts = v
+                    cb(err)
         (cb) ->
             n = 0
-            f = (project, cb) ->
+            f = (project_id, cb) ->
                 n += 1
-                {project_id} = project
                 host = misc.random_choice(hosts)
                 dbg("#{n}/#{projects.length}: assigning #{project_id} to #{host}")
                 database.get_project_storage  # do a quick check that storage isn't defined -- maybe slightly avoid race condition (we are being lazy)
@@ -1139,6 +1153,7 @@
                                 project_id : project_id
                                 host       : host
                                 cb         : cb
+
             async.mapLimit(projects, 10, f, cb)
     ], cb)

@@ -1188,12 +1203,9 @@
             dbg("create new pid file")
             fs.writeFile(PID_FILE, "#{process.pid}", cb)
         (cb) ->
-            require('smc-hub/rethink').rethinkdb
-                hosts : DB
-                pool  : 1
-                cb    : (err, db) ->
-                    database = db
-                    cb(err)
+            get_db (err, db) ->
+                database = db
+                cb(err)
         (cb) ->
             exports.assign_storage_to_all_projects(database, cb)
         (cb) ->
@@ -1221,11 +1233,9 @@
     database = undefined
     async.series([
         (cb) ->
-            require('smc-hub/rethink').rethinkdb
-                hosts : DB
-                pool  : 1
-                cb    : (err, db) ->
-                    database = db; cb(err)
+            get_db (err, db) ->
+                database = db
+                cb(err)
         (cb) ->
             exports.mount_snapshots_on_all_compute_vms
                 database : database
@@ -1239,7 +1249,7 @@
     )

 ###
-db = require('smc-hub/rethink').rethinkdb(hosts:['db0'], pool:1); s = require('smc-hub/storage'); 0;
+s = require('smc-hub/storage')
 s.mount_snapshots_on_all_compute_vms(database:db, cb:console.log)
 ###
 exports.mount_snapshots_on_all_compute_vms = (opts) ->
@@ -1335,9 +1345,18 @@
         dbg("ERROR: action not set -- suspicious -- please investigate")
         return

+    if not project.project_id
+        dbg("project.project_id must be a uuid")
+        return
+
     update_db = (cb) ->
-        query = database.table('projects').get(project.project_id)
-        query.update(storage_request:database.r.literal(storage_request)).run(cb)
+        database._query
+            query : "UPDATE projects"
+            where :
+                'project_id :: UUID = $' : project.project_id
+            set   :
+                storage_request : storage_request
+            cb    : cb

     opts =
         database   : database
@@ -1432,16 +1451,15 @@
                         cb()
         (cb) ->
             dbg("connect to database")
-            require('smc-hub/rethink').rethinkdb
-                hosts : DB
-                pool  : 10
-                cb    : (err, db) ->
-                    database = db
-                    cb(err)
+            get_db (err, db) ->
+                database = db
+                cb(err)
         (cb) ->
             dbg("create synchronized table")

-            # Get every project assigned to this host that has done a storage request starting within the last two hours.
+            # TODO
+            # Get every project assigned to this host that has done a storage
+            # request starting within the last two hours.
             age   = misc.hours_ago(2)
             query = database.table('projects').between([host, age], [host, database.r.maxval], index:'storage_request').pluck(FIELDS...)
             database.synctable
@@ -1565,15 +1583,12 @@
         async.series([
             (cb) =>
                 dbg("connect to database")
-                require('smc-hub/rethink').rethinkdb
-                    hosts : DB
-                    pool  : 10
-                    cb    : (err, database) =>
-                        @_database = database
-                        cb(err)
+                get_db (err, db) =>
+                    @_database = db
+                    cb(err)
             (cb) =>
                 dbg("create synchronized table")
-                # Get every project that has done a storage request recently
+                # TODO: Get every project that has done a storage request recently
                 age   = misc.minutes_ago(@_age_m)
                 FIELDS   = ['project_id', 'storage_request', 'storage', 'host', 'state']
                 database = @_database
@@ -1671,12 +1686,9 @@
     async.series([
         (cb) ->
             dbg("connect to database")
-            require('smc-hub/rethink').rethinkdb
-                hosts : misc.random_choice(DB)
-                pool  : 1
-                cb    : (err, _db) ->
-                    db = _db
-                    cb(err)
+            get_db (err, _db) ->
+                db = _db
+                cb(err)
         (cb) ->
             dbg("doing query")
             # Projects...
@@ -1724,12 +1736,9 @@
     async.series([
         (cb) ->
             dbg("connect to database")
-            require('smc-hub/rethink').rethinkdb
-                hosts : DB
-                pool  : 10
-                cb    : (err, _db) ->
-                    db = _db
-                    cb(err)
+            get_db (err, _db) ->
+                db = _db
+                cb(err)
         (cb) ->
             dbg("get projects with ignored save requests")
             exports.ignored_storage_requests
@@ -1772,16 +1781,16 @@
 # Command line interface
 ###########################

-program     = require('commander')
-
-LOGS = join(process.env.HOME, 'logs')
-program.usage('[start/stop/restart/status] [options]')
-    .option('--pidfile [string]', 'store pid in this file', String, "#{LOGS}/storage.pid")
-    .option('--logfile [string]', 'write log to this file', String, "#{LOGS}/storage.log")
-    .option('-e')   # gets passed by coffee -e
-    .parse(process.argv)
+program = require('commander')

 main = () ->
+    LOGS = join(process.env.HOME, 'logs')
+    program.usage('[start/stop/restart/status] [options]')
+        .option('--pidfile [string]', 'store pid in this file', String, "#{LOGS}/storage.pid")
+        .option('--logfile [string]', 'write log to this file', String, "#{LOGS}/storage.log")
+        .option('-e')   # gets passed by coffee -e
+        .parse(process.argv)
+
     winston.debug("running as a deamon")
     daemon = require('start-stop-daemon')
     # run as a server/daemon (otherwise, is being imported as a library)
@@ -1790,12 +1799,9 @@
         winston.debug("Uncaught exception: " + err)
         winston.debug(err.stack)
         winston.debug("BUG ****************************************************************************")
-        require('smc-hub/rethink').rethinkdb
-            hosts : DB
-            pool  : 1
-            cb    : (e, db) ->
-                if not e
-                    db?.uncaught_exception(err)
+        get_db (e, db) ->
+            if not e
+                db?.uncaught_exception(err)

     async.series([
         (cb) ->
@@ -1810,39 +1816,4 @@
     main()

-###
-One off code to change from 'project[n]' to 'storage[n]' in storage table of db.
-###
-###
-# way better than the below is:
-#   (db.table('projects').filter(storage:{host:"projects#{n}"}).update(storage:{host:"storage#{n}"}).run(console.log) for n in [0,1,2,3,4,5])
-exports.one_off_storage_db_update = () =>
-    dbg = (m) -> winston.debug("storage_db_update: #{m}")
-    projects = undefined
-    require('smc-hub/rethink').rethinkdb
-        hosts : DB
-        pool  : 1
-        cb    : (err, db) ->
-            async.series([
-                (cb) ->
-                    dbg("getting all projects")
-                    db.table('projects').pluck('project_id', 'storage').run (err, x) ->
-                        if err
-                            cb(err)
-                        else
-                            projects = (a for a in x when a.host?.slice(0,8) == 'projects')
-                            cb()
-                (cb) ->
-                    dbg("got #{projects.length} projects")
-                    cb()
-                    #f = (project, cb) ->
-                    #    host = project.storage.host
-                    #    host = 'storage' + host.slice(8)
-                    #    db.table('projects').get(project.project_id).update(storage:{host:host}).run(cb)
-                    #async.mapLimit(projects, 10, f, (err) -> cb(err))
-            ], (err) ->
-                dbg("TOTALLY DONE: #{err}")
-            )
-###
-
-
+


refactored isSafari

refactored isSafari

--- coffee/jPlayer.coffee
+++ coffee/jPlayer.coffee
@@ -32,6 +32,12 @@
     defaults:
       file:null
       type:"mp3"
+ApiHeroUI.widgets.jPlayer.utils =
+  isSafari:->
+    ($.browser.webkit && (!window.navigator.userAgent.match /.*(Chrome)+.*/))?
+console.log ApiHeroUI.widgets.jPlayer.utils
+ApiHeroUI.widgets.jPlayer.controls = {}
+ApiHeroUI.widgets.jPlayer.components = {}
 ApiHeroUI.widgets.jPlayer::defaults =
   swfPath:"/"
   supplied: "#{if ApiHeroUI.widgets.jPlayer.utils.isSafari() then 'mp3' else 'rtmpv'}"
@@ -59,10 +65,4 @@
     if evt.jPlayer.status.duration > 0 && evt.jPlayer.status.duration != @_duration
       @trigger "durationChange", (@_duration = evt.jPlayer.status.duration)
   ended: (evt)=>
-    @trigger "trackEnded"
-ApiHeroUI.widgets.jPlayer.utils =
-  isSafari:->
-    ($.browser.webkit && (!window.navigator.userAgent.match /.*(Chrome)+.*/))?
-console.log ApiHeroUI.widgets.jPlayer.utils
-ApiHeroUI.widgets.jPlayer.controls = {}
-ApiHeroUI.widgets.jPlayer.components = {}+    @trigger "trackEnded"


change popup message to be info not an ERROR.

--- src/smc-webapp/misc_page.coffee
+++ src/smc-webapp/misc_page.coffee
@@ -1791,13 +1791,13 @@
         tab = window.open(url, '', 'menubar=yes,toolbar=no,resizable=yes,scrollbars=yes,height=640,width=800')
     else
         tab = window.open(url)
-    if(!tab || tab.closed || typeof tab.closed=='undefined')
+    if not tab?.closed? or tab.closed   # either ab isn't even defined (or doesn't have close method) -- or already closed -- popup blocked
         {alert_message} = require('./alerts')
         alert_message
             title   : "Pop-ups blocked."
             message : "Either enable pop-ups for this website or click on this link."
-            type    : 'error'
-            timeout : 10
+            type    : 'info'
+            timeout : 15
         return null
     return tab


Improve command panel meta-f spec

Improve command panel meta-f spec

--- spec/extensions/command-panel-spec.coffee
+++ spec/extensions/command-panel-spec.coffee
@@ -97,11 +97,13 @@
       expect(commandInterpreter.lastRelativeAddress.subcommands[0].regex.toString()).toEqual "/\\(items\\)/"

   describe "when command-panel:find-in-file is triggered on an editor", ->
-    it "pre-populates command panel's editor with / and moves the cursor to column 1", ->
+    it "pre-populates the command panel's editor with / and moves the cursor to the last column", ->
+      spyOn(commandPanel, 'attach').andCallThrough()
       commandPanel.miniEditor.setText("foo")
       commandPanel.miniEditor.setCursorBufferPosition([0, 0])

       rootView.getActiveEditor().trigger "command-panel:find-in-file"
+      expect(commandPanel.attach).toHaveBeenCalled()
       expect(commandPanel.parent).not.toBeEmpty()
       expect(commandPanel.miniEditor.getText()).toBe "/"
       expect(commandPanel.miniEditor.getCursorBufferPosition()).toEqual [0, 1]


Removing header and footer code.

Removing header and footer code.

--- src/toaster/core/builder.coffee
+++ src/toaster/core/builder.coffee
@@ -162,7 +162,7 @@
     fsu.rm_rf @config.release_dir
     fsu.mkdir_p @config.release_dir

-  compile:( header_code, footer_code )->
+  compile:()->
     # clear release folder
     @clear()

@@ -171,7 +171,7 @@
     @copy_vendors_to_release()
     @write_loader()

-  optimize:( header_code, footer_code )->
+  optimize:()->
     log 'Optimizing project...'

     # clear release folder


use individual requires so it doesn't pull in entire lodash

I must have been thinking too much es6 importy there

--- shared/src/model/notifications/pollers.coffee
+++ shared/src/model/notifications/pollers.coffee
@@ -1,4 +1,10 @@
-{ extend, bindAll, difference, without, keys, isEmpty, values } = require 'lodash'
+extend     = require 'lodash/extend'
+bindAll    = require 'lodash/bindAll'
+difference = require 'lodash/difference'
+without    = require 'lodash/without'
+keys       = require 'lodash/keys'
+isEmpty    = require 'lodash/isEmpty'
+values     = require 'lodash/values'

 moment      = require 'moment'
 axios       = require 'axios'


Fix datetimepicker closing issue

Fix datetimepicker closing issue

--- src/views/route-settings.coffee
+++ src/views/route-settings.coffee
@@ -43,8 +43,6 @@
             @headerRegion.currentView.render()
             @accessibilitySummaryRegion.currentView.render()
             @transportModeControlsRegion.currentView.render()
-            @routeControllersRegion.currentView.render()
-

     class RouteEndpointsView extends base.SMLayout
         template: 'route-endpoints'


used jQuery.each -> in cleanupbrackets

used jQuery.each -> in cleanupbrackets

--- client/src/Screen/GoalScreen.coffee
+++ client/src/Screen/GoalScreen.coffee
@@ -254,7 +254,7 @@

   cleanUpBrackets: () ->
     thisReference = this
-    for d in $('#added-goal li')
+    $('#added-goal li').each (index, d) ->
       leftBracketType = $(d).prev().attr('data-bracket')
       rightBracketType = $(d).attr('data-bracket')

@@ -278,14 +278,14 @@
           $(d).prev().attr('data-bracket', 'right')

         # Special case for when there is just one dot left
-        if(@numberDots == 2)
+        if(thisReference.numberDots == 2)
           $(d).prev().remove()
-          @numberDots--
+          thisReference.numberDots--

         # Always remove the right dot/bracket
-        @numberDots--
+        thisReference.numberDots--
         $(d).remove()
         # Now try and clean up again
-        @cleanUpBrackets()
-
-
+        thisReference.cleanUpBrackets()
+
+


raise ping check interval

raise ping check interval

--- salvus/client.coffee
+++ salvus/client.coffee
@@ -257,7 +257,7 @@
         # this many ms, or client will start freaking out and trying
         # to reconnect.  This limits things like max size of files we
         # can edit.
-        @_ping_check_interval = 20000  # 20 seconds.
+        @_ping_check_interval = 45000  # 45 seconds.
         @_ping_check_id = setInterval((()=>@ping(); @_ping_check()), @_ping_check_interval)

     close: () ->


Members: increase member listing size to 20

--- client/Social/Members/AppController.coffee
+++ client/Social/Members/AppController.coffee
@@ -31,7 +31,7 @@
       listControllerClass   : MembersListViewController
       useHeaderNav          : yes
       noItemFoundText       : "There is no member."
-      limitPerPage          : 10
+      limitPerPage          : 20
       delegate              : this
       help                  :
         subtitle            : "Learn About Members"


Client: Sort by authority correctly.

Client: Sort by authority correctly.

--- client/app/js/collections/user_list.coffee
+++ client/app/js/collections/user_list.coffee
@@ -2,8 +2,8 @@
   model: User

   comparator: (a, b) =>
-    aAuthority = a.authority
-    bAuthority = b.authority
+    aAuthority = a.get('authority')
+    bAuthority = b.get('authority')
     aName = a.id.toLowerCase()
     bName = b.id.toLowerCase()
     if aAuthority < bAuthority      then -1


Add description to the common assertions to make failure messages more clear

--- spec/SpecHelper.coffee
+++ spec/SpecHelper.coffee
@@ -55,7 +55,7 @@
     verifySwitchingAggressively src, expectedEvents

 @expectPropertyEvents = (src, expectedEvents, {unstable, extraCheck} = {}) ->
-  expect(expectedEvents.length > 0).to.deep.equal(true)
+  expect(expectedEvents.length > 0).to.deep.equal(true, "at least one expected event is specified")
   verifyPSingleSubscriber src, expectedEvents, extraCheck
   verifyPLateEval src, expectedEvents
   if not unstable
@@ -113,7 +113,7 @@
       if event.isEnd()
         done()
       else
-        expect(event instanceof Bacon.Initial).to.deep.equal(false)
+        expect(event instanceof Bacon.Initial).to.deep.equal(false, "no Initial events")
         events.push(event)

@@ -123,7 +123,7 @@
       if event.isEnd()
         done()
       else
-        expect(event instanceof Bacon.Initial).to.deep.equal(false)
+        expect(event instanceof Bacon.Initial).to.deep.equal(false, "no Initial events")
         events.push(toValue(event))

 # get each event with new subscriber
@@ -134,7 +134,7 @@
         if event.isEnd()
           done()
         else
-          expect(event instanceof Bacon.Initial).to.deep.equal(false)
+          expect(event instanceof Bacon.Initial).to.deep.equal(false, "no Initial events")
           events.push(toValue(event))
           src.subscribe(newSink())
           Bacon.noMore
@@ -163,7 +163,7 @@
             ended = true
             done()
           else
-            expect(event instanceof Bacon.Initial).to.deep.equal(false)
+            expect(event instanceof Bacon.Initial).to.deep.equal(false, "no Initial events")
             events.push(toValue(event))
             prevUnsub = unsub
             noMoreExpected = true
@@ -182,7 +182,7 @@
     events = []
     before ->
       src = srcF()
-      expect(src instanceof Bacon.EventStream).to.equal(true)
+      expect(src instanceof Bacon.EventStream).to.equal(true, "is an EventStream")
     before (done) ->
       collectF(src, events, done)
     it "outputs expected value in order", ->
@@ -197,7 +197,7 @@
     events = []
     before ->
       src = srcF()
-      expect(src instanceof Bacon.EventStream).to.equal(true)
+      expect(src instanceof Bacon.EventStream).to.equal(true, "is an EventStream")
     before (done) ->
       newSink = ->
         unsub = null
@@ -205,7 +205,7 @@
           if event.isEnd()
             done()
           else
-            expect(event instanceof Bacon.Initial).to.deep.equal(false)
+            expect(event instanceof Bacon.Initial).to.deep.equal(false, "no Initial events")
             events.push(toValue(event))
             unsub() if unsub?
             unsub = src.subscribe(newSink())


Removed wizard loading code from home view, for now anyway.

--- app/views/home_view.coffee
+++ app/views/home_view.coffee
@@ -8,10 +8,6 @@
 module.exports = class HomeView extends View
   id: 'home-view'
   template: template
-
-  events:
-    'mouseover #beginner-campaign': 'onMouseOverButton'
-    'mouseout #beginner-campaign': 'onMouseOutButton'

   constructor: ->
     super(arguments...)
@@ -34,9 +30,6 @@
     @$el.find('.modal').on 'shown.bs.modal', ->
       $('input:visible:first', @).focus()

-    @wizardType = ThangType.wizardType
-    if @wizardType.loaded then @initCanvas else @wizardType.once 'sync', @initCanvas, @
-
     # Try to find latest level and set "Play" link to go to that level
     if localStorage?
       lastLevel = localStorage["lastLevel"]
@@ -48,57 +41,4 @@
           href = href.join("/")
           playLink.attr("href", href)
     else
-      console.log("TODO: Insert here code to get latest level played from the database. If this can't be found, we just let the user play the first level.")
-
-  initCanvas: ->
-    @stage = new createjs.Stage($('#beginner-campaign canvas', @$el)[0])
-    @createWizard()
-
-  turnOnStageUpdates: ->
-    clearInterval @turnOff
-    @interval = setInterval(@updateStage, 40) unless @interval
-
-  turnOffStageUpdates: ->
-    turnOffFunc = =>
-      clearInterval @interval
-      clearInterval @turnOff
-      @interval = null
-      @turnOff = null
-    @turnOff = setInterval turnOffFunc, 2000
-
-  createWizard: (scale=3.7) ->
-    spriteOptions = thangID: "Beginner Wizard", resolutionFactor: scale
-    @wizardSprite = new WizardSprite @wizardType, spriteOptions
-    wizardDisplayObject = @wizardSprite.displayObject
-    wizardDisplayObject.x = 70
-    wizardDisplayObject.y = 120
-    wizardDisplayObject.scaleX = wizardDisplayObject.scaleY = scale
-    wizardDisplayObject.scaleX *= -1
-    @wizardSprite.queueAction 'idle'
-    @wizardSprite.update()
-    @stage.addChild wizardDisplayObject
-    @stage.update()
-
-  onMouseOverButton: ->
-    @turnOnStageUpdates()
-    @wizardSprite?.queueAction 'cast'
-
-  onMouseOutButton: ->
-    @turnOffStageUpdates()
-    @wizardSprite?.queueAction 'idle'
-
-  updateStage: =>
-    @stage?.update()
-
-  willDisappear: ->
-    super()
-    clearInterval(@interval) if @interval
-    @interval = null
-
-  didReappear: ->
-    super()
-    @turnOnStageUpdates()
-
-  destroy: ->
-    @wizardSprite?.destroy()
-    super()
+      console.log("TODO: Insert here code to get latest level played from the database. If this can't be found, we just let the user play the first level.")


Use skinny arrow for onPath callback

Use skinny arrow for onPath callback

--- src/stdlib/fs-utils.coffee
+++ src/stdlib/fs-utils.coffee
@@ -93,7 +93,7 @@

   listTreeSync: (rootPath) ->
     paths = []
-    onPath = (childPath) =>
+    onPath = (childPath) ->
       paths.push(childPath)
       true
     @traverseTreeSync(rootPath, onPath, onPath)


fixes output

fixes output

--- src/steroids/Deploy.coffee
+++ src/steroids/Deploy.coffee
@@ -106,11 +106,13 @@
   updateConfigurationFile: (callback)->
     # util.log "Updating #{paths.application.configs.cloud}"

-    config = new CloudConfig
+    cloudConfig = new CloudConfig
       id: @cloudApp.id
       identification_hash: @cloudApp.identification_hash

-    config.saveSync()
+    cloudConfig.saveSync()
+
+    config = cloudConfig.getCurrentSync()

     util.log "Deployment complete"


multiple filters

multiple filters

--- components/filter/artworks_nav/view.coffee
+++ components/filter/artworks_nav/view.coffee
@@ -6,6 +6,7 @@
   initialize: (options) ->
     _.extend @, options
     @counts.on 'sync', @renderCounts
+    @params.on 'change', @renderActive

   renderCounts: =>
     for range, val of @counts.get('price_range')
@@ -13,9 +14,15 @@
     for size, val of @counts.get('dimension')
       @$("a[data-size='#{size}'] .filter-dropdown-count").html '(' + val + ')'

-  toggleActive: (el) ->
-    @$('.is-active').removeClass 'is-active'
-    $(el).addClass('is-active')
+  renderActive: =>
+    @$('.is-active').removeClass('is-active')
+    if @params.keys().length is 0
+      @$('.filter-artworks-nav-allworks').addClass('is-active')
+    else
+      @$("a[data-range='#{@params.get('price_range')}']")
+        .addClass('is-active').closest('.filter-dropdown').addClass('is-active')
+      @$("a[data-size='#{@params.get('dimension')}']")
+        .addClass('is-active').closest('.filter-dropdown').addClass('is-active')

   events:
     'click .filter-artworks-nav-allworks': 'allWorks'
@@ -25,20 +32,16 @@
     'click .filter-dropdown a': 'checkDropdownItem'

   allWorks: ->
-    @toggleActive @$('.filter-artworks-nav-allworks')
     @params.clear()

   filterPrice: (e) ->
-    @toggleActive @$('.filter-artworks-nav-price')
-    @params.set { price_range: $(e.target).data 'range' }
+    @params.set { price_range: $(e.currentTarget).data 'range' }

   filterMedium: (e) ->
-    @toggleActive @$('.filter-artworks-nav-medium')
-    @params.set { medium: $(e.target).data 'medium' }
+    @params.set { medium: $(e.currentTarget).data 'medium' }

   filterSize: (e) ->
-    @toggleActive @$('.filter-artworks-nav-size')
-    @params.set { dimension: $(e.target).data 'size' }
+    @params.set { dimension: $(e.currentTarget).data 'size' }

   checkDropdownItem: (e) ->
     $(e.currentTarget)


Update models/branches/local-branch.coffee

--- lib/models/branches/local-branch.coffee
+++ lib/models/branches/local-branch.coffee
@@ -1,25 +1,35 @@
+git    = require '../../git'
 Branch = require './branch'

-{git} = require '../../git'
+# Public: LocalBranch class that extends the {Branch} prototype.
+class LocalBranch extends Branch

-module.exports =
-class LocalBranch extends Branch
   remote: false
-
   local: true

+  # Public: Return the 'unpushed' property.
+  #
+  # Returns the property as {String}.
   unpushed: ->
     @get 'unpushed'

+  # Public: Delete the branch.
   delete: ->
-    git.git "branch -D #{@name()}"
+    git.cmd 'branch', {D: true}, @name()

   # TODO tracking branch or something?
   remoteName: -> ''

+  # Public: Checkout the branch.
+  #
+  # callback - The callback as {Function}.
   checkout: (callback) ->
-    git.git "checkout #{@localName()}"
+    git.checkout @localName()

-  push: (remote) ->
-    remote ||= "origin #{@name()}"
-    git.remotePush remote
+  # Public: Push the branch to remote.
+  #
+  # remote - The remote to push to as {String}.
+  push: (remote='origin') ->
+    git.cmd 'push', [remote, @name()]
+
+module.exports = LocalBranch


Default to empty arrays for user and bundled package paths

--- spec/spec-suite.coffee
+++ spec/spec-suite.coffee
@@ -30,9 +30,9 @@
       'user'

   # Run bundled package specs
-  requireSpecs(packagePath) for packagePath in packagePaths.bundled
+  requireSpecs(packagePath) for packagePath in packagePaths.bundled ? []
   setSpecType('bundled')

   # Run user package specs
-  requireSpecs(packagePath) for packagePath in packagePaths.user
+  requireSpecs(packagePath) for packagePath in packagePaths.user ? []
   setSpecType('user')


appeas mr travis

appeas mr travis

--- apps/artists/client.coffee
+++ apps/artists/client.coffee
@@ -18,7 +18,7 @@
     $(window).on 'resize', @resize

     # Wait for the first image to load before enabling anything
-    @$el.imagesLoaded().progress _.once(@updateValues)
+    @$el.imagesLoaded?().progress _.once(@updateValues)

   updateValues: (e) =>
     @$panels ||= @$('.afc-artist')


integrates channel drag view

integrates channel drag view

--- apps/channel/client.coffee
+++ apps/channel/client.coffee
@@ -11,6 +11,7 @@
 NewBlockView = require '../../components/new_block/client/new_block_view.coffee'
 ChannelCollaborationView = require './client/channel_collaboration_view.coffee'
 ChannelFileDropView = require './client/channel_file_drop_view.coffee'
+ChannelDragView = require './client/channel_drag_view.coffee'
 ChannelVisibilityView = require '../../components/channel_visibility/client/channel_visibility_view.coffee'
 ShareLinkView = require '../../components/share_link/client/share_link_view.coffee'
 ChannelExportView = require '../../components/channel_export/client/channel_export_view.coffee'
@@ -73,6 +74,7 @@
       @setupExportView()
       @setupEditTitleView()
       @setupEditDescriptionView()
+      @setUpDragView()

     @maybeSetEmpty()

@@ -116,6 +118,13 @@
         model: @channel
         blocks: @blocks
         autoRender: should_render
+
+  setUpDragView: ->
+    @dragView = new ChannelDragView
+      el: @$('.grid')
+      model: @channel
+
+    @dragView.setupDragAndDrop()

   setupVisibilityView: ->
     @channelVisibilityView = new ChannelVisibilityView


updated the slugs.coffee to give a proper slug to a team

--- scripts/slugs.coffee
+++ scripts/slugs.coffee
@@ -8,22 +8,41 @@
 async = require 'async'
 Team = mongoose.model 'Team'

+
 Team.find {}, (err, teams) ->
   return err if err

-  async.mapSeries teams, (team, next) ->
+  async.mapSeries teams, (team, next) ->
     return next(null, team) if team.peopleIds.length is 0  # skip empty
     # return next(null, team) if team.slug is team.slugBase
     # return next(null, team) # don't modify right now

-    if team.name == 'ヽ( ´¬`)ノ'
+    changes =
+      "-1": "theteam"
+      "3": "heart3"
+      "-3": "annteens"
+      "-4": "tigercat"
+      # "-2": "hiten" This team is still empty
+
+
+    if team.name == "ヽ( ´¬`)ノ" #special that there was no slug previously
       old = team.slug
       team.slug = 'waving'
       team.save (err) ->
         console.log "#{old.red} -> #{team.slug.green} (#{team.name})"
         return next err, team
-    else
-      return next(null, team) # If it's not one of the odd cases, it's ok :)
+    else
+      if changes[team.slug]
+        console.log "Slug is #{team.slug}"
+        old = team.slug
+        team.slug = changes[team.slug]
+        team.save (err) ->
+          console.log "#{old.red} -> #{team.slug.green} (#{team.name})"
+          return next err, team
+        # return next err, team
+      else
+        return next(null, team) # If it's not one of the odd cases, it's ok :)
+
   , (err, teams) ->
     console.log err if err


Include protocol field with raw WebSocket connections.

--- src/trans-websocket.coffee
+++ src/trans-websocket.coffee
@@ -102,6 +102,8 @@

 # Inheritance only for decorateConnection.
 class RawWebsocketSessionReceiver extends transport.Session
+    protocol: "websocket-raw"
+
     constructor: (req, conn, server, @ws) ->
         @prefix = server.options.prefix
         @readyState = Transport.OPEN


Clear ping/pong timer when ws closed.

Clear ping/pong timer when ws closed.

Signed-off-by: Manson Li <f2a598d7717cc6757bef28b84a9ab02e5efc6048@matchstick.tv>

--- lib/dial/session/ReceiverSession.coffee
+++ lib/dial/session/ReceiverSession.coffee
@@ -27,6 +27,7 @@
             throw new Error "cannot create a ReceiverSession with a null channel!!!"

         super sessionManager, token, appId
+        @ppTimerId = null
         @timeout = Config.RECEIVER_SESSION_TIMEOUT
         @tag = "ReceiverSession"

@@ -114,7 +115,7 @@
             interval: Config.RECEIVER_SESSION_PP_INTERVAL
         @_onHeartbeat "ping"
         @triggerTimer()
-        setInterval ( =>
+        @ppTimerId = setInterval ( =>
                 @_onHeartbeat "ping"
             ), Config.RECEIVER_SESSION_PP_INTERVAL

@@ -145,6 +146,8 @@
             if app and (app.getAppId() is @appId)
                 app.terminate()
         @_clearTimer()
+        if @ppTimerId
+            clearTimeout @ppTimerId
         @sessionManager.clearReceiverSession()
         @channel.close()


Fix main loop not being resumed when something has finished loading.

--- engine/main_loop.coffee
+++ engine/main_loop.coffee
@@ -84,6 +84,12 @@
         @frame_duration = frame_duration = time - @last_time
         @last_time = time

+        task_time = time
+        max_task_time = MAX_TASK_DURATION + time
+        while (task_time < max_task_time) and (@_frame_callbacks.length != 0)
+            @_frame_callbacks.shift()()
+            task_time = performance.now()
+
         if not @enabled
             return
         @last_frame_durations[@_fdi] = frame_duration
@@ -91,12 +97,6 @@

         # Limit low speed of logic and physics
         frame_duration = Math.min(frame_duration, MAX_FRAME_DURATION)
-
-        task_time = time
-        max_task_time = MAX_TASK_DURATION + time
-        while (task_time < max_task_time) and (@_frame_callbacks.length != 0)
-            @_frame_callbacks.shift()()
-            task_time = performance.now()

         for scene_name in @context.loaded_scenes
             scene = @context.scenes[scene_name]


Handle the initial view change and concepts reset

In order to render the selected items, both the view and concepts
collection need to be present. Listeners for each are defined to fire
once when they trigger the initial state change.

--- src/coffee/cilantro/ui/concept/columns.coffee
+++ src/coffee/cilantro/ui/concept/columns.coffee
@@ -90,8 +90,8 @@
                 throw new Error 'concepts collection required'

         serializeData: ->
-            concept = @data.concepts.get(@model.get('concept'))
-            return name: concept.get('name')
+            if (concept = @data.concepts.get(@model.get('concept')))
+                return name: concept.get('name')

         events:
             'click button': 'triggerRemove'
@@ -178,6 +178,11 @@

             # Create the initial facets collection
             @data.facets = @options.view.facets.clone()
+
+            # Listen to for sync event to synchronize with the server
+            @listenToOnce(@data.view, 'change:json', @resetFacets)
+            @listenToOnce(@data.concepts, 'reset', @resetFacets)
+
             @$el.modal(show: false)

         resetFacets: ->


:bug: Fix invalid range access on display buffer markers

--- lib/mixins/decoration-management.coffee
+++ lib/mixins/decoration-management.coffee
@@ -25,6 +25,7 @@
     marker = @getMarker(marker.id)

     @decorationMarkerDestroyedSubscriptions[marker.id] ?= @subscribe marker, 'destroyed', =>
+
       @removeAllDecorationsForMarker(marker)

     @decorationMarkerChangedSubscriptions[marker.id] ?= @subscribe marker, 'changed', (event) =>
@@ -52,10 +53,11 @@
     decoration

   stackDecorationChanges: (decoration) ->
-    return unless decoration.marker.range?
+    range = decoration.marker.getScreenRange()
+    return unless range?

-    startScreenRow = decoration.marker.range.start.row
-    endScreenRow = decoration.marker.range.end.row
+    startScreenRow = range.start.row
+    endScreenRow = range.end.row
     screenDelta = (@lastRenderedScreenRow - @firstRenderedScreenRow) - (endScreenRow - startScreenRow)

     changeEvent =


simplify insert

simplify insert

--- client/controllers/menus.coffee
+++ client/controllers/menus.coffee
@@ -13,13 +13,8 @@
 			user: $rootScope.currentUser
 			order_items: []

-		good = (inserts) ->
+		saved.then (inserts) ->
 			id = _.first(inserts)._id
 			console.log "inserted new order with id:#{id}"
 			$state.go 'order', {id: id}
-		bad = (reason) ->
-			console.log reason
-
-		saved.then good, bad
-
 ]


Place enemy properly.

Place enemy properly.

--- src/components/entities/BattleShip.coffee
+++ src/components/entities/BattleShip.coffee
@@ -118,7 +118,7 @@

     @attach Crafty.e('2D, DroneShipCorePlace').attr(
       x: @x + 900
-      y: @y + 50
+      y: @y - 125
       z: -8
       w: 15
       h: 2


Revert fix for Firefox

Revert fix for Firefox

- Reverts commit 83202193f3d898c874cc1f6c14bef254755730bc.
- Reverts commit af142f2e0d4227f4cdf32ccfd36d39983cdf179e.

The fix for Firefox breaks Chrome, and didn't really fix Firefox anyway.

The reason Firefox was broken is because we make a synchronous request
for user details. We should not be doing this anyway. The decision was
made to revert the Firefox "fix" and focus on making the user request
async instead.

--- templates/embed/Carnival.coffee
+++ templates/embed/Carnival.coffee
@@ -21,8 +21,7 @@

   @get: (url, callback) ->
     request = new XMLHttpRequest
-    request.beforeSend = (xhr) ->
-      xhr.withCredentials = true
+    request.withCredentials = true
     request.open('GET', url, true)
     request.setRequestHeader('Content-Type', 'application/json')
     request.onload = () ->
@@ -32,8 +31,7 @@

   @post: (url, data, callback) ->
     request = new XMLHttpRequest()
-    request.beforeSend = (xhr) ->
-      xhr.withCredentials = true
+    request.withCredentials = true
     request.open('POST', url, true)
     request.setRequestHeader('Content-Type', 'application/json')
     request.onload = () ->
@@ -56,8 +54,7 @@

   @getUser: ->
     request = new XMLHttpRequest
-    request.beforeSend = (xhr) ->
-      xhr.withCredentials = true
+    request.withCredentials = true
     request.open('GET', '@{UserR}', false)
     request.setRequestHeader('Accept', 'application/json')
     request.send()


Trim addresses prior to parsing them, ignore blank addresses and some other various small fixes.

--- client/tx/lib/tx-builder.coffee
+++ client/tx/lib/tx-builder.coffee
@@ -67,6 +67,7 @@
       unspent = _unspent
       balance = sum_inputs unspent
       $('.balance').text (formatValue balance)+' BTC'
+      do update_change
   do update_balance

   cb_success = cb.bind null, null
@@ -93,7 +94,7 @@

   # Get raw transaction
   el.find('.show-rawtx').click ->
-    try show_rawtx_dialog build_tx unspent, el
+    try show_rawtx_dialog build_tx()
     catch e then display_error e

   # Auto-update the change amount
@@ -125,14 +126,21 @@
     # Read outputs from DOM
     el.find('.address').each ->
       $this = $ this
-      amount = +parseValue $this.find('[name=value]').val()
-      try out_script = create_out_script $this.find('[name=address]').val()
+      # Ignore empty addresses
+      return unless address = $this.find('[name=address]').val().trim()
+
+      amount = +parseValue $this.find('[name=value]').val().trim()
+      try out_script = create_out_script address
       catch err
         $this.find('[name=address]').focus()
         throw err
       tx.addOutput new TransactionOut
-        script: create_out_script $this.find('[name=address]').val()
+        script: out_script
         value: amount
+
+    unless tx.outs.length
+      throw new Error 'No output address provided'
+
     change = tx.total_in - (sum_inputs tx.outs) - get_fee()
     if change > 0
       tx.addOutput new TransactionOut
@@ -230,6 +238,6 @@
 sum_value_inputs = ($els) ->
   sum_inputs $els
     .filter(->!!@value)
-    .map(-> +parseValue @value).get()
+    .map(-> +parseValue @value.trim()).get()

 module.exports = tx_builder


Simple quotes instead of double ones

Simple quotes instead of double ones

--- assets/js/_networks.coffee
+++ assets/js/_networks.coffee
@@ -3,30 +3,30 @@
     {
       name: 'mt-facebook'
       imagePath: '//mojotech-static.s3.amazonaws.com/facebook@2x.png'
-      shareUrl: (url) -> "//www.facebook.com/sharer/sharer.php?u=#{url}"
+      shareUrl: (url) -> '//www.facebook.com/sharer/sharer.php?u=#{url}'
       selected: true
     },
     {
       name: 'mt-twitter'
       imagePath: '//mojotech-static.s3.amazonaws.com/twitter@2x.png'
-      shareUrl: (url, text) -> "//twitter.com/intent/tweet?text=#{text}&url=#{url}"
+      shareUrl: (url, text) -> '//twitter.com/intent/tweet?text=#{text}&url=#{url}'
       selected: true
     },
     {
       name: 'mt-linkedin'
       imagePath: '//mojotech-static.s3.amazonaws.com/linkedin@2x.png'
-      shareUrl: (url, text) -> "//www.linkedin.com/shareArticle?mini=true&url=#{url}&summary=#{text}"
+      shareUrl: (url, text) -> '//www.linkedin.com/shareArticle?mini=true&url=#{url}&summary=#{text}'
     },
     {
       name: 'mt-google'
       imagePath: '//mojotech-static.s3.amazonaws.com/google@2x.png'
-      shareUrl: (url) -> "//plus.google.com/share?url=#{url}"
+      shareUrl: (url) -> '//plus.google.com/share?url=#{url}'
     },
     {
       name: 'mt-pinterest'
       imagePath: '//mojotech-static.s3.amazonaws.com/pinterest@2x.png'
-      shareUrl: (url, text, mediaUrl) -> "//www.pinterest.com/pin/create/button/?url=#{url}&media=#{mediaUrl}&guid=1234&description=#{text}"
+      shareUrl: (url, text, mediaUrl) -> '//www.pinterest.com/pin/create/button/?url=#{url}&media=#{mediaUrl}&guid=1234&description=#{text}'
     }
   ])

-  App.reqres.setHandler "supportedNetworks", -> supportedNetworks
+  App.reqres.setHandler 'supportedNetworks', -> supportedNetworks


All /qiprofile files resolve to the SPA index.html.

--- server/server.coffee
+++ server/server.coffee
@@ -94,47 +94,10 @@
 restUrl = process.env.QIREST_HOST or 'localhost'
 server.use '/qirest', rest(restUrl)

-# # Serve the static files from root.
-# server.get '/static/*', (req, res) ->
-#   path = root + req.path.replace('/static', '')
-#   res.sendFile path
-#
-# # Serve the partial HTML files.
-# server.get '/partials/*', (req, res) ->
-#   res.sendFile "#{root}/#{req.path}.html"
-#
-# # The app javascript files.
-# server.get '/javascripts*', (req, res) ->
-#   res.sendFile(root + req.path)
-#
-# # Since qiprofile is an Angular Single-Page Application,
-# # serve the landing page for all qiprofile routes.
-# # The qiprofile application then resolves the URL on the
-# # client and requests the partial.
-# # TODO - is this necessary with Angular 2?
-# server.get '/qiprofile', (req, res) ->
-#   res.sendFile "#{root}/index.html"
-
-
 # Strip the app prefix from the request URL and serve up the
 # file in the root directory.
-server.get '/qiprofile(/*)?', (req, res) ->
-  tail = req.path.substr('/qiprofile'.length)
-  if not tail then tail = '/index.html'
-  res.sendFile "#{ root }#{ tail }"
-
-# Work around the following jspm beta bug:
-#   jspm internally tries to fetch .json files by file path
-#   rather than web app url, e.g.:
-#      /public/lib/npm/typescript@1.8.10.json
-#   rather than:
-#      /javascripts/lib/npm/typescript@1.8.10.json
-# The work-around is to match on public in the server
-# request.
-# TODO - isolate and resolve or report this bug to jspm.
-# server.get '/public(/*)?', (req, res) ->
-#   tail = req.path.substr('/public'.length)
-#   res.sendFile "#{ root }#{ tail }"
+server.get '/qiprofile/*', (req, res) ->
+  res.sendFile "#{ root }/index.html"

 # Kludge to work around repeat requests. See the app
 # error.coffee FIXME.


Fix a bad replace of globals

Fix a bad replace of globals

--- src/elements/ListView/ListViewTreeNode.coffee
+++ src/elements/ListView/ListViewTreeNode.coffee
@@ -109,7 +109,7 @@
 		lines.push(padding.join("")+@dumpString())
 		if @children
 			for c in @children
-				c.CUI.util.dump(lines, depth+1)
+				c.dump(lines, depth+1)

 		if depth==0
 			return "\n"+lines.join("\n")+"\n"


Added basic YouTube API query subroutine to script

--- src/youtube-tracker.coffee
+++ src/youtube-tracker.coffee
@@ -2,11 +2,10 @@
 #   A Hubot script for tracking YouTube
 #
 # Configuration:
-#   LIST_OF_ENV_VARS_TO_SET
+#   GOOGLE_API_KEY is required to make use of this script.
 #
 # Commands:
-#   hubot hello - <what the respond trigger does>
-#   orly - <what the hear trigger does>
+#   <none>
 #
 # Notes:
 #   <optional notes required for the script>
@@ -14,9 +13,27 @@
 # Author:
 #   Karl Birch <mbwkarl@gmail.com>

+GOOGLE_API_KEY = process.env.GOOGLE_API_KEY
+
+querystring = require 'querystring'
+url = require 'url'
+
 module.exports = (robot) ->
-  robot.respond /hello/, (res) ->
-    res.reply "hello!"
+  if not GOOGLE_API_KEY?
+    return robot.logger.error "hubot-youtube-tracker: Missing GOOGLE_API_KEY in environment.  Please set and try again."

-  robot.hear /orly/, ->
-    res.send "yarly"
+  robot.respond /(?:tracker|ytt)(?: chan)? (.*)/i, (msg) ->
+    query = msg.match[1]
+
+QueryYoutube = (res, api, params = {}, handler) ->
+  params.key = GOOGLE_API_KEY
+  res.http("https://www.googleapis.com/youtube/v3#{api}")
+    .query(params)
+    .get() (err, httpRes, body) ->
+      if err or httpRes.statusCode isnt 200
+        err = "503 Service Unavailable" if httpStatus is 503
+        res.reply "An error occurred while attemping to process this request."
+        return res.robot.logger.error "hubot-youtube-tracker: #{err}"
+
+      handler JSON.parse(body)
+


* Guarantee that Date objects are cloned
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch and 'origin/master' have diverged,
# and have 3 and 1 different commit each, respectively.
#
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   app/assets/javascripts/backbone.rails_store.js.coffee
#

RailsStor.commit emits "commit:before" e "commit:finished"

--- app/assets/javascripts/backbone.rails_store.js.coffee
+++ app/assets/javascripts/backbone.rails_store.js.coffee
@@ -349,11 +349,14 @@
       error - Called upon error

     Triggers:
+      commit:before - before starting saving process
       commit:start - just after starting server request
       commit:done - after successful server request response
       commit:failed - if something wrong happened
+      commit:finished - after commit process (success or error)
   ###
   commit: (options) ->
+    @trigger('commit:before', @)
     options = options || {}
     commitData = {}
     _.each @_changedModels, (models, modelType) =>
@@ -427,6 +430,8 @@
           console.log(e)
           @trigger('comm:fatal', e)
           @trigger('commit:failed', {errors: "Communication error!"})
+        finally
+          @trigger('commit:finished', @)
           throw e
       error: =>
         # TODO: i18n
@@ -1039,11 +1044,13 @@
     # Process attributeModifiers
     if not _.isUndefined(@attributes[attr]) and @attributeModifiers[attr] and @attributeModifiers[attr].getConverter
       if @attributeModifiers[attr].getConverter == 'DateTime'
+        return '' if _.isNull(@attributes[attr])
         if @attributeModifiers[attr].options and @attributeModifiers[attr].options.format
           return @attributes[attr].toString(@attributeModifiers[attr].options.format)
         else
           return @attributes[attr].toString(@_store.getBuiltinModifierFormat('DateTime'))
       else if @attributeModifiers[attr].getConverter == 'Date'
+        return '' if _.isNull(@attributes[attr])
         if @attributeModifiers[attr].options and @attributeModifiers[attr].options.format
           return @attributes[attr].toString(@attributeModifiers[attr].options.format)
         else


added a placeholder during the upload.

added a placeholder during the upload.

--- app/assets/javascripts/components/post_view.js.coffee
+++ app/assets/javascripts/components/post_view.js.coffee
@@ -40,15 +40,22 @@
     @ui.markdownPreview.html(html)

   _fileAdded: (ev, data) ->
-    console.log 'file added'
-    console.log data
+    text = @ui.markdownText.val()
+    text += @_placeholderForFile(data.files[0].name)
+    @ui.markdownText.val(text)

   _uploadCompleted: (ev, data) ->
     file = data.result.getElementsByTagName('Location')[0].firstChild.nodeValue
     text = @ui.markdownText.val()
-    text += "![](#{file})"
+    text = text.replace(@_placeholderForFile(data.files[0].name), @_markdownForImage(file))
     @ui.markdownText.val(text)
     @ui.markdownText.change()
+
+  _placeholderForFile: (filename) ->
+    "![Uploading #{filename} ...]()"
+
+  _markdownForImage: (file) ->
+    "![](#{file})"

 $(document).on 'ready', PostView.registerViews
 $(document).on 'page:load', PostView.registerViews


OX-723 Expect 16 bit texture

OX-723 Expect 16 bit texture

--- app/assets/javascripts/oxalis/geometries/plane.coffee
+++ app/assets/javascripts/oxalis/geometries/plane.coffee
@@ -85,7 +85,7 @@

       void main() {
         vec4 volumeColor = texture2D(volumeTexture, vUv * repeat + offset);
-        float id = (volumeColor[0] * 255.0);
+        float id = (volumeColor[0] * 65535.0);
         float golden_ratio = 0.618033988749895;

         /* Color map (<= to fight rounding mistakes) */


fix in file

fix in file

--- app/assets/javascripts/spree/backend/taxons.js.coffee
+++ app/assets/javascripts/spree/backend/taxons.js.coffee
@@ -41,7 +41,7 @@
         id: e.val,
         token: Spree.api_key
       success: (data) ->
-        el.empty();
+        el.empty()
         if data.products.length == 0
           $('#sorting_explanation').hide()
           $('#taxon_products').html("<h4>" + Spree.translations.no_results + "</h4>")


updated row-logic for calculating nSurviving list since its now an optional field

--- shared/shared.coffee
+++ shared/shared.coffee
@@ -423,7 +423,15 @@
     if e then e.endpointGroups.map((v) -> v.nStart).join(", ") else "NR"

 share.getAnimalNSurvivings = (e) ->
-    if e then  e.endpointGroups.map((v) -> v.nSurviving).join(", ") else "NR"
+    if not e? or not e.endpointGroups? then return "NR"
+    numeric = false
+    survivings = e.endpointGroups.map (eg) ->
+        if eg.nSurviving? and eg.nSurviving isnt ""
+            numeric = true
+            return eg.nSurviving
+        else
+            return "NR"
+    return if numeric then survivings.join(", ") else "NR"

 share.getAnimalEndpointIncidents = (egs) ->
     if _.pluck(egs, "incidence").join("").length>0


Integration tests should read creds from environment followed by configuration file

--- test/integration/helpers.coffee
+++ test/integration/helpers.coffee
@@ -14,6 +14,10 @@
 helpers = require('../helpers');

 AWS = helpers.AWS
-AWS.configuration = new AWS.FileSystemConfig(__dirname + '/../../configuration')
+
+configFile = __dirname + '/../../configuration'
+creds = new AWS.FileSystemCredentials(configFile)
+AWS.configuration = new AWS.FileSystemConfig(configFile)
+AWS.configuration.credentials = new AWS.DefaultCredentials(creds)

 module.exports = helpers


Don’t change Arguments column label to Arg 1, Arg 2, etc. — leave it as Arguments all the time.

--- coffee/PartialCallsComponent.coffee
+++ coffee/PartialCallsComponent.coffee
@@ -34,16 +34,11 @@
           tr {},
             th { key: 'receiver' }, 'Receiver'
             th { key: 'method' }, 'Method'
-            _.times (max_num_cols - 2), (arg_num) ->
-              th { key: "arg#{arg_num}" },
-                switch arg_num
-                  when 0 then '1st Arg.'
-                  when 1 then '2nd Arg.'
-                  when 2 then '3rd Arg.'
-                  else        "#{arg_num + 1}th Arg."
-            th { key: 'extra-space', className: 'extra-space' },
-              if max_num_cols <= 2
-                'Arguments'
+            th
+              key: 'extra-space'
+              className: 'extra-space'
+              colSpan: max_num_cols - 2 + 1 # 1 is for the extra space
+              'Arguments'

         tbody {},
           if calls.length == 0


Port ApplyVignette to Process API

Port ApplyVignette to Process API

--- components/ApplyVignette.coffee
+++ components/ApplyVignette.coffee
@@ -4,21 +4,15 @@
   c = new noflo.Component
   c.description = 'Apply a vignette effect to a given image.'
   c.icon = 'file-image-o'
-
-  c.canvas = null
-
-  c.inPorts.add 'canvas', (event, payload) ->
-    return unless event is 'data'
-    c.canvas = payload
-    c.computeEffect()
-
-  c.outPorts.add 'canvas'
-
-  c.computeEffect = ->
-    return unless c.outPorts.canvas.isAttached()
-    return unless c.canvas?
-
-    canvas = c.canvas
+  c.inPorts.add 'canvas',
+    datatype: 'object'
+  c.outPorts.add 'canvas',
+    datatype: 'object'
+  c.forwardBrackets =
+    canvas: ['canvas']
+  c.process (input, output) ->
+    return unless input.hasData 'canvas'
+    canvas = input.getData 'canvas'

     ctx = canvas.getContext '2d'
     width = canvas.width
@@ -48,7 +42,6 @@
     ctx.fillStyle = gradient
     ctx.fillRect 0, 0, width, height

-    c.outPorts.canvas.send canvas
-
-  c
-
+    output.sendDone
+      canvas: canvas
+    return


Port to Process API

Port to Process API

--- components/DatabaseReady.coffee
+++ components/DatabaseReady.coffee
@@ -4,7 +4,8 @@
   c = new noflo.Component
   c.inPorts.add 'in',
     datatype: 'bang'
-    process: (event, payload) ->
-      return unless event is 'data'
-      window.nofloDBReady = true
-  c
+  c.process (input, output) ->
+    return unless input.hasData 'in'
+    input.getData 'in'
+    window.nofloDBReady = true
+    output.done()


Added lines to allow collection soring

Added lines to allow collection soring

--- app/webroot/coffee/collections/collection_list.coffee
+++ app/webroot/coffee/collections/collection_list.coffee
@@ -2,3 +2,11 @@
 # ----------------------
 class arcs.collections.CollectionList extends Backbone.Collection
   model: arcs.models.Collection
+
+  initialize: () ->
+    this.sortVar = 'created'
+
+  comparator: (resource) ->
+    resource.get this.sortVar
+
+