Update signatures for pre-signed URLs

Update signatures for pre-signed URLs

--- test/services/s3.spec.coffee
+++ test/services/s3.spec.coffee
@@ -464,16 +464,16 @@

     it 'gets a signed URL for getObject', ->
       url = s3.getSignedUrl('getObject', Bucket: 'bucket', Key: 'key')
-      expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=900&Signature=gUAM0PxELXi27AXC0alWL%2BM2ehw%3D')
+      expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=900&Signature=uefzBaGpqvO9QhGtT%2BbYda0pgQY%3D')

     it 'gets a signed URL with Expires time', ->
       url = s3.getSignedUrl('getObject', Bucket: 'bucket', Key: 'key', Expires: 60)
-      expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=60&Signature=gUAM0PxELXi27AXC0alWL%2BM2ehw%3D')
+      expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=60&Signature=ZJKBOuhI99B2OZdkGSOmfG86BOI%3D')

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

     it 'gets a signed URL with callback', ->
       url = null
@@ -481,20 +481,20 @@
         s3.getSignedUrl 'getObject', Bucket: 'bucket', Key: 'key', (err, value) -> url = value
       waitsFor -> url
       runs ->
-        expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=900&Signature=gUAM0PxELXi27AXC0alWL%2BM2ehw%3D')
+        expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=900&Signature=uefzBaGpqvO9QhGtT%2BbYda0pgQY%3D')

     it 'gets a signed URL for putObject with no body', ->
       url = s3.getSignedUrl('putObject', Bucket: 'bucket', Key: 'key')
-      expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=900&Signature=bQNttlfmy9E7SHU0r6gUg7jaeig%3D')
+      expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=900&Signature=h%2FphNvPoGxx9qq2U7Zhbfqgi0Xs%3D')

     it 'gets a signed URL for putObject with special characters', ->
       url = s3.getSignedUrl('putObject', Bucket: 'bucket', Key: '!@#$%^&*();\':"{}[],./?`~')
-      expect(url).toEqual('https://bucket.s3.amazonaws.com/%21%40%23%24%25%5E%26%2A%28%29%3B%27%3A%22%7B%7D%5B%5D%2C./%3F%60~?AWSAccessKeyId=akid&Expires=900&Signature=O6r7DGjWkcqnDqwGUKmd0uy51sU%3D')
+      expect(url).toEqual('https://bucket.s3.amazonaws.com/%21%40%23%24%25%5E%26%2A%28%29%3B%27%3A%22%7B%7D%5B%5D%2C./%3F%60~?AWSAccessKeyId=akid&Expires=900&Signature=ymM%2F7R7Ri6bIXA7vrhzaaZgNRHg%3D')

     it 'gets a signed URL for putObject with a body (and checksum)', ->
       url = s3.getSignedUrl('putObject', Bucket: 'bucket', Key: 'key', Body: 'body')
-      expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Content-MD5=hBotaJrYa9FhFEdFPCLG%2FA%3D%3D&Expires=900&Signature=gABIG%2BdP5FI%2BE2DHuWU8tSoN6Qc%3D')
+      expect(url).toEqual('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Content-MD5=hBotaJrYa9FhFEdFPCLG%2FA%3D%3D&Expires=900&Signature=7%2BXiHEwB%2B3nSg2rhTyatSigkGPI%3D')

     it 'gets a signed URL and appends to existing query parameters', ->
       url = s3.getSignedUrl('listObjects', Bucket: 'bucket', Prefix: 'prefix')
-      expect(url).toEqual('https://bucket.s3.amazonaws.com/?prefix=prefix&AWSAccessKeyId=akid&Expires=900&Signature=%2FOeqjdbPbtyEni%2B4%2FNFD0mDzmzg%3D')
+      expect(url).toEqual('https://bucket.s3.amazonaws.com/?prefix=prefix&AWSAccessKeyId=akid&Expires=900&Signature=fWeCHJBop4LyDXm2%2F%2BvR%2BqzH5zk%3D')


typo fix

typo fix

--- test/unit/parser-test.coffee
+++ test/unit/parser-test.coffee
@@ -23,7 +23,7 @@
       before () ->
         output = parser.parseHeaders headerLines

-      describe "its retrun", () ->
+      describe "its return", () ->
         it "should be object", () ->
           assert.isObject output


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_levels.coffee
+++ src/engine_levels.coffee
@@ -62,7 +62,7 @@
   Reload the current level. Useful for retrying after a player dies.

-  engine.realodLevel
+  engine.reloadLevel()

   @name reloadLevel


Only close when bindTo is defined

Only close when bindTo is defined

We also use this from a Backbone View which isn't a Marionette view, so it does not have the bindTo

--- core/app/backbone/factlink/popover_mixin.coffee
+++ core/app/backbone/factlink/popover_mixin.coffee
@@ -37,7 +37,7 @@
     container = @popover_options.container || @$el

     @_popovers[selector] = { positionedRegion, container, view }
-    @bindTo view, 'close', => @popoverRemove(selector)
+    @bindTo? view, 'close', => @popoverRemove(selector)

     @_popoverBindAll() unless @isClosed
     @on 'render', @_popoverBindAll


add test

add test

--- test/printf.coffee
+++ test/printf.coffee
@@ -208,6 +208,7 @@
     printf('%.0O', test).replace(/\s+/g, ' ').should.eql '{ foo: [Object] }'
     printf('%#O', test.foo.isnot.array).should.eql '[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 ]'
     # Object inspect serialize object in different order when showHidden is true
+    printf('%+O', test.foo.is).should.eql '{ bar: \u001b[33mtrue\u001b[39m, baz: \u001b[33mfalse\u001b[39m }'
     return if semver.lt process.version, 'v0.9.0'
     printf('%O', test).replace(/\s+/g, ' ').should.eql '{ foo: { is: { bar: true, baz: false }, isnot: { array: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, [length]: 10 ] }, maybe: undefined } }'
     if semver.lt process.version, 'v8.0.0'


Pass the form, and re-init() on object change

I can't make angularjs directive synchronized to scope object, this seems to be a hack, but it works.

Also add form in the scope (which was not added...)

--- app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee
+++ app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee
@@ -3,6 +3,7 @@
   templateUrl: "admin/tags_input.html"
   scope:
     object: "="
+    form: "="
     tagsAttr: "@?"
     tagListAttr: "@?"
     findTags: "&"
@@ -18,7 +19,15 @@
       scope.limitReached = scope.object[scope.tagsAttr].length >= scope.max if scope.max != undefined
       scope.object[scope.tagListAttr] = (tag.text for tag in scope.object[scope.tagsAttr]).join(",")

+    scope.$watch "object", (newObject) ->
+      scope.object = newObject
+      init()
+
     $timeout ->
+      init()
+
+    init = ->
+      return unless scope.object
       # Initialize properties if necessary
       scope.tagsAttr ||= "tags"
       scope.tagListAttr ||= "tag_list"


[skip] Fixed rendering the roadmap at the end of a save (10m)

--- app/assets/javascripts/houston/roadmap/views/edit_project_roadmap_view.coffee
+++ app/assets/javascripts/houston/roadmap/views/edit_project_roadmap_view.coffee
@@ -76,7 +76,9 @@
         .success =>
           $buttons.prop('disabled', false)
           @milestones.url = "/roadmap/by_project/#{@projectSlug}"
-          @milestones.fetch(reset: true, parse: true)
+          @milestones.fetch
+            parse: true
+            wait: true
         .error (response)->
           $buttons.prop('disabled', false)
           errors = Errors.fromResponse(response)


Fix test

Fix test

--- app/models/User.coffee
+++ app/models/User.coffee
@@ -230,7 +230,8 @@
       when 4 then ['loop-da-loop', ['cell-commentary', 'kithgard-librarian', 'haunted-kithmaze', 'dread-door', 'closing-the-distance']]
       when 5 then ['haunted-kithmaze', ['cell-commentary', 'kithgard-librarian', 'loop-da-loop', 'dread-door', 'closing-the-distance']]
       when 6 then ['none', ['cell-commentary', 'kithgard-librarian', 'loop-da-loop', 'haunted-kithmaze', 'dread-door', 'closing-the-distance']]
-    skipTest = me.isAdmin() or me.isPremium() or me.isOnFreeOnlyServer() or me.isOnPremiumServer()
+      else ['control', []]
+    skipTest = me.isAdmin() or me.isPremium() or me.isOnFreeOnlyServer() or me.isOnPremiumServer() or not me.get('testGroupNumber')?
     if skipTest
       [@dungeonLevelsGroup, @dungeonLevelsHidden] = ['control', []]
     else


Separating one-liners - it looks nicer

Separating one-liners - it looks nicer

--- app/models/hand.coffee
+++ app/models/hand.coffee
@@ -17,7 +17,9 @@
     @cards = cards

   size: -> @cards.length
+
   is_bust: -> @score() > BLACKJACK
+
   is_blackjack: -> @score() == BLACKJACK

   add: (card) ->


Rethinking some item model stuff.

Rethinking some item model stuff.

--- app/models/item.coffee
+++ app/models/item.coffee
@@ -1,12 +1,19 @@
 Item = DS.Model.extend
+  blueprint: DS.belongsTo 'blueprint'
+  author: DS.belongsTo 'user'
   created_at: DS.attr 'date'
-  extension: DS.belongsTo 'extension'
-  blueprint_name: DS.attr 'string'
-  blueprint_slug: DS.attr 'string'
+  updated_at: DS.attr 'date'
+  published: DS.attr 'boolean'
+
+  validations:
+    extension:
+      presence: true
+    blueprint_slug:
+      presence: true

 Item.reopenClass
   FIXTURES: [
-    { id: 1, extension: 1, blueprint_name: 'blog', blueprint_slug: 'blog' }
+    { id: 1, blueprint: 1 }
   ]

 `export default Item`


Added the settings for the Meters.

Added the settings for the Meters.

--- widgets/fitbit/fitbit.coffee
+++ widgets/fitbit/fitbit.coffee
@@ -3,6 +3,7 @@
   ready: ->
     @visible = true
     @determineView()
+    @transformMeters()

   onData: (data) ->
     @currentView(@index).fadeOut() if @index
@@ -15,6 +16,19 @@
       @index = 0

     @determineView() if @visible
+
+  transformMeters: ->
+    meter = $(@node).find(".meter")
+    meter.attr "data-bgcolor", "#16b5b5"
+    meter.attr "data-fgcolor", "#fff"
+    meter.attr "data-width", "125"
+    meter.attr "data-height", "120"
+    meter.attr "data-thickness", ".3"
+    meter.attr "data-angleArc", "250"
+    meter.attr "data-angleOffset", "-125"
+    meter.attr "data-displayInput", "false"
+    meter.attr "data-readOnly", "true"
+    meter.knob()

   determineView: ->
     if @error


Show only finished repositories on landing page

--- app/routes/home.coffee
+++ app/routes/home.coffee
@@ -9,7 +9,9 @@
       isLoadedBinding: 'repos.isLoaded'
       repos: @store.filter 'repo', (repo) ->
         buildId = repo.get('lastBuildId')
-        store.hasRecordForId('build', buildId)
+        if store.hasRecordForId('build', buildId)
+          state = repo.get('lastBuild.state')
+          state == 'passed' || state == 'failed'
       sorted: Ember.computed.sort('repos', 'sortedReposKeys')
       content: limit('sorted', 'limit')
       sortedReposKeys: ['sortOrder:asc']
@@ -40,7 +42,7 @@
     if @get('repos.length') < 3
       return true

-    if event == 'build:started' && @get('letMoreReposThrough')
+    if event == 'build:finished' && @get('letMoreReposThrough')
       @set('letMoreReposThrough', false)
       return true


redirect user to manage-group-funds-page if people null

--- app/components/review-bulk-allocation-page/review-bulk-allocation-page.coffee
+++ app/components/review-bulk-allocation-page/review-bulk-allocation-page.coffee
@@ -9,12 +9,13 @@
   params:
     people: null
     groupId: null
-  controller: (config, CurrentUser, Error, LoadBar, Dialog, Records, $scope, $stateParams, $timeout, UserCan) ->
-
-    # TODO: need to redirect if people is null
+  controller: (config, CurrentUser, Error, LoadBar, $location, Dialog, Records, $scope, $stateParams, $timeout, UserCan) ->

     LoadBar.start()
     groupId = parseInt($stateParams.groupId)
+    unless $stateParams.people
+      $location.path("/groups/#{groupId}/manage_funds")
+
     Records.groups.findOrFetchById(groupId)
       .then (group) ->
         LoadBar.stop()


Updated jade to compile pretty formatted markup by default.

--- src/plugins/jade-template.coffee
+++ src/plugins/jade-template.coffee
@@ -25,6 +25,7 @@
       try
         rv = jade.compile buffer.toString(),
           filename: fullpath
+          pretty: true
         callback null, new JadeTemplate rv
       catch error
         callback error


Allow for multiple-element case in the transclude of pdf

--- app/assets/asset/pdfViewer.coffee
+++ app/assets/asset/pdfViewer.coffee
@@ -13,7 +13,7 @@
       objectTag.setAttribute('width', '80%')
       objectTag.setAttribute('type', $scope.asset.format.mimetype)
       $transclude (elem) ->
-        objectTag.appendChild elem[0]
+        _.each elem, (i) -> objectTag.appendChild i
       pdfElement.appendChild(objectTag)
       return


replaced coffeescript with fusion (copy paste legacy :)

--- src/command.coffee
+++ src/command.coffee
@@ -11,7 +11,7 @@

 # The list of all the valid option flags that 'fusion' knows how to handle.
 SWITCHES = [
-  ['-v', '--version',                     'display CoffeeScript version']
+  ['-v', '--version',                     'display fusion version']
   ['-h', '--help',                        'display this help message']
   ['-i', '--input [DIR]',                 'set input path of templates']
   ['-o', '--output [FILE]',               'set output path of templates']


выбирается россия на карте

выбирается россия на карте

--- app/assets/javascripts/map.coffee
+++ app/assets/javascripts/map.coffee
@@ -1,6 +1,7 @@
 $ ->
   map.init()
   map.show_places()
+  map.color_countries()

 map =
   #параметры карты
@@ -8,7 +9,7 @@
   options:
     center: app.current_location,
     disableDefaultUI: true,
-    zoom: 12
+    zoom: 3

   show_places: ->
     #пытается разместить окно по центру текущего местоположения, а также отображает места
@@ -29,6 +30,19 @@

         #TODO: сделать так, чтобы места появлялись на карте даже в случае отсутствия текущей геолокации
+  color_countries: ->
+    #окрашивает страны
+    CountryName = "Russia"
+
+    table_id = 420419
+
+    FT_Query = "SELECT 'kml_4326' FROM #{table_id} WHERE 'name_0' = '#{CountryName}';"
+    FT_Options =
+      query: FT_Query
+
+    layer = new google.maps.FusionTablesLayer(table_id, FT_Options)
+    layer.setMap app.google_map
+

   init: ->
     #инициализация карты
@@ -58,7 +72,9 @@
     marker = new google.maps.Marker(place_marker_options)

     place_infobox = new InfoBox(infobox_options)
-    infobox_content = "
" + place.type + ": " + place.name + "

Расстояние: " + place.distance.toFixed(1) + " км
"
+    infobox_content = "
+
#{place.type} : #{place.name}

+
Расстояние: #{place.distance.toFixed(1)} км
"

     google.maps.event.addListener marker, 'mouseover', ->
       place_infobox.setContent(infobox_content)


removed unnecessary hashbang

removed unnecessary hashbang

--- src/complex.coffee
+++ src/complex.coffee
@@ -1,5 +1,3 @@
-#!/usr/bin/env coffee
-
 exports.Complex = class Complex

   constructor: (@re = 0, @im = 0) ->


Modified weather regex

Modified weather regex

--- src/scripts/f-ing-weather.coffee
+++ src/scripts/f-ing-weather.coffee
@@ -25,7 +25,7 @@
       cb(temp, remark, flavor)

 module.exports = (robot) ->
-  robot.respond /(what's|what is) the weather for (.*)/i, (msg) ->
+  robot.respond /(what's|what is) the weather( for (.*))?/i, (msg) ->
     city = msg.match[2] || "Ithaca, NY"
     weather msg, city, (temp, remark, flavor) ->
       out = temp + " degrees " + remark + " " + flavor


Don't use page number in links to other versions of book (#1600)

--- src/scripts/helpers/links.coffee
+++ src/scripts/helpers/links.coffee
@@ -59,7 +59,7 @@

       if model.isBook?()
         title = trim(model.get('currentPage')?.get('title'))
-        page = ":#{model.getPageNumber()}"
+        page = ":#{model.get('currentPage')?.getShortUuid()}"

       return "#{settings.root}contents/#{id}#{page}/#{title}"


More tutorial

More tutorial

--- src/scripts/tutorialSteps.coffee
+++ src/scripts/tutorialSteps.coffee
@@ -17,6 +17,7 @@
         'In this sound we\'ll hear three sounds.'
       ]
       attach: x: 'left', y: 'bottom', to: '.interface .controls .play', at: x: 'left', y: 'top'
+      nextOn: click: '.play'

     new Step
       content: [
@@ -25,6 +26,7 @@
         'Drag vertically from the top of the mark to the bottom to select the frequency range.'
       ]
       attach: y: 'top', to: '.spectrogram img', at: y: 'top'
+      nextOn: mouseup: '.spectrogram'

     new Step
       content: [
@@ -32,6 +34,7 @@
         'Drag horizontally from the start of the first sound to the end of the first sound.'
       ]
       attach: x: 'left', to: '.spectrogram img', at: x: 'left'
+      nextOn: mouseup: '.spectrogram'

     new Step
       content: [
@@ -39,6 +42,7 @@
         'Drag horizontally from the start of the second sound to the end of the second sound.'
       ]
       attach: to: '.spectrogram img'
+      nextOn: mouseup: '.spectrogram'

     new Step
       content: [
@@ -47,36 +51,46 @@
         'From the frequency range, we can guess that this is a bat. Click the "Bat calls" tab on the field guide.'
       ]
       attach: y: 'bottom', to: '.field-guide a[href="#!/classify/bat-calls"]', at: y: 'top'
+      nextOn: click: '.field-guide a[href="#!/classify/bat-calls"]'

     new Step
+      delay: 100
       content: [
-        'Since these marks are shaped like hockey sticks, let\'s click to see an example.'
+        'Since these marks are shaped like horizontal hockey sticks, let\'s click to see an example.'
       ]
       attach: x: 'right', to: '.field-guide a[href="#!/classify/bat-calls/searching/horizontal-hockey-stick"]', at: x: 'left'
+      nextOn: click: '.field-guide a[href="#!/classify/bat-calls/searching/horizontal-hockey-stick"]'

     new Step
+      delay: 100
       content: [
         'And let\'s play it to see what it sounds like.'
       ]
       attach: x: 'right', to: '.field-guide [data-page="horizontal-hockey-stick"] .play', at: x: 'left'
+      nextOn: click: '.field-guide [data-page="horizontal-hockey-stick"] .play'

     new Step
       content: [
         'Now we can identify this as a bat...'
       ]
       attach: x: 'right', to: '.interface .answer:contains("Bat Call")', at: x: 'left'
+      nextOn: click: '.answer:contains("Bat Call")'

     new Step
+      delay: 100
       content: [
         '...and a searching call...'
       ]
-      attach: to: '.interface .answer:contains("Searching")'
+      attach: x: 'right', to: '.interface .answer:contains("Searching")', at: x: 'left'
+      nextOn: click: '.answer:contains("Searching")'

     new Step
+      delay: 100
       content: [
         '...and let\'s save this one.'
       ]
-      attach: to: '.interface .answer:contains("Save selection")'
+      attach: x: 'right', to: '.interface .answer:contains("Save selection")', at: x: 'left'
+      nextOn: click: '.interface .answer:contains("Save selection")'

     new Step
       content: [
@@ -84,35 +98,42 @@
         'Select the frequency range by dragging from the top of the mark to the bottom.'
       ]
       attach: to: '.spectrogram img'
+      nextOn: mouseup: '.spectrogram'

     new Step
       content: [
         'And then drag from the start of the sound to the end.'
       ]
-      attach: x: 'left', to: '.spectrogram img', at: x: 'left'
+      attach: to: '.spectrogram img'
+      nextOn: mouseup: '.spectrogram'

     new Step
       content: [
         'Look through the field guide. It looks and sounds like this is an insect!'
       ]
-      attach: to: '.field-guide a[href="#!/classify/insect-noise"]'
+      attach: y: 'bottom', to: '.field-guide a[href="#!/classify/insect-noise"]', at: y: 'top'
+      nextOn: click: '.field-guide a[href="#!/classify/insect-noise"]'

     new Step
       content: [
         'Select insect...'
       ]
       attach: x: 'right', to: '.interface .answer:contains("Insect")', at: x: 'left'
+      nextOn: click: '.answer:contains("Insect")'

     new Step
+      delay: 100
       content: [
-        '...then "done".'
+        '...then "save selection".'
       ]
-      attach: x: 'right', to: '.interface .answer:contains("Save Selection")', at: x: 'left'
+      attach: x: 'right', to: '.interface .answer:contains("Save selection"):last', at: x: 'left'
+      nextOn: click: '.answer:contains("Save selection")'

     new Step
       content: [
         'Now you\'re ready to investigate on your own.'
-        'Click "Next" to begin'
+        'Click "next sound" to begin'
       ]
       attach: to: '.spectrogram img'
+      nextOn: click: '.next-subject'
   ]


Also track direct url route based addServices

--- src/control.coffee
+++ src/control.coffee
@@ -3,14 +3,16 @@
     'backbone.marionette',
     'URI',
     'cs!app/base',
-    'cs!app/models'
+    'cs!app/models',
+    'cs!app/analytics'
 ],
 (
     $,
     Marionette,
     URI,
     sm,
-    Models
+    Models,
+    Analytics
 ) ->

     PAGE_SIZE = appSettings.page_size
@@ -258,6 +260,9 @@
                         error: =>
                             deferred.resolve()

+        addServices: (services) ->
+            sm.resolveImmediately()
+
         setService: (service) ->
             @services.set []
             @addService service
@@ -303,10 +308,27 @@
                 sm.resolveImmediately()

         renderUnitsByServices: (serviceIdString, queryParameters) ->
+            municipalityIds = queryParameters?.municipality?.split ','
+
             serviceIds = serviceIdString.split ','
-            municipalityIds = queryParameters?.municipality?.split ','
-            deferreds = _.map serviceIds, (id) =>
-                @addService new models.Service(id: id), municipalityIds
+            services = _.map serviceIds, (id) -> new models.Service id: id
+
+            serviceDeferreds = _.map services, (service) ->
+                return sm.withDeferred (deferred) ->
+                    service.fetch
+                        data: include: 'ancestors'
+                        success: -> deferred.resolve(service)
+                        error: -> deferred.fail()
+
+            deferreds = _.map services, -> $.Deferred()
+            $.when(serviceDeferreds...).done (serviceObjects...) =>
+                _.each serviceObjects, (srv, idx) =>
+                    # trackCommand needs to be called manually since
+                    # commands don't return promises so
+                    # we need to call @addService directly
+                    Analytics.trackCommand 'addService', [srv]
+                    @addService(srv, municipalityIds).done ->
+                        deferreds[idx].resolve()
             return $.when deferreds...

         _fetchDivisions: (divisionIds, callback) ->


focus on click down, so click and select works in jupyter

--- src/smc-webapp/jupyter/cell.cjsx
+++ src/smc-webapp/jupyter/cell.cjsx
@@ -168,7 +168,7 @@
         # Note that the cell id is used for the cell-list.cjsx scroll functionality.
         <div
             style     = {style}
-            onMouseUp = {@click_on_cell if not @props.is_current}
+            onMouseDown = {@click_on_cell if not @props.is_current}
             onDoubleClick = {@double_click}
             id        = {@props.id}
         >


Precompute contentWidth

Precompute contentWidth

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -44,6 +44,7 @@
   observeModel: ->
     @disposables.add @model.onDidChange =>
       @updateContentHeight()
+      @updateContentWidth()

       @updateHeightState()
       @updateVerticalScrollState()
@@ -91,6 +92,7 @@
   updateState: ->
     @updateContentHeight()
     @updateHeight()
+    @updateContentWidth()

     @updateHeightState()
     @updateVerticalScrollState()
@@ -349,7 +351,7 @@
     Math.min(@model.getScreenLineCount(), endRow)

   computeScrollWidth: ->
-    Math.max(@computeContentWidth(), @computeClientWidth())
+    Math.max(@contentWidth, @computeClientWidth())

   computeScrollHeight: ->
     contentHeight = @contentHeight
@@ -358,10 +360,9 @@
       contentHeight += extraScrollHeight if extraScrollHeight > 0
     Math.max(contentHeight, @height)

-  computeContentWidth: ->
-    contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left
-    contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
-    contentWidth
+  updateContentWidth: ->
+    @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left
+    @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width

   updateContentHeight: ->
     @contentHeight = @lineHeight * @model.getScreenLineCount()
@@ -392,15 +393,14 @@
       Math.max(0, scrollLeft) if scrollLeft?

   computeHorizontalScrollbarHeight: ->
-    contentWidth = @computeContentWidth()
     clientWidthWithoutVerticalScrollbar = @contentFrameWidth
     clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @verticalScrollbarWidth
     clientHeightWithoutHorizontalScrollbar = @height
     clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @horizontalScrollbarHeight

     horizontalScrollbarVisible =
-      contentWidth > clientWidthWithoutVerticalScrollbar or
-        contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar
+      @contentWidth > clientWidthWithoutVerticalScrollbar or
+        @contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar

     if horizontalScrollbarVisible
       @horizontalScrollbarHeight
@@ -408,7 +408,6 @@
       0

   computeVerticalScrollbarWidth: ->
-    contentWidth = @computeContentWidth()
     clientWidthWithoutVerticalScrollbar = @contentFrameWidth
     clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @verticalScrollbarWidth
     clientHeightWithoutHorizontalScrollbar = @height
@@ -416,7 +415,7 @@

     verticalScrollbarVisible =
       @contentHeight > clientHeightWithoutHorizontalScrollbar or
-        @contentHeight > clientHeightWithHorizontalScrollbar and contentWidth > clientWidthWithoutVerticalScrollbar
+        @contentHeight > clientHeightWithHorizontalScrollbar and @contentWidth > clientWidthWithoutVerticalScrollbar

     if verticalScrollbarVisible
       @verticalScrollbarWidth
@@ -606,6 +605,8 @@
     @characterWidthsChanged() unless @batchingCharacterMeasurement

   characterWidthsChanged: ->
+    @updateContentWidth()
+
     @updateHorizontalScrollState()
     @updateContentState()
     @updateDecorations()


don't show internal <br> tags

don't show internal <br> tags

--- src/textorum/tinymce/tree.coffee
+++ src/textorum/tinymce/tree.coffee
@@ -89,6 +89,8 @@
         id = node.getAttribute('id')
       while depth < (holder.length - 1)
         holder.shift()
+      if not node.getAttribute('data-xmlel') && node.localName == "br"
+        return false
       title = node.getAttribute('data-xmlel') || ("[" + node.localName + "]")
       if nodeTitleCallback
         additional = nodeTitleCallback(node, title)


adding constructor tags to docs

adding constructor tags to docs

--- src/durable.coffee
+++ src/durable.coffee
@@ -5,6 +5,7 @@

 @name Durable
 @module
+@constructor

 @param {Object} I Instance variables
 ###


default screen size change

default screen size change

--- src/environ.coffee
+++ src/environ.coffee
@@ -8,8 +8,8 @@

 # static values setting ***********************************************************************************
 DEBUG           = true                      # デバッグモード
-SCREEN_WIDTH    = 1080                      # 画面の幅
-SCREEN_HEIGHT   = 1920                      # 画面の高さ
+SCREEN_WIDTH    = 800                       # 画面の幅
+SCREEN_HEIGHT   = 1280                      # 画面の高さ
 BGCOLOR         = "black"                   # 背景色
 OBJECTNUM       = 256                       # キャラの最大数
 FPS             = 60                        # FPS


Don't use deprecated [l]statSyncNoException methods in Electron >= 3

--- src/fs-plus.coffee
+++ src/fs-plus.coffee
@@ -552,8 +552,30 @@
   # Returns `true` if case sensitive, `false` otherwise.
   isCaseSensitive: -> not fsPlus.isCaseInsensitive()

+  # Public: Calls `fs.statSync`, catching all exceptions raised. This
+  # method calls `fs.statSyncNoException` when provided by the underlying
+  # `fs` module (Electron < 3.0).
+  #
+  # Returns `fs.Stats` if the file exists, `undefined` otherwise.
+  statSyncNoException: (args...) ->
+    statSyncNoException(args...)
+
+  # Public: Calls `fs.lstatSync`, catching all exceptions raised.  This
+  # method calls `fs.statSyncNoException` when provided by the underlying
+  # `fs` module (Electron < 3.0).
+  #
+  # Returns `fs.Stats` if the file exists, `undefined` otherwise.
+  lstatSyncNoException: (args...) ->
+    lstatSyncNoException(args...)
+
+# Built-in [l]statSyncNoException methods are only provided in
+# Electron releases before 3.0.
+isElectron3OrHigher =
+  process.versions.electron &&
+  parseInt(process.versions.electron.split('.')[0]) >= 3
+
 statSyncNoException = (args...) ->
-  if fs.statSyncNoException
+  if fs.statSyncNoException and !isElectron3OrHigher
     fs.statSyncNoException(args...)
   else
     try
@@ -562,7 +584,7 @@
       false

 lstatSyncNoException = (args...) ->
-  if fs.lstatSyncNoException
+  if fs.lstatSyncNoException and !isElectron3OrHigher
     fs.lstatSyncNoException(args...)
   else
     try


Use : for nested properties

Use : for nested properties

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -68,11 +68,10 @@
   #             tokenizing the first line in the file.
   #
   # Returns an {Object} containing `tokens` and `ruleStack` properties:
-  #   token     - An {Array} of tokens covering the entire line of text.
-  #
-  #   ruleStack - An {Array} of rules representing the tokenized state at the
-  #               end of the line. These should be passed back into this method
-  #               when tokenizing the next line in the file.
+  #   :token     - An {Array} of tokens covering the entire line of text.
+  #   :ruleStack - An {Array} of rules representing the tokenized state at the
+  #                end of the line. These should be passed back into this method
+  #                when tokenizing the next line in the file.
   tokenizeLine: (line, ruleStack, firstLine=false) ->
     if ruleStack?
       ruleStack = ruleStack.slice()


activity_scores graph: fix markings initialization

--- static/js/activity_scores.coffee
+++ static/js/activity_scores.coffee
@@ -84,7 +84,7 @@
                     to: @avg_bin_score
             ]
         else
-            marking = []
+            markings = []

         @plot_options =
             colors: colors


hooks can be pinged via `POST .../hooks/:id/pings`

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -100,6 +100,7 @@
         | hooks
         | hooks /[^/]+
         | hooks /[^/]+ /tests
+        | hooks /[^/]+ /pings
         | assignees
         | languages
         | teams
@@ -301,6 +302,7 @@
       'statuses'    : false
     'hooks':
       'tests'       : false
+      'pings'       : false
     'assignees'     : false
     'languages'     : false
     'teams'         : false


Account for open scopes in tokens shim result

Since this has overhead, I’ve also added a compatibilityMode parameter
that will disable the special result shim.

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -100,12 +100,18 @@
   # * `ruleStack` An {Array} of rules representing the tokenized state at the
   #   end of the line. These should be passed back into this method when
   #   tokenizing the next line in the file.
-  tokenizeLine: (line, ruleStack, firstLine=false) ->
+  tokenizeLine: (line, ruleStack, firstLine=false, compatibilityMode=true) ->
     tags = []

     if ruleStack?
       ruleStack = ruleStack.slice()
-    else
+      if compatibilityMode
+        openScopeTags = []
+        for {scopeName, contentScopeName} in ruleStack
+          openScopeTags.push(@registry.idForScope(scopeName)) if scopeName
+          openScopeTags.push(@registry.idForScope(contentScopeName)) if contentScopeName
+    else
+      openScopeTags = [] if compatibilityMode
       initialRule = @getInitialRule()
       ruleStack = [initialRule]
       tags.push(@idForScope(initialRule.scopeName)) if initialRule.scopeName
@@ -174,7 +180,11 @@
             break

     rule.clearAnchorPosition() for rule in ruleStack
-    new TokenizeLineResult(line, tags, ruleStack, @registry)
+
+    if compatibilityMode
+      new TokenizeLineResult(line, openScopeTags, tags, ruleStack, @registry)
+    else
+      {line, tags, ruleStack}

   activate: ->
     @registration = @registry.addGrammar(this)
@@ -290,7 +300,7 @@
     EmitterMixin::on.apply(this, arguments)

 class TokenizeLineResult
-  constructor: (@line, @tags, @ruleStack, @registry) ->
+  constructor: (@line, @openScopeTags, @tags, @ruleStack, @registry) ->

   Object.defineProperty @prototype, 'tokens', get: ->
-    @registry.decodeTokens(@line, @tags)
+    @registry.decodeTokens(@line, @tags, @openScopeTags)


fix big in Mailbox.fromSeed

fix big in Mailbox.fromSeed

--- src/mailbox.coffee
+++ src/mailbox.coffee
@@ -29,7 +29,7 @@
   # well-known seed.
   @fromSeed: (seed, id = seed, strMasterKey = null) ->
     mbx = new MailBox(id, strMasterKey)
-    mbx.keyRing.commFromSeed(id)
+    mbx.keyRing.commFromSeed(seed)
     mbx._hpk = null
     return mbx


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -121,7 +121,7 @@
   @param {String} borderColor Color of the meter's border
   @param {Number} borderWidth Width of the meter's border
   @param {String} color Color of the meter's inner rectangle
-    nameColor - Color of the property name displayed above the meter
+  @param {String} nameColor Color of the property name displayed above the meter
     font - Size and style of the meter's font
     height - Height of the meter
     position - An x, y object representing the position of the meter


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -18,7 +18,7 @@
         width
       } = meterData

-      if
+      if I[]

       ratio = I[name] / I["max#{name.capitalize()}"]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -127,7 +127,7 @@
   @param {Object} position An x, y object representing the position of the meter
   @param {Number} radius Border radius of the meter
   @param {Boolean} showName Boolean to toggle whether or not to show the attribute associated with the meter
-    text - A String to display over the meter. Overrides default name attribute
+  @param {String} text - A String to display over the meter. Overrides default name attribute
     width - How wide the meter is
   ###
   meter: (options={}) ->


Add 'method end' log message for native methods.

--- src/methods.coffee
+++ src/methods.coffee
@@ -115,10 +115,14 @@
       native_methods[sig](runtime_state)
       s = runtime_state.meta_stack.pop().stack
       switch s.length
-        when 2 then runtime_state.push s[0], s[1]; return
-        when 1 then runtime_state.push s[0]; return
-        when 0 then return
-      throw "too many items on the stack after native method #{sig}"
+        when 2 then runtime_state.push s[0], s[1]
+        when 1 then runtime_state.push s[0]
+        when 0 then break
+        else
+          throw "too many items on the stack after native method #{sig}"
+      cf = runtime_state.curr_frame()
+      console.log "stack: [#{cf.stack}], local: [#{cf.locals}] (method end)"
+      return
     code = @get_code().opcodes
     while true
       cf = runtime_state.curr_frame()
@@ -137,4 +141,4 @@
         break
       unless op instanceof opcodes.BranchOpcode
         runtime_state.inc_pc(1 + op.byte_count)  # move to the next opcode
-    console.log "stack: [#{cf.stack}], local: [#{cf.locals}] (method end)"+    console.log "stack: [#{cf.stack}], local: [#{cf.locals}] (method end)"


concision

concision

--- src/methods.coffee
+++ src/methods.coffee
@@ -185,10 +185,10 @@
         unless caller_stack.length-@param_bytes >= 0 and obj?
           util.java_throw runtime_state, 'java/lang/NullPointerException',
             "null 'this' in virtual lookup for #{sig}"
-        m_spec = {class: obj.type.toClassString(), sig: @name + @raw_descriptor}
-        m = runtime_state.method_lookup(m_spec)
-        #throw "abstract method got called: #{@name}#{@raw_descriptor}" if m.access_flags.abstract
-        return m.run(runtime_state)
+        return runtime_state.method_lookup({
+            class: obj.type.toClassString(),
+            sig: @name + @raw_descriptor
+          }).run(runtime_state)
       params = @take_params caller_stack
       ms.push(new runtime.StackFrame(this,params,[]))
     padding = (' ' for [2...ms.length()]).join('')


threadSeqNumber should be a long.

threadSeqNumber should be a long.

--- src/natives.coffee
+++ src/natives.coffee
@@ -316,7 +316,7 @@
               # have to run the private ThreadGroup constructor
               rs.method_lookup({class: 'java/lang/ThreadGroup', sig: '<init>()V'}).run(rs)
               rs.main_thread = rs.init_object 'java/lang/Thread', { priority: 1, group: g_ref, threadLocals: 0 }
-              rs.push 0  # set up for static_put
+              rs.push gLong.ZERO, null  # set up for static_put
               rs.static_put {class:'java/lang/Thread', name:'threadSeqNumber'}
             rs.main_thread
         o 'setPriority0(I)V', (rs) -> # NOP


:art: package activation

:art: package activation

--- src/package.coffee
+++ src/package.coffee
@@ -330,10 +330,8 @@
     @uriHandlerSubscription?.dispose()

   handleURI: (methodName, args) ->
-    @activate().then =>
-      @mainModule[methodName]?.apply(@mainModule, args)
-    unless @mainActivated
-      @activateNow()
+    @activate().then => @mainModule[methodName]?.apply(@mainModule, args)
+    @activateNow() unless @mainActivated

   registerTranspilerConfig: ->
     if @metadata.atomTranspilers


Use error field as message when available

--- src/publish.coffee
+++ src/publish.coffee
@@ -124,7 +124,7 @@
         if error?
           callback(error)
         else if response.statusCode isnt 201
-          message = body.message ? body
+          message = body.message ? body.error ? body
           callback("Registering package failed: #{message}")
         else
           callback()
@@ -153,7 +153,7 @@
         if error?
           callback(error)
         else if response.statusCode isnt 201
-          message = body.message ? body
+          message = body.message ? body.error ? body
           callback("Creating new version failed: #{message}")
         else
           callback()


Added sender & receiver

Added sender & receiver

Added sender & receiver

--- src/scraper.coffee
+++ src/scraper.coffee
@@ -113,9 +113,12 @@
                 children.push
                   comment: $(".comment-renderer-text-content", @).text()
                   like: check_like_score $(".comment-renderer-like-count.off", @).text()
+                  author: $(@).data("author-name")
+                  receiver: $(".comment-renderer-text-content", @).find("a").text()

               res.push
                 root: $(".comment-renderer-text-content", root).text()
+                author: root.data("author-name")
                 like: check_like_score $(".comment-renderer-like-count.off", root).text()
                 children: children


zThunk: fix mount/unmount hook order again

--- src/z_thunk.coffee
+++ src/z_thunk.coffee
@@ -31,8 +31,9 @@
       mountQueueCnt = 0
       unmountQueueCnt = 0
       mountedEl = null
+      isMounted = false
       runHooks = =>
-        $el = mountedEl
+        wasMounted = isMounted

         if mountQueueCnt > unmountQueueCnt + 1
           throw new Error "Component '#{@component.constructor?.name}'
@@ -41,11 +42,16 @@
         if unmountQueueCnt > 0
           @component.beforeUnmount?()
           @component.__disposable?.dispose()
-          mountedEl = null
+          isMounted = false

-        if mountQueueCnt > 0 and mountQueueCnt > unmountQueueCnt
+        # basic if mounts > unmounts but also
+        # if component is mounted and unmountCnt == mountCnt, re-mount it!
+        if mountQueueCnt > 0 and \
+            (mountQueueCnt > unmountQueueCnt or
+            wasMounted and mountQueueCnt is unmountQueueCnt)
           @component.__disposable = state?.subscribe dirty
-          @component.afterMount?($el)
+          @component.afterMount?(mountedEl)
+          isMounted = true

         unmountQueueCnt = 0
         mountQueueCnt = 0


app-tab-view: Fixed merge conflicts.

app-tab-view: Fixed merge conflicts.

--- client/landing/app/CommonViews/applicationview/applicationtabview.coffee
+++ client/landing/app/CommonViews/applicationview/applicationtabview.coffee
@@ -14,9 +14,9 @@
     appManager        = KD.getSingleton 'appManager'
     @isSessionEnabled = options.saveSession and options.sessionName

-	@initSession() if @isSessionEnabled
+    @initSession() if @isSessionEnabled

-    @on "PaneAdded", =>
+    @on "PaneAdded", (pane) =>
       @tabHandleContainer.repositionPlusHandle @handles
       @updateSession() if @isSessionEnabled and @sessionData


Fix : update ComposeView when MailsInput change

--- client/app/components/mails_input.coffee
+++ client/app/components/mails_input.coffee
@@ -26,7 +26,7 @@
         state.selected = 0
         state.open     = false
         state.focus    = !!state.known.length
-        return state
+        state

     # Code from the StoreWatch Mixin. We don't use the mixin
     # because we store other things into the state
@@ -38,16 +38,13 @@

     _setStateFromStores: -> @setState @getStateFromStores()

-    shouldComponentUpdate: (nextProps, nextState) ->
-        return not(_.isEqual(nextState, @state)) or
-            not (_.isEqual(nextProps, @props))
-
     render: ->
+        @props.valueLink.requestChange @state.known
+
         renderTag = (address, idx) =>
             remove = =>
-                known = @state.known.filter (a) ->
-                    return a.address isnt address.address
-                @props.valueLink.requestChange known
+                @setState known: @state.known.filter (a) ->
+                    a.address isnt address.address

             onDragStart = (event) =>
                 event.stopPropagation()
@@ -61,8 +58,7 @@
                     event.dataTransfer.setData @props.id, true

             onDragEnd = (event) ->
-                if event.dataTransfer.dropEffect is 'move'
-                    remove()
+                remove() if event.dataTransfer.dropEffect is 'move'

             if address.name? and address.name.trim() isnt ''
                 display = address.name
@@ -90,12 +86,12 @@
         onChange = (event) =>
             value = event.target.value.split ','
             if value.length is 2
-                known = _.clone @state.known
-                known.push(MessageUtils.parseAddress value[0])
-                @props.valueLink.requestChange known
-                @setState unknown: value[1].trim()
-            else
-                @setState unknown: event.target.value
+                @setState
+                    known: MessageUtils.parseAddress value[0]
+                    unknown: value[1].trim()
+            else
+                @setState
+                    unknown: event.target.value

         onInput = (event) =>
             input = @refs.contactInput.getDOMNode()
@@ -251,22 +247,12 @@
         # if user cancel compose, component may be unmounted when the timeout
         # is fired
         if @isMounted()
-            state = {}
-            # close suggestion list
-            state.open = false
             # Add current value to list of addresses
             value = @refs.contactInput.getDOMNode().value
-
-            if value.trim() isnt ''
+            unless _.isEmpty value.trim()
                 address = MessageUtils.parseAddress value
-
                 if address.isValid
-                    @state.known.push address
-                    state.known   = @state.known
-                    state.unknown = ''
-                    @props.valueLink.requestChange state.known
-                    @setState state
-
+                    @update address
                 else
                     # Trick to make sure that the alert error is not pop up
                     # twiced due to multiple blur and key down.
@@ -277,39 +263,44 @@
                         msg = t 'compose wrong email format',
                             address: address.address
                         LayoutActionCreator.alertError msg
-
-            else
-                @setState state
+            else
+                @setState open: false

     onContact: (contact) ->
-        address =
-            name    : contact.get 'fn'
-            address : contact.get 'address'
-        known = _.clone @state.known
-        known.push address
-        @props.valueLink.requestChange known
-        @setState unknown: '', contacts: null, open: false
+        @update
+            name : contact.get 'fn'
+            address: contact.get 'address'

         # try to put back the focus at the end of the field
         setTimeout =>
             query = @refs.contactInput.getDOMNode().focus()
         , 200

+    update: (known, unknown) ->
+        state =
+            unknown: unknown or ''
+            contacts: null
+            # close suggestion list
+            open: false
+        if known
+            state.known = _.clone @state.known
+            state.known.push known
+
+        @setState state
+
     onDrop: (event) ->
         event.preventDefault()
         event.stopPropagation()
-        {name, address} = JSON.parse(event.dataTransfer.getData 'address')
+
+        data = event.dataTransfer.getData 'address'
+        {name, address} = JSON.parse data
+
         exists = @state.known.some (item) ->
             return item.name is name and item.address is address
+
         if address? and not exists
-            address =
-                name    : name
-                address : address
-            known = _.clone @state.known
-            known.push address
-            @props.valueLink.requestChange known
-            @setState unknown: '', contacts: null, open: false
+            @update name : name, address: address
             event.dataTransfer.dropEffect = 'move'
         else
             event.dataTransfer.dropEffect = 'none'


Extensions might not have methods

Extensions might not have methods

--- client/lanes/extension/Extensions.coffee
+++ client/lanes/extension/Extensions.coffee
@@ -12,11 +12,13 @@

     setBootstrapData: (bootstrap_data)->
         for identifier,data of bootstrap_data
-            instance.setBootstrapData(data) if instance = this.instances[identifier]
+            instance?.setBootstrapData?(data)

     makeNamespace: (identifier)->
         for ns in ['Data','Views','Controllers','Screens']
             Lanes.namespace("#{identifier}.#{ns}")

+    get: (identifier)->
+        this.instances[identifier]
 }


Rename repo to repo_slug

Rename repo to repo_slug

Address #879

--- _includes/coffee/simulation.coffee
+++ _includes/coffee/simulation.coffee
@@ -509,7 +509,7 @@
   return data

-build = (data, sim_name, codes_data, chart_data, axes_names, repo) ->
+build = (data, sim_name, codes_data, chart_data, axes_names, repo_slug) ->
   ### Build the simulation landing page

   Args:
@@ -517,6 +517,7 @@
     sim_name: the name of the simulation
     codes_data: data about the possible codes
     chart_data: Vega data for 1D charts
+    repo_slug: the repo slug from _config.yml
   ###

   header(sim_name)
@@ -528,7 +529,7 @@
   benchmark(data)
   date(data)
   software(data, codes_data)
-  pull_request_link(data, sim_name, repo)
+  pull_request_link(data, sim_name, repo_slug)
   results_table(data)

   result_data = groupBy(((x) -> x.type), data.data)


Keep VMS as array

Keep VMS as array

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -67,19 +67,19 @@
           bundle.debitGroup defaultVMOptions, callback

   fetchVMs:(callback)->
-    return callback null, @vms  if @vms
+    return callback null, @vms  if @vms.length > 0
     KD.remote.api.JVM.fetchVms (err, vms)=>
       @vms = vms  unless err
       callback err, vms

   fetchGroupVMs:(callback)->
-    return callback null, @groupVms  if @groupVms
+    return callback null, @groupVms  if @groupVms.length > 0
     KD.remote.api.JVM.fetchVmsByContext (err, vms)=>
       @groupVms = vms  unless err
       callback err, vms

   resetVMData:->
-    @vms = @groupVms = null
+    @vms = @groupVms = []

   # fixme GG!
   fetchTotalVMCount:(callback)->


Removed unneeded parantheses

Removed unneeded parantheses

--- js-library/app/js/views/templates.coffee
+++ js-library/app/js/views/templates.coffee
@@ -12,7 +12,7 @@
     if not requesting[str]
       requesting[str] = true

-      $.ajax(
+      $.ajax
         url: "#{FactlinkConfig.api}/templates/#{str}"
         dataType: 'jsonp'
         crossDomain: true
@@ -21,7 +21,6 @@
         success: (data) ->
           Factlink.tmpl[str] = _.template(data)
           Factlink.el.trigger "factlink.tmpl.#{str}"
-      )

 Factlink.templates.preload = ->
   Factlink.templates.getTemplate 'indicator'


unhide range question from selection

unhide range question from selection

--- jsapp/xlform/src/view.rowSelector.coffee
+++ jsapp/xlform/src/view.rowSelector.coffee
@@ -69,10 +69,7 @@
       for mrow in $icons.grouped()
         menurow = $("<div>", class: "questiontypelist__row").appendTo $menu
         for mitem, i in mrow when mitem
-          if mitem.id is 'range'
-            console.warn("range question type is not fully supported yet")
-          else
-            menurow.append $viewTemplates.$$render('xlfRowSelector.cell', mitem.attributes)
+          menurow.append $viewTemplates.$$render('xlfRowSelector.cell', mitem.attributes)

       @scrollFormBuilder('+=220')
       @$('.questiontypelist__item').click _.bind(@selectMenuItem, @)


Resolve "headers already sent" problems on errors.

--- lib/action_handlers/action_series.coffee
+++ lib/action_handlers/action_series.coffee
@@ -12,7 +12,7 @@
       silo= [silo]
     i= 0
     nextActionHandler= (err)=>
-      next(err) if err
+      return next(err) if err
       try
         actor= silo[i++]
         if (actor)


Fix sites with no ROI for #40

Fix sites with no ROI for #40

--- show-roi.coffee
+++ show-roi.coffee
@@ -22,6 +22,7 @@

 classifyPage.on classifyPage.LOAD_SUBJECT, (e, subject) ->
   nestingOutline?.destroy()
+  nestingOutline = null

   $.when(roi).then (roi) ->
     site = subject.metadata.path.split('/')[1].split('_')[0]
@@ -32,19 +33,20 @@
     scaleY = subject.metadata.original_size.height / height

     # Reverse the points so they're counterclockwise and knock out the clockwise outer shape.
-    nestingPoints = roi[site].slice(0).reverse()
+    nestingPoints = roi[site]?.slice(0).reverse()

-    nestingOutline = classifyPage.subjectViewer.markingSurface.svg.addShape 'path.nesting-area',
-      d: """
-        M 0 0 L #{width + 0} 0 L #{width + 0} #{height + 0} L 0 #{height + 0}
-        L 0 0
+    if nestingPoints?
+      nestingOutline = classifyPage.subjectViewer.markingSurface.svg.addShape 'path.nesting-area',
+        d: """
+          M 0 0 L #{width + 0} 0 L #{width + 0} #{height + 0} L 0 #{height + 0}
+          L 0 0

-        M #{nestingPoints[0][0] / scaleX}, #{nestingPoints[0][1] / scaleY}
-        #{("L #{[x / scaleX, y / scaleY]}" for [x, y] in nestingPoints[1...]).join '\n'}
-        L #{nestingPoints[0][0] / scaleX}, #{nestingPoints[0][1] / scaleY}
-      """
+          M #{nestingPoints[0][0] / scaleX}, #{nestingPoints[0][1] / scaleY}
+          #{("L #{[x / scaleX, y / scaleY]}" for [x, y] in nestingPoints[1...]).join '\n'}
+          L #{nestingPoints[0][0] / scaleX}, #{nestingPoints[0][1] / scaleY}
+        """

-    nestingOutline.addEvent 'mousedown', stopPropagation
-    nestingOutline.addEvent 'touchstart', stopPropagation
-    nestingOutline.addEvent 'mousemove', stopPropagation
-    nestingOutline.addEvent 'touchmove', stopPropagation
+      nestingOutline.addEvent 'mousedown', stopPropagation
+      nestingOutline.addEvent 'touchstart', stopPropagation
+      nestingOutline.addEvent 'mousemove', stopPropagation
+      nestingOutline.addEvent 'touchmove', stopPropagation


update legend position

update legend position

--- site/app.coffee
+++ site/app.coffee
@@ -156,7 +156,7 @@
       stdev = calculateStdev(values, mean)
       NonInfluenzaData[state] = [mean, stdev]
   return NonInfluenzaData
-
+
 calculateColor = (NonInfluenzaData, epidata, ep) ->
   mappedData = {}
   levelData = {}
@@ -361,14 +361,14 @@
         @zoomIn()
     pinchend = (e) =>
       if isPinching
-        if e.originalEvent.changedTouches.length == 2
+        if e.originalEvent.changedTouches.length == 2
           xe2 = e.originalEvent.changedTouches[0].pageX
           xe3 = e.originalEvent.changedTouches[1].pageX
           ye2 = e.originalEvent.changedTouches[0].pageY
           ye3 = e.originalEvent.changedTouches[1].pageY
           isPinching = false
           pinchZoom()
-        if e.originalEvent.changedTouches.length == 1
+        if e.originalEvent.changedTouches.length == 1
           if xe2 < 0
             xe2 = e.originalEvent.changedTouches[0].pageX
             ye2 = e.originalEvent.changedTouches[0].pageY
@@ -384,7 +384,7 @@
         xs0 = e.originalEvent.targetTouches[0].pageX
         xs1 = e.originalEvent.targetTouches[1].pageX
         ys0 = e.originalEvent.targetTouches[0].pageY
-        ys1 = e.originalEvent.targetTouches[1].pageY
+        ys1 = e.originalEvent.targetTouches[1].pageY
     $('#button_view_nat').click(clicker('nat', NATIONAL))
     $('#button_view_hhs').click(clicker('hhs', HHS_REGIONS))
     $('#button_view_cen').click(clicker('cen', CENSUS_REGIONS))
@@ -393,7 +393,7 @@
     $('#button_zoom_out').click(() => @zoomOut())
     @dataTimeline = $("#dataTimeline")
     @canvasMap = $('#canvas_map')
-    @canvasMap.mousemove((e) =>
+    @canvasMap.mousemove((e) =>
       loc = @hitTest(e.offsetX, e.offsetY)
       if loc?
         $('#canvas_map').css('cursor','pointer')
@@ -462,7 +462,7 @@
       )
     window.onpopstate = (e) => @backToHome()
     $('#back_arrow').click((e) -> window.history.back())
-
+
   loadEpidata: (epweek=null) ->
     if epweek?
       epiweek2 = epweek
@@ -643,7 +643,7 @@
     for loc in @locations
       $('#map_list_element_'+loc).click(()->saveThis.showLocationDetails(loc))
       $('#map_list_element_'+loc).hover(
-        ((ev) ->
+        ((ev) ->
           loc = this.id.substring(17)
           current = saveThis.mapData[loc]
           ili = '' + (Math.round(current.value * 100) / 100)
@@ -663,7 +663,7 @@
           ili = '(' + ili + '±' + std + ')%'
           $('#map_list_element_text_'+loc).text(ili)
           saveThis.renderMap(loc))
-        ((ev) ->
+        ((ev) ->
           loc = this.id.substring(17)
           $('#map_list_element_text_'+loc).text(NAMES[loc])
           saveThis.renderMap()))
@@ -757,8 +757,10 @@
       ctx.fillText('               due to differences in reporter types.', wx, h-24-12 * Math.max(0.7, w / 1000))
       ctx.fillText('NOTE 2: Color intensity is similar to, but not the same as, CDC’s Flu Activity Level.', wx, h-24)
     # Draw legend
+    areas_box = $('#map_list')
+    x_offset = areas_box.position().left + areas_box.width() + 32
     [lw, lh] = [w/25, h/2]
-    [x0, y0, x1, y1] = [w/5, h/10, w/5+lw, h/10+lh]
+    [x0, y0, x1, y1] = [x_offset, h / 10, x_offset + lw, h / 10 + lh]
     ctx.strokeStyle = '#000'
     ctx.lineWidth = 0.5
     for i in [1..10]
@@ -1009,15 +1011,3 @@
   onFluviewReceived: (ilidata) ->
     @truthData = ilidata
     @resizeCanvas()
-
-
-
-
-
-
-
-
-
-
-
-


Fixes responses from batched requests

Fixes responses from batched requests

--- src/remote/handler.coffee
+++ src/remote/handler.coffee
@@ -91,14 +91,14 @@
             memo
           , {}

-          h = { response: response.body, headers: headers }
+          h = { response: response.body, headers: headers, request: requests[i] }
           if response.status >= 400
             deferred.reject(h)
           else
             deferred.resolve(h)
       , (xhr) ->
         for request in requests
-          request[1].reject(response: xhr.responseJSON, headers: {})
+          request[1].reject(response: xhr.responseJSON, headers: {}, request: request)

     ajax: (options) ->
       options = $.extend true, options,


[EVENTRIC-58] Fix require call in remote factory

--- src/remote_factory.coffee
+++ src/remote_factory.coffee
@@ -2,7 +2,7 @@
 Remote          = require 'eventric/src/remote'
 remoteInMemory  = require 'eventric/src/remote_inmemory'

-domainEventFactory    = require './domain_event_factory.coffee'
+domainEventFactory    = require './domain_event_factory'
 stubFactory           = require './stub_factory'

 class RemoteFactory
@@ -50,4 +50,4 @@
     wiredRemote

-module.exports = new RemoteFactory+module.exports = new RemoteFactory


delete old commented out code

delete old commented out code

--- src/schema_manager.coffee
+++ src/schema_manager.coffee
@@ -17,10 +17,4 @@
   find: (args...) ->
     @jsck.find(args...)

-  #schema: (args...) ->
-    #@jsck.validate(args...)

-  #validate: (args...) ->
-    #@jsck.validate(args...)
-
-


Help text + more idiomatic coffeescript

Help text + more idiomatic coffeescript

--- src/scripts/whatis.coffee
+++ src/scripts/whatis.coffee
@@ -1,21 +1,20 @@
 #
-# whatis <term> - search the term on urbandictionary.com, return the
+# whatis <term> - search the term on urbandictionary.com and get a random popular definition for the term.

 jsdom = require('jsdom').jsdom

 module.exports = (robot) ->
   robot.respond /whatis (.+)$/i, (msg) ->
     msg
-      .http('http://www.urbandictionary.com/define.php?term=' + msg.match[1])
+      .http('http://www.urbandictionary.com/define.php?term=' + (encodeURIComponent msg.match[1]))
       .get() (err, res, body) ->
-        window = (jsdom body, null, {
-          features : {
+        window = (jsdom body, null,
+          features :
             FetchExternalResources : false
             ProcessExternalResources : false
             MutationEvents : false
             QuerySelector : false
-          }
-        }).createWindow()
+        ).createWindow()

         $ = require('jquery').create(window)

@@ -23,8 +22,5 @@
         $(".definition").each (idx, item) ->
           definitions.push $(item).text()

-        if definitions.length == 0
-          msg.send "No definition found."
-        else
-          msg.send (msg.random definitions)
-
+        msgText = if definitions.length==0 then "No definition found." else (msg.random definitions)
+        msg.send msgText


rtmp: Disable log message for outgoing ACK

--- rtmp.coffee
+++ rtmp.coffee
@@ -1707,7 +1707,7 @@
     if @windowAckSize?
       @receivedBytes += buf.length
       if @receivedBytes - @lastSentAckBytes > @windowAckSize / 2
-        console.log "[rtmp:send] Ack=#{@receivedBytes}"
+#        console.log "[rtmp:send] Ack=#{@receivedBytes}"
         outputs.push @createAck()
         @lastSentAckBytes = @receivedBytes


spectrogram limited functionality

spectrogram limited functionality

--- examples/audio/static/coffee/spectrogram.coffee
+++ examples/audio/static/coffee/spectrogram.coffee
@@ -1,7 +1,8 @@
 Collections = require('base').Collections

 all_palettes = require('../palettes/palettes').all_palettes
-ColorMapper = require('../color_mapper').ColorMapper
+console.log all_palettes
+ColorMapper = require('../mappers/color/linear_color_mapper').LinearColorMapper

 NUM_SAMPLES = 1024
 SAMPLING_RATE = 44100
@@ -86,7 +87,7 @@
     success: (data, textStatus, jqXHR) =>
       if not data[0]?
         _.delay((=> @throttled_request_data), 130)
-      else
+      else
         @on_data(data)
         requestAnimationFrame(
           => @throttled_request_data())
@@ -106,7 +107,11 @@
     for i in [0..(data[1].length-1)]
       data[1][i] *= @gain

-    cmap = new ColorMapper(all_palettes["YlGnBu-9"], 0, 10)
+    cmap = new ColorMapper({}, {
+      palette: all_palettes["YlGnBu-9"],
+      low: 0,
+      high: 10
+    })
     buf = cmap.v_map_screen(data[0])

     image32 = new Uint32Array(@image[0])
@@ -252,21 +257,30 @@
     _.defer(myrender)

   create_spec: () ->
-    plot_model = Collections('Plot').create()

     xrange = Collections('Range1d').create({start: 0, end: NGRAMS})
     yrange = Collections('Range1d').create({start: 0, end: MAX_FREQ})

-    xaxis = Collections('LinearAxis').create(
-      orientation: 'bottom'
-      parent: plot_model.ref()
-      data_range: xrange.ref()
-    )
-    yaxis = Collections('LinearAxis').create(
-      orientation: 'left'
-      parent: plot_model.ref()
-      data_range: yrange.ref()
-    )
+    plot_model = Collections('Plot').create(
+      x_range: xrange
+      y_range: yrange
+      canvas_width: @image_width
+      canvas_height: @image_height
+      outer_width: @image_width
+      outer_height: @image_height
+      tools: []
+    )
+
+    # xaxis = Collections('LinearAxis').create(
+    #   orientation: 'bottom'
+    #   parent: plot_model.ref()
+    #   data_range: xrange.ref()
+    # )
+    # yaxis = Collections('LinearAxis').create(
+    #   orientation: 'left'
+    #   parent: plot_model.ref()
+    #   data_range: yrange.ref()
+    # )

     glyphspec = {
       type: 'image_rgba'
@@ -286,33 +300,35 @@
       ydata_range: yrange.ref()
       glyphspec: glyphspec
     })
-
-    plot_model.set(
-      renderers: [glyph.ref()]
-      axes: [xaxis.ref(), yaxis.ref()]
-      tools: []
-      width: @image_width
-      height: @image_height
-    )
+    plot_model.add_renderers([glyph])

     return plot_model

   create_power: () ->
-    plot_model = Collections('Plot').create()

     xrange = Collections('Range1d').create({start: 0, end: window.TIMESLICE})
     yrange = Collections('Range1d').create({start: -0.25, end: 0.25})

-    xaxis = Collections('LinearAxis').create(
-      orientation: 'bottom'
-      parent: plot_model.ref()
-      data_range: xrange.ref()
-    )
-    yaxis = Collections('LinearAxis').create(
-      orientation: 'left'
-      parent: plot_model.ref()
-      data_range: yrange.ref()
-    )
+    plot_model = Collections('Plot').create(
+      x_range: xrange
+      y_range: yrange
+      canvas_width: 550
+      canvas_height: 220
+      outer_width: 550
+      outer_height: 220
+      tools: []
+    )
+
+    # xaxis = Collections('LinearAxis').create(
+    #   orientation: 'bottom'
+    #   parent: plot_model.ref()
+    #   data_range: xrange.ref()
+    # )
+    # yaxis = Collections('LinearAxis').create(
+    #   orientation: 'left'
+    #   parent: plot_model.ref()
+    #   data_range: yrange.ref()
+    # )

     glyphspec = {
       type: 'line'
@@ -325,34 +341,36 @@
       ydata_range: yrange.ref()
       glyphspec: glyphspec
     })
-
-    plot_model.set(
-      renderers: [glyph.ref()]
-      axes: [xaxis.ref(), yaxis.ref()]
-      tools: []
-      width: 500
-      height: 220
-    )
+    plot_model.add_renderers([glyph])

     return plot_model

   create_fft: () ->
-    plot_model = Collections('Plot').create()

     xrange = Collections('Range1d').create({start: 0, end: SPECTROGRAM_LENGTH})
     @fft_xrange = xrange
     yrange = Collections('Range1d').create({start: -1.75, end: 3.75})

-    xaxis = Collections('LinearAxis').create(
-      orientation: 'bottom'
-      parent: plot_model.ref()
-      data_range: xrange.ref()
-    )
-    yaxis = Collections('LinearAxis').create(
-      orientation: 'left'
-      parent: plot_model.ref()
-      data_range: yrange.ref()
-    )
+    plot_model = Collections('Plot').create(
+      x_range: xrange
+      y_range: yrange
+      canvas_width: 550
+      canvas_height: 220
+      outer_width: 550
+      outer_height: 220
+      tools: []
+    )
+
+    # xaxis = Collections('LinearAxis').create(
+    #   orientation: 'bottom'
+    #   parent: plot_model.ref()
+    #   data_range: xrange.ref()
+    # )
+    # yaxis = Collections('LinearAxis').create(
+    #   orientation: 'left'
+    #   parent: plot_model.ref()
+    #   data_range: yrange.ref()
+    # )

     glyphspec = {
       type: 'line'
@@ -365,14 +383,7 @@
       ydata_range: yrange.ref()
       glyphspec: glyphspec
     })
-
-    plot_model.set(
-      renderers: [glyph.ref()]
-      axes: [xaxis.ref(), yaxis.ref()]
-      tools: []
-      width: 500
-      height: 220
-    )
+    plot_model.add_renderers([glyph])

     return plot_model

@@ -380,6 +391,16 @@
     plot_model = Collections('Plot').create()

     range = Collections('Range1d').create({start: -20, end: 20})
+
+    plot_model = Collections('Plot').create(
+      x_range: range
+      y_range: range
+      canvas_width: 500
+      canvas_height: 500
+      outer_width: 500
+      outer_height: 500
+      tools: []
+    )

     glyphspec = {
       type: 'annular_wedge',
@@ -399,14 +420,7 @@
       ydata_range: range.ref()
       glyphspec: glyphspec
     })
-
-    plot_model.set(
-      renderers: [glyph.ref()]
-      axes: []
-      tools: []
-      width: 500
-      height: 500
-    )
+    plot_model.add_renderers([glyph])

     return plot_model

@@ -415,5 +429,5 @@
   setInterval((() ->
     spec.request_data()),
     400)
-
-
+
+


/node_modules/ltx/node_modules/node-expat/build updated deploy stuff

--- core/deploy.coffee
+++ core/deploy.coffee
@@ -84,7 +84,7 @@
     moduleArray = []
     if config.modules and Array.isArray(config.modules)
         for module in config.modules
-            if Fs.existsSync(@getSourceFile("/mew_modules/#{module}/package.json"))
+            if Fs.existsSync(getSourceFile("/mew_modules/#{module}/package.json"))
                 moduleArray.push module
     moduleArrayCallback = ->
         module = moduleArray.shift()
@@ -110,7 +110,7 @@
     serviceArrayCallback = =>
         service = serviceArray.shift()
         if service
-            Fs.write app.getRepoFile("/var/conf/!#{service.name}"),JSON.stringify(service.config),(err)->
+            Fs.writeFile getLocationFile(location,"/var/conf/!#{service.name}"),JSON.stringify(service.config),(err)->
                 return callback(err) if err
                 Fse.overwriteRecursive getSourceFile("/mew_modules/!#{service.name}"),makeLocationDir(location,"/mew_modules/!#{service.name}"),(err)->
                     if err


Highlight "My Songs" for more routes.

Highlight "My Songs" for more routes.

--- app/client/js/react/header.coffee
+++ app/client/js/react/header.coffee
@@ -15,7 +15,10 @@
       render: ->
         header {},
           nav {},
-            link.to routes.home, Header.MSG_MY_SONGS
+            link.to
+              route: routes.home
+              text: Header.MSG_MY_SONGS
+              activeFor: [routes.editSong, routes.newSong, routes.trash]
             link.to routes.songs, Header.MSG_SONGS
             link.to routes.about, Header.MSG_ABOUT
             if userStore.isLogged()


Allowing html in translations jsons

Allowing html in translations jsons

--- app/coffee/plugins/i18next.coffee
+++ app/coffee/plugins/i18next.coffee
@@ -21,7 +21,7 @@
             element = angular.element(elm)
             for value in attrs.i18next.split(",")
                 if value.indexOf(":") == -1
-                    element.text(t(value))
+                    element.html(t(value))
                 else
                     [ns, value] = value.split(":")
                     element.attr(ns, t(value))


Remove old buffer test

Remove old buffer test

--- test/buffer.coffee
+++ test/buffer.coffee
@@ -1,44 +0,0 @@
-
-should = require 'should'
-fs = require 'fs'
-BufferedStream = if process.env.BF_COV then require '../lib-cov/buffered-stream' else require '../lib/buffered-stream'
-
-file = '/tmp/node-buffered-stream'
-
-go = (data, callback) ->
-  buffer = new BufferedStream 1024
-  out = fs.createWriteStream file
-  out.on 'error', (err) ->
-    console.log 'error', err
-  out.on 'close', ->
-    fs.readFile file, 'utf8', (err, content) ->
-      should.not.exist err
-      # console.log 'oka', content
-      content.should.eql data
-      fs.unlink file, (err) ->
-        should.not.exist err
-        callback()
-  buffer.pipe out
-  offset = 0
-  length = 100
-  while offset + length < data.length
-    buffer.write data.substr offset, length
-    offset += length
-  buffer.write data.substr offset
-  buffer.end()
-
-describe 'buffered stream', ->
-
-  it 'should write ascii each 100 chars as 1 Ko chuncks', (next) ->
-    data = ''
-    for i in [0...1000000] then data += "fjieorjf#{i}\n"
-    go data, next
-
-  it 'should write utf8 each 100 chars as 1 Ko chuncks', (next) ->
-    data = ''
-    for i in [0...1000000] then data += "àèêûîô#{i}\n"
-    go data, next
-
-
-
-


added checks for invalid/duplicate snaps

--- test/common.coffee
+++ test/common.coffee
@@ -12,6 +12,9 @@
     module.exports.testOpenOnSnap lego
     module.exports.testCloseOnUnsnap lego
     module.exports.testStrength lego
+    module.exports.testInvalidLegoSnap lego
+    module.exports.testDupeSnaps lego
+    module.exports.testDupeUnsnaps lego

   testInstanceOf: (lego)->
     it 'should be an instanceof Lego', ->
@@ -108,6 +111,49 @@
       lego.close()
       lego._open.should.equal no
       lego._openStrong.should.equal no
+
+  testInvalidLegoSnap: (lego)->
+    it 'should not accept non-legos when (un)snapping', ->
+      check = (fn)-> fn.should.throw 'not a Lego'
+
+      check -> lego.snap 'hello!'
+      check -> lego.snap {}
+      check -> lego.snap 1234
+      check -> lego.snap null
+      check -> lego.snap()
+      check -> lego.unsnap 'hello!'
+      check -> lego.unsnap {}
+      check -> lego.unsnap 1234
+      check -> lego.unsnap null
+      check -> lego.unsnap()
+
+  testDupeSnaps : (lego)->
+    it 'should gracefully ignore duplicate snaps', ->
+      next = new legos.Lego
+      try
+        lego.snap next
+        count = lego._outputs.length
+        lego.snap next
+        lego._outputs.length.should.equal count
+      finally
+        lego.unsnap next
+
+  testDupeUnsnaps : (lego)->
+    it 'should gracefully ignore duplicate unsnaps', ->
+      next = new legos.Lego
+      next2 = new legos.Lego
+      try
+        lego.snap next
+        count = lego._outputs.length
+        lego.snap next2
+
+        lego.unsnap next2
+        lego._outputs.length.should.equal count
+        lego.unsnap next2
+        lego._outputs.length.should.equal count
+      finally
+        lego.unsnap next
+        lego.unsnap next2

   testWrite: (description, config)->
     it description, (done)->


reset zoom level after subject finished

reset zoom level after subject finished

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -101,7 +101,7 @@
       @canvasGraph.enableMarking()
       @drawSliderAxisNums()

-      @zoomRanges = [ @canvasGraph.largestX, (@canvasGraph.largestX * 10/@canvasGraph.largestX), @canvasGraph.largestX * 3/@canvasGraph.largestX ]
+      @zoomRanges = [ @canvasGraph.largestX, @canvasGraph.largestX/10, @canvasGraph.largestX/20 ]
       @magnification = [ '1x', '10x', '20x' ]
       @showZoomMessage(@magnification[@zoomLevel])

@@ -212,6 +212,7 @@
     @el.find("#toggle-zoom").removeClass("toggled")
     @el.find(".noUi-handle").hide()
     @isZoomed = false
+    @zoomLevel = 0

   showSummary: ->
     @classifySummary.fadeIn(150)


use mark.frame instead of slider val for ghosts

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -195,7 +195,7 @@
   addGhostMark: (mark) ->
     svgElement = null
     for surface, i in @markingSurfaceList
-      if i isnt +@el.find('#frame-slider').val()
+      if i isnt +mark.frame
         svgElement = surface.addShape 'circle', class: "ghost-mark", opacity: 1, cx: mark.x, cy: mark.y, r: 16, fill: "none", stroke: "#25b4c5", strokewidth: 1
         svgElement.el.setAttribute 'from-frame', mark.frame
         svgElement.el.setAttribute 'from-asteroid', @currAsteroid.id


"Other" storms have no advanced classification

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -211,7 +211,11 @@
       @matchImage.attr src: @defaultImageSrc

   startDetailedClassify: =>
-    if @classification.get('category') is 'eye'
+    category = @classification.get 'category'
+
+    if category is 'other'
+      @onClickNext()
+    else if category is 'eye'
       @setupEye()
     else
       @setupCenter()


Isolated scope is unnecessary.

Isolated scope is unnecessary.

--- app/javascripts/directives.coffee
+++ app/javascripts/directives.coffee
@@ -114,8 +114,6 @@
     # Displays the clinical profile.
     directives.directive 'qiClinicalTable', ['Clinical', (Clinical) ->
       restrict: 'E'
-      scope:
-        subject: '='
       link: (scope, element, attrs) ->
         scope.$watch 'subject', (subject) ->
           if subject
@@ -125,12 +123,9 @@

     # Displays the subject demographics.
-    directives.directive 'qiDemographicsTable', ['Race', 'Ethnicity', (Race, Ethnicity) ->
+    directives.directive 'qiDemographicsTable', ->
       restrict: 'E'
-      scope:
-        subject: '='
       templateUrl: '/partials/demographics-table.html'
-    ]

     # Displays a clinical outcome.


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -28,6 +28,6 @@
   fastEnemy.include(Follow)
   fastEnemy.follow(player)

-  ok fastEnemy.I.velocity.equal(Point(0, -5)), 'fastEne'
+  ok fastEnemy.I.velocity.equal(Point(0, -5)), 'fastEnem'

 module()


チュートリアルを出して消すと、フォロイー欄に(タスク内容)と出てくるのを修正

チュートリアルを出して消すと、フォロイー欄に(タスク内容)と出てくるのを修正

--- app/js/project/tutorial.js.coffee
+++ app/js/project/tutorial.js.coffee
@@ -38,13 +38,13 @@

 show_turorial = () ->
   $('#tutorial, .fade-plane').show('fade', 500)
-  $('.task').not('#task-template').hide('fade', 500)
+  $('.task').not('#task-template').not('.followee-task .task').hide('fade', 500)
   $('.staff:gt(1)').hide('fade', 500) # don't hide first staff, for explain
   $('.followee').not('#followee-template').hide('fade', 500)

   $('#tutorial, .fade-plane').one('click', () ->
     $('#tutorial, .fade-plane').hide('fade', 500)
-    $('.task').not('#task-template').show('fade', 500)
+    $('.task').not('#task-template').not('.followee-task .task').show('fade', 500)
     $('.staff:gt(1)').show('fade', 500)
     $('.followee').not('#followee-template').show('fade', 500)
   )


cache on for inline optimized test

cache on for inline optimized test

--- test/inline.coffee
+++ test/inline.coffee
@@ -10,4 +10,4 @@
   describe '#yield(name, attr)', ->
     it 'should render', ->
       t = -> p "This text could use #{yield -> strong -> a href: '/', 'a link'}."
-      cc.render(t, optimized: true).should.equal '
This text could use a link.
'
+      cc.render(t, optimized: true, cache: on).should.equal '
This text could use a link.
'


Remove trailing line

Remove trailing line

--- app/scripts/helpers/routes.coffee
+++ app/scripts/helpers/routes.coffee
@@ -1,5 +1,4 @@
 App = require('scripts/application')
-
 AppConfig = require('scripts/config')

 class RoutesHelper


Fix typo

Fix typo

--- test/random.coffee
+++ test/random.coffee
@@ -66,8 +66,8 @@
   buffer = new Buffer buffer  unless Buffer.isBuffer buffer
   for z in [0..3]
     x = [buffer]
-    x.unshift new Buffer utf8 27  if x & 1
-    x.push new Buffer utf8 27  if x & 2
+    x.unshift new Buffer utf8 27  if z & 1
+    x.push new Buffer utf8 27  if z & 2
     x = valid8 Buffer.concat x
     x = !x unless good
     assert x


Update translations.coffee

Update translations.coffee

--- addon/locales/zh-tw/translations.coffee
+++ addon/locales/zh-tw/translations.coffee
@@ -164,7 +164,7 @@
     "accepted_items": "已接受物資，正在等候運送"
     "driver_picked_up_items": "GoGoVan司機已接收物資"
     "charity_sale": "閣下同意我們義賣捐贈物資嗎？"
-    "crossroads_phone": "Crossroads' Phone"
+    "crossroads_phone": "十字路會電話"

   "items":
     "add_item":
@@ -276,7 +276,7 @@
       "title" : "確定車輛"
       "base_fee" : "基本費用"
       "porterage" : "搬運"
-      "speak_english" : "Speak English"
+      "speak_english" : "通曉英語"
       "details" : "詳情"
       "other" : "其他"
       "confirm_with_driver" : "在搬運前，請先與司機商討總價格"
@@ -285,9 +285,9 @@
       "book_delivery" : "確認及預約"
       "negotiate" : "與司機商討"
       "no_porterage" : "不需要"
-      "goods_6ft": "Goods >6ft"
-      "trolley": "Trolley"
-      "estimate": "Estimate"
+      "goods_6ft": "貨物>6尺"
+      "trolley": "手推車"
+      "estimate": "預計"
       "confirm_total_price": "預計價格並不包括隧道或天橋費、泊車以及其他雜費。請在上貨前與司機確認最終價錢。"
       "ggv_link": "請到GoGovan網站查閱更多有關費用詳情。"


Add MEASURE option to webpack builds

Add MEASURE option to webpack builds

--- src/webpack.config.coffee
+++ src/webpack.config.coffee
@@ -114,6 +114,7 @@
 DEVMODE       = not PRODMODE
 MINIFY        = !! process.env.WP_MINIFY
 DEBUG         = '--debug' in process.argv
+MEASURE       = process.env.MEASURE
 SOURCE_MAP    = !! process.env.SOURCE_MAP
 STATICPAGES   = !! process.env.CC_STATICPAGES  # special mode where just the landing page is built
 date          = new Date()
@@ -142,6 +143,7 @@
 console.log "CDN_BASE_URL     = #{CDN_BASE_URL}"
 console.log "DEBUG            = #{DEBUG}"
 console.log "MINIFY           = #{MINIFY}"
+console.log "MEASURE          = #{MEASURE}"
 console.log "INPUT            = #{INPUT}"
 console.log "OUTPUT           = #{OUTPUT}"
 console.log "GOOGLE_ANALYTICS = #{GOOGLE_ANALYTICS}"
@@ -461,10 +463,11 @@
 else
     plugins = plugins.concat(staticPages)

-BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
-bundleAnalyzerPlugin = new BundleAnalyzerPlugin({analyzerMode: 'static'})
-
-plugins = plugins.concat([assetsPlugin, statsWriterPlugin, bundleAnalyzerPlugin])
+if PRODMODE
+    # configuration for the number of chunks and their minimum size
+    plugins.push new webpack.optimize.LimitChunkCountPlugin(maxChunks: 5)
+
+plugins = plugins.concat([assetsPlugin, statsWriterPlugin])

 UglifyJsPlugin = require('uglifyjs-webpack-plugin')
 minimizer = new UglifyJsPlugin
@@ -510,6 +513,11 @@
     publicPath = CDN_BASE_URL
 else
     publicPath = path.join(BASE_URL, misc_node.OUTPUT_DIR) + '/'
+
+if MEASURE
+    BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+    bundleAnalyzerPlugin = new BundleAnalyzerPlugin({analyzerMode: 'static'})
+    plugins = plugins.concat([bundleAnalyzerPlugin])

 module.exports =
     cache: true


Dumping cookies caused a crash.

Dumping cookies caused a crash.

--- src/zombie/cookies.coffee
+++ src/zombie/cookies.coffee
@@ -152,7 +152,7 @@
     # debugging.  If you need to save/load, use comma as the line
     # separator and then call `cookies.update`.
     this.dump = (separator = "\n")->
-      (@serialize(browser, match[0], match[1], match[2], match[3]) for match in selected()).join(separator)
+      (serialize(browser, match[0], match[1], match[2], match[3]) for match in selected()).join(separator)

 # ### document.cookie => String


adding pandas plot source

adding pandas plot source

--- static/coffee/base.coffee
+++ static/coffee/base.coffee
@@ -599,6 +599,7 @@

   IPythonRemoteData: ['./pandas/pandas', 'ipythonremotedatas']
   PandasPivotTable: ['./pandas/pandas', 'pandaspivottables']
+  PandasPlotSource: ['./pandas/pandas', 'pandasplotsources']

   LinearAxis: ['./renderers/guide/axis', 'linearaxes']
   Rule: ['./renderers/guide/rule', 'rules']


Fix for clearTimeout bugging chained and looped animations. Closes #9

--- src/Moon.coffee
+++ src/Moon.coffee
@@ -152,7 +152,7 @@
                 return this._stack[step]

             anm = undefined
-            # if is paused, we need to recaulate delay and duration
+            # if is paused, we need to recalculate delay and duration
             if this._paused?
                 anm = getAnm(this._step)

@@ -195,14 +195,15 @@

                 # timer to continue animation
                 this._nextTimeout = setTimeout =>
-
+                    clearTimeout(@._nextTimeout)
+
                     # after animation function
                     anm.after() if typeof anm.after == "function"

                     # continue chained animations
                     @._play()

-                    clearTimeout(@._nextTimeout)
+
                 , anm.delay + anm.duration

                 this._lastTimePlayed = new Date()


Added configuration options for AJAX method

--- src/ajax.coffee
+++ src/ajax.coffee
@@ -59,6 +59,12 @@

   clearQueue: ->
     @queue []
+
+  config:
+    loadMethod: 'GET'
+    updateMethod: 'PUT'
+    createMethod: 'POST'
+    destroyMethod: 'DELETE'

 class Base
   defaults:
@@ -118,7 +124,7 @@
     record = new @model(id: id)
     @ajaxQueue(
       params, {
-        type: 'GET'
+        type: options.method or Ajax.config.loadMethod
         url: options.url or Ajax.getURL(record)
         parallel: options.parallel
       }
@@ -128,7 +134,7 @@
   all: (params, options = {}) ->
     @ajaxQueue(
       params, {
-        type: 'GET'
+        type: options.method or Ajax.config.loadMethod
         url: options.url or Ajax.getURL(@model)
         parallel: options.parallel
       }
@@ -159,7 +165,7 @@
   reload: (params, options = {}) ->
     @ajaxQueue(
       params, {
-        type: 'GET'
+        type: options.method or Ajax.config.loadMethod
         url: options.url
         parallel: options.parallel
       }, @record
@@ -169,7 +175,7 @@
   create: (params, options = {}) ->
     @ajaxQueue(
       params, {
-        type: 'POST'
+        type: options.method or Ajax.config.createMethod
         contentType: 'application/json'
         data: @record.toJSON()
         url: options.url or Ajax.getCollectionURL(@record)
@@ -181,7 +187,7 @@
   update: (params, options = {}) ->
     @ajaxQueue(
       params, {
-        type: 'PUT'
+        type: options.method or Ajax.config.updateMethod
         contentType: 'application/json'
         data: @record.toJSON()
         url: options.url
@@ -193,7 +199,7 @@
   destroy: (params, options = {}) ->
     @ajaxQueue(
       params, {
-        type: 'DELETE'
+        type: options.method or Ajax.config.destroyMethod
         url: options.url
         parallel: options.parallel
       }, @record


Drop space-pen-extensions from atom.coffee

--- src/atom.coffee
+++ src/atom.coffee
@@ -11,7 +11,6 @@
 fs = require 'fs-plus'
 {mapSourcePosition} = require 'source-map-support'
 Model = require './model'
-{$} = require './space-pen-extensions'
 WindowEventHandler = require './window-event-handler'
 StylesElement = require './styles-element'
 StorageFolder = require './storage-folder'
@@ -468,7 +467,7 @@
   # Extended: Focus the current window.
   focus: ->
     ipc.send('call-window-method', 'focus')
-    $(window).focus()
+    window.focus()

   # Extended: Show the current window.
   show: ->


getUserAgent()

getUserAgent()

--- src/atom.coffee
+++ src/atom.coffee
@@ -352,6 +352,10 @@
   getVersion: ->
     app.getVersion()

+  # Public: Gets the user agent of the atom instance.
+  getUserAgent: ->
+    "GitHubAtom/#{app.getVersion()}"
+
   # Public: Get the directory path to Atom's configuration area.
   #
   # Returns the absolute path to ~/.atom


project the dem to meters

project the dem to meters

--- src/data.coffee
+++ src/data.coffee
@@ -37,6 +37,23 @@

 ensureDir = (path) ->
   fs.mkdirSync(path) unless fs.existsSync(path)
+
+
+albers = (bbox) ->
+  round = (v) -> d3.round(v, 4)
+  dp = (bbox[3] - bbox[1]) / 6
+  return {
+    lat_1: round(bbox[3] + dp)
+    lat_2: round(bbox[1] - dp)
+    lat_0: round((bbox[1] + bbox[3]) / 2)
+    lon_0: round((bbox[0] + bbox[2]) / 2)
+  }
+
+
+albersProj = (param) ->
+  return "+proj=aea
+          +lat_1=#{param.lat_1} +lat_2=#{param.lat_2}
+          +lat_0=#{param.lat_0} +lon_0=#{param.lon_0}"

 query = (bbox) ->
@@ -208,6 +225,7 @@
   buildDem = ->
     demDone = Q.defer()
     exec("rm -f data/contours/#{region}.*")
+    exec("rm -f data/contours/#{region}-prj.*")
     .then ->
       exec("gdalwarp
             data/srtm-1arcsec-ro.tiff
@@ -220,21 +238,28 @@
             -a elevation
             -i 100")
     .then ->
+      exec("ogr2ogr
+            -t_srs '#{albersProj(albers(bbox))}'
+            data/contours/#{region}-prj.shp
+            data/contours/#{region}.shp")
+    .then ->
       exec("topojson
-            contour=data/contours/#{region}.shp
+            contour=data/contours/#{region}-prj.shp
             -o #{demPath}
             --id-property ID
             -p elevation
             -s .00000000001")
     .done ->
       dem = JSON.parse(fs.readFileSync(demPath))
+      dem.wgsBbox = bbox
+      fs.writeFileSync(demPath, JSON.stringify(dem))
       demDone.resolve(dem)

     return demDone.promise

   if fs.existsSync(demPath)
     dem = JSON.parse(fs.readFileSync(demPath))
-    if JSON.stringify(bbox) == JSON.stringify(dem.objects.contour.bbox)
+    if JSON.stringify(bbox) == JSON.stringify(dem.wgsBbox)
       console.log "using cached dem topojson"
       return Q(dem)


Refs #99; Better namespace for the data method, added Epoch.data helper to make data formatters easier to use.

--- src/data.coffee
+++ src/data.coffee
@@ -1,4 +1,5 @@
-Epoch.DataFormat = {}
+Epoch.Data ?= {}
+Epoch.Data.Format ?= {}

 # Default options for Data Formats
 defaultOptions =
@@ -37,7 +38,7 @@
 # @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true.
 # @option options [Number] startTime Unix timestamp used as the starting point for auto acsending times in
 #   real-time data formatting.
-Epoch.DataFormat.array = (->
+Epoch.Data.Format.array = (->
   buildLayers = (data, options, mapFn) ->
     result = []
     if Epoch.isArray(data[0])
@@ -79,3 +80,11 @@
     else
       formatBasicPlot data, opt
 )()
+
+# Convenience data formatting method for easily accessing the various formatters.
+# @param [String] formatter Name of the formatter to use.
+# @param [Array] data Data to format.
+# @param [Object] options Options to pass to the formatter (if any).
+Epoch.data = (formatter, data, options={}) ->
+  return [] unless (formatFn = Epoch.Data.Format[formatter])?
+  formatFn(data, options)


allow LegacyImageHeader to be used

allow LegacyImageHeader to be used

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -255,7 +255,7 @@

 # The old image banner + optional text description below
-LegacyImageHeader = (opts) ->
+window.LegacyImageHeader = (opts) ->
   subdomain = fetch '/subdomain'
   loc = fetch 'location'
   homepage = loc.url == '/'


config for random2015 evaluation

config for random2015 evaluation

Former-commit-id: c1486cc1adb9242e0a78672c9550f65a1346233d [formerly 94a9e2dd8e3bdf7db24cfee3f4e19ad50e9b1609] [formerly 75126826e2dc66b459d4c946ffeaf43491b4aa08 [formerly 44824d98579ac6e74bca89b49c163f5dc64ede6a]] [formerly 1c5425fb3ae47dd2dfb91214856ed12a6e71c69a [formerly 8e8a1348e9176e6206183c9392290866d52256a9] [formerly c5a96479190433f937b912d6846bf000c9909037 [formerly c5a96479190433f937b912d6846bf000c9909037 [formerly ff9e7950e4fe5463e78e85c739840aabd8f1bb9e]]]]
Former-commit-id: 99106e9adcc9b38d506bf988a22e17a256b82da8 [formerly 94dda379e85a2c217fe2be6dc8f88686102a6ee3] [formerly d8ab2cee4a9cf36a67720fa0271605c7df0621de [formerly efd8c332ff421979d974aeb70d0bf7c8949109a3]]
Former-commit-id: b854a16a8c15674edeccfdf8fe89447a7758a170 [formerly 7556ee3b11b3082d46a5ab242f589755980934fc]
Former-commit-id: c9cb5cfb21e01ec567faf1e5b4b22f39c50bcbb8
Former-commit-id: ceb2872c873eff6ee03d17e812f23a9b76b32b7a

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -142,6 +142,16 @@
   your_header: "--valences-- you observe"
   other_header: "--valences-- observed"
   top_header: "Foremost --valences--"
+
+strengths_limitations =
+  pro: 'strength'
+  pros: 'strengths'
+  con: 'limitation'
+  cons: 'limitations'
+  your_header: "--valences-- you observed"
+  other_header: "--valences-- observed"
+  top_header: "Foremost --valences--"
+

 challenge_justify =
   pro: 'justification'
@@ -713,8 +723,14 @@
 # RANDOM2015

 customizations['random2015'] = _.extend {}, conference_config,
-  opinion_value: (o) -> 3 * o.stance
-
+  opinion_value: (o) -> 3 * o.stance,
+  "/proposal/2638" :
+    point_labels: strengths_limitations
+    slider_pole_labels: yes_no
+  "/proposal/2639" :
+    point_labels: strengths_weaknesses
+    slider_pole_labels: yes_no
+
 customizations['program-committee-demo'] = conference_config


Nodejs: metrics: move sending metrics logic to handleRequest method

--- node_modules_koding/bongo/lib/bongo.coffee
+++ node_modules_koding/bongo/lib/bongo.coffee
@@ -346,6 +346,7 @@
         @update node

   handleRequest:(secretName, request, callback)->
+    start = new Date()
     { message, client } = request

     @emit 'message', { secretName, message }
@@ -369,6 +370,18 @@
         fn.autoCull = yes
         @remoteStore.add storeKey, fn
       @remoteStore.get storeKey
+
+    if @metrics
+      # wrapping callback function if there is one in the scrubbed args
+      if typeof (cb = unscrubbed[unscrubbed.length - 1]) is 'function'
+        unscrubbed[unscrubbed.length - 1] = (rest...) =>
+          duration = new Date() - start
+          @metrics.sendMethodMetrics { method, duration }
+          cb rest...
+      # else sending metrics with duration equal to zero
+      else
+        @metrics.sendMethodMetrics { method, 0 }
+
     @invokeMethod method, unscrubbed, userArea, sessionToken, secretName, client

   pong: (callback) -> callback? Date.now()


random writes test

random writes test

--- test_string.coffee
+++ test_string.coffee
@@ -28,6 +28,31 @@
    break
  console.log "res: ", pass

+
+print2 = (arr) ->
+ get = (i) ->
+  if arr instanceof Array
+   arr[i]
+  else
+   arr.get i
+
+ n = arr.length
+ if n < 32
+  for i in [0...n]
+   console.log get i
+
+ pass = on
+ for i in [0...1000]
+  p = Math.floor Math.random()*n
+  p-- if p is n
+  s = get p
+  ps = "#{p}"
+  if s? and ps isnt s.substr 0, ps.length
+   pass = off
+   console.log p, s
+   break
+ console.log "res: ", pass
+
 test_js = (n) ->
  console.time 'test_js'
  arr = []
@@ -35,6 +60,29 @@
   arr.push "#{i}#{STR}"
  print arr
  console.timeEnd 'test_js'
+
+test_js_random_write = (n) ->
+ console.time 'test_js_random_write'
+ M = 1000000
+ arr = new Array M
+ for i in [0...n]
+  p = Math.floor Math.random()*M
+  p-- if p is M
+  arr[p] = "#{p}#{i}#{STR}"
+ print2 arr
+ console.timeEnd 'test_js_random_write'
+
+test_random_write = (n) ->
+ console.time 'test_random_write'
+ M = 1000000
+ arr = TDS.Array TDS.Struct.String, M
+ for i in [0...n]
+  p = Math.floor Math.random()*M
+  p-- if p is M
+  arr.set p, "#{p}#{i}#{STR}"
+ print2 arr
+ console.timeEnd 'test_random_write'
+

 test = (n) ->
  console.time 'test'
@@ -45,8 +93,10 @@
  console.timeEnd 'test'

 run = (n) ->
- test_js n
- test n
+ #test_js n
+ test_js_random_write n
+ #test_random_write n
+ #test n

 if GLOBAL?
  #exports.run = run


one line is simpler

one line is simpler

--- app/assets/javascripts/cloudTags.coffee
+++ app/assets/javascripts/cloudTags.coffee
@@ -11,6 +11,5 @@
       $("#suggest").val(($("#suggest").val().trim() + " " + $(this).text().trim()).trim())

     $(".legend").click ->
-      $(".clickTag").toggle()
-      $(".legend img").toggle()
-  $.ajax "/tags", success : document.cloudThis+      $(".clickTag,.legend img").toggle()
+  $.ajax "/tags", success : document.cloudThis


Set reaction stars as read only depending on data-readonly attribute.

Fixes #10

--- app/assets/javascripts/custom.js.coffee
+++ app/assets/javascripts/custom.js.coffee
@@ -29,6 +29,7 @@
         $("##{div_id}").raty
           path: "/assets"
           score: reaction
+          readOnly: $("##{div_id}").attr("data-readonly")
           click: (score,evt) ->
             $.ajax
               url: $(this).attr("data-url"),


Adjust sankey tooltop position

Adjust sankey tooltop position

Tooltips will display below the node, when possible, in order not to
obscure links entering or leaving the node.

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -566,8 +566,8 @@
             title: -> $(this).parent('g').attr('data-tooltip-title')
             text: -> $(this).parent('g').attr('data-tooltip')
           position:
-            my: 'right center'
-            at: 'left center'
+            my: 'bottom center'
+            at: 'top center'
             viewport: $("#{@container_selector()}")
             adjust:
               method: 'flipinvert'


Automatically embed Twitter status URLs on mobile

--- app/assets/javascripts/mobile.js.coffee
+++ app/assets/javascripts/mobile.js.coffee
@@ -72,6 +72,11 @@
   body = body.replace(
     /\b(https?:\/\/instagram\.com\/p\/[^\/]+\/)/g,
     ''
+  )
+  # Embed Twitter statuses directly when a URL is pasted
+  body = body.replace(
+    /^https?:\/\/(mobile\.)?twitter\.com\/([\w\d_]+)\/status(es)?\/([\d]+)/gm,
+    '
https://twitter.com/\$2/statuses/\$4
'
   )
   return body


I'm into sharing

I'm into sharing

--- app/assets/javascripts/router.js.coffee
+++ app/assets/javascripts/router.js.coffee
@@ -5,4 +5,4 @@
   home: ->
     console.log "home"

-appRouter = new AppRouter
+@appRouter = new AppRouter


Updates browser URL through History API.
https://developer.mozilla.org/en-US/docs/Web/API/History_API

Line 44 is changed to submit() to avoid the click() update.

--- app/assets/javascripts/search.js.coffee
+++ app/assets/javascripts/search.js.coffee
@@ -41,9 +41,15 @@
           $('#filters').prop('disabled', false) # Enable filters panel (if present)

         when 'advanced_search'
-          $("#advanced_search form input:submit").click()
+          $('#advanced_search form input:submit').submit()
           $('#filters').prop('disabled', true) # Disable filters panel (if present)

       return

+    # Update URL in browser #434
+    $(document).on 'click', '#advanced_search form input:submit', ->
+      # history.pushState(stateObj, title, url)
+      history.pushState("","",window.location.pathname + '?' + $('form.ransack_search').serialize())
+      return
+
 ) jQuery


Animate the numbers up from 0 to their target

--- app/assets/javascripts/static.js.coffee
+++ app/assets/javascripts/static.js.coffee
@@ -1,3 +1,14 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
+
+$ ->
+  animateMetric = $(".metric-box strong").each ->
+    $this = $(this)
+    jQuery(counter: 0).animate { counter: $this.text().replace(/\,/g, '') },
+      duration: 3000
+      easing: 'swing'
+      step: ->
+        $this.text Math.ceil(@counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
+        return
+    return


cleaned up flag labels

cleaned up flag labels

--- app/assets/javascripts/stocks.js.coffee
+++ app/assets/javascripts/stocks.js.coffee
@@ -9,6 +9,7 @@

   annotation_flags = []
   for singleAnnotation in annotations
+    console.log Date.parse(singleAnnotation.date)
     objectFlag = {
               x: Date.parse(singleAnnotation.date)
               title: singleAnnotation.title
@@ -56,25 +57,18 @@
         name: name
         data: data

-
-        # type: 'flags'
-        # name: 'Flags on axis'
-        # data = [
-        #     x: Date.UTC(2014, 2, 1)
-        #     title: "Flag 1"
-        #   ,
-
-        #     x: Date.UTC(2014, 3, 1)
-        #     title: "Flag 2"
-        # ]
-        # shape: "squarepin"
       seriesCounter += 1

       if seriesCounter == names.length
         obj1 =
           type: 'flags'
-          name: 'Flags on axis'
+          # name: ''
           data: annotation_flags
+          tooltip:
+            dateTimeLabelFormats:
+              day: "%b %e, %Y"
+            pointFormat: ""
+            valueDecimals: 1
         seriesOptions.push obj1
         console.log "stored annotations in stocks" + @stored_annotations_passing
         createChart()


Only and without now works

Only and without now works

--- tests/index.coffee
+++ tests/index.coffee
@@ -50,7 +50,7 @@

     it "Should work", ->

-      # $
-        # search: ["node_modules/", "tests/my-modules/"]
-        # only: ["gulp", "coffee-script", "big-wall"]
-        # without: ["gulp"]+      assert.isTrue $
+        search: ["node_modules/", "tests/my-modules/"]
+        only: ["gulp", "coffee-script", "big-wall"]
+        without: ["gulp", "coffee-script", "big-wall"]


cooldown finilazation

cooldown finilazation

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -98,7 +98,7 @@

   equals obj.I.age, 1, "should successfully update"

-test "use existing value of instance variable as starting value if no value given", ->
+test "use existing value of instance variable as starting value if no value param given", ->
   obj = GameObject()

   obj.include(Cooldown)


More test for appear/disappear.

More test for appear/disappear.

--- tests/tests.coffee
+++ tests/tests.coffee
@@ -4,7 +4,7 @@
 # Debug:
 # casperjs test --verbose --log-level=debug tests/tests.coffee

-TESTS = 93
+TESTS = 95

 casper.on 'remote.message', (message) ->
 	console.log 'BROWSER:', message
@@ -92,7 +92,15 @@
 		test.assertEval (->
 			!(jQuery '.js-dialog').hasClass('is-transit')
 		), '.is-transit class has been removed after transition but before fallback timeout'
-
+	casper.thenEvaluate ->
+		jQuery('.js-dialog').trigger('appear.tamia').trigger('disappear.tamia')
+	casper.wait 750, ->
+		test.assertNotVisible '.js-dialog', 'Dialog is not visible after firing appear.tamia then disappear.tamia'
+	casper.thenEvaluate ->
+		$('.js-dialog').removeState('hidden')  # Show immediately
+		jQuery('.js-dialog').trigger('disappear.tamia').trigger('appear.tamia')
+	casper.wait 750, ->
+		test.assertVisible '.js-dialog', 'Dialog is visible after firing disappear.tamia then appear.tamia'

 	# Controls
 	casper.thenClick '.js-control-link', ->


prompt window for deleting

prompt window for deleting

--- lms/static/coffee/src/discussion/content.coffee
+++ lms/static/coffee/src/discussion/content.coffee
@@ -232,8 +232,13 @@
     handleDelete = (elem) ->
       if $content.hasClass("thread")
         url = Discussion.urlFor('delete_thread', id)
+        confirmValue = confirm("Are you sure? This will delete the thread and all comments associated with it and cannot be recovered.")
       else
         url = Discussion.urlFor('delete_comment', id)
+        confirmValue = confirm("Are you sure? This will delete the comment and all replies associated with it and cannot be recovered.")
+
+      if not confirmValue
+        return

       Discussion.safeAjax
         $elem: $(elem)