Bindings related code has been moved to separated repo

--- impl/base/level1/binding.coffee
+++ impl/base/level1/binding.coffee
@@ -1,163 +1,22 @@
 'use strict'

-utils = require 'neft-utils'
-signal = require 'neft-signal'
-log = require 'neft-log'
-assert = require 'neft-assert'
-
-log = log.scope 'Renderer', 'Binding'
-
-{isArray} = Array
-
-getPropHandlerName = do ->
-	cache = Object.create null
-	(prop) ->
-		cache[prop] ||= "on#{utils.capitalize(prop)}Change"
-
-MAX_LOOPS = 50
+Binding = require 'neft-binding'

 module.exports = (impl) ->
-	{items} = impl

-	class Connection
-		pool = []
-
-		@factory = (binding, item, prop, parent=null) ->
-			if pool.length > 0 and (elem = pool.pop())
-				Connection.call elem, binding, item, prop, parent
-				elem
-			else
-				new Connection binding, item, prop, parent
-
-		constructor: (@binding, item, @prop, @parent) ->
-			@handlerName = getPropHandlerName @prop
-			@isConnected = false
-
-			if isArray(item)
-				@itemId = ''
-				@child = Connection.factory @binding, item[0], item[1], @
-				@item = @child.getValue()
-			else
-				@itemId = item
-				@child = null
-				@item = Binding.getItemById @binding, item
-			@connect()
-
-			Object.preventExtensions @
-
-		getSignalChangeListener = do ->
-			withParent = ->
-				@parent.updateItem()
-			noParent = ->
-				@binding.update()
-
-			(connection) ->
-				if connection.parent
-					withParent
-				else
-					noParent
-
-		update: ->
-			getSignalChangeListener(@).call @
-
-		connect: ->
-			if @item
-				handler = @item[@handlerName]
-				if handler?
-					@isConnected = true
-					handler getSignalChangeListener(@), @
-			return
-
-		disconnect: ->
-			if @item and @isConnected
-				@item[@handlerName].disconnect getSignalChangeListener(@), @
-			@isConnected = false
-			return
-
-		updateItem: ->
-			oldVal = @item
-			if @child
-				val = @child.getValue()
-			else
-				val = Binding.getItemById @binding, @itemId
-
-			if oldVal and not @isConnected
-				@connect()
-				oldVal = null
-
-			if oldVal isnt val
-				@disconnect()
-				@item = val
-				@connect()
-				unless @parent
-					@binding.update()
-
-			if @parent
-				@parent.updateItem()
-			return
-
-		getValue: ->
-			if @item
-				@item[@prop]
-			else
-				null
-
-		destroy: ->
-			@disconnect()
-			@child?.destroy()
-			pool.push @
-			return
-
-	# isSimpleBinding = (binding) ->
-	# 	binding[1].length is 1 and isArray(binding[1][0]) and not isArray(binding[1][0][0])
-
-	# class SimpleBinding
-	# 	constructor: (@obj, @prop, binding, ctx) ->
-	# 		item = obj._ref or obj
-	# 		target = binding[1][0]
-
-	# 		@func = binding[0]
-	# 		@targetProp = target[1]
-	# 		if target[0] is 'this'
-	# 			@targetItem = ctx
-	# 		else
-	# 			@targetItem = ctx._component.itemsById[target[0]]
-
-	# 		handlerName = "on#{utils.capitalize(target[1])}Change"
-	# 		@targetItem[handlerName]? @update, @
-
-	# 		Object.preventExtensions @
-
-	# 		@update()
-
-	# 	update: ->
-	# 		@obj[@prop] = @targetItem[@targetProp
-	# 		return
-
-	# 	destroy: ->
-	# 		handlerName = "on#{utils.capitalize(@targetProp)}Change"
-	# 		@targetItem[handlerName].disconnect @update, @
-	# 		# remove from the list
-	# 		@obj._impl.bindings[@prop] = null
-	# 		return
-
-	class Binding
+	class RendererBinding extends Binding
 		pool = []

 		@factory = (obj, prop, binding, component, ctx) ->
 			if elem = pool.pop()
-				Binding.call elem, obj, prop, binding, component, ctx
-				elem
-			else
-				new Binding obj, prop, binding, component, ctx
+				RendererBinding.call elem, obj, prop, binding, component, ctx
+			RendererBinding.New obj, prop, binding, component, ctx, elem

-		@getItemById = (binding, item) ->
-			if item is 'this'
-				binding.ctx
-			else if item is 'view'
-				impl.Renderer.window
-			else
-				binding.component.objects[item] or impl.Renderer[item] or null
+		@New = (obj, prop, binding, component, ctx, target) ->
+			target ?= new RendererBinding obj, prop, binding, component, ctx
+			Binding.New binding, ctx, target
+			target.update()
+			target

 		onComponentObjectChange = (id) ->
 			for conn in @connections
@@ -167,90 +26,42 @@
 					conn.updateItem()
 			return

-		constructor: (@obj, @prop, binding, @component, @ctx) ->
-			assert.lengthOf binding, 2
-			assert.isFunction binding[0]
-			assert.isArray binding[1]
+		constructor: (@obj, @prop, binding, component, ctx) ->
+			super binding, ctx
+			@component = component
+			@args = component.objectsOrder

-			item = @item = @obj._ref or @obj
 			if @listensOnComponentObjectChange = @component.onObjectChange?
 				@component.onObjectChange onComponentObjectChange, @

-			# properties
-			@func = binding[0]
-			@args = @component.objectsOrder
+		getItemById: (item) ->
+			if item is 'this'
+				@ctx
+			else if item is 'view'
+				impl.Renderer.window
+			else
+				@component.objects[item] or impl.Renderer[item] or null

-			# connections
-			connections = @connections ||= []
-			for elem in binding[1]
-				if isArray(elem)
-					connections.push Connection.factory @, elem[0], elem[1]
+		getValue: ->
+			@obj[@prop]

-			# update
-			`//<development>`
-			@updatePending = false
-			@updateLoop = 0
-			`//</development>`
-			Object.seal @
-
-			@update()
-
-		getDefaultValue = (binding) ->
-			val = binding.obj[binding.prop]
-			switch typeof val
-				when 'string'
-					''
-				when 'number'
-					0
-				when 'boolean'
-					false
-				else
-					null
-
-		update: ->
-			unless @args
-				return
-
-			`//<development>`
-			if @updatePending
-				if @updateLoop > MAX_LOOPS
-					return
-				if ++@updateLoop is MAX_LOOPS
-					log.error "Potential loop detected. Property binding '#{@prop}' on item '#{@item.toString()}' has been disabled."
-					return
-			else
-				@updateLoop = 0
-			`//</development>`
-
-			result = utils.tryFunction @func, @ctx, @args
-			unless result?
-				result = getDefaultValue @
-
-			if typeof result is 'number' and not isFinite(result)
-				result = getDefaultValue @
-
-			`//<development>`
-			@updatePending = true
-			`//</development>`
-			@obj[@prop] = result
-			`//<development>`
-			@updatePending = false
-			`//</development>`
+		setValue: (val) ->
+			if not val? or val isnt val
+				val = @getDefaultValue()
+			@obj[@prop] = val
 			return

+		getLoopDetectedErrorMessage: ->
+			"Potential loop detected. Property binding '#{@prop}' on '#{@item.toString()}' has been disabled."
+
 		destroy: ->
-			# destroy connections
-			for connection in @connections
-				connection.destroy()
-
 			# remove from the list
 			@obj._impl.bindings[@prop] = null

-			# clear props
-			@args = null
-			utils.clear @connections
 			if @listensOnComponentObjectChange
 				@component.onObjectChange.disconnect onComponentObjectChange, @
+
+			super()

 			pool.push @
 			return
@@ -262,9 +73,6 @@
 		data.bindings[prop]?.destroy()

 		if binding?
-			# if isSimpleBinding(binding)
-			# 	data.bindings[prop] = new SimpleBinding @, prop, binding, ctx
-			# else
-			data.bindings[prop] = Binding.factory @, prop, binding, component, ctx
+			data.bindings[prop] = RendererBinding.factory @, prop, binding, component, ctx

 		return


Implemented collectPackages in the index

--- index.coffee
+++ index.coffee
@@ -50,10 +50,27 @@
     symbols = _.map supported, sym
     _.object symbols, modules

+# This method is the base method for very important functionality.
+# It scans the supplied directory, find all the packages there and
+# return an object, where keys are names of modules minus the ext.
+# This is used to build up entire module hierarchy of the framework.
+collectPackages = (directory) ->
+    fix = (p) -> "#{directory}/#{p}"
+    stat = (p) -> fs.statSync fix p
+    isDir = (p) -> stat(p).isDirectory()
+    nodes = fs.readdirSync directory.toString()
+    directories = _.filter nodes, isDir
+    scanner = (d) -> collectPackages fix d
+    symbols = _.map directories, paths.basename
+    packages = _.map directories, scanner
+    packages = _.object symbols, packages
+    modules = collectModules directory, yes
+    _.merge modules, packages
+
 # Build up the entire module hierarchy of the framework. Please do
 # refer to the `collectModules` method implementation for more
 # information on how this is being done. See the modules in the
 # framework library to see the structure of the built hieararchy.
-module.exports.nucleus = collectModules "library/nucleus"
-module.exports.visualize = collectModules "library/visualize"
-module.exports.widgets = collectModules "library/widgets"
+module.exports = collectPackages "library"
+module.exports.collectModules = collectModules
+module.exports.collectPackages = collectPackages


Forgot about heroku port

Forgot about heroku port

--- index.coffee
+++ index.coffee
@@ -2,4 +2,5 @@
 app = express()
 app.use express.static "public"
 app.get "*", (req, res) -> res.sendFile __dirname + "/public/index.html"
-app.listen 3000, "0.0.0.0", -> console.log "Listening on 0.0.0.0:3000"
+port = process.env.PORT || 5000
+app.listen port, -> console.log "Listening on port " + port


Move walking event listener into voxel-walk

Requires https://github.com/deathcap/voxel-walk/commit/f1971ebd058ec747cc507ca9b86acc8279ff106b

--- index.coffee
+++ index.coffee
@@ -96,7 +96,14 @@
   game.flyer = createFlyForGame controlsTarget
   game.flyer.enabled = false

-  walk = createWalk(game, { skin: controlsTarget.playerSkin })
+  walk = createWalk(game, {
+    skin: controlsTarget.playerSkin
+    bindGameEvents: true
+    shouldStopWalking: () =>
+      vx = Math.abs(controlsTarget.velocity.x)
+      vz = Math.abs(controlsTarget.velocity.z)
+      return vx > 0.001 || vz > 0.001
+    })

   reach = createReach(game, { reachDistance: REACH_DISTANCE })
   mine = createMine(game, {
@@ -169,14 +176,4 @@
   debris.on 'collect', (item) ->
     console.log("collect", item)

-  # TODO: refactor into voxel-walk?
-  game.on 'tick', () ->
-    walk.render()
-    vx = Math.abs controlsTarget.velocity.x
-    vz = Math.abs controlsTarget.velocity.z
-    if vx > 0.001 || vz > 0.001
-      walk.stopWalking()
-    else
-      walk.startWalking()

-


using utf-8 emoji for likes, comments, etc

--- index.coffee
+++ index.coffee
@@ -37,13 +37,8 @@
         title: o.title
         description: "
           <img align='left' hspace='5' src='#{o.artwork_url}'/>
-          #{o.description}\n
-          Plays: #{o.playback_count}\n
-          Downloads: #{o.download_count}\n
-          Favoritings: #{o.favoritings_count}\n
-          Comments: #{o.comment_count}\n
-          Likes: #{o.likes_count}\n
-          Reposts: #{o.reposts_count}
+          |▶#{o.playback_count}|⬇#{o.download_count}|💬#{o.comment_count}|❤#{o.likes_count}|🔁#{o.reposts_count}|\n
+          #{o.description}
         "
         guid: o.stream_url
         url: o.permalink_url


Update the page number in the url bar any time the toc is modified

--- src/scripts/modules/media/tabs/contents/tree/leaf.coffee
+++ src/scripts/modules/media/tabs/contents/tree/leaf.coffee
@@ -51,12 +51,6 @@
       if TocNodeView.dragging isnt @
         @model = @content.move(TocNodeView.dragging, @model, 'after')

-        href = linksHelper.getPath 'contents',
-          id: @model.get('id')
-          version: @model.get('version')
-          page: @model.getPageNumber()
-        router.navigate(href, {trigger: false, analytics: false})
-
       return false

     initialize: () ->
@@ -64,6 +58,14 @@

       @content = @model.get('book')
       @editable = @content.get('editable')
+
+      # If this is the active page, update the URL bar to the correct page number
+      if @model.get('active')
+        href = linksHelper.getPath 'contents',
+          id: @content.get('id')
+          version: @content.get('version')
+          page: @model.getPageNumber()
+        router.navigate(href, {trigger: false, analytics: false})

       @listenTo(@model, 'change:active change:page change:changed change:title', @render)


add edge case for FF where it adds the leading xml tag

--- src/textorum/tinymce/loader.coffee
+++ src/textorum/tinymce/loader.coffee
@@ -90,6 +90,8 @@
       .replace(/^(<!DOCTYPE[^>]*>\s*<[^>]*?)[ ]?xmlns:xml="http:\/\/www.w3.org\/XML\/1998\/namespace"/g, "$1")
       # XSLT 1.0 doesn't support params in <xsl:output>, so use a placeholder
       # Chrome adds an unneeded xmlns:xml
+      .replace(/^<\?xml[^>]*>/, '')
+      # FF adds the leading `<?xml` tag which is particularly undesirable
     if is_wrapped # unwrap
       xmlString = xmlString
         .replace(/^.*<textorum>/, '')


Avoid unnecessary use of `Element.classList`, unsupported in IE9. Fixes #89.

--- src/turbolinks/progress_bar.coffee
+++ src/turbolinks/progress_bar.coffee
@@ -79,5 +79,5 @@

   createProgressElement: ->
     element = document.createElement("div")
-    element.classList.add("turbolinks-progress-bar")
+    element.className = "turbolinks-progress-bar"
     element


logger: log token to debug 'We have no record of this message' logs

--- workers/social/lib/social/models/passwordrecovery.coffee
+++ workers/social/lib/social/models/passwordrecovery.coffee
@@ -247,7 +247,7 @@
         if mail.dateDelivered
         then mail.dateDelivered
         else
-          console.warn "We have no record of this message"
+          console.warn "We have no record of this message", @token
           mail.dateAttempted

       if (Date.now() - dateThen > @expiryPeriod)


Errors shown in Browser Console [#81459622]

 - check of the DOM exist before creating the sortable object
 (BookmarksManager)

--- app/assets/javascripts/bookmarks.js.coffee
+++ app/assets/javascripts/bookmarks.js.coffee
@@ -143,4 +143,4 @@
       elem.disable()

 document.observe "dom:loaded", ->
-  window.bookmarksManager = new BookmarksManager()
+  window.bookmarksManager = new BookmarksManager() if $$(CollectionSelector).length


Updates countdown date

Updates countdown date

--- app/assets/javascripts/countdown.js.coffee
+++ app/assets/javascripts/countdown.js.coffee
@@ -1,7 +1,7 @@
 App.Countdown =

   initialize: ->
-    deadline = 'Jun 30 2016 00:00:00 GMT-0200'
+    deadline = 'Jul 1 2016 00:00:00 GMT-0200'

     getTimeRemaining = (endtime) ->
       t = Date.parse(endtime) - Date.parse(new Date)


Turn of DataTables pagination and sorting.

--- app/assets/javascripts/databases.js.coffee
+++ app/assets/javascripts/databases.js.coffee
@@ -22,7 +22,7 @@

 bindEventHandlers = ->
   enableEditables()
-  $('.data-table').dataTable()
+  $('.data-table').dataTable(bPaginate: false, bSort: false)
   $('th.column-head').on 'dblclick', (e) ->
     e.preventDefault()
     openColumnEditForm $(this)


only add required attr to those inputs with required classes

--- app/assets/javascripts/highlight.js.coffee
+++ app/assets/javascripts/highlight.js.coffee
@@ -21,13 +21,13 @@
     target.hide()
   else
     target.slideUp()
-  target.find("input").removeAttr("required")
-  target.find("textarea").removeAttr("required")
+  target.find("input.required").removeAttr("required")
+  target.find("textarea.required").removeAttr("required")

 window.show_and_add_required = (target, immediate) ->
   if immediate
     target.show()
   else
     target.slideDown()
-  target.find("input").attr("required", "required")
-  target.find("textarea").attr("required", "required")
+  target.find("input.required").attr("required", "required")
+  target.find("textarea.required").attr("required", "required")


Fix VT#deleteCharacters for good

Fix VT#deleteCharacters for good

--- app/assets/javascripts/player/vt.js.coffee
+++ app/assets/javascripts/player/vt.js.coffee
@@ -592,7 +592,7 @@
     line = @getLine()
     brush = line[line.length-1][1]
     line.splice(@cursorX, n)
-    @fill(@cursorY, @cols - n - 1, n, ' ', brush)
+    @fill(@cursorY, @cols - n, n, ' ', brush)
     @updateLine()

   insertCharacters: (n) ->


Another TODO

Another TODO

--- app/assets/javascripts/player/vt.js.coffee
+++ app/assets/javascripts/player/vt.js.coffee
@@ -250,6 +250,8 @@
         @setScrollRegion @n or 0, @m or @lines - 1
       when "^"
         # reserved
+        # Privacy Message (password verification), terminated by ST
+        # TODO
       else
         throw "no handler for CSI term: " + term


add terminal button handling

add terminal button handling

--- app/assets/javascripts/workflows.js.coffee
+++ app/assets/javascripts/workflows.js.coffee
@@ -30,6 +30,7 @@
     update_job_details_panel()
     update_open_dir_button()
     update_edit_button()
+    update_terminal_button()
     update_submit_button()
     update_stop_button()
     update_template_button()
@@ -65,6 +66,7 @@
     $("#script-name").text(content.name)
     $("#open-script-dir-button").attr("href", "#{content.fs_base}")
     $("#open-terminal-dir-button").attr("href", "#{content.terminal_base}")
+    update_terminal_button(content.terminal_base)
     $.ajax
       type: 'GET'
       url: content.apiurl
@@ -106,6 +108,17 @@
     $("#edit_button").removeAttr("href")
     $("#edit_button").attr("disabled", true)
     $("#edit_button").bind('click', false)
+
+@update_terminal_button = (path) ->
+  if path?
+    $("#terminal_button").attr("href", path)
+    $("#terminal_button").data("method", "GET")
+    $("#terminal_button").removeAttr("disabled")
+    $("#terminal_button").unbind('click', false)
+  else
+    $("#terminal_button").removeAttr("href")
+    $("#terminal_button").attr("disabled", true)
+    $("#terminal_button").bind('click', false)

 @update_submit_button = (id, status_char) ->
   if id? && !status_char?


Disable dashboard type input

Disable dashboard type input

--- app/assets/rdb/mixins/Configuration.coffee
+++ app/assets/rdb/mixins/Configuration.coffee
@@ -29,14 +29,14 @@
             @props.board.save({'name': val}, wait: true)
               .catch (xhr) =>
                 throw new Input.Error JSON.parse(xhr.responseText)?['errors']?['name']
-        Input
-          label: Translate.t('rdb.configure.general.dashboard_type')
-          help: Translate.t('rdb.configure.general.dashboard_type_text')
-          value: @props.board.get('type')
-          onSave: (val) =>
-            @props.board.save({'type': val}, wait: true)
-              .catch (xhr) =>
-                throw new Input.Error JSON.parse(xhr.responseText)?['errors']?['type']
+        # Input
+        #   label: Translate.t('rdb.configure.general.dashboard_type')
+        #   help: Translate.t('rdb.configure.general.dashboard_type_text')
+        #   value: @props.board.get('type')
+        #   onSave: (val) =>
+        #     @props.board.save({'type': val}, wait: true)
+        #       .catch (xhr) =>
+        #         throw new Input.Error JSON.parse(xhr.responseText)?['errors']?['type']
       ]

   render: ->


heregex: now allows bare slashes and empty body

--- lexer.coffee
+++ lexer.coffee
@@ -198,7 +198,8 @@
     [heregex, body, flags] = match
     @i += heregex.length
     unless ~body.indexOf '#{'
-      @token 'REGEX', '/' + body.replace(HEREGEX_OMIT, '') + '/' + flags
+      re = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/')
+      @token 'REGEX', "/#{  re or '(?:)' }/#{flags}"
       return true
     @token 'IDENTIFIER', 'RegExp'
     @tokens.push ['CALL_START', '(']


Issue #1547 'use strict' future reserved keywords as identifiers prohibited

--- lexer.coffee
+++ lexer.coffee
@@ -572,6 +572,8 @@
   'case', 'default', 'function', 'var', 'void', 'with'
   'const', 'let', 'enum', 'export', 'import', 'native'
   '__hasProp', '__extends', '__slice', '__bind', '__indexOf'
+  'implements', 'interface', 'let', 'package',
+  'private', 'protected', 'public', 'static', 'yield'
 ]

 # The superset of both JavaScript keywords and reserved words, none of which may


Exit with non-zero status if files do not match

--- lib/cli.iced
+++ lib/cli.iced
@@ -44,3 +44,5 @@
   else
     process.stderr.write "Producing colored output...\n"  if options.verbose
     process.stdout.write colorize(result, color: options.color)
+
+  process.exit 1 if result


Fix copy-to-clipboard links in highlighted code blocks

--- assets/javascripts/views/pages/base.coffee
+++ assets/javascripts/views/pages/base.coffee
@@ -37,7 +37,12 @@
         @nodesPerFrame = Math.round(Math.max(@nodesPerFrame * .8, 10))
     else
       @nodesPerFrame = 10
-    Prism.highlightElement(el) for el in @highlightNodes.splice(0, @nodesPerFrame)
+
+    for el in @highlightNodes.splice(0, @nodesPerFrame)
+      $.remove(clipEl) if clipEl = el.lastElementChild
+      Prism.highlightElement(el)
+      $.append(el, clipEl) if clipEl
+
     $.requestAnimationFrame(@paintCode) if @highlightNodes.length > 0
     @previousTiming = timing
     return


better error loging for defaults check

better error loging for defaults check

--- salvus/misc.coffee
+++ salvus/misc.coffee
@@ -143,26 +143,34 @@
     if typeof(obj1) != 'object'
         # We put explicit traces before the errors in this function,
         # since otherwise they can be very hard to debug.
+        err = "misc.defaults -- TypeError: function takes inputs as an object #{error()}"
+        console.log(err)
         console.trace()
-        throw "misc.defaults -- TypeError: function takes inputs as an object #{error()}"
+        throw err
     r = {}
     for prop, val of obj2
         if obj1.hasOwnProperty(prop) and obj1[prop]?
             if obj2[prop] == exports.defaults.required and not obj1[prop]?
+                err = "misc.defaults -- TypeError: property '#{prop}' must be specified: #{error()}"
+                console.log(err)
                 console.trace()
-                throw "misc.defaults -- TypeError: property '#{prop}' must be specified: #{error()}"
+                throw err
             r[prop] = obj1[prop]
         else if obj2[prop]?  # only record not undefined properties
             if obj2[prop] == exports.defaults.required
+                err = "misc.defaults -- TypeError: property '#{prop}' must be specified: #{error()}"
+                console.log(err)
                 console.trace()
-                throw "misc.defaults -- TypeError: property '#{prop}' must be specified: #{error()}"
+                throw err
             else
                 r[prop] = obj2[prop]
     if not allow_extra
         for prop, val of obj1
             if not obj2.hasOwnProperty(prop)
+                err = "misc.defaults -- TypeError: got an unexpected argument '#{prop}' #{error()}"
+                console.log(err)
                 console.trace()
-                throw "misc.defaults -- TypeError: got an unexpected argument '#{prop}' #{error()}"
+                throw err
     return r

 # WARNING -- don't accidentally use this as a default:


[grunt] slightly more logical regex for humanReadableUrl

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -34,7 +34,7 @@
   localesDir = grunt.config('path.source.locales')

   humanReadableUrl = (pagepath) ->
-    exclude  = /^(index|\d\d\d)$/
+    exclude  = /^(index|\d{3})$/

     ext      = path.extname(pagepath)
     basename = path.basename(pagepath, ext)


resize tool much better behaved in the gridplot

--- bokehjs/src/coffee/tool/resize_tool.coffee
+++ bokehjs/src/coffee/tool/resize_tool.coffee
@@ -17,6 +17,7 @@
       super(plotview)

     eventGeneratorClass: TwoPointEventGenerator
+    toolType: "ResizeTool"
     evgen_options: {keyName:"", buttonText:"Resize"}
     tool_events: {
       activated: "_activate",
@@ -57,22 +58,27 @@
       return [x, y]

     _activate: (e) ->
+      if @active
+        return
       @active = true
       @popup = $(
-        '

'
-      )
-      bbar = @plot_view.$el.find('.button_bar')
+        '''

''')
+      bbar = @plot_view.$el.find('.bokeh_canvas_wrapper')
       @popup.appendTo(bbar)
       ch = @plot_view.view_state.get('outer_height')
       cw = @plot_view.view_state.get('outer_width')
       @popup.text("width: #{cw} height: #{ch}")

+      @request_render()
       @plot_view.request_render()
       return null

     _deactivate: (e) ->
       @active = false
       @popup.remove()
+      @request_render()
       @plot_view.request_render()
       return null


Update doc of net.coffee

Update doc of net.coffee

--- scripts/net.coffee
+++ scripts/net.coffee
@@ -2,7 +2,7 @@
 #   Provide network commands
 #
 # Commands:
-#   hubot nslookup <host> - lookup host's ip-address and host name resolved by ip-address
+#   hubot nslookup <host> - [net] lookup host's ip-address and host name resolved by ip-address

 URL = require "url"
 DNS = require "dns"


adding pings query endpoint

adding pings query endpoint

--- server/ping.coffee
+++ server/ping.coffee
@@ -36,6 +36,10 @@
       'Content-length': img.length
     this.response.end img
     this.done()
+
+API.add 'pings',
+  get: () -> return pings.search this.queryParams
+  post: () -> return pings.search this.bodyParams

 API.add 'ping',
   get: () ->


tests: access module components more simply

--- coffee/test/shells/acorn.shell.spec.coffee
+++ coffee/test/shells/acorn.shell.spec.coffee
@@ -7,6 +7,10 @@
 describe 'acorn.shells.AcornLinkShell', ->
   Shell = acorn.shells.Shell
   AcornLinkShell = acorn.shells.AcornLinkShell
+
+  Model = AcornLinkShell.Model
+  MediaView = AcornLinkShell.MediaView
+  RemixView = AcornLinkShell.RemixView

   options =
     shellid: 'acorn.AcornLinkShell'
@@ -44,16 +48,16 @@
   describe 'AcornLinkShell.Model', ->

     it 'should retrieve acornid from link correctly', ->
-      model = new AcornLinkShell.Model options
+      model = new Model options
       expect(model.acornid()).toBe 'nyfskeqlyx'

     it 'should have an acornModel', ->
-      model = new AcornLinkShell.Model options
+      model = new Model options
       expect(model.acornModel instanceof acorn.Model).toBe true

     it 'should load (and call `onceLoaded` callbacks)', ->
       spy = jasmine.createSpy()
-      model = new AcornLinkShell.Model options
+      model = new Model options
       model.onceLoaded spy
       model.onceLoaded spy
       model.onceLoaded spy
@@ -64,13 +68,13 @@
         expect(spy.callCount).toBe 3

     it 'should have a shellModel (onceLoaded)', ->
-      model = new AcornLinkShell.Model options
+      model = new Model options
       model.onceLoaded =>
         expect(model.shellModel instanceof Shell.Model).toBe true
       waitsFor (-> model.shellModel), 'model to load', 10000

     it 'description should use acornModel.title', ->
-      model = new AcornLinkShell.Model options
+      model = new Model options
       spyOn model.acornModel, 'title'
       waitsFor (-> model.shellModel), 'model to load', 10000
       runs ->
@@ -85,15 +89,15 @@
     acorn.util.appendCss()
     $player = $('<div>').addClass('acorn-player').appendTo('body')

-    model = new AcornLinkShell.Model options
+    model = new Model options

-    contentView = new AcornLinkShell.MediaView model: model
+    contentView = new MediaView model: model
     contentView.$el.width 600
     contentView.render()
     $player.append contentView.el

-    remixView = new AcornLinkShell.RemixView model: model
+    remixView = new RemixView model: model
     remixView.$el.width 600
     remixView.render()
     $player.append remixView.el


some keyboard shortcuts

some keyboard shortcuts

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -1563,6 +1563,9 @@
         # implement in a derived class if you need this
         @syncdoc?.disconnect_from_session()
         cb?()
+
+    codemirrors: () =>
+        return [@codemirror, @codemirror1]

     action_key: (opts) =>
         # opts ignored by default; worksheets use them....
@@ -5085,6 +5088,8 @@

         @init_preview_select()

+        @init_keybindings()
+
         # this is entirely because of the chat
         # currently being part of @source_editor, and
         # only calling the show for that; once chat
@@ -5096,6 +5101,28 @@

     cm: () =>
         return @source_editor.syncdoc.focused_codemirror()
+
+    init_keybindings: () =>
+        keybindings =  # inspired by http://www.door2windows.com/list-of-all-keyboard-shortcuts-for-sticky-notes-in-windows-7/
+            bold      : 'Cmd-B Ctrl-B'
+            italic    : 'Cmd-I Ctrl-I'
+            underline : 'Cmd-U Ctrl-U'
+            comment   : 'Shift-Ctrl-3'
+            strikethrough : 'Shift-Cmd-X Shift-Ctrl-X'
+            justifycenter : "Cmd-E Ctrl-E"
+            justifyright  : "Cmd-R Ctrl-R"
+            subscript     : "Cmd-= Ctrl-="
+            superscript   : "Shift-Cmd-= Shift-Ctrl-="
+            insertunorderedlist : "Shift-Cmd-L Shift-Ctrl-L"
+            insertimage : "Shift-Cmd-I Shift-Ctrl-I"
+
+        extra_keys = @cm().getOption("extraKeys") # current keybindings
+        for cmd, keys of keybindings
+            for k in keys.split(' ')
+                ( (cmd) => extra_keys[k] = (cm) => @command(cm, cmd) )(cmd)
+
+        for cm in @source_editor.codemirrors()
+            cm.setOption("extraKeys", extra_keys)

     init_draggable_split: () =>
         @_split_pos = @local_storage("split_pos")
@@ -5134,6 +5161,20 @@
             @click_save_button()
         @init_edit_buttons()

+    command: (cm, cmd, args) =>
+        switch cmd
+            when "link"
+                @insert_link(cm)
+            when "image"
+                @insert_image(cm)
+            else
+                cm.edit_selection
+                    cmd  : cmd
+                    args : args
+                    mode : @ext
+                @sync()
+
+
     init_edit_buttons: () =>
         that = @
         @edit_buttons.find("a").click () ->
@@ -5143,17 +5184,7 @@
                 args = "#{args}"
                 if args.indexOf(',') != -1
                     args = args.split(',')
-            switch cmd
-                when "link"
-                    that.insert_link()
-                when "image"
-                    that.insert_image()
-                else
-                    that.cm().edit_selection
-                        cmd  : cmd
-                        args : args
-                        mode : that.ext
-                    that.sync()
+            that.command(that.cm(), cmd, args)
             return false

         if true #  @ext != 'html'
@@ -5499,7 +5530,7 @@
         @source_editor?.focus()

-    insert_link: () =>
+    insert_link: (cm) =>
         dialog = templates.find(".salvus-html-editor-link-dialog").clone()
         dialog.modal('show')
         dialog.find(".btn-close").off('click').click () ->
@@ -5513,7 +5544,7 @@
         title   = dialog.find(".salvus-html-editor-title")
         anchor  = dialog.find(".salvus-html-editor-anchor")

-        selected_text = @cm().getSelection()
+        selected_text = cm.getSelection()
         display.val(selected_text)

         if @ext in ['md', 'rst', 'tex']
@@ -5587,7 +5618,6 @@
                 else
                     s = "\\url{#{url}}"

-            cm = @cm()
             selections = cm.listSelections()
             selections.reverse()
             for sel in selections
@@ -5610,7 +5640,7 @@
         # ensures that the given line is the pre-amble of the latex document.
         # TODO: actually implement this!

-    insert_image: () =>
+    insert_image: (cm) =>

         dialog = templates.find(".salvus-html-editor-image-dialog").clone()
         dialog.modal('show')
@@ -5682,7 +5712,6 @@
                 if title.length > 0
                     title = " title='#{title}'"
                 s = "<img src='#{url.val()}'#{width}#{height}#{title}>"
-            cm = @cm()
             selections = cm.listSelections()
             selections.reverse()
             for sel in selections


Use modern signal/slot mechanism

Use modern signal/slot mechanism

--- components/tabs/KDTabViewController.coffee
+++ components/tabs/KDTabViewController.coffee
@@ -70,7 +70,7 @@
       false

   removePane:(pane)->
-    pane.handleEvent type : "KDTabPaneDestroy"
+    pane.emit "KDTabPaneDestroy"
     index = @getPaneIndex pane
     isActivePane = @getActivePane() is pane
     @panes.splice(index,1)


Cridaos diversos snippets

Cridaos diversos snippets

git-svn-id: b23113aba4c270f2fbd233eb8d34d067bf54b1d8@33 c1abd303-7ba8-1044-b39f-eab719197c0a

--- snippets/rech.cson
+++ snippets/rech.cson
@@ -34,27 +34,22 @@
      'prefix': 'GR'
      'body': 'IS GREATER THAN $1'

-#'.source.cobol':
   'LESS THAN':
      'prefix': 'LE'
      'body': 'IS LESS THAN $1'

-#'.source.cobol':
   'EQUAL TO':
      'prefix': 'EQ'
      'body': 'IS EQUAL TO $1'

-#'.source.cobol':
   'NOT EQUAL TO':
      'prefix': 'NQ'
      'body': 'IS NOT EQUAL TO $1'

-#'.source.cobol':
   'PERFORM':
      'prefix': 'PE'
      'body': 'PERFORM $1'

-#'.source.cobol':
   'PERFORM UNTIL':
      'prefix': 'PU'
      'body': """
@@ -62,9 +57,120 @@
         $1
      END-PERFORM.$2
      """
+<<<<<<< .mine

+  'ADD':
+    'prefix': 'AD'
+    'body': 'ADD $1 TO $2.'
+
+  'COMPUTE':
+    'prefix': 'CP'
+    'body': 'COMPUTE $1 = $2.'
+
+#
+  'Digita ADD | TO |':
+    'prefix': 'AD'
+    'body': 'ADD $1 TO $2'
+#
+  'Digita ADD 1 TO |':
+    'prefix': 'AD1'
+    'body': 'ADD 1 TO $2'
+#
+  'Digita SUBTRACT | TO |':
+    'prefix': 'SU'
+    'body': 'SUBTRACT $1 TO $2'
+#
+  'Digita SUBTRACT 1 TO |':
+    'prefix': 'SU1'
+    'body': 'SUBTRACT 1 TO $2'
+#
+  'Digita COMPUTE | = |':
+    'prefix': 'CP'
+    'body': 'COMPUTE $1 = $2'
+#
+  'Digita COMPUTE ROUNDED | = |':
+    'prefix': 'CR'
+    'body': 'COMPUTE ROUNDED $1 = $2'
+#
+  'Digita ELSE,':
+    'prefix': 'EL'
+    'body': 'ELSE,'
+#
+  'Digita EXIT PERFORM CYCLE':
+    'prefix': 'XC'
+    'body': 'EXIT PERFORM CYCLE'
+#
+  'Digita EXIT PERFORM':
+    'prefix': 'XP'
+    'body': 'EXIT PERFORM'
+#
+  'Digita VALUE IS':
+    'prefix': 'VL'
+    'body': 'VALUE IS'
+#
+  'Digita INITIALIZE':
+    'prefix': 'IN'
+    'body': 'INITIALIZE'
+#
+  'Digita ZEROS':
+    'prefix': 'ZE'
+    'body': 'ZEROS'
+#
+  'Digita SPACES':
+    'prefix': 'SP'
+    'body': 'SPACES'
+#
+  'Digita MOVE | TO |':
+    'prefix': 'MV'
+    'body': 'MOVE $1 TO $2'
+#
+  'Digita LOW-VALUES':
+    'prefix': 'LV'
+    'body': 'LOW-VALUES'
+#
+  'Digita | IS EQUAL TO |':
+    'prefix': 'EQ'
+    'body': '$1 IS EQUAL TO $2'
+#
+  'Digita | IS NOT EQUAL TO |':
+    'prefix': 'NQ'
+    'body': '$1 IS NOT EQUAL TO $2'
+#
+  'Digita | IS GREATER THAN |':
+    'prefix': 'GT'
+    'body': '$1 IS GREATER THAN $2'
+#
+  'Digita | IS LESS THAN |':
+    'prefix': 'LE'
+    'body': '$1 IS LESS THAN $2'
+#
+  'Digita EVALUATE':
+    'prefix': 'EV'
+    'body': """
+             EVALUATE   $1,
+                WHEN $2
+             END-EVALUATE.
+            """
+#
+  'Digita PERFORM UNTIL EXIT':
+    'prefix': 'PU'
+    'body': """
+            PERFORM UNTIL EXIT
+                $1
+            END-PERFORM.
+            """
+#
+  'Digita PERFORM VARYING':
+     'prefix': 'PB'
+     'body': """
+     PERFORM                WITH TEST BEFORE
+        VARYING             $1 FROM $2 BY $3
+           UNTIL            $4
+        $5
+     END-PERFORM.$6
+     """
 #'.source.cobol':
-  'PERFORM VARYING':
+  'Digita PERFORM VARYING':
      'prefix': 'PB'
      'body': """
      PERFORM                WITH TEST BEFORE
@@ -75,7 +181,7 @@
      """

 #'.source.cobol':
-  'EXIT PARAGRAPH':
+  'Digita EXIT PARAGRAPH':
      'prefix': 'XH'
      'body': """
      EXIT PARAGRAPH$1


Use calculated outerHeight for equalHeight plugin

--- app/assets/javascripts/helpers.js.coffee
+++ app/assets/javascripts/helpers.js.coffee
@@ -1,6 +1,6 @@
 $.fn.equalHeights = () ->
     max = -1
     $(this).each ->
-        h = $(this).height()
+        h = $(this).outerHeight()
         max = (if h > max then h else max)
         $(this).css "min-height": max


Added click function.

Added click function.

--- app/assets/javascripts/hotkeys.js.coffee
+++ app/assets/javascripts/hotkeys.js.coffee
@@ -10,40 +10,40 @@
       when 74 # j/J
         if e.shiftKey # J
           # Click next topic
-          $ns.find(ss).next(ts).click()
+          click $ns.find(ss).nextAll(ts).first()
         else if e.ctrlKey # ^j
           # Go to next group
-          $("#groups_list li.selected").next('li').click()
+          click $("#groups_list li.selected").next('li')
         else          # j
           # Click the next row from the one that is selected
-          $ns.find(ss).next('tr').click()
+          click $ns.find(ss).next('tr')
       when 75 # k/K
         if e.shiftKey # K
           # Go to previous topic
-          $ns.find(ss).prev(ts).prev(ts).click()
+          click $ns.find(ss).prevAll(ts)[1]
         else if e.ctrlKey # ^k
           # Go to previous group
-          $("#groups_list li.selected").prev('li').click()
+          click $("#groups_list li.selected").prev('li')
         else          # k
           # Go to previous row
-          $ns.find(ss).prev('tr').click()
+          click $ns.find(ss).prev('tr')
       when 78         # n
         # Go to Next Unread
-        $("#next_unread").click()
+        click $("#next_unread")
       when 79 # o/O
         if e.shiftKey # O
           # Close the current topic
-          $ns.find(ss).prev(ts).click()
+          click $ns.find(ss).prevAll(ts).first()
         else          # o
           # Open/Close the selected row
-          $ns.find(ss).click()
+          click $ns.find(ss)
       when 82 # r/R
         if e.shiftKey # R
           # Mark all as read
-          $("#toolbar .mark_read").click()
+          click $("#toolbar .mark_read")
         else          # r
           # Mark all in group read
-          $("#group_view .mark_read").click()
+          click $("#group_view .mark_read")

     false # Prevent Default

@@ -63,4 +63,10 @@
     return true
   else
     return false
+
+click = (el) ->
+  $el = $(el)
+  $el.click()
+  if (hash = $el.attr('href')) && hash[0..1] == '#!'
+    location.hash = hash[1..-1]


add support for complete picker selection (for breadcrumb)

--- app/assets/javascripts/hub_client.coffee
+++ app/assets/javascripts/hub_client.coffee
@@ -157,7 +157,7 @@

   selected: (event) ->
     @close()
-    @then(event.path)
+    @then(event.path, event.selection)

   close: ->
     @container.remove()


delected

delected

--- app/assets/javascripts/welcome.js.coffee
+++ app/assets/javascripts/welcome.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/


add album to queue

add album to queue

--- app/frontend/scripts/templates.js.coffee
+++ app/frontend/scripts/templates.js.coffee
@@ -62,11 +62,14 @@

 templates.queuing = """
   {{#queued}}
-    remove from queue
+
remove from queue
   {{/queued}}

   {{^queued}}
-    add to queue
+

+
add song to queue

+
add album to queue

+

   {{/queued}}
 """


Removing comments for log messages

Removing comments for log messages

--- app/scripts/angular-smooth-scroll.coffee
+++ app/scripts/angular-smooth-scroll.coffee
@@ -87,10 +87,10 @@
       element.bind 'click', ->
         if attr.target
           offset = attr.offset or 100
-          #$log.log 'Smooth scroll: scrolling to', attr.target, 'with offset', offset
+          $log.log 'Smooth scroll: scrolling to', attr.target, 'with offset', offset
           smoothScroll attr.target, offset
-        #else
-        #  $log.warn 'Smooth scroll: no target specified'
+        else
+          $log.warn 'Smooth scroll: no target specified'
   ])
   .directive('smoothScrollJquery', ['$log', ($log) ->
     restrict: 'A'
@@ -100,9 +100,9 @@
           offset = attr.offset or 100;
           target = $('#'+attr.target)
           speed = attr.speed or 500
-          #$log.log 'Smooth scroll jQuery: scrolling to', attr.target, 'with offset', offset, 'and speed', speed
+          $log.log 'Smooth scroll jQuery: scrolling to', attr.target, 'with offset', offset, 'and speed', speed
           $('html,body').stop().animate({scrollTop: target.offset().top - offset}, speed)
         else
-          #$log.log 'Smooth scroll jQuery: no target specified, scrolling to top'
+          $log.log 'Smooth scroll jQuery: no target specified, scrolling to top'
           $('html,body').stop().animate({scrollTop: 0}, speed);
   ])


[backport] sending events for each ecommerce action

--- app/coffee/modules/common/analytics.coffee
+++ app/coffee/modules/common/analytics.coffee
@@ -106,6 +106,7 @@
             'step': stepId,
             'Option': option
         })
+        @.trackEvent("ecommerce", "add-step", step, stepId)

     addEcImpression: (plan, page, position) ->
         @win.ga('ec:addImpression', {
@@ -114,6 +115,7 @@
            'list': page,
            'position': position,
         })
+        @.trackEvent("ecommerce", "add-impression", plan.name, plan.plan_id)

     addEcProduct: (plan) ->
         @win.ga('ec:addProduct', {
@@ -121,11 +123,12 @@
            'name': plan.name,
            'position': 1,
         })
+        @.trackEvent("ecommerce", "add-product", plan.name, plan.plan_id)

     setEcAction: (action, page) ->
         @win.ga('ec:setAction', action, {
             'list': page
         })
+        @.trackEvent("ecommerce", "set-action", action, page)

 module.service("$tgAnalytics", AnalyticsService)
-


The moment of truth: CoffeeScript has now compiled itself ten times over.

--- nodes.coffee
+++ nodes.coffee
@@ -4,7 +4,7 @@

 # Tabs are two spaces for pretty printing.
 TAB: '  '
-TRAILING_WHITESPACE: /\s+$/g
+TRAILING_WHITESPACE: /\s+$/gm

 # Keep the identifier regex in sync with the Lexer.
 IDENTIFIER:   /^[a-zA-Z$_](\w|\$)*$/
@@ -382,13 +382,13 @@

   # Hooking one constructor into another's prototype chain.
   compile_node: (o) ->
-    constructor:  o.scope.free_variable()
-    child:        @child.compile(o)
-    parent:       @parent.compile(o)
-    @idt() + constructor + ' = function(){};\n' + @idt() +
-      constructor + '.prototype = ' + parent + ".prototype;\n" + @idt() +
+    construct:  o.scope.free_variable()
+    child:      @child.compile(o)
+    parent:     @parent.compile(o)
+    @idt() + construct + ' = function(){};\n' + @idt() +
+      construct + '.prototype = ' + parent + ".prototype;\n" + @idt() +
       child + '.__superClass__ = ' + parent + ".prototype;\n" + @idt() +
-      child + '.prototype = new ' + constructor + "();\n" + @idt() +
+      child + '.prototype = new ' + construct + "();\n" + @idt() +
       child + '.prototype.constructor = ' + child + ';'

 }


fixing infinite recursion when compiling if statements containing pure_statements

--- nodes.coffee
+++ nodes.coffee
@@ -56,7 +56,8 @@
     del @options, 'operation' unless this instanceof ValueNode
     top:      if @top_sensitive() then @options.top else del @options, 'top'
     closure:  @is_statement() and not @is_pure_statement() and not top and
-              not @options.returns and not (this instanceof CommentNode)
+              not @options.returns and not (this instanceof CommentNode) and
+              not (@contains (n) -> n.is_pure_statement())
     if closure then @compile_closure(@options) else @compile_node(@options)

   # Statements converted into expressions via closure-wrapping share a scope
@@ -549,7 +550,7 @@
   constructor: (objects) ->
     @children: @objects: objects or []
     @compile_splat_literal: SplatNode.compile_mixed_array <- @, @objects
-
+
   compile_node: (o) ->
     o.indent: @idt(1)
     objects: []
@@ -800,7 +801,7 @@
   compile_value: (o, name, index) ->
     "Array.prototype.slice.call($name, $index)"

-# Utility function that converts arbitrary number of elements, mixed with
+# Utility function that converts arbitrary number of elements, mixed with
 # splats, to a proper array
 SplatNode.compile_mixed_array: (list, o) ->
   args: []


spec/mocha: Remove some cruft

spec/mocha: Remove some cruft

--- spec/mocha.coffee
+++ spec/mocha.coffee
@@ -1,14 +1,9 @@
 fbpspec = require '..'

 rt =
-  label: "Example runtime"
-  description: "Simple Python example FBP runtime"
-  type: "fbp-python-example"
   protocol: "websocket"
   address: "ws://localhost:3569"
   secret: "py3k"
-  id: "2ef763ff-1f28-49b8-b58f-5c6a5c23af26"
-  user: "3f3a8187-0931-4611-8963-239c0dff1939"
   command: 'python2 protocol-examples/python/runtime.py'

 fbpspec.mocha.run rt, './examples/simple-passing.yaml', { starttimeout: 1000 }


make a longtouch have the same consequence of a double click

--- src/Cell.coffee
+++ src/Cell.coffee
@@ -47,6 +47,9 @@
         ) else (span
           onClick: @handleClick
           onDoubleClick: @handleDoubleClick
+          onTouchStart: @handleRouchStart
+          onTouchEnd: @handleRouchEnd
+          onTouchCancel: @handleRouchEnd
           onMouseDown: @handleMouseDown
           onMouseUp: @handleMouseUp
           onMouseMove: @handleMouseMove
@@ -72,6 +75,15 @@
     e.preventDefault()
     dispatcher.handleCellDoubleClicked [@props.rowKey, @props.key]

+  handleTouchStart: (e) ->
+    @timer = setTimeout @handleLongTouch, 700
+
+  handleTouchEnd: (e) ->
+    clearTimeout @timer
+
+  handleLongTouch: ->
+    dispatcher.handleCellDoubleClicked [@props.rowKey, @props.key]
+
   handleMouseDown: (e) ->
     e.preventDefault()
     dispatcher.handleCellMouseDown [@props.rowKey, @props.key]


Update Dumper.coffee

Update Dumper.coffee

--- src/Dumper.coffee
+++ src/Dumper.coffee
@@ -22,6 +22,10 @@
     #
     dump: (input, inline = 0, indent = 0, exceptionOnInvalidType = false, objectEncoder = null) ->
         output = ''
+
+        if typeof(input) is 'function'
+            return output
+
         prefix = (if indent then Utils.strRepeat(' ', indent) else '')

         if inline <= 0 or typeof(input) isnt 'object' or input instanceof Date or Utils.isEmpty(input)


Increase threshold

Increase threshold

--- spec/ApplyVignette.coffee
+++ spec/ApplyVignette.coffee
@@ -6,10 +6,9 @@
 else
   ApplyVignette = require 'noflo-image/components/ApplyVignette.js'
   testutils = require 'noflo-image/spec/testutils.js'
-
-
+
 describe 'ApplyVignette component', ->
-
+
   c = null
   inImage = null
   outImage = null
@@ -20,13 +19,13 @@
     outImage = noflo.internalSocket.createSocket()
     c.inPorts.canvas.attach inImage
     c.outPorts.canvas.attach outImage
-
+
   describe 'when instantiated', ->
     it 'should have one input port', ->
       chai.expect(c.inPorts.canvas).to.be.an 'object'
     it 'should have one output port', ->
       chai.expect(c.outPorts.canvas).to.be.an 'object'
-
+
   describe 'with file system image', ->
     it 'should make an image with a dark vignette', (done) ->
       id = null
@@ -35,19 +34,17 @@
         groups.push group
       outImage.once 'data', (res) ->
         chai.expect(res).to.be.an 'object'
-
+        testutils.writePNG 'vignette_new.png', res
         # Tests result versus reference data
         refSrc = 'vignette.png'
         idOut = testutils.getCanvasWithImageNoShift refSrc, (ref) =>
           resCtx = res.getContext '2d'
           resData = resCtx.getImageData(0, 0, res.width, res.height).data
-
           refCtx = ref.getContext '2d'
           refData = refCtx.getImageData(0, 0, ref.width, ref.height).data
-
           for x in [0...resData.length] by 4
             difference = Math.abs(refData[x]-resData[x])
-            threshold = 2.5
+            threshold = 20.0
             chai.expect(difference).to.be.at.most threshold
           done()

@@ -55,4 +52,4 @@
       id = testutils.getCanvasWithImageNoShift inSrc, (image) ->
         inImage.beginGroup id
         inImage.send image
-        inImage.endGroup()+        inImage.endGroup()


Round top position before comparing to height

--- spec/app/pane-spec.coffee
+++ spec/app/pane-spec.coffee
@@ -550,7 +550,7 @@
       expect(row2.height()).toBe 2/3 * container.height()
       expect(pane2.outerWidth()).toBe column1.outerWidth()
       expect(pane2.outerHeight()).toBe 1/3 * container.height()
-      expect(pane2.position().top).toBe row2.height()
+      expect(Math.round(pane2.position().top)).toBe row2.height()

       expect(row2.children().length).toBe 2
       column3 = row2.children(':eq(0)').view()
@@ -568,7 +568,7 @@
       expect(pane4.outerWidth()).toBe column3.width()
       expect(pane4.outerHeight()).toBe 1/3 * container.height()
       expect(pane5.outerWidth()).toBe column3.width()
-      expect(pane5.position().top).toBe pane4.outerHeight()
+      expect(Math.round(pane5.position().top)).toBe pane4.outerHeight()
       expect(pane5.outerHeight()).toBe 1/3 * container.height()

       pane5.remove()


Added getTaskById

Added getTaskById

--- src/Task.coffee
+++ src/Task.coffee
@@ -7,16 +7,18 @@
     @_parent = null
     @_children = []

-  update: (frameCount) =>
+  getParent: -> @_parent
+
+  update: (elapsedTime) =>
     if @active
-      @onUpdate? frameCount
+      @onUpdate? elapsedTime

       for child in @_children
-        child.update frameCount
+        child.update elapsedTime

   destroy: ->
     @onDestroy?()
-    @_parent.removeChild this
+    @_parent?.removeChild this

     for child in @_children
       child.destroy()
@@ -31,5 +33,15 @@
     if i > -1
       @_children.splice i, 1
       task._parent = null
+    return @
+
+  getTaskById: (id) ->
+    if @id is id
+      return @
+    else
+      for child in @_children
+        task = child.getTaskById id
+        return task if task?
+      return null

 DivSugar.rootTask = DivSugar.createTask('root')


DRY up prefixes-spec.coffee

DRY up prefixes-spec.coffee

--- spec/prefixes-spec.coffee
+++ spec/prefixes-spec.coffee
@@ -17,30 +17,33 @@
     options.element ?= editor[0]
     helpers.keydown(key, options)

-  describe "the Repeat prefix", ->
-    it "repeats the following operation N times", ->
-      editor.setText('12345')
-      editor.setCursorScreenPosition([0, 1])
+  describe "Repeat", ->
+    describe "with operations", ->
+      beforeEach ->
+        editor.setText("123456789abc")
+        editor.setCursorScreenPosition([0, 0])

-      keydown('3')
-      keydown('x')
+      it "repeats N times", ->
+        keydown('3')
+        keydown('x')

-      expect(editor.getText()).toBe '15'
+        expect(editor.getText()).toBe '456789abc'

-      editor.setText('123456789abc')
-      editor.setCursorScreenPosition([0, 0])
-      keydown('1')
-      keydown('0')
-      keydown('x')
+      it "repeats NN times", ->
+        keydown('1')
+        keydown('0')
+        keydown('x')

-      expect(editor.getText()).toBe 'bc'
+        expect(editor.getText()).toBe 'bc'

-    it "repeats the following motion N times", ->
-      editor.setText('one two three')
-      editor.setCursorScreenPosition([0, 0])
+    describe "with motions", ->
+      beforeEach ->
+        editor.setText('one two three')
+        editor.setCursorScreenPosition([0, 0])

-      keydown('d')
-      keydown('2')
-      keydown('w')
+      it "repeats N times", ->
+        keydown('d')
+        keydown('2')
+        keydown('w')

-      expect(editor.getText()).toBe 'three'
+        expect(editor.getText()).toBe 'three'


AdBlock's blacklisted query string added

--- src/adbd.coffee
+++ src/adbd.coffee
@@ -9,7 +9,7 @@
   constructor: (next)->
     scriptElement = document.getElementsByTagName('script')[0]
     adsjs = document.createElement('script')
-    adsjs.src = '/ads.js'
+    adsjs.src = '/ads.js?&ad_box_=true'
     adsjs.onload = ->
       # ads.js script loaded
       checkDOM = window.getDocumentById root.ADBD.checkId


Remove unused Atom::getTempDirPath

Remove unused Atom::getTempDirPath

--- src/atom.coffee
+++ src/atom.coffee
@@ -347,10 +347,6 @@
   getHomeDirPath: ->
     process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']

-  # Private:
-  getTempDirPath: ->
-    if process.platform is 'win32' then os.tmpdir() else '/tmp'
-
   # Public: Get the directory path to Atom's configuration area.
   getConfigDirPath: ->
     @configDirPath ?= fs.absolute('~/.atom')


Remove unused resource path

Remove unused resource path

--- src/atom.coffee
+++ src/atom.coffee
@@ -574,7 +574,7 @@

   # Call this method when establishing a real application window.
   startEditorWindow: ->
-    {resourcePath, safeMode} = @getLoadSettings()
+    {safeMode} = @getLoadSettings()

     CommandInstaller = require './command-installer'
     CommandInstaller.installAtomCommand false, (error) ->


Use async.map for my schedule

Use async.map for my schedule

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -372,7 +372,6 @@
     if pagerduty.missingEnvironmentForApi(msg)
       return

-
     campfireUserToPagerDutyUser msg, msg.message.user, (user) ->
       userId = user.id

@@ -387,43 +386,38 @@
       else
         timezone = 'UTC'

-      scheduleQuery = {}
-      pagerduty.getSchedules scheduleQuery, (err, schedules) ->
+      pagerduty.getSchedules (err, schedules) ->
         if err?
           robot.emit 'error', err, msg
           return

         if schedules.length > 0
-          for schedule in schedules
-            withScheduleMatching msg, schedule.name, (schedule) ->
-              scheduleId = schedule.id
-              return unless scheduleId
-
-              pagerduty.get "/schedules/#{scheduleId}/entries", query, (err, json) ->
-                if err?
-                  robot.emit 'error', err, msg
-                  return
-
-                entries = json.entries
-
-                if entries
-                  sortedEntries = entries.sort (a, b) ->
-                    moment(a.start).unix() - moment(b.start).unix()
-
-                  buffer = ""
-                  for entry in sortedEntries
-                    if userId == entry.user.id
-                      startTime = moment(entry.start).tz(timezone).format()
-                      endTime   = moment(entry.end).tz(timezone).format()
-
-                      buffer += "* #{startTime} - #{endTime} #{entry.user.name} (#{schedule.name})\n"
-
-                  if buffer == ""
-                    msg.send "None found!"
-                  else
-                    msg.send buffer
-                else
-                  msg.send "None found!"
+          renderSchedule = (schedule, cb) ->
+            pagerduty.get "/schedules/#{schedule.id}/entries", query, (err, json) ->
+              if err?
+                cb(err)
+
+              entries = json.entries
+
+              if entries
+                sortedEntries = entries.sort (a, b) ->
+                  moment(a.start).unix() - moment(b.start).unix()
+
+                buffer = ""
+                for entry in sortedEntries
+                  if userId == entry.user.id
+                    startTime = moment(entry.start).tz(timezone).format()
+                    endTime   = moment(entry.end).tz(timezone).format()
+
+                    buffer += "* #{startTime} - #{endTime} #{entry.user.name} (#{schedule.name})\n"
+                cb(null, buffer)
+
+          async.map schedules, renderSchedule, (err, results) ->
+            if err?
+              robot.emit 'error', err, msg
+              return
+            msg.send results.join("")
+
         else
           msg.send 'No schedules found!'


Fix resolve

Fix resolve

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -150,6 +150,7 @@

   robot.respond /(pager|major)( me)? res(olve)?(d)?$/i, (msg) ->
     pagerDutyIncidents msg, "acknowledged", (incidents) ->
+      email  = msg.message.user.pagerdutyEmail || msg.message.user.email_address
       filteredIncidents = incidentsForEmail(incidents, email)

       if filteredIncidents.length is 0
@@ -172,7 +173,6 @@
       for note in json.notes
         buffer += "#{note.created_at} #{note.user.name}: #{note.content}\n"
       msg.send buffer
-

   robot.respond /(pager|major)( me)? note ([\d\w]+) (.+)$/i, (msg) ->
     incidentId = msg.match[3]


Fixes issue with deletions from the middle of arrays

--- src/base.coffee
+++ src/base.coffee
@@ -776,7 +776,15 @@
           when 'ObjectNode'
             @set restoreChange.path, restoreChange.node.data
           when 'ArrayNode'
-            @insert restoreChange.node.parent.getPath(), restoreChange.node.data
+            parentPath = restoreChange.node.parent.getPath()
+            parentData = @get parentPath
+            deleteIndex = parseInt (restoreChange.path.substring (restoreChange.path.lastIndexOf('/') + 1))
+
+            if deleteIndex < parentData.length
+              parentData.splice deleteIndex, 0, restoreChange.node.data
+              @set parentPath, parentData
+            else
+              @insert parentPath, restoreChange.node.data

       when 'edit'
         @set restoreChange.path, restoreChange.oldData
@@ -790,7 +798,6 @@

     root.currentStateIndex--
     @reverting = false
-    @refreshDisplay()

   redo: ->
     @reverting = true
@@ -821,7 +828,6 @@

     root.currentStateIndex++
     @reverting = false
-    @refreshDisplay()

   getTrackedActions: ->
     @getRoot().trackedActions


refactoring of the top/bottom loading order; delete re-indexing bug

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -51,7 +51,10 @@
 							throw new Error "#{datasourceName} is not a valid datasource" unless isDatasource datasource

 						bufferSize = Math.max(3, +$attr.bufferSize || 10)
-						bufferPadding = -> viewport.height() * Math.max(0.2, +$attr.padding || 0.5) # some extra space to initate preload
+						bufferPadding = -> viewport.height() * Math.max(0.1, +$attr.padding || 0.1) # some extra space to initate preload
+
+						scrollHeight = (elem)->
+							elem[0].scrollHeight || elem[0].document.documentElement.scrollHeight

 						handler = null

@@ -90,9 +93,6 @@
 								topPadding = createPadding(padding(repeaterType), element, 'top')
 								bottomPadding = createPadding(padding(repeaterType), element, 'bottom')

-								scrollHeight = (elem)->
-									elem[0].scrollHeight || elem[0].document.documentElement.scrollHeight
-
 								tempScope.$destroy()

 								handler =
@@ -133,7 +133,7 @@
 							pending = []
 							eof = false
 							bof = false
-							adjustBuffer(true)
+							adjustBuffer(false)

 						bottomVisiblePos = ->
 							viewport.scrollTop() + viewport.height()
@@ -186,19 +186,20 @@
 								first += overage
 								console.log "clipped off top #{overage} top padding #{handler.topPadding()}"

-						enqueueFetch = (direction)->
+						enqueueFetch = (direction, scrolling)->
 							if (!isLoading)
 								isLoading = true
 								loading(true)
-							#console.log "Requesting fetch... #{{true:'bottom', false: 'top'}[direction]} pending #{pending.length}"
 							if pending.push(direction) == 1
-								fetch()
-
-						adjustBuffer = (reloadRequested)->
+								fetch(scrolling)
+
+						adjustBuffer = (scrolling)->
+
 							console.log "top {actual=#{handler.topDataPos()} visible from=#{topVisiblePos()} bottom {visible through=#{bottomVisiblePos()} actual=#{handler.bottomDataPos()}}"
-
-							enqueueFetch(true) if reloadRequested || shouldLoadBottom()
-							enqueueFetch(false) if !reloadRequested && shouldLoadTop()
+							if shouldLoadBottom()
+								enqueueFetch(true, scrolling)
+							else
+								enqueueFetch(false, scrolling) if shouldLoadTop()

 						insert = (index, item, top) ->
 							itemScope = $scope.$new()
@@ -218,7 +219,7 @@

 							# this watch fires once per item inserted after the item template has been processed and values inserted
 							# which allows to gather the 'real' height of the thing
-							itemScope.$watch 'heightAdjustment', ->
+							dereg = itemScope.$watch 'heightAdjustment', ->
 								if top
 									# an element is inserted at the top
 									newHeight = handler.topPadding() - wrapper.element.outerHeight(true)
@@ -229,32 +230,30 @@
 									else
 										# if not, increment scrollTop
 										scrollTop = viewport.scrollTop() + wrapper.element.outerHeight(true)
-										# below is an attempt to ensure that the scrollbar is always there even if
-										# there is not enough data. But now I am not sure it is necessary. Commenting out for now
-										#if viewport.height() + scrollTop > canvas.height()
-										#handler.bottomPadding(handler.bottomPadding() + viewport.height() + scrollTop - canvas.height())
 										viewport.scrollTop(scrollTop)
 								else
 									handler.bottomPadding(Math.max(0,handler.bottomPadding() - wrapper.element.outerHeight(true)))
+								# deregister itself after first execution
+								dereg()

 							itemScope

-						finalize = ->
+						finalize = (scrolling)->
 							pending.shift()
 							if pending.length == 0
 								isLoading = false
 								loading(false)
 							else
-								fetch()
-
-						fetch = () ->
+								fetch(scrolling)
+
+						fetch = (scrolling) ->
 							direction = pending[0]
 							#console.log "Running fetch... #{{true:'bottom', false: 'top'}[direction]} pending #{pending.length}"
 							lastScope = null
 							if direction
 								if buffer.length && !shouldLoadBottom()
-									finalize()
+									finalize(scrolling)
 								else
 									#console.log "appending... requested #{bufferSize} records starting from #{next}"
 									datasource.get next, bufferSize,
@@ -262,20 +261,21 @@
 										if result.length == 0
 											eof = true
 											console.log "appended: requested #{bufferSize} records starting from #{next} recieved: eof"
-											finalize()
+											finalize(scrolling)
 											return
 										clipTop()
 										for item in result
 											lastScope = insert ++next, item, false

 										console.log "appended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
-										finalize()
-										lastScope.$watch 'adjustBuffer', ->
-											adjustBuffer()
+										finalize(scrolling)
+										dereg = lastScope.$watch 'adjustBuffer', ->
+											adjustBuffer(scrolling)
+											dereg()

 							else
 								if buffer.length && !shouldLoadTop()
-									finalize()
+									finalize(scrolling)
 								else
 									#console.log "prepending... requested #{size} records starting from #{start}"
 									datasource.get first-bufferSize, bufferSize,
@@ -283,24 +283,25 @@
 										if result.length == 0
 											bof = true
 											console.log "prepended: requested #{bufferSize} records starting from #{first-bufferSize} recieved: bof"
-											finalize()
+											finalize(scrolling)
 											return
 										clipBottom()
 										for i in [result.length-1..0]
 											lastScope = insert first--, result[i], true
 										console.log "prepended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
-										finalize()
-										lastScope.$watch 'adjustBuffer', ->
-											adjustBuffer()
+										finalize(scrolling)
+										dereg = lastScope.$watch 'adjustBuffer', ->
+											adjustBuffer(scrolling)
+											dereg()

 						viewport.bind 'resize', ->
 							if !$rootScope.$$phase && !isLoading
-								adjustBuffer()
+								adjustBuffer(false)
 								$scope.$apply()

 						viewport.bind 'scroll', ->
 							if !$rootScope.$$phase && !isLoading
-								adjustBuffer()
+								adjustBuffer(true)
 								$scope.$apply()

 						$scope.$watch datasource.revision,
@@ -320,7 +321,6 @@
 							else
 								if 0 <= locator-first-1 < buffer.length
 									buffer[locator-first-1].scope[itemName] = newItem
-							undefined

 						eventListener.$on "delete.items", (event, locator)->
 							temp = []
@@ -329,13 +329,14 @@
 								((wrapper)->
 									if locator wrapper.scope
 										removeFromBuffer temp.length-1-i, temp.length-i
+										next--
 								) wrapper for wrapper,i in temp
 							else
 								if 0 <= locator-first-1 < buffer.length
 									removeFromBuffer locator-first-1, locator-first
+									next--

 							item.scope.$index = first + i for item,i in buffer
-
-							undefined
+							adjustBuffer(false)

 		])


Kill unused import

Kill unused import

--- src/bundle.coffee
+++ src/bundle.coffee
@@ -1,4 +1,3 @@
-exec = require('child_process').exec
 minimatch = require 'minimatch'
 fstream = require 'fstream'
 chalk = require 'chalk'


Moved signal generation into constructor.

--- edf.coffee
+++ edf.coffee
@@ -33,8 +33,16 @@
 		# Open a handle.
 		@_handle	= fs.openSync @edf_path, "r"

-		# Populates _signals
-		@_generate_signals( )
+		# Run through and populate _signals using the specs.
+		for i in [0..parseInt(@get_header_item( "num_signals_in_data_record"))]
+
+			_specs = { }
+
+			# Grab all the particular specs from the signal header..
+			for spec in _signal_spec
+				_specs[spec.name] = @get_signal_item i, spec.name
+
+			_signals.push _specs

 	get_header_offset: ( ) ->
 		# 256 + ( number of signals  * 256 )
@@ -94,21 +102,6 @@
 		k = new Buffer length
 		fs.readSync @_handle, k, 0, length, position
 		k
-
-	_generate_signals: ( ) ->
-		# Internal function that populates _signals.
-		# This is called from the constructor.
-
-		# Iterate over all the signal indexes that are defined in the header..
-		for i in [0..parseInt(@get_header_item( "num_signals_in_data_record"))]
-
-			_specs = { }
-
-			# Grab all the particular specs from the signal header..
-			for spec in _signal_spec
-				_specs[spec.name] = @get_signal_item i, spec.name
-
-			_signals.push _specs

 	get_header_item: ( name ) ->
 		# Wraps the _get_header_spec call and _get_buffer_slice into one handy method.


Basic auto reconnect logic

Basic auto reconnect logic

--- src/client.coffee
+++ src/client.coffee
@@ -24,6 +24,9 @@
         @_messageID = 0
         @_pending = {}
         @_pingInterval = if @options.pingInterval? then @options.pingInterval else defaultPingInterval
+        @autoReconnect = if @options.autoReconnect? then @options.autoReconnect else true
+        @_connecting = false
+        @_reconnecting = false

         @_connAttempts  = 0

@@ -36,7 +39,9 @@
     _onLogin: (data, headers) =>
         if data
             if not data.id
+                @logger.error 'Login call failed'
                 @authenticated = false
+                @_reconnecting = false
                 @reconnect()
             else
                 @authenticated = true
@@ -83,6 +88,9 @@
             @emit 'error', { msg: 'failed to get channel list'}

     connect: ->
+        if @_connecting
+            return
+        @_connecting = true
         @logger.info 'Connecting...'
         options =
             headers: {authorization: "BEARER " + @token}
@@ -94,21 +102,26 @@
             @emit 'error', error

         @ws.on 'open', =>
+            @_connecting = false
+            @_reconnecting = false
             @connected = true
             @emit 'connected'
             @_connAttempts = 0
             @_lastPong = Date.now()
             @logger.info 'Starting pinger...'
             @_pongTimeout = setInterval =>
-                if not @connected then return
-
-                @logger.info 'ping'
-                @_send {"action": "ping"}
-                if @_lastPong? and Date.now() - @_lastPong > (2*@_pingInterval)
+                if not @connected
+                    @logger.error 'Not connected in pongTimeout'
+                    @reconnect()
+                    return
+                if @_lastPong? and (Date.now() - @_lastPong) > (2*@_pingInterval)
                     @logger.error "Last pong is too old: %d", (Date.now() - @_lastPong) / 1000
                     @authenticated = false
                     @connected = false
                     @reconnect()
+                else
+                    @logger.info 'ping'
+                    @_send {"action": "ping"}
             , @_pingInterval

         @ws.on 'message', (data, flags) =>
@@ -118,10 +131,14 @@
             @emit 'close', code, message
             @connected = false
             @socketUrl = null
-
+            if @autoReconnect
+                @reconnect()
         return true

     reconnect: ->
+        if @_reconnecting
+            return
+        @_reconnecting = true
         if @_pongTimeout
             clearInterval @_pongTimeout
             @_pongTimeout = null
@@ -238,7 +255,7 @@
                         callback({'id': null, 'error': 'API response: '+res.statusCode}, res.headers)

         req.on 'error', (error) =>
-            if callback? then callback({'id': null, 'error': error.errno})
+            if callback? then callback({'id': null, 'error': error.errno}, {})

         req.write('' + post_data)
         req.end()


API fixes for Mattermost 3.5.0

API fixes for Mattermost 3.5.0

--- src/client.coffee
+++ src/client.coffee
@@ -96,7 +96,6 @@
     _onChannels: (data, headers) =>
         if data && not data.error
             @channels = data.members
-            @logger.debug 'Found '+Object.keys(@channels).length+' channels.'
             @channel_details = data.channels
         else
             @logger.error 'Failed to get subscribed channels list from server.'
@@ -113,7 +112,7 @@

     loadUsersList: ->
         # Load userlist
-        @_apiCall 'GET', usersRoute + '/profiles/' + @teamID, null, @_onProfiles
+        @_apiCall 'GET', @teamRoute() + '/users/0/1000', null, @_onProfiles
         @_apiCall 'GET', @channelRoute(''), null, @_onChannels

@@ -217,7 +216,7 @@
                 # These are personal messages
                 @emit message.event, message
             when 'new_user'
-                @_apiCall 'GET', usersRoute + '/profiles/' + @teamID, null, @_onProfiles
+                @_apiCall 'GET', teamRoute() + '/users/0/1000', null, @_onProfiles
                 @emit 'new_user', message
             else
                 # Check for `pong` response


Match process.platform directly

Match process.platform directly

--- src/config.coffee
+++ src/config.coffee
@@ -59,7 +59,7 @@
     path.resolve(__dirname, '..', '.apmrc')

   isWin32: ->
-    !!process.platform.match(/^win/)
+    process.platform is 'win32'

   isWindows64Bit: ->
     fs.existsSync "C:\\Windows\\SysWow64\\Notepad.exe"


Pass source to ::scopedSettingsStore on calls to ::set

Default it to the user’s config if no source is specified.

--- src/config.coffee
+++ src/config.coffee
@@ -528,6 +528,9 @@
     else
       [keyPath, value, options] = arguments
       scopeSelector = options?.scopeSelector
+      source = options?.source
+
+    source ?= @getUserConfigPath()

     unless value is undefined
       try
@@ -536,7 +539,7 @@
         return false

     if scopeSelector?
-      @setRawScopedValue(scopeSelector, keyPath, value)
+      @setRawScopedValue(source, scopeSelector, keyPath, value)
     else
       @setRawValue(keyPath, value)

@@ -916,7 +919,7 @@
       disposable.dispose()
       @emitter.emit 'did-change'

-  setRawScopedValue: (selector, keyPath, value) ->
+  setRawScopedValue: (source, selector, keyPath, value) ->
     if keyPath?
       newValue = {}
       _.setValueForKeyPath(newValue, keyPath, value)
@@ -924,7 +927,7 @@

     settingsBySelector = {}
     settingsBySelector[selector] = value
-    @usersScopedSettings.add @scopedSettingsStore.addProperties(@getUserConfigPath(), settingsBySelector, @usersScopedSettingPriority)
+    @usersScopedSettings.add @scopedSettingsStore.addProperties(source, settingsBySelector, @usersScopedSettingPriority)
     @emitter.emit 'did-change'

   getRawScopedValue: (scopeDescriptor, keyPath) ->


Node is updated to v0.10.18.

Node is updated to v0.10.18.

--- src/config.coffee
+++ src/config.coffee
@@ -20,7 +20,7 @@
     process.env.ATOM_PACKAGES_URL ? 'http://atom.iriscouch.com/registry/_design/apm/_view/atom_packages'

   getNodeVersion: ->
-    process.env.ATOM_NODE_VERSION ? '0.10.15'
+    process.env.ATOM_NODE_VERSION ? '0.10.18'

   getUserConfigPath: ->
     path.resolve(__dirname, '..', '.apmrc')


Fix spec description

Fix spec description

--- spec/vim-mode-spec.coffee
+++ spec/vim-mode-spec.coffee
@@ -268,7 +268,7 @@
         editor.getBuffer().setText "012\n"
         editor.setCursorScreenPosition [0, 1]

-      it "deletes the contents until the end of the line", ->
+      it "switches to insert and adds a newline above the current one", ->
         keydown('O', shift: true, element: editor[0])

         expect(editor.getText()).toBe "\n012\n"


fix a missing return that was deleted

fix a missing return that was deleted

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -1223,7 +1223,7 @@

     render : ->
         if not @props.project_map?
-            <Loading />
+            return <Loading />
         title = @props.project_map?.get(@props.project_id)?.get('title')
         if title?
             {html_to_text(title)}


string_extensions

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -21,6 +21,14 @@

 ###*
 Returns a new string that is a camelCase version.
+
+

+"camel_case".camelize()
+"camel-case".camelize()
+"camel case".camelize()
+
+# => "camelCase"
+

 @name camelize
 @methodOf String#


Q() instead of Q.resolve() and update  documentation

--- src/file.coffee
+++ src/file.coffee
@@ -126,7 +126,7 @@
   # Public: Returns a {Boolean}, always false.
   isDirectory: -> false

-  # Public: Returns a {Boolean}, true if the file exists, false otherwise.
+  # Returns a promise that resolves to a {Boolean}, true if the file exists, false otherwise.
   exists: ->
     Q.Promise (resolve, reject) =>
       fs.exists @getPath(), resolve
@@ -187,7 +187,7 @@
   # Public: Returns a promise that resolves to the file's completely resolved {String} path.
   getRealPath: ->
     if @realPath?
-      Q.resolve @realPath
+      Q(@realPath)
     else
       Q.nfcall(fs.realpath, @path).then (realPath) =>
         @realPath = realPath


remember longest combination pharse

remember longest combination pharse

--- src/kick.coffee
+++ src/kick.coffee
@@ -4,9 +4,6 @@

 stage_height = 300
 stage_width = 1000
-
-animate_id = null
-shoot_name = null

 player = new class Player
@@ -15,6 +12,8 @@
         @point = 0
         @die = false
         @word = null
+        @game = null
+        @longest = []

 pool_words = new class PoolWord
@@ -52,21 +51,24 @@
 inventory = new class Inventory
     constructor: ->
         @words = []
+        @name = null

     add: (word) ->
-        if word == shoot_name?.full
+        if word == @name?.full
+            if @words.length >= player.longest.length
+                player.longest = @words
             @words = []
         else if word in @words
             @words.remove(word)
         else
             @words.push(word)
         if not @words.length
-            shoot_name = null
+            @name = null
         else
-            shoot_name = new ShootingWord('@neizod')
+            @name = new ShootingWord('@neizod')

     show: ->
-        follow = if shoot_name? then ' ' + shoot_name.show.html() else ''
+        follow = if @name? then ' ' + @name.show.html() else ''
         (word for word in @words).join(' ') + follow

@@ -111,7 +113,7 @@
     $('#keep').html(inventory.show())
     $('#playground').empty()
     if player.word?
-        if player.word.full == shoot_name?.full
+        if player.word.full == inventory.name?.full
             player.word.update()
         else
             player.word.move()
@@ -121,15 +123,15 @@
     player.die = true if pool_words.attack()
     pool_words.draw()
     if player.die
-        clearInterval(animate_id)
-        animate_id = null
+        clearInterval(player.game)
+        player.game = null
         $('#playground').css('background-color', 'darkred')

 $(document).keydown (event) ->
     if event.keyCode == 13 # enter
-        if not animate_id?
-            animate_id = setInterval(draw, 12)
+        if not player.game?
+            player.game = setInterval(draw, 12)
     if event.keyCode in [8, 27, 46] # backspace, escape, delete
         if player.word?
             player.word.reset()
@@ -140,9 +142,9 @@
     c = String.fromCharCode(event.charCode)
     if not player.word?
         player.word = pool_words.get(c)
-    if not player.word? and shoot_name?
-        if c == shoot_name.remain[0]
-            player.word = shoot_name
+    if not player.word? and inventory.name?
+        if c == inventory.name.remain[0]
+            player.word = inventory.name
     if c == player.word?.remain[0]
         player.word.shot()
     if player.word?.remain == ''


Local declaration not needed

Local declaration not needed

--- src/kona.coffee
+++ src/kona.coffee
@@ -10,7 +10,7 @@
 #
 #
 # Set the main object on the window
-Kona = window.Kona = {}
+window.Kona = {}

 # Set this to true to log info to the console
 # Make sure to disable for production dists
@@ -29,6 +29,7 @@
 #
 #     Kona.ready ->
 #       console.log "Ready to go!"
+#
 Kona.ready = (callback) ->
   if document.readyState == 'complete'
     Kona.isReady = true


fixes #73

fixes #73

--- src/leaf.coffee
+++ src/leaf.coffee
@@ -21,10 +21,14 @@
     return @formats

   deleteText: (offset, length) ->
+    return unless length > 0
     @text = @text.slice(0, offset) + @text.slice(offset + length)
     @length = @text.length
-    targetNode = @node.firstChild or @node
-    DOM.setText(targetNode, @text)
+    if DOM.EMBED_TAGS[@node.tagName]?
+      textNode = @node.ownerDocument.createTextNode(@text)
+      @node = DOM.replaceNode(textNode, @node)
+    else
+      DOM.setText(@node, @text)

   insertText: (offset, text) ->
     @text = @text.slice(0, offset) + text + @text.slice(offset)


Calling AtomApplication.open() without parameters should work.

--- src/main.coffee
+++ src/main.coffee
@@ -173,7 +173,7 @@
   sendCommand: (command) ->
     atomWindow.sendCommand command for atomWindow in @windows when atomWindow.browserWindow.isFocused()

-  open: (pathsToOpen) ->
+  open: (pathsToOpen = []) ->
     pathsToOpen = [null] if pathsToOpen.length == 0
     for pathToOpen in pathsToOpen
       pathToOpen = path.resolve(executedFrom, pathToOpen) if executedFrom and pathToOpen


re-implement `$defer()`, `$delay()` without using `$async()`


added direct argument passing to ninja

added direct argument passing to ninja

--- src/mist.coffee
+++ src/mist.coffee
@@ -13,17 +13,32 @@
 os = require 'os'
 path = require 'path'
 spawn = (require 'child_process').spawn
+config = require 'commander'
+packageJson = require '../package'
 MistParser = require './mist-parser'

 ninjaProc = process.env.NINJA || "#{__dirname}/ninja/ninja"
+
+ninjaArgs = []
+mistArgs = []
+for a, i in process.argv.slice 2
+  if a is '--'
+    ninjaArgs = process.argv.slice i+3
+    break
+  else mistArgs.push a
+
+config
+  .version packageJson.version
+  .parse mistArgs

 runNinja = (ninja, base)->
   console.log 'platform:', os.platform()
   switch os.platform()
     when 'darwin' or 'linux'
       console.log 'running Ninja'
-      proc = spawn ninjaProc, [ '-vf', '/dev/stdin' ], stdio: [ null,
-        process.stdout, process.stderr ]
+      proc = spawn ninjaProc, ([ '-vf', '/dev/stdin' ].concat ninjaArgs),
+        stdio: [ null,
+          process.stdout, process.stderr ]
       console.log 'uploading translated Mist configuration to Ninja...'
       proc.stdin.write ninja
       console.log 'upload complete'
@@ -36,7 +51,7 @@
       fs.closeSync tmpFile.fd
       console.log 'running Ninja'
       spawn ninjaProc,
-        [ '-vf', tmpFile.name, '-C', base ],
+        ([ '-vf', tmpFile.name, '-C', base ].concat ninjaArgs),
           stdio: [ 'pipe',process.stdout, process.stderr ]

 findMist = ()->


molo binds itself on module.exports on CommonJS environments and hasOwnProperty call is independent from the current object

--- src/molo.coffee
+++ src/molo.coffee
@@ -16,7 +16,7 @@
 isJavaScriptFile = (filename) -> hasExtension filename, '.js'

 # Wrapper function
-do (root = exports ? this) ->
+do (root = module?.exports ? this) ->
   cache = {}
   queue = {}
   pathSep = '/'
@@ -86,7 +86,7 @@
       # Checking for cache[i] is not strict enough
       # because a module could have undefined, null, 0 or an empty string
       # as its export value
-      if cache.hasOwnProperty(i)
+      if Object.hasOwnProperty.call cache, i
         cacheDeps.push cache[i]
       else
         # Plugin functionality


Don't collect a list of added elements.

Don't collect a list of added elements.

--- src/engine.coffee
+++ src/engine.coffee
@@ -43,6 +43,7 @@
             @elements.push(element)
             element.bind 'destroyed', (e) =>
                 @remove(e)
+        return this

     # Remove the given elements from the engine.
     remove : (elements...) ->


Adding comments.

Adding comments.

--- src/engine.coffee
+++ src/engine.coffee
@@ -25,7 +25,7 @@
         @timestamp = null  # The timestamp of the last step.
         @isRunning = false
         @interval = 5      # The # of the milliseconds to sleep between steps.
-        @iteration = 0
+        @iteration = 0     # The # of iterations the engine has executed.

     # Start the engine's time simulation.
     start : () ->


small bugfix in filter

small bugfix in filter

--- src/filter.coffee
+++ src/filter.coffee
@@ -4,15 +4,15 @@
 filter.edgesAssigned = (fold, target) ->
   i for assignment, i in fold.edges_assignment when assignment == target
 filter.mountainEdges = (fold) ->
-  assignment.edgesAssigned fold, 'M'
+  filter.edgesAssigned fold, 'M'
 filter.valleyEdges = (fold) ->
-  assignment.edgesAssigned fold, 'V'
+  filter.edgesAssigned fold, 'V'
 filter.flatEdges = (fold) ->
-  assignment.edgesAssigned fold, 'F'
+  filter.edgesAssigned fold, 'F'
 filter.boundaryEdges = (fold) ->
-  assignment.edgesAssigned fold, 'B'
+  filter.edgesAssigned fold, 'B'
 filter.unassignedEdges = (fold) ->
-  assignment.edgesAssigned fold, 'U'
+  filter.edgesAssigned fold, 'U'

 filter.keysStartingWith = (fold, prefix) ->
   key for key of fold when key[...prefix.length] == prefix


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,7 +1,7 @@
 ###*
 The Follow module provides a simple method to set an object's
 velocity so that it will approach another object. This method
-
+relies on

 player = GameObject


Clean up existing pane API docs

Clean up existing pane API docs

--- src/pane.coffee
+++ src/pane.coffee
@@ -6,9 +6,10 @@
 Editor = require './editor'
 PaneView = null

-# Extended: A container for multiple items, one of which is *active* at a given
-# time. With the default packages, a tab is displayed for each item and the
-# active item's view is displayed.
+# Extended: A container for presenting content in the center of the workspace.
+# Panes can contain multiple items, one of which is *active* at a given time.
+# The view corresponding to the active item is displayed in the interface. In
+# the default configuration, tabs are also displayed for each item.
 module.exports =
 class Pane extends Model
   atom.deserializers.add(this)
@@ -164,6 +165,9 @@
   onWillDestroyItem: (callback) ->
     @emitter.on 'will-destroy-item', callback

+  # Public: Determine whether the pane is active.
+  #
+  # Returns a {Boolean}.
   isActive: ->
     @container?.getActivePane() is this

@@ -177,8 +181,7 @@
     @focused = false
     true # if this is called from an event handler, don't cancel it

-  # Public: Makes this pane the *active* pane, causing it to gain focus
-  # immediately.
+  # Public: Makes this pane the *active* pane, causing it to gain focus.
   activate: ->
     @container?.setActivePane(this)
     @emit 'activated'
@@ -203,12 +206,15 @@
       @emitter.emit 'did-change-active-item', @activeItem
     @activeItem

-  # Public: Returns an {Editor} if the pane item is an {Editor}, or null
-  # otherwise.
+  # Return an {Editor} if the pane item is an {Editor}, or null otherwise.
   getActiveEditor: ->
     @activeItem if @activeItem instanceof Editor

-  # Public: Returns the item at the specified index.
+  # Public: Return the item at the given index.
+  #
+  # * `index` {Number}
+  #
+  # Returns an item or `null` if no item exists at the given index.
   itemAtIndex: (index) ->
     @items[index]

@@ -228,27 +234,33 @@
     else
       @activateItemAtIndex(@items.length - 1)

-  # Returns the index of the current active item.
+  # Public: Get the index of the active item.
+  #
+  # Returns a {Number}.
   getActiveItemIndex: ->
     @items.indexOf(@activeItem)

-  # Makes the item at the given index active.
+  # Public: Activate the item at the given index.
+  #
+  # * `index` {Number}
   activateItemAtIndex: (index) ->
     @activateItem(@itemAtIndex(index))

-  # Makes the given item active, adding the item if necessary.
+  # Public: Make the given item *active*, causing it to be displayed by
+  # the pane's view.
   activateItem: (item) ->
     if item?
       @addItem(item)
       @setActiveItem(item)

-  # Public: Adds the item to the pane.
-  #
-  # * `item` The item to add. It can be a model with an associated view or a view.
-  # * `index` (optional) {Number} at which to add the item. If omitted, the item is
-  #   added after the current active item.
-  #
-  # Returns the added item
+  # Public: Add the given item to the pane.
+  #
+  # * `item` The item to add. It can be a model with an associated view or a
+  #   view.
+  # * `index` (optional) {Number} at which to add the item. If omitted, the item
+  #   is added after the current active item.
+  #
+  # Returns the added item.
   addItem: (item, index=@getActiveItemIndex() + 1) ->
     return if item in @items

@@ -261,15 +273,15 @@
     @setActiveItem(item) unless @getActiveItem()?
     item

-  # Public: Adds the given items to the pane.
-  #
-  # * `items` An {Array} of items to add. Items can be models with associated
-  #   views or views. Any items that are already present in items will
-  #   not be added.
-  # * `index` (optional) {Number} index at which to add the item. If omitted, the item is
-  #   added after the current active item.
-  #
-  # Returns an {Array} of the added items
+  # Public: Add the given items to the pane.
+  #
+  # * `items` An {Array} of items to add. Items can be views or models with
+  #   associated views. Any objects that are already present in the pane's
+  #   current items will not be added again.
+  # * `index` (optional) {Number} index at which to add the items. If omitted,
+  #   the item is #   added after the current active item.
+  #
+  # Returns an {Array} of added items.
   addItems: (items, index=@getActiveItemIndex() + 1) ->
     items = items.filter (item) => not (item in @items)
     @addItem(item, index + i) for item, i in items
@@ -295,7 +307,10 @@
     @container?.itemDestroyed(item) if destroyed
     @destroy() if @items.length is 0 and atom.config.get('core.destroyEmptyPanes')

-  # Public: Moves the given item to the specified index.
+  # Public: Move the given item to the given index.
+  #
+  # * `item` The item to move.
+  # * `index` {Number} indicating the index to which to move the item.
   moveItem: (item, newIndex) ->
     oldIndex = @items.indexOf(item)
     @items.splice(oldIndex, 1)
@@ -303,18 +318,26 @@
     @emit 'item-moved', item, newIndex
     @emitter.emit 'did-move-item', {item, oldIndex, newIndex}

-  # Public: Moves the given item to the given index at another pane.
+  # Public: Move the given item to the given index on another pane.
+  #
+  # * `item` The item to move.
+  # * `pane` {Pane} to which to move the item.
+  # * `index` {Number} indicating the index to which to move the item in the
+  #   given pane.
   moveItemToPane: (item, pane, index) ->
     pane.addItem(item, index)
     @removeItem(item)

-  # Public: Destroys the currently active item and make the next item active.
+  # Public: Destroy the active item and activate the next item.
   destroyActiveItem: ->
     @destroyItem(@activeItem)
     false

-  # Public: Destroys the given item. If it is the active item, activate the next
-  # one. If this is the last item, also destroys the pane.
+  # Public: Destroy the given item.
+  #
+  # If the item is active, the next item will be activated. If the item is the
+  # last item, the pane will be destroyed if the `core.destroyEmptyPanes` config
+  # setting is `true`.
   destroyItem: (item) ->
     index = @items.indexOf(item)
     if index isnt -1
@@ -327,14 +350,18 @@
       else
         false

-  # Public: Destroys all items and destroys the pane.
+  # Public: Destroy all items.
   destroyItems: ->
     @destroyItem(item) for item in @getItems()

-  # Public: Destroys all items but the active one.
+  # Public: Destroy all items except for the active item.
   destroyInactiveItems: ->
     @destroyItem(item) for item in @getItems() when item isnt @activeItem

+  # Public: Destroy the pane and all its items.
+  #
+  # If this is the last pane, all the items will be destroyed but the pane
+  # itself will not be destroyed.
   destroy: ->
     if @container?.isAlive() and @container.getPanes().length is 1
       @destroyItems()
@@ -347,8 +374,6 @@
     @container.activateNextPane() if @isActive()
     item.destroy?() for item in @items.slice()

-  # Public: Prompts the user to save the given item if it can be saved and is
-  # currently unsaved.
   promptToSaveItem: (item) ->
     return true unless item.shouldPromptToSave?()

@@ -363,18 +388,23 @@
       when 1 then false
       when 2 then true

-  # Public: Saves the active item.
-  saveActiveItem: ->
-    @saveItem(@activeItem)
-
-  # Public: Saves the active item at a prompted-for location.
-  saveActiveItemAs: ->
-    @saveItemAs(@activeItem)
-
-  # Public: Saves the specified item.
+  # Public: Save the active item.
+  saveActiveItem: (nextAction) ->
+    @saveItem(@getActiveItem(), nextAction)
+
+  # Public: Prompt the user for a location and save the active item with the
+  # path they select.
+  #
+  # * `nextAction` (optional) {Function} which will be called after the item is
+  #   successfully saved.
+  saveActiveItemAs: (nextAction) ->
+    @saveItemAs(@getActiveItem(), nextAction)
+
+  # Public: Save the given item.
   #
   # * `item` The item to save.
-  # * `nextAction` (optional) {Function} which will be called after the item is saved.
+  # * `nextAction` (optional) {Function} which will be called after the item is
+  #   successfully saved.
   saveItem: (item, nextAction) ->
     if item?.getUri?()
       item.save?()
@@ -382,10 +412,12 @@
     else
       @saveItemAs(item, nextAction)

-  # Public: Saves the given item at a prompted-for location.
+  # Public: Prompt the user for a location and save the active item with the
+  # path they select.
   #
   # * `item` The item to save.
-  # * `nextAction` (optional) {Function} which will be called after the item is saved.
+  # * `nextAction` (optional) {Function} which will be called after the item is
+  #   successfully saved.
   saveItemAs: (item, nextAction) ->
     return unless item?.saveAs?

@@ -395,17 +427,20 @@
       item.saveAs(newItemPath)
       nextAction?()

-  # Public: Saves all items.
+  # Public: Save all items.
   saveItems: ->
     @saveItem(item) for item in @getItems()

-  # Public: Returns the first item that matches the given URI or undefined if
+  # Public: Return the first item that matches the given URI or undefined if
   # none exists.
+  #
+  # * `uri` {String} containing a URI.
   itemForUri: (uri) ->
     find @items, (item) -> item.getUri?() is uri

-  # Public: Activates the first item that matches the given URI. Returns a
-  # boolean indicating whether a matching item was found.
+  # Public: Activate the first item that matches the given URI.
+  #
+  # Returns a {Boolean} indicating whether an item matching the URI was found.
   activateItemForUri: (uri) ->
     if item = @itemForUri(uri)
       @activateItem(item)
@@ -417,19 +452,19 @@
     if @activeItem?
       @activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())

-  # Public: Creates a new pane to the left of the receiver.
-  #
-  # * `params` {Object} with keys
-  #   * `items` (optional) {Array} of items with which to construct the new pane.
+  # Public: Create a new pane to the left of this pane.
+  #
+  # * `params` (optional) {Object} with the following keys:
+  #   * `items` (optional) {Array} of items to add to the new pane.
   #
   # Returns the new {Pane}.
   splitLeft: (params) ->
     @split('horizontal', 'before', params)

-  # Public: Creates a new pane to the right of the receiver.
-  #
-  # * `params` {Object} with keys:
-  #   * `items` (optional) {Array} of items with which to construct the new pane.
+  # Public: Create a new pane to the right of this pane.
+  #
+  # * `params` (optional) {Object} with the following keys:
+  #   * `items` (optional) {Array} of items to add to the new pane.
   #
   # Returns the new {Pane}.
   splitRight: (params) ->
@@ -437,8 +472,8 @@

   # Public: Creates a new pane above the receiver.
   #
-  # * `params` {Object} with keys:
-  #   * `items` (optional) {Array} of items with which to construct the new pane.
+  # * `params` (optional) {Object} with the following keys:
+  #   * `items` (optional) {Array} of items to add to the new pane.
   #
   # Returns the new {Pane}.
   splitUp: (params) ->
@@ -446,8 +481,8 @@

   # Public: Creates a new pane below the receiver.
   #
-  # * `params` {Object} with keys:
-  #   * `items` (optional) {Array} of items with which to construct the new pane.
+  # * `params` (optional) {Object} with the following keys:
+  #   * `items` (optional) {Array} of items to add to the new pane.
   #
   # Returns the new {Pane}.
   splitDown: (params) ->


add pjaxbeforeload event

add pjaxbeforeload event

--- src/pjax.coffee
+++ src/pjax.coffee
@@ -33,7 +33,7 @@
           top: $(document).scrollTop()
           left: $(document).scrollLeft()

-    @on 'pjaxload', (e, $page, page) =>
+    @on 'pjaxbeforeload', (e, page) =>
       if page.params and page.params.scrollPosition
         $(document).scrollTop(page.params.scrollPosition.top)
           .scrollLeft(page.params.scrollPosition.left)
@@ -41,6 +41,7 @@
         $(document).scrollTop(0)
           .scrollLeft(0)

+    @on 'pjaxload', (e, $page, page) =>
       return unless page.url
       url = simple.url page.url
       return unless url.hash
@@ -125,6 +126,7 @@
     history.pushState state, state.name, state.url
     document.title = state.name

+    @trigger 'pjaxbeforeload', [page]
     @requestPage page


Store the whole packet in the message, be more explicit about what thread to reply to

--- src/quip.coffee
+++ src/quip.coffee
@@ -9,8 +9,8 @@
 WebSocket = require "ws"

 class QuipTextMessage extends TextMessage
-  constructor: (@user, @text, @quipMessage) ->
-    super @user, @text, @quipMessage.id
+  constructor: (@user, @text, @quipPacket) ->
+    super @user, @text, @quipPacket.message.id

 class QuipHubot extends Adapter
   constructor: (robot) ->
@@ -29,13 +29,13 @@
       else
         text.push(msg)
     return unless text.length or attachments.length
+    packet = envelope.message.quipPacket
     options =
-        threadId: envelope.room
-        serviceId: envelope.message.id
+        threadId: packet.thread.id
+        serviceId: packet.message.id
     options.attachments = attachments.join "," if attachments.length
     options.content = text.join "\n\n" if text.length
-    if envelope.message.quipMessage.annotation
-        options.annotationId = envelope.message.quipMessage.annotation.id
+    options.annotationId = packet.message.annotation.id if packet.message.annotation
     @robot.logger.info "Sending to #{envelope.room}: #{JSON.stringify(options)}"
     @client.newMessage options, @messageSent

@@ -136,7 +136,7 @@
         text = packet.message.text
         if @selfMention == text.substr 0, @selfMention.length
           text = @robot.name + text.substr @selfMention.length
-        message = new QuipTextMessage user, text, packet.message
+        message = new QuipTextMessage user, text, packet
         @robot.receive message
       else
         @robot.logger.info "Got %s", packet.type


added Repo#remote_push(name, callback)

added Repo#remote_push(name, callback)

--- src/repo.coffee
+++ src/repo.coffee
@@ -113,6 +113,16 @@
     , (err, stdout, stderr) ->
       callback err

+  # Public: `git push <name>`.
+  #
+  # name     - String name of the remote
+  # callback - Receives `(err)`.
+  #
+  remote_push: (name, callback) ->
+    @git "push", {}, name
+    , (err, stdout, stderr) ->
+      callback err
+
   # Public: `git merge <name>`.
   #
   # name     - String name of the source


Master added to the `pool` type.

Master added to the `pool` type.

--- src/spec.coffee
+++ src/spec.coffee
@@ -117,6 +117,8 @@
                 if value.$pool is @value.UUID
                   @field.push value.UUID
               exit: (value) -> remove @field, value.UUID
+
+          master: get('master')

           VMs: @dynamic [],
             VM:


Extension sets loaded

Extension sets loaded

--- src/atom/extension.coffee
+++ src/atom/extension.coffee
@@ -1,12 +1,18 @@
+# Extension subclasses must call super in all overridden methods.
 module.exports =
 class Extension
-  pane: null
+  loaded: false

   constructor: ->
     console.log "#{@constructor.name}: Loaded"

-  storageNamespace: -> @constructor.name
+  # `startup` should be called by you in Extension subclasses when they need
+  # to appear on the screen, attach themselves to a Resource, or otherwise become active.
+  startup: ->
+    @loaded = true

-  startup: ->
-
+  # `shutdown` shuold be called by you in Extension subclasses when they need
+  # to be remove from the screen, unattach themselves from a Resource, or otherwise become
+  # inactive.
   shutdown: ->
+    @loaded = false


sync: fix router.authorizeFor() usage

sync: fix router.authorizeFor() usage

--- src/sync.coffee
+++ src/sync.coffee
@@ -165,7 +165,7 @@
                     # Weird, skip;
                     cb3()

-                router.authorizeFor user, server, (err, valid) ->
+                router.authorizeFor server, user, (err, valid) ->
                     if err or !valid
                         console.error((err and err.stack) or err or
                             "Cannot sync #{subscription.node} from unauthorized server #{server}"


fix(user-agent): Don't assume Tasks are spawned from renderer processes #2079

--- src/task.coffee
+++ src/task.coffee
@@ -78,7 +78,7 @@
     taskPath = require.resolve(taskPath)
     taskPath = taskPath.replace(/\\/g, "\\\\")

-    env = _.extend({}, process.env, {taskPath, userAgent: navigator.userAgent})
+    env = _.extend({}, process.env, {taskPath, userAgent: 'NylasN1'})
     @childProcess = ChildProcess.fork '--eval', [bootstrap], {env, silent: true}

     @on "task:log", -> console.log(arguments...)


Add reference for the non blocking loader pattern

--- src/loader.coffee
+++ src/loader.coffee
@@ -6,6 +6,7 @@
 library without blocking the `window.onload` event.

 @see http://goo.gl/VLDc3F More information on FiF technique
+@see https://developer.akamai.com/blog/2012/12/12/non-blocking-script-loader-pattern
 ###
 ((url)->
   try
@@ -37,6 +38,9 @@
   try
     doc = iframe.contentWindow.document
   catch err
+    # In IE < 11 there is an issue if the main page sets document.domain, even
+    # if document.domain is set to itself (document.domain=document.domain).
+    # Solution: https://developer.akamai.com/blog/2012/12/12/non-blocking-script-loader-pattern
     dom = document.domain
     iframe.src = "javascript:var d=document.open();d.domain='#{dom}';void(0);"
     doc = iframe.contentWindow.document