Add test for #137

Add test for #137

--- test/services/s3.spec.coffee
+++ test/services/s3.spec.coffee
@@ -469,6 +469,11 @@
       url = s3.getSignedUrl('getObject', Bucket: 'bucket', Key: 'key', Expires: 60)
       expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=60&Signature=ZJKBOuhI99B2OZdkGSOmfG86BOI%3D')

+    it 'gets a signed URL with expiration and bound bucket parameters', ->
+      s3 = new AWS.S3(paramValidation: true, params: Bucket: 'bucket')
+      url = s3.getSignedUrl('getObject', Key: 'key', Expires: 60)
+      expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=60&Signature=ZJKBOuhI99B2OZdkGSOmfG86BOI%3D')
+
     it 'gets a signed URL with callback', ->
       url = null
       runs ->


Fix test

Fix test

--- test/unit/basics.spec.coffee
+++ test/unit/basics.spec.coffee
@@ -4,5 +4,5 @@

     expect(org.sex).toEqual BioLogica.FEMALE
     expect(org.species.name).toEqual 'Dragon'
-    expect(org.getCharacteristic('horns')).toEqual 'Horns'
+    expect(org.getCharacteristic('horns')).toEqual 'No Horns'


Add onMessage() stub to Chrome extension API mock

--- tests/dom_tests/chrome.coffee
+++ tests/dom_tests/chrome.coffee
@@ -12,9 +12,9 @@
       }
       postMessage: ->
     }
-    onRequest: {
+    onMessage: {
       addListener: ->
     }
-    sendRequest: ->
+    sendMessage: ->
   }
 }


Remove ivars set in constructor

Remove ivars set in constructor

--- lib/snippet.coffee
+++ lib/snippet.coffee
@@ -5,9 +5,6 @@
 class Snippet
   @wordRegex: /[^\s'"<>;]+/

-  name: null
-  prefix: null
-  body: null
   lineCount: null
   tabStops: null


update i18n of zh-tw/settings.cson

update i18n of zh-tw/settings.cson

--- def/zh-tw/settings.cson
+++ def/zh-tw/settings.cson
@@ -151,9 +151,9 @@
         title: "啟動時回復視窗狀態"
         desc: "當此選項勾選時，從桌面捷徑或是從命令列輸入 atom 啟動時會復原所有 Atom 視窗的狀態；否則，Atom 啟動時會開啟一個空白的工作環境。"
         select:
-          no: "no"
-          yes: "yes"
-          always: "always"
+          no: "否"
+          yes: "是"
+          always: "總是"
       }
       {
         _id: 'core.telemetryConsent'


governor: Don't access queue details if not existing

Happens in error cases

--- src/governor.coffee
+++ src/governor.coffee
@@ -27,9 +27,10 @@
     s.metric = role.metric
     s.app = role.app
     details = queueDetails[role.queue]
-    s.consumers = details.consumers
-    s.drainrate = details.drainrate
-    s.fillrate = details.fillrate
+    if details?
+      s.consumers = details.consumers
+      s.drainrate = details.drainrate
+      s.fillrate = details.fillrate

     if not s.current_jobs?
       s.error = new Error "Could not get data for queue: #{role.queue}"


Graph: Emit on addGroup() and removeGroup()

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -217,10 +217,12 @@
   addGroup: (group, nodes, metadata) ->
     @checkTransactionStart()

-    @groups.push
+    g =
       name: group
       nodes: nodes
       metadata: metadata
+    @groups.push g
+    @emit 'addGroup', g

     @checkTransactionEnd()

@@ -231,6 +233,7 @@
       continue unless group
       continue unless group.name is groupName
       @groups.splice @groups.indexOf(group), 1
+      @emit 'removeGroup', group

     @checkTransactionEnd()


Ensure groups have valid metadata

Ensure groups have valid metadata

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -745,7 +745,7 @@

   if definition.groups
     for group in definition.groups
-      graph.addGroup group.name, group.nodes, group.metadata
+      graph.addGroup group.name, group.nodes, group.metadata || {}

   graph.endTransaction 'loadJSON'


updates always run as needed, regardless of it being a new install

--- src/lib/utils.coffee
+++ src/lib/utils.coffee
@@ -508,9 +508,7 @@
   # Create a new instance of `Updater` for `namespace`.
   constructor: (@namespace) ->
     # Indicate whether or not `namespace` existed initially.
-    @isNew        = not @exists()
-    # Indicate whether or not updates should be performed on new installations.
-    @processIfNew = no
+    @isNew = not @exists()

   # Determine whether or not this namespace exists.
   exists: ->
@@ -534,7 +532,7 @@
     store.modify 'updates', (updates) =>
       updates[@namespace] ?= ''
       if updates[@namespace] < version
-        processor?() if not @isNew or @processIfNew
+        processor?()
         updates[@namespace] = version

 # Initialize analytics, logging and updates.


[FIX] karma

[FIX] karma

--- gulp/tasks/karma.coffee
+++ gulp/tasks/karma.coffee
@@ -36,7 +36,7 @@
     ]
     browserify:
       extensions: ['.coffee']
-      transform: ['coffeeify']
+      transform: ['browserify-shim', 'coffeeify']
       prebundle: (bundle) ->
         bundle.plugin(remapify, [
           src: '**/*.coffee'


Display right version in about dialog

Display right version in about dialog

--- src/main/main.coffee
+++ src/main/main.coffee
@@ -11,6 +11,8 @@

 class Main
+
+  VERSION: app.getVersion()

   constructor: ->
     @windows = []
@@ -58,11 +60,11 @@
       message: 'Markn' # String - Content of the message box
       detail: """
       Lightweight markdown viewer
-      v0.0.1
+      v#{@VERSION}
       """ # String - Extra information of the message
       # icon NativeImage

-  openHelp: -> shell.openExternal "https://github.com/minodisk/markn/blob/v#{app.getVersion()}/README.md#readme"
+  openHelp: -> shell.openExternal "https://github.com/minodisk/markn/blob/v#{@VERSION}/README.md#readme"

   onWindowClosed: (e) =>
     window = e.currentTarget


Focus dialog when it opens.

Focus dialog when it opens.

--- limit-marks.coffee
+++ limit-marks.coffee
@@ -24,4 +24,5 @@

   if count > LIMIT
     stopThatDialog.show() unless hasBeenOpened
+    stopThatDialog.el.find('button').focus()
     hasBeenOpened = true


Add line endings and newlines for fuzzy autocompletes

Closes #3351

--- app/views/play/level/tome/editor/snippets.coffee
+++ app/views/play/level/tome/editor/snippets.coffee
@@ -136,7 +136,7 @@
     beginningOfLine = session.getLine(pos.row).substring(0,pos.column - prefix.length)

     unless (fullPrefixParts.length < 3 and /^(hero|self|this|@)$/.test(fullPrefixParts[0]) ) or /^\s*$/.test(beginningOfLine)
-      console.log "Bailing", fullPrefixParts, '|', prefix, '|', beginningOfLine, '|', pos.column - prefix.length
+      # console.log "DEBUG: autocomplete bailing", fullPrefixParts, '|', prefix, '|', beginningOfLine, '|', pos.column - prefix.length
       @completions = completions
       return callback null, completions

@@ -195,10 +195,10 @@
 scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captureReturn) ->
   # console.log "Snippets snippet=#{snippet} caption=#{caption} line=#{line} input=#{input} pos.column=#{pos.column} lang=#{lang}"
   fuzzScore = 0.1
+  snippetLineBreaks = (snippet.match(lineBreak) || []).length
   # input will be replaced by snippet
   # trim snippet prefix and suffix if already in the document (line)
   if prefixStart = snippet.toLowerCase().indexOf(input.toLowerCase()) > -1
-    snippetLines = (snippet.match(lineBreak) || []).length
     captionStart = snippet.indexOf caption

     # Calculate snippet prefixes and suffixes. E.g. full snippet might be: "self." + "moveLeft" + "()"
@@ -243,14 +243,18 @@
       # console.log 'Snippets atLineEnd', pos.column, lineSuffix.length, line.slice(pos.column + lineSuffix.length), line
       toLinePrefix = line.substring 0, linePrefixIndex
       if linePrefixIndex < 0 or linePrefixIndex >= 0 and not /[\(\)]/.test(toLinePrefix) and not /^[ \t]*(?:if\b|elif\b)/.test(toLinePrefix)
-        snippet += autoLineEndings[lang] if snippetLines is 0 and autoLineEndings[lang]
-        snippet += "\n" if snippetLines is 0 and not /\$\{/.test(snippet)
+        snippet += autoLineEndings[lang] if snippetLineBreaks is 0 and autoLineEndings[lang]
+        snippet += "\n" if snippetLineBreaks is 0 and not /\$\{/.test(snippet)

         if captureReturn and /^\s*$/.test(toLinePrefix)
           snippet = captureReturn + linePrefix + snippet

     # console.log "Snippets snippetPrefix=#{snippetPrefix} linePrefix=#{linePrefix} snippetSuffix=#{snippetSuffix} lineSuffix=#{lineSuffix} snippet=#{snippet} score=#{fuzzScore}"
   else
+    # Append automatic line ending and newline for simple scenario
+    if line.trim() is input
+      snippet += autoLineEndings[lang] if snippetLineBreaks is 0 and autoLineEndings[lang]
+      snippet += "\n" if snippetLineBreaks is 0 and not /\$\{/.test(snippet)
     fuzzScore += score snippet, input

   startsWith = (string, searchString, position) ->


close #30, make EventEmitter public

close #30, make EventEmitter public

--- src/eventEmitter.coffee
+++ src/eventEmitter.coffee
@@ -52,5 +52,5 @@
         else
             @_events

-#conditionally add to namespace object
-gh.EventEmitter = EventEmitter if GH_INCLUDE_PRIVATE_API
+#add to namespace object
+gh.EventEmitter = EventEmitter


the remove() method is nonstandard (and undocumented)

--- core/app/backbone/views/popovers/react_popover.coffee
+++ core/app/backbone/views/popovers/react_popover.coffee
@@ -54,6 +54,7 @@
   componentWillUnmount: ->
     @_tether.destroy()
     React.unmountComponentAtNode @_tooltipElement
-    @_tooltipElement.remove()
+    if @_tooltipElement.parentNode
+      @_tooltipElement.parentNode.remove(@_tooltipElement)

   render: -> _span()


fixed issue of scraping rule delete not going through to the controller

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -225,18 +225,13 @@
   $("a").live 'click', ->
     t = $(this)
     form = t.parents("form")
-    if t.hasClass('category_id-delete') or t.hasClass('delete_scraping_rule')
-      t.parent().remove()
-      alert_substitute("Item has been removed.")
-      return false
-    else if t.hasClass('remove_category')
+    if t.hasClass('remove_category')
       t.closest('.cat_option').remove()
       return false
     else if t.hasClass("catnav")
       cat_id = $.trim($('#current_product_type').html())[0] + t.closest('li').attr("id")
       tree = t.closest('.tree').attr('id')
       if tree == 'product_type_tree'
-        # $("#facet_order").append data
         data="

#{cat_id} x

"
         $("#facet_order").append data
       else
@@ -246,15 +241,15 @@
         window.location = address
         return false
     else if t.attr('data-method') is "delete"
-      if confirm("Are you sure you want to delete this item?")
+      if confirm("Are you sure you want to delete this item?")
         $.ajax
           url: t.attr("href")
           data: form.serialize()
           type: "DELETE"
           success: (data) ->
-            if t.hasClass('feature-delete') or t.hasClass('spec-delete') or t.hasClass('url-delete')
+            if t.hasClass('feature-delete') or t.hasClass('spec-delete') or t.hasClass('url-delete') or t.hasClass('delete_scraping_rule')
               t.parent().remove()
-            alert_substitute("Correction has been removed.")
+            alert_substitute("Item has been removed.")
           error: ->
             alert_substitute("Error in processing the request for delete.")
       return false


Enable notification fade out

Enable notification fade out

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -84,5 +84,6 @@
         text: message
       type: type
       fadeOut:
-        enabled: false
+        enabled: true
+        delay: 5000
     .show()


Faltaba un document ready.

Faltaba un document ready.

--- app/assets/javascripts/borrar_ajax.js.coffee
+++ app/assets/javascripts/borrar_ajax.js.coffee
@@ -1,14 +1,15 @@
-$("a.destroy", $('#listado'))
-  .live('ajax:beforeSend', ->
-    $('#flash').remove()
-    $('
').prependTo('#content')
-    $("#listado").addCargando()
-  )
-  .live('ajax:success', (xhr, data, status) ->
-    $("#listado").removeCargando()
-    $(this).parents('tr:first').fadeOut(->
-      $(this).remove()
-      $('tbody tr', data).hide().appendTo('#listado tbody').fadeIn()
+$(document).ready ->
+  $("a.destroy", $('#listado'))
+    .live('ajax:beforeSend', ->
+      $('#flash').remove()
+      $('
').prependTo('#content')
+      $("#listado").addCargando()
     )
-    $('#flash').html($('.flash', data))
-  )
+    .live('ajax:success', (xhr, data, status) ->
+      $("#listado").removeCargando()
+      $(this).parents('tr:first').fadeOut(->
+        $(this).remove()
+        $('tbody tr', data).hide().appendTo('#listado tbody').fadeIn()
+      )
+      $('#flash').html($('.flash', data))
+    )


Fix a bug with whole day availability change.

--- app/assets/javascripts/back/back.js.coffee
+++ app/assets/javascripts/back/back.js.coffee
@@ -166,7 +166,7 @@
       })
   })

-  $('.fc-day-header').click () ->
+  $('#calendar').on 'click', '.fc-day-header', ->
     currentDay = moment($(this).html(), 'ddd DD/MM/YY')
     events = $('#calendar').fullCalendar('clientEvents', (event) ->
       return (event.start.diff(currentDay.startOf('day'), 'hours') == 0)
@@ -384,7 +384,7 @@
   $('input#intervention_city').autocomplete(cities,
                                             minChars:     0
                                             selectFirst:  false )
-
+
   $('input#intervention_city').focus ->
     $('input#intervention_city').click().click()


catch non-JSON errors from the server

catch non-JSON errors from the server

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -60,15 +60,18 @@
       dataType: "json",
       beforeSubmit: (data) =>
         console.log "Sending these data to the server: #{JSON.stringify(data)}"
-      success: (event) =>
-        console.log "Successful form POST; got #{JSON.stringify(event)}"
-        if event.metadata?
-          this.setAction(event.metadata.url, event.metadata.method)
-        # merge new data with window.hatTrick
-        $.extend(window.hatTrick, event)
+      success: (response) =>
+        console.log "Successful form POST; got #{JSON.stringify(response)}"
+        if response.metadata?
+          this.setAction(response.metadata.url, response.metadata.method)
+        $.extend(window.hatTrick, response) # merge new data with hatTrick
       error: (event) =>
-        # console.log "Error response: #{data.responseText}"
-        appErrors = eval "(#{event.responseText})"
+        console.log "Error response: #{event.responseText}"
+        try
+          appErrors = eval "(#{event.responseText})"
+        catch err
+          # TODO: Make this notify us when an error occurs.
+          appErrors = model: { unknown: ["A weird error has occurred."] }
         this.clearErrors()
         this.addErrorItem value[0] for key, value of appErrors.model
     ajax


run, jenkins

run, jenkins

--- app/assets/javascripts/oxalis/model.coffee
+++ app/assets/javascripts/oxalis/model.coffee
@@ -135,6 +135,7 @@

     if @getColorBinaries().length == 0
       Toast.error("No data available! Something seems to be wrong with the dataset.")
+
     @setDefaultBinaryColors()

     @flycam = new Flycam2d(constants.PLANE_WIDTH, @scaleInfo, zoomStepCount, @user)


gracefully handle missing dataset in model; fixes #1262

--- app/assets/javascripts/oxalis/model.coffee
+++ app/assets/javascripts/oxalis/model.coffee
@@ -41,7 +41,6 @@

     Request.receiveJSON(infoUrl).then( (tracing) =>

-      @set("datasetName", tracing.content.dataSet.name)

       if tracing.error
         Toast.error(tracing.error)
@@ -52,7 +51,8 @@
         return {"error" : true}

       else unless tracing.content.dataSet.dataLayers
-        if @get("datasetName")
+        if tracing.content.dataSet.name
+          @set("datasetName", tracing.content.dataSet.name)
           Toast.error("Please, double check if you have the dataset '#{@get("datasetName")}' imported.")
         else
           Toast.error("Please, make sure you have a dataset imported.")


using implementation described on 1c7c554 (gracias Fabricio)

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -4,20 +4,19 @@
       presence: true
     }
   }
+
   firstNames: (->
-    if @get("nombres")
-      nombres = @get("nombres").split(" ")
-      Ember.debug "computing user's firstNames"
-      if nombres.length > 2
-        nombres.slice(0, 2).join " "
-      else
-        @get("nombres")
+    nombres = @get("nombres")
+    unless nombres
+      return @get("username")
+    if nombres.split(" ").length > 2
+      nombres.split(" ").slice(0, 2).join " "
     else
-      nombres = @get("username")
+      nombres
     ).property("nombres")

   imagen_url_small: (->
     @get("imagen_url") + "?s=200"
   ).property("imagen_url")
-});+});


Trigger change events whenever we want to, instead of only the first time.

--- app/assets/javascripts/slickbone.js.coffee
+++ app/assets/javascripts/slickbone.js.coffee
@@ -183,7 +183,7 @@
   handleAssociatedCollectionEvent: (event, changedItem, options) =>
     switch event
       when 'change', 'add', 'remove', 'reset'
-        @change(options)
+        @trigger('change', @, options)

   # Override the default Backbone.js `toJSON` method to handle associations.
   toJSON: ->


completed mailcheck integration on login

--- app/assets/javascripts/views/login.js.coffee
+++ app/assets/javascripts/views/login.js.coffee
@@ -14,8 +14,12 @@
       "blur input.username": (e) ->
         $(e.target).mailcheck({
           suggested: (el, suggestion) ->
-            # TODO show alert/notification
-            console.log "Did you mean " + suggestion.full + "?"
+            $(el).popover({
+              content: "Did you mean " + suggestion.full + "?"
+            }).popover("show")
+
+          empty: (el) ->
+            $(el).popover("hide")
         })

       "submit form": (e) ->


verb revamp (#19)

verb revamp (#19)

--- app/assets/javascripts/zvg/verbatim.coffee
+++ app/assets/javascripts/zvg/verbatim.coffee
@@ -4,7 +4,7 @@
     @_series_header  = options.series_header or "series_title"
     @_filter_header  = options.filter_header or "filter_title"
     @_tag_header     = options.tag_header or "tag_title"
-    @extra_td_callback = options.extra_td_callback or ((d) -> null)
+    @_currentMetadata = {}
     @initializeQuestionTable()

     @_page          = 1
@@ -14,10 +14,11 @@

   render: (options = {}, callback) ->
     dataFilter = @constructDataFilter(options)
-    filtered = dataFilter(@displayData())
-    @paginate(options.page, filtered.length)
+    filtered = dataFilter(@displayData(options))
+    @data(filtered)
+    @paginate(options.page, @_currentMetadata.total_count or filtered.length)
     rows = @question_table.selectAll('tr.response')
-      .data(filtered.slice((@_page - 1) * @_perPage, @_perPage * @_page))
+      .data(@data())

     rows.enter()
       .append('tr')
@@ -44,8 +45,6 @@
       .append('span').attr('class', 'tag')

     tag_spans.text((d) -> d)
-    extra = rows.append('td')
-    @extra_td_callback(extra)
     @renderCallback(@)

   renderTags: (container, tag_list) ->
@@ -67,23 +66,24 @@
     @question_table  = d3.select(@container).append('table').attr('class', 'verbatim')
     @control_row     = @question_table.append('tr').attr('class', 'controls')

-    @series_cell     = @control_row.append('td')
+    @series_cell     = @control_row.append('td').attr('class', 'zvg_series')
     @series_title    = @series_cell.append('h5').text(@_series_header).attr('class', 'verb_selector_title')
     @series_selector = @series_cell.append('select').attr('name', 'series_selector')
-    @filter_cell     = @control_row.append('td')
+    @filter_cell     = @control_row.append('td').attr('class', 'zvg_series')
     @filter_title    = @filter_cell.append('h5').text(@_filter_header).attr('class', 'verb_selector_title')
     @filter_selector = @filter_cell.append('select').attr('name', 'filter_selector')
-    @tag_cell        = @control_row.append('td')
+    @tag_cell        = @control_row.append('td').attr('class', 'zvg_series_wide')
     @tag_title       = @tag_cell.append('h5').text(@_tag_header).attr('class', 'verb_selector_title')
     @tag_selector    = @tag_cell.append('select').attr('name', 'tag_selector')
-    @control_row.append('td') # for extra td
-

   constructDataFilter: (options) ->
+    @filterOpts = options
     s = options.series_1 if options.series_1 && options.series_1 != '[all]'
     f = options.series_2 if options.series_2 && options.series_2 != '[all]'
     t = options.tag if options.tag && options.series_2 != '[all]'

+    return (d) -> d
+    # TODO: remove the rest, filtering now happens on server
     (d) ->
       d.filter((e) -> if s then e.series_1 == s else e)
         .filter((e) -> if f then e.series_2 == f else e)
@@ -95,11 +95,18 @@
   data: (d) ->
     if d
       @_data = (x for x in d when x.question == @question_id)
-      @seriesDomain(d3.scale.ordinal().domain(x.series_1 for x in @_data).domain())
-      @filterDomain(d3.scale.ordinal().domain(x.series_2 for x in @_data).domain())
-      @tagDomain(d3.scale.ordinal().domain((x.tags for x in @_data).reduce(((x, y) -> x.concat(y)), [])).domain())
+      if @_currentMetadata
+        @seriesDomain(d3.scale.ordinal().domain(x for x, y of @_currentMetadata.primary_filter_counts).domain())
+        @filterDomain(d3.scale.ordinal().domain(x for x, y of @_currentMetadata.secondary_filter_counts).domain())
+        @tagDomain(d3.scale.ordinal().domain(@metadataTags()).domain())
+      else
+        @seriesDomain(d3.scale.ordinal().domain(x.series_1 for x in @_data).domain())
+        @filterDomain(d3.scale.ordinal().domain(x.series_2 for x in @_data).domain())
       return @
     @_data
+
+  metadataTags: ->
+    (@_currentMetadata.tags or {})[@question_id] or []

   getData: (source) =>
     d3.json(source, (error, json) =>


[backport] tracking checkout and add-to-cart as ecommerce events

--- app/coffee/modules/common/analytics.coffee
+++ app/coffee/modules/common/analytics.coffee
@@ -147,7 +147,7 @@
             'position': 1,
         })
         @win.ga('ec:setAction','add')
-        @win.ga('send', 'event', 'add-to-cart', 'Collect Payment Info')
+        @.trackEvent('ecommerce', 'add-to-cart', 'Collect Payment Info', null)

     ecConfirmChange: (plan_id, plan_name, plan_price) ->
         return if not @.initialized
@@ -192,6 +192,6 @@
             'id': plan_id,
             'revenue': plan_price,
         })
-        @win.ga('send', 'event', 'checkout', 'Plan checkout')
+        @.trackEvent('ecommerce', 'checkout', 'Plan checkout', null)

 module.service("$tgAnalytics", AnalyticsService)


use javascript dates to allow render flot points on the right place

--- app/coffee/modules/taskboard/charts.coffee
+++ app/coffee/modules/taskboard/charts.coffee
@@ -42,7 +42,7 @@
         width = element.width()
         element.height(240)

-        days = _.map(dataToDraw, (x) -> moment(x.day))
+        days = _.map(dataToDraw, (x) -> moment(new Date(x.day).getTime()))

         data = []
         data.unshift({


Fixed file for the bigint stringified ID.

ref: CNVS-7597

The change broke the favorites from being added
and removed due to javascript comparing strings
against integers / numbers.

Test Case:
 - Create a couple courses so they show up in the Courses menu.
 - When the customize window comes up all courses should be checked.
   The error would show them all as not checked.
 - Customize the Courses menu by adding and removing favorites.
   The error would not allow the ajax to update and would throw an error.
 - Refresh and the changes should still apply.

Change-Id: I8b309b9a9695eaeee630e39af93ee757e991329e
Reviewed-on: https://gerrit.instructure.com/25081
Reviewed-by: Jacob Fugal <f862f167b85d41b225785c70d70808bc7337c1fe@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Jeremy Putnam <b030d6d6f199f844ec424882be473c2ab9adc93b@instructure.com>
Product-Review: Nick Cloward <9cc571ed222c7ca8c35f018ff48a612f8cadeb73@instructure.com>

--- app/coffeescripts/widget/CustomList.coffee
+++ app/coffeescripts/widget/CustomList.coffee
@@ -187,6 +187,7 @@
       @element.find('> ul > li').each (index, element) =>
         element = jQuery element
         id      = element.data('id')
+        id      = id && String id
         item    = @items.findBy('id', id)

         return unless item
@@ -206,6 +207,7 @@
       # DOM and data get out of sync for atomic clicking, hence @animating
       return if @animating or @requests.reset
       id = element.data 'id'
+      id = id && String id
       item = @pinned.findBy 'id', id

       if item


minor cleanup

minor cleanup

--- client/landing/app/Applications/Members.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Members.kdapplication/AppView.coffee
@@ -13,23 +13,23 @@
     @header.setSearchInput()  if 'list members' in KD.config.permissions

 class MembersListItemView extends KDListItemView
-  constructor:(options, data)->

-    options = options ? {}
-    options.type = "members"
+  constructor:(options = {}, data)->
+
+    options.type          = "members"
     options.avatarSizes or= [60, 60] # [width, height]

-    super options,data
+    super options, data

     memberData = @getData()
     options    = @getOptions()

     @avatar = new AvatarView
-      size          :
-        width: options.avatarSizes[0]
-        height: options.avatarSizes[1]
-      showStatus    : yes
-      statusDiameter: 5
+      size           :
+        width        : options.avatarSizes[0]
+        height       : options.avatarSizes[1]
+      showStatus     : yes
+      statusDiameter : 5
     , memberData

     if (memberData.profile.nickname is KD.whoami().profile.nickname) or \


Integrated webterm kite into os kite.

Integrated webterm kite into os kite.

--- client/landing/app/Applications/WebTerm.kdapplication/AppView.coffee
+++ client/landing/app/Applications/WebTerm.kdapplication/AppView.coffee
@@ -81,8 +81,8 @@
       @terminal.server.input event.originalEvent.clipboardData.getData("text/plain") if @focused

     KD.singletons.kiteController.run
-      kiteName: 'webterm',
-      method: 'createServer',
+      kiteName: 'os',
+      method: 'createWebtermServer',
       withArgs: @terminal.clientInterface
     , (err, remote) =>
       @terminal.server = remote
@@ -127,4 +127,4 @@
       data.mainView.sessionBox.hide()
       data.mainView.terminal.joinSession data.id
       event.preventDefault()
-    link+    link


(fix) active and previous exercise tabs

(fix) active and previous exercise tabs

--- app/scripts/pages/exercises/exercises.coffee
+++ app/scripts/pages/exercises/exercises.coffee
@@ -17,11 +17,11 @@

     @exercisesActive = ko.computed =>
       @exercises().filter  (ex) =>
-        Date.parse(ex.dueDate()) < @serverTime()
+        Date.parse(ex.dueDate()) > @serverTime()

     @exercisesPrevious = ko.computed =>
       @exercises().filter  (ex) =>
-        Date.parse(ex.dueDate()) > @serverTime()
+        Date.parse(ex.dueDate()) < @serverTime()

   getExercises: (callback) ->
     api.get.exercises()


clearer variable names

clearer variable names

--- src/quad_tree.coffee
+++ src/quad_tree.coffee
@@ -125,10 +125,7 @@

         half_width = (width / 2).floor()
         half_height = (height / 2).floor()
-
-        x_midpoint = x + half_width
-        y_midpoint = y + half_height
-
+
         4.times (n) ->
           I.nodes[n] = Node
             bounds:


Use new Blob(ArrayBuffer) instead of new Blob(ArrayBufferView) for iOS

--- src/irc/irc_util.coffee
+++ src/irc/irc_util.coffee
@@ -95,7 +95,7 @@

 arrayBuffer2String = (buf, callback) ->
   exports.arrayBufferConversionCount++
-  blob = new Blob [new DataView buf]
+  blob = new Blob [buf]
   f = new FileReader()
   f.onload = (e) ->
     exports.arrayBufferConversionCount--


getElementById fix (can't have a parent domain)

--- src/quo.query.coffee
+++ src/quo.query.coffee
@@ -13,7 +13,7 @@
         else if TAG_SELECTOR.test(selector)
             elements = domain.getElementsByTagName(selector)
         else if ID_SELECTOR.test(selector)
-            elements = domain.getElementById selector.replace("#", "")
+            elements = document.getElementById selector.replace("#", "")
             unless elements then elements = []
         else
             elements = domain.querySelectorAll selector


Fix test for IE; use Batman._functionName rather than Function.name

--- tests/batman/view/filter_execution_test.coffee
+++ tests/batman/view/filter_execution_test.coffee
@@ -297,7 +297,7 @@
     bar: 'baz'
   helpers.render '

', ctx, (node) ->
     equals node.html(), "testValue"
-    equal spy.lastCallContext.constructor.name, 'RenderContext'
+    equal Batman._functionName(spy.lastCallContext.constructor), 'RenderContext'
     deepEqual spy.lastCallArguments, ['bar', 1, 'baz']
     delete Batman.Filters.test
     QUnit.start()


small callback fix

small callback fix

--- client.coffee
+++ client.coffee
@@ -63,7 +63,7 @@
               total: data.total
           preview
         @uploads[selector].set upload
-        callback? file
+        # callback? file
       fileuploaddone: (event, data) =>
         properties = data.response().result
         properties.name = data.files[0].name


formatting

formatting

--- src/lib/settings.coffee
+++ src/lib/settings.coffee
@@ -179,6 +179,7 @@
       rightHip: 'b4'
       leftShoulder: 'c5'

+
 # 1.2.3.5) 3D viewer star field

 _.defaults settings.viewer.threeD.starField,


Collapsing elements on research topic show page are now more intuitive, for #859

--- app/assets/javascripts/research_topics.coffee
+++ app/assets/javascripts/research_topics.coffee
@@ -13,14 +13,28 @@

   $(document)
     .on('click', "[data-object~='rt-upvote']", (e) ->
-      $('#endorse_'+$(this).data('rt')+'_1').prop 'checked', true
       target = $(this).data('target')
-      $(target).collapse 'show'
+      radio = $('#endorse_'+$(this).data('rt')+'_1')
+      if radio.prop 'checked'
+        $(target).collapse('hide')
+        $(target).on('hidden.bs.collapse', () ->
+          radio.prop 'checked', false
+        )
+      else
+        radio.prop 'checked', true
+        $(target).collapse 'show'
     )
     .on('click', "[data-object~='rt-downvote']", () ->
-      $('#endorse_'+$(this).data('rt')+'_0').prop 'checked', true
       target = $(this).data('target')
-      $(target).collapse 'show'
+      radio = $('#endorse_'+$(this).data('rt')+'_0')
+      if radio.prop 'checked'
+        $(target).collapse 'hide'
+        $(target).on('hidden.bs.collapse', () ->
+          radio.prop 'checked', false
+        )
+      else
+        radio.prop 'checked', true
+        $(target).collapse 'show'
     )
     .on('click', "[data-object~='rt-already-voted']", () ->
       target = $(this).data('target')


adds sr-only class and site name to accessibility span

--- app/assets/javascripts/social_share.js.coffee
+++ app/assets/javascripts/social_share.js.coffee
@@ -2,4 +2,6 @@

   initialize: ->
     $(".social-share-button a").each ->
-      $(this).after(" ")+      element = $(this)
+      site = element.data('site')
+      element.append("#{site}")


Try bigger

Try bigger

--- main.coffee
+++ main.coffee
@@ -1,4 +1,4 @@
-[rectWidth, rectHeight] = [20, 20]
+[rectWidth, rectHeight] = [32, 32]

 UPDATE_INTERVAL = 1250


Code refactor.

Code refactor.

--- main.coffee
+++ main.coffee
@@ -3,7 +3,6 @@
 Busboy = require "busboy"
 shortid = require "shortid"
 {extname} = require "path"
-{Readable} = require "stream"
 {tmpdir, tmpDir} = require "os"
 {IncomingMessage} = require "http"
 {createReadStream, createWriteStream} = require "fs"
@@ -20,8 +19,9 @@
 assign = Object.assign
 merge = require "lodash.merge"
 isEmpty = require "lodash.isempty"
-reduceRight = Array::reduceRight
-includes = String::includes
+
+reduceRight = (arr, args...) -> Array::reduceRight.apply arr, args
+
 tmpdir or= tmpDir

 defaults =
@@ -34,8 +34,7 @@
 # @api private
 ###
 mapListeners = (listeners, fn, ctx = null) ->
-  fn.call ctx, __name, __handler for __name, __handler of listeners
-  return
+  fn.call ctx, name, handler for name, handler of listeners; return

 ###
 # Trying to rescue field value actual type from string
@@ -49,7 +48,8 @@
   return null if value is "null"
   return false if value is "false"
   return true if value is "true"
-  return Number value if "#{Number value}" isnt "NaN" and value isnt ""
+  # return Number value if "#{Number value}" isnt "NaN" and value isnt ""
+  return Number value if value isnt "" and not isNaN value
   return value

 ###
@@ -70,7 +70,7 @@
   if isPlainObject(target) and key of target
     res[key] = rescueObjStruct val, target[key]
   else
-    if Array.isArray target
+    if isArray target
       res = [target...]
       if not res[key] or Number key
         if isPlainObject val
@@ -135,7 +135,7 @@
     target[fieldname] = rescueTypes value
     return target

-  res = reduceRight.call keys, reducer, rescueTypes value
+  res = reduceRight keys, reducer, rescueTypes value
   target = rescueObjStruct res, target
   return target


Code style changes to main.coffee

Code style changes to main.coffee

--- main.coffee
+++ main.coffee
@@ -1,97 +1,110 @@
-coffee = require 'coffee-script'
 fs = require 'fs'
-jade = require 'jade'
 http = require 'http'
 path = require 'path'
+url = require 'url'
+
+coffee = require 'coffee-script'
+jade = require 'jade'
 static_files = require 'node-static'
 stylus = require 'stylus'
-url = require 'url'

 exports.version = '0.2.0'

 file = new static_files.Server()
-read_utf8 = {encoding: 'utf8'}
+
+read_utf8 = encoding: 'utf8'
+
 error_page = '<!doctype html>
{h1}
'

-compile_coffee = (raw_text, filename, callback) ->
+compile_coffee = (raw_text, filename, callback)->
 	result = coffee.compile raw_text
+
 	callback false, result

-compile_stylus = (raw_text, filename, callback) ->
-	options =
-		#compress: true
-		filename: filename
+compile_stylus = (raw_text, filename, callback)->
+	options = filename: filename
+
 	stylus.render raw_text, options, callback

-exports.handler = (config) ->
-	(req, res, next) ->
+exports.handler = (config)->
+	(req, res, next)->
 		console.log req.method + ' ' + req.url

-		if !next
+		if not next
 			next = ->
 				respond_error 404

-		respond_error = (code) ->
-			res.writeHead code, {'Content-Type': 'text/html'}
+		respond_error = (code)->
+			res.writeHead code, 'Content-Type': 'text/html'
+
 			page = error_page.replace '{h1}', code
+
 			res.end page

-		respond_text = (text, mime_type) ->
-			res.writeHead 200, {'Content-Type': mime_type}
+		respond_text = (text, mime_type)->
+			res.writeHead 200, 'Content-Type': mime_type
+
 			res.end text

-		respond_file = (file_name, mime_type) ->
-			fs.readFile file_name, read_utf8, (err, raw_text) ->
+		respond_file = (file_name, mime_type)->
+			fs.readFile file_name, read_utf8, (err, raw_text)->
 				if err
 					console.error 'Could not read ' + file_name
+
 					respond_error 500
+
 				else
 					respond_text raw_text, mime_type

-		respond_compiled_text = (fn_base, ext_from, ext_to, mime_type, compiler) ->
+		respond_compiled_text = (fn_base, ext_from, ext_to, mime_type, compiler)->
 			full_fn_from = path.join config.path, fn_base + ext_from
+
 			full_fn_to = path.join config.path, fn_base + ext_to

 			files_exist = ->
-				fs.readFile full_fn_from, read_utf8, (err, raw_text) ->
+				fs.readFile full_fn_from, read_utf8, (err, raw_text)->
 					if err
 						console.error 'Could not read ' + full_fn_from
+
 						respond_error 500
+
 					else
-						compiler raw_text, full_fn_from, (err, compiled_text) ->
+						compiler raw_text, full_fn_from, (err, compiled_text)->
 							if err
 								console.error 'Could not compile ' + full_fn_from
+
 								respond_error 500
+
 							else
 								respond_text compiled_text, mime_type

-			fs.exists full_fn_to, (exists) ->
+			fs.exists full_fn_to, (exists)->
 				if exists
 					respond_file full_fn_to, mime_type
 				else
-					fs.exists full_fn_from, (exists) ->
+					fs.exists full_fn_from, (exists)->
 						if exists
 							files_exist()
 						else
 							next()

-		respond_jade = (fn_base) ->
-			serve_jade = (fn_jade) ->
-				fs.readFile fn_jade, read_utf8, (err, jade_text) ->
+		respond_jade = (fn_base)->
+			serve_jade = (fn_jade)->
+				fs.readFile fn_jade, read_utf8, (err, jade_text)->
 					if err
 						console.error 'Could not read ' + fn_jade
 						respond_error 500
 					else
-						fs.readFile fn_base + '.json', read_utf8, (err, json_text) ->
+						fs.readFile fn_base + '.json', read_utf8, (err, json_text)->
 							if err
 								json_obj = {}
 							else
@@ -104,12 +117,12 @@
 							respond_text html, 'text/html'

 			fn_jade = fn_base + '.jade'
-			fs.exists fn_jade, (exists) ->
+			fs.exists fn_jade, (exists)->
 				if exists
 					serve_jade fn_jade
 				else
 					fn_jade = fn_base + '/index.jade'
-					fs.exists fn_jade, (exists) ->
+					fs.exists fn_jade, (exists)->
 						if exists
 							serve_jade fn_jade
 						else
@@ -117,48 +130,62 @@

 		url_q = url.parse req.url
-		req_path = if url_q.pathname == '/' then 'index' else url_q.pathname.slice 1
+
+		req_path =
+			if '/' == url_q.pathname
+				'index'
+			else
+				url_q.pathname.slice 1
+
 		req_ext = path.extname req_path

 		if ('.html' == req_ext) or ('' == req_ext)
-			if '.html' == req_ext
-				fn_base = req_path.slice 0, -5
-			else
-				fn_base = req_path
+			fn_base =
+				if '.html' == req_ext
+					req_path.slice 0, -5
+				else
+					req_path

-			fs.exists fn_base + '.html', (exists) ->
+			fs.exists fn_base + '.html', (exists)->
 				if exists
 					respond_file fn_base + '.html', 'text/html'
+
 				else
-					fs.exists fn_base + 'index.html', (exists) ->
+					fs.exists fn_base + 'index.html', (exists)->
 						if exists
 							respond_file fn_base + 'index.html', 'text/html'
+
 						else
 							respond_jade fn_base

 		else if '.css' == req_ext
 			fn_base = req_path.slice 0, -4
-			respond_compiled_text(fn_base, '.styl', '.css', 'text/css',
-				compile_stylus)
+
+			respond_compiled_text fn_base, '.styl', '.css', 'text/css', compile_stylus

 		else if '.js' == req_ext
 			fn_base = req_path.slice 0, -3
-			respond_compiled_text(fn_base, '.coffee', '.js', 'text/javascript',
-				compile_coffee)
+
+			respond_compiled_text fn_base, '.coffee', '.js', 'text/javascript', compile_coffee

 		else
 			full_fn = path.join config.path, req_path
+
 			console.log full_fn
-			fs.exists full_fn, (exists) ->
+
+			fs.exists full_fn, (exists)->
 				if exists
-					#req.addListener 'end', ->
 					file.serve req, res
+
 				else
 					next()

-exports.startServer = (config) ->
+exports.startServer = (config)->
 	listener = exports.handler config
+
 	app = http.createServer listener
+
 	app.listen config.port, config.hostname
+
 	console.log 'Jader running on ' + config.hostname + ':' + config.port


Fix broken sorting in Gradebook (SIS ID column)

Swap the order so the sis_id line does not get included in the previous
line inside the .match() method when the CoffeeScript compiled into JS.

This is what happened before:

    b.sortCol.field.match(/^custom_col/ || b.sortCol.field === "sis_id")

And this is what we want (order doesn't matter):

    b.sortCol.field.match(/^custom_col/) || b.sortCol.field === "sis_id"

--- app/coffeescripts/gradebook2/Gradebook.coffee
+++ app/coffeescripts/gradebook2/Gradebook.coffee
@@ -1037,8 +1037,8 @@
       @grid.onSort.subscribe (event, data) =>
         if data.sortCol.field == "display_name" ||
            data.sortCol.field == "secondary_identifier" ||
-           data.sortCol.field.match /^custom_col/  ||
-           data.sortCol.field == "sis_id" # SFU MOD - CANVAS-188 Make SIS ID sortable
+           data.sortCol.field == "sis_id" || # SFU MOD - CANVAS-188 Make SIS ID sortable
+           data.sortCol.field.match /^custom_col/
           sortProp = if data.sortCol.field == "display_name"
             "sortable_name"
           else


Level chooser now teleports to selected maze

--- maze.coffee
+++ maze.coffee
@@ -158,7 +158,7 @@
   @msg['updown'] = 'Press R to move a floor up, F to move a floor down.'
   @msg['win'] = 'Yay! You mastered the maze!'

-  constructor: (@maze, frame, @messagebox, viewport) ->
+  constructor: (@maze, @frame, @messagebox, @viewport) ->
     # dynamic attributes
     [@x, @y, @z] = @maze.start
     @busy = false
@@ -193,10 +193,10 @@
     }

     # set viewport
-    #viewport.attr('content', "width=#{@here.width + 142}");
+    #@viewport.attr('content', "width=#{@here.width + 142}");

     # style frame
-    frame.css({
+    @frame.css({
         height: @here.height + 71
         width: @here.width + 71
         position: 'relative'
@@ -208,7 +208,7 @@
         top: (@here.height - @above.height + 71) / 2
         left: (@here.width - @above.width + 71) / 2
     })
-    frame.append(@container)
+    @frame.append(@container)

     # make invisible grid on which the pawn moves
     grid = $('

').css({
@@ -423,15 +423,18 @@
       @messagebox.append($('

').append(@makeTweetActionLink()).append('      play again'))
       @disable()
     if [@x, @y, @z] of @maze.portals
-      # TODO teleport into a maze of the chosen level
+      level = @maze.portals[[@x, @y, @z]]
       @destroy()
+      new MazeUI3D(new RandomMaze([level, level, level]), @frame, @messagebox,
+          @viewport)

   makeTweetActionLink: ->
     $('')
         .attr({
             class: 'actionLink'
             target: '_blank'
-            href: 'https://twitter.com/share?text=' + encodeURIComponent(@makeTweetText())
+            href: 'https://twitter.com/share?text=' + encodeURIComponent(
+                @makeTweetText())
         })
         .append('tweet')


ES5 filter and map wrt #2

ES5 filter and map wrt #2

 - also, I may have gone overboard on the whitespace stuff :)

--- mbta.coffee
+++ mbta.coffee
@@ -3,7 +3,8 @@
 _ = require 'lodash'

 class MBTA
-  constructor: (@key) -> @apiUrl = 'http://realtime.mbta.com/developer/api/v1/'
+  constructor: (@key) ->
+    @apiUrl = 'http://realtime.mbta.com/developer/api/v1/'

   buildOptions: (path, qsOpts) ->
     (
@@ -16,38 +17,50 @@
     deferred = Q.defer()

     request.get opts, (e,r,body) ->
-      if e
-        deferred.reject e
-      else
-        deferred.resolve body
+      return deferred.reject e if e
+      deferred.resolve body

     deferred.promise

-  _addKey: (opts) -> _.extend opts, "api_key": @key
+  _addKey: (opts) ->
+    opts["api_key"] = @key
+    opts

-  servertime: -> @_doRequest @buildOptions "servertime", @_addKey({})
+  servertime: ->
+    @_doRequest @buildOptions "servertime", @_addKey({})

-  routes: -> @_doRequest @buildOptions "routes", @_addKey({})
+  routes: ->
+    @_doRequest @buildOptions "routes", @_addKey({})

-  routesByStop: (stop) -> @_doRequest @buildOptions "routesbystop", @_addKey({ stop })
+  routesByStop: (stop) ->
+    @_doRequest @buildOptions "routesbystop", @_addKey({ stop })

-  stopsByRoute: (route) -> @_doRequest @buildOptions "stopsbyroute", @_addKey({ route })
+  stopsByRoute: (route) ->
+    @_doRequest @buildOptions "stopsbyroute", @_addKey({ route })

-  stopsByLocation: (pos) -> @_doRequest @buildOptions "stopsbylocation", @_addKey({ lat: pos.lat, lon: pos.lon })
+  stopsByLocation: (pos) ->
+    @_doRequest @buildOptions "stopsbylocation", @_addKey({ lat: pos.lat, lon: pos.lon })

-  scheduleByStop: (opts) -> @_doRequest @buildOptions "schedulebystop", @_addKey(opts)
+  scheduleByStop: (opts) ->
+    @_doRequest @buildOptions "schedulebystop", @_addKey(opts)

-  scheduleByRoute: (opts) -> @_doRequest @buildOptions "schedulebyroute", @_addKey(opts)
+  scheduleByRoute: (opts) ->
+    @_doRequest @buildOptions "schedulebyroute", @_addKey(opts)

-  scheduleByTrip: (opts) -> @_doRequest @buildOptions "schedulebytrip", @_addKey(opts)
+  scheduleByTrip: (opts) ->
+    @_doRequest @buildOptions "schedulebytrip", @_addKey(opts)

-  alerts: -> @_doRequest @buildOptions "alerts", @_addKey({})
+  alerts: ->
+    @_doRequest @buildOptions "alerts", @_addKey({})

-  alertByID: (id) -> @_doRequest @buildOptions "alertbyid", @_addKey({ id })
+  alertByID: (id) ->
+    @_doRequest @buildOptions "alertbyid", @_addKey({ id })

-  alertHeadersByRoute: (route) -> @_doRequest @buildOptions "alertheadersbyroute", @_addKey({ route })
+  alertHeadersByRoute: (route) ->
+    @_doRequest @buildOptions "alertheadersbyroute", @_addKey({ route })

-  alertHeadersByStop: (stop) -> @_doRequest @buildOptions "alertheadersbystop", @_addKey({ stop })
+  alertHeadersByStop: (stop) ->
+    @_doRequest @buildOptions "alertheadersbystop", @_addKey({ stop })

   _parseRoutes: (deferred, routeKey, routes) ->
     routeArray = routes.mode.map (r) ->
@@ -57,7 +70,7 @@
       route

     routeList = _.flatten routeArray
-    filteredList = _.filter routeList, (r) -> _.contains r.route_name, routeKey
+    filteredList = routeList.filter (r) -> routeKey in r.route_name

     deferred.resolve filteredList


Added method __processSyncUseTag

Added method __processSyncUseTag

--- meta-engine.coffee
+++ meta-engine.coffee
@@ -210,6 +210,74 @@
     return [ regionMap, content ]

+  __processSyncUseTag: (resourcePath, content, regionMap)->
+
+    linebreakCharacter = @optionMap.linebreakCharacter
+
+    indentCharacter = @optionMap.indentCharacter
+
+    offset = 0
+    while (tagStartIndex = content.indexOf '@use', offset) > -1
+      offset = tagStartIndex + 1
+
+      # figure out indent level
+      if tagStartIndex is 0
+        indentLevel = 0
+        tagLineStartIndex = 0
+      else
+        caseContent = content.slice 0, tagStartIndex
+        lastLineEnd = caseContent.lastIndexOf linebreakCharacter
+        tagLineStartIndex = lastLineEnd + 1 # NOTE: Also covers the scenario when lastLineEnd is -1
+        indentationString = content.slice tagLineStartIndex, tagStartIndex
+        indentLevel = 0
+        while (indentationString.indexOf indentCharacter, (indentCharacter.length * indentLevel)) > -1
+          indentLevel += 1
+
+      # extract tag
+      tagLineEndIndex = (content.indexOf linebreakCharacter, offset)
+      if tagLineEndIndex is -1
+        tagLineEndIndex = content.length - 1
+      tag = (content.slice tagStartIndex, tagLineEndIndex)
+
+      # extract name
+      indexQuote1 = tag.indexOf '"', '@use'.length
+      if indexQuote1 is -1
+        throw new Error 'Expected Double Quote'
+      indexQuote2 = tag.indexOf '"', indexQuote1 + 1
+      if indexQuote2 is -1
+        throw new Error 'Expected Double Quote'
+      name = tag.slice indexQuote1 + 1, indexQuote2
+
+      # extract other parameters
+      if ((tag.slice indexQuote2 + 1, tag.length).indexOf 'as-is') > -1
+        asIs = true
+      else
+        asIs = false
+
+      # check for duplication
+      unless name of regionMap
+        throw new Error "Unknown Region #{name}"
+      region = regionMap[name]
+
+      # decide whether to match indentation
+      matchIndent = if region.indented and not asIs then true else false
+
+      # match indent
+      if matchIndent
+        'TODO'
+        regionContent = region.regionContent
+      else
+        regionContent = region.regionContent
+
+      # replace
+      left = content.slice 0, tagLineStartIndex
+      middle = regionContent
+      right = content.slice tagLineEndIndex, content.length
+      content = left + middle + right
+
+
+
+    return content

   __processSync: (resourcePath, isolated = false)->


added defaults in a separate object

added defaults in a separate object

--- meta-engine.coffee
+++ meta-engine.coffee
@@ -6,6 +6,17 @@
   optionMap: null

   contentProvider: null
+
+  @defaults =
+    encoding: 'utf8'
+    prefix: '@'
+    postfix: ''
+    indentCharacter: '  '
+    linebreakCharacter: '\n'
+    contentProvider: null
+    insertComments: false
+    commentPrefix: '<!-- '
+    commentPostfix: ' -->'

   constructor: (optionMap = {})->


Slow down modal animatation on mobile

Slow down modal animatation on mobile

--- _source/client/templates/components/modal.coffee
+++ _source/client/templates/components/modal.coffee
@@ -1,6 +1,11 @@
 class Apollos.modal extends Apollos.Component

   @register "Apollos.modal"
+
+  vars: -> [
+    desktopDuration: 250
+    mobileDuration: 400
+  ]

   events: -> [
     "click [data-dismiss]": @.destroy
@@ -27,15 +32,16 @@
       null

   insertDOMElement: (parent, node, before) ->
+    self = @

     width = $(window).width()
     height = $(window).height()

-    if width >= 767
+    if width > 767
       # fade in background
       $(node).appendTo(parent)
         .velocity "fadeIn",
-          duration: 250
+          duration: self.desktopDuration.get()

       # slide in panel from left
       $(node).children('.side-panel')
@@ -43,27 +49,30 @@
           translateX: [0, -500]
           opacity: 1
         ,
-          duration: 250
+          duration: self.desktopDuration.get()
           complete: (elements) ->
             $('html').addClass 'modal--opened'
       super

     else
       # just add background
-      $(node).appendTo(parent)
+      # $(node).appendTo(parent).css(bottom: 0)

       # slide in panel from bottom
-      $(node).children('.side-panel')
+      $(node).appendTo(parent).children('.side-panel')
+        .css
+          transform: "translateY(#{height}px)"
         .velocity
           translateY: [0, height]
           opacity: 1
         ,
-          duration: 250
+          duration: self.mobileDuration.get()
           complete: (elements) ->
             $('html').addClass 'modal--opened'

   removeDOMElement: (parent, node) ->
+    self = @

     width = $(window).width()
     height = $(window).height()
@@ -75,11 +84,11 @@
           translateX: -500
           opacity: 1
         ,
-          duration: 250
+          duration: self.desktopDuration.get()

       # fade out background
       $(node).velocity "fadeOut",
-        duration: 250
+        duration: self.desktopDuration.get()
         complete: (elements) ->
           $(node).remove()
           $('html').removeClass 'modal--opened'
@@ -91,11 +100,7 @@
           translateY: height
           opacity: 1
         ,
-          duration: 250
-
-      # fade out background
-      $(node).velocity "fadeOut",
-        duration: 250
-        complete: (elements) ->
-          $(node).remove()
-          $('html').removeClass 'modal--opened'
+          duration: self.mobileDuration.get()
+          complete: (elements) ->
+            $(node).remove()
+            $('html').removeClass 'modal--opened'


Fix code in two_pointers

Fix code in two_pointers

--- server/materials/data/topics/two_pointers.coffee
+++ server/materials/data/topics/two_pointers.coffee
@@ -35,6 +35,9 @@
                 s:=s+a[j];
                 if s>k then begin
                     if j-i+1<min then min:=j-i+1;
+                    s:=s-a[j]; // это грубый костыль, потому что на следующей итерации
+                               // мы будем делать опять s:=s+a[j] с тем же j.
+                               // ниже будет видно, как это сделать красивее.
                     break;
                 end;
             end;
@@ -50,7 +53,8 @@
                 inc(j);
             end;
             if s>k then begin
-                if j-i+1<min then min:=j-i+1;
+                // +1 пропало, потому что теперь j указывает на первый невзятый элемент.
+                if j-i<min then min:=j-i;
             end else // если мы попали сюда, то j=n, но s<=k - дальше искать нечего
                 break;
             s:=s-a[i]; // перед началом следующей итерации выкинем число i


Update FileHandler.coffee: ignore any error that happens while deleting directory during insertFile.

--- services/filestore/app/coffee/FileHandler.coffee
+++ services/filestore/app/coffee/FileHandler.coffee
@@ -12,7 +12,6 @@
 	insertFile: (bucket, key, stream, callback)->
 		convetedKey = KeyBuilder.getConvertedFolderKey(key)
 		PersistorManager.deleteDirectory bucket, convetedKey, (error) ->
-			return callback(error) if error?
 			PersistorManager.sendStream bucket, key, stream, callback

 	deleteFile: (bucket, key, callback)->


Minor test wording change

Minor test wording change

--- test/clone.test.iced
+++ test/clone.test.iced
@@ -15,7 +15,7 @@
       wrench.rmdirSyncRecursive(baseDir)
     catch e

-  it('tests files created', (done)->
+  it('created files successfully', (done)->
     checkFilesFetched = (error)->
       if error
         done(error)


Fix event listener leak in ResizableView.

--- src/resizable/ResizableView.coffee
+++ src/resizable/ResizableView.coffee
@@ -180,7 +180,7 @@
   mm = @addEventListenerTo b, 'mousemove', onTouchOrMouseMoveInside, @
   md = @addEventListener 'mousedown', onResizeStart, @
   @__resizeableListeners.mm = mm
-  @__resizeableListeners.mm = md
+  @__resizeableListeners.md = md

 ###*


remove children in reverse order

remove children in reverse order

--- src/stage/api.coffee
+++ src/stage/api.coffee
@@ -61,7 +61,7 @@

   remove: (selector) ->
     return @select(selector).remove() if selector
-    @_context.controller.remove target for target in @_targets
+    @_context.controller.remove target for target in @_targets.slice().reverse()
     @_pop()

   set: (key, value) ->


resolve relative urls to absolute for article intro using getIntro(base), add horizontal rule <hr> as delimiter

--- src/plugins/page.coffee
+++ src/plugins/page.coffee
@@ -20,6 +20,15 @@

   getUrl: (base) ->
     super(base).replace /index\.html$/, ''
+
+  getIntro: (base) ->
+    @_html ?= @getHtml(base)
+    idx = ~@_html.indexOf('<span class="more') or ~@_html.indexOf('
-    idx = ~@html.indexOf(' @intro.length)
+    @_html ?= @getHtml()
+    @_intro ?= @getIntro()
+    @_hasMore ?= (@_html.length > @_intro.length)
     return @_hasMore

 module.exports = Page


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/approachable.coffee
+++ src/approachable.coffee
@@ -5,10 +5,8 @@
   self.bind "update", ->
     for name, cooldownOptions of I.cooldowns
       {approachBy, target} = cooldownOptions
-
-      newValue = I[name].approach(target, approachBy)
-
-      I[name] = newValue
+
+      I[name] = I[name].approach(target, approachBy)

   addCooldown: (name, value=100, options={target: 0, approachBy: 1}) ->
     unless I.cooldowns[name]


Fixed issue with sorting on nulls.

Fixed issue with sorting on nulls.

--- ribs.coffee
+++ ribs.coffee
@@ -535,8 +535,8 @@
                     b = walk_context field, mb.toJSON()

                     # make string sorting case insensitive
-                    a = a.toLowerCase() if a.toLowerCase?
-                    b = b.toLowerCase() if b.toLowerCase?
+                    a = a.toLowerCase() if a?.toLowerCase?
+                    b = b.toLowerCase() if b?.toLowerCase?

                     return  0     if a is b
                     return +1*dir if a > b or not b?


Added a custom list item cell view override variable in list item view. Added extra logic to editable to turn an array in to a select element with one option per item in the array


Fix a bug that caused the tab to not load in dev.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -9,9 +9,9 @@
 ## Configuration - It'd be nice if we could deduce these from a props
 ## file for something, but in lieu of that, this works.
 ##
-googleAnalyticsAccount = anchortab.googleAnalyticsAccount || "UA-35269224-4"
-apiDomain = anchortab.apiDomain || "local.anchortab.com"
-resourcesDomain = anchortab.resourcesDomain || "local.anchortab.com"
+googleAnalyticsAccount = anchortab?.googleAnalyticsAccount || "UA-35269224-4"
+apiDomain = anchortab?.apiDomain || "local.anchortab.com"
+resourcesDomain = anchortab?.resourcesDomain || "local.anchortab.com"
 jqVersion = "1.8.2"

 ##


OPCE6: Added test for the word class

OPCE6: Added test for the word class

--- test/rte-test.coffee
+++ test/rte-test.coffee
@@ -9,7 +9,7 @@
 chai.use(sinonChai)
 chai.config.includeStack = true

-[Rte, Selection] = require '../lib/y-rte'
+[Rte, Selection, Word] = require '../lib/y-rte'

 describe 'Rich Text type should', ->
@@ -117,7 +117,7 @@
     console.log rte1.getWord(0)

 describe 'Selection object should', ->
-  sel = rte = null
+  sel = sel2 = rte = word = null

   it 'be initialized with three parameters', ->
     rte = new Rte "Zero One two three four five"
@@ -130,3 +130,30 @@
     sel.should.have.property('endPos')
     sel.should.have.deep.property('endPos.word', 1)
     sel.should.have.deep.property('endPos.pos', 2)
+
+  it 'merge correctly the selections', ->
+    rte = new Rte "Zero One two three four five"
+    sel = new Selection 0, 1, rte
+    sel2 = new Selection 1, 10, rte
+
+    sel.merge sel2, rte
+    rte._rte.selections.length.should.equal 1
+    rte._rte.selections[0].equals(sel2).should.be.true
+
+    sel2.should.have.deep.property 'startPos.word', 0
+    sel2.should.have.deep.property 'startPos.pos', 0
+    sel2.should.have.deep.property 'endPos.word', 2
+    sel2.should.have.deep.property 'endPos.pos', 1
+
+describe 'Word objects should', ->
+  sel = sel2 = rte = word = null
+  it 'be linked correctly to selections', ->
+    rte = new Rte "This is a test"
+    sel = new Selection 0, 6, rte
+    word = rte.getWord(0)
+    word.left[0].equals(sel).should.be.true
+    word.right.length.should.equals 0
+
+    word = rte.getWord(1)
+    word.left.length.should.equals 0
+    word.right[0].equals(sel).should.be.true


Add viewport metadata to classifications

--- app/classifier/index.cjsx
+++ app/classifier/index.cjsx
@@ -168,6 +168,10 @@
     @props.classification.update
       completed: true
       'metadata.finished_at': (new Date).toISOString()
+      'metadata.viewport':
+        width: innerWidth
+        height: innerHeight
+
     @props.onComplete?()

   toggleExpertClassification: (value) ->


Add documentation

Add documentation

--- app/core/Component.coffee
+++ app/core/Component.coffee
@@ -1,7 +1,17 @@
+###
+A Result represents the...well...result of a Task.
+
+Any function given to {Scriptable#addTask} is expected to return a {Result}.
+###
+
 module.exports = class Component
   constructor: (@owner) ->

-
+  ###
+  Copys all functions from the component to {#owner}.
+  Ignores functions starting with an underscore and refuses to
+  overwrite functions.
+  ###
   mount: () =>
     for method of @
       if @owner[method]?


Archive.progress returns 100% when raw transcriptions/ total subjects is greater than 100 due to change in retirement factor from 10 to 4

--- app/models/Archive.coffee
+++ app/models/Archive.coffee
@@ -54,7 +54,11 @@
   # we are displaying this number
   progress: =>
     unless @stats? then return 0
-    Math.ceil ( (@classification_count / @transcriptions_needed() ) * 100)
+    result = Math.ceil ( (@classification_count / @transcriptions_needed() ) * 100)
+    if result > 100
+      return 100
+    else
+      return result

   recordsComplete: =>
     @formatNumber @stats.complete


fetching opentsdb data shouldn't add async output

--- src/opentsdb.coffee
+++ src/opentsdb.coffee
@@ -6,7 +6,7 @@

   opentsdb = modules.create 'opentsdb', ({fn, cmd, settings}) ->
     fn 'tsd', 'Fetches time series data from OpenTSDB', (args...) ->
-      @value @async -> opentsdb.tsd args...
+      @value opentsdb.tsd args...

     to_metric_string: ({aggregation, metric_name, downsample, tags}) ->
       parts = [aggregation]


Style tweak to options unload prompt.

Style tweak to options unload prompt.

--- options/options.coffee
+++ options/options.coffee
@@ -28,8 +28,7 @@
   document.getElementById("restoreSettings").addEventListener "click", restoreToDefaults
   document.getElementById("saveOptions").addEventListener "click", saveOptions

-window.onbeforeunload = ->
-  if $("saveOptions").disabled then null else "You have unsaved changes to options."
+window.onbeforeunload = -> "You have unsaved changes to options." unless $("saveOptions").disabled

 onOptionKeyup = (event) ->
   if (event.target.getAttribute("type") isnt "checkbox" and


Expose expandSelectionInDirectionWithGranularity

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -96,7 +96,7 @@
     range = @getLocationRange()

     if range.isCollapsed()
-      @selectionDelegate?.expandSelectionInDirectionWithGranularity(direction, granularity)
+      @expandSelectionInDirectionWithGranularity(direction, granularity)
       range = @getLocationRange()

     @document.removeTextAtLocationRange(range)
@@ -221,6 +221,9 @@
   notifyDelegateOfIntentionToSetLocationRange: ->
     @delegate?.compositionWillSetLocationRange?()

+  expandSelectionInDirectionWithGranularity: (direction, granularity) ->
+    @selectionDelegate?.expandSelectionInDirectionWithGranularity(direction, granularity)
+
   expandSelectionForEditing: ->
     for key, value of Trix.attributes when value.parent
       if @hasCurrentAttribute(key)


Prevent HTML parser from traversing outside of its container

--- src/trix/models/html_parser.coffee
+++ src/trix/models/html_parser.coffee
@@ -58,7 +58,7 @@

   appendBlockForElement: (element) ->
     if @isBlockElement(element) and not @isBlockElement(element.firstChild)
-      attributes = getBlockAttributes(element)
+      attributes = @getBlockAttributes(element)
       unless elementContainsNode(@currentBlockElement, element) and arraysAreEqual(attributes, @currentBlock.attributes)
         @currentBlock = @appendBlockForAttributesWithElement(attributes, element)
         @currentBlockElement = element
@@ -72,7 +72,7 @@

   findParentBlockElement: (element) ->
     {parentElement} = element
-    while parentElement
+    while parentElement and parentElement isnt @container
       if @isBlockElement(parentElement) and parentElement in @blockElements
         return parentElement
       else
@@ -94,13 +94,13 @@

   processTextNode: (node) ->
     if string = normalizeSpaces(node.data)
-      @appendStringWithAttributes(string, getTextAttributes(node.parentNode))
+      @appendStringWithAttributes(string, @getTextAttributes(node.parentNode))

   processElement: (element) ->
     if nodeIsAttachmentElement(element)
       attributes = getAttachmentAttributes(element)
       if Object.keys(attributes).length
-        textAttributes = getTextAttributes(element)
+        textAttributes = @getTextAttributes(element)
         @appendAttachmentWithAttributes(attributes, textAttributes)
         # We have everything we need so avoid processing inner nodes
         element.innerHTML = ""
@@ -109,12 +109,12 @@
       switch tagName(element)
         when "br"
           unless @isExtraBR(element) or @isBlockElement(element.nextSibling)
-            @appendStringWithAttributes("\n", getTextAttributes(element))
+            @appendStringWithAttributes("\n", @getTextAttributes(element))
           @processedElements.push(element)
         when "img"
           attributes = url: element.src, contentType: "image"
           attributes[key] = value for key, value of getImageDimensions(element)
-          @appendAttachmentWithAttributes(attributes, getTextAttributes(element))
+          @appendAttachmentWithAttributes(attributes, @getTextAttributes(element))
           @processedElements.push(element)
         when "tr"
           unless element.parentNode.firstChild is element
@@ -161,39 +161,7 @@
     else
       text.unshift(pieceForString(string))

-  getMarginOfBlockElementAtIndex: (index) ->
-    if element = @blockElements[index]
-      unless tagName(element) in @getBlockTagNames() or element in @processedElements
-        getBlockElementMargin(element)
-
-  getMarginOfDefaultBlockElement: ->
-    element = makeElement(Trix.config.blockAttributes.default.tagName)
-    @container.appendChild(element)
-    getBlockElementMargin(element)
-
-  translateBlockElementMarginsToNewlines: ->
-    defaultMargin = @getMarginOfDefaultBlockElement()
-
-    for block, index in @blocks when margin = @getMarginOfBlockElementAtIndex(index)
-      if margin.top > defaultMargin.top * 2
-        @prependStringToTextAtIndex("\n", index)
-
-      if margin.bottom > defaultMargin.bottom * 2
-        @appendStringToTextAtIndex("\n", index)
-
-  pieceForString = (string, attributes = {}) ->
-    type = "string"
-    {string, attributes, type}
-
-  pieceForAttachment = (attachment, attributes = {}) ->
-    type = "attachment"
-    {attachment, attributes, type}
-
-  blockForAttributes = (attributes = {}) ->
-    text = []
-    {text, attributes}
-
-  getTextAttributes = (element) ->
+  getTextAttributes: (element) ->
     attributes = {}
     for attribute, config of Trix.config.textAttributes
       if config.parser
@@ -210,9 +178,9 @@

     attributes

-  getBlockAttributes = (element) ->
+  getBlockAttributes: (element) ->
     attributes = []
-    while element
+    while element and element isnt @container
       for attribute, config of Trix.config.blockAttributes when config.parse isnt false
         if tagName(element) is config.tagName
           if config.test?(element) or not config.test
@@ -220,6 +188,38 @@
             attributes.push(config.listAttribute) if config.listAttribute
       element = element.parentNode
     attributes.reverse()
+
+  getMarginOfBlockElementAtIndex: (index) ->
+    if element = @blockElements[index]
+      unless tagName(element) in @getBlockTagNames() or element in @processedElements
+        getBlockElementMargin(element)
+
+  getMarginOfDefaultBlockElement: ->
+    element = makeElement(Trix.config.blockAttributes.default.tagName)
+    @container.appendChild(element)
+    getBlockElementMargin(element)
+
+  translateBlockElementMarginsToNewlines: ->
+    defaultMargin = @getMarginOfDefaultBlockElement()
+
+    for block, index in @blocks when margin = @getMarginOfBlockElementAtIndex(index)
+      if margin.top > defaultMargin.top * 2
+        @prependStringToTextAtIndex("\n", index)
+
+      if margin.bottom > defaultMargin.bottom * 2
+        @appendStringToTextAtIndex("\n", index)
+
+  pieceForString = (string, attributes = {}) ->
+    type = "string"
+    {string, attributes, type}
+
+  pieceForAttachment = (attachment, attributes = {}) ->
+    type = "attachment"
+    {attachment, attributes, type}
+
+  blockForAttributes = (attributes = {}) ->
+    text = []
+    {text, attributes}

   getAttachmentAttributes = (element) ->
     JSON.parse(element.dataset.trixAttachment)


Convert method to helper

Convert method to helper

--- src/trix/models/html_parser.coffee
+++ src/trix/models/html_parser.coffee
@@ -79,11 +79,6 @@
         {parentElement} = parentElement
     null

-  isExtraBR: (element) ->
-    tagName(element) is "br" and
-      isBlockElement(element.parentNode) and
-      element.parentNode.lastChild is element
-
   processTextNode: (node) ->
     if string = normalizeSpaces(node.data)
       unless elementCanDisplayNewlines(node.parentNode)
@@ -102,7 +97,7 @@
     else
       switch tagName(element)
         when "br"
-          unless @isExtraBR(element) or isBlockElement(element.nextSibling)
+          unless isExtraBR(element) or isBlockElement(element.nextSibling)
             @appendStringWithAttributes("\n", @getTextAttributes(element))
           @processedElements.push(element)
         when "img"
@@ -275,6 +270,11 @@
     return if elementCanDisplayNewlines(node.parentNode)
     isBlockElement(node.previousSibling) and isBlockElement(node.nextSibling)

+  isExtraBR = (element) ->
+    tagName(element) is "br" and
+      isBlockElement(element.parentNode) and
+      element.parentNode.lastChild is element
+
   elementCanDisplayNewlines = (element) ->
     {whiteSpace} = window.getComputedStyle(element)
     whiteSpace in ["pre", "pre-wrap", "pre-line"]


typeahead dynamic by question_id

typeahead dynamic by question_id

--- app/assets/javascripts/hanuman/surveys.js.coffee
+++ app/assets/javascripts/hanuman/surveys.js.coffee
@@ -22,37 +22,38 @@
   # TYPEAHEAD

   if $(".typeahead").length > 0
-    # instantiate bloodhound engine
-    taxonomy = new Bloodhound(
-      name: "taxonomy"
-      #local: [{"id":22,"option_text":"Adoxaceae","scientific_text":null},{"id":42,"option_text":"Agavaceae","scientific_text":null},{"id":913,"option_text":"Allen's hummingbird","scientific_text":"Selasphorus sasin"},{"id":230,"option_text":"Alliaceae","scientific_text":null},{"id":914,"option_text":"American coot","scientific_text":"Fulica americana"},{"id":915,"option_text":"American crow","scientific_text":"Corvus brachyrhynchos"},{"id":916,"option_text":"American goldfinch","scientific_text":"Carduelis tristis"},{"id":917,"option_text":"American kestrel","scientific_text":"Falco sparverius"},{"id":918,"option_text":"American robin","scientific_text":"Turdus migratorius"},{"id":919,"option_text":"American wigeon","scientific_text":"Anas amercana"},{"id":895,"option_text":"Amphibians","scientific_text":null},{"id":25,"option_text":"Anacardiaceae","scientific_text":null},{"id":920,"option_text":"Anna's hummingbird","scientific_text":"Calypte anna"},{"id":99,"option_text":"Apiaceae","scientific_text":null},{"id":407,"option_text":"Apocynaceae","scientific_text":null},{"id":1097,"option_text":"Aquatic garter snake","scientific_text":"Thamnophis aquaticus"},{"id":63,"option_text":"Araliaceae","scientific_text":null},{"id":921,"option_text":"Ash-throated flycatcher","scientific_text":"Myiarchus cinerascens"},{"id":32,"option_text":"Asteraceae","scientific_text":null},{"id":1055,"option_text":"Audubon's cottontail","scientific_text":"Sylvilagus audubonii"},{"id":922,"option_text":"Band-tailed pigeon","scientific_text":"Columbia fasciata"},{"id":923,"option_text":"Barn owl","scientific_text":"Tyto alba"},{"id":924,"option_text":"Barn swallow","scientific_text":"Hirundo rustica"},{"id":925,"option_text":"Belted kingfisher","scientific_text":"Ceryle alcyon"},{"id":389,"option_text":"Bermuda buttercup","scientific_text":"Oxalis pes-caprae"},{"id":196,"option_text":"Bermuda grass","scientific_text":"Cynodon dactylon"},{"id":61,"option_text":"Betulaceae","scientific_text":null}]
-      prefetch: "/hanuman/answer_choices.json?question_id=7"
-      #remote: "/hanuman/answer_choices.json?question_id=7"
-      datumTokenizer: (d) ->
-        Bloodhound.tokenizers.whitespace d.formatted_answer_choice
+    $(".typeahead").each ->
+      questionId = $(@).data 'question-id'
+      url = "/hanuman/answer_choices.json?question_id=" + questionId
+      # instantiate bloodhound engine
+      taxonomy = new Bloodhound(
+        name: "taxonomy"
+        #local: [{"id":22,"option_text":"Adoxaceae","scientific_text":null},{"id":42,"option_text":"Agavaceae","scientific_text":null},{"id":913,"option_text":"Allen's hummingbird","scientific_text":"Selasphorus sasin"},{"id":230,"option_text":"Alliaceae","scientific_text":null},{"id":914,"option_text":"American coot","scientific_text":"Fulica americana"},{"id":915,"option_text":"American crow","scientific_text":"Corvus brachyrhynchos"},{"id":916,"option_text":"American goldfinch","scientific_text":"Carduelis tristis"},{"id":917,"option_text":"American kestrel","scientific_text":"Falco sparverius"},{"id":918,"option_text":"American robin","scientific_text":"Turdus migratorius"},{"id":919,"option_text":"American wigeon","scientific_text":"Anas amercana"},{"id":895,"option_text":"Amphibians","scientific_text":null},{"id":25,"option_text":"Anacardiaceae","scientific_text":null},{"id":920,"option_text":"Anna's hummingbird","scientific_text":"Calypte anna"},{"id":99,"option_text":"Apiaceae","scientific_text":null},{"id":407,"option_text":"Apocynaceae","scientific_text":null},{"id":1097,"option_text":"Aquatic garter snake","scientific_text":"Thamnophis aquaticus"},{"id":63,"option_text":"Araliaceae","scientific_text":null},{"id":921,"option_text":"Ash-throated flycatcher","scientific_text":"Myiarchus cinerascens"},{"id":32,"option_text":"Asteraceae","scientific_text":null},{"id":1055,"option_text":"Audubon's cottontail","scientific_text":"Sylvilagus audubonii"},{"id":922,"option_text":"Band-tailed pigeon","scientific_text":"Columbia fasciata"},{"id":923,"option_text":"Barn owl","scientific_text":"Tyto alba"},{"id":924,"option_text":"Barn swallow","scientific_text":"Hirundo rustica"},{"id":925,"option_text":"Belted kingfisher","scientific_text":"Ceryle alcyon"},{"id":389,"option_text":"Bermuda buttercup","scientific_text":"Oxalis pes-caprae"},{"id":196,"option_text":"Bermuda grass","scientific_text":"Cynodon dactylon"},{"id":61,"option_text":"Betulaceae","scientific_text":null}]
+        prefetch: url
+        #remote: "/hanuman/answer_choices.json?question_id=7"
+        datumTokenizer: (d) ->
+          Bloodhound.tokenizers.whitespace d.formatted_answer_choice

-      queryTokenizer: Bloodhound.tokenizers.whitespace
-    )
+        queryTokenizer: Bloodhound.tokenizers.whitespace
+      )
+      # initialize the bloodhound suggestion engine
+      promise = taxonomy.initialize()

-    # initialize the bloodhound suggestion engine
-    promise = taxonomy.initialize()
+      promise
+      .done ->
+        console.log 'success!'
+      .fail ->
+        console.log 'err!'

-    promise
-    .done ->
-      console.log 'success!'
-    .fail ->
-      console.log 'err!'
-
-    # instantiate the typeahead ui
-    $(".typeahead").typeahead(
-      hint: true
-      highlight: true
-      minLength: 1
-    ,
-      name: "taxonomy"
-      displayKey: "formatted_answer_choice"
-      source: taxonomy.ttAdapter()
-    )
+      $(@).typeahead(
+        hint: true
+        highlight: true
+        minLength: 1
+      ,
+        name: "taxonomy"
+        displayKey: "formatted_answer_choice"
+        source: taxonomy.ttAdapter()
+      )

     # custom event to capture answer_choice_id
     $(".typeahead").bind "typeahead:selected", (obj, datum, name) ->


Grow to much bigger max page size

Grow to much bigger max page size

--- SingularityUI/app/collections/LogLines.coffee
+++ SingularityUI/app/collections/LogLines.coffee
@@ -15,7 +15,7 @@
     baseRequestLength: 30000

     requestLengthGrowthFactor: 1.75
-    maxRequestLength: @::baseRequestLength * 10
+    maxRequestLength: @::baseRequestLength * 100

     # Request a larger chunk at start
     initialRequestLength: @::baseRequestLength * 3


EDSC-474, fix landing page bug in production environments

--- app/assets/javascripts/modules/landing.js.coffee
+++ app/assets/javascripts/modules/landing.js.coffee
@@ -5,7 +5,9 @@
     preferences = @edsc.page.preferences) ->

   updateLandingPageState = ->
-    uiModel.isLandingPage(History.getState().cleanUrl.indexOf('/search') == -1)
+    # "https://search.<...>.indexOf('/search') == -1" is always false
+    # "https://search.<...>.indexOf('/search')" is equal to 7, look for "/search" after 8
+    uiModel.isLandingPage(History.getState().cleanUrl.indexOf('/search') > 8)

   hasLeftLandingPage = false
   updateLandingPage = (isLandingPage) ->


Fix hide_notification

Fix hide_notification

--- app/assets/javascripts/views/user_view.js.coffee
+++ app/assets/javascripts/views/user_view.js.coffee
@@ -95,7 +95,7 @@
     events: ->
       @selector.find('.bubble p, .bubble ul').bind 'click', @insertQuote

-  class window.PostNewView
+  class window.PostNewView extends CommonView
     constructor: (@topic)->
       @selector = $('#new_post')
       do @yield
@@ -107,7 +107,7 @@
     yield: ->
       @selector.append @template(@topic)

-  class window.PostView
+  class window.PostView extends CommonView
     constructor: (@post) ->
       @selector = $('#posts')
       do @yield


fix js for categories on map search

fix js for categories on map search

--- app/assets/javascripts/widgets/map_search.coffee
+++ app/assets/javascripts/widgets/map_search.coffee
@@ -152,10 +152,19 @@
     location_scroller.refresh()
     categories_scroller.refresh()

-  $('#search_categories_input input[type="checkbox"]').change ->
+  $('.categories input[type="checkbox"]').change ->
+    if $(this).hasClass('sacral-options')
+      filter = $(".categories div.sacral-categories")
+      if $(this).is(':checked')
+        filter.find('input[type=checkbox]').attr('checked', 'checked')
+        filter.slideDown()
+      else
+        filter.find('input[type=checkbox]').removeAttr('checked')
+        filter.slideUp()
+    # submit
     $('#new_search').submit()

-  $('#search_categories_input input[type="checkbox"]:disabled').each ->
+  $('.categories input[type="checkbox"]:disabled').each ->
     $(this).parents('choice').hide()

   if $$('.locations').length && $$('.categories').length


open link input if selected a link

open link input if selected a link

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -146,6 +146,10 @@
 		left = ( startEl.offset().left + endEl.offset().left ) / 2
 		$('#toolpad').css 'top', top - 35
 		$('#toolpad').css 'left', left - 90
+		el = @getSelectedElement()
+		if el.is 'a'
+			$('#toolpad input').attr 'href', el.val()
+			@openLinkInput()

 		@hideTooltip()
 		@status.showToolpad = true


adjust the way to show insertion

adjust the way to show insertion

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -238,11 +238,11 @@
 		@delegateEvents()

 	bindEditorTitleEvents:->
-		$('#editor-title').on 'keydown', (e)=>
+		$('#editor-title').delegate 'keydown', (e)=>
 			@handleTitleKeyDown(e)
-		.on 'keyup', (e)=>
+		.delegate 'keyup', (e)=>
 			@handleTitleKeyUp(e)
-		.on 'paste', (e)=>
+		.delegate 'paste', (e)=>
 			e.preventDefault()
 			raw = e.clipboardData.getData('text/html') || e.clipboardData.getData('text')
 			pasteElement = document.createElement 'p'
@@ -262,18 +262,18 @@
 			@updateTitlePrompt(true)

 	bindEditorEvents: ->
-		$('#editor').on 'keydown', (e)=>
+		$('#editor')delegate 'keydown', (e)=>
 			@hideInsertion()
 			@checkNew() if @status.new
 			@handleKeyDown(e)
 			@update()
-		.on 'keyup', (e)=>
+		.delegate 'keyup', (e)=>
 			@handleKeyUp(e)
 			@update()
 			@selection()
-		.on 'click', (e)->
+		.delegate 'click', (e)->
 			$(@).focus()
-		.on 'paste', (e)=>
+		.delegate 'paste', (e)=>
 			e.preventDefault()
 			raw = e.clipboardData.getData('text/html') || e.clipboardData.getData('text')
 			pasteElement = document.createElement 'p'
@@ -287,7 +287,7 @@
 				.replace( new RegExp('h[3-9]', 'ig'), 'h2' )
 				.removeTagsExcept(['p', 'h1', 'h2', 'a', 'br', 'pre', 'code'])
 			document.execCommand 'insertHtml', false, result
-		.on 'askid', =>
+		.delegate 'askid', =>
 			if @askid
 				@askid( @ ) unless @status.connecting
 			else


tighten up the robot.hear regex for jira issue keys so that it doesn't match direct commands (e.g. jira comments for AB-123)

--- src/scripts/jira.coffee
+++ src/scripts/jira.coffee
@@ -15,7 +15,7 @@
 #   HUBOT_JIRA_IGNOREUSERS
 #
 # Commands:
-#   <Project Key>-<Issue ID> - Displays information about the JIRA ticket (if it exists)
+#   <issue key> - Displays information about the JIRA ticket (if it exists)
 #   hubot jira watchers for <Issue Key> - Shows watchers for the given JIRA issue
 #   hubot jira comments for <Issue Key> - Shows the comments for the given JIRA issue
 #   hubor jira comment on <issue key> <comment text> - Adds a comment to the specified issue
@@ -246,7 +246,7 @@
     comment msg, msg.match[1], msg.match[2], (text) ->
       msg.reply text

-  robot.hear /([^\w\-]|^)(\w+-[0-9]+)(?=[^\w]|$)/ig, (msg) ->
+  robot.hear /^((?!jira).)*([^\w\-]|^)(\w+-[0-9]+)(?=[^\w]|$)/ig, (msg) ->
     if msg.message.user.id is robot.name
       return


Update jira.coffee

Update jira.coffee

--- src/scripts/jira.coffee
+++ src/scripts/jira.coffee
@@ -16,12 +16,12 @@
 #
 # Commands:
 #   <Project Key>-<Issue ID> - Displays information about the JIRA ticket (if it exists)
-#   hubot show watchers for <Issue Key> - Shows watchers for the given JIRA issue
-#   hubot show comments for <Issue Key> - Shows the comments for the given JIRA issue
-#   hubot show openissues for <Issue Key> - Shows the open issues for the given JQL
-#   e.g. hubot show openissues for project = "The Cornered Badgers" AND fixVersion = "13.21"
-#   hubot search for <JQL> - Search JIRA with JQL
-#   e.g. hubot search for project = "The Cornered Badgers" AND component = "Consumer Web"
+#   hubot jira show watchers for <Issue Key> - Shows watchers for the given JIRA issue
+#   hubot jira show comments for <Issue Key> - Shows the comments for the given JIRA issue
+#   hubot jira show openissues for <Issue Key> - Shows the open issues for the given JQL
+#   e.g. hubot jira show openissues for project = "The Cornered Badgers" AND fixVersion = "13.21"
+#   hubot jira search for <JQL> - Search JIRA with JQL
+#   e.g. hubot jira search for project = "The Cornered Badgers" AND component = "Consumer Web"
 #
 # Author:
 #   codec
@@ -184,28 +184,28 @@
       else
         cb resultText + " (too many to list)"

-  robot.respond /(show )?watchers (for )?(\w+-[0-9]+)/i, (msg) ->
+  robot.respond /(jira show )?watchers (for )?(\w+-[0-9]+)/i, (msg) ->
     if msg.message.user.id is robot.name
       return

     watchers msg, msg.match[3], (text) ->
       msg.send text

-  robot.respond /(show )?comments (for )?(\w+-[0-9]+)/i, (msg) ->
+  robot.respond /(jira show )?comments (for )?(\w+-[0-9]+)/i, (msg) ->
     if msg.message.user.id is robot.name
       return

     comments msg, msg.match[3], (text) ->
       msg.send text

-  robot.respond /search (for )?(.*)/i, (msg) ->
+  robot.respond /jira search (for )?(.*)/i, (msg) ->
     if msg.message.user.id is robot.name
       return

     search msg, msg.match[2], (text) ->
       msg.reply text

-  robot.respond /show (openissues for )?(.*)/i, (msg) ->
+  robot.respond /jira show (openissues for )?(.*)/i, (msg) ->
     if msg.message.user.id is robot.name
       return


Make JCrop use window height as boxWidth

--- app/assets/javascripts/avatars.coffee
+++ app/assets/javascripts/avatars.coffee
@@ -6,8 +6,8 @@
     $('#cropbox').Jcrop
       aspectRatio: 1
       setSelect: [0, 0, 600, 600]
-      boxWidth: 500
-      boxHeight: 400
+      boxWidth: window.innerWidth
+      boxHeight: window.innerHeight
       onSelect: @update
       onChange: @update


setTimeout to 10K ms

setTimeout to 10K ms

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -52,7 +52,7 @@
   setTimeout (->
     $('#visit_modal').modal 'show'
     return
-  ), 1000
+  ), 10000

   $('.get-involved-link').click (e) ->
     $('#visit_modal').modal('hide')


Fix delete bug.

Fix delete bug.

--- app/assets/javascripts/main.js.coffee
+++ app/assets/javascripts/main.js.coffee
@@ -69,7 +69,7 @@
 # confirm dialog box for class ".confirm-dialog"
 $ ->
   $(".confirm-dialog").click((event)->
-    this_link = $(".confirm-dialog")
+    this_link = $(this)
     titleText = this_link.data("title")
     bodyText = this_link.data("body")
     methodText = this_link.data("method-type")


JDomain: pass hostnameAlias along with domain options for createDomains helper

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -181,6 +181,7 @@
     { domain, account, group, stack, hostnameAlias } = options

     domainData = { proposedDomain: domain, group }
+    domainData.hostnameAlias = hostnameAlias  if hostnameAlias?

     JStack = require './stack'
     JStack.getStack account, stack, (err, stack)=>
@@ -249,6 +250,7 @@

       JDomain.createDomain {
         domain, account, group, stack
+        hostnameAlias : [ hostnameAlias ]
       }, (err, domain)->

         if err? then console.error err  unless err.code is 11000


fix indentation coffeescript

fix indentation coffeescript

--- app/assets/javascripts/welcome.coffee
+++ app/assets/javascripts/welcome.coffee
@@ -1,16 +1,19 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
 $(document).on 'ready page:load', ->
-    courses = new Bloodhound
-        datumTokenizer: Bloodhound.tokenizers.whitespace,
-        queryTokenizer: Bloodhound.tokenizers.whitespace,
-        prefetch: '/courses.json',
-        remote:
-            url: '/courses.json?search=%QUERY',
-            wildcard: '%QUERY'
+  courses = new Bloodhound
+    datumTokenizer: Bloodhound.tokenizers.whitespace,
+    queryTokenizer: Bloodhound.tokenizers.whitespace,
+    prefetch: '/courses.json',
+    remote:
+      url: '/courses.json?search=%QUERY',
+      wildcard: '%QUERY'

-    $('#search').typeahead null,
-        name: 'courses',
-        display: 'name',
-        source: courses
+  $('#search').typeahead null,
+    name: 'courses',
+    display: 'name',
+    source: courses


Clear window.me before running tests

Clear window.me before running tests

--- app/views/TestView.coffee
+++ app/views/TestView.coffee
@@ -78,6 +78,7 @@
     describe 'CodeCombat Client', =>
       jasmine.Ajax.install()
       beforeEach ->
+        me.clear()
         jasmine.Ajax.requests.reset()
         Backbone.Mediator.init()
         Backbone.Mediator.setValidationEnabled false


Add the standard output from qpdf on error

--- app/coffee/OutputFileOptimiser.coffee
+++ app/coffee/OutputFileOptimiser.coffee
@@ -27,7 +27,7 @@
 			stdout += chunk.toString()
 		callback = _.once(callback) # avoid double call back for error and close event
 		proc.on "error", (err) ->
-			logger.warn {err, args}, "qpdf failed"
+			logger.warn {err, args}, "qpdf failed", stdout
 			callback(null) # ignore the error
 		proc.on "close", (code) ->
 			timer.done()


Fix assigned users on kanban

Fix assigned users on kanban

--- app/coffee/modules/kanban/main.coffee
+++ app/coffee/modules/kanban/main.coffee
@@ -153,6 +153,7 @@

         @scope.$on("assigned-to:added", @.onAssignedToChanged)
         @scope.$on("assigned-user:added", @.onAssignedUsersChanged)
+        @scope.$on("assigned-user:deleted", @.onAssignedUsersDeleted)
         @scope.$on("kanban:us:move", @.moveUs)
         @scope.$on("kanban:show-userstories-for-status", @.loadUserStoriesForStatus)
         @scope.$on("kanban:hide-userstories-for-status", @.hideUserStoriesForStatus)
@@ -216,6 +217,23 @@
         usModel.assigned_users = assignedUsers
         if not usModel.assigned_to
             usModel.assigned_to = userid
+        @kanbanUserstoriesService.replaceModel(usModel)
+
+        promise = @repo.save(usModel)
+        promise.then null, ->
+            console.log "FAIL" # TODO
+
+    onAssignedUsersDeleted: (ctx, userid, usModel) ->
+        assignedUsersIds = _.clone(usModel.assigned_users, false)
+        assignedUsersIds = _.pull(assignedUsersIds, userid)
+        assignedUsersIds = _.uniq(assignedUsersIds)
+        usModel.assigned_users = assignedUsersIds
+
+        # Update as
+        if usModel.assigned_to not in assignedUsersIds and assignedUsersIds.length > 0
+            usModel.assigned_to = assignedUsersIds[0]
+        if assignedUsersIds.length == 0
+            usModel.assigned_to = null

         @kanbanUserstoriesService.replaceModel(usModel)


feat(swimlanes): update swimlanes on edit

--- app/coffee/modules/kanban/main.coffee
+++ app/coffee/modules/kanban/main.coffee
@@ -169,6 +169,7 @@
         @scope.$on "usform:edit:success", (event, us) =>
             @.refreshTagsColors().then () =>
                 @kanbanUserstoriesService.replaceModel(us)
+                @kanbanUserstoriesService.refresh(false)

         @scope.$on "kanban:us:deleted", (event, us) =>
             @.filtersReloadContent()
@@ -348,7 +349,7 @@
         ]).then (result) =>
             @kanbanUserstoriesService.reset(false)
             userstories = result[0]
-            swimlanes = result[1]
+            swimlanes = result[1]
             @.notFoundUserstories = false

             if !userstories.length && ((@.filterQ && @.filterQ.length) || Object.keys(@location.search()).length)


idk man

idk man

--- app/assets/javascripts/components/articles.js.coffee
+++ app/assets/javascripts/components/articles.js.coffee
@@ -8,8 +8,7 @@
 		articles.push article
 		@setState articles: articles
 	doOrderFilta: (orderFilta) ->
-		for param in orderFilta
-			console.log param
+		console.log 'wtfbro'
 	render: ->
 		React.DOM.div
 			className: 'articles'


Initial work towards adding normal hover subnav to responsive layout.

--- app/assets/javascripts/lib/core/authenticator.coffee
+++ app/assets/javascripts/lib/core/authenticator.coffee
@@ -69,14 +69,15 @@

     userOptionsMenu: ->
       userOptions = [
-        {title: 'My profile', uri: "#{@options.membersUrl.replace('[USERNAME]', @lpUserName)}", style:"js-user-profile"},
-        {title: 'Settings', uri: "#{@options.profileEditUrl.replace('[USERNAME]', @lpUserName)}",  style:"js-user-settings"},
-        {title: 'Messages', uri: "#{@options.messagesUrl.replace('[USERNAME]', @lpUserName)}", style:"js-user-msg"},
-        {title: 'Forum activity', uri: "#{@options.forumPostsUrlTemplate.replace('[USERNAME]', @lpUserName)}", style:"nav-user-options__item--forum js-user-forum" },
-        {title: 'Sign out', uri: "#{@options.signOutUrl}", style:"nav-user-options__item--signout js-user-signout" }
+        {title: 'My profile', uri: "#{@options.membersUrl.replace('[USERNAME]', @lpUserName)}", style:"js-user-profile", icon:"user"},
+        {title: 'Settings', uri: "#{@options.profileEditUrl.replace('[USERNAME]', @lpUserName)}",  style:"js-user-settings", icon:"settings"},
+        {title: 'Messages', uri: "#{@options.messagesUrl.replace('[USERNAME]', @lpUserName)}", style:"js-user-msg", icon:"envelope"},
+        {title: 'Forum activity', uri: "#{@options.forumPostsUrlTemplate.replace('[USERNAME]', @lpUserName)}", style:"nav-user-options__item--forum js-user-forum", icon:"article--line" },
+        {title: 'Sign out', uri: "#{@options.signOutUrl}", style:"nav-user-options__item--signout js-user-signout", icon:"sign-out" }
       ]
       optionElements =  ("#{u.title}#{u.extra || ''}" for u in userOptions).join('')
-      userMenu = "#{@lpUserName}

#{@lpUserName}
#{optionElements}

"
+      responsiveOptionElements =  ("#{u.title}#{u.extra || ''}" for u in userOptions).join('')
+      userMenu = "#{@lpUserName}
#{responsiveOptionElements}

#{@lpUserName}
#{optionElements}

"

     userAvatar: ->


lien pour levels

lien pour levels

--- app/assets/javascripts/packs/show-facebook.js.coffee
+++ app/assets/javascripts/packs/show-facebook.js.coffee
@@ -20,7 +20,7 @@
     $('#levels').find('.is-selected').attr('data-level-thumb')

   window.current_url = ->
-    "#{window.location.href}/levels/#{window.current_level_id()}"
+    "https://sokoban.be/levels/#{window.current_level_id()}"

   update_send_invitations_at = ->
     current_user_id = $("#user-infos").attr('data-id')


Trigger geometry update when chunks are updated

--- app/scripts/client/webgl/chunk.coffee
+++ app/scripts/client/webgl/chunk.coffee
@@ -90,6 +90,7 @@

 		for position in @_pendingEntities
 			@_chunkGeometry.merge @entities[position].geometry,  @entities[position].matrix, materialConstants.typeIndexes[ @entities[position].type]
+			@_chunkGeometry.groupsNeedUpdate = true

 		@_pendingEntities = []


Fix issue with collapsing bridge hit

Fix issue with collapsing bridge hit

--- app/scripts/components/player/PlayerSpaceship.coffee
+++ app/scripts/components/player/PlayerSpaceship.coffee
@@ -157,7 +157,7 @@

       # still hitting an object? then we where forced in
       # and are crashed (squashed probably)
-      @trigger('Hit') if @hit('Edge') or @hit('Solid')
+      @trigger('Hit', { damage: 1000 }) if @hit('Edge') or @hit('Solid')

     this


NavigationList: Fix drag issue of dock items

--- client/Main/navigation/navigationlist.coffee
+++ client/Main/navigation/navigationlist.coffee
@@ -54,7 +54,7 @@

         view.unsetClass 'no-anim remove'

-        if view.data.type isnt 'persistent' and view.getY() > 25
+        if view.data.type isnt 'persistent' and view.getRelativeY() > 25

           view.setClass 'explode'
           KD.utils.wait 500, => @removeApp view


payment: `PaymentController#creditSubscription`

--- client/Main/payment/paymentcontroller.coffee
+++ client/Main/payment/paymentcontroller.coffee
@@ -211,10 +211,14 @@
   debitSubscription: (subscription, pack, callback) ->
     subscription.debit { pack }, (err, nonce) =>
       return  if KD.showError err
-
       @emit 'SubscriptionDebited', subscription
-
       callback null, nonce
+
+  creditSubscription: (subscription, pack, callback) ->
+    subscription.credit { pack }, (err) =>
+      return  if KD.showError err
+      @emit 'SubscriptionCredited', subscription
+      callback null

   fetchSubscriptionsWithPlans: (options, callback) ->
     [callback, options] = [options, callback]  unless callback


payment: WIP wiring "buy vm" modal

payment: WIP wiring "buy vm" modal

--- client/app/MainApp/payment/buyvmmodal.coffee
+++ client/app/MainApp/payment/buyvmmodal.coffee
@@ -30,6 +30,8 @@
     else
       return new KDNotificationView
         title : "You are not authorized to create VMs in #{group} group"
+
+    modal = this

     form = new KDFormViewWithFields
       callback              : (formData) =>
@@ -81,17 +83,8 @@
               required      : yes
             messages        :
               required      : "Please select a VM type!"
-          # change            : =>
-            # form = modal.modalTabs.forms["Create VM"]
-            # {desc, selector} = form.inputs
-            # descField        = form.fields.desc
-            # descField.show()
-            # desc.show()
-            # index      = (parseInt selector.getValue(), 10) or 0
-            # monthlyFee = (@paymentPlans[index].feeMonthly/100).toFixed(2)
-            # desc.$('section').addClass 'hidden'
-            # desc.$('section').eq(index).removeClass 'hidden'
-            # modal.setPositions()
+          change            : ->
+            modal.currentPlan = plans[@getValue()]
         desc                :
           itemClass         : KDCustomHTMLView
           cssClass          : "description-field hidden"
@@ -109,11 +102,11 @@
       monthlyFee = plans[index].feeMonthly

       if limit > 0
-        credits = (limit / 100).toFixed(2)
+        credits = (limit / 100).toFixed 2

         creditsMessage = "
This group gives you $#{credits} credits.
"
         if balance > 0
-          spent = (balance / 100).toFixed(2)
+          spent = (balance / 100).toFixed 2
           creditsMessage += "
You've spent $#{spent}.
"

         form.fields.desc.setPartial creditsMessage
@@ -137,9 +130,9 @@
   preparePaymentMethods: (formData) ->
     @setState 'billing choice'

+    paymentController = KD.getSingleton 'paymentController'
+
     paymentField = @choiceForm.fields['Payment method']
-
-    paymentController = KD.getSingleton 'paymentController'

     paymentController.fetchPaymentMethods (err, paymentMethods) =>
       return if KD.showError err
@@ -252,11 +245,15 @@
       when 'confirm'
         confirmData = @getAggregatedData()
         confirmData.billingInfo = @currentMethod
+        confirmData.planInfo = @currentPlan
         @confirmForm.setData confirmData
         @confirmForm.show()

   createConfirmForm: -> new BuyVmConfirmView

+  processPayment: (data) ->
+    debugger
+
   viewAppended: ->
     super

@@ -272,4 +269,6 @@
     @confirmForm = @createConfirmForm()
     @addSubView @confirmForm

+    @confirmForm.on 'PaymentConfirmed', @bound 'processPayment'
+
     @setState 'vm type'


Adding a (big) TODO for the proposal form

--- client/app/views/pages/post_form.tmpl.coffee
+++ client/app/views/pages/post_form.tmpl.coffee
@@ -4,6 +4,9 @@
 {extend} = require('../../modules/utilities')
 {createFieldsData} = require('../../modules/auxiliaries')
 {getFields, getSchema} = require('./post_form.fn')
+
+# TODO-1 Currently there is no way to update an existing entity from the UI,
+#        you can only propose a new entity.

 classes = (formData) ->
     postID = formData['post.id']


fix(search): Do not clear search mail view filter on account switch

Fixes #94

--- src/flux/stores/focused-mail-view-store.coffee
+++ src/flux/stores/focused-mail-view-store.coffee
@@ -16,7 +16,7 @@
   _onCategoryStoreChanged: ->
     if not @_mailView
       @_setMailView(@_defaultMailView())
-    else if not CategoryStore.byId(@_mailView.categoryId())
+    else if @_mailView.categoryId() and not CategoryStore.byId(@_mailView.categoryId())
       @_setMailView(@_defaultMailView())

   _onFocusMailView: (filter) =>


remove search fix for mishandleded queryparams

--- apps/search/routes.coffee
+++ apps/search/routes.coffee
@@ -2,8 +2,6 @@

 @index = (req, res) ->
   term    = req.query.q
-  # Clean up query.q. Sometimes is returned as term?q=term
-  term    = term.split('?q=')[0]
   search  = new Search
   search.fetch
     data: { term: term }


Dont error out in format

Dont error out in format

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1816,9 +1816,10 @@
     c: charType
   [formatStringToken, args...] = _arguments call
   compiledArgs = termsCompile ctx, args
+  if not all map _tea, args
+    call.tea = toConstrained stringType
+    return malformed ctx, call, "Argument not typed"
   call.tea = new Constrained (concatMap (__ _constraints, _tea), args), stringType
-  if not all map _tea, args
-    return malformed ctx, call, "Argument not typed"
   types = []
   formatString = _stringValue formatStringToken
   while formatString.length > 0


Fix getStatistics to use jQuery.get instead of getJSON

--- app/static/script/controller/mineral-ctrl.coffee
+++ app/static/script/controller/mineral-ctrl.coffee
@@ -64,7 +64,7 @@

     $scope.getStatistics = (src) ->
       # $scope.chart.loading = true
-      $.getJSON(src)
+      $.get(src)
         .done (csv) ->
           [csv, header, footer] = dataRows(csv)  # TODO Do this on backend
           result = Papa.parse csv,