Add markdown watcher.

Add markdown watcher.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -96,6 +96,9 @@
       jade:
         files: ['app/**/*.jade']
         tasks: ['jade']
+      markdown:
+        files: ['app/partials/**/*.md']
+        tasks: ['markdown']

     nodemon:
       dev:


Intercom API fix (#3803)

Intercom API fix (#3803)

--- angular/core/services/intercom_service.coffee
+++ angular/core/services/intercom_service.coffee
@@ -69,7 +69,7 @@

     contactUs: ->
       if @available()
-        $window.Intercom.public_api.showNewMessage()
+        $window.Intercom('showNewMessage')
       else
         $window.location = LmoUrlService.contactForm()


Improved error message.

Improved error message.

--- app/assets/javascripts/app/lib/ajax.js.coffee
+++ app/assets/javascripts/app/lib/ajax.js.coffee
@@ -32,10 +32,14 @@
     )

     # show error messages
-    $('body').bind( 'ajaxError', (e,jqxhr, settings, exception) ->
+    $('body').bind( 'ajaxError', ( e, jqxhr, settings, exception ) ->
+      status = jqxhr.status
+      detail  = jqxhr.responseText
+      if !status && !detail
+        detail = 'General communication error, maybe internet is not available!'
       new App.ErrorModal(
-        message: 'StatusCode: ' + jqxhr.status
-        detail:  jqxhr.responseText
+        message: 'StatusCode: ' + status
+        detail:  detail
         close:   true
       )
     )


Add isoscope

Add isoscope

--- app/coffee_scripts/directives/superman.coffee
+++ app/coffee_scripts/directives/superman.coffee
@@ -3,14 +3,29 @@
     restrict: 'ECMA'
     template: "
I am here to save the day
"
     link: (scope,element,attrs) -> #Scope: of containing controller, element: jQuery element
-      console.log 'i am working'
+      #console.log 'i am working'
       element.bind "mouseenter", ->
-        console.log('Inside superman directive')
-        console.log(attrs)
+        #console.log('Inside superman directive')
+        #console.log(attrs)
         element.addClass(attrs.enter)
       element.bind "mouseleave", ->
-        console.log('Leave superman directive')
+        #console.log('Leave superman directive')
         element.removeClass(attrs.enter)
       element.bind 'click', ->
         scope.$apply(attrs.transport)
-      +    scope : {
+      #ability: '='
+      fly: '&transport'
+      ability: '='
+    }
+    controller: ($scope) ->
+      #$scope.ability = []
+
+      @addStrength = () ->
+        $scope.ability.push "strength"
+
+      @addSpeed = () ->
+        $scope.ability.push "speed"
+
+      @
+


fix bugs

fix bugs

--- app/modules/casting/casting.controller.coffee
+++ app/modules/casting/casting.controller.coffee
@@ -24,7 +24,7 @@
         taiga.defineImmutableProperty(@, "agents", () => @currentUserService.agents.get("all"))

         if @auth.getUser()
-            currentUser = @auth.getUser().toJS()
+            currentUser = @auth.getUser()
             console.log("current user is")
             console.log(currentUser)

@@ -42,6 +42,7 @@
                 return b_castingService.getMembersListForAgent(currentUser.id)
             else
                 return b_castingService.getCastingMembers()
+
         .then (response) ->
             b_scope.castingMembers = response.toJS()


chore: Remove stale parans

chore: Remove stale parans

--- app/routes/authenticated/project/files.coffee
+++ app/routes/authenticated/project/files.coffee
@@ -2,7 +2,7 @@

 AuthenticatedProjectFilesRoute = Ember.Route.extend

-  model: () ->
+  model: ->
     @modelFor("authenticated.project").get('files')

 `export default AuthenticatedProjectFilesRoute`


Fix toggling hooks on profile page

Fix toggling hooks on profile page

--- assets/scripts/app/controllers/account.coffee
+++ assets/scripts/app/controllers/account.coffee
@@ -11,8 +11,12 @@
       self.reloadHooks()
     ))

-  toggle: (hook) ->
-    hook.toggle()
+  actions:
+    sync: ->
+      @get('user').sync()
+
+    toggle: (hook) ->
+      hook.toggle()

   reloadHooks: ->
     if login = @get('login')
@@ -35,8 +39,3 @@
   showPublicReposHint: (->
     Travis.config.show_repos_hint == 'public'
   ) .property()
-
-  actions:
-    sync: ->
-      @get('user').sync()
-


Fixed bug where bower files copied to wrong directory.

--- web/Gruntfile.coffee
+++ web/Gruntfile.coffee
@@ -94,7 +94,7 @@
     copy:
       bowerComponents:
         files: [
-          expand: true, src: ['bower_components/**'], dest: 'app/bower_components'
+          expand: true, src: ['bower_components/'], dest: 'app/'
         ]


:bug: fix problem that folders are only expanded by route

--- src/renderer/components/list-folder-component.coffee
+++ src/renderer/components/list-folder-component.coffee
@@ -12,8 +12,9 @@
       folded: @props.folded ? true

   componentWillReceiveProps: (nextProps) ->
-    @setState
-      folded: nextProps.folded ? true
+    if @state.folded
+      @setState
+        folded: nextProps.folded

   shouldComponentUpdate: (nextProps, nextState) ->
     # console.log @props.folded, nextProps.folded, @state.folded, nextState.folded, @props.name


Cancel formatting events

Cancel formatting events

The default behavior is rarely what we want: <b> instead of <strong>, <i> instead of <em>, etc.

--- src/trix/controllers/level_2_input_controller.coffee
+++ src/trix/controllers/level_2_input_controller.coffee
@@ -67,6 +67,7 @@
   formatBackColor: (event) ->

   formatBold: (event) ->
+    event.preventDefault()
     @toggleAttribute("bold")

   formatFontColor: (event) ->
@@ -76,6 +77,7 @@
   formatIndent: (event) ->

   formatItalic: (event) ->
+    event.preventDefault()
     @toggleAttribute("italic")

   formatJustifyCenter: (event) ->
@@ -95,6 +97,7 @@
   formatSetInlineTextDirection: (event) ->

   formatStrikeThrough: (event) ->
+    event.preventDefault()
     @toggleAttribute("strike")

   formatSubscript: (event) ->


Kick semaphore

Kick semaphore

--- models/article.coffee
+++ models/article.coffee
@@ -228,4 +228,4 @@
       "articleSection": @getParselySection()
       "creator": @getAuthorArray()
       "keywords": @get('tags')
-    }
+    }


Fix Collection.buildModel()

Fix Collection.buildModel()

Use model's id_mapping_scope and don't attempt to call parseAttributes on model if it doesn't exist

--- src/marbles/collection.js.coffee
+++ src/marbles/collection.js.coffee
@@ -43,9 +43,10 @@
   @buildModel: (attrs, options = {}) ->
     model_constructor = options.model || @model

-    if @id_mapping_scope && @id_mapping_scope.length && _.every(@id_mapping_scope, ((k) => attrs.hasOwnProperty(k)))
-      model = model_constructor.find(_.inject(@id_mapping_scope, ((m, k) => m[k] = attrs[k]; m), {}), fetch: false)
-      model.parseAttributes(attrs)
+    _id_mapping_scope = @model.id_mapping_scope
+    if _id_mapping_scope && _id_mapping_scope.length && _.every(_id_mapping_scope, ((k) => attrs.hasOwnProperty(k)))
+      model = model_constructor.find(_.inject(_id_mapping_scope, ((m, k) => m[k] = attrs[k]; m), {}), fetch: false)
+      model?.parseAttributes(attrs)
     model ?= new model_constructor(attrs)
     model


Fixing the alias names

Fixing the alias names

--- src/scripts/elasticsearch.coffee
+++ src/scripts/elasticsearch.coffee
@@ -105,6 +105,6 @@
     msg.send("The alias #{alias} has been removed")

   setAlias = (msg, alias, url) ->
-    _nagiosAliases[alias] = url
+    _esAliases[alias] = url
     robot.brain.data._esAliases = _esAliases
     msg.send("The alias #{alias} for #{url} has been added to the brain")


fix(ie8): restore IE8 compatitiblity on Array's filter and map

Fixes #1

--- src/meshweaver.validation.coffee
+++ src/meshweaver.validation.coffee
@@ -635,13 +635,14 @@

         return unless isValid

-        errors = value.filter((entry) -> !entry.isNew())
+        errors = _.chain(value.filter((entry) -> !entry.isNew()))
           .map (entry) -> return entry.validate()
+          .value()

         unless _.find(errors, (error) -> !!error)?
           return

-        errorMessage = _.chain(errors.filter (error) -> !!error)
+        errorMessage = _.chain(errors).filter (error) -> !!error
                     .map (error) -> _.values(error)
                     .flatten().value().join('; ')


Add atom prefix to keys

Add atom prefix to keys

--- build/tasks/compile-packages-slug-task.coffee
+++ build/tasks/compile-packages-slug-task.coffee
@@ -63,7 +63,7 @@

     metadata = grunt.file.readJSON(path.join(appDir, 'package.json'))
     metadata._atomPackages = packages
-    metadata._menu = getMenu(appDir)
-    metadata._keymaps = getKeymaps(appDir)
+    metadata._atomMenu = getMenu(appDir)
+    metadata._atomKeymaps = getKeymaps(appDir)

     grunt.file.write(path.join(appDir, 'package.json'), JSON.stringify(metadata))


adding validation

adding validation

--- app/assets/coffee/entities/feed.coffee
+++ app/assets/coffee/entities/feed.coffee
@@ -29,6 +29,8 @@
         "summary": ""
         "feedUrl": ""
         "authors": ""
+      validate: (attrs) ->
+        "must contain episodes attribute"  unless attrs.episodes
     )
     Entities.configureStorage Entities.Feed
     Entities.Feeds = Backbone.Collection.extend(


Round y calculation one time.

Round y calculation one time.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -300,9 +300,9 @@
         context.fillStyle = "#f2f3f4"
         xPosition = 0
         for value in values
-          height = value * canvas.height
+          height = Math.ceil(value * canvas.height)
           yPosition = canvas.height - height
-          context.fillRect(xPosition, Math.ceil(yPosition), barWidth, Math.ceil(height))
+          context.fillRect(xPosition, yPosition, barWidth, height)
           xPosition = xPosition + barWidth + spaceWidth

   hideQueue: []


Copy url.

Copy url.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -530,7 +530,8 @@
     $('.share-form .url-placeholder').val(url)

     description = feedbin.getSelectedText()
-    $('.share-form .description-placeholder').val(description)
+    url = $('#source_link').attr('href')
+    $('.share-form .description-placeholder').val("#{description} #{url}")

     source = $('.entry-header .author').first().text()
     if source == ""


fixed tweet url in fixer

fixed tweet url in fixer

--- app/assets/javascripts/building.coffee
+++ app/assets/javascripts/building.coffee
@@ -146,7 +146,7 @@

 		$("#score .total").text(@allPolygonsSession)

-		url = $('#progressjs').data("server")
+		url = $('#buildingjs').data("server")
 		tweet = @allPolygonsSession + " buildings checked! Data mining old maps with the Building Inspector from @NYPLMaps @nypl_labs"
 		twitterurl = "https://twitter.com/share?url=" + url + "&text=" + tweet


layer service to interact with forge filter form

--- app/assets/javascripts/forge.js.coffee
+++ app/assets/javascripts/forge.js.coffee
@@ -43,51 +43,57 @@
   $http.defaults.headers.common.Accept = 'application/json'
   return {
     layers: null
+    layer: null
     load: ->
       $http.get('/layers.json').then (response) =>
         @layers = response.data?.items or []
         $rootScope.$broadcast 'layers:updated', @layers
+        if @layers.length is 1
+          @select @layers[0]
+    select: (layer) ->
+      @layer = layer
+      $rootScope.$broadcast 'layers:selected', @layer
   }
 forge.LayerService.$inject = ['$http', '$rootScope']

-forge.BuildingListController = ($rootScope, $scope, $http, $anchorScroll, BuildingService) ->
-  $scope.buildingTypes = window.buildingTypes
+forge.BuildingListController = ($rootScope, $scope, $http, $anchorScroll) ->
   $scope.buildings = []
-  $scope.layers = []
-  $scope.layer = null
-  $scope.form = {}

   $rootScope.$on 'buildings:updated', (event, buildings) ->
     $scope.buildings = buildings

+  return
+forge.BuildingListController.$inject = ['$rootScope', '$scope', '$http', '$anchorScroll']

+
+forge.LayersController = ($rootScope, $scope, BuildingService, LayerService) ->
+  $scope.form = {}
   $scope.form.showOnlyMapBuildings = yes
   $scope.form.buildingType = null
+  $scope.buildingTypes = window.buildingTypes

   $scope.applyFilters = -> BuildingService.load $scope.form
+  $scope.selectLayer  = -> LayerService.select $scope.layer
+
+  $rootScope.$on 'layers:updated', (event, layers) ->
+    $scope.layers = layers
+
+  $rootScope.$on 'layers:selected', (event, layer) ->
+    $scope.layer = layer
+

   BuildingService.load $scope.form
+  LayerService.load()

   return
-forge.BuildingListController.$inject = ['$rootScope', '$scope', '$http', '$anchorScroll', 'BuildingService']

-
-forge.LayersController = ($scope) ->
-  $scope.selectLayer = ->
-    console.log "Layer selected: ", $scope.layer.name
-    $scope.$parent.layer = $scope.layer
-    return
-  return
-forge.LayersController.$inject = ['$scope']
+forge.LayersController.$inject = ['$rootScope', '$scope', 'BuildingService', 'LayerService']

 forge.MapController = ($rootScope, $scope, NgMap, $anchorScroll, $timeout, BuildingService, LayerService) ->
   wmslayer = null

-  $rootScope.$on 'layers:updated', (event, layers) ->
-    $scope.layers = layers
-    $scope.layer = layers[0] if $scope.layers.length is 1
-
-  LayerService.load()
+  $rootScope.$on 'layers:selected', (event, layer) ->
+    $scope.layer = layer

   $rootScope.$on 'buildings:updated', (event, buildings) ->
     $scope.buildings = buildings


locations based on department - remove all options from location on create - show locations based on department on editing items

--- app/assets/javascripts/items.js.coffee
+++ app/assets/javascripts/items.js.coffee
@@ -1,3 +1,13 @@
+filterLocationsBasedOnDepartment = (department, locations, reset = false) ->
+  espaced_department = department.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g, '\\$1')
+  options = $(locations).filter("optgroup[label='#{espaced_department}']").html()
+  if options
+    $('#item_location_id').html(options)
+  else
+    $('#item_location_id').empty()
+  if reset
+    $('#item_location_id').prepend("<option value='' selected='selected'></option>")
+
 jQuery ->
   $('form').on 'click', '.add_fields', (event) ->
     time = new Date().getTime()
@@ -16,12 +26,9 @@
                   { orderable: false, targets: -1 },
                 ]

+  department = $('#item_department_id :selected').text()
   locations = $('#item_location_id').html()
+  filterLocationsBasedOnDepartment(department, locations)
   $('#item_department_id').change ->
     department = $('#item_department_id :selected').text()
-    espaced_department = department.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g, '\\$1')
-    options = $(locations).filter("optgroup[label='#{espaced_department}']").html()
-    if options
-      $('#item_location_id').html(options).prepend("<option value='' selected='selected'></option>")
-    else
-      $('#item_location_id').empty()+    filterLocationsBasedOnDepartment(department, locations, true)


Fix BG parallax

Fix BG parallax

--- app/assets/javascripts/pages.js.coffee
+++ app/assets/javascripts/pages.js.coffee
@@ -47,7 +47,7 @@
     posY = (if (document.documentElement.scrollTop) then document.documentElement.scrollTop else window.pageYOffset)
     ground = document.getElementById("s")
     title = document.getElementById("outer_title")
-    groundparallax = calcParallax(300, 6, posY)
+    groundparallax = calcParallax(300, -6, posY)
     titleparallax = 714 - (Math.floor(posY / 4))
     ground.style.backgroundPosition = "0 " + groundparallax + "px"
     if title


Show only primary doss and ssn in suggestion list

--- app/assets/javascripts/refugees.coffee
+++ app/assets/javascripts/refugees.coffee
@@ -66,7 +66,7 @@
           items: 10
         success: (data) ->
           response $.map data, (item) ->
-            value: "#{item.name} #{item.dossier_numbers.concat(item.ssns).join(', ')}"
+            value: item.value
             id: item.id
     select: (event, ui) ->
       event.preventDefault()
@@ -95,7 +95,7 @@
             if data.length
               items = data.length
               response $.map data, (item) ->
-                $.extend(item, { value: item.name })
+                $.extend(item, { value: item.value })
                 item
             else
               $queryRefugeeField.autocomplete("close")
@@ -116,5 +116,5 @@
       ul.addClass('search-refugees')
       $("
")
         .data("ui-autocomplete-item", item)
-        .append("#{item.name} #{item.dossier_numbers.concat(item.ssns).join(', ')}")
+        .append("#{item.value}")
         .appendTo(ul).after($more)


update words

update words

--- app/assets/javascripts/users.js.coffee
+++ app/assets/javascripts/users.js.coffee
@@ -19,7 +19,7 @@

 	showSuccess = ->
 		# replace current page with success message
-		successMsg = 'Done! Now get Out.'
+		successMsg = "Done! You're all set. Please feel free to close this window."
 		showMsg(successMsg)

 	showError = ->


OX-70 gui integration

OX-70 gui integration

--- app/assets/javascripts/view/gui.coffee
+++ app/assets/javascripts/view/gui.coffee
@@ -27,16 +27,16 @@
                                           "_blank", "width=700,height=400,location=no,menubar=no")

                 resume : false
-                dataset : null
-                changeDataset : ->
+                experiments : null
+                selectedExperimentIndex : null
+                changeExperiment : =>

                   request(
                     method : "POST"
-                    url : "experiment"
-                    data :
-                      dataset : @settings.dataset
-                      resume : @settings.resume
-                  ).done -> window.location.refresh()
+                    url : "/experiment"
+                    contentType : "application/json"
+                    data : @settings.experiments[@settings.selectedExperimentIndex]
+                  ).always -> window.location.reload()

                 position : initPos[0] + ", " + initPos[1] + ", " + initPos[2]
                 lockZoom: data.lockZoom
@@ -75,14 +75,19 @@

     fExperiment = @gui.addFolder("Experiment")
     request(
-      url : "/datasets"
-    ).done (datasets) =>
-
-      (fExperiment.add @settings, "resume")
-                            .name("Resume Experiment")
-      (fExperiment.add @settings, "dataset", datasets)
+      url : "/experiment"
+      responseType : "json"
+    ).done (experiments) =>
+
+      @settings.experiments = experiments
+
+      options = {}
+      for experiment, i in experiments
+        options[experiment.name] = i
+
+      (fExperiment.add @settings, "selectedExperimentIndex", options)
                             .name("Datasets")
-      (fExperiment.add @settings, "changeDataset")
+      (fExperiment.add @settings, "changeExperiment")
                             .name("Apply")


Initial Push to Upstream

Initial Push to Upstream

--- app/classifier/drawing-tools/grid.cjsx
+++ app/classifier/drawing-tools/grid.cjsx
@@ -69,14 +69,14 @@

     <DrawingToolRoot tool={this}>
       {if @state?.template
-        @renderCells(points)
+        <polyline points={points} onClick={@destroyTool.bind null, this} />
       else
         <polyline points={points} onClick={@destroyTool.bind null, this} />}
     </DrawingToolRoot>

   renderCells: (points) ->
     for cell in @state.cells
-
+
       <polyline points={points} onClick={@destroyTool.bind null, this} />

   destroyTool: ->


update test before decryption in find function

--- app/controllers/data_controller.coffee
+++ app/controllers/data_controller.coffee
@@ -110,7 +110,7 @@

 # Decrypt data in field password
 before 'decryptPassword', ()->
-    if not body.docType? or not(body.docType.toLowerCase() is "application")
+    if not @doc.docType? or not(@doc.docType.toLowerCase() is "application")
         encryption.decrypt @doc.password, (err, password) =>
             if err?
                 send error: err, 500


debug merge response

debug merge response

--- app/controllers/data_controller.coffee
+++ app/controllers/data_controller.coffee
@@ -136,4 +136,4 @@
             console.log "[Merge] err: " + JSON.stringify err
             send 500
         else
-            send 200
+            send success: true, 200


Issue #20: add the apply function to the Atom plugin

--- tools/atom-plugin/language-micro/grammars/micro.cson
+++ tools/atom-plugin/language-micro/grammars/micro.cson
@@ -47,7 +47,7 @@
 	}
 	{
 		'comment': 'Built-in functions (it should be described before functions)'
-		'match': '\\b(nil|false|true|num|type|arity|floor|ceil|trunc|round|sin|cos|tn|arcsin|arccos|arctn|angle|pow|sqrt|exp|ln|lg|abs|random|head|tail|if|str|strb|strl|env|time|in|out|outln|err|errln|exit)\\b'
+		'match': '\\b(nil|false|true|num|type|arity|apply|floor|ceil|trunc|round|sin|cos|tn|arcsin|arccos|arctn|angle|pow|sqrt|exp|ln|lg|abs|random|head|tail|if|str|strb|strl|env|time|in|out|outln|err|errln|exit)\\b'
 		'name': 'variable.function.micro'
 	}
 	{


a couple more specs for now

a couple more specs for now

--- test/flux/tasking.spec.coffee
+++ test/flux/tasking.spec.coffee
@@ -138,3 +138,54 @@

     expect(TaskingStore.getTaskingsIsAll(NEW_TASK_ID)).to.be.false

+  it 'should not have dates for creating regardless of defaults', ->
+
+    course = makeCourse()
+    courseDifferent = makeCourse(DEFAULT_TIMES, PERIOD_DEFAULT_TIMES_DIFFERENT)
+    courseDifferent.id = '2'
+
+    TaskingActions.loadDefaults(course.id, course)
+    TaskingActions.loadTaskToCourse(NEW_TASK_ID, course.id)
+    TaskingActions.create(NEW_TASK_ID)
+
+    TaskingActions.loadDefaults(courseDifferent.id, courseDifferent)
+    TaskingActions.loadTaskToCourse('different', courseDifferent.id)
+    TaskingActions.create('different')
+
+    newTaskings = TaskingStore.get(NEW_TASK_ID)
+    newTaskingsDifferent = TaskingStore.get('different')
+
+    _.each newTaskings, (tasking) ->
+      expect(TimeHelper.isDateTimeString(tasking.opens_at)).to.be.false
+      expect(TimeHelper.isDateTimeString(tasking.due_at)).to.be.false
+
+    _.each newTaskingsDifferent, (tasking) ->
+      expect(TimeHelper.isDateTimeString(tasking.opens_at)).to.be.false
+      expect(TimeHelper.isDateTimeString(tasking.due_at)).to.be.false
+
+  it 'should have dates for creating with dates set regardless of defaults', ->
+
+    course = makeCourse()
+    courseDifferent = makeCourse(DEFAULT_TIMES, PERIOD_DEFAULT_TIMES_DIFFERENT)
+    courseDifferent.id = '2'
+
+    TaskingActions.loadDefaults(course.id, course)
+    TaskingActions.loadTaskToCourse(NEW_TASK_ID, course.id)
+    TaskingActions.create(NEW_TASK_ID, {open_date: DATES.YESTERDAY, due_date: DATES.TOMORROW})
+
+    TaskingActions.loadDefaults(courseDifferent.id, courseDifferent)
+    TaskingActions.loadTaskToCourse('different', courseDifferent.id)
+    TaskingActions.create('different', {open_date: DATES.YESTERDAY, due_date: DATES.TOMORROW})
+
+    newTaskings = TaskingStore.get(NEW_TASK_ID)
+    newTaskingsDifferent = TaskingStore.get('different')
+
+    _.each newTaskings, (tasking) ->
+      expect(TimeHelper.isDateTimeString(tasking.opens_at)).to.be.true
+      expect(TimeHelper.isDateTimeString(tasking.due_at)).to.be.true
+
+    _.each newTaskingsDifferent, (tasking) ->
+      expect(TimeHelper.isDateTimeString(tasking.opens_at)).to.be.true
+      expect(TimeHelper.isDateTimeString(tasking.due_at)).to.be.true
+
+


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -122,7 +122,7 @@
   ]

   4.circularPoints (p, i) ->
-    equalEnough p.x, points[i].x, "#{p.x} == #{points[i].x}"
+    equalEnough p.x, points[i].x, 000 "#{p.x} == #{points[i].x}"
     equalEnough p.y, points[i].y, "#{p.y} == #{points[i].y}"

 module undefined


Listen for middle mouse paste on linux

Listen for middle mouse paste on linux

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -405,6 +405,7 @@
     window.addEventListener 'resize', @requestHeightAndWidthMeasurement

     @listenForIMEEvents()
+    @listenForMiddleMousePaste() if process.platform is 'linux'

   listenForIMEEvents: ->
     node = @getDOMNode()
@@ -431,6 +432,19 @@
     node.addEventListener 'compositionend', (event) ->
       editor.insertText(selectedText, select: true, undo: 'skip')
       event.target.value = ''
+
+  listenForMiddleMousePaste: ->
+    clipboard = require 'clipboard'
+
+    @refs.scrollView.getDOMNode().addEventListener 'mouseup', =>
+      if selection = clipboard.readText('selection')
+        {editor} = @props
+        editor.insertText(selection)
+
+    @subscribe @props.editor.onDidChangeSelectionRange =>
+      {editor} = @props
+      if selectedText = editor.getSelectedText()
+        clipboard.writeText(selectedText, 'selection')

   observeConfig: ->
     @subscribe atom.config.observe 'editor.useHardwareAcceleration', @setUseHardwareAcceleration


asterisk now works

asterisk now works

--- bokehjs/src/coffee/renderer/glyph/asterisk.coffee
+++ bokehjs/src/coffee/renderer/glyph/asterisk.coffee
@@ -8,7 +8,8 @@
   class AsteriskView extends Glyph.View

-    _base_glyphspec = ['size']
+    _base_glyphspec : ['x','y', 'size']
+    _data_fields : ['size']
     set_data: (request_render=true) ->
       @set_data_new(request_render)

@@ -29,61 +30,11 @@
           @mask[i] = false
         else
           @mask[i] = true
-      #_render_core()
-      selected = @mget_obj('data_source').get('selected')
-      for idx in selected
-        @selected_mask[idx] = true
-      ctx = @plot_view.ctx
-
-      ctx.save()
-      if @glyph_props.fast_path
-        if selected and selected.length and @nonselection_glyphprops
-          if @selection_glyphprops
-            props =  @selection_glyphprops
-          else
-            props = @glyph_props
-          @_fast_path(ctx, props, true)
-          @_fast_path(ctx, @nonselection_glyphprops, false)
-        else
-          @_fast_path(ctx)
-      else
-        if selected and selected.length and @nonselection_glyphprops
-          if @selection_glyphprops
-            props =  @selection_glyphprops
-          else
-            props = @glyph_props
-          @_full_path(ctx, props, true)
-          @_full_path(ctx, @nonselection_glyphprops, false)
-        else
-          @_full_path(ctx)
-      ctx.restore()
-
-    _fast_path: (ctx, glyph_props, use_selection) ->
-      if glyph_props.line_properties.do_stroke
-        glyph_props.line_properties.set(ctx, @glyph_props)
-        for i in [0..@sx.length-1]
-          if isNaN(@sx[i] + @sy[i] + @size[i]) or not @mask[i]
-            continue
-          if use_selection and not @selected_mask[i]
-            continue
-          if use_selection == false and  @selected_mask[i]
-            continue
-          r = @size[i]/2
-          r2 = r*0.65
-          ctx.beginPath()
-          ctx.moveTo(@sx[i],   @sy[i]+r)
-          ctx.lineTo(@sx[i],   @sy[i]-r)
-          ctx.moveTo(@sx[i]-r, @sy[i])
-          ctx.lineTo(@sx[i]+r, @sy[i])
-          ctx.moveTo(@sx[i]-r2, @sy[i]+r2)
-          ctx.lineTo(@sx[i]+r2, @sy[i]-r2)
-          ctx.moveTo(@sx[i]-r2, @sy[i]-r2)
-          ctx.lineTo(@sx[i]+r2, @sy[i]+r2)
-          ctx.stroke()
+      @_render_core()

     _full_path: (ctx, glyph_props, use_selection) ->
-      if not glyph_props
-        glyph_props = @glyph_props
+      source = @mget_obj('data_source')
+      glyph_props.line_properties.set_prop_cache(source)
       for i in [0..@sx.length-1]
         if isNaN(@sx[i] + @sy[i] + @size[i]) or not @mask[i]
           continue
@@ -104,7 +55,7 @@
         ctx.lineTo(@sx[i]+r2, @sy[i]+r2)

         if glyph_props.line_properties.do_stroke
-          glyph_props.line_properties.set(ctx, @data[i])
+          glyph_props.line_properties.set_vectorize(ctx, i)
           ctx.stroke()

     select: (xscreenbounds, yscreenbounds) ->


FIX: typo [skip ci]

FIX: typo [skip ci]

--- vendor/assets/javascripts/polyfills/polyfills.coffee
+++ vendor/assets/javascripts/polyfills/polyfills.coffee
@@ -1,2 +1,2 @@
-# = require ./native-shim
+#= require ./native-shim
 #= require ./custom-elements


Fix `balena preload --commit current` alias

Change-type: patch

--- lib/actions/preload.coffee
+++ lib/actions/preload.coffee
@@ -213,6 +213,7 @@
 				console.log()
 				spinner.stop()

+		options.commit = if isCurrent(options.commit) then 'latest' else options.commit
 		options.image = params.image
 		options.appId = options.app
 		delete options.app


Bugfix: missing require('fs');

Bugfix: missing require('fs');

--- lib/cake/_vulcanize.coffee
+++ lib/cake/_vulcanize.coffee
@@ -1,6 +1,7 @@
 Promise = require "bluebird"
 pkg = require "#{__dirname}/../package"
 root = "#{__dirname}/../../"
+fs = require "fs"

 _vulcanize = no
 module.exports = vulcanize = ->
@@ -44,6 +45,7 @@
   try
     fs.symlinkSync "#{root}node_modules/prismjs-package", "#{root}node_modules/prism"
   catch err
+    console.log err unless err.code is "EEXIST"
     yes
   for p in deps
     redirects.push "#{path.resolve "#{root}../"+p}|#{root}node_modules/#{p}"


Decode uri in image element

Decode uri in image element

--- lib/emojis-provider.coffee
+++ lib/emojis-provider.coffee
@@ -21,6 +21,10 @@

     suggestions = for word in words when word isnt prefix
       emojiImg = emoji word, @emojiFolder, 20
+      if emojiImg.match(/src="(.*\.png)"/)
+        uri = RegExp.$1
+        emojiImg = emojiImg.replace uri, decodeURIComponent(uri)
+
       new Suggestion this, word: word, prefix: prefix, label: emojiImg, renderLabelAsHtml: true

     return suggestions


fix for tabpane and changed html to text for xss issues

--- client/Framework/components/tabs/KDTabView.coffee
+++ client/Framework/components/tabs/KDTabView.coffee
@@ -263,7 +263,8 @@
   #MODIFY PANES/HANDLES
   setPaneTitle:(pane,title)->
     handle = @getHandleByPane pane
-    handle.getDomElement().find("b").html title
+    handle.getDomElement().find("b").text title
+    handle.setDomAttributes title: title

   getHandleByPane: (pane) ->
     index  = @getPaneIndex pane


activity: recover when there is no bot channel

--- client/activity/lib/util/fetchChatChannels.coffee
+++ client/activity/lib/util/fetchChatChannels.coffee
@@ -14,7 +14,10 @@

     # fetch bot channel.
     account.fetchBotChannel (err, botChannel) ->
-      return callback err  if err
+
+      # if there is an error in the botChannel fetch, return all the channels
+      # that has been fetched before.
+      return callback null, pmChannels  if err

       # fetch bot account.
       fetchAccount 'bot', (err, botAccount) ->


:art: I think this would be more straightforward

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -77,19 +77,18 @@
         else
           operation = new klass(@vimState, properties)

-      if @isEmpty()
-        isValidOperation = true
-        if (@mode is 'visual' and operation.isMotion()) or operation.isTextObject()
-          operation = new Select(@vimState).setTarget(operation)
-      else
-        isValidOperation = @peekTop().isOperator() and (operation.isMotion() or operation.isTextObject())
-
-      if isValidOperation
-        @stack.push(operation)
-        @process()
-      else
-        @vimState.emitDidFailToPushToOperationStack()
-        @vimState.resetNormalMode()
+      switch
+        when @isEmpty()
+          if (@mode is 'visual' and operation.isMotion()) or operation.isTextObject()
+            operation = new Select(@vimState).setTarget(operation)
+          @stack.push(operation)
+          @process()
+        when @peekTop().isOperator() and (operation.isMotion() or operation.isTextObject())
+          @stack.push(operation)
+          @process()
+        else
+          @vimState.emitDidFailToPushToOperationStack()
+          @vimState.resetNormalMode()
     catch error
       @handleError(error)


Don't start transactions for repeated changes

--- lib/operators/input.coffee
+++ lib/operators/input.coffee
@@ -36,7 +36,9 @@
   #
   # Returns nothing.
   execute: (count=1) ->
-    @editor.beginTransaction()
+    # If we've typed, we're being repeated. If we're being repeated,
+    # undo transactions are already handled.
+    @editor.beginTransaction() unless @typed
     operator = new Delete(@editor, @vimState, allowEOL: true, selectOptions: {excludeWhitespace: true})
     operator.compose(@motion)
     operator.execute(count)


appendToBottom is deprecated

appendToBottom is deprecated

--- lib/stacktrace-view.coffee
+++ lib/stacktrace-view.coffee
@@ -14,4 +14,4 @@
       @append $$ ->
         @div "#{relativeFile}:#{item.line} at #{item.caller}", class: 'block failure'

-    atom.workspaceView.prependToBottom(this);
+    atom.workspace.addBottomPanel(item: this);


create the kue jobs

create the kue jobs

--- src/services/interval-kue.coffee
+++ src/services/interval-kue.coffee
@@ -1,13 +1,19 @@
 _ = require 'lodash'
+kue = require('kue')
 debug = require('debug')('interval-service')
-kue = require('kue')

 class IntervalKue
   constructor: (options={}) ->
     @queue = kue.createQueue()

   subscribeNode: (flowId, nodeId, intervalTime) =>
+    @queue.create('subscribe-node', {
+      flowId: flowId,
+      nodeId: nodeId,
+      intervalTime: intervalTime
+    }).save()

   unsubscribeFlow: (flowId) =>
+    @queue.create('unsubscribe-flow', {flowId: flowId}).save()

 module.exports = IntervalKue


hub/db: statement_timeout

hub/db: statement_timeout

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -313,6 +313,13 @@
                     locals.i += 1
                     client.query "SELECT NOW()", (err) =>
                         clearTimeout(timeout)
+                        cb(err)
+                async.map(locals.clients, f, cb)
+            (cb) =>
+                # we set a statement_timeout, to avoid queries locking up PG
+                f = (client, cb) =>
+                    sto = 30 * 60 * 1000 # 30 secs in millisecs
+                    client.query "SET statement_timeout TO #{sto}", (err) =>
                         cb(err)
                 async.map(locals.clients, f, cb)
             (cb) =>


more fine-tunning in fsutil class, handling listeners properly

--- src/toaster/utils/fs-util.coffee
+++ src/toaster/utils/fs-util.coffee
@@ -89,13 +89,13 @@
 	@watch_file:(filepath, onchange, dispatch_create)->
 		filepath = pn filepath

+		FsUtil.unwatch_file filepath
+
 		if dispatch_create
 			onchange?( type:"file", path:filepath, action:"created" )

 		onchange?( type:"file", path:filepath, action:"watching" )
-
-		FsUtil.watched[filepath] = true
-		fs.unwatchFile filepath
+
 		fs.watchFile filepath, {interval : 250}, (curr,prev)=>
 			mtime = curr.mtime.valueOf() != prev.mtime.valueOf()
 			ctime = curr.ctime.valueOf() != prev.ctime.valueOf()
@@ -103,6 +103,12 @@
 				onchange?( {type: "file", path:filepath, action: "updated"} )

+	@unwatch_file:( filepath, onchange )->
+		FsUtil.watched[filepath] = false
+		fs.unwatchFile filepath
+
+
+

 	@snapshots: {}
 	@watchers: {}
@@ -111,9 +117,12 @@

 	@watch_folder:(folderpath, filter_regexp, onchange, dispatch_create)->
+
 		# initialize listeners array
-		FsUtil.watchers[folderpath] = FsUtil.watchers[folderpath] || []
-		watchers = FsUtil.watchers[folderpath]
+		if FsUtil.watchers[folderpath]
+			watchers = FsUtil.watchers[folderpath]
+		else
+			watchers = FsUtil.watchers[folderpath] = []

 		folderpath = pn folderpath
 		onchange?( type:"folder", path:folderpath, action:"watching" )
@@ -134,7 +143,10 @@
 						path:item.path
 						action: "created"

-				FsUtil.watch_folder item.path, filter_regexp, onchange, dispatch_create
+				FsUtil.watch_folder item.path,
+									filter_regexp,
+									onchange,
+									dispatch_create

 			# watch files
 			# ------------------------------------------------------------------
@@ -142,6 +154,7 @@
 				if dispatch_create
 					onchange type:item.type, path:item.path, action: "created"
 				FsUtil.watch_file item.path, onchange
+

 		# add watcher to watchers/listeners array
 		watchers.push {
@@ -155,11 +168,14 @@
 		unless FsUtil.watched[folderpath]
 			FsUtil.watched[folderpath] = true
 			on_folder_change = FnUtil.proxy FsUtil._on_folder_change, folderpath
+
+			fs.unwatchFile folderpath
 			fs.watchFile folderpath, {interval : 250}, on_folder_change

 	@_on_folder_change:( folderpath, curr, prev)=>
+		# console.log "FOLDER CHANGE!"

 		# abort if the folder doest not exist
 		return unless path.existsSync folderpath
@@ -180,6 +196,8 @@
 		# loop all diff itens
 		# ----------------------------------------------------------------------
 		for item in diff
+			# console.log ".........."
+			# console.log "ITEM..."

 			info = item.item
 			info.action = item.action
@@ -192,11 +210,13 @@
 				# --------------------------------------------------------------
 				if info.type == "file"

+					# console.log "FILE CREATED!"
 					# looping all watchers (listeners)
 					for watcher in watchers

 						# dispatch item if it passes the filter regexp
 						if watcher.filter.test info.path
+							# console.log "ON CHANGE!"
 							watcher.onchange?( info )
 							FsUtil.watch_file info.path, watcher.onchange

@@ -226,6 +246,7 @@
 							watcher.onchange?( info )

 					# unwatch file
+					# FsUtil.unwatch_file( path )
 					fs.unwatchFile info.path
 					FsUtil.watched[info.path] = false

@@ -237,8 +258,25 @@
 					for watcher in watchers
 						watcher.onchange?( info )

-					# removes watched flag fpr everything bellow the folder
-					for item, exists of FsUtil.watched
-
-						if exists && new RegExp("^#{info.path}").test( item )
-							FsUtil.watched[item] = false
+					FsUtil.unwatch_folder info.path
+
+
+
+	@unwatch_folder:( folderpath, onchange )->
+
+		# loop through all watchers
+		for watcher_path, list of FsUtil.watchers
+
+			# if it matches the folder path resets all its listeners
+			if new RegExp("^#{folderpath}").test( watcher_path )
+				FsUtil.watchers[watcher_path] = null
+
+		# loop through all watched items
+		for item, exists of FsUtil.watched
+
+			# if its still being watched and matches the regexp
+			if exists && new RegExp("^#{folderpath}").test( item )
+
+				# unwatch item
+				FsUtil.watched[item] = false
+				fs.unwatchFile item


better headerInfo on error events

better headerInfo on error events

--- lib/utils/multipart.coffee
+++ lib/utils/multipart.coffee
@@ -41,8 +41,8 @@
     bodyStream = null
     if !objectStream
       return
-    if !err.error || !err.headerInfo
-      err = {type: 'error', error: err, headerInfo: retsContext.headerInfo}
+    if !err.error
+      err = {type: 'error', error: err, headerInfo: (err.headerInfo ? retsContext.headerInfo)}
     objectStream.write(err)

   handleEnd = () ->


Fixed issue preventing dynamic matchers from working on objects.

--- lib/Spec/ObjectExtensions.coffee
+++ lib/Spec/ObjectExtensions.coffee
@@ -2,15 +2,13 @@
 window.Spec.ObjectExtensions = {
   # Tests for a positive match
   should: (matcher) ->
-    if typeof matcher is 'function'
-      result = matcher(this)
-      Spec.fail "expected #{result[1]}" unless result[0]
+    result = Spec.findMatcher(matcher)(this)
+    Spec.fail "expected #{result[1]}" unless result[0]

   # Tests for a negative match
   shouldNot: (matcher) ->
-    if typeof matcher is 'function'
-      result = matcher(this)
-      Spec.fail "expected not #{result[1]}" if result[0]
+    result = Spec.findMatcher(matcher)(this)
+    Spec.fail "expected not #{result[1]}" if result[0]

   # Creates a stub method, with an expectation of no calls
   shouldNotReceive: (name) ->


Fix backend stop command

Fix backend stop command

--- lib/ghc-modi-process-base.coffee
+++ lib/ghc-modi-process-base.coffee
@@ -176,8 +176,8 @@
   killProcess: =>
     return unless @processMap?
     debug "Killing all ghc-modi processes"
-    atom.project.getDirectories().forEach (dir) =>
-      @killProcessForDir dir
+    atom.workspace.getTextEditors().forEach (editor) =>
+      @killProcessForDir @getRootDir(editor.getBuffer())

   killProcessForDir: (dir) =>
     return unless @processMap?


"Corrected ordering of recent plunks"

"Corrected ordering of recent plunks"

--- lib/plunker/stores/memory.coffee
+++ lib/plunker/stores/memory.coffee
@@ -42,7 +42,7 @@
         self._add(plunk) for id, plunk of JSON.parse(data)
         console.log "Restore completed"

-        self.queue = _.sortBy self.queue, (id) -> new cromag(self.plunks[id].created_at).valueOf()
+        self.queue = _.sortBy self.queue, (id) -> -new cromag(self.plunks[id].created_at).valueOf()

     setInterval @backup, 1000 * 30 # Every 30s


Fix copying `ResultsView` after opening and closing a search result

--- lib/project/results-model.coffee
+++ lib/project/results-model.coffee
@@ -4,7 +4,18 @@

 class Result
   @create: (result) ->
-    if result?.matches?.length then new Result(result) else null
+    if result?.matches?.length
+      matches = result.matches.map((m) ->
+        return {
+          matchText: m.matchText,
+          lineText: m.lineText,
+          lineTextOffset: m.lineTextOffset,
+          range: m.range
+        }
+      )
+      new Result({filePath: result.filePath, matches})
+    else
+      null

   constructor: (result) ->
     _.extend(this, result)


Debug intro ending

Debug intro ending


Fixed #76

Fixed #76

--- lib/views/claim-core-view.coffee
+++ lib/views/claim-core-view.coffee
@@ -58,6 +58,8 @@
         else
           @miniEditor.setEnabled true
           @miniEditor.editor.addClass 'editor-error'
+          if e.errors.length == 2
+            delete e.errors[1]
           @showError e.errors

           @claimPromise = null


Return to homepage when finished response

--- app/assets/javascripts/actions/index.coffee
+++ app/assets/javascripts/actions/index.coffee
@@ -68,7 +68,12 @@
       dispatch @startFinishResponse()
       $.putJSON("/responses/#{responseUUID}").then (data) =>
         dispatch @receiveFinishResponse(data)
-        dispatch routerActions.push('/') #  Return to home page
+        dispatch
+          type: '@@router/LOCATION_CHANGE'
+          payload:
+            pathname: '/'
+            search: ''
+            hash: ''

   startFinishResponse: ->
     type: 'START_FINISH_RESPONSE'


fix datatables pagination

fix datatables pagination

--- app/assets/javascripts/datatables.js.coffee
+++ app/assets/javascripts/datatables.js.coffee
@@ -10,7 +10,7 @@
       orderable: false
       targets: 'no-sort'
     }]
-    iDisplayLength: 20
+    iDisplayLength: 25

 jQuery ->
 # Set up datatable
@@ -20,4 +20,4 @@
       orderable: false
       targets: 'no-sort'
     }]
-    iDisplayLength: 20
+    iDisplayLength: 10


Fix syntax highlighting

Fix syntax highlighting

Don't break highlighting on multi-line spans (multi-line comments for
example)

--- app/assets/javascripts/dispatcher.js.coffee
+++ app/assets/javascripts/dispatcher.js.coffee
@@ -59,7 +59,7 @@

   initHighlight: ->
     $('.highlight pre code').each (i, e) ->
+      $(e).html($.map($(e).html().split("\n"), (line, i) ->
+        "" + line + ""
+      ).join("\n"))
       hljs.highlightBlock(e)
-      $(e).html($.map($(e).html().split("\n"), (line, i) ->
-        "
" + line + "
"
-      ).join("\n"))


FIxes referenceLayer display.

FIxes referenceLayer display.

--- app/assets/javascripts/map_editor.js.coffee
+++ app/assets/javascripts/map_editor.js.coffee
@@ -478,7 +478,7 @@
         else
           this.reference = L.GeoJSON.geometryToLayer(this.options.show)

-        if reference?
+        if this.reference?
           this.reference.setStyle this.options.showStyle
           this.reference.addTo this.map
       this


Don't jump around in Safari when a tab is focused

--- app/assets/javascripts/sugar/tabs.js.coffee
+++ app/assets/javascripts/sugar/tabs.js.coffee
@@ -9,7 +9,10 @@
       jQuery(@parentNode).removeClass "active"

   controls.showTab = (tab) ->
-    @hideAllTabs()
+    jQuery(@tabs).each ->
+      unless this.tabId == tab.tabId
+        jQuery(@tabId).hide()
+        jQuery(@parentNode).removeClass "active"
     jQuery(tab.tabId).show()
     jQuery(tab.parentNode).addClass "active"


Fix internal links.

Fix internal links.

--- app/assets/javascripts/views/root.js.coffee
+++ app/assets/javascripts/views/root.js.coffee
@@ -27,7 +27,7 @@
     $(@el).bind 'clouds:initialize', => @renderSidebar()

     $('a').live 'click', (e) ->
-      if @hostname.match(document.location.hostname) != null && @pathname.match(/\/auth\/.*/ig) == null && @pathname.match(/\/logout/ig) == null
+      if (@hostname == document.location.hostname) && (@pathname.match(/^\/(auth|logout|login|register|password|oauth\/.*/ig) == null)
         Backbone.history.navigate(@pathname,true)
         e.preventDefault()
         false


logging

logging

--- chrome/content/zotero-opds/zotero-opds.coffee
+++ chrome/content/zotero-opds/zotero-opds.coffee
@@ -17,7 +17,7 @@
         when m instanceof Error then "#{e}\n#{e.stack}"
         else JSON.stringify(m)

-    Zotero.debug("[better-bibtex] #{msg.join(' ')}")
+    Zotero.debug("[opds] #{msg.join(' ')}")
     return

   url: ->
@@ -237,6 +237,7 @@
           for collection in collection.getChildCollections() or []
             @collection(collection)

+          Zotero.OPDS.log("collection #{collection.name} has #{(collection.getChildItems(false) || []).length} items")
           for item in collection.getChildItems(false) || []
             @item(item)

@@ -365,6 +366,7 @@
     else
       attachments = item.getAttachments() or []
       attachments = Zotero.Items.get(attachments) if attachments.length != 0
+    Zotero.OPDS.log("'#{item.getDisplayTitle(true)}' has #{attachments.length} attachments")
     attachments = (a for a in attachments when a.attachmentMIMEType? != "text/html")

     return if attachments.length == 0


onboarding: fix to refresh throbbers when minimizing collaboration chat

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -733,7 +733,7 @@
     { onboardingController } = kd.singletons
     onboardingController.runOnboarding OnboardingEvent.CollaborationStarted
     @chat.on ['ViewBecameHidden', 'ViewBecameVisible'], ->
-      onboardingController.refreshOnboarding()
+      onboardingController.refreshOnboarding OnboardingEvent.CollaborationStarted

   onCollaborationEnding: ->


More consistent callback names in pod controller

--- static/coffee/controllers.coffee
+++ static/coffee/controllers.coffee
@@ -836,9 +836,9 @@
     $scope.podData.actions = updateAction(type, {isBusy: true})

     PodApi.trigger($scope.podId, $scope.caseId, type, payload)
-      .then((res) -> triggerDone(type, res))
-
-  triggerDone = (type, {success, payload}) ->
+      .then((res) -> onTriggerDone(type, res))
+
+  onTriggerDone = (type, {success, payload}) ->
     if success
       onTriggerSuccess()
     else


[backport] remove console.log ¬¬

[backport] remove console.log ¬¬

--- app/coffee/modules/common/estimation.coffee
+++ app/coffee/modules/common/estimation.coffee
@@ -40,7 +40,6 @@

     link = ($scope, $el, $attrs, $model) ->
         $scope.$watch $attrs.ngModel, (us) ->
-            console.log "watch"
             if us
                 estimationProcess = $tgEstimationsService.create($el, us, $scope.project)
                 estimationProcess.onSelectedPointForRole = (roleId, pointId) ->


better section length checking

better section length checking

fixes CNVS-12786

test plan:
 - regression on assignment edit page due dates

Change-Id: Ib8391e776e69794c4c828a073df1c137cea082cd
Reviewed-on: https://gerrit.instructure.com/34119
Reviewed-by: Liz Abinante <ce3305c9be70e1a5a9ff49df19e1a5b24d5a47f8@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Product-Review: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
QA-Review: Anna Koalenz <8df3af669343a6e717fd7441a9deea414b21645f@instructure.com>

--- app/coffeescripts/models/DueDateList.coffee
+++ app/coffeescripts/models/DueDateList.coffee
@@ -9,6 +9,9 @@
   class DueDateList

     constructor: (@overrides, @sections, @assignment) ->
+      # how many course sections before we add defaults
+      @courseSectionsLength = @sections.length
+
       unless @sections.length
         # If there's no "default" section for the course already present,
         # such as a course that has been created without one, create a fake one
@@ -66,14 +69,7 @@

     containsSectionsWithoutOverrides: =>
       return false if @overrides.containsDefaultDueDate()
-      @sectionsWithOverrides().length < @_sectionsLength()
-
-    _sectionsLength: =>
-      if @assignment?
-        # to account for default section
-        @sections.length - 1
-      else
-        @sections.length
+      @sectionsWithOverrides().length < @courseSectionsLength

     sectionIDs: => @sections.ids()


exclude radio from dummy data test

exclude radio from dummy data test

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -467,7 +467,7 @@
         # this is an order request
         $(input).val('order')
         submitTxt = "Submit Order Request"
-        $('label[for="form-submit"]').text(submitTxt)
+        $('label[for="form-submit"]').text(submitTxt)

         if $(p_transfer).hasClass('Button--state-active')
           $('#payment-text').text(paymentText + bankTransferRequest)
@@ -498,7 +498,7 @@

         if !$(button).hasClass('Button--state-active')
           $(paymentButton).toggleClass('Button--state-active')
-
+
         @updateOrderTypePaymentText()

   eventSubmitForm: ->
@@ -677,7 +677,7 @@
               else
                 $("span[name='#{$(input).prop('id')}']").addClass('checkmark--active')
                 $(".Form-shipping-container").fadeIn(250)
-            else
+            else if $(input).prop('type') != 'radio'
               $(input).val(testData[$(input).prop('name')])
           console.log('Test Success')


more fixes to symbols spec

more fixes to symbols spec

--- src/packages/symbols-view/spec/symbols-view-spec.coffee
+++ src/packages/symbols-view/spec/symbols-view-spec.coffee
@@ -16,7 +16,7 @@
     rootView.deactivate()
     setArraySpy.reset()

-  fdescribe "when tags can be generated for a file", ->
+  describe "when tags can be generated for a file", ->
     it "initially displays all JavaScript functions with line numbers", ->
       rootView.open('sample.js')
       expect(rootView.find('.symbols-view')).not.toExist()
@@ -165,7 +165,7 @@
         editor.setCursorBufferPosition([8,14])
         editor.trigger 'symbols-view:go-to-declaration'
         expect(symbolsView.list.children('li').length).toBe 1
-        expect(symbolsView.list.children('li:first').find('.function-name')).toHaveText 'tagged.js'
+        expect(symbolsView.list.children('li:first').find('.label')).toHaveText 'tagged.js'

   describe "project symbols", ->
     it "displays all tags", ->
@@ -181,9 +181,9 @@
         expect(symbolsView.find('.loading')).toBeEmpty()
         expect(rootView.find('.symbols-view')).toExist()
         expect(symbolsView.list.children('li').length).toBe 4
-        expect(symbolsView.list.children('li:first').find('.function-name')).toHaveText 'callMeMaybe'
+        expect(symbolsView.list.children('li:first').find('.label')).toHaveText 'callMeMaybe'
         expect(symbolsView.list.children('li:first').find('.function-details')).toHaveText 'tagged.js'
-        expect(symbolsView.list.children('li:last').find('.function-name')).toHaveText 'thisIsCrazy'
+        expect(symbolsView.list.children('li:last').find('.label')).toHaveText 'thisIsCrazy'
         expect(symbolsView.list.children('li:last').find('.function-details')).toHaveText 'tagged.js'
         expect(symbolsView).not.toHaveClass "error"
         expect(symbolsView.error).not.toBeVisible()


needed to pass arguments allow for changing view (#1493)

--- src/scripts/modules/media/body/embeddables/coach.coffee
+++ src/scripts/modules/media/body/embeddables/coach.coffee
@@ -167,6 +167,6 @@

       _.clone(options)

-    openCoach: =>
+    openCoach: (args...) =>
       options = @getOptionsForCoach()
-      @cc.open(options)
+      @cc.open(options, args...)


fix mailfred for preview pane

fix mailfred for preview pane

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -36,9 +36,11 @@
 			LAST_VERSION:	'lastVersion'

 		@GM_SEL:
-			ARCHIVE_BUTTON: 	'.T-I.J-J5-Ji.lR.T-I-ax7.T-I-Js-IF.ar7:visible'
-			THREAD_BUTTON_BAR: 	'.iH > div'
-			INSERT_AFTER: 		'.G-Ni.J-J5-Ji:visible:nth-child(3)'
+			ARCHIVE_BUTTON: 				'.T-I.J-J5-Ji.lR.T-I-ax7.T-I-Js-IF.ar7:visible'
+			THREAD_BUTTON_BAR: 				'.iH > div'
+			INSERT_AFTER: 					'.G-Ni.J-J5-Ji:visible:nth-child(3)'
+			PREVIEW_PANE_ENABLED:			'.apF .apJ'
+			PREVIEW_PANE_THREAD_BUTTON_BAR:	"[gh='mtb'] > div > div"

 		# URL
 		url: null
@@ -47,6 +49,7 @@
 		currentGmail: null
 		settingEmail: null
 		settingProps: {}
+		selectedConversationId: null
 		# lastUsed: []

 		currentView: null
@@ -187,6 +190,7 @@

 						when 'viewThread'
 						# User moves to previous or next convo
+							@selectedConversationId = evt.args[0]
 							@inject()
 						when 'viewChanged'
 						# User switches view (conversation <-> threads)
@@ -229,10 +233,22 @@
 		#	navs.append @composeButton M.TYPE_NAV if navs.length > 0
 		#	return

+		isPreviewPaneEnabled: ->
+			($ M.GM_SEL.PREVIEW_PANE_ENABLED).length > 0
+
 		injectThread: ->
 			log 'Injecting buttons in thread view'
-			threads = ($ M.GM_SEL.THREAD_BUTTON_BAR).filter (index) ->
+
+			if @isPreviewPaneEnabled()
+				# Preview pane is enabled
+				sel = M.GM_SEL.PREVIEW_PANE_THREAD_BUTTON_BAR
+			else
+				# Preview pane is not enabled
+				sel = M.GM_SEL.THREAD_BUTTON_BAR
+
+			threads = ($ sel).filter (index) ->
 				($ ".#{M.CLS_THREAD}", @).length is 0
+
 			if threads.length > 0
 				after = threads.find M.GM_SEL.INSERT_AFTER
 				bar = @composeButton()
@@ -494,18 +510,21 @@
 						@_specified other

 		getMessageId: ->
-			id = /\/([0-9a-f]{16})/.exec window.location.hash
+			if @isPreviewPaneEnabled()
+				id = @selectedConversationId
+			else
+				id = /\/([0-9a-f]{16})/.exec window.location.hash
+				id = id?[1]
+
 			if id is null
 				throw __msg 'errorNotWithinAConversation'
-			else
-				id[1]
+			id

 		onSchedule: (props, loadingIcon, resetIcon) =>
 			try
 				messageId = @getMessageId()
 			catch e
-				chrome.extension.sendMessage
-					error: e.toString()
+				@onScheduleError null, null, e.toString()
 				return

 			archive = !!props.archive


move redirect into success callback

move redirect into success callback

--- desktop/apps/editorial_features/routes.coffee
+++ desktop/apps/editorial_features/routes.coffee
@@ -41,7 +41,11 @@
   @curation.fetch
     success: (curation) ->
       res.locals.sd.CURATION = curation.toJSON()
-      videoIndex = setVideoIndex(curation, req.params.slug, res) or 0
+      videoIndex = 0
+      if req.params.slug
+        videoIndex = setVideoIndex(curation, req.params.slug)
+        unless videoIndex
+          return res.redirect 301, '/venice-biennale'
       res.render 'components/venice_2017/templates/index',
         videoIndex: videoIndex
         curation: curation
@@ -56,8 +60,7 @@
   proxy.web req, res, target: target, (err) ->
     res.redirect 301, '/articles' if err

-setVideoIndex = (curation, slug, res) ->
+setVideoIndex = (curation, slug) ->
   for section, i in curation.get 'sections'
     if section.slug is slug
-      return i
-  return res.redirect 301, '/venice-biennale'+      return i


fix tests

fix tests

--- apps/artwork_purchase/test/templates.coffee
+++ apps/artwork_purchase/test/templates.coffee
@@ -86,14 +86,6 @@
         zip: '10013'
         country: 'USA'

-    html.should.containEql 'Full Name'
-    html.should.containEql 'Address Line 1'
-    html.should.containEql 'Address Line 2'
-    html.should.containEql 'City'
-    html.should.containEql 'State'
-    html.should.containEql 'Zip code'
-    html.should.containEql 'Country'
-
     html.should.containEql 'input name="name" value="Foo Bar"'
     html.should.containEql 'input name="street1" value="401 Broadway"'
     html.should.containEql 'input name="street2" value="c/o Artsy"'
@@ -105,14 +97,6 @@
   it 'otherwiser renders empty inputs', ->
     html = render('address_form') purchase: {}

-    html.should.containEql 'Full Name'
-    html.should.containEql 'Address Line 1'
-    html.should.containEql 'Address Line 2'
-    html.should.containEql 'City'
-    html.should.containEql 'State'
-    html.should.containEql 'Zip code'
-    html.should.containEql 'Country'
-
     html.should.not.containEql 'value='

 describe 'summary', ->
@@ -123,7 +107,7 @@
     $html = $(html)
     $html.find('.ap-summary__artist').text().should.eql "John Doe"
     $html.find('.ap-summary__title').text().should.eql "Foo Bar"
-    $html.find('.ap-summary__partner').text().should.eql "Galerie Foo Bar"
+    $html.find('.ap-summary__partner').text().should.eql "Offered by Galerie Foo Bar"
     $html.find('.ap-summary__price').text().should.containEql 'Price'
     $html.find('.ap-summary__price').text().should.containEql '€9,500'


Check auto_remove data correctly

Check auto_remove data correctly

auto_remove can be 0, so auto_remove? is proper check for that.


Fix excerpt level display

Fix excerpt level display

--- app/assets/volume/spreadsheet.coffee
+++ app/assets/volume/spreadsheet.coffee
@@ -349,7 +349,7 @@
             $location.url if editing then slots[i].editRoute(t) else slots[i].route(t)
           icon.className = "format hint-format-" + a.format.extension
           generateCell(row, 'classification', a.classification, id + '-class_' + b)
-          generateCell(row, 'excerpt', a.excerpt, id + '-excerpt_' + b)
+          generateCell(row, 'excerpt', Math.max(a.classification, a.excerpt), id + '-excerpt_' + b)
           return

         # Fill out rows[i].


Tag::queryAll() with asterisk fixed

Tag::queryAll() with asterisk fixed

--- Element/element/tag/query.coffee
+++ Element/element/tag/query.coffee
@@ -194,9 +194,9 @@

 	# set iterator
 	for funcs in queries
-		if not reversed and not funcs[0].isIterator
+		if not reversed and not funcs[0]?.isIterator
 			funcs[reversedArrFunc] distantTagFunc, null, null
-		else if opts & OPTS_QUERY_BY_PARENTS and not funcs[0].isIterator
+		else if opts & OPTS_QUERY_BY_PARENTS and not funcs[0]?.isIterator
 			funcs[arrFunc] distantTagFunc, null, null

 	# save to the cache


Support silencing keepalive

Support silencing keepalive

--- app/scripts/views/event-modal.coffee
+++ app/scripts/views/event-modal.coffee
@@ -25,7 +25,9 @@

     @event.on('change', this.update, this)
     @client.on('change', this.update, this)
-    @check.on('change', this.update, this)
+
+    # Strangely: keepalive doesn't have a check
+    @check?.on('change', this.update, this)

   render: ->
     @update()
@@ -33,11 +35,12 @@
     @$el.modal("show")

   update: ->
+    console.log(@check?.toJSON() ? (new MetaDash.Models.Check).toJSON())
     html = @template(
       {
-        event: @event?.toJSON({helperAttributes: true}) ? {}
-        client: @client?.toJSON() ? {}
-        check: @check?.toJSON() ? {}
+        event: @event?.toJSON({helperAttributes: true}) ? (new MetaDash.Models.Event).toJSON({helperAttributes: true}),
+        client: @client?.toJSON() ? (new MetaDash.Models.Client).toJSON(),
+        check: @check?.toJSON() ? (new MetaDash.Models.Check).toJSON()
       }
     )
     @$el.html(html);


- [grunt] added various file extensions to `sprites` in `grunt-contrib-watch`

--- tasks/grunt-contrib-watch.coffee
+++ tasks/grunt-contrib-watch.coffee
@@ -12,7 +12,7 @@
         'autoprefixer'
       ]
     sprite:
-      files: ['<%= path.source.sprites %>/{,**/}*.png']
+      files: ['<%= path.source.sprites %>/{,**/}*.{jpg,jpeg,gif,png}']
       tasks: ['sprite']
     nunjucks:
       files: ['<%= path.source.layouts %>/{,**/}*.nj']


Add frequency and refills to medication datatype

--- app/assets/javascripts/datatypes/medication.js.coffee
+++ app/assets/javascripts/datatypes/medication.js.coffee
@@ -34,7 +34,7 @@
   ###
   dosage: ->
     if @_dosage?
-      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['value']})
+      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['scalar']})
     else
       null

@@ -74,7 +74,7 @@
   ###
   supply: ->
     if @_supply?
-      new cql.Quantity({unit: @_supply['unit'], value: @_supply['value']})
+      new cql.Quantity({unit: @_supply['unit'], value: @_supply['scalar']})
     else
       null

@@ -116,7 +116,7 @@
   ###
   dosage: ->
     if @_dosage?
-      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['value']})
+      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['scalar']})
     else
       null

@@ -174,7 +174,7 @@
   ###
   supply: ->
     if @_supply?
-      new cql.Quantity({unit: @_supply['unit'], value: @_supply['value']})
+      new cql.Quantity({unit: @_supply['unit'], value: @_supply['scalar']})
     else
       null

@@ -210,7 +210,7 @@
   ###
   dosage: ->
     if @_dosage?
-      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['value']})
+      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['scalar']})
     else
       null

@@ -255,7 +255,7 @@
   ###
   supply: ->
     if @_supply?
-      new cql.Quantity({unit: @_supply['unit'], value: @_supply['value']})
+      new cql.Quantity({unit: @_supply['unit'], value: @_supply['scalar']})
     else
       null

@@ -301,7 +301,7 @@
   ###
   dosage: ->
     if @_dosage?
-      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['value']})
+      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['scalar']})
     else
       null

@@ -357,7 +357,7 @@
   ###
   supply: ->
     if @_supply?
-      new cql.Quantity({unit: @_supply['unit'], value: @_supply['value']})
+      new cql.Quantity({unit: @_supply['unit'], value: @_supply['scalar']})
     else
       null

@@ -376,7 +376,7 @@
     @_activeDatetime = CQL_QDM.Helpers.convertDateTime(@entry.active_datetime)
     @_authorDatetime = CQL_QDM.Helpers.convertDateTime(@entry.start_time)
     @_dosage = @entry.dose
-    @_frequency = @entry.frequency
+    @_frequency = @entry.administrationTiming
     @_method = @entry.method
     @_negationRationale = @entry.negationReason
     @_reason = @entry.reason
@@ -407,7 +407,7 @@
   ###
   dosage: ->
     if @_dosage?
-      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['value']})
+      new cql.Quantity({unit: @_dosage['unit'], value: @_dosage['scalar']})
     else
       null

@@ -416,7 +416,7 @@
   ###
   frequency: ->
     # TODO: Frequency currently not in HDS model.  Can probably add by calculating based off of dose & regimen
-    throw new Error('Bonnie does not currently support MedicationOrder.frequency')
+    #throw new Error('Bonnie does not currently support MedicationOrder.frequency')
     if @_frequency?
       new cql.Code(@_frequency.code, @_frequency.code_system)
     else
@@ -454,7 +454,11 @@
   ###
   refills: ->
     # TODO: Refills should represent 'The number of refills allowed by the prescription.'
-    throw new Error('Bonnie does not currently support MedicationOrder.refills')
+    #throw new Error('Bonnie does not currently support MedicationOrder.refills')
+    if @_refills?
+      parseInt(@_refills['scalar'])
+    else
+      null

   ###
   @returns {Interval<Date>}
@@ -481,6 +485,6 @@
   ###
   supply: ->
     if @_supply?
-      new cql.Quantity({unit: @_supply['unit'], value: @_supply['value']})
-    else
-      null
+      new cql.Quantity({unit: @_supply['unit'], value: @_supply['scalar']})
+    else
+      null


refining how tokenisation works

refining how tokenisation works

--- fizzylogo_tokenizer.coffee
+++ fizzylogo_tokenizer.coffee
@@ -16,15 +16,25 @@
   command = command.replace /;/g, " ; "

   # separate numbers and punctuation from anything else
-  # note that numbers and punctuation are dealt together because
+  # note that:
+  # 1) numbers and punctuation are dealt together because
   # otherwise something like 3.14 is separated into 3 . 14
   # so we need to handle them both here so we get that floating
   # point case before we separate the dots
-  command = command.replace /([0-9]*\.[0-9]+([eE][- ]?[0-9]*)?)|([+\-^*/()=←⇒.])/g, " $1 $3 "
-
-  # re-group consecutive punctuation, so things like ++, --, +=, **
-  # ^^, etc. can all live separately to have their own meaning
-  command = command.replace /([+\-^*/=←⇒.])[ ]*([+\-^*/=←⇒.])/g, "$1$2"
+  #
+  # 2) consecutive operators like ++, --, +=, **
+  # ^^ are left untouched, so you can give good meaning to them
+  # for example this:
+  #   a++ +
+  # becomes
+  #   a ++ +
+  # so the spacing between the operators can be significant
+  #
+  # see this playground with an example:
+  # https://regex101.com/r/LRqxeN/3
+  #
+  command = command.replace /([0-9]*\.[0-9]+([eE][- ]?[0-9]*)?)|([^+\-^*/()=←⇒.])([+\-^*/()=←⇒.]+)/g, "$1$3 $4 "
+

   command = command.replace /'/g, " ' "
   command = command.replace /:/g, " : "


Fixed missed conflict

Fixed missed conflict

--- app/assets/javascripts/merge_request_widget.js.coffee
+++ app/assets/javascripts/merge_request_widget.js.coffee
@@ -72,15 +72,11 @@
         @firstCICheck = false
         @opts.ci_status = data.status

-<<<<<<< a918e8bf277418048776a5d9c34a64b39f4e56f3
       if @opts.ci_status is ''
         @opts.ci_status = data.status
         return

-      if data.status isnt @opts.ci_status
-=======
       if data.status isnt @opts.ci_status and data.status?
->>>>>>> Build notification null check
         @showCIStatus data.status
         if data.coverage
           @showCICoverage data.coverage


使用类方法

使用类方法

--- app/assets/javascripts/question_bank/questions.coffee
+++ app/assets/javascripts/question_bank/questions.coffee
@@ -11,7 +11,8 @@
     that = this

     @$sin.on 'click', '.add-choice', ->
-      $choice_answer_indexs = jQuery('.form-question-single-choice .question_choice_answer_indexs')
+      $choice_answer_indexs = that.$sin.find('.question_choice_answer_indexs')
+      $add = that.$sin.find('.add-choice')
       next_choice_answer_index = $choice_answer_indexs.find('.radio:not(.hidden)').length
       dom = $choice_answer_indexs.find('.radio:first').clone()
       dom.removeClass('hidden')
@@ -20,8 +21,7 @@
       jQuery(this).before(dom)

     @$sin.on 'click', '.delete-choice', ->
-      $choice_answer_indexs = jQuery('.form-question-single-choice .question_choice_answer_indexs')
-
+      $choice_answer_indexs = that.$sin.find('.question_choice_answer_indexs')
       $delete_radio = jQuery(this).closest('.radio')
       delete_index  = parseInt($delete_radio.find('input:first').val())
       last_index    = $choice_answer_indexs.find('.radio:not(.hidden)').length - 1
@@ -33,7 +33,7 @@

     @$sin.on 'submit', (evt)->
-      $choice_answer_indexs = jQuery('.form-question-single-choice .question_choice_answer_indexs')
+      $choice_answer_indexs = that.$sin.find('.question_choice_answer_indexs')
       $choice_answer_indexs.find('.radio.hidden').remove()
 class MultiChoice
   constructor: (@$mul)->
@@ -43,7 +43,7 @@
     that = this

     @$mul.on 'click', ' .add-choice', ->
-      $choice_answer_indexs = jQuery('.form-question-multi-choice .question_choice_answer_indexs')
+      $choice_answer_indexs = that.$mul.find('.question_choice_answer_indexs')
       next_choice_answer_index = $choice_answer_indexs.find('.checkbox:not(.hidden)').length
       dom = $choice_answer_indexs.find('.checkbox:first').clone()
       dom.removeClass('hidden')
@@ -52,7 +52,7 @@
       jQuery(this).before(dom)

     @$mul.on 'click', ' .delete-choice', ->
-      $choice_answer_indexs = jQuery('.form-question-multi-choice .question_choice_answer_indexs')
+      $choice_answer_indexs = that.$mul.find('.question_choice_answer_indexs')

       $delete_checkbox = jQuery(this).closest('.checkbox')
       delete_index = parseInt($delete_checkbox.find('input:first').val())
@@ -63,8 +63,8 @@

       $delete_checkbox.remove()

-    @$mul.on 'submit', ' form', (evt)->
-      $choice_answer_indexs = jQuery('.form-question-multi-choice .question_choice_answer_indexs')
+    @$mul.on 'submit','form', (evt)->
+      $choice_answer_indexs = that.$mul.find('.question_choice_answer_indexs')
       $choice_answer_indexs.find('.checkbox.hidden').remove()
 class Mapping
   constructor: (@$map)->
@@ -77,15 +77,15 @@
       position_atr = jQuery(this).closest('.item').find('input').attr('name');
       zhengze = new RegExp(/[0-9]+/)
       position = zhengze.exec(position_atr)
-      item_length = jQuery(".form-question-mapping .item").length
+      item_length = that.$map.find(".item").length
       x
       for x in [position...item_length]
         q = x - 1
-        jQuery(".form-question-mapping .item").eq(x).find("input").attr('name','question[mapping_answer]['+q+'][]')
+        that.$map.find(".item").eq(x).find("input").attr('name','question[mapping_answer]['+q+'][]')
       if item_length == 1
-        fuben = jQuery(".form-question-mapping .item:first").clone()
-        jQuery(".form-question-mapping .item:first").after(fuben)
-        jQuery(".form-question-mapping .item:last").addClass('hidden')
+        fuben = that.$map.find(".item:first").clone()
+        that.$map.find(".item:first").after(fuben)
+        that.$map.find(".item:first").addClass('hidden')
       jQuery(this).closest('.item').remove();

     @$map.on 'click',' .append',->
@@ -94,15 +94,15 @@
         jQuery('.option-key-field .hidden input').attr('name','question[mapping_answer][0][]')
         jQuery('.option-key-field .hidden' ).removeClass('hidden')
       else
-        shuzi = jQuery('.form-question-mapping .item:last input ').attr('name');
+        shuzi = that.$map.find('.item:last input').attr('name');
         zhengze = new RegExp(/[0-9]+/)
         shuzi = zhengze.exec(shuzi)
         shuzi = Number(shuzi) + 1
         atr = jQuery(this).closest('.form-question-mapping').find(".item:last").clone()
         jQuery(this).closest('.add-items').before(atr);
-        jQuery(this).closest('.form-question-mapping').find(".item:last").removeClass('hidden')
-        jQuery(this).closest('.form-question-mapping').find(".item:last input").attr('name','question[mapping_answer]['+shuzi+'][]')
-        jQuery(this).closest('.form-question-mapping').find(".item:last input").val()
+        that.$map.find(".item:last").removeClass('hidden')
+        that.$map.find(".item:last input").attr('name','question[mapping_answer]['+shuzi+'][]')
+        that.$map.find(".item:last input").val()
 class Fill
   constructor: (@$fill)->
     @bind_events()
@@ -110,18 +110,18 @@
   bind_events: ->
     that = this

-    @$fill.on 'click','.insert',->
-      str = jQuery('.form-question-fill').find("[name='question[content]']").val();
+    @$fill.on 'click','.insert',=>
+      str = @$fill.find("[name='question[content]']").val();
       str = str+" ___ "
       jQuery('.form-question-fill').find("[name='question[content]']").val(str);

     @$fill.on 'click','.delete ',->
       jQuery(this).closest(".answer").remove()

-    @$fill.on 'click',' .append',->
-      blank = jQuery('.form-question-fill .answer:last').clone();
+    @$fill.on 'click',' .append',=>
+      blank = @$fill.find('.answer:last').clone();
       blank.removeClass("hidden")
       blank.find("input").val("")
-      jQuery('.form-question-fill .answer:last').after(blank)
+      @$fill.find('.answer:last').after(blank)


add preventDefault to flash dismissing

add preventDefault to flash dismissing

--- app/assets/javascripts/tenon/features/flash.js.coffee
+++ app/assets/javascripts/tenon/features/flash.js.coffee
@@ -8,5 +8,6 @@
     @$flash.insertAfter($('header'))
     @$flash.show()

-  _dismissFlash: =>
+  _dismissFlash: (e) =>
+    e.preventDefault()
     @$flash.fadeOut()


Change Komputer to Computer

Change Komputer to Computer

Komputer is not used in danish for Computer (maybe it was in the early days of computes.. but today "computer" is word for a computer in danish).

Proof: Dansk Sprognævn / Danish Language Council
http://dsn.dk/?retskriv=Computer&ae=0

--- app/assets/javascripts/uploadcare/locale/da.js.coffee
+++ app/assets/javascripts/uploadcare/locale/da.js.coffee
@@ -40,14 +40,14 @@
       tabs:
         names:
           preview: 'Vis'
-          file: 'Komputer'
+          file: 'Computer'
           gdrive: 'Google Drev'
           url: 'Direkte link'
         file:
           drag: 'Drop en fil her'
-          nodrop: 'Hent filer fra din komputer'
+          nodrop: 'Hent filer fra din computer'
           or: 'eller'
-          button: 'Hent fil fra din komputer'
+          button: 'Hent fil fra din computer'
           also: 'Du kan også hente fra'
         url:
           title: 'Filer fra en Web adresse'


Updated russian translation.

Updated russian translation.

--- app/assets/javascripts/uploadcare/locale/ru.js.coffee
+++ app/assets/javascripts/uploadcare/locale/ru.js.coffee
@@ -2,8 +2,14 @@
   ns.ru =
     ready: 'Выберите файл'
     uploading: 'Идет загрузка'
+    loadingInfo: 'Загрузка информации...'
     errors:
       default: 'Ошибка'
+      baddata: 'Некорректные данные'
+      size: 'Слишком большой файл'
+      upload: 'Ошибка загрузки'
+      user: 'Загрузка прервана'
+      info: 'Ошибка загрузки информации'
       image: 'Разрешены только изображения'
     draghere: 'Перетащите файл сюда'
     file:
@@ -16,25 +22,65 @@
       remove: 'Удалить'
       file: 'Компьютер'
     dialog:
-      title: 'Загрузите что угодно, откуда угодно'
-      poweredby: 'Предоставлено'
-      support:
-        images: 'Изображения'
-        audio: 'Аудио'
-        video: 'Видео'
-        documents: 'Документы'
       tabs:
         file:
-          title: 'Мой компьютер'
-          line1: 'Загрузите любой файл со своего компьютера.'
-          line2: 'Выберите его через окно поиска или перетащите.'
-          button: 'Поиск файлов'
+          drag: 'Перетащите файл сюда'
+          nodrop: 'Загрузка файлов с вашего компьютера'
+          or: 'или'
+          button: 'Выбрать файлы'
+          also: 'Вы также можете загрузить файлы используя'
+          tabNames:
+            facebook: 'Facebook'
+            dropbox: 'Dropbox'
+            gdrive: 'Google Drive'
+            instagram: 'Instagram'
+            url: 'Внешнюю ссылку'
+
         url:
           title: 'Файлы с других сайтов'
           line1: 'Загрузите любой файл из сети.'
           line2: ''
           input: 'Укажите здесь ссылку...'
           button: 'Загрузить'
+
+        preview:
+          unknownName: 'неизвестно'
+          change: 'Отмена'
+          back: 'Назад'
+          done: 'Загрузить'
+          unknown:
+            title: 'Загрузка. Пожалуйста подождите.'
+            done: 'Пропустить предварительный просмотр'
+          regular:
+            title: 'Загрузить этот файл?'
+            line1: 'Вы собираетесь загрузить представленный файл.'
+            line2: 'Пожалуйста, подтвердите.'
+          image:
+            title: 'Загрузить эту картинку?'
+            change: 'Отмена'
+          crop:
+            title: 'Обрезать и загрузить эту картинку'
+          error:
+            default:
+              title: 'Ошибка загрузки'
+              line1: 'Что-то пошло не так во время загрузки.'
+              line2: 'Пожалуйста, попробуйте ещё раз.'
+            image:
+              title: 'Только картинки'
+              line1: 'Можно загрузить только картинку.'
+              line2: 'Попробуйте загрузить другой файл.'
+            size:
+              title: 'Файл слишком большой'
+              line1: 'Размер выбранного файла превышает 100 Мб.'
+              line2: 'Попробуйте загрузить другой файл.'
+      footer:
+        text: 'Для загрузки, хранения и обработки файлов используется'
+        link: 'Uploadcare.com'
+    crop:
+      error:
+        title: 'Ошибка'
+        text: 'Картинка не может быть загружена'
+      done: 'ОК'

 uploadcare.namespace 'uploadcare.locale.pluralize', (ns) ->


mixedice function returns target

mixedice function returns target

--- src/mixedice.coffee
+++ src/mixedice.coffee
@@ -52,7 +52,7 @@
       else
         mixObject target, name

-    null
+    target

   # Define a mixin to be used a lot of times
   mixedice.define = (name, definition) ->


move more into run context

move more into run context

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -331,6 +331,8 @@
       current_options: {}
       default_options: notebook.default_options
       output: output output_cell.$el
+      functions: lead.define_functions {}, graphite_function_names
+      vars: lead: {github, graphite, colors}
       set_code: (code) ->
         cell = add_input_cell notebook, code: code, after: run_context.cell
         focus_cell cell
@@ -350,6 +352,13 @@
         nested_context.cli = bind_cli nested_context
         handle_renderable.call nested_context, o
         # TODO warn if not renderable
+
+      handle_exception: (e, compiled) ->
+        console.error e.stack
+        @cli.error printStackTrace({e}).join('\n')
+        @cli.text 'Compiled JavaScript:'
+        @cli.source 'javascript', compiled
+
       value: (value) -> _lead_cli_value: value
       open_file: -> open_file_picker run_context
       export_notebook: -> export_notebook input_cell
@@ -394,15 +403,6 @@

     run_context.cli = cli = bind_cli run_context

-    functions = {}
-    vars = lead: {github, graphite, colors}
-
-    handle_exception = (e, compiled) ->
-      console.error e.stack
-      cli.error printStackTrace({e}).join('\n')
-      cli.text 'Compiled JavaScript:'
-      cli.source 'javascript', compiled
-
     error = (message) ->
       $pre = $ '<pre class="error"/>'
       $pre.text message
@@ -420,23 +420,22 @@
       for handler in result_handlers
         return if handler.call run_context, object

-    lead.define_functions functions, graphite_function_names
     try
       compiled = CoffeeScript.compile(string, bare: true) + "\n//@ sourceURL=console-coffeescript.js"
     catch e
       if e instanceof SyntaxError
         error "Syntax Error: #{e.message} at #{e.location.first_line + 1}:#{e.location.first_column + 1}"
       else
-        handle_exception e, compiled
+        run_context.handle_exception e, compiled

     if compiled?
       try
-        `with (cli) { with (functions) { with (vars) {`
+        `with (run_context.cli) { with (run_context.functions) { with (run_context.vars) {`
         result = eval compiled
         `}}}`
         display_object result
       catch e
-        handle_exception e, compiled
+        run_context.handle_exception e, compiled

       scroll_to_result $top


Make sure the passed records are clones when refreshing a Collection.

--- src/relation.coffee
+++ src/relation.coffee
@@ -45,7 +45,7 @@
       record[@fkey] = @record.id
       @model.records[record.id] = record

-    @model.trigger('refresh', records)
+    @model.trigger('refresh', @model.cloneArray(records))

   create: (record) ->
     record[@fkey] = @record.id


remove ie8 code path

remove ie8 code path

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -94,11 +94,8 @@
   addStyles: (css) ->
     style = @root.ownerDocument.createElement('style')
     style.type = 'text/css'
-    css = Renderer.objToCss(css) unless _.isString(css)
-    if style.styleSheet?
-      style.styleSheet.cssText = css
-    else
-      style.appendChild(@root.ownerDocument.createTextNode(css))
+    css = Renderer.objToCss(css)
+    style.appendChild(@root.ownerDocument.createTextNode(css))
     # Firefox needs defer
     _.defer( =>
       @root.ownerDocument.querySelector('head').appendChild(style)


Fix bug where directory was reported empty while actually containing a file

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -28,7 +28,8 @@
         # TODO: use in-memory readstreams instead of buffers if possible
         writeStream = fs.createWriteStream destination
         if result instanceof fs.ReadStream
-          util.pump result, writeStream, callback
+          util.pump result, writeStream, (error) ->
+            callback error, true
         else
           writeStream.write result
           writeStream.end()


attach message object into envelope

attach message object into envelope

--- src/response.coffee
+++ src/response.coffee
@@ -10,6 +10,7 @@
     @envelope =
       room: @message.room
       user: @message.user
+      message: @message

   # Public: Posts a message back to the chat source
   #


Deploy did not make package

Deploy did not make package

--- src/steroids.coffee
+++ src/steroids.coffee
@@ -352,11 +352,15 @@
         project = new Project
         project.make
           onSuccess: =>
-            Deploy = require "./steroids/deploy"
-            deploy = new Deploy(otherOptions)
-            deploy.uploadToCloud ()=>
-              # all complete
-              process.exit 0
+            project.package
+              onSuccess: =>
+                Deploy = require "./steroids/deploy"
+                deploy = new Deploy(otherOptions)
+                deploy.uploadToCloud ()=>
+                  # all complete
+                  process.exit 0
+              onFailure: =>
+                console.log "Can not make package, not deploying to cloud."
           onFailure: =>
             console.log "Can not build project locally, not deploying to cloud."


getMapping and runWithMapping API calls

getMapping and runWithMapping API calls

* `svgtiler.getMapping` returns current `Mapping` object,
  when used at top level of a JS/CS mapping file.
* `svgtiler.runWithMapping` runs the specified function as if it were
  called at the top level of the specified mapping.

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -721,6 +721,21 @@
 class Styles extends ArrayWrapper
   @itemClass: Style

+currentMapping = null
+getMapping = ->
+  ## Returns current `Mapping` object,
+  ## when used at top level of a JS/CS mapping file.
+  currentMapping
+runWithMapping = (mapping, fn) ->
+  ## Runs the specified function `fn` as if it were called
+  ## at the top level of the specified mapping.
+  oldMapping = currentMapping
+  currentMapping = mapping
+  try
+    fn()
+  finally
+    currentMapping = oldMapping
+
 class Mapping extends Input
   constructor: (...args) ->
     super ...args
@@ -807,7 +822,8 @@
       #}
       #console.log code
       pirates?.settings = @settings
-      @load require(filename).default ? {}
+      @module = runWithMapping @, -> require filename
+      @load @module.default ? {}
       @walkDeps filename
     else
       ## But if file has been explicitly loaded (e.g. in browser),
@@ -817,7 +833,7 @@
         filename: @filename
       }
       #console.log code
-      exports = {}
+      @module = {}
       ## Mimick NodeJS module's __filename and __dirname variables
       ## [https://nodejs.org/api/modules.html#modules_the_module_scope]
       _filename = path.resolve @filename
@@ -827,9 +843,10 @@
       #@load eval code
       func = new Function \
         'exports', '__filename', '__dirname', 'svgtiler', 'preact', code
-      func exports, _filename, _dirname, svgtiler,
-        (if code.includes 'preact' then require 'preact')
-      @load exports.default
+      runWithMapping @, ->
+        func @module, _filename, _dirname, svgtiler,
+          (if code.includes 'preact' then require 'preact')
+      @load @module.default
   walkDeps: (filename) ->
     deps = new Set
     recurse = (modname) =>
@@ -1645,6 +1662,7 @@
 svgtiler = {
   Tile, unrecognizedTile,
   Mapping, ASCIIMapping, JSMapping, CoffeeMapping,
+  getMapping, runWithMapping,
   Drawing, ASCIIDrawing, DSVDrawing, SSVDrawing, CSVDrawing, TSVDrawing,
   Drawings, XLSXDrawings,
   Style, CSSStyle, StylusStyle,


Added bit of documentation in the code

Added bit of documentation in the code

--- src/tumblrip.coffee
+++ src/tumblrip.coffee
@@ -1,5 +1,6 @@
 # TumblRip -- Download photos from Tumblr
-# By: Olivier ORABONA
+# Made by Olivier ORABONA
+# Licence: MIT

 # NodeJS imports
 fs = require 'fs'
@@ -63,6 +64,7 @@
 ###*
 @name getPostsData -- get all posts data from Tumblr blog
 @param blogname (String) : the blog name (we construct the URL from it)
+@param this (Object) : database read from / written to
 ###
 getPostsData = (blogname) ->
   # Build url and retrieve all data
@@ -81,10 +83,10 @@
     # modified to download other stuff from a Tumblr blog ! :)
     api "#{url}?type=photo&num=50&start=#{start}"
     .then (parsed) ->
-      # If we have a total number of posts and we are in the first loop, we are going to update..
+      # Get total number of posts (when first time, this value is 0).
       {total} = self

-      # Whether we are updating or inserting, set these only once :)
+      # Update these global variables only once
       if init
         self.title = parsed.tumblelog.title

@@ -96,7 +98,9 @@
         else
           self.nbNewPosts = parsed['posts-total'] - total

-        self.total = total = parsed['posts-total']
+        # We do not set local variable 'total' which will remain set to its
+        # initial value. This will be used later for sub array find.
+        self.total = parsed['posts-total']

       parsed.posts.forEach (post) ->
         photoUrl = post['photo-url-1280']
@@ -113,7 +117,7 @@
         else
           found = findInSubArray self.posts, 1, photoUrl

-        # Sometimes we have a 'slug' attribute that might have a better (human meaningful)
+        # Sometimes we have a 'slug' attribute that might have a better (human readable)
         # filename (everything is better than a long hex string right ?)
         if !!post.slug
           slugfile = "#{post.slug}#{path.extname photoUrl}"
@@ -122,11 +126,15 @@
           # If we already have more than one occurence or if that occurence does
           # not link to the same file (the long hex string one), we have a conflict.
           if fileslug.length > 1 or (fileslug.length is 1 and fileslug[0] isnt found)
-            log.info "Already got #{fileslug.length} of that name", slugfile, '. Using', outputFile, 'instead.\n'
+            log.info "Already got #{fileslug.length} file(s) of that name '", slugfile, "'. Using '", outputFile, "' instead.\n"
           else
             outputFile = slugfile

-        array = [outputFile, photoUrl, timestamp]
+        # We build our database record as an array where
+        # outputFile: definitive file with name either slugfile || photoUrl
+        # photoUrl: the remote photo endpoint URL
+        # status: 'D' for 'delete', 'U' for 'update' (default action)
+        array = [outputFile, photoUrl, 'U']

         if found is -1
           log.debug 'Record not found, creating', array, '\n'
@@ -144,7 +152,7 @@
         # consequence of cache db tampering, this check, if enforced (opt in),
         # will throw an ApiError, meaning we can retry.
         if options.check and self.nbNewPosts isnt newPosts.length
-          Q.reject new ApiError "BUG ? #{self.nbNewPosts} new posts but we got #{newPosts.length}"
+          Q.reject new ApiError "BUG ? #{self.nbNewPosts} new posts but we got only #{newPosts.length} records!"
         else
           # Prepend new posts (newest first like the API)
           self.posts = Array::concat.call [], newPosts, self.posts
@@ -206,20 +214,20 @@
         stream.pipe fs.createWriteStream file

 ###*
-@name main -- Everything starts here!
+@name main -- this starts here!
 ###
 main = ->
   # Parse options first
   res = options.parse()

-  # If we have a number, we should stop right away.
+  # If we have a number, we should stop right away (error code).
   return res if typeof res is 'number'

   {blogname, dest} = options

   # Promise to...
   Q.Promise (accept, reject, notify) ->
-    # Create destination if not existing.
+    # Create destination if it does not exist.
     try
       isDir = fs.statSync(dest).isDirectory()

@@ -235,7 +243,7 @@
     cacheFile = "#{options.dest}/.tumblrip"

     # See if we already have a cache (.tumblrip file) in our destination directory.
-    # If so, load it, otherwise we start from an empty 'database'.
+    # If so, load it, otherwise we initialize an empty 'database'.
     try
       if fs.statSync(cacheFile).isFile()
         readFile cacheFile


docs(deploy): test travis 2

docs(deploy): test travis 2

--- docs/scripts/deploy/deploy.coffee
+++ docs/scripts/deploy/deploy.coffee
@@ -22,8 +22,10 @@
 	switch deployer
 		when 'ci'
 			runTasks = async ->
-				res1 = await testTravis docsRoot
-				res  = [res1]
+				res1 = await decryptKey docsRoot
+				res2 = await addDeployKey docsRoot
+				resX = await testTravis docsRoot
+				res  = [res1, res2, resX]
 				res.filter(Boolean).join '\n'

 				# res1 = await decryptKey docsRoot


Moved change was on wrong line (#6025)

Moved change was on wrong line (#6025)


adjust the namespace of unbinding resize event for the window object

--- src/coffee/breadcrumbsEatr.coffee
+++ src/coffee/breadcrumbsEatr.coffee
@@ -453,7 +453,7 @@
             o.documentBody.off ".#{ pluginName }"

             # remove any events attached to window object
-            browserWindow.off ".#{ pluginName }"
+            browserWindow.off ".#{ pluginName }_#{ o.pluginKey }"

             # Remove Plugin Data
             o.el.removeData pluginName


Escuta pelo evento 'vtex.cart.productRemoved' no minicart

--- src/coffee/portal-minicart.coffee
+++ src/coffee/portal-minicart.coffee
@@ -54,6 +54,7 @@
 		$(window).on "cartUpdated", @updateCart
 		$(window).on 'productAddedToCart', @updateCart
 		$(window).on 'vtex.cart.productAdded', @updateCart
+		$(window).on 'vtex.cart.productRemoved', @updateCart

 	updateCart: =>
 		@context.addClass 'amount-items-in-cart-loading'


Fix too many digits on total time.

Fix too many digits on total time.

--- src/coffee/services/ticket.coffee
+++ src/coffee/services/ticket.coffee
@@ -158,6 +158,7 @@
   ###
   _sanitize = (params) ->
     if not params.assigned_to then params.assigned_to = NOT_CONFIGED
+    params.total = Math.floor(params.total * 100) / 100

   return {


Repaired CLI tests

Repaired CLI tests

--- test/integration/cli-test.coffee
+++ test/integration/cli-test.coffee
@@ -1,22 +1,22 @@
 {assert} = require('chai')
 {exec} = require('child_process')
 express = require 'express'
-request = require 'request'
+

 PORT = '3333'

 describe.only "Command line interface", () ->
-  stderr = ''
-  stdout = ''
-  exitStatus = ''
-  requests = []
+

   describe "Arguments with existing bleurpint and responding server", () ->
     describe "when executing the command and the server is responding as specified in the blueprint", () ->
+      stderr = ''
+      stdout = ''
+      exitStatus = null
+      requests = []

       before (done) ->
-
         cmd = "./bin/dredd ./test/fixtures/single_get.md http://localhost:#{PORT}"

         app = express()
@@ -36,11 +36,12 @@
             stderr = err

             if error
-              exitStatus = error.status
+              exitStatus = error.code

           eventName = if process.version.split('.')[1] is '6' then 'exit' else 'close'

-          cli.on eventName, (code) ->
+          cli.on eventName, (code) ->
+            exitStatus = code if exitStatus == null and code != undefined
             server.close()

         server.on 'close', done
@@ -49,8 +50,12 @@
         assert.equal exitStatus, 0

     describe "when executing the command and the server is sending different response", () ->
+      stderr = ''
+      stdout = ''
+      exitStatus = null
+      requests = []
+
       before (done) ->
-
         cmd = "./bin/dredd ./test/fixtures/single_get.md http://localhost:#{PORT}"

         app = express()
@@ -70,11 +75,12 @@
             stderr = err

             if error
-              exitStatus = error.status
+              exitStatus = error.code

           eventName = if process.version.split('.')[1] is '6' then 'exit' else 'close'

-          cli.on eventName, (code) ->
+          cli.on eventName, (code) ->
+            exitStatus = code if exitStatus == null and code != undefined
             server.close()

         server.on 'close', done


slightly better new_view integration test

--- test/integration/new_view.coffee
+++ test/integration/new_view.coffee
@@ -11,18 +11,15 @@

   context 'when I click on an Apricot dataset', ->
     before (done) ->
-      # wait for tiles to fade in
-      setTimeout ->
-        browser.elementByPartialLinkText 'Apricot', (err, link) ->
+        wd40.elementByPartialLinkText 'Apricot', (err, link) ->
           link.click done
-      , 500

     it 'takes me to the Apricot dataset page', (done) ->
       wd40.trueURL (err, result) ->
         result.should.match /\/dataset\/(\w+)/
         done()

-    context 'when I click on "More tools" in the "Tools" dropdown', ->
+    context 'when I click on "More tools" in the toolbar', ->
       before (done) ->
         wd40.click '#toolbar .new-view', ->
           browser.waitForElementByCss '#chooser .tool', 4000, done


ssh view test removed

ssh view test removed

--- test/integration/ssh_view.coffee
+++ test/integration/ssh_view.coffee
@@ -1,71 +0,0 @@
-should = require 'should'
-{wd40, browser, login_url, home_url, prepIntegration} = require './helper'
-
-describe 'View SSH Details', ->
-  prepIntegration()
-
-  before (done) ->
-    wd40.fill '#username', 'ehg', ->
-      wd40.fill '#password', 'testing', -> wd40.click '#login', done
-
-  context 'when I click on an Prune dataset', ->
-    before (done) ->
-      # wait for tiles to fade in
-      setTimeout ->
-        browser.elementByPartialLinkText 'Prune', (err, link) ->
-          link.click done
-      , 500
-
-    context 'when I click the "SSH in" menu link', ->
-      before (done) ->
-        browser.elementByCss '.view.tile .dropdown-toggle', (err, settingsLink) =>
-          settingsLink.click =>
-            wd40.click '.view.tile .git-ssh', done
-
-      it 'a modal window appears', (done) =>
-        wd40.getText '.modal', (err, text) =>
-          @modalTextContent = text.toLowerCase()
-          done()
-
-      it 'the modal window asks for my SSH key', =>
-        @modalTextContent.should.include 'add your ssh key:'
-
-      it 'the modal tells me the command I should run', =>
-        @modalTextContent.should.include 'ssh-keygen'
-
-      context 'when I paste my ssh key into the box and press submit', ->
-        before (done) ->
-          wd40.fill '#ssh-key', 'ssh-rsa AAAAB3Nza...ezneI9HWBOzHnh foo@bar.local', ->
-            wd40.click '#add-ssh-key', done
-
-        before (done) =>
-          wd40.getText '.modal', (err, text) =>
-            @modalTextContent = text.toLowerCase()
-            done()
-
-        it 'the modal title says "ssh into your Graph of Prunes view"', =>
-          @modalTextContent.should.include 'graph of prunes view'
-
-        it 'the modal window no longer asks for my SSH key', =>
-          @modalTextContent.should.not.include 'add your ssh key:'
-
-        it 'the modal window tells me how to SSH in', =>
-          @modalTextContent.should.include 'ssh 4008115731@box.scraperwiki.com'
-
-        context 'when I close the modal, and reopen it', ->
-          before (done) =>
-            wd40.click '#done', =>
-              setTimeout =>
-                browser.elementByCss '.view.tile .dropdown-toggle', (err, settingsLink) =>
-                  settingsLink.click =>
-                    wd40.click '.view.tile .git-ssh', =>
-                      wd40.getText '.modal', (err, text) =>
-                        @modalTextContent = text.toLowerCase()
-                        done()
-              , 400
-
-          it 'the modal window does not ask for my SSH key', =>
-            @modalTextContent.should.not.include 'add your ssh key:'
-
-          it 'the modal window tells me how to SSH in', =>
-            @modalTextContent.should.include 'ssh 4008115731@box.scraperwiki.com'


refinements to Luca.Application component definition, adding Luca.Application.registerInstance and other explicitly defined classMethods

--- src/components/application.coffee
+++ src/components/application.coffee
@@ -90,8 +90,7 @@
     appName         = @name
     alreadyRunning  = Luca.getApplication?()

-    Luca.Application.instances ||= {}
-    Luca.Application.instances[ appName ] = app
+    Luca.Application.registerInstance(@)

     Luca.containers.Viewport::initialize.apply @, arguments

@@ -299,7 +298,7 @@
     # you can control which by setting the @startHistoryOn property
     if @router and @autoStartHistory
       @autoStartHistory = "before:render" if @autoStartHistory is true
-      @defer( Luca.util.startHistory, false).until(@, @autoStartHistory)
+      @defer( Luca.Application.startHistory, false).until(@, @autoStartHistory)

   setupKeyHandler: ()->
     return unless @keyEvents
@@ -314,6 +313,45 @@
     for keyEvent in (@keypressEvents || ["keydown"])
       $( document ).on( keyEvent, handler )

-Luca.util.startHistory = ()->
-  Backbone.history.start()
-
+application.classInterface
+  instances: {}
+  registerInstance: (app)->
+    Luca.Application.instances[ app.name ] = app
+  routeTo: (pages...)->
+    lastPage = _( pages ).last()
+    callback = undefined
+    specifiedAction = undefined
+
+    routeHelper = (args...)->
+      path = @app || Luca.getApplication()
+      index = 0
+
+      for page in pages when _.isString(page)
+        nextItem = pages[++index]
+        action = undefined
+
+        if _.isFunction(nextItem)
+          action = nextItem
+        else if _.isObject(nextItem) and nextItem.action?
+          action = nextItem.action
+        else if page is lastPage and routeHandler = Luca(lastPage)?.routeHandler
+          action = Luca.util.read(routeHandler)
+
+        if _.isString( action )
+          callback = ()->
+            @[ action ].apply(@, args)
+
+        if _.isFunction( action )
+          callback = nextItem
+
+        path = path.navigate_to(page, callback)
+
+      routeHelper.action = (action)->
+        pages.push(action: action)
+
+      return routeHelper
+
+  startHistory: ()->
+    Backbone.history.start()
+
+application.register()


adding in a way to set the initial free response field

--- src/components/exercise/mode.cjsx
+++ src/components/exercise/mode.cjsx
@@ -55,15 +55,17 @@
     @props.onAnswerChanged?(answer)

   getFreeResponse: ->
+    freeResponseValue = @state.freeResponse
     {mode, free_response, disabled} = @props
-    {freeResponse} = @state
+    {freeResponseValue} = @props unless freeResponseValue
+

     if mode is 'free-response'
       <textarea
         disabled={disabled}
         ref='freeResponse'
         placeholder='Enter your response'
-        value={freeResponse}
+        value={freeResponseValue}
         onChange={@onFreeResponseChange}
       />
     else