chore(web): more stable browserify

chore(web): more stable browserify

--- apps/actor-web/gulpfile.coffee
+++ apps/actor-web/gulpfile.coffee
@@ -1,4 +1,5 @@
 argv = require('yargs').argv
+assign = require 'lodash.assign'
 autoprefixer = require 'gulp-autoprefixer'
 browserify = require 'browserify'
 buffer = require 'vinyl-buffer'
@@ -8,7 +9,6 @@
 gulp = require 'gulp'
 gutil = require 'gulp-util'
 gulpif = require 'gulp-if'
-assign = require 'lodash.assign'
 minifycss = require 'gulp-minify-css'
 sass = require 'gulp-sass'
 source = require 'vinyl-source-stream'
@@ -40,14 +40,22 @@

 jsBundleFile = 'app-js.js'

-gulp.task 'js', ->
-  bundler.bundle()
-    .pipe(source(jsBundleFile))
-    .pipe(buffer())
-    .pipe(gulpif(!argv.production, sourcemaps.init({loadMaps: true})))
-      .pipe(gulpif(argv.production, uglify()))
-    .pipe(gulpif(!argv.production, sourcemaps.write('./')))
-    .pipe(gulp.dest('./dist/assets/js'))
+gulp.task 'browserify', ->
+  watcher = watchify(bundler)
+
+  watcher.on 'update', ->
+    updateStart = Date.now()
+    console.log('Browserify started')
+    watcher.bundle()
+      .pipe(source(jsBundleFile))
+        ## uglify
+      .pipe(gulp.dest('./dist/assets/js'))
+      .pipe(connect.reload())
+    console.log('Browserify ended', (Date.now() - updateStart) + 'ms')
+  .bundle()
+  .pipe(source(jsBundleFile))
+  .pipe(gulp.dest('./dist/assets/js'))
+  .pipe(connect.reload())

 gulp.task 'sass', ->
   gulp.src ['./app/**/*.scss']
@@ -68,28 +76,10 @@
     .pipe gulp.dest './dist/'
     .pipe connect.reload()

-gulp.task 'watchify', ->
-  watcher = watchify(bundler)
-  watcher
-    .on 'error', gutil.log.bind(gutil, 'Browserify Error')
-    .on 'update', ->
-      watcher.bundle()
-        .pipe(source(jsBundleFile))
-        .pipe(buffer())
-        .pipe(sourcemaps.init({loadMaps: true}))
-        .pipe(sourcemaps.write('./'))
-        .pipe(gulp.dest('./dist/assets/js'))
-
-      gutil.log("Updated JavaScript sources")
-    .bundle()
-    .pipe(source(jsBundleFile))
-    .pipe(gulp.dest('./dist/assets/js'))
-    .pipe connect.reload()

 gulp.task 'watch', ['server'], ->
   gulp.watch ['./app/**/*.coffee'], ['coffee']
   gulp.watch ['./app/**/*.scss'], ['sass']
-  gulp.watch ['./app/**/*.js', './app/**/*.jsx'], ['watchify']
   gulp.watch ['./index.html', './app/**/*.html'], ['html']

 gulp.task 'assets', ->
@@ -117,9 +107,9 @@
     root: ['./dist/', './bower_components/', './node_modules/']
     livereload: true

-gulp.task 'build', ['assets', 'coffee', 'js', 'sass', 'html', 'usemin']
+gulp.task 'build', ['assets', 'coffee', 'browserify', 'sass', 'html', 'usemin']

-gulp.task 'build:dev', ['assets', 'coffee', 'js', 'sass', 'html']
+gulp.task 'build:dev', ['assets', 'coffee', 'browserify', 'sass', 'html']

 gulp.task 'dev', ['build:dev', 'server', 'watch']


Only (un)subscribe from the talk component when (un)mounting the zooniverse talk section

--- app/talk/init.cjsx
+++ app/talk/init.cjsx
@@ -30,10 +30,10 @@
     loading: true

   componentWillMount: ->
-    sugarClient.subscribeTo @props.section
+    sugarClient.subscribeTo('zooniverse') if @props.section is 'zooniverse'

   componentWillUnmount: ->
-    sugarClient.unsubscribeFrom @props.section
+    sugarClient.unsubscribeFrom('zooniverse') if @props.section is 'zooniverse'

   setBoards: ->
     talkClient.type('boards').get(section: @props.section)


Format material exchange message

Format material exchange message

--- scripts/bungie-data-helper.coffee
+++ scripts/bungie-data-helper.coffee
@@ -173,10 +173,10 @@
   'parseMaterialExchangeItems': (materialExchangeItems) ->
     message = ''
     for exchangeItem in materialExchangeItems
-      message += "#{exchangeItem.faction}: #{exchangeItem.material} (#{exchangeItem.cost})\n"
+      message += "#{exchangeItem.faction}: *#{exchangeItem.material}* (_#{exchangeItem.cost}_)\n"

     attachment =
-      author_name: 'Material Exchanges'
+      author_name: 'Faction Vendor Material Exchange'
       fallback: message
       text: message
       mrkdwn_in: ['text']


only load demos after document is ready

only load demos after document is ready

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -52,24 +52,6 @@
   error: (jqXHR, textStatus, errorThrown) ->
     console.error errorThrown
 }
-
-demo_files = ['special/DiffPrint.class', 'special/Chatterbot.java',
-  'special/Chatterbot.class', 'special/Lzw.java', 'special/Lzw.class',
-  'special/RegexTestHarness.java', 'special/RegexTestHarness.class',
-  'special/FileRead.java', 'special/Fib.java', 'special/foo', 'special/bar']
-for demo in demo_files
-  $.ajax "test/#{demo}", {
-    type: 'GET'
-    dataType: 'text'
-    async: false
-    beforeSend: (jqXHR) -> jqXHR.overrideMimeType('text/plain; charset=x-user-defined')
-    success: (data) ->
-      fname = _.last(demo.split '/')
-      (new DoppioFile fname).write(data).save()
-    error: ->
-      fname = _.last(demo.split '/')
-      controller.message "Could not load '#{fname}'.\n", 'error', true
-  }

 if RELEASE?
   $.ajax "third_party/classes/sun/tools/javac/Main.class", {
@@ -186,6 +168,24 @@
     animateScroll: true
     promptHistory: true
     welcomeMessage: "Enter 'help' for a list of commands. Ctrl-D is EOF."
+
+  demo_files = ['special/DiffPrint.class', 'special/Chatterbot.java',
+  'special/Chatterbot.class', 'special/Lzw.java', 'special/Lzw.class',
+  'special/RegexTestHarness.java', 'special/RegexTestHarness.class',
+  'special/FileRead.java', 'special/Fib.java', 'special/foo', 'special/bar']
+  for demo in demo_files
+    $.ajax "test/#{demo}", {
+      type: 'GET'
+      dataType: 'text'
+      async: false
+      beforeSend: (jqXHR) -> jqXHR.overrideMimeType('text/plain; charset=x-user-defined')
+      success: (data) ->
+        fname = _.last(demo.split '/')
+        (new DoppioFile fname).write(data).save()
+      error: ->
+        fname = _.last(demo.split '/')
+        controller.message "Could not load '#{fname}'.\n", 'error', true
+    }

   user_input = (n_bytes, resume) ->
     oldPrompt = controller.promptLabel


REMOVE: click handler

REMOVE: click handler

--- app/assets/javascripts/media_lib/jquery.nmdUploader.js.coffee
+++ app/assets/javascripts/media_lib/jquery.nmdUploader.js.coffee
@@ -66,14 +66,13 @@
               <iframe width='<%= width %>' height='<%= height %>' src='<%= src %>' frameborder='0'></iframe>
             '''

-            $uploader.find('.js-uploader-open').on 'click', ->
-              $uploaderIframes.each ->
-                $iframe = $ @
-                iframeData = $iframe.data()
-                $iframe.html iframeTemplate
-                  src: getUrl()
-                  width: iframeData.width or '100%'
-                  height: iframeData.height or 500
+            $uploaderIframes.each ->
+              $iframe = $ @
+              iframeData = $iframe.data()
+              $iframe.html iframeTemplate
+                src: getUrl()
+                width: iframeData.width or '100%'
+                height: iframeData.height or 500
           else
             popupWindow = open getUrl(), 'NMD Media Lib', 'scrollbars=1, width=800, height=500'
             popupWindow.focus()


Use atom.workspace.open() for specs to make them less flaky

--- spec/about-spec.coffee
+++ spec/about-spec.coffee
@@ -19,7 +19,7 @@
       jasmine.attachToDOM(workspaceElement)

       expect(workspaceElement.querySelector('.about')).not.toExist()
-      atom.commands.dispatch workspaceElement, 'application:about'
+      atom.workspace.open('atom://about')

       waitsFor ->
         atom.workspace.getActivePaneItem()
@@ -30,7 +30,7 @@

   describe "when the version number is clicked", ->
     it "copies the version number to the clipboard", ->
-      atom.commands.dispatch workspaceElement, 'application:about'
+      atom.workspace.open('atom://about')

       waitsFor ->
         atom.workspace.getActivePaneItem()


adding color parameter to heatmap function

--- pivot.coffee
+++ pivot.coffee
@@ -872,7 +872,7 @@
     Heatmap post-processing
     ###

-    $.fn.heatmap = (scope = "heatmap") ->
+    $.fn.heatmap = (scope = "heatmap", color = "red") ->
         numRows = @data "numrows"
         numCols = @data "numcols"

@@ -901,14 +901,14 @@

         switch scope
             when "heatmap"
-                heatmapper ".pvtVal", "red"
+                heatmapper ".pvtVal", color
             when "rowheatmap"
-                heatmapper ".pvtVal.row#{i}", "red" for i in [0...numRows]
+                heatmapper ".pvtVal.row#{i}", color for i in [0...numRows]
             when "colheatmap"
-                heatmapper ".pvtVal.col#{j}", "red" for j in [0...numCols]
-
-        heatmapper ".pvtTotal.rowTotal", "red"
-        heatmapper ".pvtTotal.colTotal", "red"
+                heatmapper ".pvtVal.col#{j}", color for j in [0...numCols]
+
+        heatmapper ".pvtTotal.rowTotal", color
+        heatmapper ".pvtTotal.colTotal", color

         return this


Adding tests for comparison ops.

Adding tests for comparison ops.

--- spec/spec-index.coffee
+++ spec/spec-index.coffee
@@ -9,27 +9,30 @@

   describe 'logical ops', ->

+    it 'should match nothing', ->
+      y null, null
+
     describe '$and', ->

       it 'should match two positives', ->
-        y { foo: bar: '123' }, { $and: [ { 'foo': $exists: true }, { 'foo.bar': { $eq: '123' } } ] }
+        y { foo: bar: '123' }, { $and: [ { foo: $exists: true }, { 'foo.bar': { $eq: '123' } } ] }

       it 'should not match one positive and one negative', ->
-        n { foo: bar: '123' }, { $and: [ { 'foo': $exists: true }, { 'foo.bar': { $eq: '1234' } } ] }
+        n { foo: bar: '123' }, { $and: [ { foo: $exists: true }, { 'foo.bar': { $eq: '1234' } } ] }

       it 'should match nested positive', ->
-        y { foo: 1, bar: 2}, { $and: [ $and: [ { 'foo': { $eq: 1 } } ] ] }
+        y { foo: 1, bar: 2}, { $and: [ $and: [ { foo: { $eq: 1 } } ] ] }

     describe '$or', ->

       it 'should match one positive', ->
-        y { foo: 1 }, { $or: [ { 'foo': $gt: 0 } ] }
+        y { foo: 1 }, { $or: [ { foo: $gt: 0 } ] }

       it 'should match one positive and one negative', ->
-        y { foo: 1 }, { $or: [ { 'foo': $gt: 0 }, { 'foo': $lt: -1 } ] }
+        y { foo: 1 }, { $or: [ { foo: $gt: 0 }, { foo: $lt: -1 } ] }

       it 'should not match two negatives', ->
-        n { foo: 1 }, { $or: [ { 'foo': $gt: 2 }, { 'foo': $lt: 0 } ] }
+        n { foo: 1 }, { $or: [ { foo: $gt: 2 }, { foo: $lt: 0 } ] }

       it 'should not match single negatives', ->
         n { foo: 1 }, { $or: [ { 'foo2': $eq: 1 } ] }
@@ -37,39 +40,95 @@
     describe '$nor', ->

       it 'should not match single positive', ->
-        n { foo: 1 }, { $nor: [ { 'foo': $eq: 1 } ] }
+        n { foo: 1 }, { $nor: [ { foo: $eq: 1 } ] }

       it 'should match single negative', ->
-        y { foo: 1 }, { $nor: [ { 'foo': $eq: 2 } ] }
+        y { foo: 1 }, { $nor: [ { foo: $eq: 2 } ] }

       it 'should match two negatives', ->
-        y { foo: 1 }, { $nor: [ { 'foo': $eq: 2 }, { 'bar': $eq: 1 } ] }
+        y { foo: 1 }, { $nor: [ { foo: $eq: 2 }, { 'bar': $eq: 1 } ] }

       it 'should not match one positive and one negative', ->
-        n { foo: 1 }, { $nor: [ { 'foo': $eq: 2 }, { 'foo': $eq: 1 } ] }
+        n { foo: 1 }, { $nor: [ { foo: $eq: 2 }, { foo: $eq: 1 } ] }

     describe '$not', ->

       it 'should not match negated positive equality', ->
-        n { foo: 1 }, { 'foo': $not: $eq: 1 }
+        n { foo: 1 }, { foo: $not: $eq: 1 }

       it 'should match negated negative equality', ->
-        y { foo: 1 }, { 'foo': $not: $eq: 2 }
+        y { foo: 1 }, { foo: $not: $eq: 2 }

-  it 'should work with nothing', ->
-    y null, null
+    describe 'comparision ops', ->

-  it 'should match eq', ->
-    y { foo: 1 }, { 'foo': { $eq: 1 } }
+      it 'should match equality', ->
+        y { foo: 1 }, { foo: $eq: 1 }
+
+      it 'should not match equality', ->
+        n { foo: 1 }, { foo: $eq: 2 }
+
+      it 'should not match non existing', ->
+        n { foo: 1 }, { bar: $eq: 2 }
+
+      it 'should match non equality', ->
+        y { foo: 1 }, { foo: $ne: 2 }
+
+      it 'should not match non equality', ->
+        n { foo: 1 }, { foo: $ne: 1 }
+
+      it 'should match lower than', ->
+        y { foo: 1 }, { foo: $lt: 2 }
+
+      it 'should not match lower than', ->
+        n { foo: 1 }, { foo: $lt: 1 }
+
+      it 'should match lower than or equal', ->
+        y { foo: 1 }, { foo: $lte: 1 }
+
+      it 'should match lower than or equal 2', ->
+        y { foo: 1 }, { foo: $lte: 2 }
+
+      it 'should not match lower than or qual', ->
+        n { foo: 1 }, { foo: $lte: 0 }
+
+      it 'should match greater than', ->
+        y { foo: 1 }, { foo: $gt: 0 }
+
+      it 'should not match greater than', ->
+        n { foo: 1 }, { foo: $gt: 1 }
+
+      it 'should match greater than or equal', ->
+        y { foo: 1 }, { foo: $gte: 1 }
+
+      it 'should match greater than or equal 2', ->
+        y { foo: 1 }, { foo: $gte: 0 }
+
+      it 'should not match greater than or qual', ->
+        n { foo: 1 }, { foo: $gte: 2 }
+
+      it 'should match in', ->
+        y { foo: 1 }, { foo: $in: [ 1 ] }
+
+      it 'should match in with more options', ->
+        y { foo: 1 }, { foo: $in: [ 2, 3, 1, 4 ] }
+
+      it 'should not match in', ->
+        n { foo: 1 }, { foo: $in: [ 2, 3, 4 ] }
+
+      it 'should match not in', ->
+        y { foo: 1 }, { foo: $nin: [ 2, 3, 4 ] }
+
+      it 'should not match not in', ->
+        n { foo: 1 }, { foo: $nin: [ 1, 2 ] }

   it 'should not match eq', ->
-    n { foo: 1 }, { 'foo': { $eq: 2 } }
+    n { foo: 1 }, { foo: { $eq: 2 } }

   it 'should not match non existing eq', ->
     n { foo: 1 }, { 'foo2': { $eq: 1 } }

   it 'should work with simple $and', ->
-    y { foo: bar: 1 }, { $and: [ { 'foo.bar': { $eq: 1 } }, { 'foo': $exists: true } ] }
+    y { foo: bar: 1 }, { $and: [ { 'foo.bar': { $eq: 1 } }, { foo: $exists: true } ] }

   it 'should work with simple $exists false', ->
     n { foo: bar: 1 }, { 'foo.bar': $exists: false }


Updated deprecation msg to reactionary-atom-fork

--- exports/atom.coffee
+++ exports/atom.coffee
@@ -118,7 +118,7 @@
     require 'react-atom-fork'

   Object.defineProperty module.exports, 'Reactionary', get: ->
-    deprecate "Please require `reactionary` instead: `Reactionary = require 'reactionary'`. Add `\"reactionary\": \"^0.9\"` to your package dependencies."
+    deprecate "Please require `reactionary-atom-fork` instead: `Reactionary = require 'reactionary-atom-fork'`. Add `\"reactionary-atom-fork\": \"^0.9\"` to your package dependencies."
     require 'reactionary-atom-fork'

 Object.defineProperty module.exports, 'Git', get: ->


Handle nonexistent browser files properly.

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -130,8 +130,9 @@
     compile_source args[0], report
   java: (args, report) ->
     return "Usage: java class [args...]" unless args[0]?
-    class_data = process_bytecode load_file "#{args[0]}.class"
-    return "Could not find class '#{args[0]}'" unless class_data?
+    raw_data = load_file "#{args[0]}.class"
+    return "Could not find class '#{args[0]}'." unless raw_data?
+    class_data = process_bytecode raw_data
     stdout = (str) -> report str, true # no reprompting
     rs ?= new runtime.RuntimeState(stdout, user_input, read_classfile)
     jvm.run_class(rs, class_data, args[1..], ->
@@ -140,8 +141,9 @@
     )
   javap: (args) ->
     return "Usage: javap class" unless args[0]?
-    class_data = process_bytecode load_file "#{args[0]}.class"
-    return "Could not find class '#{args[0]}'" unless class_data?
+    raw_data = load_file "#{args[0]}.class"
+    return "Could not find class '#{args[0]}'." unless raw_data?
+    class_data = process_bytecode raw_data
     disassembler.disassemble class_data
   clear_heap: (args) ->
     rs = null


Authorise delete for shipments

Authorise delete for shipments

--- app/assets/javascripts/trade/controllers/search_results_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/search_results_controller.js.coffee
@@ -61,6 +61,21 @@
     @set('page', page)
     @transitionToRoute('search.results', {queryParams: {page: page}})

+  userCanEdit: (callback) ->
+    $.ajax({
+      type: 'GET'
+      url: "/trade/user_can_edit"
+      data: {}
+      dataType: 'json'
+      success: (response) =>
+        if response.can_edit
+          callback()
+        else
+          alert('It is not possible to perform this action. If you require further assistance please contact the Species team on species@unep-wcmc.org')
+      error: (error) ->
+        console.log(error)
+    })
+
   actions:
     newShipment: () ->
       shipment = Trade.Shipment.createRecord()
@@ -71,16 +86,6 @@

     # saves the new shipment (bound to currentShipment) to the db
     saveShipment: (shipment, ignoreWarnings) ->
-      $.ajax({
-        type: 'GET'
-        url: "/trade/user_can_edit"
-        data: {}
-        dataType: 'json'
-        success: (response) ->
-          console.log(response)
-        error: (error) ->
-          console.log(error)
-      })
       shipment.set('ignoreWarnings', ignoreWarnings)
       # Before trying to save a shipment
       # we need to reset the model to a valid state.
@@ -116,40 +121,44 @@

     # discards the new shipment (bound to currentShipment)
     deleteShipment: (shipment) ->
-      if confirm("This will delete a shipment. Proceed?")
-        if (!shipment.get('isSaving'))
-          shipment.deleteRecord()
-          shipment.get('transaction').commit()
-          shipment.one('didDelete', this, ->
-            @set('currentShipment', null)
-            @flashSuccess(message: 'Successfully deleted shipment.')
-            @send("dataChanged")
-          )
-
-    deleteBatch: ->
-      @transitionToRoute('search.results', {queryParams: @get('controllers.search.searchParams')})
-      .then(
-        # resolve
-        (() =>
-          if confirm("This will delete " + @get('total') + " shipments. Are you sure?")
-            $.ajax(
-              url: '/trade/shipments/destroy_batch'
-              type: 'POST'
-              data:
-                filters: @get('controllers.search.searchParams')
-            )
-            .done( (data) =>
-              @flashSuccess(message: 'Successfully deleted ' + data.rows + ' shipments.')
+      @userCanEdit( =>
+        if confirm("This will delete a shipment. Proceed?")
+          if (!shipment.get('isSaving'))
+            shipment.deleteRecord()
+            shipment.get('transaction').commit()
+            shipment.one('didDelete', this, ->
+              @set('currentShipment', null)
+              @flashSuccess(message: 'Successfully deleted shipment.')
               @send("dataChanged")
             )
-            .fail( (xhr) =>
-              @flashError(message: 'Error occurred when deleting shipments.')
-              console.log "bad luck: ", xhr.responseText
-            )
-            .always( () =>
-              @set('currentShipment', null)
-              $('.batch-form-modal').modal('hide')
-            )
+      )
+
+    deleteBatch: ->
+      @userCanEdit( =>
+        @transitionToRoute('search.results', {queryParams: @get('controllers.search.searchParams')})
+        .then(
+          # resolve
+          (() =>
+            if confirm("This will delete " + @get('total') + " shipments. Are you sure?")
+              $.ajax(
+                url: '/trade/shipments/destroy_batch'
+                type: 'POST'
+                data:
+                  filters: @get('controllers.search.searchParams')
+              )
+              .done( (data) =>
+                @flashSuccess(message: 'Successfully deleted ' + data.rows + ' shipments.')
+                @send("dataChanged")
+              )
+              .fail( (xhr) =>
+                @flashError(message: 'Error occurred when deleting shipments.')
+                console.log "bad luck: ", xhr.responseText
+              )
+              .always( () =>
+                @set('currentShipment', null)
+                $('.batch-form-modal').modal('hide')
+              )
+          )
         )
       )


remove afterFile argument from __fileAdded

--- app/assets/javascripts/uploadcare/widget/tabs/preview-tab-multiple.js.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/preview-tab-multiple.js.coffee
@@ -35,9 +35,7 @@
         )
       @__updateContainerView()

-      fileAdded = (f) =>
-        @__fileAdded(f)
-      @dialogApi.fileColl.onAdd.add(fileAdded, @__updateContainerView)
+      @dialogApi.fileColl.onAdd.add(@__fileAdded, @__updateContainerView)
       @dialogApi.fileColl.onRemove.add(@__fileRemoved, @__updateContainerView)
       @dialogApi.fileColl.onReplace.add(@__fileReplaced, @__updateContainerView)

@@ -139,27 +137,25 @@
       @__find('file-error', fileEl)
         .text(t("errors.#{error}"))

-    __fileAdded: (file, afterFile) =>
+    __fileAdded: (file) =>
       fileEl = @__createFileEl(file)
-      if afterFile?
-        afterFileEl = @__fileToEl(afterFile)
-        fileEl.insertAfter(afterFileEl)
-      else
-        fileEl.appendTo(@fileListEl)
-      $(file).data('dpm-el', fileEl)
+      fileEl.appendTo(@fileListEl)

     __fileRemoved: (file) =>
       @__fileToEl(file).remove()
       $(file).removeData()

     __fileReplaced: (oldFile, newFile) =>
-      @__fileAdded(newFile, oldFile)
+      fileEl = @__createFileEl(newFile)
+      fileEl.insertAfter(@__fileToEl(oldFile))
       @__fileRemoved(oldFile)

     __fileToEl: (file) ->
       $(file).data('dpm-el')

     __createFileEl: (file) ->
-      @__fileTpl.clone()
+      fileEl = @__fileTpl.clone()
         .on 'click', '.' + CLASS_PREFIX + 'file-remove', =>
           @dialogApi.fileColl.remove(file)
+      $(file).data('dpm-el', fileEl)
+      fileEl


payment: code style

payment: code style

--- client/app/Applications/Dashboard.kdapplication/views/producteditform.coffee
+++ client/app/Applications/Dashboard.kdapplication/views/producteditform.coffee
@@ -127,17 +127,18 @@
     return selectOptions

   subscriptionTypeChanged: ->
-    subscriptionType = @inputs.subscriptionType.getValue()
+    { subscriptionType, perMonth } = @inputs
+    newType = subscriptionType.getValue()
     if subscriptionType is 'single'
-      @inputs.perMonth.hide()
+      perMonth.hide()
     else
-      @inputs.perMonth.show()
-      @inputs.perMonth.updatePartial "/ #{subscriptionType}"
+      perMonth.show()
+      perMonth.updatePartial "/ #{ newType }"

-  feeChanged: (num) ->
-    num = parseFloat @getValue()
-
-    @setValue if isNaN num then '' else num.toFixed(2)
+  feeChanged: ->
+    { feeAmount } = @inputs
+    num = parseFloat feeAmount.getValue()
+    feeAmount.setValue if isNaN num then '' else num.toFixed 2

   priceVolatilityChanged: ->
     enabled = @inputs.priceIsVolatile.getValue()


<controller> Measure - Better error message.

--- server/controllers/measure.coffee
+++ server/controllers/measure.coffee
@@ -9,7 +9,9 @@

 module.exports.fetch = (req, res, next, id) ->
 	Measure.find id, (err, measure) ->
-		if err or not measure
+		if err
+			res.send error: err, 500
+		else if not measure
 			res.send error: "Measure not found", 404
 		else
 			req.measure = measure
@@ -25,16 +27,17 @@
 	res.send req.measure

 module.exports.update = (req, res) ->
-	data = req.body
+	res.send error: "Measures can't be updated", 401
+	###data = req.body
 	req.measure.update data, (err, measure) ->
-		if err?
-			res.send error: "Server error while saving measure", 500
+		if err
+			res.send error: err, 500
 		else
-			res.send measure, 200
+			res.send measure, 200###

 module.exports.delete = (req, res) ->
 	req.measure.destroy (err) ->
-		if err?
-			res.send error: "Server error while deleting measure", 500
+		if err
+			res.send error: err, 500
 		else
 			res.send success: true, 200


Remove console.log

Remove console.log

--- lib/adapters/hTwitterAdapter.coffee
+++ lib/adapters/hTwitterAdapter.coffee
@@ -47,7 +47,6 @@
         @stream = stream
         stream.on "data", (data) =>
           unless data.disconnect
-            console.log data.user.lang
             if @properties.langFilter is undefined or data.user.lang is @properties.langFilter
               hTweet = {}
               hTweetAuthor = {}


Fix get subject method to wait for locale fully loaded

--- server/controllers/sharing.coffee
+++ server/controllers/sharing.coffee
@@ -7,25 +7,35 @@
 localizationManager = require '../helpers/localization_manager'

-getDisplayName = (callback) ->
+getUser = (callback) ->
     cozydb.api.getCozyUser (err, user) ->
+        return callback err if err
+
         if user?.public_name and user.public_name.length > 0
-            callback null, user.public_name
+            callback null,
+                name:  user.public_name
+                email: user.email
         else
             localizationManager.ensureReady (err) ->
-                callback null, localizationManager.t 'default user name'
+                callback null,
+                    name: localizationManager.t 'default user name'
+                    email: null
+

 clearanceCtl = clearance.controller
     mailTemplate: (options, callback) ->
-        getDisplayName (err, displayName) ->
-            options.displayName = displayName
+        getUser (err, user) ->
+            options.displayName = user.name
+            options.displayEmail= user.email
             localizationManager.render 'sharemail', options, callback

     mailSubject: (options, callback) ->
-        getDisplayName (err, displayName) ->
-            callback null, localizationManager.t 'email sharing subject',
-                displayName: displayName
-                name: options.doc.title
+        getUser (err, user) ->
+            # Need to force locale to be loaded before executing callback
+            localizationManager.ensureReady (err) ->
+                callback null, localizationManager.t 'email sharing subject',
+                    displayName: user.name
+                    name: options.doc.title

 # fetch album, put it in req.doc
 module.exports.fetch = (req, res, next, id) ->


Validate project-linked-file data before doing import

--- services/web/app/coffee/Features/LinkedFiles/ProjectFileAgent.coffee
+++ services/web/app/coffee/Features/LinkedFiles/ProjectFileAgent.coffee
@@ -15,6 +15,7 @@
 	return error
 AccessDeniedError.prototype.__proto__ = Error.prototype

+
 BadEntityTypeError = (message) ->
 	error = new Error(message)
 	error.name = 'BadEntityType'
@@ -23,16 +24,31 @@
 BadEntityTypeError.prototype.__proto__ = Error.prototype

+BadDataError = (message) ->
+	error = new Error(message)
+	error.name = 'BadData'
+	error.__proto__ = BadDataError.prototype
+	return error
+BadDataError.prototype.__proto__ = Error.prototype
+
+
 module.exports = ProjectFileAgent =

 	sanitizeData: (data) ->
-		# TODO:
-		#   - Nothing?
 		return data
+
+	_validate: (data) ->
+		return (
+			!!data.source_project_id &&
+			!!data.source_entity_path &&
+			!!data.source_project_display_name
+		)

 	writeIncomingFileToDisk:
 		(project_id, data, current_user_id, callback = (error, fsPath) ->) ->
 			callback = _.once(callback)
+			if !ProjectFileAgent._validate(data)
+				return callback(new BadDataError())
 			{source_project_id, source_entity_path} = data
 			AuthorizationManager.canUserReadProject current_user_id, source_project_id,
 				null, (err, canRead) ->
@@ -61,10 +77,10 @@
 	handleError: (error, req, res, next) ->
 		if error instanceof AccessDeniedError
 			res.status(403).send("You do not have access to this project")
-		else if error instanceof FileNotFoundError
-			res.status(404).send("The file does not exist")
+		else if error instanceof BadDataError
+			res.status(400).send("The submitted data is not valid")
 		else if error instanceof BadEntityTypeError
-			res.status(404).send("The file is the wrong type")  # TODO: better error message
+			res.status(404).send("The file is the wrong type")
 		else
 			next(error)
 		next()


only set status and merge fields if required

Not strictly nessaserry but it is a bit safer

also improve error reporting for change email

--- services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
+++ services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
@@ -16,10 +16,10 @@

 	subscribe: (user, callback = () ->)->
 		options = buildOptions(user, true)
-		logger.log options:options, user:user, email:user.email, "trying to subscribe user to the mailing list"
+		logger.log options:options, user:user, email:user.email, "subscribing user to the mailing list"
 		mailchimp.request options, (err)->
 			if err?
-				logger.err err:err, "error subscribing person to newsletter"
+				logger.err err:err, user:user, "error subscribing person to newsletter"
 			else
 				logger.log user:user, "finished subscribing user to the newsletter"
 			callback(err)
@@ -29,7 +29,7 @@
 		options = buildOptions(user, false)
 		mailchimp.request options, (err)->
 			if err?
-				logger.err err:err, "error unsubscribing person to newsletter"
+				logger.err err:err, user:user, "error unsubscribing person to newsletter"
 			else
 				logger.log user:user, "finished unsubscribing user to the newsletter"
 			callback(err)
@@ -39,12 +39,15 @@
 		delete options.body.status
 		options.body.email_address = newEmail
 		mailchimp.request options, (err)->
-			if err? and err?.message?.indexOf("merge fields were invalid")
-				logger.log {oldEmail, newEmail}, "unable to change email in newsletter as user has not subscribed"
+			if err? and err?.message?.indexOf("merge fields were invalid") != -1
+				logger.log {oldEmail, newEmail}, "unable to change email in newsletter, user has never subscribed"
 				return callback()
-			# if the user has unsubscribed mailchimp will error on email address change
-			else if err? and err?.message?.indexOf("could not be validated") == -1
-				logger.err err:err, "error changing email in newsletter"
+			else if err? and err?.message?.indexOf("could not be validated") != -1
+				logger.log {oldEmail, newEmail},
+					"unable to change email in newsletter, user has previously unsubscribed or new email already exist on list"
+				return callback(err)
+			else if err?
+				logger.err {err, oldEmail, newEmail}, "error changing email in newsletter"
 				return callback(err)
 			else
 				logger.log "finished changing email in the newsletter"
@@ -54,18 +57,24 @@
 	crypto.createHash('md5').update(email.toLowerCase()).digest("hex")

 buildOptions = (user, is_subscribed)->
-	status =  if is_subscribed then "subscribed" else "unsubscribed"
 	subscriber_hash = hashEmail(user.email)
 	opts =
 		method: "PUT"
 		path: "/lists/#{Settings.mailchimp?.list_id}/members/#{subscriber_hash}"
 		body:
-			status_if_new: status
-			status: status
 			email_address:user.email
-			merge_fields:
-				FNAME: user.first_name
-				LNAME: user.last_name
-				MONGO_ID:user._id
+
+	status = if is_subscribed then "subscribed" else "unsubscribed"
+	if is_subscribed?
+		opts.body.status = status
+		opts.body.status_if_new = status
+
+	if user._id?
+		opts.body.merge_fields =
+			FNAME: user.first_name
+			LNAME: user.last_name
+			MONGO_ID:user._id
+
+	console.log opts
 	return opts


fix title

fix title

--- lib/carte/client/views/content.cjsx
+++ lib/carte/client/views/content.cjsx
@@ -30,11 +30,9 @@
         cards.fetch success: ()-> cards.fetching = false
         title = []
         for k, v of cards.query
-          if k != 'title'
-            title.push(String(k).capitalize() + ': ' + v)
+          title.push(String(k).capitalize() + ': ' + v)
         title = title.join(', ')
-        title = 'search: ' + cards.query.title + ' (' + title + ')' if cards.query.title
-        title += ' - ' + config.title
+        title = config.title + ' (' + title + ')'
         document.title = title
         <List key='list' router={@props.router} cards={cards} />
       when "show"
@@ -61,7 +59,7 @@
           error: (card, response)=>
             console.log 'error!!!!!!!!!!!!!!!!!!!!!!!!', response
             cards.fetching = false
-        document.title = card.get('title') + ' - ' + config.title
+        document.title = config.title + ' (' + card.get('title') + ')'
         <List key='show' cards={cards} card={card} />
       else
         console.log 'else'


Issue #67: add spaces to snippets in the Atom plugin


Add getDoc()

Add getDoc()

--- src/Collections.coffee
+++ src/Collections.coffee
@@ -337,11 +337,22 @@
     unless id? then throw new Error('No document ID provided')
     collection = @get(arg)
     unless collection then throw new Error('No collection provided')
-    if Meteor.isClient
-      collection._collection?._docs?.has(id) ? false
-    else
-      collection.findOne(_id: id)?
-
+    collection._collection?._docs?.has(id) ? collection.findOne(_id: id)? ? false
+
+  # @param {String|Meteor.Collection|Cursor} arg
+  # @param {String} id - A document ID.
+  # @param {Object} [options.clone=true] - Whether to clone the document (default). If false,
+  #     the document object will be accessed directly from the IdMap if possible. This results in
+  #     higher performance at the risk of exposing the mutable document.
+  # @returns {Object} The document exists in the given collection.
+  getDoc: (arg, id, options) ->
+    unless id? then throw new Error('No document ID provided')
+    collection = @get(arg)
+    unless collection then throw new Error('No collection provided')
+    if options?.clone == false
+      collection._collection?._docs?.get(id)
+    else
+      collection.findOne(_id: id)

   getDocChanges: (oldDoc, newDoc) ->
     oldDoc = Objects.flattenProperties(oldDoc)


- reenable twitter publishing

- reenable twitter publishing

--- src/bracketsextensiontweetbot.coffee
+++ src/bracketsextensiontweetbot.coffee
@@ -100,7 +100,7 @@
       twitterConf = JSON.parse fs.readFileSync(TWITTER_CONFIG)

       twitterPublisher = new TwitterPublisher twitterConf
-      # twitterPublisher.post notification for notification in notifications
+      twitterPublisher.post notification for notification in notifications

       swapRegistryFiles newRegistry


New point utility methods

New point utility methods

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -540,6 +540,21 @@

 # Point

+Utils.pointDivide = (pointA, pointB, fraction) ->
+	return point =
+		x: (pointA.x + pointB.x) / fraction
+		y: (pointA.y + pointB.y) / fraction
+
+Utils.pointAdd = (pointA, pointB) ->
+	return point =
+		x: pointA.x + pointB.x
+		y: pointA.y + pointB.y
+
+Utils.pointSubtract = (pointA, pointB) ->
+	return point =
+		x: pointA.x - pointB.x
+		y: pointA.y - pointB.y
+
 Utils.pointZero = (args={}) ->
 	return _.defaults(args, {x:0, y:0})

@@ -577,6 +592,11 @@
 	return false if point.x < Utils.frameGetMinX(frame) or point.x > Utils.frameGetMaxX(frame)
 	return false if point.y < Utils.frameGetMinY(frame) or point.y > Utils.frameGetMaxY(frame)
 	return true
+
+Utils.pointCenter = (pointA, pointB) ->
+	return Utils.pointDivide(pointA, pointB, 2)
+
+

 # Size


Input cleanup

Input cleanup

--- public/app/input.coffee
+++ public/app/input.coffee
@@ -10,15 +10,10 @@
             @on "key:down", document.onkeydown if document.onkeydown
             @on "key:up", document.onkeyup if document.onkeyup

-            window.onkeyup = @up
-            window.onkeydown = @down
+            window.onkeyup = (e) => @key 'up', e
+            window.onkeydown = (e) => @key'down', e

-        up: (event) =>
-            @emit "key:up", event
-            do (mapping = @CODES[event.which]) =>
-                @emit "key:up[#{mapping}]" if mapping
-
-        down: (event) =>
-            @emit "key:down", event
-            do (mapping = @CODES[event.which]) =>
-                @emit "key:down[#{mapping}]" if mapping+        key: (type, e) =>
+            @emit "key:#{type}", e
+            do (mapping = @CODES[e.which]) =>
+                @emit "key:#{type}[#{mapping}]" if mapping


fix(calculator): remove dev values

fix(calculator): remove dev values

--- src/app/calculator/controller.coffee
+++ src/app/calculator/controller.coffee
@@ -14,9 +14,6 @@
     units: 'metric'
     gender: 'male'
     activity: $scope.activityLevels[0]
-    age: 25
-    height: 178
-    weight: 74

   $scope.diet =
     type: 'normal'


Remove unused code

Remove unused code

--- src/scripts/router.coffee
+++ src/scripts/router.coffee
@@ -29,21 +29,3 @@
     navigate: () ->
       super(arguments...)
       @trigger('navigate')
-
-    # Helper function to determine the current route's parameters
-    current: () ->
-      routes = []
-      params = null
-
-      # Find the route matching the current URL
-      _.each Backbone.history.handlers, (handler) -> routes.push(handler.route)
-      matched = _.find routes, (route) -> route.test(Backbone.history.fragment)
-
-      # Determine the matching route parameters
-      if matched then params = @_extractParameters(matched, Backbone.history.fragment)
-
-      return {
-        regex: matched
-        fragment: Backbone.history.fragment
-        params: params
-      }


Wording change.

Wording change.

--- src/scripts/sifter.coffee
+++ src/scripts/sifter.coffee
@@ -9,7 +9,7 @@

 module.exports = (robot) ->
   robot.respond /(give me a )*(sift(e|a)r report)+/i, (msg)->
-    msg.send "Generating a report for open issues per project..."
+    msg.send "-Issues per project-"
     msg.http("https://#{company}.sifterapp.com/api/projects/")
       .header('X-Sifter-Token', token)
       .header('Accept', 'application/json')


Show no auto complete suggestions when already unfocsed

--- lib/extensions/auto-complete.coffee
+++ lib/extensions/auto-complete.coffee
@@ -48,11 +48,10 @@
     updateItems = (orig) =>
       (e) =>
         promise = config.query(e.target.value).then (items) =>
-          @setState __autocomplete_items: items
+          @setState __autocomplete_items: items if @state.focus
         @refs['indicator']?.track?(promise)
         orig? e

     props.onChange = do (orig = props.onChange) => updateItems orig
     props.onFocus = do (orig = props.onFocus) => updateItems orig
-
     props.classList.push 'm-autocompleted'


Show dummy messages as notifications

Show dummy messages as notifications

--- lib/ghc-mod/ghc-modi-process.coffee
+++ lib/ghc-mod/ghc-modi-process.coffee
@@ -279,7 +279,18 @@
       args: args
     .then (lines) =>
       rootDir = @getRootDir buffer
-      lines.map (line) ->
+      lines
+      .filter (line) ->
+        res = false
+        switch
+          when line.startsWith 'Dummy:0:0:Error:'
+            atom.notifications.addError line.slice(16)
+          when line.startsWith 'Dummy:0:0:Warning:'
+            atom.notifications.addError line.slice(18)
+          else
+            res = true
+        return res
+      .map (line) ->
         match =
           line.match(/^(.*?):([0-9]+):([0-9]+): *(?:(Warning|Error): *)?/)
         unless match?


* Renamed all components, new markup
* Partial resolution pattern changed: subfolders are declared like this: {{> pat

* New unified signature to declare components: `Hull.component('component_name',
* Completed documentation and examples for each component
* Upload components has a default backend store now

* New property in components: `require` is an Array of dependencies. Those will
* New Handlebars helper: `fallback`: {{fallback value 'default_value'}}

--- src/client/handlebars-helpers.coffee
+++ src/client/handlebars-helpers.coffee
@@ -360,13 +360,14 @@
      * write a value or a fallback if the value is falsy
      *
      *      foo='bar'
-     *
-     *      1. {{outputIf foo 'bar'}}
-     *      2. {{outputIf foo 'baz' 'checked'}}
+     *      baz=0
+     *
+     *      1. {{fallback foo 'default_value'}}
+     *      2. {{fallback baz 'default_value'}}
      *
      *      =>
-     *      1.
-     *      2. 'checked'
+     *      1. 'bar'
+     *      2. 'default_value'
     ###

     HandlebarsHelpers.fallback = (obj, fb)->


Add DisplayBufferMarker.inspect

Add DisplayBufferMarker.inspect

--- src/app/display-buffer-marker.coffee
+++ src/app/display-buffer-marker.coffee
@@ -145,8 +145,11 @@

   # Destroys the marker
   destroy: ->
-    delete @displayBuffer.markers[@id]
     @bufferMarker.destroy()
+
+  # Returns a {String} representation of the marker
+  inspect: ->
+    "DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange().inspect()})"

   ###
   # Internal #
@@ -165,12 +168,10 @@
           newTailBufferPosition: newTailPosition
           bufferChanged: bufferChanged
           valid: valid
-    @displayBuffer.markers[@id] = this

   unobserveBufferMarkerIfNeeded: ->
     return if @subscriptionCount()
     @bufferMarkerSubscription.cancel()
-    delete @displayBuffer.markers[@id]

   notifyObservers: ({oldHeadBufferPosition, oldTailBufferPosition, bufferChanged, valid} = {}) ->
     oldHeadScreenPosition = @getHeadScreenPosition()


forward `abort` from the deferred result of `fetch` onto the deferred object generated by `pipe`

--- V1.Backbone.coffee
+++ V1.Backbone.coffee
@@ -152,8 +152,13 @@

     data = JSON.stringify(query)

-    @options.fetch(@options.url, data)
-      .pipe((data) -> prepareResultFor(data[0], type))
+    xhr = @options.fetch(@options.url, data)
+
+    deferred = xhr.pipe (data) -> prepareResultFor(data[0], type)
+    deferred.abort = _.bind(xhr.abort, xhr) if xhr.abort? and !deferred.abort?
+
+    deferred
+

   findOrCreateBatch: ->
     @currentBatch = @options.defer() unless @currentBatch?


remove forgotten log statement

remove forgotten log statement

--- src/Environment.coffee
+++ src/Environment.coffee
@@ -48,7 +48,6 @@

         return {} unless p

-        console.log p
         contents = fs.readFileSync(p)
         return {} unless contents


remove unnecessary option from subscribe button tag

--- src/javascripts/subscribe_button_trigger.coffee
+++ src/javascripts/subscribe_button_trigger.coffee
@@ -12,7 +12,6 @@
     @scriptTag.src = "https://cdn.podlove.org/subscribe-button/javascripts/app.js"
     @scriptTag.dataset.language = 'en'
     @scriptTag.dataset.size = 'medium'
-    @scriptTag.setAttribute('data-json-data', 'podcastData_id_adeba7cb_390a_4a57_80a0_d79c7a9b9c1e')
     @scriptTag.setAttribute('data-hide', true)
     @scriptTag.setAttribute('data-buttonid', @id)


create shell of Instance addAccessConfig

--- src/jobs/instances-addAccessConfig/message.cson
+++ src/jobs/instances-addAccessConfig/message.cson
@@ -1,6 +1,6 @@
 {
   type: 'object'
-  title: addAccessConfig - Adds an access config to an instance's network interface - fix job.coffee'
+  title: 'addAccessConfig - Adds an access config to an instances network interface - fix job.coffee'
   'x-group-name': 'Instances'
   required: ['data']
   properties:


Don't create a new CODAP table until a record button is pushed.

[#135145637]
https://www.pivotaltracker.com/story/show/135145637

--- src/code/models/codap-connect.coffee
+++ src/code/models/codap-connect.coffee
@@ -29,8 +29,7 @@
     @lastTimeSent = @_timeStamp()
     @sendThrottleMs = 300

-    # Create a new case in these instances:
-    SimulationStore.actions.resetSimulation.listen    @_openNewCase.bind(@)
+    # Create a new case for each experiment:
     SimulationStore.actions.createExperiment.listen   @_openNewCase.bind(@)

     SimulationStore.actions.recordingFramesCreated.listen  @_addData.bind(@)
@@ -131,21 +130,29 @@
     caseData = {}
     caseData[tr '~CODAP.SIMULATION.EXPERIMENT'] = SimulationStore.store.settings.experimentNumber
     @currentCaseID = null
-    @_createCollection()
-    @codapPhone.call
-      action: 'create',
-      resource: 'collection[Simulation].case',
-      values: {
-        values: caseData
-      }
+    collectionAction = @_createCollection()
+
+    @codapPhone.call([
+      collectionAction,
+      {
+        action: 'create'
+        resource: 'collection[Simulation].case',
+        values: { values: caseData }
+      }
+    ]
     , (result) =>
-      if result?.success
-        @currentCaseID = result.values[0].id
-        @_flushQueue()
-        if not @standaloneMode
-          @createTable()
-      else
-        log.info "CODAP returned an error on 'openCase'"
+      if result?
+        [collectionResponse, caseResponse] = result
+
+        if caseResponse.success
+          @caseOpened = true
+          @currentCaseID = caseResponse.values[0].id
+          @_flushQueue()
+          if not @standaloneMode
+            @createTable()
+        else
+          log.info "CODAP returned an error on 'openCase'"
+    )

   _createCollection: ->
     nodes = @graphStore.getNodes()
@@ -168,15 +175,17 @@
     _.each nodes, (node) ->
       addSampleDataAttr(node)

-    @codapPhone.call
+    return {
       action: 'create',
       resource: 'collection[Samples].attribute',
       values: sampleDataAttrs
+    }

   _timeStamp: ->
     new Date().getTime()

   _shouldSend: ->
+    @_openNewCase() unless @caseOpened?
     return false unless @currentCaseID
     currentTime = @_timeStamp()
     elapsedTime = currentTime - @lastTimeSent


Whitespace

Whitespace

--- lib/installed-packages-panel.coffee
+++ lib/installed-packages-panel.coffee
@@ -65,10 +65,13 @@

         _.each @addPackageViews(@communityPackages, @packages.user), (v) => @packageViews.push(v)
         @communityCount.text " (#{@packages.user.length})"
+
         _.each @addPackageViews(@corePackages, @packages.core), (v) => @packageViews.push(v)
         @coreCount.text " (#{@packages.core.length})"
+
         _.each @addPackageViews(@devPackages, @packages.dev), (v) => @packageViews.push(v)
         @devCount.text " (#{@packages.dev.length})"
+
       .catch (error) =>
         @loadingMessage.hide()
         # TODO errors by section
@@ -90,6 +93,7 @@
       packView.widen()
       packageViews.push(packView) # used for search filterin'
       packageRow.append(packView)
+
     packageViews

   filterPackageListByText: (text) ->


Forces positioning of datetimepicker widget at bottom of input

--- app/assets/javascripts/form/dates.js.coffee
+++ app/assets/javascripts/form/dates.js.coffee
@@ -36,6 +36,9 @@
           showClear: true
           showClose: true
           showTodayButton: true
+          widgetPositioning:
+            horizontal: 'auto'
+            vertical: 'bottom'
         element.attr "autocomplete", "off"
       return


Send the type of the status update to redis, since we're sending the message and that makes no sense without the type

--- server/code/model/dataset.coffee
+++ server/code/model/dataset.coffee
@@ -53,7 +53,8 @@
       boxes = _.map @views, (v) -> v.box
       message = JSON.stringify
         boxes: boxes
-        message: status.message
+        type: @status.type
+        message: @status.message
       env = process.env.NODE_ENV
       Dataset.redisClient.publish "#{env}.cobalt.dataset.#{@box}.update", message
       callback err


don't update deleted tool instances

don't update deleted tool instances

--- server/code/model/dataset.coffee
+++ server/code/model/dataset.coffee
@@ -57,11 +57,15 @@
       @dbClass.findOne {box: id, user: args[0]}, args[1]

   @findAllByTool: (toolName, callback) ->
-    @dbClass.find {tool: toolName}, callback
+    @dbClass.find {tool: toolName, state: {$ne: 'deleted'}}, callback

 Dataset.View =
   findAllByTool: (toolName, callback) ->
-    Dataset.dbClass.find 'views.tool': toolName, (err, docs) ->
+    Dataset.dbClass.find
+      'views.tool': toolName
+      state:
+        $ne: 'deleted'
+    , (err, docs) ->
       # convert from dataset to its views...
       listoflists = _.map docs, (item) ->
         item.views


Fix typo.

Fix typo.

--- src/chaplin/models/collection.coffee
+++ src/chaplin/models/collection.coffee
@@ -25,9 +25,12 @@
       items = []
       for model in @models
         item = if model instanceof Chaplin.Model
+          # Use optimised Chaplin serialization
           model.serialize()
         else
-          model.toJSON() ? model
+          # Fall back to unoptimized Backbone stuff
+          model.toJSON()
+        items.push item
       items

     # Adds a collection atomically, i.e. throws no event until


fix(thread-list): AccountId resolution for items outside loaded range

--- internal_packages/thread-list/lib/thread-list.cjsx
+++ internal_packages/thread-list/lib/thread-list.cjsx
@@ -107,11 +107,10 @@
     dataSource = ThreadListStore.dataSource()
     if itemThreadId in dataSource.selection.ids()
       dragThreadIds = dataSource.selection.ids()
+      dragAccountIds = _.pluck(dataSource.selection.items(), 'accountId')
     else
       dragThreadIds = [itemThreadId]
-
-    dragAccountIds = dragThreadIds.map (threadId) -> dataSource.getById(threadId).accountId
-    dragAccountIds = _.uniq(dragAccountIds)
+      dragAccountIds = [dataSource.getById(itemThreadId).accountId]

     dragData = {
       accountIds: dragAccountIds,


Removed strong tag

Removed strong tag

--- app/assets/javascripts/lib/core/base.coffee
+++ app/assets/javascripts/lib/core/base.coffee
@@ -48,7 +48,7 @@
     showCookieComplianceMsg: ->
       if LocalStore.get('cookie-compliance') is undefined or LocalStore.get('cookie-compliance') is null
         args =
-          content: "
Hi there, we use cookies to improve your experience on our website. You can update your settings by clicking the Cookie Policy link at the bottom of the page.
"
+          content: "
Hi there, we use cookies to improve your experience on our website. You can update your settings by clicking the Cookie Policy link at the bottom of the page.
"
           style: "row--cookie-compliance js-cookie-compliance"
           userOptions :
             close: true


fixed graph

fixed graph

--- src/backside/updates_graph.coffee
+++ src/backside/updates_graph.coffee
@@ -23,16 +23,20 @@
       all_files = FileSys.files
       myregex = /logs\\Personal/
       # console.log(FileSys.files)
-      fd = fs.openSync('static\\data.tsv', 'w')
+      fd = fs.openSync('static\\data.csv', 'w')
+      sortNumber = (a, b) ->
+        a - b
       for files in all_files
         if myregex.test(files) == true
           matches.push(files)
+      console.log(matches.sort(sortNumber))
       while i < matches.length
         if matches[i] != null
           # file_array = matches[i].split "\\"
           # sync_date = file_array[9]
           temp_date_array.push(date_regex.exec(matches[i]))
         i++
+      #console.log(temp_date_array)
       while j < temp_date_array.length
         if temp_date_array[j] != null
           date = temp_date_array[j][0]
@@ -45,8 +49,11 @@
           else
             dict[month]+=1
         j++
+      final_array.push("date,close" + "\n")
       for line in date_array
         check = line.split('-')
-        final_array.push(line + ' ' + dict[check[1]] + "\n")
-      fs.writeSync(fd, final_array)
+        console.log(line)
+        final_array.push(line + ',' + dict[check[1]] + "\n")
+      fs.writeSync(fd, final_array.join(""))
       fs.closeSync(fd)
+      console.log(dict)


Fixed 3 lint bugs

Fixed 3 lint bugs

--- src/code/models/codap-connect.coffee
+++ src/code/models/codap-connect.coffee
@@ -149,7 +149,7 @@
       doResolve = (listAttributeResponse) =>
         if listAttributeResponse.success
           values = listAttributeResponse.values
-          newAttributes = _.select currentAttributes, (a) =>  (! _.includes(values,a.name))
+          newAttributes = _.select currentAttributes, (a) -> (! _.includes(values,a.name))
           message =
             action: 'create'
             resource: 'dataContext[Sage Simulation].collection[Samples].attribute'
@@ -232,7 +232,7 @@
   addData: (data) ->
     timeUnit = TimeUnits.toString SimulationStore.store.stepUnits(), true
     # Create the sample data values (node values array)
-    sampleData = _.map data, (frame) =>
+    sampleData = _.map data, (frame) ->
       sample = {}
       sample[tr '~CODAP.SIMULATION.EXPERIMENT'] = SimulationStore.store.settings.experimentNumber
       sample[timeUnit] = frame.time
@@ -245,7 +245,7 @@
     else
       setTimeout(@_sendSimulationData.bind(@), @sendThrottleMs)

-  createGraph: (yAttributeName)->
+  createGraph: (yAttributeName) ->
     @_createMissingDataAttributes()
     timeUnit = TimeUnits.toString SimulationStore.store.stepUnits(), true

@@ -259,7 +259,7 @@
         size: { width: 242, height: 221 },
         position: 'bottom'

-  createTable: () ->
+  createTable: ->
     unless @tableCreated
       @codapPhone.call
         action: 'create',


minor bugfix

minor bugfix

--- src/coffee/order_state_update.coffee
+++ src/coffee/order_state_update.coffee
@@ -103,10 +103,10 @@
             else
               {ignored: true, reason: "Conditions not met"}

-    getMasterOrderId = (sphere, msg) ->
-      if sphere?
+    getMasterOrderId = (sourceSphere, targetSphere, msg) ->
+      if targetSphere?
         masterSyncInfosP = _.map msg.resource.obj.syncInfo, (si) ->
-          sphere.getChannelByRef(si.channel)
+          sourceSphere.getChannelByRef(si.channel)
           .then (ch) ->
             {channel: ch, syncInfo: si}

@@ -124,7 +124,7 @@
     changeOrderStateIfNeeded = (sourceInfo, msg) ->
       sphere = sourceInfo.sphere

-      getMasterOrderId(targetSphere, msg)
+      getMasterOrderId(sphere, targetSphere, msg)
       .then (masterOrderId) ->
         Q.spread [
           sphere.getStateByRef(msg.fromState)


Add ON DELETE CASCADE snippet

Add ON DELETE CASCADE snippet

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -698,6 +698,10 @@
     'prefix': 'foreignkey'
     'body': 'FOREIGN KEY(${1:column}) REFERENCES ${2:tname}(${3:column})$4'

+  'on delete cascade':
+    'prefix': 'ondeletecascade'
+    'body': 'ON DELETE CASCADE'
+
   'not null':
     'prefix': 'notnull'
     'body': 'NOT NULL'


Fixed passthrough of response objects in Backbone.ajax #1009

--- app/assets/javascripts/libs/core_ext.coffee
+++ app/assets/javascripts/libs/core_ext.coffee
@@ -379,4 +379,7 @@
     options.url
     method : options.type
     data : options.data
-  )).then( options.success, options.error )
+  ))
+    # Needs to be done/fail because we don't care about the return value of the callbacks
+    .done(options.success)
+    .fail(options.error)


Fix to use default leaflet popup z-index

--- app/assets/javascripts/map_editor.js.coffee
+++ app/assets/javascripts/map_editor.js.coffee
@@ -288,7 +288,7 @@
       popup += "<input class='updateAttributesInPopup' type='button' value='ok'/>"
       popup += "</div>"

-      layer.bindPopup popup, keepInView: true, maxWidth: 600
+      layer.bindPopup popup, keepInView: true, maxWidth: 600, className: 'leaflet-popup-pane'

     colorize: (level) ->
       #levels rane is set to [-3,3]


Removed unnecessary attributes

Removed unnecessary attributes

--- app/assets/javascripts/mrn_picker.js.coffee
+++ app/assets/javascripts/mrn_picker.js.coffee
@@ -8,15 +8,10 @@
         success: (data) ->
           response($.map(data.patients, (patient) ->
             return {
-            label: patient.last_name + "," + patient.first_name + " - MRN: " + patient.mrn
+            label: patient.last_name + ", " + patient.first_name + " - MRN: " + patient.mrn
             value: patient.mrn
             }
           )))
     minLength: 2

-    select: (event,ui) ->
-      open: ->
-        $(this).addClass("unstyled")
-
-
   )


:bug: Fix decorations not destroyed on deactivation

--- lib/minimap-pigments-binding.coffee
+++ lib/minimap-pigments-binding.coffee
@@ -9,17 +9,20 @@
     @colorBuffer.initialize().then => @updateMarkers()

     @subscriptions.add @colorBuffer.onDidUpdateColorMarkers => @updateMarkers()
+    @decorations = []

   updateMarkers: ->
     markers = @colorBuffer.findValidColorMarkers()
+    @decorations = []

     for m in @displayedMarkers when m not in markers
       @minimap.removeAllDecorationsForMarker(m.marker)

     for m in markers when m.color?.isValid() and m not in @displayedMarkers
-      @minimap.decorateMarker(m.marker, type: 'highlight', color: m.color.toCSS())
+      @decorations.push @minimap.decorateMarker(m.marker, type: 'highlight', color: m.color.toCSS())

     @displayedMarkers = markers

   destroy: ->
     @subscriptions.dispose()
+    decoration.destroy() for decoration in @decorations


Adapt new lines to code style guide

Adapt new lines to code style guide

--- servers/lib/server/client.coffee
+++ servers/lib/server/client.coffee
@@ -9,7 +9,9 @@
   console.error err
   return callback? err

+
 updateCookie = (req, res, session) ->
+
   { clientId }       = session
   { maxAge, secure } = KONFIG.sessionCookie

@@ -114,5 +116,3 @@

 module.exports = { generateFakeClient: generateFakeClientFromReq, updateCookie }
-
-


resize input element on backspace keyup event

--- client/Framework/components/inputs/KDInputView.coffee
+++ client/Framework/components/inputs/KDInputView.coffee
@@ -332,7 +332,7 @@
       $growCalculator.detach()
       @$()[0].style.height = "none" # hack to set to initial

-    @on "keydown", =>
+    resize = =>
       $growCalculator.html @getValue().replace /\n/gm, "<br />"
       height = $growCalculator.height()
       if @$().css('box-sizing') is "border-box"
@@ -341,6 +341,9 @@
         height     = height + border + padding

       @setHeight height
+
+    @on "keydown", => resize()
+    @on "keyup", (event) => resize() if @getValue().length is 0

   enableTabKey:-> @inputTabKeyEnabled = yes


GroupsInvitation: disable out-dated method call

--- client/admin/lib/views/groupsinvitationtabview.coffee
+++ client/admin/lib/views/groupsinvitationtabview.coffee
@@ -32,7 +32,7 @@
     @approvalEnabled = @getDelegate().policy?.approvalEnabled
     @showResolved    = no

-    @on 'PaneAdded', (pane)=> pane.options.view.updatePendingCount pane
+    # @on 'PaneAdded', (pane)=> pane.options.view.updatePendingCount pane

     @createTabs()
     @addHeaderButtons()
@@ -116,5 +116,3 @@

   _windowDidResize:->
     # @setHeight @parent.getHeight() - @getTabHandleContainer().getHeight()
-
-


Made sure that box document is an actual box model.

--- server/code/model/user.coffee
+++ server/code/model/user.coffee
@@ -99,6 +99,7 @@
       async.forEach boxes, (box, boxCb) ->
         # call cobalt with list of sshkeys of box
         # sshkeys <--> user <--> box
+        box = Box.makeModelFromMongo box
         box.distributeSSHKeys (err, res, body) ->
           try
             obj = JSON.parse body


removed table specific selectors from methods

--- app/coffee/drm-calendar/drm-calendar.coffee
+++ app/coffee/drm-calendar/drm-calendar.coffee
@@ -495,7 +495,7 @@
                 self.addEventForm.find(':input').val ''
                 self.addEventForm.find('input:checked').prop 'checked', false

-            self.calendar.on 'click', "td:not(.#{self.classes.muted})", ->
+            self.calendar.on 'click', ".drm-date", ->
                 that = $ @
                 month = self.months[that.data('month')]
                 date = that.data 'date'
@@ -512,7 +512,7 @@
             # show event details
             self.calendar.on 'click', "ul.#{self.eventClass} a", (e) ->
                 that = $ @
-                day = that.closest('td')
+                day = that.closest('.drm-date')
                 eventId = that.data 'event'
                 fullDate =
                     month: self.months[day.data('month')]
@@ -551,7 +551,7 @@
             year = calendarInner.data 'year'

             if month is @currentMonth and year is @currentYear
-                calendarInner.find("td[data-date=#{@currentDate}]").addClass @classes.today
+                calendarInner.find(".drm-date[data-date=#{@currentDate}]").addClass @classes.today

         createEvent: (newEvent) =>
             id = @events.length
@@ -575,16 +575,18 @@
             calendarInner = @calendar.find "div.#{@calendarInnerClass}"

             if @view is 'month'
-                calendarItem = calendarInner.find "td[data-date=#{dates}]"
+                calendarItem = calendarInner.find ".drm-date[data-date=#{dates}]"
             else if @view is 'week' and (events.allDayEvent or !events.time?)
-                calendarItem = calendarInner.find "td[data-date=#{dates}][data-hour='All Day Event']"
+                calendarItem = calendarInner.find ".drm-date[data-date=#{dates}][data-hour='All Day Event']"
             else if @view is 'week' and !events.allDayEvent
                 # find hour td element
                 re = new RegExp '^0?','gi'
                 re2 = new RegExp ':[0-9]{2}', 'gi'
                 hour = events.time.replace re, ''
                 hour = hour.replace re2, ''
-                calendarItem = calendarInner.find "td[data-date=#{dates}][data-hour=#{hour}]"
+                calendarItem = calendarInner.find ".drm-date[data-date=#{dates}][data-hour=#{hour}]"
+            else if @view is 'day'
+                calendarItem = calendarInner.find ".drm-date[data-date=#{dates}]"

             eventList = calendarItem.find "ul.#{@eventClass}"
             length = eventList.length
@@ -732,23 +734,25 @@
         getEventWeekNum: (dayNum, day, numberDays, dayShift) =>
             self = @
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
-            weeks = if self.view is 'month' then calendarInner.find('tbody').find 'tr.drm-week' else calendarInner.find 'tbody.drm-week'
+            weeks = calendarInner.find '.drm-week'
             numberWeeks = self.getWeeksInMonth numberDays, dayShift
             lastWeekLength = weeks.eq(numberWeeks).length

             if dayNum is 'last' and dayShift <= day
-                if lastWeekLength < day then (numberWeeks - 2) else numberWeeks - 1
+                eventWeekNum = if lastWeekLength < day then (numberWeeks - 2) else numberWeeks - 1
             else if dayNum is 'last' and dayShift > day
-                numberWeeks - 2
+                eventWeekNum = numberWeeks - 2
             else
-                parseInt(dayNum, 10) - 1
+                eventWeekNum = parseInt(dayNum, 10) - 1
+
+            eventWeekNum = if dayShift <= day then eventWeekNum else eventWeekNum + 1

         addYearlyEvents: (events, eventDates) =>
             self = @
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
             currentMonth = calendarInner.data 'month'
             currentYear = calendarInner.data 'year'
-            weeks = if self.view is 'month' then calendarInner.find('tbody').find 'tr.drm-week' else calendarInner.find 'tbody.drm-week'
+            weeks = calendarInner.find '.drm-week'
             firstDay = self.getDayOfWeek currentMonth, currentYear, 1
             numberDays = self.getDaysInMonth (currentMonth + 1), currentYear
             dayShift = if firstDay is self.daysPerWeek then 0 else firstDay
@@ -761,8 +765,12 @@
                     eventWeekNum = self.getEventWeekNum events.dayNum, day, numberDays, dayShift

                     if currentMonth is month
-                        eventWeek = if dayShift <= day then eventWeek = weeks.eq eventWeekNum else eventWeek = weeks.eq eventWeekNum + 1
-                        eventDates.push eventWeek.find("td.drm-date[data-day=#{day}]").data 'date'
+                        weeks.each ->
+                            that = $ @
+                            firstDay = that.find(".#{self.classes.date}").first().data 'date'
+                            weekInfo = self.getDatesInWeek currentMonth, firstDay, currentYear
+                            if eventWeekNum is weekInfo.weekNum
+                                eventDates.push that.find(".#{self.classes.date}[data-day=#{day}]").data 'date'
             else
                 eventDates.push parseInt(events.eventDate, 10)

@@ -771,7 +779,7 @@
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
             currentMonth = calendarInner.data 'month'
             currentYear = calendarInner.data 'year'
-            weeks = if self.view is 'month' then calendarInner.find('tbody').find 'tr.drm-week' else calendarInner.find 'tbody.drm-week'
+            weeks = calendarInner.find '.drm-week'
             firstDay = self.getDayOfWeek currentMonth, currentYear, 1
             numberDays = self.getDaysInMonth (currentMonth + 1), currentYear
             dayShift = if firstDay is self.daysPerWeek then 0 else firstDay
@@ -782,7 +790,7 @@
                     day = $.inArray value, self.days
                     eventWeekNum = self.getEventWeekNum events.dayNum, day, numberDays, dayShift
                     eventWeek = if dayShift <= day then eventWeek = weeks.eq eventWeekNum else eventWeek = weeks.eq eventWeekNum + 1
-                    eventDates.push eventWeek.find("td.drm-date[data-day=#{day}]").data 'date'
+                    eventDates.push eventWeek.find(".#{self.classes.date}[data-day=#{day}]").data 'date'
             else
                 eventDates.push parseInt(events.eventDate, 10)

@@ -791,7 +799,7 @@
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
             currentMonth = calendarInner.data 'month'
             currentYear = calendarInner.data 'year'
-            weeks = if self.view is 'month' then calendarInner.find('tbody').find "tr.drm-week" else calendarInner.find "tbody.drm-week"
+            weeks = calendarInner.find '.drm-week'

             # events that occur every 2 weeks
             if events.day
@@ -800,12 +808,12 @@
                     day = $.inArray value, self.days
                     length = weeks.length
                     weekPattern = if weekInfo.weekNum % 2 is 0 then 'even' else 'odd'
-                    eventWeeks = if self.view is 'month' then calendarInner.find('tbody').find "tr.#{weekPattern}-week" else calendarInner.find "tbody.#{weekPattern}-week"
+                    eventWeeks = calendarInner.find ".#{weekPattern}-week"

                     $.each eventWeeks, (key, value) ->
                         that = $ value
-                        weekLength = that.find("td:not(.#{self.classes.muted})").length
-                        eventDates.push that.find("td.drm-date[data-day=#{day}]").data('date')
+                        weekLength = that.find(".drm-date").length
+                        eventDates.push that.find(".#{self.classes.date}[data-day=#{day}]").data('date')

         addWeeklyEvents: (events, eventDates) =>
             self = @
@@ -815,7 +823,7 @@
             firstDay = self.getDayOfWeek currentMonth, currentYear, 1
             numberDays = self.getDaysInMonth (currentMonth + 1), currentYear
             dayShift = if firstDay is self.daysPerWeek then 0 else firstDay
-            weeks = if self.view is 'month' then calendarInner.find('tbody').find 'tr.drm-week' else calendarInner.find 'tbody.drm-week'
+            weeks = calendarInner.find '.drm-week'

             # weekly events
             if events.day
@@ -824,21 +832,21 @@
                     length = weeks.length
                     $.each weeks, (key, value) ->
                         that = $ value
-                        days = if self.view is 'month' then that.find "td.drm-date" else that.find "td.drm-date[data-hour='All Day Event']"
+                        days = if self.view is 'month' then that.find ".#{self.classes.date}" else that.find ".#{self.classes.date}[data-hour='All Day Event']"
                         weekLength = days.length
                         if key is 0 and length isnt 1
-                            if dayShift <= day then eventDates.push that.find("td.drm-date[data-day=#{day}]").data('date')
+                            if dayShift <= day then eventDates.push that.find(".#{self.classes.date}[data-day=#{day}]").data('date')
                         else if key is (length - 1) and length isnt 1
-                            if day < weekLength then eventDates.push that.find("td.drm-date[data-day=#{day}]").data('date')
+                            if day < weekLength then eventDates.push that.find(".#{self.classes.date}[data-day=#{day}]").data('date')
                         else
-                            eventDates.push that.find("td.drm-date[data-day=#{day}]").data('date')
+                            eventDates.push that.find(".#{self.classes.date}[data-day=#{day}]").data('date')

             eventDates

         addDailyEvents: (events, eventDates) =>
             self = @
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
-            days = if self.view is 'month' then calendarInner.find "tbody td.drm-date" else calendarInner.find "td.drm-date[data-hour='All Day Event']"
+            days = if self.view is 'month' then calendarInner.find ".#{self.classes.date}" else calendarInner.find ".#{self.classes.date}[data-hour='All Day Event']"
             days.each ->
                 eventDates.push $(@).data 'date'

@@ -877,12 +885,12 @@
         highlightWeekends: =>
             self = @
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
-            weeks = if self.view is 'month' then calendarInner.find('tbody').find 'tr.drm-week' else calendarInner.find 'tbody.drm-week'
+            weeks = calendarInner.find '.drm-week'

             $.each weeks, ->
                 that = $ @
                 $.each self.weekend, (key, value) ->
-                    weekend = that.find("td[data-day=#{value}]").not ".#{self.classes.muted}, .#{self.classes.today}, .#{self.classes.holiday}"
+                    weekend = that.find(".#{self.classes.date}[data-day=#{value}]").not ".#{self.classes.muted}, .#{self.classes.today}, .#{self.classes.holiday}"
                     weekend.addClass self.classes.weekend

         addWeekNumbers: =>
@@ -891,7 +899,7 @@
             month = calendarInner.data 'month'
             year = calendarInner.data 'year'
             weekNum = 1
-            weeks = if self.view is 'month' then calendarInner.find('tbody').find 'tr.drm-week' else calendarInner.find 'tbody.drm-week'
+            weeks = calendarInner.find '.drm-week'
             weekNums = []

             $.each self.months, (key, value) ->
@@ -954,7 +962,7 @@
             lastMonth = if currentMonth is 0 then 11 else currentMonth - 1

             if direction is 'prev'
-                firstDay = calendarInner.find('td.drm-date').first().data 'date'
+                firstDay = calendarInner.find(".#{@classes.date}").first().data 'date'
                 lastDayOfPrevMonth = @getDaysInMonth lastMonth + 1, currentYear

                 if firstDay is 1
@@ -965,7 +973,7 @@
                     currentDate = firstDay - 1

             else if direction is 'next'
-                lastDay = calendarInner.find('td.drm-date').last().data 'date'
+                lastDay = calendarInner.find(".#{@classes.date}").last().data 'date'
                 lastDayOfMonth = @getDaysInMonth currentMonth + 1, currentYear

                 if lastDay is lastDayOfMonth
@@ -1198,11 +1206,14 @@
         createDateView: (currentMonth, currentDate, currentYear) =>
             self = @
             self.calendarInnerClass = "drm-calendar-#{self.view}-view"
-
-            dayListHtml = "
"
+            day = self.getDayOfWeek currentMonth, currentYear, currentDate
+            weekNumber = self.getWeekNumber currentMonth, currentDate, currentYear
+            weekClass = if weekNumber % 2 is 0 then 'even-week' else 'odd-week'
+
+            dayListHtml = "
"
             $.each self.hours, (key, value) ->
                 hour = value.name
-                dayListHtml += "
#{hour}
"
+                dayListHtml += "
#{hour}
"
             dayListHtml += '
'

             calendar = $ '

',
@@ -1213,7 +1224,7 @@

             heading = $ '

',
                 class: 'drm-calendar-header'
-                text: "#{@months[currentMonth]} #{currentDate} #{currentYear}"
+                text: "#{self.days[day]}, #{self.months[currentMonth]} #{currentDate} #{currentYear}"

             calendar.appendTo ".#{self.calendarClass}"
             heading.prependTo ".#{self.calendarInnerClass}"


fix firefox open popup blink

fix firefox open popup blink

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -45,6 +45,8 @@

         @animationFrame.add =>
             $el.addClass("open")
+
+        @animationFrame.add =>
             lightboxContent.show()
             defered.resolve()


[backport] fix focus on open lightbox

[backport] fix focus on open lightbox

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -44,9 +44,8 @@

         @animationFrame.add ->
             $el.addClass("open")
-
-        @animationFrame.add ->
-            $el.find('input,textarea').first().focus()
+            $el.one "transitionend", =>
+              $el.find('input,textarea').first().focus()

         @animationFrame.add =>
             lightboxContent.show()


fix #2024 - fix watchers

fix #2024 - fix watchers

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -518,9 +518,10 @@
 ## Watchers Lightbox directive
 #############################################################################

-WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService) ->
+WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template) ->
     link = ($scope, $el, $attrs) ->
         selectedItem = null
+        usersTemplate = $template.get("common/lightbox/lightbox-assigned-to-users.html", true)

         # Get prefiltered users by text
         # and without now watched users.
@@ -595,7 +596,7 @@
         link:link
     }

-module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", WatchersLightboxDirective])
+module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", WatchersLightboxDirective])

 #############################################################################
 ## Notion Lightbox Directive


Reformat the indent pattern regexes

Reformat the indent pattern regexes

"regexes"?


Test coverage for delegated links

Test coverage for delegated links

--- source/javascripts/suite.js.coffee
+++ source/javascripts/suite.js.coffee
@@ -213,4 +213,14 @@
     $trigger.click()
     tester.verifyVisible()

+  asyncTest 'calling windoze() on a delegated link loads a remote file', ->
+    tester.use('.remote_load')
+    $trigger = tester.init({
+      delegate: 'a'
+      afterLoad: ->
+        start()
+    }, $('.remote_load'));
+    $trigger.find('a').click()
+    tester.verifyVisible()
+
 )(jQuery)


Remove env.coffee

Remove env.coffee

--- SingularityUI/app/env.coffee
+++ SingularityUI/app/env.coffee
@@ -1,3 +0,0 @@
-module.exports =
-    env: 'local'
-    SINGULARITY_BASE: 'http://heliograph.iad01.hubspot-networks.net:7015'


[fix] Typo

[fix] Typo

--- backend/operation_queue.coffee
+++ backend/operation_queue.coffee
@@ -414,7 +414,7 @@
                 log.debug "File doesn't exist locally, abort."
                 pouch.files.get filePaths.relative, (error, doc) ->
                     if doc?
-                        pouchd.db.remove doc, (error2, res) ->
+                        pouch.db.remove doc, (error2, res) ->
                             callback err
                     else
                         callback err


:lipstick: tabify

:lipstick: tabify

--- snippets/language-powershell.cson
+++ snippets/language-powershell.cson
@@ -25,7 +25,7 @@
     'body': '.SYNOPSIS\n'
   'Function Cmdlet':
     'prefix': 'func'
-    'body': '<#\n.Synopsis\nShort description\n.DESCRIPTION\nLong description\n.EXAMPLE\nExample of how to use this cmdlet\n.EXAMPLE\nAnother example of how to use this cmdlet\n#>\nfunction ${1:Verb}-${2:Noun}\n{\n[CmdletBinding()]\n[OutputType([int])]\nParam\n(\n\t# Param1 help description\n\t[Parameter(Mandatory, ValueFromPipelineByPropertyName, Position=0)]\n\t\$Param1,\n\n\t# Param2 help description\n\t[int]\n\t\$Param2\n)\nBegin\n{$\n\t{3:# code}\n}\nProcess\n{\n}\nEnd\n{\n}\n}'
+    'body': '<#\n.Synopsis\nShort description\n.DESCRIPTION\nLong description\n.EXAMPLE\nExample of how to use this cmdlet\n.EXAMPLE\nAnother example of how to use this cmdlet\n#>\nfunction ${1:Verb}-${2:Noun}\n{\n\t[CmdletBinding()]\n\t[OutputType([int])]\n\tParam\n\t(\n\t\t# Param1 help description\n\t\t[Parameter(Mandatory, ValueFromPipelineByPropertyName, Position=0)]\n\t\t\$Param1,\n\n\t\t# Param2 help description\n\t\t[int]\n\t\t\$Param2\n\t)\n\tBegin\n\t{$\n\t\t{3:# code}\n\t}\n\tProcess\n\t{\n\t}\n\tEnd\n\t{\n\t}\n}'
   'Switch':
     'prefix': 'switch'
     'body': 'switch (\$${1:x})\n{\n\t"value1" {}\n\t{\$_ -in "A","B","C"} {}\n\t"value3" {}\n\tDefault {}\n}'


Fix server crash

Fix server crash

--- server/imap/connection.coffee
+++ server/imap/connection.coffee
@@ -206,6 +206,7 @@
 # remove message from multiple boxes
 Imap::multiremove = (paths, callback) ->
     async.eachSeries paths, ({path, uid}, cb) =>
+        return cb new Error 'no message to remove' unless uid?
         @deleteMessageInBox path, uid, cb
     , callback


spec dragging a directory and files to a new directory this test guarantees that dragging a directory d and additional files f into a new directory will move the entire directory d discreetly along with any additional files f.

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -3700,6 +3700,41 @@
         runs ->
           expect(findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length).toBe 4

+    describe "when dropping a DirectoryView and FileViews onto a DirectoryView's header", ->
+      it "should move the files and directory to the hovered directory", ->
+        # Dragging alpha.txt and alphaDir into thetaDir
+        rootDir = $(treeView.roots[0])
+
+        alphaFile = rootDir[0].entries.children[2]
+        alphaDir = $(treeView.roots[0].entries).find('.directory:contains(alpha):first')
+        alphaDir[0].expand()
+
+        gammaDir = $(treeView.roots[0].entries).find('.directory:contains(gamma):first')
+        gammaDir[0].expand()
+        thetaDir = $(gammaDir[0].entries).find('.directory:contains(theta):first')
+
+        dragged = [alphaFile, alphaDir[0]]
+
+        [dragStartEvent, dragEnterEvent, dropEvent] =
+            eventHelpers.buildInternalDragEvents(dragged, thetaDir.find('.header')[0], thetaDir[0])
+
+        runs ->
+          treeView.onDragStart(dragStartEvent)
+          treeView.onDrop(dropEvent)
+          expect(thetaDir[0].children.length).toBe 2
+
+        waitsFor "directory view contents to refresh", ->
+          $(treeView.roots[0].entries).find('.directory:contains(theta):first .entry').length > 2
+
+        runs ->
+          thetaDir = $(gammaDir[0].entries).find('.directory:contains(theta):first')
+          thetaDir[0].expand()
+          expect(thetaDir.find('.entry').length).toBe 2
+          # alpha dir still has all its entries
+          alphaDir = $(thetaDir[0].entries).find('.directory:contains(alpha):first')
+          alphaDir[0].expand()
+          expect(alphaDir.find('.entry').length).toBe 2
+
     describe "when dropping a DirectoryView onto a DirectoryView's header", ->
       beforeEach ->
         waitForWorkspaceOpenEvent ->


Refactor coupon validation

Refactor coupon validation

--- src/smc-hub/client.coffee
+++ src/smc-hub/client.coffee
@@ -2023,28 +2023,14 @@
                 (cb) =>
                     if options.coupon
                         dbg("add coupon to customer history")
-                        async.series([
-                            (local_cb) =>
-                                dbg("retrieve the coupon")
-                                @_stripe.coupons.retrieve(options.coupon, local_cb)
-                            (local_cb) =>
-                                @database.get_coupon_history
-                                    account_id : @account_id
-                                    cb         : local_cb
-                        ], (err, [coupon, coupon_history]) =>
-                            if not coupon.valid
-                                cb("Coupon is no longer valid")
-
-                            times_used = coupon_history[coupon.id] ? 0
-                            if times_used >= (coupon.metadata.max_per_account ? 1)
-                                cb("Coupon used too many times")
-
-                            coupon_history[coupon.id] = times_used + 1
+                        @validate_coupon options.coupon, (err, coupon, coupon_history) =>
+                            if err
+                                cb(err)
+                            coupon_history[coupon.id] += 1
                             @database.update_coupon_history
                                 account_id     : @account_id
                                 coupon_history : coupon_history
                                 cb             : cb
-                        )
                 (cb) =>
                     dbg("add customer subscription to stripe")
                     @_stripe.customers.createSubscription customer_id, options, (err, s) =>
@@ -2146,29 +2132,41 @@
         if not @ensure_fields(mesg, 'coupon_id')
             dbg("missing field coupon_id")
             return
-
-        @stripe_get_customer mesg.id, (err, customer) =>
-            if err
-                return
-            async.waterfall([
-                (cb) =>
-                    dbg("retrieve the coupon")
-                    @_stripe.coupons.retrieve(mesg.coupon_id, cb)
-                (coupon, cb) =>
-                    dbg("check account coupon_history")
-                    @database.get_coupon_history
-                        account_id : @account_id
-                        cb         : (err, coupon_history) =>
-                            if coupon_history[mesg.coupon_id] >= (coupon.metadata.max_per_account ? 1)
-                                cb("You have already used this coupon the maximum number of times")
-                            else
-                                cb(undefined, coupon)
-            ], (err, coupon) =>
-                if err
-                    @stripe_error_to_client(id:mesg.id, error:err)
-                else
-                    @push_to_client(message.stripe_coupon(id:mesg.id, coupon:coupon))
-            )
+        @validate_coupon mesg.coupon_id, (err, coupon) =>
+            if err
+                @stripe_error_to_client(id:mesg.id, error:err)
+            else
+                @push_to_client(message.stripe_coupon(id:mesg.id, coupon:coupon))
+
+    # Checks these coupon criteria:
+    # - Exists
+    # - Is valid
+    # - Used by this account less than the max per account (hard coded default is 1)
+    # Calls cb(err, coupon, coupon_history)
+    validate_coupon: (coupon_id, cb) =>
+        dbg = @dbg("validate_coupon")
+        @_stripe = get_stripe()
+        async.series([
+            (local_cb) =>
+                dbg("retrieve the coupon")
+                @_stripe.coupons.retrieve(coupon_id, local_cb)
+            (local_cb) =>
+                dbg("check account coupon_history")
+                @database.get_coupon_history
+                    account_id : @account_id
+                    cb         : local_cb
+        ], (err, [coupon, coupon_history]) =>
+            if err
+                cb(err)
+            if not coupon.valid
+                cb("Sorry! This coupon has expired.")
+            times_used = coupon_history[coupon.id] ? 0
+            if times_used >= (coupon.metadata.max_per_account ? 1)
+                cb("You've already used this coupon.")
+
+            coupon_history[coupon.id] = times_used
+            cb(err, coupon, coupon_history)
+        )

     mesg_stripe_get_charges: (mesg) =>
         dbg = @dbg("mesg_stripe_get_charges")


Use a URL parameter as well as the referrer

--- _assets/javascripts/frontend/homepage.js.coffee
+++ _assets/javascripts/frontend/homepage.js.coffee
@@ -6,9 +6,22 @@
     width: screenWidth
     height: screenHeight

+urlParam = (target) ->
+  url    = window.location.search.substring(1)
+  params = url.split '&'
+
+  for param in params
+    values = param.split('=')
+
+    if values[0] == target
+      return values[1]
+
+  ''
+
 $ ->
   resize()
   $(window).resize resize

-  if document.referrer.match /flatterline/i
+  urlMatch = urlParam('ref')
+  if document.referrer.match(/flatterline/i) || urlMatch.match(/flatterline/i)
     $('#intro .hero h2').before "

Welcome Flatterline visitor!
We recently merged with another awesome development company and became Velocity Labs!
"


fixes lessthan3/packages#3816, dont clone password and services

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -643,6 +643,9 @@
             site.slug = dst_slug
             site.name = dst_slug
             site.settings.domain.url = "www.lessthan3.com/#{dst_slug}"
+            site.settings.security.password = ''
+            for k, v of site.settings.services
+              site.settings.services[k] = ''
             site.users[user.admin_uid] = 'admin'
             db.get('sites').insert site, (err, dst_site) ->
               exit err if err


variable name had changed

variable name had changed

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -697,7 +697,7 @@
           updated = false
           for src_id, dst_id of ids
             if data.indexOf(src_id) > -1
-              data = json.replace new RegExp(src_id, 'g'), dst_id
+              data = data.replace new RegExp(src_id, 'g'), dst_id
               updated = true

           if updated


Polling rerenders problem when individual queued items return

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -32,7 +32,8 @@

   queueing: =>
     @queued_items = @$(".xqueue")
-    if @queued_items.length > 0
+    @num_queued_items = @queued_items.length
+    if @num_queued_items > 0
       if window.queuePollerID # Only one poller 'thread' per Problem
         window.clearTimeout(window.queuePollerID)
       queuelen = @get_queuelen()
@@ -49,12 +50,16 @@

   poll: =>
     $.postWithPrefix "#{@url}/problem_get", (response) =>
-      @queued_items = $(response.html).find(".xqueue")
-      if @queued_items.length == 0
+      # If queueing status changed, then render
+      @new_queued_items = $(response.html).find(".xqueue")
+      if @new_queued_items.length isnt @num_queued_items
         @el.html(response.html)
         @executeProblemScripts () =>
           @setupInputTypes()
           @bind()
+
+      @num_queued_items = @new_queued_items.length
+      if @num_queued_items == 0
         delete window.queuePollerID
       else
         # TODO: Some logic to dynamically adjust polling rate based on queuelen


only read config if file exists

only read config if file exists

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -6,7 +6,6 @@
 coffeelint = require 'coffeelint'
 colors = require 'colors'
 columnify = require 'columnify'
-config = require '/u/config/dobi-server'
 clipboard = require('copy-paste').noConflict()
 crypto = require 'crypto'
 extend =  require 'node.extend'
@@ -66,7 +65,9 @@
 }
 XMLparser = new xml2js.Parser {explicitArray: false}

-
+config = {}
+if fs.existsSync '/u/config/dobi-server'
+  config = require '/u/config/dobi-server'

 connect = (next) ->
   login ({token, user}) ->


Teamwork: Introducing pane launcher for Teamwork.

--- client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkapp.coffee
+++ client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkapp.coffee
@@ -13,7 +13,6 @@
       shareSessionKeyInfo : "
This is your session key, you can share this key with your friends to work together.
"
       firebaseInstance    : instanceName
       sessionKey          : @getOptions().sessionKey
-      enableChat          : yes
       panelClass          : TeamworkPanel
       delegate            : this
       panels              : [
@@ -26,6 +25,7 @@
             callback      : (panel, workspace) => @showToolsModal panel, workspace
           }
         ]
+        floatingPanes     : [ "chat" , "terminal", "preview" ]
         layout            :
           direction       : "vertical"
           sizes           : [ "25%", null ]
@@ -36,21 +36,8 @@
               name        : "finder"
             }
             {
-              type        : "split"
-              options     :
-                direction : "horizontal"
-                sizes     : [ "50%", null ]
-                splitName : "InnerSplit"
-              views       : [
-                {
-                  type    : "tabbedEditor"
-                  name    : "editor"
-                }
-                {
-                  type    : "terminal"
-                  name    : "terminal"
-                }
-              ]
+              type        : "tabbedEditor"
+              name        : "editor"
             }
           ]
       ]


Teamwork: Refactored content management on playground selection.

--- client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkapp.coffee
+++ client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkapp.coffee
@@ -192,25 +192,35 @@
         KD.mixpanel "User Changed Playground", playground

         if contentDetails.type is "zip"
-          appStorage = KD.getSingleton("appStorageController").storage "Teamwork", "1.0"
-          appStorage.fetchStorage (storage) =>
-            version  = appStorage.getValue "#{playground}AppVersion"
-            if KD.utils.versionCompare manifest.version, "gt", version
-              FSHelper.exists "Web/Teamwork/#{playground}", KD.getSingleton("vmController").defaultVmName, (err, res) =>
-                return @setVMRoot "Web/Teamwork/#{playground}"  if res
-                if contentDetails.url
-                  @teamwork.importInProgress = yes
-                  @showImportWarning contentDetails.url, =>
-                    appStorage.setValue "#{playground}AppVersion", manifest.version
-                    @teamwork.emit "ContentImportDone"
-                    @teamwork.importModalContent = no
-                else
-                  warn "Missing url parameter to import zip file for #{name}"
-            else
-              KD.singletons.vmController.fetchVMs (err, res) =>
-                @setVMRoot "Web/Teamwork/#{playground}"
+          root            = "Web/Teamwork/#{playground}"
+          folder          = FSHelper.createFileFromPath root, "folder"
+          contentUrl      = contentDetails.url
+          manifestVersion = manifest.version
+
+          folder.exists (err, exists) =>
+            return @setUpImport contentUrl, manifestVersion, playground  unless exists
+
+            appStorage  = KD.getSingleton("appStorageController").storage "Teamwork", "1.0"
+            appStorage.fetchStorage (storage) =>
+              currentVersion  = appStorage.getValue "#{playground}PlaygroundVersion"
+              hasNewVersion   = KD.utils.versionCompare manifestVersion, "gt", currentVersion
+              if hasNewVersion
+                @setUpImport contentUrl, manifestVersion, playground
+              else
+                @setVMRoot root
         else
           warn "Unhandled content type for #{name}"
+
+  setUpImport: (url, version, playground) ->
+    unless url
+      return warn "Missing url parameter to import zip file for #{playground}"
+
+    @teamwork.importInProgress = yes
+    @showImportWarning url, =>
+      @teamwork.emit "ContentImportDone"
+      @teamwork.importModalContent = no
+      appStorage = KD.getSingleton("appStorageController").storage "Teamwork", "1.0"
+      appStorage.setValue "#{playground}PlaygroundVersion", version

   doCurlRequest: (path, callback = noop) ->
     KD.getSingleton("vmController").run "curl -kLs #{path}", (err, contents) =>


Attempt to resolve dma issue (not capturing)

--- src/01_tinycpu/features/dma.coffee
+++ src/01_tinycpu/features/dma.coffee
@@ -60,17 +60,33 @@
 		if @debug
 			console.log.apply(console, arguments)
 		return
+
+	isInRange: (loc) ->
+		if @rangeStart > 0 && @rangeEnd > 0
+			(loc >= @rangeStart && loc <= @rangeEnd)
+		else if @rangeStart < 0 && @rangeEnd < 0
+			(loc <= @rangeStart && loc >= @rangeEnd)
+		else
+			false

-	handle_read: (loc, cpu) ->
-		offset = @offset loc
-		result = @dma_read offset, cpu
-		log "dma_read(#{offset}) = #{result}"
+	handle_read: (loc, cpu, real_read) ->
+		match = @isInRange loc
+		if match
+			offset = @offset loc
+			result = @dma_read offset, cpu
+		else
+			result = real_read loc
+		log "dma_read(#{offset}) = #{result}", (match ? "(dma handled)" : "" )
 		result

-	handle_write: (loc, value, cpu) ->
-		offset = @offset loc
-		result = @dma_write offset, value, cpu
-		log "dma_write(#{offset}) = #{result}"
+	handle_write: (loc, value, cpu, real_write) ->
+		match = @isInRange loc
+		if match
+			offset = @offset loc
+			result = @dma_write offset, value, cpu
+		else
+			result = real_write loc, value
+		log "dma_write(#{offset}) = #{result}", (match ? "(dma handled)" : "" )
 		result

 	dma_read: (loc, cpu) ->
@@ -85,4 +101,4 @@
 exports[FEATURE_NAME]  = FEATURE_DMA
 exports[FEATURE_CLASS] = DMA
 exports.register_feature = register_range
-exports.find_feature = find_feature+exports.find_feature = find_feature


Changed wording for late reading progress acceptance button ("score" to "progress").

--- src/components/scores/late-work.cjsx
+++ src/components/scores/late-work.cjsx
@@ -72,9 +72,9 @@
     accepted:   "You accepted this student's late reading progress."
     pending:    "Reading progress after the due date"
   button: ->
-    additional: 'Accept new late score'
-    accepted:   'Use this score'
-    pending:    'Accept late score'
+    additional: 'Accept new late progress'
+    accepted:   'Use this progress'
+    pending:    'Accept late progress'
   body: ->
     null


Register helpers with Template.template.helpers()

--- lib/tap_i18n/tap_i18n-client.coffee
+++ lib/tap_i18n/tap_i18n-client.coffee
@@ -101,8 +101,10 @@
     # template specific helpers
     if package_name != globals.project_translations_domain
       # {{_ }}
-      if Template[template]?
-        Template[template][self.packages[package_name].helper_name] = underscore_helper
+      if Template[template]? and Template[template].helpers?
+        helpers = {}
+        helpers[self.packages[package_name].helper_name] = underscore_helper
+        Template[template].helpers(helpers)

     # global helpers
     else


Fix ban checkbox being passed into ban payload

--- app/javascript/legacy/moderation/ban.coffee
+++ app/javascript/legacy/moderation/ban.coffee
@@ -23,18 +23,13 @@
   modalForm.addEventListener "submit", (event) ->
     event.preventDefault();

-    checktostr = (el) ->
-      if el.checked
-        "1"
-      else
-        "0"
-
     data = {
-      ban: banCheckbox ? checktostr banCheckbox : false
+      ban: "0"
       user: modalForm.elements["user"].value
     }

     if banCheckbox && banCheckbox.checked
+      data.ban = "1"
       data.reason = modalForm.elements["reason"].value.trim()
       unless permabanCheckbox.checked
         data.duration = modalForm.elements["duration"].value.trim()


remove console.log

remove console.log

--- app/assets/javascripts/revised/sections/bike_search_bar.coffee
+++ app/assets/javascripts/revised/sections/bike_search_bar.coffee
@@ -43,7 +43,6 @@
     $('#search_distance').text($('#distance').val())
     $('#search_location').text(location)
     search_data = _.merge(window.interpreted_params, { location: location })
-    console.log search_data
     $.ajax
       type: 'GET'
       url: '/api/v3/search/count'


update hover reference crumb to current step as steps change

--- src/components/task/breadcrumbs.cjsx
+++ src/components/task/breadcrumbs.cjsx
@@ -69,6 +69,9 @@
   componentDidUpdate: (prevProps, prevState) ->
     @_resizeListener(@state) if @state.crumbsWidth isnt prevState.crumbsWidth

+  componentWillReceiveProps: (nextProps) ->
+    @setState(hoverCrumb: nextProps.currentStep)
+
   crumbMounted: ->
     @calculateCrumbsWidth() if @state.crumbsWidth?