fix #1050 fix prev/next for comments if list was reordered and also enable wrap around

--- app/assets/javascripts/oxalis/view/skeletontracing/right-menu/comment_tab_view.coffee
+++ app/assets/javascripts/oxalis/view/skeletontracing/right-menu/comment_tab_view.coffee
@@ -125,7 +125,10 @@

   nextComment : ->

-    nextComment = @collection.find((model) => model.get("node") > @activeComment.get("node"))
+    activeComment = @activeComment
+    nextComment = @collection.find( ((model) -> @comparator(model) > @comparator(activeComment)), @collection)
+    # try to wrap around if no next comment was found
+    nextComment = @collection.at(0) if not nextComment
     if nextComment

       @setActiveNode(nextComment)
@@ -133,7 +136,10 @@

   previousComment : ->

-    previousComment = _.findLast(@collection.models, (model) => model.get("node") < @activeComment.get("node"))
+    activeComment = @activeComment
+    previousComment = _.findLast(@collection.models, ((model) -> @comparator(model) < @comparator(activeComment)), @collection)
+    # try to wrap around if no previous comment was found
+    previousComment = @collection.at(@collection.length - 1) if not previousComment
     if previousComment

       @setActiveNode(previousComment)


Some fixes

Some fixes

--- packages/rocketchat-lib/server/sendMessage.coffee
+++ packages/rocketchat-lib/server/sendMessage.coffee
@@ -99,7 +99,7 @@
 				mentionIds.push mention._id

 			if mentionIds.length > 0
-				usersOfMention = Meteor.users.find({_id: {$in: mentionIds}}, {fields: {_id: 1}}).fetch()
+				usersOfMention = Meteor.users.find({_id: {$in: mentionIds}}, {fields: {_id: 1, username: 1}}).fetch()

 				if mentionIds.indexOf('all') is -1
 					for usersOfMentionItem in usersOfMention


fix max item alert

fix max item alert

--- views/components/settings/parts/poi-config.cjsx
+++ views/components/settings/parts/poi-config.cjsx
@@ -58,7 +58,7 @@
     @setState
       freeShipSlot: freeShipSlot
   handleSetMapStartCheckItem: ->
-    enabled = @state.enableConfirmQuit
+    enabled = @state.mapStartCheckItem
     config.set 'poi.mapstartcheck.item', !enabled
     @setState
       mapStartCheckItem: !enabled


refactor: fix typo

refactor: fix typo

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -25,7 +25,7 @@
   setupRFB: ->
     canvasContainer = @element.getElementsByClassName("canvas-container")[0]
     childCount = canvasContainer.childElementCount
-    if noChild is 0
+    if childCount is 0
       newCanvas = document.createElement("canvas")
       newCanvas.setAttribute("class", "canvas")
       canvasContainer.appendChild(newCanvas)


Remove spine require in dashboard

Remove spine require in dashboard

--- app/controllers/Dashboard.coffee
+++ app/controllers/Dashboard.coffee
@@ -1,4 +1,3 @@
-Spine = require('spine')
 _      = require('underscore/underscore')
 ToolWindow = require('controllers/ToolWindow')


tweak syncstring max age...

tweak syncstring max age...

--- src/smc-project/client.coffee
+++ src/smc-project/client.coffee
@@ -21,7 +21,7 @@
 # not active for this long (should be at least 5 minutes).  Longer is better since
 # it reduces how long a user might have to wait for save, etc.,
 # but it slightly increases database work (managing a changefeed).
-SYNCSTRING_MAX_AGE_M = 20
+SYNCSTRING_MAX_AGE_M = 7
 #SYNCSTRING_MAX_AGE_M = 1 # TESTING

 {PROJECT_HUB_HEARTBEAT_INTERVAL_S} = require('smc-util/heartbeat')


synctable: backport a fix from the sync2 branch -- only open a table once signed in

--- src/smc-util/synctable.coffee
+++ src/smc-util/synctable.coffee
@@ -94,8 +94,13 @@
         # Whether or not currently successfully connected.
         @_connected = false

-        # Reconnect on connect.
-        @_client.on 'connected', =>
+        # Reconnect on event: either connected
+        if @_schema.anonymous
+            event = 'connected'
+        else
+            # query will completely fail anyways without being fully signed in -- so we wait for that.
+            event = 'signed_in'
+        @_client.on event, =>
             @_connected = false
             # We delete @_reconnecting to ensure that it immediately reconnects.
             # This is safe, since if we just connected, the only possibility for


term: change rendering pause resize logi

--- src/smc-webapp/console.coffee
+++ src/smc-webapp/console.coffee
@@ -505,8 +505,11 @@
             # NOTE: we could do this on all keystrokes.  WE restrict as above merely for efficiency purposes.
             # See http://stackoverflow.com/questions/3902635/how-does-one-capture-a-macs-command-key-via-javascript
             @textarea.val('')
-        if @_rendering_is_paused and not (ev.ctrlKey or ev.metaKey or ev.altKey)
-            @unpause_rendering()
+        if @_rendering_is_paused
+            if not (ev.ctrlKey or ev.metaKey or ev.altKey)
+                @unpause_rendering()
+            else
+                return false

     _increase_font_size: () =>
         @opts.font.size += 1
@@ -865,7 +868,7 @@
     # element for the editor, then resize the renderer and the
     # remote PTY.
     resize: =>
-        if not @user_was_recently_active()
+        if not @user_was_recently_active() or @_rendering_is_paused
             return

         if not @session?


Test pinning a new file

Test pinning a new file

Updated the test spec for the package to protect against the issue
described in #27, particular the steps described in the third comment.

--- spec/pinned-tabs-spec.coffee
+++ spec/pinned-tabs-spec.coffee
@@ -266,6 +266,19 @@
       PinnedTabs.pin item, tab
       expect(PinnedTabs.state.data[paneId]).toContain item.getURI()

+    it 'pins a new file', ->
+      waitsForPromise ->
+        atom.workspace.open('').then (item) ->
+          item = atom.workspace.getPaneItems()[1] # [0] = package.json from beforeEach
+
+          tabbar = document.createElement 'div'
+          tab = document.createElement 'div'
+          document.body.appendChild tabbar
+          tabbar.appendChild tab
+
+          PinnedTabs.pin item, tab
+          expect(tab.classList.contains 'pinned').toBeTruthy()
+
   describe '::isPinned()', ->
     it 'returns true if the tab is pinned', ->
       tab = document.createElement 'li'


Increase test repetition on CI

Increase test repetition on CI

--- spec/schema/main-spec.coffee
+++ spec/schema/main-spec.coffee
@@ -81,7 +81,7 @@

           # support for "exhaustive" testing, increase or set in .json spec
           # for detecting more bugs quickly by executing the same test N-times
-          nth = test.repeat or 1
+          nth = test.repeat or (if process.CI then 100 else 10)

           tasks = []
           tasks.push(tryTest(test, refs, schema)) while nth--


webapp: fix mailto link processing -- fixes issue #2437

--- src/smc-webapp/process-links.coffee
+++ src/smc-webapp/process-links.coffee
@@ -33,6 +33,8 @@
             if href?
                 if href[0] == '#'
                     # CASE: internal link on same document - do not touch (e.g., sections in jupyter/sagews)
+                    continue
+                if misc.startswith(href, 'mailto:')
                     continue
                 if opts.href_transform?
                     # special option; used, e.g., for Jupyter's attachment: url's


Drop tests for set/getFirstVisibleScreenRow

These are now tested in text-editor-component-spec

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -5521,72 +5521,6 @@
       editor.selectPageUp()
       expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [12, 2]]]

-  describe "::setFirstVisibleScreenRow() and ::getFirstVisibleScreenRow()", ->
-    beforeEach ->
-      line = Array(9).join('0123456789')
-      editor.setText([1..100].map(-> line).join('\n'))
-      expect(editor.getLineCount()).toBe 100
-      expect(editor.lineTextForBufferRow(0).length).toBe 80
-
-    describe "when the editor doesn't have a height and lineHeightInPixels", ->
-      it "does not affect the editor's visible row range", ->
-        expect(editor.getVisibleRowRange()).toBeNull()
-
-        editor.setFirstVisibleScreenRow(1)
-        expect(editor.getFirstVisibleScreenRow()).toEqual 1
-
-        editor.setFirstVisibleScreenRow(3)
-        expect(editor.getFirstVisibleScreenRow()).toEqual 3
-
-        expect(editor.getVisibleRowRange()).toBeNull()
-        expect(editor.getLastVisibleScreenRow()).toBeNull()
-
-    describe "when the editor has a height and lineHeightInPixels", ->
-      beforeEach ->
-        editor.update({scrollPastEnd: true})
-        editor.setHeight(100, true)
-        editor.setLineHeightInPixels(10)
-
-      it "updates the editor's visible row range", ->
-        editor.setFirstVisibleScreenRow(2)
-        expect(editor.getFirstVisibleScreenRow()).toEqual 2
-        expect(editor.getLastVisibleScreenRow()).toBe 12
-        expect(editor.getVisibleRowRange()).toEqual [2, 12]
-
-      it "notifies ::onDidChangeFirstVisibleScreenRow observers", ->
-        changeCount = 0
-        editor.onDidChangeFirstVisibleScreenRow -> changeCount++
-
-        editor.setFirstVisibleScreenRow(2)
-        expect(changeCount).toBe 1
-
-        editor.setFirstVisibleScreenRow(2)
-        expect(changeCount).toBe 1
-
-        editor.setFirstVisibleScreenRow(3)
-        expect(changeCount).toBe 2
-
-      it "ensures that the top row is less than the buffer's line count", ->
-        editor.setFirstVisibleScreenRow(102)
-        expect(editor.getFirstVisibleScreenRow()).toEqual 99
-        expect(editor.getVisibleRowRange()).toEqual [99, 99]
-
-      it "ensures that the left column is less than the length of the longest screen line", ->
-        editor.setFirstVisibleScreenRow(10)
-        expect(editor.getFirstVisibleScreenRow()).toEqual 10
-
-        editor.setText("\n\n\n")
-
-        editor.setFirstVisibleScreenRow(10)
-        expect(editor.getFirstVisibleScreenRow()).toEqual 3
-
-      describe "when the 'editor.scrollPastEnd' option is set to false", ->
-        it "ensures that the bottom row is less than the buffer's line count", ->
-          editor.update({scrollPastEnd: false})
-          editor.setFirstVisibleScreenRow(95)
-          expect(editor.getFirstVisibleScreenRow()).toEqual 89
-          expect(editor.getVisibleRowRange()).toEqual [89, 99]
-
   describe "::scrollToScreenPosition(position, [options])", ->
     it "triggers ::onDidRequestAutoscroll with the logical coordinates along with the options", ->
       scrollSpy = jasmine.createSpy("::onDidRequestAutoscroll")


found a bug in fetch_directory_listing via automated testing

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -564,7 +564,8 @@
     # Use current path if path not provided
     fetch_directory_listing: (path, sort_by_time, show_hidden) =>
         if not path?
-            path = @get_store().current_path
+            # This ? below is NEEDED!  -- there's no guarantee the store is defined yet.
+            path = @get_store()?.current_path
         if not path?
             # nothing to do if path isn't defined -- there is no current path -- see https://github.com/sagemathinc/smc/issues/818
             return


tennet

tennet

--- server/fetcher.coffee
+++ server/fetcher.coffee
@@ -10,15 +10,17 @@

 class Import

+class ImportTennet extends Import
+  url: (month) ->
+    "http://www.tennettso.de/site/Transparenz/veroeffentlichungen/netzkennzahlen/tatsaechliche-und-prognostizierte-windenergieeinspeisung/tatsaechliche-und-prognostizierte-windenergieeinspeisung/monthDataSheetCsv?monat=#{month}"

-ImportTennet =
   fetch: (month, cb) ->
-    request "http://www.tennettso.de/site/Transparenz/veroeffentlichungen/netzkennzahlen/tatsaechliche-und-prognostizierte-windenergieeinspeisung/tatsaechliche-und-prognostizierte-windenergieeinspeisung/monthDataSheetCsv?monat=#{month}",
-      (error, response, body) ->
+    request @url(month),
+      (error, response, body) =>
         csv.parse body,
           delimiter: ";",
-          (err, data) ->
-            ImportTennet.parse data, cb
+          (err, data) =>
+            @parse data, cb

   parse: (data, cb) ->
     current = today = false
@@ -38,8 +40,8 @@

         day[row[1]-1] =
           time: time
-          produced: Number row[2]
-          estimated: Number row[3]
+          actual: Number row[2]
+          forecast: Number row[3]

     cb days

@@ -82,5 +84,6 @@

     cb days

-importer = new Import50Hertz
-importer.fetch "2014-07-01", "wind", "actual", (days) -> console.log days
+importer = new ImportTennet
+importer.fetch "2014-07", (days) -> console.log days
+#importer.fetch "2014-07-01", "wind", "actual", (days) -> console.log days


fix: remove redundant `new` keyword

fix: remove redundant `new` keyword

--- server/lib/app.coffee
+++ server/lib/app.coffee
@@ -13,7 +13,7 @@

 utils = require("./utils")

-ProductProvider = new require("./product_provider")
+ProductProvider = require("./product_provider")
 productProvider = new ProductProvider()

 # bootstrap with dummy data


UI changes. New slack token

UI changes. New slack token

--- server/methods.coffee
+++ server/methods.coffee
@@ -1,7 +1,7 @@
 log = logger.bunyan.createLogger name:'method-invocation'

 loggedMethod = (name, f) -> ->
-  log.info method: name, arguments: arguments, client: @connection.clientAddress
+  log.info method: name, arguments: arguments, client: @connection.clientAddress, user: Meteor.user().username
   f.apply @, arguments

 logInvocation = (methods) ->


Style consistency fix

Style consistency fix

--- src/transaction-runner.coffee
+++ src/transaction-runner.coffee
@@ -477,9 +477,9 @@
                   for entityResult in data['results']
                     message += resultKey + ": " + entityResult['message'] + "\n"

-              test.message = message
-
-              transaction.results ?= {}
+              test['message'] = message
+
+              transaction['results'] ?= {}

               for key, value of gavelResult
                 if transaction['results'][key]?['results']?
@@ -490,12 +490,12 @@
                 if beforeResults?
                   transaction['results'][key]['results'] = transaction['results'][key]['results'].concat beforeResults

-              test.results = transaction.results
-
-              test.valid = isValid
+              test['results'] = transaction.['results']
+
+              test['valid'] = isValid

               # propagate test to after hooks
-              transaction.test = test
+              transaction['test'] = test

               if test.valid == false
                 configuration.emitter.emit 'test fail', test, () ->


Docs - target 5.1 allow 5 with subversions

--- scripts/docs.coffee
+++ scripts/docs.coffee
@@ -13,7 +13,7 @@
 cheerio   = require 'cheerio'
 htmlStrip = require 'htmlstrip-native'

-TARGET_VERSION = 5
+TARGET_VERSION = 5.1
 SEARCH_URL = 'https://www.google.com/search'
 USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36'
 #Rommie=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17
@@ -21,7 +21,6 @@
 module.exports = (robot) ->
   getQueryUrl = (doctype, version, query) ->
     version = 3 if version?.match /^3(.*)/i
-    version = 5 if version?.match /^5(.*)/i

     if doctype == 'api'
       if version == 3


don't truncate attribute stringification

--- src/textorum/relaxng/objects.coffee
+++ src/textorum/relaxng/objects.coffee
@@ -600,7 +600,7 @@
       return new Empty("good attribute: #{this}", this, attribute)

     toString: =>
-      "attribute '#{@nameClass} { #{@pattern} }".substr(0, 20) + "'"
+      "attribute #{@nameClass} { #{@pattern} }"

   ###*


Update maxChatWindows at runtime.

Update maxChatWindows at runtime.

--- app/assets/javascripts/app/controllers/chat.coffee
+++ app/assets/javascripts/app/controllers/chat.coffee
@@ -213,7 +213,7 @@

   settings: (errors = {}) ->
     new Setting(
-      maxChatWindows: @maxChatWindows
+      windowSpace: @
       errors: errors
     )

@@ -256,7 +256,7 @@
   constructor: ->
     super

-    @showTimeEveryXMinutes = 1
+    @showTimeEveryXMinutes = 2
     @lastTimestamp
     @lastAddedType
     @isTyping = false
@@ -552,7 +552,7 @@
     if !preferences.chat.phrase
       preferences.chat.phrase = {}
     if !preferences.chat.max_windows
-      preferences.chat.max_windows = @maxChatWindows
+      preferences.chat.max_windows = @windowSpace.maxChatWindows

     App.view('customer_chat/setting')(
       chats: App.Chat.all()
@@ -565,6 +565,9 @@
     params = @formParam(e.target)

     @formDisable(e)
+
+    # update runtime
+    @windowSpace.maxChatWindows = params.chat.max_windows

     # update user preferences
     @ajax(


Extend negative capacity series to full chart width

--- app/assets/javascripts/charts/load_chart.js.coffee
+++ app/assets/javascripts/charts/load_chart.js.coffee
@@ -58,7 +58,9 @@

   generateCapacity: (data) =>
     top_line = data[0].values.map((sample) => { x: sample.x, y: @capacity })
-    bottom_line = data[0].values.map((sample) => { x: sample.x, y: (@capacity * -1) })
+    bottom_line = data[0].values.reverse().map((sample) => { x: sample.x, y: (@capacity * -1) })
+
+    bottom_line.unshift(x: bottom_line[0].x + 1, y: bottom_line[0].y)

     total = bottom_line.concat top_line


switch bottom and top layer indices

switch bottom and top layer indices

--- app/assets/javascripts/forge/controllers.js.coffee
+++ app/assets/javascripts/forge/controllers.js.coffee
@@ -54,21 +54,21 @@
   $rootScope.$on 'layers:selected:top', (event, layer) ->
     $scope.layer = layer
     NgMap.getMap().then (map) ->
-      map.overlayMapTypes.removeAt(0)# if map.overlayMapTypes.length > 0
+      map.overlayMapTypes.removeAt(1)# if map.overlayMapTypes.length > 0
       if $scope.layer.id
         url = "/layers/#{$scope.layer.id}/wms?"
         # fitToBoundingBox(map, $scope.layer.bbox)
-        wmslayerTop = loadWMS map, url, null, 0
+        wmslayerTop = loadWMS map, url, null, 1
       else
         wmsLayerTop = null
   $rootScope.$on 'layers:selected:bottom', (event, layer2) ->
     $scope.layer2 = layer2
     NgMap.getMap().then (map) ->
-      map.overlayMapTypes.removeAt(1) #if map.overlayMapTypes.length > 0
+      map.overlayMapTypes.removeAt(0) #if map.overlayMapTypes.length > 0
       if $scope.layer2.id
         url = "/layers/#{$scope.layer2.id}/wms?"
         # fitToBoundingBox(map, $scope.layer.bbox)
-        wmslayerBottom = loadWMS map, url, null, 1
+        wmslayerBottom = loadWMS map, url, null, 0
       else
         wmsLayerBottom = null
   $rootScope.$on 'buildings:updated', (event) ->


Refactored password_strength configuration

--- app/assets/javascripts/password_strength.js.coffee
+++ app/assets/javascripts/password_strength.js.coffee
@@ -4,29 +4,25 @@

 overwritten_rules =
   wordSequences: false
+
+options =
+  showProgressBar: false
+  showVerdicts: false
+  showPopover: true
+  showErrors: true
+  showStatus: true
+  errorMessages: overwritten_messages

 $(document).ready ->
   profileOptions = {}
-  profileOptions.ui =
-    showProgressBar: false
-    showVerdicts: false
-    showPopover: true
-    showErrors: true
-    showStatus: true
-    errorMessages: overwritten_messages
+  profileOptions.ui = options
   profileOptions.rules =
     activated: overwritten_rules

   deviseOptions = {}
   deviseOptions.common =
     usernameField: "#user_username"
-  deviseOptions.ui =
-    showPopover: true
-    showErrors: true
-    showVerdicts: false
-    showProgressBar: false
-    showStatus: true
-    errorMessages: overwritten_messages
+  deviseOptions.ui = options
   deviseOptions.rules =
     activated: overwritten_rules


added debug info

added debug info

--- app/assets/javascripts/shared/mobile_sms.js.coffee
+++ app/assets/javascripts/shared/mobile_sms.js.coffee
@@ -1,13 +1,22 @@
 sms_delimiter = ->
+  alert("1")
   ua = navigator.userAgent.toLowerCase()
+  alert("2")
   if ua.indexOf('iphone') > -1 or ua.indexOf('ipad') > -1
+    alert("3")
     _iOSVersion = Number((navigator.userAgent.match(/\b[0-9]+_[0-9]+(?:_[0-9]+)?\b/) or [ '' ])[0].split('_')[0])
-    if (_iOSVersion < 8) then ';' else '&'
-  else '?'
+    alert("4")
+    a = if (_iOSVersion < 8) then ';' else '&'
+    alert("5")
+    return a
+  else
+    return '?'

 $ ->
   $sms_field = $('.mobile-sms')
   if $sms_field.length > 0
     mobile_href = $sms_field.attr('href').replace('?', sms_delimiter())
+    alert("6")
     window.location = mobile_href
+    alert("7")


Added indication for sponsor user when uploading logos.

--- app/assets/javascripts/sponsor/dashboard.js.coffee
+++ app/assets/javascripts/sponsor/dashboard.js.coffee
@@ -16,16 +16,18 @@
       centerOnScroll: true

   $('#activation_modal form').live('ajax:beforeSend', ->
-    $.fancybox.showActivity()
   ).live('ajax:success', (xhr, data, status) ->
     $.fancybox.close()
   ).live('ajax:error', (xhr, data, status) ->
+    $('#submit_logos').val 'Activate Account'
     $('.upload_errors').html ''
     errors = data.responseText
     printUploadErrors errors
   ).live('ajax:complete', (xhr, data, status) ->
-    $.fancybox.hideActivity()
   )
+
+  $('#submit_logos').live 'click', (event) ->
+    $(this).val 'sending...'

   $('#new_user').live('ajax:beforeSend', ->
     $('.errors').html ''


Transition tweak

Transition tweak

--- src/assets/javascripts/web/timeline/main.coffee
+++ src/assets/javascripts/web/timeline/main.coffee
@@ -159,10 +159,10 @@
     actors: [
       { element: '.frame--vcenter', opacity: opacity.out, translateY: translateY.out }
       { element: '#categories', translateY: [-50, -150] }
-      { element: '#category-0', opacity: [1, 0] }
-      { element: '#category-1', opacity: [1, -0.25] }
-      { element: '#category-2', opacity: [1, -0.5] }
-      { element: '#category-3', opacity: [1, -0.75] }
+      { element: '#category-0', opacity: [1, -0.75]}
+      { element: '#category-1', opacity: [1, -0.5] }
+      { element: '#category-2', opacity: [1, -0.25] }
+      { element: '#category-3', opacity: [1, 0] }
     ]
   }


Fix typo -- gg!

Fix typo -- gg!

--- app/lib/LevelLoader.coffee
+++ app/lib/LevelLoader.coffee
@@ -122,7 +122,7 @@
         majorVersion: @level.get('version').major
       creator: me.id
       state:
-        completegg: false
+        complete: false
         scripts: {}
       permissions: [
         {target: me.id, access: 'owner'}


Bring css and canvas bounding box locations into alignment

--- bokehjs/src/coffee/models/annotations/label.coffee
+++ bokehjs/src/coffee/models/annotations/label.coffee
@@ -2,15 +2,16 @@
 $ = require "jquery"

 Annotation = require "./annotation"
+ColumnDataSource = require "../sources/column_data_source"
 Renderer = require "../renderers/renderer"
 p = require "../../core/properties"

 class LabelView extends Renderer.View
   initialize: (options) ->
     super(options)
-
-    @set_visuals(@mget('source'))
-    @set_data(@mget('source'))
+    if not @mget('source')?
+      this.mset('source', new ColumnDataSource.Model())
+    @set_data()

   bind_bokeh_events: () ->
     if @mget('render_mode') == 'css'
@@ -18,6 +19,10 @@
       @listenTo(@model, 'data_update', @render)
     else
       @listenTo(@model, 'data_update', @plot_view.request_render)
+
+  set_data: () ->
+    super(@mget('source'))
+    @set_visuals(@mget('source'))

   _set_data: () ->
     @label_div = ($("<div>").addClass('label').hide() for i in @text)
@@ -30,7 +35,7 @@
     for i in [0...@text.length]
       @visuals.text.set_vectorize(ctx, i)
       @width[i] = ctx.measureText(@text[i]).width
-      @height[i] = ctx.measureText(@text[i]).ascent / 1.6 * 1.175
+      @height[i] = ctx.measureText(@text[i]).ascent / 1.175
       [ @x_shift[i], @y_shift[i] ] = @_calculate_offset(ctx, @height[i], @width[i])

   _calculate_offset: (ctx, height, width) ->
@@ -42,22 +47,20 @@
       x_shift = -width

     if ctx.textBaseline == 'top'
-      y_shift = 0.2 * height
+      y_shift = 0.0
     if ctx.textBaseline == 'middle'
-      y_shift = -0.4 * height
+      y_shift = -0.5 * height
     if ctx.textBaseline == 'bottom'
       y_shift = -1.0 * height
     if ctx.textBaseline == 'alphabetic'
       y_shift = -0.8 * height
     if ctx.textBaseline == 'hanging'
-      y_shift = -0.025 * height
+      y_shift = -0.17 * height

     return [x_shift, y_shift]

   render: () ->
-
-    @map_data() ##unclear why calling this in init returns NaNs
-
+    @map_data()
     if @mget('render_mode') == 'canvas'
       @_canvas_text()
     else
@@ -74,23 +77,27 @@
       ctx.beginPath()
       ctx.rect(@x_shift[i], @y_shift[i], @width[i], @height[i])

-      @visuals.fill.set_vectorize(ctx, i)
-      ctx.fill()
+      if @visuals.fill.doit
+        @visuals.fill.set_vectorize(ctx, i)
+        ctx.fill()

-      @visuals.line.set_vectorize(ctx, i)
-      ctx.stroke()
+      if @visuals.line.doit
+        @visuals.line.set_vectorize(ctx, i)
+        ctx.stroke()

-      @visuals.text.set_vectorize(ctx, i)
-      ctx.fillText(@text[i], 0, 0)
+      if @visuals.text.doit
+        @visuals.text.set_vectorize(ctx, i)
+        ctx.fillText(@text[i], 0, 0)
       ctx.restore()

   _css_text: () ->
     ctx = @plot_view.canvas_view.ctx

     for i in [0...@text.length]
+      @visuals.text.set_vectorize(ctx, i)

-      if @label_div[i].html() == ""
-        @label_div[i].appendTo(@plot_view.$el.find('div.bk-canvas-events'))
+      if not @label_div[i].style?
+        @label_div[i].appendTo(@plot_view.$el.find('div.bk-canvas-overlays')).hide()

       @label_div[i]
         .html(@text[i])
@@ -98,7 +105,7 @@
           'position': 'absolute'
           'top': "#{@sy[i] + @mget('y_offset') + @y_shift[i]}px"
           'left': "#{@sx[i] + @mget('x_offset') + @x_shift[i]}px"
-          # 'background-color': 'rgba(255, 255, 255, 0)'
+          # 'background-color': 'rgba(0, 255, 0, 0.2)'
           'font-size': "#{@text_font_size[i]}"
           'font-family': "#{@mget('text_font')}"
           })


citations work again, add this back

citations work again, add this back

--- static/embed/oab_embed.coffee
+++ static/embed/oab_embed.coffee
@@ -4,7 +4,7 @@
 _L.d = document
 _L.gebi = (id) -> return document.getElementById id.replace('#','')
 _L.gebc = (cls) -> return document.getElementsByClassName cls.replace('.','')
-_L.gebn = (n) ->
+_L.gebn = (n) ->
   r = document.getElementsByTagName n.replace('<','').replace('>','') # e.g. by the element name, like "div"
   return if r? then r else  document.getElementsByName n # otherwise by the "name" attribute matching n
 _L.each = (elems, key, val) ->
@@ -22,7 +22,7 @@
       if elem?
         if typeof key is 'function' then key(elem) else _L.set elem, key, val
 _L.listen = (action, els, fn) ->
-  _L.each els, (el) ->
+  _L.each els, (el) ->
     if action is 'enter'
       action = 'keyup'
       wfn = (e) -> fn(e) if e.keyCode is 13
@@ -33,14 +33,14 @@
       _L.class el, 'listen_'+action
       el.addEventListener action, (e) -> wfn(e)
 _L.show = (els, html, append) ->
-  _L.each els, (el) ->
+  _L.each els, (el) ->
     if typeof html is 'string'
       el.innerHTML = (if append then el.innerHTML else '') + html
     was = _L.get el, '_l_display'
     was = (if el.tagName is 'DIV' then 'block' else 'inline') if typeof was isnt 'string' or was is 'none' # TODO should be inline in which cases...
     el.style.display = was
 _L.hide = (els) ->
-  _L.each els, (el) ->
+  _L.each els, (el) ->
     if el.style.display isnt 'none'
       _L.set el, '_l_display', el.style.display
     el.style.display = 'none'
@@ -65,7 +65,7 @@
   return res
 _L.html = (els, html, append, show) ->
   rs = []
-  _L.each els, (el) ->
+  _L.each els, (el) ->
     if typeof html is 'string'
       el.innerHTML = (if append then el.innerHTML else '') + html
     rs.push el.innerHTML
@@ -73,7 +73,7 @@
   return if rs.length is 1 then (rs[0] ? '') else if rs.length then rs else ''
 _L.append = (els, html) -> _L.html els, html, true
 _L.remove = (els) -> _L.each els, (el) -> el.parentNode.removeChild el
-_L.class = (el, cls) ->
+_L.class = (el, cls) ->
   rs = []
   classes = el.getAttribute 'class'
   classes ?= ''
@@ -116,7 +116,7 @@
     n.appendChild(el.firstChild) while el.hasChildNodes()
   el.parentNode.replaceChild n, el
   return n
-
+
 _L.jx = (route, q, success, error, api, method, data, headers) ->
   # add auth options to this
   api ?= _L.api
@@ -157,7 +157,7 @@
       headers['Content-type'] ?= 'application/json'
   route += (if route.indexOf('?') is -1 then '?' else '&') + '_=' + Date.now() # set a random header to try to break any possible caching
   _L.jx route, undefined, success, error, api, 'POST', data, headers
-
+
 _L.dot = (obj, key, value, del) ->
   if typeof key is 'string'
     return API.collection.dot obj, key.split('.'), value, del
@@ -202,7 +202,7 @@
   this.data ?= {} # the data obj to send to backend
   this.f ?= {} # the result of the find/ill/permission request to the backend
   this.template ?= _oab[this.plugin + '_template'] # template or css can be passed in or are as defined below
-
+
   this.css ?= _oab.css
   this._loading = false # tracks when loads are occurring
   this.submit_after_metadata = false # used by instantill to track if metadata has been provided by user
@@ -258,7 +258,7 @@
   if load isnt true and (this._loading or load is false)
     try clearInterval this._loading
     this._loading = false
-    _L.each '._oab_loading', (el) =>
+    _L.each '._oab_loading', (el) =>
       if _L.has el, '_oab_continue'
         el.innerHTML = 'Continue'
       else if _L.has el, '_oab_submit'
@@ -300,7 +300,7 @@
             u += k + '=' + this.data[k] + window.location.hash
       window.history.pushState "", (if pop? then "search" else "find"), u
       if pop?
-        # what to do with the pop event? for now just triggers a restart if user
+        # what to do with the pop event? for now just triggers a restart if user
         # tries to go back, unless called with a boolean from the restart event
         this.restart() if typeof pop isnt 'boolean'

@@ -321,7 +321,7 @@
     this.find()
   else
     _L.set '#_oab_input', ''
-
+
 _oab.prototype.ping = (what) ->
   try
     if what.indexOf(this.plugin) is -1
@@ -414,7 +414,7 @@
             _L.show '#_oab_error', '
Please try again with your university email address.
'
           else
             _L.show '#_oab_error', '
Sorry, your email does not look right. ' + (if res isnt false then 'Did you mean ' + res + '? ' else '') + 'Please check and try again.
'
-        () =>
+        () =>
           this.data.email = _L.get('#_oab_email').trim()
           if this.plugin is 'instantill' then this.submit() else this.deposit()
       )
@@ -436,7 +436,7 @@
     return ''
   else
     config = JSON.parse JSON.stringify this.config
-    defaults =
+    defaults =
       sid: 'sid'
       title: 'atitle' # this is what iupui needs (title is also acceptable, but would clash with using title for journal title, which we set below, as iupui do that
       doi: 'rft_id' # don't know yet what this should be
@@ -766,7 +766,7 @@
     if this.submit_after_metadata
       this.submit()
       return
-
+
   this.data.title ?= this.data.atitle if this.data.atitle
   this.data.doi ?= this.data.rft_id if this.data.rft_id
   if this.data.doi and this.data.doi.indexOf('10.') isnt -1 and (this.data.doi.indexOf('/') is -1 or this.data.doi.indexOf('http') is 0)
@@ -882,8 +882,8 @@

     If you need an article you can request it from any library in the world through Interlibrary loan.
-
Start by entering a full article title, DOI or URL:

-

+
Start by entering a full article title, citation, DOI or URL:

+

<input class="_oab_form" type="text" id="_oab_input" placeholder="e.g. World Scientists Warning of a Climate Emergency" aria-label="Enter a search term" style="box-shadow:none;"></input>

Find article

@@ -942,7 +942,7 @@
 _oab.shareyourpaper_template = '

Make your research visible and see 30% more citations

-
We can help you make your paper Open Access, for free, wherever you publish. It\'s legal and takes just minutes.
+
We can help you make your paper Open Access, for free, wherever you publish. It\'s legal and takes just minutes.
   Join millions of researchers sharing their papers freely with colleagues and the public.

Start by entering the DOI of your paper

We\'ll gather information about your paper and find the easiest way to share it.

@@ -961,11 +961,11 @@

You may share your paper if you ask the journal

-
Unlike most, the journal requires that you ask them before you share your paper freely.
+
Unlike most, the journal requires that you ask them before you share your paper freely.
     Asking only takes a moment as we find out who to contact and have drafted an email for you.

Review Email

I\'ve got permission now!

-

+

Your paper is already freely available!

@@ -1004,7 +1004,7 @@

<input type="file" name="file" id="_oab_file" class="_oab_form">

By depositing you\'re agreeing to the terms and to license your work CC-BY.

-
+

Deposit

@@ -1023,12 +1023,12 @@

We\'ll double check your paper

You\'ve done your part for now. Hopefully, we\'ll send you a link soon. First, we\'ll check in the next working day to make sure it\'s legal to share.

-
+

Congrats, you\'re done!

Check back soon to see your paper live, or we\'ll email you with issues.

-
+

Congrats! Your paper will be available to everyone, forever!

@@ -1050,14 +1050,14 @@

You\'ve done your part

All that\'s left to do is wait. Once the journal gives you permission to share, come back and we\'ll help you finish the job.

-
+

Do another

'

-# can pass in a key/value pair, or key can be a config object, in which case val can optionally be a user ID string,
-# or key can be a user ID string and val must be empty, or key and val can both be empty and config will attempt
+# can pass in a key/value pair, or key can be a config object, in which case val can optionally be a user ID string,
+# or key can be a user ID string and val must be empty, or key and val can both be empty and config will attempt
 # to be retrieved from setup, or localstorage and/or from the API if a user ID is available from setup
 _oab.prototype.configure = (key, val, build, preview) ->
   if typeof key is 'string' and not val? and key.startsWith '{'
@@ -1071,9 +1071,9 @@
         lc = JSON.parse localStorage.getItem '_oab_config_' + this.plugin
         if typeof lc is 'object' and lc isnt null
           console.log 'Config retrieved from local storage'
-          this.config = lc
+          this.config = lc
     if this.remote isnt false and this.uid and this.uid isnt 'anonymous' and JSON.stringify(this.config) is '{}' # should a remote call always be made to check for superseded config if one is not provided at startup?
-      _L.jx this.api + '/' + (if this.plugin is 'instantill' then 'ill' else 'deposit') + '/config?uid='+this.uid, undefined, (res) =>
+      _L.jx this.api + '/' + (if this.plugin is 'instantill' then 'ill' else 'deposit') + '/config?uid='+this.uid, undefined, (res) =>
         console.log 'Config retrieved from API'
         this.configure res
       if this.local is false
@@ -1180,11 +1180,11 @@
     _L.listen 'click', '._oab_submit', (e) => this.submit(e)
     _L.listen 'click', '._oab_restart', (e) => this.restart(e)
     _L.listen 'click', '._oab_ping', (e) => this.ping(_L.get e.target, 'message')
-    _L.listen 'click', '._oab_wrong', (e) =>
+    _L.listen 'click', '._oab_wrong', (e) =>
       e.preventDefault()
       this.ping '_wrong_article'
       this.metadata()
-    _L.listen 'click', '._oab_reload', (e) =>
+    _L.listen 'click', '._oab_reload', (e) =>
       e.preventDefault()
       gf.value = '' if gf = _L.gebi "_oab_file"
       this.file = false
@@ -1192,7 +1192,7 @@
     _L.listen 'click', '._oab_confirm', (e) => e.preventDefault(); this.data.confirmed = true; this.deposit()
     _L.listen 'click','#_oab_reviewemail', (e) => this.done 'review'
     _L.listen 'click','._oab_deposit', (e) => this.deposit(e)
-
+
   if el = _L.gebi '_oab_config'
     el.innerHTML = JSON.stringify wc
   if window.location.search.indexOf('panel=') isnt -1
@@ -1212,4 +1212,4 @@
   return wc

 @shareyourpaper = (opts) -> opts ?= {}; opts.plugin = 'shareyourpaper'; return new _oab(opts);
-@instantill = (opts) -> opts ?= {}; opts.plugin = 'instantill'; return new _oab(opts);+@instantill = (opts) -> opts ?= {}; opts.plugin = 'instantill'; return new _oab(opts);


use different query more logic

use different query more logic

--- app/routes/branches.coffee
+++ app/routes/branches.coffee
@@ -14,15 +14,18 @@
     if @get('auth.signedIn')
       options.headers = { Authorization: "token #{@auth.token()}" }

-    $.ajax("#{apiEndpoint}/v3/repo/#{repoId}/branches?include=build.commit&limit=100", options).then (response) ->
+    $.ajax("#{apiEndpoint}/v3/repo/#{repoId}/branches?include=build.commit&limit=100&sort_by=exists_on_github", options).then (response) ->
       allTheBranches = response.branches
-      unless response['@pagination'].is_last
-        $.ajax("#{apiEndpoint}/v3/repo/#{repoId}/branches?include=build.commit&limit=100&offset=1", options).then (response) ->
+
+      defaultBranch = allTheBranches.filter (item, index) ->
+        item if item.repository.default_branch['@href'] == item['@href']
+
+      allTheBranches
+      unless defaultBranch.length == 1
+        $.ajax("#{apiEndpoint}/v3/repo/#{repoId}/branches?include=build.commit&limit=100&offset=1&sort_by=exists_on_github", options).then (response) ->
           allTheBranches = allTheBranches.concat(response.branches)
+
           allTheBranches
-      else
-        allTheBranches
-

   activate: () ->
     $('.tab.tabs--main li').removeClass('active')


editing codesnip wip

editing codesnip wip

--- client/landing/app/Applications/Activity.kdapplication/widgets/widgetcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/widgets/widgetcontroller.coffee
@@ -63,7 +63,7 @@
     if data.activity
       {activity} = data
       delete data.activity
-      activity.modify $set: data, (err, res)=>
+      activity.modify data, (err, res)=>
         callback? err, res
         unless err
           new KDNotificationView type : "mini", title : "Updated successfully"
@@ -80,7 +80,9 @@
   codeSnippetWidgetSubmit:(data, callback)->

     if data.activity
-      data.activity.modify data, (err, res)=>
+      {activity} = data
+      delete data.activity
+      activity.modify data, (err, res)=>
         callback? err, res
         unless err
           new KDNotificationView type : "mini", title : "Updated successfully"


better arg handling

better arg handling

--- dev/msg.iced
+++ dev/msg.iced
@@ -1,3 +1,4 @@
+#!/usr/bin/env iced

 fs = require 'fs'
 {make_esc} = require 'iced-error'
@@ -56,7 +57,31 @@

 #=================================================================

-argv = require('optimist').alias("m", "msg").alias("k","keyfile").alias("p","passphrase").argv
+argv = require('optimist')
+       .alias("m", "msg")
+       .alias("k","keyfile")
+       .alias("s", "sign")
+       .boolean("s")
+       .alias("e", "encrypt")
+       .boolean("e")
+       .usage("$0 -m <msg> -k <keyfile> -p <passphrase> -s -e")
+       .alias("p","passphrase").argv
+
+#=================================================================
+
+ok = false
+if not argv.msg?
+  console.error "need a msg file to operate on"
+else if not argv.keyfile?
+  console.error "need a keyfile to read keys from "
+else if (argv.s or not argv.e) and not argv.passphrase
+  console.error "need a passphrase to unlock a signing or decrypting key"
+else
+  ok = true
+
+process.exit -1 unless ok
+
+#=================================================================

 runner = new Runner { msgfile : argv.m, keyfile : argv.k, passphrase : argv.p }
 await runner.run defer err


- fixed a problem where a fact-check with 0 claims answered could be published

Former-commit-id: 414d5e5cf8a048f559201bb61d6385bb9151bf4c [formerly d2497b2aef750f89a3344f2c929efced735e3563] [formerly e375d1ac66c6f3fd665fa6a24cd7c32d632d9b78 [formerly e375d1ac66c6f3fd665fa6a24cd7c32d632d9b78 [formerly 09cd27e1b8a667825af4ca476e24aa4f3888b10f]]]
Former-commit-id: 7bf136fda09d55247b20b4b4c40b54323e60131a [formerly 690428fa7bcb92a62b72f8b6c2e2ed7baffa14a9]
Former-commit-id: 7e3df89bc5d801eb4e1b6075f04a9e45f89ec3f9
Former-commit-id: d48d8173c6bee8e4e3745cd44ac44435c7774daa

--- app/assets/javascripts/backbone/apps/dash/admin/assessment/edit/assess_edit_view.coffee
+++ app/assets/javascripts/backbone/apps/dash/admin/assessment/edit/assess_edit_view.coffee
@@ -166,12 +166,13 @@

     serializeData : ->
       current_user = ConsiderIt.request 'user:current'
-
+      has_claims = @model.getClaims().length > 0
+
       params = _.extend {}, @model.attributes,
         assessable : @model.getAssessable().attributes
         current_user : current_user.id
-        can_publish : @model.allClaimsApproved()
-        all_answered : @model.allClaimsAnswered()
+        can_publish : @model.allClaimsApproved() && has_claims
+        all_answered : @model.allClaimsAnswered() && has_claims

       params


Classroom: Added application name.

Classroom: Added application name.

--- client/landing/app/Applications/Classroom.kdapplication/Views/classroomworkspace.coffee
+++ client/landing/app/Applications/Classroom.kdapplication/Views/classroomworkspace.coffee
@@ -22,6 +22,7 @@

   extendOptions: (options) ->
     config = {}
+    config.name                = "Classroom"
     config.joinModalTitle      = "Join a coding session"
     config.joinModalContent    = "
Paste the session key that you received and start coding together.
"
     config.shareSessionKeyInfo = "
This is your session key, you can share this key with your friends to work together.
"


Use on_webgl_failed, on_context_lost, on_context_restored and no_s3tc

--- engine/render.coffee
+++ engine/render.coffee
@@ -11,7 +11,7 @@

 class RenderManager
-    constructor: (context, canvas, width, height, glflags, on_webgl_failed, on_context_lost, on_context_restored, no_s3tc)->
+    constructor: (context, canvas, width, height, glflags)->
         try
             gl = canvas.getContext("webgl", glflags) or canvas.getContext("experimental-webgl", glflags)
         catch e
@@ -27,7 +27,7 @@
                 gl = canvas.getContext("webgl", glflags) or canvas.getContext("experimental-webgl", glflags)

         if not gl
-            on_webgl_failed?()
+            context.MYOU_PARAMS.on_webgl_failed?()
             throw "Error: Can't start WebGL"

         @context = context
@@ -62,7 +62,7 @@
         @pixel_ratio_x = pixel_ratio_y = 1
         @camera_z = vec3.create()
         @lod_factor = 1
-        @no_s3tc = no_s3tc
+        @no_s3tc = @context.MYOU_PARAMS.no_s3tc
         ba = @context.MYOU_PARAMS.background_alpha
         @background_alpha = if ba? then ba else 1
         @compiled_shaders_this_frame = 0
@@ -89,11 +89,11 @@

         lost = (event)->
             event.preventDefault()
-            on_context_lost?()
+            @context.MYOU_PARAMS.on_context_lost?()
             render_manager.clear_context()
         restored = (event)->
             render_manager.restore_context()
-            on_context_restored? and requestAnimationFrame(on_context_restored)
+            @context.MYOU_PARAMS.on_context_restored? and requestAnimationFrame(@context.MYOU_PARAMS.on_context_restored)
         canvas.addEventListener "webglcontextlost", lost, false
         canvas.addEventListener "webglcontextrestored", restored, false
         @initialize()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -17,7 +17,7 @@
   equals 3.ceil(), 3, "(3).ceil() equals 3"

 test "#clamp", ->
-  equals 5.
+  equals 5.clamp(0, 3), 3 ""

 test "#floor", ->
   equals 4.9.floor(), 4, "(4.9).floor() equals 4"


virtualization: comment out the bits about requiring membership for now

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -45,23 +45,23 @@
     @fetchDefaultVmName (defaultVm)=>
       vm or= defaultVm
       return  unless vm
-      KD.requireMembership
-        callback : =>
-          @askForApprove command, (approved)=>
-            if approved
-              cb = unless command is 'vm.info' then @_cbWrapper vm, callback \
-                   else callback
-              @run
-                kiteName : 'os'
-                method   : command
-                vmName   : vm
-              , cb
-            else unless command is 'vm.info' then @info vm
-        onFailMsg : "Login required to use VMs"  unless command is 'vm.info'
-        onFail    : =>
-          unless command is 'vm.info' then callback yes
-          else callback null, state: 'STOPPED'
-        silence   : yes
+      # KD.requireMembership
+      #   callback : =>
+      @askForApprove command, (approved)=>
+        if approved
+          cb = unless command is 'vm.info' then @_cbWrapper vm, callback \
+               else callback
+          @run
+            kiteName : 'os'
+            method   : command
+            vmName   : vm
+          , cb
+        else unless command is 'vm.info' then @info vm
+        # onFailMsg : "Login required to use VMs"  unless command is 'vm.info'
+        # onFail    : =>
+        #   unless command is 'vm.info' then callback yes
+        #   else callback null, state: 'STOPPED'
+        # silence   : yes

   start:(vm, callback)->
     @_runWraper 'vm.start', vm, callback


Add schema/datatype end-to-end tests with a component

--- spec/Component.coffee
+++ spec/Component.coffee
@@ -613,6 +613,105 @@
         groups: ['foo']
       sin2.post new noflo.IP 'data', 'bar',
         groups: ['bar']
+
+    it 'should stamp IP objects with the datatype of the outport when sending', (done) ->
+      c = new noflo.Component
+        inPorts:
+          foo: datatype: 'all'
+        outPorts:
+          baz: datatype: 'string'
+        process: (input, output) ->
+          return unless input.has 'foo'
+          foo = input.get 'foo'
+          output.sendDone
+            baz: foo
+
+      c.inPorts.foo.attach sin1
+      c.outPorts.baz.attach sout1
+
+      sout1.once 'ip', (ip) ->
+        chai.expect(ip).to.be.an 'object'
+        chai.expect(ip.type).to.equal 'data'
+        chai.expect(ip.data).to.equal 'foo'
+        chai.expect(ip.datatype).to.equal 'string'
+        done()
+
+      sin1.post new noflo.IP 'data', 'foo'
+    it 'should stamp IP objects with the datatype of the inport when receiving', (done) ->
+      c = new noflo.Component
+        inPorts:
+          foo: datatype: 'string'
+        outPorts:
+          baz: datatype: 'all'
+        process: (input, output) ->
+          return unless input.has 'foo'
+          foo = input.get 'foo'
+          output.sendDone
+            baz: foo
+
+      c.inPorts.foo.attach sin1
+      c.outPorts.baz.attach sout1
+
+      sout1.once 'ip', (ip) ->
+        chai.expect(ip).to.be.an 'object'
+        chai.expect(ip.type).to.equal 'data'
+        chai.expect(ip.data).to.equal 'foo'
+        chai.expect(ip.datatype).to.equal 'string'
+        done()
+
+      sin1.post new noflo.IP 'data', 'foo'
+    it 'should stamp IP objects with the schema of the outport when sending', (done) ->
+      c = new noflo.Component
+        inPorts:
+          foo: datatype: 'all'
+        outPorts:
+          baz:
+            datatype: 'string'
+            schema: 'text/markdown'
+        process: (input, output) ->
+          return unless input.has 'foo'
+          foo = input.get 'foo'
+          output.sendDone
+            baz: foo
+
+      c.inPorts.foo.attach sin1
+      c.outPorts.baz.attach sout1
+
+      sout1.once 'ip', (ip) ->
+        chai.expect(ip).to.be.an 'object'
+        chai.expect(ip.type).to.equal 'data'
+        chai.expect(ip.data).to.equal 'foo'
+        chai.expect(ip.datatype).to.equal 'string'
+        chai.expect(ip.schema).to.equal 'text/markdown'
+        done()
+
+      sin1.post new noflo.IP 'data', 'foo'
+    it 'should stamp IP objects with the schema of the inport when receiving', (done) ->
+      c = new noflo.Component
+        inPorts:
+          foo:
+            datatype: 'string'
+            schema: 'text/markdown'
+        outPorts:
+          baz: datatype: 'all'
+        process: (input, output) ->
+          return unless input.has 'foo'
+          foo = input.get 'foo'
+          output.sendDone
+            baz: foo
+
+      c.inPorts.foo.attach sin1
+      c.outPorts.baz.attach sout1
+
+      sout1.once 'ip', (ip) ->
+        chai.expect(ip).to.be.an 'object'
+        chai.expect(ip.type).to.equal 'data'
+        chai.expect(ip.data).to.equal 'foo'
+        chai.expect(ip.datatype).to.equal 'string'
+        chai.expect(ip.schema).to.equal 'text/markdown'
+        done()
+
+      sin1.post new noflo.IP 'data', 'foo'

     it 'should receive and send just IP data if wanted', (done) ->
       c = new noflo.Component


spec: asar: fs.readFile

spec: asar: fs.readFile

--- spec/asar-spec.coffee
+++ spec/asar-spec.coffee
@@ -29,3 +29,28 @@
         p = path.join fixtures, 'asar', 'a.asar', 'not-exist'
         throws = -> fs.readFileSync p
         assert.throws throws, /ENOENT/
+
+    describe 'fs.readFile', ->
+      it 'reads a normal file', (done) ->
+        p = path.join fixtures, 'asar', 'a.asar', 'file1'
+        fs.readFile p, (err, content) ->
+          assert.equal String(content), 'file1\n'
+          done()
+
+      it 'reads a linked file', (done) ->
+        p = path.join fixtures, 'asar', 'a.asar', 'link1'
+        fs.readFile p, (err, content) ->
+          assert.equal String(content), 'file1\n'
+          done()
+
+      it 'reads a file from linked directory', (done) ->
+        p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'
+        fs.readFile p, (err, content) ->
+          assert.equal String(content), 'file1\n'
+          done()
+
+      it 'throws ENOENT error when can not find a file', (done) ->
+        p = path.join fixtures, 'asar', 'a.asar', 'not-exist'
+        fs.readFile p, (err, content) ->
+          assert.equal err.code, 'ENOENT'
+          done()


Fixed test

Fixed test

--- spec/main-spec.coffee
+++ spec/main-spec.coffee
@@ -50,7 +50,7 @@
             return

           if test.hasNot
-            expect(sample.toString()).not.toContain test.hasNot
+            expect(JSON.stringify sample).not.toContain test.hasNot

           if test.type
             expect(sample).toHaveType test.type


More logging.

More logging.

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -31,11 +31,12 @@

     withAuthor = (callback) -> (id, created, room, user, body) ->
       if user is null
-        user = bot.Me
-        console.log user.id
-        author = self.userForId(user.id, user)
-        author.room = room
-        callback id, created, room, user, body, author
+        bot.Me (userData) ->
+          console.log "Callback using bot.Me"
+          author = self.userForId(userData.user.id, userData.user)
+          author.room = room
+          console.log author.room
+          callback id, created, room, user, body, author
       else
         bot.User user, (err, userData) ->
           if userData.user


Fix failing specs

Fix failing specs

Atom has now been upgraded to use customElements inplace of
document.registerElement. We now need to attach to the DOM on some specs

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -21,6 +21,7 @@

   describe ".activate()", ->
     it "appends a tab bar all existing and new panes", ->
+      jasmine.attachToDOM(centerElement)
       expect(centerElement.querySelectorAll('.pane').length).toBe 1
       expect(centerElement.querySelectorAll('.pane > .tab-bar').length).toBe 1

@@ -36,6 +37,7 @@
     it "removes all tab bar views and stops adding them to new panes", ->
       pane = atom.workspace.getActivePane()
       pane.splitRight()
+      jasmine.attachToDOM(centerElement)
       expect(centerElement.querySelectorAll('.pane').length).toBe 2
       expect(centerElement.querySelectorAll('.pane > .tab-bar').length).toBe 2

@@ -929,6 +931,7 @@

     describe "when a tab is dragged over a pane item", ->
       beforeEach ->
+        jasmine.attachToDOM(atom.workspace.getElement())
         layout.activate()

       afterEach ->


transport: Add test for multiple consumers

--- spec/transport.coffee
+++ spec/transport.coffee
@@ -5,6 +5,7 @@

 Coordinator = require('../src/coordinator').Coordinator
 transport = require '../src/transport'
+common = require '../src/common'
 participants = require './fixtures/participants'

 # Note: most require running an external broker service
@@ -42,8 +43,9 @@

 createBindQueues = (broker, queueMapping, callback) ->
   createBindQueue = (det, cb) ->
-    [client, srcQ, tgtQ] = det
-    client.createQueue 'outqueue', srcQ, (err) ->
+    [client, type, srcQ, tgtQ] = det
+    createQ = if type == 'outqueue' then srcQ else tgtQ
+    client.createQueue type, createQ, (err) ->
       return cb err if err
       broker.bindQueue srcQ, tgtQ, cb

@@ -55,6 +57,17 @@
     client.sendToQueue queue, data, cb

   async.map packets, send, callback
+
+subscribeData = (handlers, callback) ->
+  sub = (h, cb) ->
+    [client, queue, handler] = h
+    ackHandler = (msg) ->
+      client.ackMessage msg
+      return handler msg
+    client.subscribeToQueue queue, ackHandler, cb
+
+  async.map handlers, sub, callback
+

 describe 'Transport', ->

@@ -134,9 +147,10 @@
           sender = transport.getClient address
           receiver = transport.getClient address
           payload = { foo: 'bar99' }
-          inQueue = 'inqueue23'
-          outQueue = 'outqueue32'
+          inQueue = 'inqueue232'
+          outQueue = 'outqueue353'
           onReceive = (msg) ->
+            receiver.ackMessage msg
             chai.expect(msg).to.include.keys 'data'
             chai.expect(msg.data).to.eql payload
             done()
@@ -189,7 +203,7 @@
                 # Bind all outqueues to same inqueue
                 queueMapping = []
                 for name in senders
-                  queueMapping.push [ clients[name], name, inQueue ]
+                  queueMapping.push [ clients[name], 'outqueue', name, inQueue ]
                 createBindQueues broker, queueMapping, (err) ->
                   chai.expect(err).to.not.exist

@@ -199,3 +213,51 @@
                   sendPackets packets, (err) ->
                     chai.expect(err).to.not.exist

+
+      describe 'multiple inqueues bound to one outqueue', ->
+        it 'data sent on outqueue shows up on all inqueues', (done) ->
+          @timeout 3000
+          senders = [ 'sender' ]
+          receivers = ['r1', 'r2', 'r3']
+          clientNames = common.clone receivers
+          clientNames.push.apply clientNames, senders
+          createConnectClients address, clientNames, (err, clients) ->
+            chai.expect(err).to.not.exist
+
+            expect = [ {q:'r1',d:'ident'}, {q:'r2',d:'ident'}, {q:'r3',d:'ident'} ]
+
+            received = []
+            checkExpected = (q, msg) ->
+              received.push { q: q, d: msg.data.data }
+              if received.length == expect.length
+                received.sort (a,b) ->
+                  return -1 if a.q < b.q
+                  return 1 if a.q > b.q
+                  return 0
+                chai.expect(received).to.eql expect
+                done()
+
+            onReceives =
+              r1: (msg) -> checkExpected 'r1', msg
+              r2: (msg) -> checkExpected 'r2', msg
+              r3: (msg) -> checkExpected 'r3', msg
+
+            outQueue2 = 'outqueue39'
+            clients.sender.createQueue 'outqueue', outQueue2, (err) ->
+              chai.expect(err).to.not.exist
+
+              # Bind same outqueue to all inqueues
+              queueMapping = []
+              for name in receivers
+                queueMapping.push [ clients[name], 'inqueue', outQueue2, name ]
+              createBindQueues broker, queueMapping, (err) ->
+                chai.expect(err).to.not.exist
+
+                handlers = []
+                for name in receivers
+                  handlers.push [ clients[name], name, onReceives[name] ]
+                subscribeData handlers, (err) ->
+                  chai.expect(err).to.not.exist
+                  clients.sender.sendToQueue outQueue2, {data: 'ident'}, (err) ->
+                    chai.expect(err).to.not.exist
+


token transaction blue print

token transaction blue print

--- src/blueprints.coffee
+++ src/blueprints.coffee
@@ -94,6 +94,7 @@
   'subscriber'
   'user'
   'wallet'
+  'tokentransaction'
 ]

 for model in models


Allow display of up to 1000 pending invitations

--- angular/core/components/memberships_page/pending_invitations_card/pending_invitations_card.coffee
+++ angular/core/components/memberships_page/pending_invitations_card/pending_invitations_card.coffee
@@ -8,7 +8,7 @@
       CurrentUser.isAdminOf($scope.group)

     if $scope.canSeeInvitations()
-      Records.invitations.fetchPendingByGroup($scope.group.key)
+      Records.invitations.fetchPendingByGroup($scope.group.key, per: 1000)

     $scope.baseUrl = AppConfig.baseUrl


Operation creates a marker immediately when given a buffer

--- src/packages/command-panel/lib/operation.coffee
+++ src/packages/command-panel/lib/operation.coffee
@@ -1,7 +1,9 @@
 module.exports =
 class Operation
   constructor: ({@project, @path, @buffer, @bufferRange, @newText, @preserveSelection, @errorMessage}) ->
-    @buffer?.retain()
+    if @buffer?
+      @buffer.retain()
+      @getMarker()

   getMarker: ->
     @marker ?= @getBuffer().markRange(@bufferRange)


refactor(watch): simplify the watch for build option gulp-compile-js-html-imports

--- src/plugins/gulp-compile-js-html-imports.coffee
+++ src/plugins/gulp-compile-js-html-imports.coffee
@@ -9,13 +9,11 @@
 fse         = require 'fs-extra'
 PLUGIN_NAME = 'gulp-compile-js-html-imports'
 PluginError = gutil.PluginError
+require('colors').setTheme error:['red','bold'] unless 'colors'.error

-# Helpers
+# HELPERS
 # =======
 Help =
-	logJson: (json) -> # log pretty json
-		console.log JSON.stringify json, null, '\t'
-
 	inspectFile: (file, sort=false) -> # log vinyl props
 		props = base: null, relative: null, extname: null, stem: null, basename: null, cwd: null, dirname: null, path: null, event: null
 		if sort then Object.keys(props).sort().forEach (key) -> delete props[key]; props[key] = null
@@ -23,16 +21,20 @@
 		json = JSON.stringify(props, null, '\t').replace(/"/g, "'").replace /(\t)'(\w*)'/g, '$1$2'
 		console.log json

+	logJson: (json) -> # log pretty json
+		console.log JSON.stringify json, null, '\t'
+
+	isObject: (val) ->
+		return false if typeof val isnt 'object'
+		return false if val is null
+		return false if Array.isArray val
+		true
+
 	isEmptyObject: (obj) -> # :boolean
-		return false unless typeof obj is 'object'
+		return false unless @isObject obj
 		!!Object.getOwnPropertyNames(obj).length

-	hasImports: (obj) -> # :boolean (obj = js file object in HtmlImports)
-		return false unless typeof obj is 'object'
-		@isEmptyObject obj['imports']
-
-
-# Regular Expressions
+# REGULAR EXPRESSIONS
 # ===================
 Regx =
 	htmlImports:
@@ -49,103 +51,85 @@
 		# /\btemplate(?=\n|\s?;|\s(?!\S))/g
 		new RegExp "\\b#{variable}(?=\\n|\\s?;|\\s(?!\\S))", 'g'

-# Html Imports
-# jsPath:
+# HTML IMPORTS (hashmap)
+# jsPath:                 (dist/client/scripts/xxx.js)
 # 	imports:
-# 		htmlPath:
-# 			statement: ''
-# 			variable: ''
-# 			html: ''
-# 	contents:
-# 		uncompiled: ''
-# 		compiled: ''
-# paths     = absolute
-# statement = import template from '../views/rb-nav.html';
-# variable  = template
+# 		htmlPath:         (dist/client/views/xxx.html)
+# 			statement: '' (import template from '../views/xxx.html';)
+# 			variable: ''  (template)
+# 			html: ''      (html file contents)
 # ======================================================================
 HtmlImports = {}

-# Compiler
+# COMPILER
 # ========
 InlineImports =
-	js:
-		changed:  false # (boolean) is file.contents same as HtmlImports.jsPath.compiledJS
-		dir:      null  # dist/client/scripts
-		path:     null  # dist/client/scripts/xxx.js
-		relPath:  null  # scripts/xxx.js
-		contents: null  # original uncompiled js contents, needed for html watch
+	# Public Methods
+	# ==============
+	get: (file, opts={}) -> # :@JS (run in order)
+		@initJS file
+		@setHtmlImportVars()
+		@updateJS changed: @didFileChange()
+		# console.log 'CHANGED:', @JS.changed
+		return @JS unless @JS.changed
+		@htmlImportReplace()
+		@templateVarReplace()
+		# Help.logJson @JS
+		# Help.logJson HtmlImports
+		@JS

-	get: (file, opts={}) -> # :@js (run in order)
-		@_setJsProps file
-		@_setHtmlImportVars()
-		@_setJsChanged()
-		return @js unless @js.changed
-		js = @_htmlImportReplace @js.contents
-		js = @_templateVarReplace js
-		@_setHtmlImportContents js
-		@js.contents = js
-		# console.log js
-		# Help.inspectFile file
-		# Help.logJson @js
-		# Help.logJson HtmlImports
-		# Help.logJson returnVal
-		@js
+	# Private Methods
+	# ===============
+	initJS: (file) -> # :void (@JS will be the return value for @get())
+		_path = path.join file.base, file.relative
+		@JS =
+			changed:  false                    # changed if js contents has html import(s)
+			dir:      path.dirname _path       # dist/client/scripts
+			path:     _path                    # dist/client/scripts/xxx.js
+			relPath:  file.relative            # scripts/xxx.js
+			contents: file.contents.toString() # original uninlined js contents (needed for html watch)

-	_setJsProps: (file) -> # :void (run in order)
-		@js.relPath  = file.relative
-		@js.path     = path.join file.base, @js.relPath
-		@js.dir      = path.dirname @js.path
-		@js.contents = file.contents.toString()
+	updateJS: (props={}) -> # :void
+		return unless Help.isEmptyObject props
+		for key, val of props
+			continue if typeof @JS[key] is 'undefined'
+			@JS[key] = val

-	_setJsChanged: -> # :void
-		@js.changed = @_didFileChange()
+	didFileChange: -> # :boolean
+		!!HtmlImports[@JS.path]

-	_didFileChange: -> # :boolean
-		return false unless HtmlImports[@js.path]
-		@js.contents isnt HtmlImports[@js.path].contents.compiled
-
-	_setHtmlImportVars: -> # :void
+	setHtmlImportVars: -> # :void
 		htmlImports = {}
-		while (match = Regx.htmlImports.exec(@js.contents)) != null
+		while (match = Regx.htmlImports.exec(@JS.contents)) != null
 			statement      = match[0] # import template from '../views/xxx.html';
 			variable       = match[1] # template
 			importPath     = match[2] # ../views/xxx.html
-			importDistPath = path.join @js.dir, importPath # dist/client/views/xxx.html
+			importDistPath = path.join @JS.dir, importPath # dist/client/views/xxx.html
 			try
 				html = fse.readFileSync importDistPath, 'utf8'
 			catch e
-				e.message = "html import file in #{@js.relPath} doesn't exist: #{importPath}"
+				e.message = "html import file in #{@JS.relPath} doesn't exist: #{importPath}"
 				console.error e.message.error
 				continue
 			htmlImport = { statement, variable, html }
 			htmlImports[importDistPath] = htmlImport

 		hasImports = Help.isEmptyObject htmlImports
-		return delete HtmlImports[@js.path] unless hasImports
-		HtmlImports[@js.path] = {} unless HtmlImports[@js.path]
-		# Object.assign HtmlImports[@js.path], htmlImports
-		HtmlImports[@js.path].imports  = htmlImports
-		HtmlImports[@js.path].contents = {}
+		return delete HtmlImports[@JS.path] unless hasImports
+		HtmlImports[@JS.path] = {} unless HtmlImports[@JS.path]
+		HtmlImports[@JS.path].imports = htmlImports

-	_setHtmlImportContents: (js) -> # :void
-		return js unless Help.hasImports HtmlImports[@js.path]
-		contents = uncompiled: @js.contents, compiled: js
-		HtmlImports[@js.path].contents = contents
+	htmlImportReplace: -> # :void
+		return unless !!HtmlImports[@JS.path]
+		for htmlPath, htmlImport of HtmlImports[@JS.path].imports
+			@JS.contents = @JS.contents.replace Regx.htmlImport(htmlImport.statement), ''

-	_htmlImportReplace: (js) -> # :string | null
-		return js unless Help.hasImports HtmlImports[@js.path]
-		for htmlPath, htmlImport of HtmlImports[@js.path].imports
-			js = js.replace Regx.htmlImport(htmlImport.statement), ''
-		js
+	templateVarReplace: -> # :void
+		return unless !!HtmlImports[@JS.path]
+		for htmlPath, htmlImport of HtmlImports[@JS.path].imports
+			@JS.contents = @JS.contents.replace Regx.templateVar(htmlImport.variable), "`#{htmlImport.html}`"

-	_templateVarReplace: (js) -> # :string
-		return js unless Help.hasImports HtmlImports[@js.path]
-		for htmlPath, htmlImport of HtmlImports[@js.path].imports
-			js = js.replace Regx.templateVar(htmlImport.variable), "`#{htmlImport.html}`"
-			js
-		js
-
-# Gulp Plugin
+# GULP PLUGIN
 # ===========
 inlineJsHtmlImports = (opts={}) ->
 	through.obj (file, enc, cb) ->
@@ -162,6 +146,6 @@

 		cb null, file

-# Export It!
+# EXPORT IT!
 # ==========
 module.exports = inlineJsHtmlImports


ability to set html for subtitle in hawaiiheader template

--- @client/customizations_helpers.coffee
+++ @client/customizations_helpers.coffee
@@ -482,11 +482,14 @@
         opts.title

       if homepage && opts.subtitle
-
+        subtitle_is_html = opts.subtitle.indexOf('<') > -1 && opts.subtitle.indexOf('>') > -1
         DIV
           style: opts.subtitle_style

-          opts.subtitle
+          dangerouslySetInnerHTML: if subtitle_is_html then {__html: opts.subtitle}
+
+          if !subtitle_is_html
+            opts.subtitle

       if homepage && customization('homepage_tabs')
         DIV


revisions to uses copy & icons

revisions to uses copy & icons

Former-commit-id: 483a338c619d51c5c5dfeb3890efc79db28a3d62 [formerly ff65618d898dbe09ec162a3dcc8e25bfad84c411] [formerly b53fc9f6e941d160efa52784e4c342667bcecfa0 [formerly 6ad01bcee9ea2280857a32abefdac186f9b0c840]] [formerly 6ce8eaf75b5a14677a3f5dd55f4207ed24520daf [formerly ae35d2e6d9cb7c6a01752199d4e7611f7f375a97] [formerly 64a0ad0349174c77f57e91cdd4143a31ef622531 [formerly 64a0ad0349174c77f57e91cdd4143a31ef622531 [formerly 06920517b0ac62ecef5e247e4577a805f81cbd19]]]]
Former-commit-id: 0c19688938246240c51a44067b6925a0a565fd8a [formerly e1b158452f68d8b308400d7f826ceafb28f57276] [formerly b8337536887c2d80b3ee47b60880949487dc3880 [formerly 8040788d7ecd6c8aa956619174df3c16d42a7bcd]]
Former-commit-id: 812b80da8427d379df535ec5758e267dbeab3fad [formerly 65b85e89f6e75632dd6257ab7780f6fb20e1f27d]
Former-commit-id: fca411efb5015c7a2575d243ddda7a58c8f2d71f
Former-commit-id: 577792d5ed2b118b4ae773ceaec0808d61e3aaca

--- @client/saas_landing_page/uses.coffee
+++ @client/saas_landing_page/uses.coffee
@@ -5,11 +5,11 @@
     icon: 'doc'
     strong: 'Collect feedback'
     body: """
-          You have a draft plan or policy. Or an idea for a new product direction,
-          architecture, or design. Consider.it can help you gather organized
-          feedback from anyone you invite to participate. Improve your idea
-          with the insights of others, without having to sort through a long
-          email chain.
+          You have a draft proposal. Maybe it’s a plan, a policy, or an idea for
+          a new product direction. Whatever it may be, Consider.it can help you
+          gather organized feedback from anyone you invite to participate.
+          Improve your proposal with the insights of others, without having to
+          sort through a long email chain.
           """
   }, {
     icon: 'crossroads'
@@ -39,17 +39,18 @@
     icon: 'public'
     strong: "Engage the public"
     body: """
-          Make decisions as a whole, without resorting to hierarchy. The will of a community,
-          and the thoughts behind that will, become visible and actionable.
+          Enable constituents to provide input on an upcoming decision. Consider.it
+          organizes this feedback into a guide to public thought that can be used
+          to refine the proposal or target common misconceptions during outreach.
           """

   }, {
     icon: 'meeting'
     strong: 'Conduct meetings'
     body: """
-          Plan more effective meetings by creating agendas on Consider.it.
-          And after a meeting, don’t set up a followup if you can just use
-          Consider.it to tie up the loose ends!
+          Plan more effective meetings by creating agendas on Consider.it. Thresh
+          through ideas before a meeting. After a meeting, use Consider.it to
+          close the loose ends!
           """
   }
 ]
@@ -80,7 +81,7 @@
         style: _.extend {}, h1,
           color: 'white'

-        'What can you do better with Consider.it?'
+        'What can Consider.it help you do better?'

       for u, idx in uses
         use _.extend {}, u,


artik710.coffee: Change state: experimental -> HIDDEN

Signed-off-by: Michal Mazurek <a2df1e659c9fd2578de0a26565357cb273292eeb@resin.io>

--- artik710.coffee
+++ artik710.coffee
@@ -18,7 +18,7 @@
 	aliases: [ 'artik710' ]
 	name: 'Samsung Artik 710'
 	arch: 'aarch64'
-	state: 'experimental'
+	state: 'HIDDEN'

 	stateInstructions:
 		postProvisioning: postProvisioningInstructions


title edit shows the current title instead of empty title

--- scripts/views/workspace/sidebar/toc-branch.coffee
+++ scripts/views/workspace/sidebar/toc-branch.coffee
@@ -213,9 +213,9 @@

     editSettings: ->
-      model = @model.dereferencePointer?() or @model
-
-      title = prompt('Edit Title:', model.getTitle?(@container) or model.get('title'))
-      if title then model.setTitle?(@container, title) or model.set('title', title)
+      # Use `.toJSON().title` instead of `.get('title')` to support
+      # TocPointerNodes which inherit their title if it is not overridden
+      title = prompt('Edit Title:', @model.toJSON().title)
+      if title then @model.set('title', title)

       @renderModelOnly()


Coffeescript scoping.

Coffeescript scoping.

--- server/migrations/0018-publication-cacheid.coffee
+++ server/migrations/0018-publication-cacheid.coffee
@@ -17,7 +17,7 @@
       return callback error if error
       cursor = collection.find {_schema: currentSchema, cached: {$exists: true}, cachedId: {$exists: false}}, {source: 1, foreignId: 1}
       document = null
-      async.doWhilst Meteor.bindEnvironment (callback) =>
+      async.doWhilst Meteor.bindEnvironment((callback) =>
           cursor.nextObject Meteor.bindEnvironment (error, doc) =>
             return callback error if error
             document = doc
@@ -41,7 +41,7 @@
               , callback)()
             , callback
           , callback
-        , callback
+        , callback)
       ,
         =>
           document
@@ -57,7 +57,7 @@
       return callback error if error
       cursor = collection.find {_schema: currentSchema, cached: {$exists: true}, cachedId: $exists: true}, {cachedId: 1, source: 1, foreignId: 1}
       document = null
-      async.doWhilst Meteor.bindEnvironment (callback) =>
+      async.doWhilst Meteor.bindEnvironment((callback) =>
           cursor.nextObject Meteor.bindEnvironment (error, doc) =>
             return callback error if error
             document = doc
@@ -79,7 +79,7 @@
               , callback)()
             , callback
           , callback
-        , callback
+        , callback)
       ,
         =>
           document


Fixed Issue with check_logged_in

Fixed Issue with check_logged_in

For some reason, SOMER.blink now has an hr tag at the bottom of the page
with the id of blink_logged_out even when you're logged in. This change
limits the selector to just divs.

--- src/coffee/background.coffee
+++ src/coffee/background.coffee
@@ -18,7 +18,7 @@
         @page = $(data)

   check_logged_in: ->
-    if $('#blink_logged_out', @page).length > 0
+    if $('div#blink_logged_out', @page).length > 0
       chrome.browserAction.setBadgeBackgroundColor color: [255, 0, 0, 255]
       chrome.browserAction.setBadgeText text: ":("


Don't try and install cli commands if they are already symlinked.

Closes #1548

--- src/command-installer.coffee
+++ src/command-installer.coffee
@@ -35,14 +35,20 @@

     commandName = path.basename(commandPath, path.extname(commandPath))
     destinationPath = path.join(@getInstallDirectory(), commandName)
-    symlinkCommand commandPath, destinationPath, (error) =>
-      if askForPrivilege and error?.code is 'EACCES'
-        try
-          error = null
-          symlinkCommandWithPrivilegeSync(commandPath, destinationPath)
-        catch error

-      callback?(error)
+    fs.readlink destinationPath, (error, realpath) ->
+      if realpath == commandPath
+        callback()
+        return
+
+      symlinkCommand commandPath, destinationPath, (error) =>
+        if askForPrivilege and error?.code is 'EACCES'
+          try
+            error = null
+            symlinkCommandWithPrivilegeSync(commandPath, destinationPath)
+          catch error
+
+        callback?(error)

   installAtomCommand: (resourcePath, askForPrivilege, callback) ->
     commandPath = path.join(resourcePath, 'atom.sh')


Allow multiple arguments to be passed to modes when switching.


Add header 'variantId'.

Add header 'variantId'.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -182,9 +182,11 @@
             console.error "Problems on reading template file '#{opts.template}': " + err
             process.exit 2
           Csv().from.string(content).to.array (data, count) ->
+            header = data[0]
+            header.push ['variantId']
             csv = variants.groupVariants _.rest(data), opts.headerIndex
             exporter = new Exporter()
-            exporter._saveCSV opts.out, csv
+            exporter._saveCSV opts.out, [header].concat(csv)

     program.parse argv
     program.help() if program.args.length is 0


fix(export): remove erroneous default values

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -284,8 +284,8 @@
       .option '--queryEncoded', 'Whether the given query string is already encoded or not', false
       .option '--fillAllRows', 'When given product attributes like name will be added to each variant row.'
       .option '--categoryBy <name>', 'Define which identifier should be used to for the categories column - either slug or externalId. If nothing given the named path is used.'
-      .option '--filterVariantsByAttributes <query>', 'Query string to filter variants of products', 'query'
-      .option '--filterPrices <query>', 'Query string to filter prices of products', 'query'
+      .option '--filterVariantsByAttributes <query>', 'Query string to filter variants of products'
+      .option '--filterPrices <query>', 'Query string to filter prices of products'
       .usage '--projectKey <project-key> --clientId <client-id> --clientSecret <client-secret> --template <file> --out <file>'
       .action (opts) =>
         GLOBALS.DEFAULT_LANGUAGE = opts.language


Remove comment

Remove comment

--- src/coffeelint.coffee
+++ src/coffeelint.coffee
@@ -575,8 +575,6 @@
         else
             null

-    # have to send in the raw value of the prevToken
-    # because doing @peek(-1) does not contain the 'spaced' property
     lintArrowSpacing : (token) ->
         # Throw error unless the following happens.
         unless (token.spaced? or token.newLine?) and


Bug fix: async's callback was called twice

--- application.coffee
+++ application.coffee
@@ -52,7 +52,11 @@
 		shouldRestartApp = Boolean(@process)
 		tasks = [
 			# Stop the application if running
-			(callback) => shouldRestartApp and @_stop(callback) or callback()
+			(callback) =>
+				if shouldRestartApp
+					@_stop(callback)
+				else
+					callback()

 			# Pull new commits
 			(callback) =>
@@ -63,7 +67,11 @@
 				spawn('npm', ['install'], @options).on('exit', callback).on('error', callback)

 			# Start the app
-			(callback) => shouldRestartApp and @_start(callback) or callback()
+			(callback) =>
+				if shouldRestartApp
+					@_start(callback)
+				else
+					callback()
 		]
 		async.series(tasks, callback)


Sort by name

Sort by name

--- resources/assets/coffee/_classes/beatmapset-filter.coffee
+++ resources/assets/coffee/_classes/beatmapset-filter.coffee
@@ -19,6 +19,13 @@
 parseInt10 = (string) -> parseInt string, 10

 class @BeatmapsetFilter
+  @cast:
+    mode: parseInt10
+    status: parseInt10
+    genre: parseInt10
+    language: parseInt10
+
+
   @charToKey:
     m: 'mode'
     s: 'status'
@@ -41,10 +48,4 @@
     query: ''

-  @cast:
-    mode: parseInt10
-    status: parseInt10
-    genre: parseInt10
-    language: parseInt10
-
   @expand: ['genre', 'language', 'extra', 'rate']


add more statistics to log messages

add more statistics to log messages

--- src/controllers/index.coffee
+++ src/controllers/index.coffee
@@ -138,7 +138,7 @@
             console.log 'Error: ' + error
             return

-          #console.log 'I recieved response from geonames:'
+          console.log 'I recieved response from geonames:'
           counter += 1
           img_url = (rsp.client._httpMessage.path.match /uri\=.+&/)[0].slice 4, -1
           #console.log img_url


Less hasty lasso tweak

Less hasty lasso tweak

--- src/controller.coffee
+++ src/controller.coffee
@@ -379,6 +379,7 @@

             # Push this to the set of floating blocks
             floating_blocks.push block: selection, position: dest
+          else lassoSegment = null # Unlasso if we just deleted the lasso.

       else if focus?
         # Make the selection


Rename markedBlockKey to nextMarkedBlockId

--- src/controller.coffee
+++ src/controller.coffee
@@ -3347,7 +3347,7 @@

   hook 'populate', 0, ->
     @markedLines = {}
-    @markedBlocks = {}; @markedBlockKey = 0
+    @markedBlocks = {}; @nextMarkedBlockId = 0
     @extraMarks = {}

   Editor::getHighlightPath = (model, style) ->
@@ -3397,7 +3397,7 @@

     # `key` is a unique identifier for this
     # mark, to be used later for removal
-    key = @markedBlockKey++
+    key = @nextMarkedBlockId++

     @markedBlocks[key] = {
       model: head.container


Fix #3281: move triggers group out of harm's way before Ajax response

--- form-builder/jvm/src/main/assets/forms/orbeon/builder/resources/cell/action-icons.coffee
+++ form-builder/jvm/src/main/assets/forms/orbeon/builder/resources/cell/action-icons.coffee
@@ -53,7 +53,8 @@

 $ ->

-    # Save reference to last operation that positioned the triggers
+    # Save reference to last groups of triggers and to operation that positioned the triggers
+    lastTriggerGroups = null
     lastPositionTriggers = null

     # Create a div.fb-hover inside the td, if there isn't one already
@@ -70,10 +71,10 @@
     tidyTriggerGroups = (triggerGroups) ->
         $(triggerGroups).hide()
         $('.fb-cell-editor').append(triggerGroups)
-        lastPositionTriggers = null

     Builder.mouseEntersGridTdEvent.subscribe ({triggers, triggerGroups, gridTd}) ->
         gridTdId = gridTd.id
+        lastTriggerGroups = triggerGroups
         lastPositionTriggers = ->
             # Move cell editor controls under this td
             gridTd = YD.get gridTdId
@@ -95,6 +96,7 @@
                     trigger.style.display = if triggerRelevant then '' else 'none'
             else
                 tidyTriggerGroups(triggerGroups)
+                lastPositionTriggers = null
         lastPositionTriggers()

     # Normally, we add the div.fb-hover when the mouse first enters the .fr-grid-td, but for the TinyMCE, if we do
@@ -112,6 +114,7 @@
     # See: https://github.com/orbeon/orbeon-forms/issues/44
     Builder.mouseExitsGridTdEvent.subscribe ({triggerGroups, gridTd}) ->
         tidyTriggerGroups(triggerGroups)
+        lastPositionTriggers = null

     # Change current cell on click on trigger
     Builder.triggerClickEvent.subscribe ({trigger}) ->
@@ -121,6 +124,10 @@
         event = new AjaxServer.Event form, activable.id, null, "DOMActivate"
         AjaxServer.fireEvents [event]

+    # Before we process a response from the server, move trigger move out of harm's way
+    AjaxServer.ajaxResponseReceived.add ->
+        tidyTriggerGroups(lastTriggerGroups)
     # Reposition triggers after an Ajax request
     Events.ajaxResponseProcessedEvent.subscribe ->
         lastPositionTriggers() if lastPositionTriggers?
+


Fix tab and shift-tab bug

Fix tab and shift-tab bug

--- src/controller.coffee
+++ src/controller.coffee
@@ -2213,7 +2213,7 @@
       head = head.prev

     return chars
-
+
   getSocketAtChar = (parent, chars) ->
     head = parent.start
     charsCounted = 0
@@ -2230,8 +2230,10 @@
       if @textFocus? then head = @textFocus.start
       else head = @cursor

-      until (not head?) or head.type is 'socketEnd' and head.container.start.next.type is 'text'
-        head = head.prev
+      until (not head?) or head.type is 'socketStart' and
+          (head.container.start.next.type is 'text' or head.container.start.next is head.container.end)
+        head = head.next
+
       if head?
         if @textFocus? and head.container.hasParent @textFocus.parent
           persistentParent = @textFocus.parent.parent
@@ -2250,7 +2252,8 @@
       if @textFocus? then head = @textFocus.end
       else head = @cursor

-      until (not head?) or head.type is 'socketStart' and head.container.start.next.type is 'text'
+      until (not head?) or head.type is 'socketStart' and
+          (head.container.start.next.type is 'text' or head.container.start.next is head.container.end)
         head = head.next
       if head?
         if @textFocus? and head.container.hasParent @textFocus.parent


Add extra space at the bottom of the screen

--- src/controller.coffee
+++ src/controller.coffee
@@ -3606,6 +3606,12 @@
       bounds.unite @view.getViewNodeFor(record.block).getBounds()

     @mainScrollerStuffing.style.width = "#{bounds.right()}px"
+
+    # We add some extra height to the bottom
+    # of the document so that the last block isn't
+    # jammed up against the edge of the screen.
+    #
+    # Default this extra space to fontSize (approx. 1 line).
     @mainScrollerStuffing.style.height = "#{bounds.bottom() + (@options.extraBottomHeight ? @fontSize)}px"

   hook 'redraw_palette', 0, ->


horizontal scroller fine-tuning

horizontal scroller fine-tuning

--- src/JCSGYK/AdminBundle/Resources/public/js/hblocks.coffee
+++ src/JCSGYK/AdminBundle/Resources/public/js/hblocks.coffee
@@ -217,7 +217,17 @@
         blockW = @blockW()
         # try to center the selected block
         x = Math.round((block - 1) * blockW - (($("#content").width() - blockW) / 2))
-        $(".contentwrapper").animate({scrollLeft: x}, 500, ->
+        if x < 0
+          x = 0
+
+        # calculate the animation time based on the scroll distance
+        dist =  Math.abs($(".contentwrapper").scrollLeft() - x);
+        anim_duration = 500 * (dist / blockW);
+        # minimal duration for smooth animations
+        if anim_duration < 250
+          anim_duration = 250
+        #console.log(blockW, x, dist, anim_duration)
+        $(".contentwrapper").animate({scrollLeft: x}, anim_duration, ->
             if $.isFunction(complete)
                 complete()
         )


Atom changes

Atom changes

--- etc/atom/config.cson
+++ etc/atom/config.cson
@@ -1,4 +1,4 @@
-'global':
+'*':
   'exception-reporting':
     'userId': 'f87332bf-54f7-bc72-aeba-5b58d78cb5d4'
   'release-notes':
@@ -9,9 +9,9 @@
     'userId': '305d8eb962055d82cd69f9974fcc3c7d0e3f288e'
   'editor':
     'fontFamily': 'Source Code Pro'
+    'fontSize': 14
     'showIndentGuide': true
     'softWrapAtPreferredLineLength': true
-    'fontSize': 14
     'invisibles': {}
   'core':
     'themes': [


Add config

Add config

--- etc/atom/config.cson
+++ etc/atom/config.cson
@@ -12,6 +12,7 @@
     'showIndentGuide': true
     'softWrapAtPreferredLineLength': true
     'fontSize': 14
+    'invisibles': {}
   'core':
     'themes': [
       'atom-dark-ui'


Fix exception TypeError: Cannot read property 'statusCode' of null

--- packages/rocketchat-integrations/server/triggers.coffee
+++ packages/rocketchat-integrations/server/triggers.coffee
@@ -235,14 +235,14 @@
 			if result?
 				logger.outgoing.error 'Error for trigger', trigger.name, 'to', url, result

-			if result.statusCode is 410
-				RocketChat.models.Integrations.remove _id: trigger._id
-				return
-
-			if result.statusCode is 500
-				logger.outgoing.error 'Error [500] for trigger', trigger.name, 'to', url
-				logger.outgoing.error result.content
-				return
+				if result.statusCode is 410
+					RocketChat.models.Integrations.remove _id: trigger._id
+					return
+
+				if result.statusCode is 500
+					logger.outgoing.error 'Error [500] for trigger', trigger.name, 'to', url
+					logger.outgoing.error result.content
+					return

 			if tries <= 6
 				# Try again in 0.1s, 1s, 10s, 1m40s, 16m40s, 2h46m40s and 27h46m40s


No need to specify path when path is the same as name

--- assets/scripts/app/routes.coffee
+++ assets/scripts/app/routes.coffee
@@ -52,8 +52,8 @@
 Travis.Router.map ->
   @resource 'index', path: '/', ->
     @resource 'getting_started'
-    @route 'recent', path: '/recent'
-    @route 'my_repositories', path: '/my_repositories'
+    @route 'recent'
+    @route 'my_repositories'
     @resource 'repo', path: '/:owner/:name', ->
       @route 'index', path: '/'
       @resource 'build', path: '/builds/:build_id'
@@ -166,7 +166,6 @@
     @currentRepoDidChange()
     @controllerFor('repos').addObserver('firstObject', this, 'currentRepoDidChange')

-
   deactivate: ->
     @controllerFor('repos').removeObserver('firstObject', this, 'currentRepoDidChange')


wording changes

wording changes

Former-commit-id: 01a1e2ec8513d92a745a6925f0168c0002a3914f [formerly 0b61f8e9d3bbad67a0d642245ee31aceaba64c9a] [formerly 58267ca679cd7afa6ac07e170147e0335be21447 [formerly 68039de5821740ebc8cffd3af2db805d525eea16]] [formerly 9c10a012185e1d5041e2aa0a334d8963a360ff58 [formerly 57479b9852bf7e5a34cc8dd8d0edf743d862105e] [formerly 8cb12bb016f81ebe7f0d02494ea5e555ad537454 [formerly 8cb12bb016f81ebe7f0d02494ea5e555ad537454 [formerly 809ce6441e01ba8e979102fd089f60c2fae2df76]]]]
Former-commit-id: 5567953f75f186ae865085aad3ad6e148ef7b8cb [formerly ae7166ee47f0b79578eb6d15b0e28d3643d75ed0] [formerly c79a84b2623788497ddd5b48b6d0776b630d88c7 [formerly 4677ef0d4df6dae8098a9f5e881c2e5ce557e310]]
Former-commit-id: 284d25ace94c4488929f62c499eb5e52f6b2716e [formerly c5bdd43cb9636957a7969924e0d09418d2d49f0f]
Former-commit-id: bcc3018862c2a9c5a09b1ee0cf68d1f9fa3e6572
Former-commit-id: e6fbf07aa11b219cb5228ebcf60670730cf2a6a7

--- @client/saas_landing_page.coffee
+++ @client/saas_landing_page.coffee
@@ -64,7 +64,7 @@
           e.g. \"caption,controls,video\"
           Must contain 'video' somewhere.
           """
-        frame: 'laptop'
+        frame: 'browser'
         frame_instr: """"
           Whether to draw a frame around the video, and if so, what it should be.
           Supported values:
@@ -384,31 +384,37 @@
           if v.currentTime < 5.5
             "Proposals often require careful thought"
           else if v.currentTime < 10.5
-            "The Pro/Con list encourages thinking about tradeoffs"
+            # "The Pro/Con list encourages thinking about tradeoffs"
+            # "The Pro/Con list emphasizes tradeoffs"
+            # "The Pro/Con list emphasizes considering tradeoffs"
+            # "The Pro/Con list focuses thought on tradeoffs"
+            # "The Pro/Con list stresses tradeoffs"
+            "The Pro/Con list focuses attention on tradeoffs"
+
           else if v.currentTime < 18
-            "Singular points describe a consideration"
+            "Each thought becomes a Pro or Con point"
           else if v.currentTime < 25
-            "Learn and build from other\’s thoughts"
+            "Learn and include the thoughts of peers"
           else if v.currentTime < 33.5
-            "Use a slider to express your conclusion"
+            "Express your conclusion on a sliding scale"
           else if v.currentTime < 39
-            "Integrate your considered opinion with the group"
+            "Add your opinion to the group"
           else if v.currentTime < 43
-            "Histogram shows the spectrum of opinion"
+            "The spectrum of opinion is shown on a Histogram"
           else if v.currentTime < 46
-            "Points ranked by salience across all individuals"
+            "Points are ranked by importance for the group"
           else if v.currentTime < 55
             "Now explore patterns of thought!"
           else if v.currentTime < 61
-            "Understand the reservations of proposal opposers"
+            "Learn the reservations of opposers"
           else if v.currentTime < 67.5
-            "Inspect what a particular individual believes"
+            "Inspect a peer's opinion"
           else if v.currentTime < 72
-            "Figure out who has been persuaded by the top Pro"
+            "See who has been persuaded by the top Pro"
           else if v.currentTime < 87
-            "Drill into points for focused discussion"
+            "Have a focused discussion about a single point"
           else
-            ""
+            "...and those are the basics of Consider.it!"

         controls.value = v.currentTime / v.duration

@@ -880,7 +886,7 @@
         margin: 'auto'
         color: 'white'

-      'Friend, that is our story.'
+      'That is our story, new friend!'
       BR null,
       'Tell us '
       A


Bug fix for recursive view model create: https://github.com/kmalakoff/knockback/issues/53

--- src/knockback-core/knockback-collection-observable.coffee
+++ src/knockback-core/knockback-collection-observable.coffee
@@ -311,8 +311,8 @@
         observable = kb.utils.wrappedObservable(@)
         collection = @_collection()
         return if (view_model = @viewModelByModel(arg)) # it may have already been added by a change event
+        @in_edit++
         view_model = @_createViewModel(arg)
-        @in_edit++
         if (comparator = @_comparator())
           observable().push(view_model)
           observable.sort(comparator)


Ask for student id when confirming course

--- src/course/registration.cjsx
+++ src/course/registration.cjsx
@@ -21,10 +21,10 @@
   startRegistration: ->
     course = @getCourse()
     course.set(ecosystem_book_uuid: @props.collectionUUID)
-    course.register(React.findDOMNode(@refs.input).value)
+    course.register(React.findDOMNode(@refs.inviteInput).value)

   startConfirmation: ->
-    @getCourse().confirm()
+    @getCourse().confirm(React.findDOMNode(@refs.stduentIdInput).value)

   cancelConfirmation: ->
     @getCourse().cancelJoin()
@@ -80,7 +80,7 @@
           Course invitation code:

             <input
-              ref='input' autofocus
+              ref='inviteInput' autofocus
               onKeyPress={@onKeyPress} type="text" className="form-control"
             />

@@ -96,19 +96,27 @@
     Saving ...

   renderPending: (course) ->
-
+

         Would you like to join {course.description()}?

       {@renderErrors()}
-

-        <button className="btn"
-          onClick={@cancelConfirmation}>Cancel</button>
-        <button className="btn btn-success"
-          style={marginLeft: '3rem'}
-          onClick={@startConfirmation}>Confirm</button>
+

+
+          My Student ID is:
+          <input ref='stduentIdInput' autofocus
+            onKeyPress={@onKeyPress} type="text" className="form-control" />
+
+
+

+          <button className="btn"
+            onClick={@cancelConfirmation}>Cancel</button>
+          <button className="btn btn-success"
+            style={marginLeft: '3rem'}
+            onClick={@startConfirmation}>Confirm</button>
+

-
+

   renderComplete: (course) ->


Make it so replies clear out after submission.

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -806,7 +806,7 @@
       slot.postComment {text:message}, getSelection(), replyTo?.comment.id
       .then (c) ->
           slot.getSlot(slot.segment, ['comments']).then((res) ->
-              $scope.form.comment.text = ''
+              $scope.form.comment.text = $scope.form.reply = ''
               $scope.comments = (new Comment(comment) for comment in res.comments)
             , (res) ->
                 messages.addError


Fix file add/upload blank slot

Fix file add/upload blank slot

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -738,12 +738,6 @@
             return
         return

-      @addBlank = ->
-        unless blank
-          $scope.assets.push(blank = new Asset())
-        blank.choose()
-        blank
-
       @sort = ->
         return unless $scope.assets
         $scope.assets.sort (a, b) ->
@@ -769,6 +763,12 @@
         for e in slot.excerpts
           assets[e.id]?.excerpts.push(new Excerpt(e))
         return
+
+    $scope.addBlank = ->
+      unless blank
+        $scope.assets.push(blank = new Asset())
+      blank.choose()
+      blank

     $scope.fileAdded = (file) ->
       $flow = file.flowObj


Fixes #3031

Fixes #3031

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -366,6 +366,7 @@
       fillData: ->
         @data =
           measures: angular.extend({}, @record.measures)
+          add: ''
         return

       Object.defineProperty @prototype, 'id',


if position not specified we resolve position based on link/node associated

--- app/coffeescripts/Util.coffee
+++ app/coffeescripts/Util.coffee
@@ -5,18 +5,33 @@
     Math.round(num * Math.pow(10,dec)) / Math.pow(10,dec)

   @_getLat: (elem) ->
-    if elem.get('position')?
-      lat = elem.get('position').get('point')[0].get('lat')
-    if elem.get('display_position')
-      lat = elem.get('display_position').get('point')[0].get('lat')
-    lat
+    @_getElementLatOrLng(elem, 'lat')

   @_getLng: (elem) ->
+    @_getElementLatOrLng(elem, 'lng')
+
+  # called by getLat, getLng to first get the position and point attribute
+  # (this can be complicated depending on the type of element, some store
+  # as display_position and some store as position). In some cases, there
+  # is no position specified in this case we get the position of the node
+  # or the links begin node that it is associated with.
+  @_getElementLatOrLng: (elem, type) ->
     if elem.get('position')?
-      lng = elem.get('position').get('point')[0].get('lng')
-    if elem.get('display_position')
-      lng = elem.get('display_position').get('point')[0].get('lng')
-    lng
+      pos = elem.get('position')
+    else if elem.get('display_position')?
+      pos = elem.get('display_position')
+    else if elem.get('node')?
+      pos = elem.get('node').get('position')
+      @_offsetPosition(pos) # offset just to left of the node
+    else if elem.get('link')?
+      pos = elem.get('link').get('begin').get('node').get('position')
+      @_offsetPosition(pos) # offset just to left of the link's begin node
+
+    pos.get('point')[0].get(type)
+
+  # used to move the symbol just off to the left of the node
+  @_offsetPosition: (pos) ->
+    pos.get('point')[0].set({'lng' : pos.get('point')[0].get('lng') - .0002})

   # returns a google LatLng obect by retrieving the latitude and longitude
   # from the elements object. In some cases it is stored in position and


Fix typo (#41)

Fix typo (#41)

--- app/lib/tutorial_steps.coffee
+++ app/lib/tutorial_steps.coffee
@@ -37,7 +37,7 @@
     nextOn: 'click': 'button[name="finish"]'

   new Step
-    content: 'Some images will have many deposits, some will have none. Some fan will share a source -- please mark them all!'
+    content: 'Some images will have many deposits, some will have none. Some fans will share a source -- please mark them all!'
     attachment: x: 'left', to: '.followup .talk', at: x: 'right'

   new Step


messaging around rate limited dump workers

--- app/pages/lab/data-dumps.cjsx
+++ app/pages/lab/data-dumps.cjsx
@@ -27,7 +27,8 @@

   render: ->

-
Get your project's data exports

+
Project data exports

+
Please note that the Zooniverse will process at most 1 of each export within a 24 hour period

           Project Data


Main header links to Lab index

Main header links to Lab index

--- app/partials/main-header.cjsx
+++ app/partials/main-header.cjsx
@@ -54,7 +54,7 @@
         <Translate content="mainNav.discuss" />

         {if @state.user?
-          <Link to="build" className="main-nav-item"><Translate className="minor" content="mainNav.lab" /></Link>}
+          <Link to="lab" className="main-nav-item"><Translate className="minor" content="mainNav.lab" /></Link>}


add language-chef

add language-chef

--- atom/packages.cson
+++ atom/packages.cson
@@ -23,6 +23,7 @@
   "gruvbox"
   "jshint"
   "jumpy"
+  "language-chef"
   "language-diff"
   "language-docker"
   "language-elixir"


Add some TypeScript snippets

Add some TypeScript snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -1,3 +1,52 @@
+'.source.ts':
+  Describe:
+    prefix: 'desc'
+    body: """
+    describe('$1', () => {
+      $0
+    })
+    """
+  AsyncDescribe:
+    prefix: 'desca'
+    body: """
+    describe('$1', async () => {
+      $0
+    })
+    """
+  AfterEach:
+    prefix: 'afe'
+    body: """
+    afterEach(() => {
+      $0
+    })
+    """
+  BeforeEach:
+    prefix: 'befe'
+    body: """
+    beforeEach(() => {
+      $0
+    })
+    """
+  AsyncIt:
+    prefix: 'ita'
+    body: """
+    it('$1', async () => {
+      $0
+    })
+    """
+  It:
+    prefix: 'it'
+    body: """
+    it('$1', () => {
+      $0
+    })
+    """
+  Expect:
+    prefix: 'ex'
+    body: "expect($1).to"
+  ExpectEqual:
+    prefix: 'exeq'
+    body: "expect($1).to.equal($2)"
 '.source.js':
   Babel:
     prefix: 'bab'
@@ -215,6 +264,13 @@
   'Expect Exception':
     prefix: 'exex'
     body: 'expect fn -> ${1:body} end |> to(raise_exception ${2:Error})'
+  'Test with context':
+    prefix: 'testc'
+    body: """
+    test "$1", context do
+      $0
+    end
+    """
 '.source.ruby':
   Before:
     prefix: 'bef'


Intercom integration is successful

Intercom integration is successful

--- app/routes/application.coffee
+++ app/routes/application.coffee
@@ -141,6 +141,10 @@
             email: user.get "email"
             id: user.get "id"
           Raven.captureMessage "User Logged in", level: "info"
+          window.Intercom "boot",
+            app_id: "mbkqc0o1"
+            name: user.get "username"
+            email: user.get "email"
         controller.set 'initLoaded', true
         resolve result


Fix weak dependency error

Fix weak dependency error

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -1,3 +1,6 @@
+AutoForm = Package['aldeed:autoform'].AutoForm
+return unless AutoForm
+
 Forms =

   defineModelForm: (formArgs) ->