Fixed name not being required for registration with settings requiring it.

--- packages/rocketchat-ui-login/login/form.coffee
+++ packages/rocketchat-ui-login/login/form.coffee
@@ -6,7 +6,7 @@
 		return if RocketChat.settings.get 'Accounts_RequireNameForSignUp' then t('Name') else t('Name_optional')

 	showFormLogin: ->
-		return RocketChat.settings.get 'Accounts_ShowFormLogin'
+		return RocketChat.settings.get 'Accounts_ShowFormLogin'

 	showName: ->
 		return 'hidden' unless Template.instance().state.get() is 'register'
@@ -156,7 +156,7 @@
 				validationObj['pass'] = t('Invalid_pass')

 		if instance.state.get() is 'register'
-			if RocketChat.settings.get 'Accounts_RequireNameForSignUp' and not formObj['name']
+			if RocketChat.settings.get('Accounts_RequireNameForSignUp') and not formObj['name']
 				validationObj['name'] = t('Invalid_name')
 			if formObj['confirm-pass'] isnt formObj['pass']
 				validationObj['confirm-pass'] = t('Invalid_confirm_pass')


Include channel, events, and schedule when hashing presets

--- lib/cache.coffee
+++ lib/cache.coffee
@@ -62,6 +62,9 @@
   h = crypto.createHash('md5')
   for p in presets
     h.update(JSON.stringify(p.name))
+    h.update(JSON.stringify(p.channel))
+    h.update(JSON.stringify(p.schedule))
+    h.update(JSON.stringify(p.events))
     h.update(JSON.stringify(p.precondition))
     h.update(JSON.stringify(p.provisions))


Update @votes from redis in @update()

Update @votes from redis in @update()

--- lib/comic.coffee
+++ lib/comic.coffee
@@ -6,8 +6,11 @@

   # The Class and Instance accessors for the keys.
   # The instance accessor just looks it up by class one
-  @key: () -> "#{@prefix}:comics"
-  key: -> @constructor.key()
+  @key: (suffix) ->
+    key = "#{@prefix}:comics"
+    key += ":#{suffix}" if suffix?
+    key
+  key: (suffix) -> @constructor.key(suffix)

   # Describe either a new, or exsting comic
   constructor: (@url, @time, options={}) ->
@@ -35,6 +38,7 @@
     cb("@time not set") unless @time

     failed = false
+    gotVotes = false
     finish = (finishPart) =>
       (err, res) =>
         return if failed # Make sure nothing else happens after we fail
@@ -44,7 +48,8 @@
         finishPart(res)

         # Then return success to the caller if we filled in both sides
-        return cb(undefined, this) if (@next isnt undefined) and (@prev isnt undefined)
+        if (@next isnt undefined) and (@prev isnt undefined) and gotVotes
+          return cb(undefined, this)

     # Fire the workers to update the next and prev
     #
@@ -61,6 +66,12 @@
         @next = res[1] || null
     redis.zrevrangebyscore [@key(), "#{@time - 1}", '-inf', 'WITHSCORES', 'LIMIT', 0, 1], finish (res) =>
         @prev = res[1] || null
+
+    # Kick off the job to fetch the vote count, or keep it at 0
+    redis.zscore @key('votes'), @url, finish (res) =>
+      @votes = res or 0
+      gotVotes = true
+

   # Return a comic stamped at `stamp` to caller by
   # invoking callback as `cb(err, Comic)` if it is found.


Support custom temp directory

Support custom temp directory

--- lib/index.coffee
+++ lib/index.coffee
@@ -13,6 +13,7 @@
 module.exports = (keys, options={}) ->
   _(options).defaults
     delim: '\t'
+    tmp_dir: null
   new Understream()
     .map (obj) ->
       vals = _.chain(keys)
@@ -26,9 +27,13 @@
       vals.join(options.delim) + options.delim + JSON.stringify(obj) + '\n'
     .spawn('sort', do ->
       key_opts = _.flatten _([1..keys.length]).map (i) -> ['-k', "#{i},#{i}"]
-      [ '-s'                # stable sort
+      flags = [
+        '-s'                # stable sort
         '-t', options.delim # column separator
-      ].concat key_opts     # which columns to sort by
+      ]
+      flags = flags.concat ["--temporary-directory", options.tmp_dir] if options.tmp_dir
+      flags = flags.concat key_opts # which columns to sort by
+      flags
     )
     .spawn('cut', ['-d', options.delim, '-f', keys.length + 1])
     .split('\n')


authenticate subscriber redis client as well

--- lib/index.coffee
+++ lib/index.coffee
@@ -194,6 +194,7 @@
       stream._read = ->

       redisObserver = redisLib.createClient redis.port, redis.host, redis.options
+      redisObserver.auth redis.auth_pass if redis.auth_pass

       open = true


Revert default `texPath` value to remove "pain"

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -6,7 +6,7 @@

 module.exports =
   configDefaults:
-    texPath: ""
+    texPath: "$PATH:/usr/texbin"
     outputDirectory: ""
     enableShellEscape: false


show panel header only when there's something

--- lib/panel.coffee
+++ lib/panel.coffee
@@ -16,7 +16,7 @@

 Panel = Vue.extend
     template: '''
-

+

{{content.title}}

             <panel-input-method id="panel-input-method" v-show="inputMethodMode" :input="inputMethod"></panel-input-method>


Leave dumb logging; remove extra-dumb logging

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -66,10 +66,6 @@

   console.log 'Mounting middleware...'

-  app.use (req, res, next) ->
-    console.log 'Passing through middleware'
-    next()
-
   app.use sharify

   blocker.addToReferrers [
@@ -124,10 +120,6 @@

   console.log 'Mounting apps...'

-  app.use (req, res, next) ->
-    console.log 'Passing through routes'
-    next()
-
   app
     # Normal routing: In order
     .use require '../apps/feed'


Bug fix for batch with sort

Bug fix for batch with sort

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -285,8 +285,8 @@

     batch_cursor = _.extend({
       $limit: options.$limit or BATCH_DEFAULT_LIMIT
-      $offset: parsed_query.$offset or 0
-      $sort: parsed_query.$sort or 'id' # TODO: generalize sort for different types of sync
+      $offset: parsed_query.cursor.$offset or 0
+      $sort: parsed_query.cursor.$sort or 'id' # TODO: generalize sort for different types of sync
     }, parsed_query.find) # add find parameters
     runBatch(batch_cursor, callback)


:fire: unused and fix saveEditorState for v1.14.0-beta

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -1,4 +1,4 @@
-{Range, Disposable, CompositeDisposable} = require 'atom'
+{Disposable} = require 'atom'
 _ = require 'underscore-plus'

 getAdjacentPaneForPane = (pane) ->
@@ -46,13 +46,6 @@
       flashDisposable = null
   marker

-getVisibleBufferRange = (editor) ->
-  [startRow, endRow] = editor.element.getVisibleRowRange()
-  return null unless (startRow? and endRow?)
-  startRow = editor.bufferRowForScreenRow(startRow)
-  endRow = editor.bufferRowForScreenRow(endRow)
-  new Range([startRow, 0], [endRow, Infinity])
-
 smartScrollToBufferPosition = (editor, point) ->
   editorElement = editor.element
   editorAreaHeight = editor.getLineHeightInPixels() * (editor.getRowsPerPage() - 1)
@@ -78,12 +71,11 @@
     Element.prototype = options.prototype if options.prototype?
   Element

-# Return function to restore editor's scrollTop and fold state.
 saveEditorState = (editor) ->
   editorElement = editor.element
   scrollTop = editorElement.getScrollTop()

-  foldStartRows = editor.displayLayer.findFoldMarkers({}).map (m) -> m.getStartPosition().row
+  foldStartRows = editor.displayLayer.foldsMarkerLayer.findMarkers({}).map (m) -> m.getStartPosition().row
   ->
     for row in foldStartRows.reverse() when not editor.isFoldedAtBufferRow(row)
       editor.foldBufferRow(row)
@@ -91,7 +83,6 @@

 module.exports = {
   getAdjacentPaneForPane
-  getVisibleBufferRange
   openItemInAdjacentPaneForPane
   decorateRange
   smartScrollToBufferPosition


Utils added for table name

Utils added for table name

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -22,10 +22,12 @@
   @guid = -> return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4())

   @urlToModelName: (url) ->
+    return inflection.classify(inflection.singularize(Utils.urlToTableName(url)))
+
+  @urlToTableName: (url) ->
     url_parts = URL.parse(url)
     database_parts = url_parts.pathname.split('/')
     table = database_parts[database_parts.length-1]
-    return inflection.classify(inflection.singularize(table))

   @urlToDatabaseEndpoint: (url) ->
     url_parts = URL.parse(url)


Bug fix

Bug fix

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -30,7 +30,7 @@
       result[key] = Utils.parseValues(value) for key, value of values
       return result
     else if _.isString(values)
-      if (values.length > 20) and values[values.length-1] is 'Z'
+      if (values.length >= 20) and values[values.length-1] is 'Z'
         date = moment.utc(values)
         return if date and date.isValid() then date.toDate() else values
       else


fix(core): revert wrong usage of dollar sign

--- packages/core/test/actions/execute/wait.coffee
+++ packages/core/test/actions/execute/wait.coffee
@@ -239,7 +239,7 @@
           await @execute.wait
             command: "test -d #{tmpdir}/a_file"
             interval: 100
-            $retry: 3
+            retry: 3
         catch err
           err.code.should.eql 'NIKITA_EXECUTE_WAIT_MAX_RETRY'
           err.$logs


Script link fix.

Script link fix.

--- packages/retronator-hq/floor1/cafe/cafe.coffee
+++ packages/retronator-hq/floor1/cafe/cafe.coffee
@@ -51,8 +51,8 @@
     @id: -> "Retronator.HQ.Cafe.Burra"

     @scriptUrls: -> [
-      'retronator_retronator-hq/floor1/cafe/user.script'
-      'retronator_retronator-hq/floor1/cafe/character.script'
+      'retronator_retronator-hq/floor1/cafe/burra.script'
+      'retronator_retronator-hq/floor1/cafe/burra-character.script'
     ]

     class @UserScript extends LOI.Adventure.Script


Config: GenerateKonfig: check for value instead of existence

--- config/generateKonfig.coffee
+++ config/generateKonfig.coffee
@@ -13,14 +13,14 @@
     userAgent   : "Koding-Bridge-#{options.configName}"

   gitlab =
-    host: options.gitlabHost ? credentials.gitlab.host
-    port: options.gitlabPort ? credentials.gitlab.port ? 3000
-    applicationId: options.gitlabAppId ? credentials.gitlab.applicationId
-    applicationSecret: options.gitlabAppSecret ? credentials.gitlab.applicationSecret
+    host: options.gitlabHost or credentials.gitlab.host
+    port: options.gitlabPort or credentials.gitlab.port or 3000
+    applicationId: options.gitlabAppId or credentials.gitlab.applicationId
+    applicationSecret: options.gitlabAppSecret or credentials.gitlab.applicationSecret
     team: credentials.gitlab.team
     redirectUri: "http://#{credentials.gitlab.team}.#{options.host}/-/oauth/gitlab/callback"
-    systemHookToken: options.gitlabToken ? credentials.gitlab.systemHookToken
-    hooksEnabled: (options.gitlabHost ? credentials.gitlab.host) isnt ''
+    systemHookToken: options.gitlabToken or credentials.gitlab.systemHookToken
+    hooksEnabled: options.gitlabHost or credentials.gitlab.host

   regions =
     kodingme: "#{options.configName}"


Change a few methods' names

Change a few methods' names

--- app/assets/javascripts/components/todo_list/app.js.coffee
+++ app/assets/javascripts/components/todo_list/app.js.coffee
@@ -78,7 +78,7 @@
     flux = @getFlux()
     flux.store('TodoStore').getState()

-  handleTodoTextChange: (e) ->
+  onChangeTodoText: (e) ->
     @setState(newTodoText: e.target.value)

   onSubmitForm: (e) ->
@@ -87,7 +87,7 @@
       @getFlux().actions.addTodo(@state.newTodoText)
       @setState(newTodoText: "")

-  clearCompletedTodos: (e) ->
+  onClickClearButton: (e) ->
     @getFlux().actions.clearTodos()

   render: ->
@@ -105,14 +105,14 @@
           <input type="text"
                  placeholder="New Todo"
                  value={@state.newTodoText}
-                 onChange={@handleTodoTextChange}
+                 onChange={@onChangeTodoText}
                  className="form-control" />
         </div>
         <div className="form-group">
           <input type="submit"
                  value="Add Todo"
                  className="btn btn-success"/>
-          <button onClick={@clearCompletedTodos}
+          <button onClick={@onClickClearButton}
                   className={clearButton}>
             Clear Completed
           </button>


Ace: Enable brace completion feature.

Ace: Enable brace completion feature.

--- client/ace/lib/ace.coffee
+++ client/ace/lib/ace.coffee
@@ -328,14 +328,15 @@
     tabView.removePane_ tabView.getActivePane()

+  setTrimTrailingWhitespaces: (value) -> @isTrimWhiteSpacesEnabled = value
+
+
+  setEnableBraceCompletion: (state) -> @editor.setBehavioursEnabled state
+
+
   ###
   FS REQUESTS
   ###
-
-  setTrimTrailingWhitespaces: (value) ->
-
-    @isTrimWhiteSpacesEnabled = value
-

   requestSave: (options = {}) ->

@@ -435,6 +436,9 @@

   getEnableEmmet: ->
     @appStorage.getValue('enableEmmet') ? no
+
+  getEnableBraceCompletion: ->
+    @appStorage.getValue('enableBraceCompletion') ? yes

   getSettings: ->


Removed twipsy.

Removed twipsy.

--- core/KDWindowController.coffee
+++ core/KDWindowController.coffee
@@ -72,8 +72,8 @@
     @layers = layers = []

     document.body.addEventListener 'mousedown', (e)=>
-      $('.twipsy').remove() # temporary for beta
-      lastLayer = layers[layers.length-1]
+      # $('.twipsy').remove() # temporary for beta
+      lastLayer = layers.last

       if lastLayer and $(e.target).closest(lastLayer?.$()).length is 0
         # log lastLayer, "ReceivedClickElsewhere"


Useless closure

Useless closure

--- app/assets/javascripts/uploadcare/widget/widget.js.coffee
+++ app/assets/javascripts/uploadcare/widget/widget.js.coffee
@@ -54,8 +54,7 @@

       __setLoaded: (infoPr) ->
         $.when(infoPr)
-          .fail =>
-            @__fail()
+          .fail(@__fail)
           .done (info) =>
             if @settings.imagesOnly && !uploads.isImage(info)
               return @__fail('image')


fix minor error

fix minor error

Former-commit-id: c123e85ab8ddcd4dc4aa7bb24c0ddf81c40ba0b0 [formerly 8996f4b9f5f7e833ad2c74275c5bb4751aa5c0f6] [formerly 0f65b603b9cff0d99fd58ca4643306eb463ff838 [formerly 0f65b603b9cff0d99fd58ca4643306eb463ff838 [formerly 8aaf9ab3efa9d5fc93d176851cc9a74c4ec194a1]]]
Former-commit-id: 4613e7cd454240d4492538fcf4918450adac1bb0 [formerly 45024b204dd99bb2e38cd3afc8cf4843670c2402]
Former-commit-id: 7edafc67efdb2c6cf90b3a12073ffe82666772a5
Former-commit-id: 0f6963a4abf1f68ec5cd93a61e21ac56868d3c59

--- app/assets/javascripts/views_list/proposallistview.coffee
+++ app/assets/javascripts/views_list/proposallistview.coffee
@@ -31,13 +31,13 @@
     if proposalview? && proposal.data_loaded
       proposalview[callback].apply(proposalview, callback_params)
     else if proposal?
-      callback_params[0]['data_just_loaded'] = true
+      callback_params[0]['data_just_loaded'] = true if callback_params
       $.get Routes.proposal_path(long_id), (data) =>
         proposal.set_data(data)
         proposalview[callback].apply(proposalview, callback_params)
         @listenTo ConsiderIt.app, 'user:signout', @post_signout
     else
-      callback_params[0]['data_just_loaded'] = true
+      callback_params[0]['data_just_loaded'] = true if callback_params

       $.get Routes.proposal_path(long_id), (data) =>
         proposal = new ConsiderIt.Proposal(data.proposal)


Touche events support added

Touche events support added

--- impl/css/level0/item.coffee
+++ impl/css/level0/item.coffee
@@ -1,4 +1,6 @@
 'use strict'
+
+utils = require 'utils'

 # get transform CSS property name
 transformProp = do ->
@@ -17,6 +19,8 @@
 	rad * 180/Math.PI

 {now} = Date
+
+isTouch = 'ontouchstart' of window

 SIGNALS =
 	'pointerClicked': 'click'
@@ -31,12 +35,30 @@
 		else
 			'mousewheel'

+if isTouch
+	utils.merge SIGNALS,
+		'pointerPressed': 'touchstart'
+		'pointerReleased': 'touchend'
+		'pointerEntered': null
+		'pointerExited': null
+		'pointerMove': 'touchmove'
+
 SIGNALS_CURSORS =
 	'pointerClicked': 'pointer'

-mouseCoordsArgs = (e) ->
-	x: e.screenX
-	y: e.screenY
+mouseCoordsArgs = do ->
+	getArgs = (e) ->
+		x: e.screenX
+		y: e.screenY
+
+	if isTouch
+		(e) ->
+			if e.touches.length
+				getArgs e.touches[0]
+			else
+				getArgs e.changedTouches[0]
+	else
+		getArgs

 SIGNALS_ARGS =
 	'pointerWheel': do ->
@@ -172,14 +194,14 @@
 		if @_impl.isHot
 			updateTransforms @
 		else
-			@_impl.elem.style.left = val
+			@_impl.elem.style.left = "#{val}px"
 			markAction @

 	setItemY: (val) ->
 		if @_impl.isHot
 			updateTransforms @
 		else
-			@_impl.elem.style.top = val
+			@_impl.elem.style.top = "#{val}px"
 			markAction @

 	setItemZ: (val) ->
@@ -198,6 +220,10 @@
 		{elem} = @_impl

 		signal = SIGNALS[name]?(elem, func) or SIGNALS[name]
+
+		# break if event is not supported (e.g. some events on touch devices)
+		unless signal?
+			return

 		customFunc = (e) ->
 			arg = SIGNALS_ARGS[name]? e


CSS optimization for hot position changing added

--- impl/css/level0/item.coffee
+++ impl/css/level0/item.coffee
@@ -16,6 +16,8 @@
 rad2deg = (rad) ->
 	rad * 180/Math.PI

+{now} = Date
+
 SIGNALS =
 	'pointerClicked': 'click'
 	'pointerPressed': 'mousedown'
@@ -24,21 +26,45 @@
 	'pointerExited': 'mouseleave'
 	'pointerWheel': 'wheel'

+HOT_MAX_TIME = 120
+HOT_MAX_ACTIONS = 100
+
 module.exports = (impl) ->
 	{items} = impl

-	updateTransforms = (id) ->
-		item = items[id]
+	updateTransforms = (item) ->
+		transform = ''
+		if item.rotation
+			transform += "rotate(#{rad2deg(item.rotation)}deg) "
+		if item.scale isnt 1
+			transform += "scale(#{item.scale}) "
+		if item.isHot
+			transform += "translate3d(#{item.x}px, #{item.y}px, 0) "
+		item.elem.style[transformProp] = transform

-		transform = "rotate(#{rad2deg(item.rotation)}deg) scale(#{item.scale})"
-		item.elem.style[transformProp] = transform
+	markAction = (item) ->
+		if now() - item.lastAction < HOT_MAX_TIME
+			if item.hotActions++ > HOT_MAX_ACTIONS
+				{style, id} = item.elem
+				item.isHot = true
+				item.x = getItemX id
+				item.y = getItemY id
+				style.left = style.top = '0'
+		else
+			item.lastAction = now()

 	create: (id, target) ->
 		target.elem ?= document.createElement 'div'
 		target.elem.id = id

+		target.x = 0
+		target.y = 0
 		target.rotation = 0
 		target.scale = 1
+
+		target.lastAction = now()
+		target.hotActions = 0
+		target.isHot = false

 	confirmItemChild: (id, child) ->
 		!!items[id].elem.querySelector "##{child}"
@@ -73,17 +99,33 @@
 	setItemHeight: (id, val) ->
 		items[id].elem.style.height = "#{val}px"

-	getItemX: (id) ->
-		parseFloat(items[id].elem.style.left) or 0
+	getItemX: getItemX = (id) ->
+		item = items[id]
+		item.x or parseFloat(item.elem.style.left) or 0

 	setItemX: (id, val) ->
-		items[id].elem.style.left = val
+		item = items[id]

-	getItemY: (id) ->
-		parseFloat(items[id].elem.style.top) or 0
+		if item.isHot
+			item.x = val
+			updateTransforms item
+		else
+			item.elem.style.left = val
+			markAction item
+
+	getItemY: getItemY = (id) ->
+		item = items[id]
+		item.y or parseFloat(item.elem.style.top) or 0

 	setItemY: (id, val) ->
-		items[id].elem.style.top = val
+		item = items[id]
+
+		if item.isHot
+			item.y = val
+			updateTransforms item
+		else
+			item.elem.style.top = val
+			markAction item

 	getItemZ: (id) ->
 		val = items[id].elem.style.zIndex
@@ -100,15 +142,17 @@
 		items[id].scale

 	setItemScale: (id, val) ->
-		items[id].scale = val
-		updateTransforms id
+		item = items[id]
+		item.scale = val
+		updateTransforms item

 	getItemRotation: (id) ->
 		items[id].rotation

 	setItemRotation: (id, val) ->
-		items[id].rotation = val
-		updateTransforms id
+		item = items[id]
+		item.rotation = val
+		updateTransforms item

 	getItemOpacity: (id) ->
 		opacity = items[id].elem.style.opacity


CSS Text impl bug with size change detecting fixed

--- impl/css/level0/text.coffee
+++ impl/css/level0/text.coffee
@@ -36,8 +36,6 @@
 			@_impl.innerElemStyle.fontFamily = @_impl.innerElemStyle.fontFamily
 		return

-	sizeUpdatePending = false
-
 	updateSize = do ->
 		windowLoadQueue = []

@@ -56,14 +54,12 @@
 			if not isFontReady
 				windowLoadQueue.push item

-			sizeUpdatePending = true
-
+			data.sizeUpdatePending = true
 			if data.autoWidth
 				item.width = innerElem.offsetWidth
 			if data.autoHeight
 				item.height = innerElem.offsetHeight
-
-			sizeUpdatePending = false
+			data.sizeUpdatePending = false

 			if innerElem.parentNode is hatchery
 				implUtils.prependElement data.elem, innerElem
@@ -107,7 +103,7 @@
 			return

 	onWidthChange = ->
-		if not sizeUpdatePending
+		if not @_impl.sizeUpdatePending
 			{width} = @
 			{innerElemStyle} = @_impl
 			auto = @_impl.autoWidth = width is 0
@@ -118,7 +114,7 @@
 		return

 	onHeightChange = ->
-		if not sizeUpdatePending
+		if not @_impl.sizeUpdatePending
 			{height} = @
 			{innerElemStyle} = @_impl
 			auto = @_impl.autoHeight = height is 0
@@ -163,6 +159,7 @@
 		uid: 0
 		contentUpdatePending: false
 		containsHTML: false
+		sizeUpdatePending: false

 	exports =
 	DATA: DATA


Clean whitespace

Clean whitespace

--- src/lib/dbc/entities/world-map-continent.coffee
+++ src/lib/dbc/entities/world-map-continent.coffee
@@ -3,25 +3,19 @@

 module.exports = Entity(
   id: r.uint32le
-
   mapID: r.uint32le
-
   bounds: new r.Struct(
     left: r.uint32le
     right: r.uint32le
     top: r.uint32le
     bottom: r.uint32le
   )
-
   offsetX: r.floatle
   offsetY: r.floatle
-
   scale: r.floatle
-
   taxiMinX: r.floatle
   taxiMinY: r.floatle
   taxiMaxX: r.floatle
   taxiMaxY: r.floatle
-
   worldMapID: r.uint32le
 )


Trailing and whitespace fixes only. Move along please.

--- src/main/client/sockets/sockets-services.coffee
+++ src/main/client/sockets/sockets-services.coffee
@@ -8,24 +8,24 @@
       protocol = $location.protocol()
       host = $location.host()
       port = $location.port()
-
+
       ## In some rare cases, this might need to be overridden, especially during development
       ## where the Gulp proxying system is borked. That means we need to handle things specially
       ## during the Gulp code. We can pounce on this chance to actually show the exact version
-      ## in use while we are at it.
-
+      ## in use while we are at it.
+
       ## @if NODE_ENV = 'development' *
       ## host = 'localhost'
       ## protocol = 'http'
       ## port = 8080
       ## @endif *
-
-      class SocketEventEmitter
-
+
+      class SocketEventEmitter
+
         constructor: (protocol, host, port) ->
           @events = {}
           @atmosphere = atmosphere
-
+
           @request = {
             url: "http://#{host}:#{port}/events"
             contentType: "application/json"
@@ -34,41 +34,41 @@
             trackMessageLength: true
             reconnectInterval: 5000
           }
-
+
           @request.onOpen = (response) ->
             console.log "Called onOpen", response
-
+
           @request.onReopen = (response) ->
             console.log "Called onReopen", response
-
+
           @request.onReconnect = (response) ->
             console.log "Called onReconnect", response
-
+
           @request.onError = (response) ->
             console.log "Called onError", response
-
+
           @request.onClose = (response) ->
             console.log "Called onClose", response
-
+
           @request.onMessage = (response) =>
             console.log "Called onMessage", response
             decoded = JSON.parse response.responseBody
             if @events[decoded.type]
               listener decoded for listener in @events[decoded.type]
-
+
           @socket = @atmosphere.subscribe @request
-
+
         addListener: (event, listener) ->
           if @events['newListener']
             l listener for l in @events['newListener']
           (@events[event]?=[]).push listener
           return @
-
+
         removeListener: (event, listener) ->
           return @ unless @events[event]
           @events[event] = (l for l in @events[event] when l isnt listener)
           return @
-
+
         removeAllListeners: (event) ->
           delete @events[event]
           return @
@@ -77,24 +77,24 @@
         emit: (evt, data) =>
           console.log "Called emit", evt, data
           @socket.push JSON.stringify { type: evt, data: data }
-
+
         on: (evt, handler) =>
           @addListener(evt, handler)
-
+
         once: (event, listener) ->
           fn = =>
             @removeListener event, fn
             listener arguments...
           @on event, fn
           return @
-
+
         disconnect: () ->
           console.log "Disconnect requested"
           @atmosphere.unsubscribeUrl @request.url
-
+
       socket = new SocketEventEmitter(protocol, host, port)
-
+
       socket.on 'newListener', (data...) ->
         console.log "Added listener", data
-
+
       socket


Add propType checking. Closes #2

Add propType checking. Closes #2

--- cjsx/index.cjsx
+++ cjsx/index.cjsx
@@ -1,6 +1,8 @@
 React = require 'react'

 module.exports = React.createClass
+  propTypes:
+    spinnerName: React.PropTypes.string.isRequired

   render: ->
     switch @props.spinnerName


Unnecessary build error messages removed

--- cli/init.coffee
+++ cli/init.coffee
@@ -123,10 +123,7 @@
 else if platforms.length > 0
     options.isRunning = args.run.length > 0
     require('./tasks/build') options, (err) ->
-        if err
-            return log.error err?.stack or err
-
-        if args.run.length > 0
+        if args.run.length > 0 and not err
             for platform in platforms
                 require('./tasks/run') platform, options
         else if not options.watch


improved readability of deviceImageUrl function

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -278,23 +278,23 @@
 		# If the image is externally hosted, we'd like to use that
 		if _.startsWith(name, "http://") or _.startsWith(name, "https://")
 			return name
-		else if @_deviceType not in BuiltInDevices
-			# If this device is added by the user we use the name as it is
+
+		# If this device is added by the user we use the name as it is
+		if @_deviceType not in BuiltInDevices
 			return name
-		else
-			# If we're running Framer Studio and have local files, we'd like to use those
-			if Utils.isFramerStudio() and window.FramerStudioInfo
-				resourceUrl = window.FramerStudioInfo.deviceImagesUrl
-
-			# If not, we want to get these image from our public resources server
-			else
-				resourceUrl = "//resources.framerjs.com/static/DeviceResources"
-
-			# We'd like to use jp2 if possible, or check if we don't for this specific device
-			if Utils.isJP2Supported() and @_device.deviceImageJP2 is true
-				return "#{resourceUrl}/#{name.replace(".png", ".jp2")}"
-			else
-				return "#{resourceUrl}/#{name}"
+
+		# We want to get these image from our public resources server
+		resourceUrl = "//resources.framerjs.com/static/DeviceResources"
+
+		# If we're running Framer Studio and have local files, we'd like to use those
+		if Utils.isFramerStudio() and window.FramerStudioInfo
+			resourceUrl = window.FramerStudioInfo.deviceImagesUrl
+
+		# We'd like to use jp2 if possible, or check if we don't for this specific device
+		if Utils.isJP2Supported() and @_device.deviceImageJP2 is true
+			return "#{resourceUrl}/#{name.replace(".png", ".jp2")}"
+
+		return "#{resourceUrl}/#{name}"

 	###########################################################################
 	# DEVICE ZOOM


extra padding for hand

extra padding for hand

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -366,6 +366,8 @@

 		paddingOffset = @_device?.paddingOffset or 0

+		paddingOffset = 36 if @selectedHand && @_orientation == 0
+
 		phoneScale = _.min([
 			(window.innerWidth  - ((@padding + paddingOffset) * 2)) / width,
 			(window.innerHeight - ((@padding + paddingOffset) * 2)) / height
@@ -598,7 +600,9 @@
 				nextHandIndex = hands.indexOf(@selectedHand) + 1
 				nextHand = ""
 				nextHand = hands[nextHandIndex] if nextHandIndex < hands.length
-				return @setHand(nextHand)
+				hand = @setHand(nextHand)
+				@_update()
+				return hand
 		return false

 	setHand: (hand) ->


Stringify toInspect

Stringify toInspect

--- src/mixins/base_class.coffee
+++ src/mixins/base_class.coffee
@@ -30,7 +30,7 @@
     description = [
       @constructor.className
       @toID()
-      @toInspect()
+      JSON.stringify(@toInspect())
     ]

     "<#{_.compact(description).join ' '}>"


Update# fix navigation scroll top

Update# fix navigation scroll top

--- coffeescripts/main.coffee
+++ coffeescripts/main.coffee
@@ -50,7 +50,7 @@

 $menu.on "click", "a", (e) ->
   targetScrollTop = $sections[e.target.hash].offset().top
-  $dom.scrollTo(targetScrollTop - 60, 1000)
+  $dom.scrollTo(targetScrollTop - 58, 1000)
   e.preventDefault()

 ###


Add test for setting source using deprecated API

--- spec/ComponentLoader.coffee
+++ spec/ComponentLoader.coffee
@@ -437,6 +437,37 @@
             chai.expect(err).to.be.an 'error'
             chai.expect(inst).to.be.an 'undefined'
             done()
+      describe 'with deprecated process callback', ->
+        nonWorkingSource = """
+        var noflo = require('noflo');
+        exports.getComponent = function() {
+          var c = new noflo.Component();
+
+          c.inPorts.add('in', {
+            process: function(packet, outPorts) {
+              if (packet.event !== 'data') {
+                return;
+              }
+              // Do something with the packet, then
+              c.outPorts.out.send(packet.data);
+            }
+          });
+
+          c.outPorts.add('out');
+
+          return c;
+        };"""
+
+        it 'should be able to set the source', (done) ->
+          unless noflo.isBrowser()
+            nonWorkingSource = nonWorkingSource.replace "'noflo'", "'../src/lib/NoFlo'"
+          l.setSource 'foo', 'NotWorkingProcess', nonWorkingSource, 'js', done
+        it 'should not be a loadable component', (done) ->
+          l.load 'foo/NotWorkingProcess', (err, inst) ->
+            chai.expect(err).to.be.an 'error'
+            chai.expect(err.message).to.contain 'process callback is deprecated'
+            chai.expect(inst).to.be.an 'undefined'
+            done()

 describe 'ComponentLoader with a fixture project', ->
   l = null


added tests for #fns

added tests for #fns

--- spec/lib/router-spec.coffee
+++ spec/lib/router-spec.coffee
@@ -201,8 +201,22 @@
       When -> @path = @router.getPath ['event']
       Then -> expect(@path).toEqual [@a, @b, @c]

-   describe '#index', ->
-
-     When -> expect(@router.index()).toBe 0
-     When -> expect(@router.index()).toBe 1
-     When -> expect(@router.index()).toBe 2
+    describe '#index', ->
+
+      When -> expect(@router.index()).toBe 0
+      When -> expect(@router.index()).toBe 1
+      When -> expect(@router.index()).toBe 2
+
+    describe '#fns', ->
+
+      Given -> console.log @router
+      When -> @res = @router.fns()
+      Then -> expect(@res).toEqual {}
+
+    describe '#fns (name:String="test")', ->
+
+      Given -> @name = 'test'
+      When -> @res = @router.fns @name
+      Then -> expect(@res).toEqual []
+
+


added lastAngle property

added lastAngle property

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -96,6 +96,7 @@

     @axes = ['left', 'right', 'top', 'bottom']
     @lastAnchor = @axes[0]
+    @lastAngle = 0
     @panels = {}
     @stages = {}
     stage = document.createElement 'div'


after code load but before core or host ready, start fetching facts

--- js-library/app/js/jail_iframe/util/highlight.coffee
+++ js-library/app/js/jail_iframe/util/highlight.coffee
@@ -84,7 +84,7 @@
       console.error "Could not highlight, empty factlink or complete overlap? Text: <#{text}>"

 highlightFacts = (facts_data) ->
-  FactlinkJailRoot.perf.add_timing_event 'fetchFacts:done'
+

   # If there are multiple matches on the page, loop through them all
   for fact_data in facts_data
@@ -99,15 +99,18 @@
 # Returns deferred object
 fetchFacts = (siteUrl) ->
   FactlinkJailRoot.perf.add_timing_event 'fetchFacts:start'
-
   $.ajax
-    # The URL to the FactlinkJailRoot backend
+  # The URL to the FactlinkJailRoot backend
     url: FactlinkConfig.api + "/site?url=" + encodeURIComponent(siteUrl)
     dataType: "jsonp"
     crossDomain: true
     type: "GET"
     jsonp: "callback"
-    success: highlightFacts
+    success: -> FactlinkJailRoot.perf.add_timing_event 'fetchFacts:done'
+
+facts_promise = null
+
+FactlinkJailRoot.initializers.push( -> facts_promise = fetchFacts FactlinkJailRoot.siteUrl())

 highlighting = false
 FactlinkJailRoot.startHighlighting = ->
@@ -116,10 +119,10 @@
   FactlinkJailRoot.initializeFactlinkButton()

   console.info "FactlinkJailRoot:", "startHighlighting"
-  fetchFacts FactlinkJailRoot.siteUrl()
+  facts_promise.done(highlightFacts)

 # Don't check for highlighting here, as this is a
 # special hacky-patchy method for in the blog
 FactlinkJailRoot.highlightAdditionalFactlinks = (siteUrl) ->
   console.info "FactlinkJailRoot:", "highlightAdditionalFactlinks"
-  fetchFacts siteUrl
+  fetchFacts(siteUrl).done(highlightFacts)


New jobs should come in at the top of the dashboard.

When processing a large queue, this gives a better idea of progress.

--- dashboard/assets/javascripts/models.js.coffee
+++ dashboard/assets/javascripts/models.js.coffee
@@ -61,7 +61,7 @@
     job = Dashboard.Job.create autoScroll: true, messageProcessor: this

     @get('jobIndex')[ident] = job
-    @get('jobs').pushObject job
+    @get('jobs').unshiftObject job

     job


debug mail resizing

debug mail resizing

--- client/app/views/mail.coffee
+++ client/app/views/mail.coffee
@@ -45,10 +45,11 @@
                 @iframehtml.find('head').append csslink
                 @iframehtml.find('head').append basetarget

-                #@iframe.height $(window).height() - 220 - $('.mail-panel h3').height()
-
+                @resize()
             , 100

+            $(window).unbind 'resize', @resize
+            $(window).bind 'resize', @resize
             # this timeout is a walkaround for content, which takes a while to load
             #@timeout2 = setTimeout () =>
                 #@timeout2 = null
@@ -60,7 +61,22 @@
             @attachmentsView?.render().$el.appendTo @$el

-        @$el.niceScroll()
+    resize: =>
+        panelHeight = $('.mail-panel').height()
+        titleHeight = $('.mail-panel h3').height()
+        infoHeight =  $('.mail-panel p:first').height()
+        buttonBarHeight = $('.mail-panel .clearfix').height()
+
+        if panelHeight < @iframehtml.height()
+            $('.mail-panel').height @iframehtml.height() + titleHeight + infoHeight + buttonBarHeight + 100
+            panelHeight = $('.mail-panel').height()
+
+        if panelHeight > $(window).height()
+            $('.mail-panel').height $(window).height()
+            panelHeight = $('.mail-panel').height()
+
+        @iframe.height panelHeight - titleHeight - infoHeight - buttonBarHeight - 50
+

     remove: =>
         @$el.getNiceScroll().remove()


change spinner to icon at the end of loading

--- client/app/views/main.coffee
+++ client/app/views/main.coffee
@@ -227,8 +227,6 @@

         frame = @$("##{slug}-frame")
         onLoad = =>
-            @$("#app-btn-#{slug} .spinner").hide()
-            @$("#app-btn-#{slug} .icon").show()
             @frames.show()
             @content.hide()
             @backButton.show()
@@ -243,6 +241,9 @@
             window.document.title = "Cozy - #{name}"
             $("#current-application").html name
             @resetLayoutSizes()
+
+            @$("#app-btn-#{slug} .spinner").hide()
+            @$("#app-btn-#{slug} .icon").show()

         if frame.length is 0


collaboration: handle session ended state for participant

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -904,12 +904,12 @@
         quit       :
           style    : 'solid light-gray medium'
           title    : 'LEAVE'
-          callback : => @modal.destroy()
-
+          callback : =>
+            @modal.destroy()
+
+    @chat.end()
     @showModal options
-    @removeMachineNode()
-    @removeParticipant nick()
-    @quit()
+    @handleCollaborationEndedForParticipant()

   handleParticipantLeaveAction: ->


Bind participant action events to active state

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -301,9 +301,11 @@
   bindRealtimeEvents: ->

     @rtm.on 'CollaboratorJoined', (doc, participant) =>
+      return  unless @stateMachine.state is 'Active'
       @handleParticipantAction 'join', participant

     @rtm.on 'CollaboratorLeft', (doc, participant) =>
+      return  unless @stateMachine.state is 'Active'
       @handleParticipantAction 'left', participant

     @rtm.on 'ValuesAddedToList', (list, event) =>


collaboration: controller move participant end to its own method

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -689,11 +689,28 @@
     else
       @endCollaborationForParticipant
         success: =>
-          # TODO: move this into its own method.
-          @removeParticipant nick()
-          @removeMachineNode()
-          sharedSuccessFn()
-          kd.utils.defer @bound 'quit'
+          @modal?.destroy()
+          @handleCollaborationEndedForParticipant()
+
+
+  handleCollaborationEndedForParticipant: ->
+
+    # TODO: fix explicit state checks.
+    return  unless @stateMachine.state in ['Active', 'Ending']
+
+    # TODO: fix implicit emit.
+    @rtm.once 'RealtimeManagerWillDispose', =>
+      @chat.emit 'CollaborationEnded'
+      @chat.destroy()
+      @chat = null
+      @statusBar.emit 'CollaborationEnded'
+      @removeParticipant nick()
+      @removeMachineNode()
+
+    @rtm.once 'RealtimeManagerDidDispose', =>
+      kd.utils.defer @bound 'quit'
+
+    @cleanupCollaboration()

   endCollaborationForHost: (callbacks) ->


Notify query when load starts

Notify query when load starts

--- client/lanes/models/query/SyncedResult.coffee
+++ client/lanes/models/query/SyncedResult.coffee
@@ -14,7 +14,7 @@
         }

         _.extend(options, _.omit(@result.query.syncOptions, 'include'))
-
+        @result.query.trigger('request', @result.query, 'GET', {})
         @pendingLoad = Lanes.Models.Sync.perform('GET', options).then (resp) =>
             @result.total = resp.total
             @rows  = resp.data


BUG Add missing require()

BUG Add missing require()

--- examples/express/app.coffee
+++ examples/express/app.coffee
@@ -1,4 +1,5 @@
 app = require('express').createServer()
+sys = require('sys')

 app.register '.coffee', require('coffeekup')
 app.set 'view engine', 'coffee'
@@ -15,4 +16,4 @@

 app.listen 3000

-puts "Listening on 3000..."
+sys.puts "Listening on 3000..."


Fix Promise resolution

Fix Promise resolution

--- lib/Task/ActivityTask/Save/_3DCartSaveOrders.coffee
+++ lib/Task/ActivityTask/Save/_3DCartSaveOrders.coffee
@@ -11,6 +11,6 @@
     if externalObject.InvoiceNumber and externalObject.OrderItemList.length
       super
     else
-      false
+      Promise.resolve(false)

 module.exports = _3DCartSaveOrders


add videoLoader to utils

add videoLoader to utils

--- app/assets/javascripts/uploadcare/utils.coffee
+++ app/assets/javascripts/uploadcare/utils.coffee
@@ -22,6 +22,15 @@
       .on('load', def.resolve)
       .on('error', def.reject)
       .attr('src', src)
+    def.promise()
+
+  ns.videoLoader = (src) ->
+    def = $.Deferred()
+    $('<video/>')
+    .on('loadeddata', def.resolve)
+    .on('error', def.reject)
+    .attr('src', src)
+    .load()
     def.promise()

   ns.defer = (fn) ->


chore(utils): moves utils to angoolar package.

--- coffee/000_angoolar_resource_util.tail.coffee
+++ coffee/000_angoolar_resource_util.tail.coffee
@@ -1,15 +1,3 @@
-# This takes a name like 'SomethingCrazy' and turns it into 'something_crazy'
-angoolar.camelToUnderscores = ( someText ) ->
-	someText?.
-		replace( /([a-z])([A-Z])/g, ( match, lowerPart, upperPart ) -> lowerPart + '_' + upperPart.toLowerCase() ).
-		toLowerCase()
-
-# This takes a name like 'SomethingCrazy' and turns it into 'something-crazy'
-angoolar.camelToDashes = ( someText ) ->
-	someText?.
-		replace( /([a-z])([A-Z])/g, ( match, lowerPart, upperPart ) -> lowerPart + '-' + upperPart.toLowerCase() ).
-		toLowerCase()
-
 angoolar.escapeColons = ( text ) ->
 	text?.
 		replace( /:/g, '\\:' )


AjaxForm now sends files using iframe, resolves #114

--- wzk/ui/form/AjaxForm.coffee
+++ wzk/ui/form/AjaxForm.coffee
@@ -3,6 +3,7 @@
 goog.require 'wzk.ui.form.BackgroundForm'
 goog.require 'goog.dom.forms'
 goog.require 'goog.events.Event'
+goog.require 'goog.net.IframeIo'

 ###*
   Sending an existing form via AJAX.
@@ -35,7 +36,44 @@
     super form
     data = goog.dom.forms.getFormDataString form

-    @client.postForm @url, data, @onSuccess, @onError
+    if @formContainsFile(form)
+      @uploadFiles(form)
+    else
+      @client.postForm @url, data, @onSuccess, @onError
+
+  ###*
+    Upload files in iframe
+    @protected
+    @param {Element} form
+  ###
+  uploadFiles: (form) ->
+    iframeIO = new goog.net.IframeIo()
+    iframeIO.sendFromForm(form)
+    iframeIO.listen goog.net.EventType.SUCCESS, @uploadSuccess
+    iframeIO.listen goog.net.EventType.ERROR, @uploadError
+
+  ###*
+    @protected
+    @param {goog.events.Event} event
+  ###
+  uploadSuccess: (event) ->
+    @onSuccess event.target.getResponseJson()
+
+  ###*
+    @protected
+    @param {goog.events.Event} event
+  ###
+  uploadError: (event) ->
+    @onError event.target.getResponseHtml()
+
+  ###*
+    @param {Element} form
+  ###
+  formContainsFile: (form) ->
+    for fileInput in form.querySelectorAll('input[type=file]')
+      if !!goog.dom.forms.getValue fileInput
+        return true
+    return false

   ###*
     @suppress {checkTypes}


Eliminated currentChannel from ChannelList and fixed a memory leak too.

--- core/app/backbone/collections/channels.coffee
+++ core/app/backbone/collections/channels.coffee
@@ -43,19 +43,10 @@

     clearTimeout @_currentTimeout if @_currentTimeout?
     delete @_currentTimeout
-    callMyselfSoon = =>
-      @_currentTimeout = setTimeout _.bind(args.callee, this), (10*60*1000)-1
-

     @fetch
-      success: (collection, response) =>
-        if typeof window.currentChannel isnt "undefined"
-          newCurrentChannel = collection.get(currentChannel.id)
-          if newCurrentChannel?
-            currentChannel.set newCurrentChannel.attributes
-        callMyselfSoon()
-
-      error: callMyselfSoon()
+      complete: =>
+        @_currentTimeout = setTimeout _.bind(args.callee, this), (10*60*1000)-1

   getBySlugTitle: (slug_title)->
     results = @filter (ch)-> ch.get('slug_title') == slug_title


add compatibility patch

add compatibility patch

--- app.coffee
+++ app.coffee
@@ -9,6 +9,15 @@
 global.EXECROOT = path.join(process.execPath, '..')
 global.APPDATA_PATH = path.join(app.getPath('appData'), 'poi')
 global.EXROOT = global.APPDATA_PATH
+
+# TODO: Remove in the next release
+if process.platform == 'win32'
+  try
+    fs.copySync path.join(global.EXECROOT, 'config.cson'),
+      path.join(global.EXROOT, 'config.cson'),
+      clobber: false
+  catch
+    # expected EEXIST

 if process.env.DEBUG?
   global.SERVER_HOSTNAME = '127.0.0.1:17027'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -78,7 +78,7 @@

 4.circularPoints (p) ->

-# p gets Point(1, 0), Point(0)
+# p gets Point(1, 0), Point(0, 1), Point()

 @name circularPoint


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -75,6 +75,7 @@

 ###*
 @name circularPoint
+@method
 ###
 Number::circularPoints = (block) ->
   n = this


Correctly pass event args to minicart

Correctly pass event args to minicart

--- src/coffee/portal-minicart.coffee
+++ src/coffee/portal-minicart.coffee
@@ -51,10 +51,11 @@
 				$(".vtexsc-cart").stop(true, true).slideUp()
 			, 800

-		$(window).on "cartUpdated", @updateCart
-		$(window).on 'productAddedToCart', @updateCart
-		$(window).on 'vtex.cart.productAdded', @updateCart
-		$(window).on 'vtex.cart.productRemoved', @updateCart
+		$(window).on "cartUpdated", (evt, args...) => @updateCart(args...)
+		$(window).on 'productAddedToCart', (evt, args...) => @updateCart(args...)
+		$(window).on 'vtex.cart.productAdded', (evt, args...) => @updateCart(args...)
+		$(window).on 'vtex.cart.productRemoved', (evt, args...) => @updateCart(args...)
+#		$(window).on 'vtex.checkout.orderform.update', (evt, args...) => @handleOrderForm(args...)

 	updateCart: (slide = true) =>
 		@element.addClass 'amount-items-in-cart-loading'
@@ -71,7 +72,12 @@
 			@element.removeClass 'amount-items-in-cart-loading'
 			@element.trigger 'vtex.minicart.updated'
 		.success (data) =>
-			@cartData = data
+			@handleOrderForm(data, slide)
+
+	handleOrderForm: (orderForm, slide = true) =>
+		console.log 'handling'
+		if orderForm.items?
+			@cartData = orderForm
 			@prepareCart()
 			@render()
 			@slide() if slide


fix not a number error

fix not a number error

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -40,10 +40,11 @@
   randomLetter = () ->
     if @reveal == @current
       return
-    n = Math.floor(Math.random * @current.length)
-    if @reveal[n] == "_"
-      @reveal[n] = @current[n]
-      return
+    loop do
+      n = Math.floor(Math.random() * @current.length)
+      if @reveal[n] == "_"
+        @reveal[n] = @current[n]
+        return

   update_keyHandler = () ->
     $("#input_answer").off()


changed default route from ./upload to upload

--- modules/gui/platforms/node/lib/UploadContext.coffee
+++ modules/gui/platforms/node/lib/UploadContext.coffee
@@ -6,7 +6,7 @@
         configure: (config)->
             super new floyd.Config
                 data:
-                    route:'./upload'
+                    route:'upload'

                     accept: '.*'


watch configuration appears to now require arrays for task definition (this is generally becoming a theme throughout grunt, which is trying to  add support for spaces in task args)

--- config/application.coffee
+++ config/application.coffee
@@ -164,44 +164,44 @@
   watch:
     js:
       files: ["<%= files.glob.js.vendor %>", "<%= files.glob.js.app %>"]
-      tasks: "configure concat:js"
+      tasks: ["configure", "concat:js"]

     coffee:
       files: "<%= files.glob.coffee.app %>"
-      tasks: "configure coffee configure concat:js"
+      tasks: ["configure","coffee","configure","concat:js"]

     jsSpecs:
       files: ["<%= files.glob.js.specHelpers %>", "<%= files.glob.js.spec %>"]
-      tasks: "configure concat:spec"
+      tasks: ["configure","concat:spec"]

     coffeeSpecs:
       files: ["<%= files.glob.coffee.specHelpers %>", "<%= files.glob.coffee.spec %>"]
-      tasks: "configure coffee configure concat:spec"
+      tasks: ["configure","coffee","configure","concat:spec"]

     css:
       files: "<%= files.glob.css.app %>"
-      tasks: "configure concat:css"
+      tasks: ["configure","concat:css"]

     less:
       files: "<%= files.glob.less.app %>"
-      tasks: "configure less configure concat:css"
+      tasks: ["configure","less","configure","concat:css"]

     handlebars:
       files: "<%= files.glob.template.handlebars %>"
-      tasks: "configure handlebars configure concat:js"
+      tasks: ["configure","handlebars","configure","concat:js"]

     underscore:
       files: "<%= files.glob.template.underscore %>"
-      tasks: "configure jst configure concat:js"
+      tasks: ["configure","jst","configure","concat:js"]

     images:
       files: ["<%= files.glob.img.app %>", "<%= files.glob.img.vendor %>"]
-      tasks: "configure images:dev"
+      tasks: ["configure","images:dev"]

     homepage:
       files: "<%= homepage.template %>"
-      tasks: "configure homepage:dev"
+      tasks: ["configure","homepage:dev"]

     lint:
       files: "<%= files.glob.js.app %>"
-      tasks: "configure lint"
+      tasks: ["configure","lint"]


Adding $log dependence

Adding $log dependence

--- src/app/components/directive.coffee
+++ src/app/components/directive.coffee
@@ -1,7 +1,7 @@
 'use strict'

 angular.module("ngFillHeight.directives")
-  .directive 'ngFillHeight', ($parse) ->
+  .directive 'ngFillHeight', ($parse, $log) ->

     ngFillHeightLink = (scope, element, attrs) ->
       ngFillHeightOption = ($parse attrs.ngFillHeight)(scope)


fix race condition in prom logging on client

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -2136,9 +2136,11 @@
         #log         : console.log
         cb          : (err) ->
             #console.log opts.path, 'get_directory_listing.success or timeout', err
-            if prom_client.enabled
+            if prom_client.enabled and prom_dir_listing_start?
                 prom_labels.err = !!err
-                prom_get_dir_listing_h?.observe(prom_labels, (misc.server_time() - prom_dir_listing_start) / 1000)
+                tm = (misc.server_time() - prom_dir_listing_start) / 1000
+                if not isNaN(tm)
+                    prom_get_dir_listing_h?.observe(prom_labels, tm)

             opts.cb(err ? listing_err, listing)
             if time0 and state != 'running' and not err


webapp/first-steps: also check that first-steps tutorial exists!

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -896,16 +896,17 @@
     # this is called once by the project initialization
     check_first_steps: =>
         {NO_FIRST_STEPS_SENTINEL_FILE} = require('./project_files')
+        cmd = "test ! -e #{NO_FIRST_STEPS_SENTINEL_FILE} && test -e #{LIBRARY.first_steps}"
         webapp_client.exec
             project_id      : @project_id
-            command         : 'test'   # /usr/bin/test
-            args            : ['-e', NO_FIRST_STEPS_SENTINEL_FILE]
+            command         : cmd
+            bash            : true
             timeout         : 30
             network_timeout : 120
             err_on_exit     : false
             path            : '.'
             cb              : (err, output) =>
-                if output.exit_code != 0
+                if output.exit_code == 0
                     @setState(show_first_steps:true)
                 else
                     @setState(show_first_steps:false)


Remove unnecessary @repeat

Remove unnecessary @repeat

--- app/scripts/lazerscripts/stage1/stalker.coffee
+++ app/scripts/lazerscripts/stage1/stalker.coffee
@@ -23,12 +23,10 @@
       @moveTo(y: 340, speed: 200)

       # Wobble once before moving up
-      @repeat(1, @sequence(
-        @wait 100
-        @moveTo(y: 350, speed: 100)
-        @wait 100
-        @moveTo(y: 340, speed: 100)
-      ))
+      @wait 100
+      @moveTo(y: 350, speed: 100)
+      @wait 100
+      @moveTo(y: 340, speed: 100)

       @moveTo(y: -50)
     )


Adding comments.

Adding comments.

--- server.coffee
+++ server.coffee
@@ -6,6 +6,10 @@
 MongoInternals.Connection::_observeChanges = (cursorDescription, ordered, callbacks) ->
   initializing = true

+  # If observeChanges is called inside a reactive context we have to make extra effort to pass the computation to the
+  # observeChanges callbacks so that the computation is available to the "added" publish method, if it is called. We use
+  # fiber object for that. observeChanges callbacks are not called in a reactive context. Additionally, we want this to
+  # be passed only during the observeChanges initialization (when it is calling "added" callbacks in a blocking manner).
   if Tracker.active
     Meteor._nodeCodeMustBeInFiber()
     currentComputation = Tracker.currentComputation
@@ -35,31 +39,39 @@
     documents = {}

     publish._setAfterFlush = (computation) ->
-      unless computation._publishAfterFlushSet
-        computation._publishAfterFlushSet = true
+      return if computation._publishAfterFlushSet
+      computation._publishAfterFlushSet = true

-        computation._trackerInstance.afterFlushCallbacks.push =>
-          # We remove those which are not published anymore.
-          for collectionName of @_documents
-            currentlyPublishedDocumentIds = _.keys(@_documents[collectionName] or {})
-            currentComputationAddedDocumentIds = _.keys(documents[computation._id]?[collectionName] or {})
-            # If afterFlush for other autoruns in the publish function have not yet run, we have to look in "documents" as well.
-            otherComputationsAddedDocumentsIds = _.union (_.keys(docs[collectionName] or {}) for computationId, docs of documents when computationId isnt "#{computation._id}")...
-            # But after afterFlush, "documents" is empty to be ready for next rerun of the computation, so we look into "oldDocuments".
-            otherComputationsPreviouslyAddedDocumentsIds = _.union (_.keys(docs[collectionName] or {}) for computationId, docs of oldDocuments when computationId isnt "#{computation._id}")...
+      # We cannot really use Tracker.afterFlush here because we have to make sure afterFlush callback is called in
+      # the same fiber as current computation is being run. Otherwise if the current computation calls observeChanges,
+      # observeChanges callbacks are called in a different fiber, so when those callbacks call "added" publish method,
+      # it is as well called in a different fiber, and when that method calls "_setAfterFlush" method, Tracker.afterFlush
+      # would be called in a different fiber as well, which is not the same as the computation which is being run is in.
+      # This makes afterFlush be called every time after every "added" method call, instead after the whole current
+      # computation (because those are two different fibers) which breaks the behavior of the afterFlush callback which
+      # expects to be run after the whole computation finishes.
+      computation._trackerInstance.afterFlushCallbacks.push =>
+        # We remove those which are not published anymore.
+        for collectionName of @_documents
+          currentlyPublishedDocumentIds = _.keys(@_documents[collectionName] or {})
+          currentComputationAddedDocumentIds = _.keys(documents[computation._id]?[collectionName] or {})
+          # If afterFlush for other autoruns in the publish function have not yet run, we have to look in "documents" as well.
+          otherComputationsAddedDocumentsIds = _.union (_.keys(docs[collectionName] or {}) for computationId, docs of documents when computationId isnt "#{computation._id}")...
+          # But after afterFlush, "documents" is empty to be ready for next rerun of the computation, so we look into "oldDocuments".
+          otherComputationsPreviouslyAddedDocumentsIds = _.union (_.keys(docs[collectionName] or {}) for computationId, docs of oldDocuments when computationId isnt "#{computation._id}")...

-            # We ignore IDs found in both otherComputationsAddedDocumentsIds and otherComputationsPreviouslyAddedDocumentsIds
-            # which might ignore more IDs then necessary (an ID might be previously added which has not been added in this
-            # iteration) but this is OK because in afterFlush of other computations this will be corrected and documents
-            # with those IDs removed.
-            for id in _.difference currentlyPublishedDocumentIds, currentComputationAddedDocumentIds, otherComputationsAddedDocumentsIds, otherComputationsPreviouslyAddedDocumentsIds
-              @removed collectionName, id
+          # We ignore IDs found in both otherComputationsAddedDocumentsIds and otherComputationsPreviouslyAddedDocumentsIds
+          # which might ignore more IDs then necessary (an ID might be previously added which has not been added in this
+          # iteration) but this is OK because in afterFlush of other computations this will be corrected and documents
+          # with those IDs removed.
+          for id in _.difference currentlyPublishedDocumentIds, currentComputationAddedDocumentIds, otherComputationsAddedDocumentsIds, otherComputationsPreviouslyAddedDocumentsIds
+            @removed collectionName, id

-          oldDocuments[computation._id] = documents[computation._id] or {}
-          documents[computation._id] = {}
-          computation._publishAfterFlushSet = false
+        oldDocuments[computation._id] = documents[computation._id] or {}
+        documents[computation._id] = {}
+        computation._publishAfterFlushSet = false

-        computation._trackerInstance.requireFlush()
+      computation._trackerInstance.requireFlush()

     originalAdded = publish.added
     publish.added = (collectionName, id, fields) ->
@@ -68,11 +80,17 @@
       if Tracker.active
         currentComputation = Tracker.currentComputation
       else
+        # Computation can also be passed through current fiber in the case this "added" method is called
+        # from the observeChanges callback from an observeChanges called inside a reactive context.
         currentComputation = Fiber.current._publishComputation
       if currentComputation
         @_setAfterFlush currentComputation
         Meteor._ensure(documents, currentComputation._id, collectionName)[stringId] = true
+
       # If document as already present in publish then we call changed to send updated fields (Meteor sends only a diff).
+      # This can hide some errors in publish functions if they one calls "added" on an existing document and we could
+      # make it so that this behavior works only inside reactive computation (if "currentComputation" is set), but we
+      # can also make it so that publish function tries to do something smarter (sending a diff) in all cases, as we do.
       if @_documents[collectionName]?[stringId]
         oldFields = {}
         # If some field existed before, but does not exist anymore, we have to remove it by calling "changed"
@@ -96,13 +114,16 @@
       if currentComputation
         @_setAfterFlush currentComputation

-      # Mark it as ready only the first time
+      # Mark it as ready only the first time.
       originalReady.call @ unless ready
       ready = true
+
       # To return nothing.
       return

     handles = []
+    # This autorun is nothing special, just that it makes sure handles are stopped when publish stops,
+    # and that you can return cursors from the function which would be automatically published.
     publish.autorun = (runFunc) ->
       handle = Tracker.autorun (computation) ->
         result = runFunc computation


Create memory history when initializing store in feed handler.

--- server.coffee
+++ server.coffee
@@ -5,6 +5,7 @@
 favicon = require 'serve-favicon'
 bodyParser = require 'body-parser'
 _ = require 'lodash'
+{ createMemoryHistory } = require 'react-router'

 PROD = process.env.NODE_ENV == 'production'
 DEV = !PROD
@@ -45,7 +46,7 @@
 server.get '/index.atom', (req, res) ->
   lang = negotiateLang req
   res.header 'Content-Type', 'application/atom+xml; charset=utf8'
-  { store } = createStore()
+  { store } = createStore createMemoryHistory()
   init(store, {}, lang).then ->
     articles = articleSelectors.containerSelector(store.getState()).articles
     articles.forEach (article) ->


Style.

Style.

--- server.coffee
+++ server.coffee
@@ -1,5 +1,5 @@
 # External code can't access this, and so won't be able to directly construct a Tracker.Computation instance
-privateObj = {}
+privateObject = {}
 nextId = 1
 afterFlushCallbacks = []
 queue = new Meteor._SynchronousQueue()
@@ -24,7 +24,7 @@
   autorun: (f) ->
     throw new Error 'Tracker.autorun requires a function argument' unless typeof f is 'function'

-    c = new Tracker.Computation(f, Tracker.currentComputation, privateObj)
+    c = new Tracker.Computation(f, Tracker.currentComputation, privateObject)

     if Tracker.active
       Tracker.onInvalidate ->
@@ -51,7 +51,7 @@
     Tracker.currentComputation.onInvalidate(f)

   afterFlush: (f) ->
-    afterFlushCallbacks.push(f)
+    afterFlushCallbacks.push f

 # Compatibility with the client-side Tracker. On node.js we can use defineProperties to define getters.
 Object.defineProperties Tracker,
@@ -64,9 +64,8 @@
       !!Tracker._currentComputation.get()

 class Tracker.Computation
-  constructor: (f, @_parent, p)->
-    if p != privateObj
-      throw new Error("Tracker.Computation constructor is private; use Tracker.autorun")
+  constructor: (f, @_parent, private) ->
+    throw new Error "Tracker.Computation constructor is private; use Tracker.autorun" if private isnt privateObject

     @stopped = false
     @invalidated = false
@@ -76,20 +75,18 @@
     @_recomputing = false

     Tracker._currentComputation.withValue @, =>
-      @_func = Meteor.bindEnvironment(f, null, @)
+      @_func = Meteor.bindEnvironment f, null, @

     errored = true
     try
-      @._compute()
+      @_compute()
       errored = false
     finally
       @firstRun = false
-      if errored
-        @stop()
+      @stop() if errored

   onInvalidate: (f) ->
-    if typeof f != "function"
-      throw new Error("onInvalidate requires a function")
+    throw new Error "onInvalidate requires a function" unless typeof f is 'function'

     f = Tracker._makeNonreactive(Meteor.bindEnvironment(f, null, @))

@@ -102,7 +99,7 @@
     if not @invalidated
       if not @_recomputing and not @stopped
         queue.queueTask =>
-          @._recompute()
+          @_recompute()
           Tracker._postRun()

       @invalidated = true


Added CORS support

Added CORS support

--- server.coffee
+++ server.coffee
@@ -36,9 +36,20 @@
 app.use(express.bodyParser())
 app.use(express.methodOverride())
 app.use(app.router)
+
+app.use (req, res, next) ->
+  res.header 'Access-Control-Allow-Origin', '*'
+  res.header 'Access-Control-Allow-Credentials', true
+  res.header 'Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS'
+  res.header 'Access-Control-Allow-Headers', 'Content-Type'
+  next()
+
 app.use(express.static('./public'))
 app.use (req, res) ->
   res.sendfile("./public/index.html")
+
+app.options '/*', (req, res) ->
+  res.end()

 app.get '/off/:name/:from', (req, res) ->
   message = "Fuck off, #{req.params.name}."


Transfer props to surrounding div

Transfer props to surrounding div

--- cjsx/index.cjsx
+++ cjsx/index.cjsx
@@ -7,9 +7,11 @@
       when "three-bounce"
         require '../css/three-bounce.css'
         return (
-

-

-

-

-

+          @transferPropsTo(
+

+

+

+

+

+          )
         )


Persist player options across streaminfo ajax call

--- app/assets/javascripts/avalon_player.js.coffee
+++ app/assets/javascripts/avalon_player.js.coffee
@@ -209,6 +209,9 @@
           uri = "#{splitUrl[0]}/stream"
           params = ["content=#{segment}"]
           params.push(splitUrl[1]) if splitUrl[1]?
-          $.getJSON uri, params.join('&'), (data) => @setStreamInfo(data)
+          autostart = @player.options.autostart
+          $.getJSON uri, params.join('&'), (data) =>
+            @player.options.autostart = autostart
+            @setStreamInfo(data)

 (exports ? this).AvalonPlayer = AvalonPlayer


implemented angular object shorthand for expressions (functions) to handle responses from the server

--- src/directives/file_upload.coffee
+++ src/directives/file_upload.coffee
@@ -7,7 +7,12 @@
 # mac-upload-selector:  ????
 # mac-upload-drop-zone: The selector that we can drop files onto

-angular.module("Mac").directive "macUpload", ["$parse", ($parse)->
+angular.module("Mac").directive "macUpload", [ () ->
+    scope: {
+      macUploadSubmit:  "&macUploadSubmit"
+      macUploadError:   "&macUploadError"
+      macUploadSuccess: "&macUploadSuccess"
+    }
     link: (scope, element, attributes) ->
       disableOn = attributes.macUploadDisableOn if attributes.macUploadDisableOn?
       enableOn  = attributes.macUploadEnableOn  if attributes.macUploadEnableOn?
@@ -37,21 +42,16 @@
           submit: (event, response) ->
             runExpression = ->
               if attributes.macUploadSubmit?
-                expression = $parse attributes.macUploadSubmit
-                scope.$apply expression scope, $event: event, $response: response
-
+                scope.$apply scope.macUploadSubmit $event: event, $response: response
               runExpression()

           error: (response, status) ->
-            $rootScope.$broadcast "showError", "Failed to upload file. Please try again."
             if attributes.macUploadError?
-              expression = $parse attributes.macUploadError
-              scope.$apply expression scope, $response: response, $data: response.data, $status: status
+              scope.$apply scope.macUploadError $response: response, $data: response.data, $status: status

           success: (response, status) ->
             if attributes.macUploadSuccess?
-              expression = $parse attributes.macUploadSuccess
-              scope.$apply expression scope, $response: response, $data: response.data, $status: status
+              scope.$apply scope.macUploadSuccess $response: response, $data: response.data, $status: status

         options.dropZone = if attributes.macUploadDropZone? then $(attributes.macUploadDropZone) else null


IDE: Silently close pane if it's read-only.

--- client/ide/lib/views/tabview/ideapplicationtabview.coffee
+++ client/ide/lib/views/tabview/ideapplicationtabview.coffee
@@ -22,13 +22,16 @@

   removePane_: KDTabView::removePane

+
   removePane: (pane, shouldDetach, quiet = no) ->

     return  unless pane

-    { aceView } = pane.getOptions()
+    { aceView }        = pane.getOptions()
+    { isFileReadonly } = pane.view
+    isContentChanged   = aceView.ace.isContentChanged()

-    if quiet or not aceView or not aceView.ace.isContentChanged()
+    if quiet or not aceView or not isContentChanged or isFileReadonly
       return @removePane_ pane, shouldDetach

     @askForSave pane, aceView


only build a deployment if we have Targets

--- src/client/embeds/deployment.coffee
+++ src/client/embeds/deployment.coffee
@@ -94,19 +94,25 @@
     # If we're refreshing, rebuild the target list
     @targets = @getTargets(opts)
     ds = @getDeploymentStrategy()
-    ds.embed(@targets, opts).then ()=>
-      @onEmbed()
-    ,throwErr
-    .catch throwErr
+    if @targets.length
+      ds.embed(@targets, opts).then ()=>
+        @onEmbed()
+      ,throwErr
+      .catch throwErr
+    else
+      Promise.resolve()

   boot: ()=>
-    @getDeploymentStrategy().boot()
+    if @targets.length
+      @getDeploymentStrategy().boot()

   onEmbed : (callback)=>
-    @getDeploymentStrategy().onEmbed(callback)
+    if @targets.length
+      @getDeploymentStrategy().onEmbed(callback)

   destroy: ()=>
-    @getDeploymentStrategy().destroy()
-    @deploymentStrategy = null
+    if @targets.length
+      @getDeploymentStrategy().destroy()
+      @deploymentStrategy = null

 module.exports = Deployment


cleanups

cleanups

--- src/client/embeds/deployment.coffee
+++ src/client/embeds/deployment.coffee
@@ -37,7 +37,6 @@
     # "_width" : "100%", //Dimensions to give the containing element. Passed as-is as style tag
     # "_height" : "50px", //Dimensions to give the containing element. Passed as-is as style tag

-    # @settings._sandbox = false
     @targets = @getTargets()
     @deploymentStrategy = @getDeploymentStrategy()
     @elements  = []
@@ -64,7 +63,7 @@

     DS = if @ship.index.match(/\.js$/)
       JSDeploymentStrategy
-    else if @settings._sandbox
+    else if !!@settings._sandbox
       SandboxedDeploymentStrategy
     else
       RawDeploymentStrategy


A single click on the title of the document should put the title box in editing mode. [#110254212]

--- src/code/views/menu-bar-view.coffee
+++ src/code/views/menu-bar-view.coffee
@@ -16,14 +16,11 @@
   filenameClicked: (e) ->
     e.preventDefault()
     e.stopPropagation()
-    now = (new Date()).getTime()
-    if now - @lastClick <= 250
-      if @props.client.state.metadata?.provider?.can 'rename'
-        @setState editingFilename: true
-        setTimeout (=> @focusFilename()), 10
-      else
-        @props.client.saveFileDialog()
-    @lastClick = now
+    if @props.client.state.metadata?.provider?.can 'rename'
+      @setState editingFilename: true
+      setTimeout (=> @focusFilename()), 10
+    else
+      @props.client.saveFileDialog()

   filenameChanged: ->
     @setState filename: @filename().value


aml search video valid id Fresnoy

aml search video valid id Fresnoy

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -100,17 +100,21 @@
         )

-    search = (artwork.title)+" "+slug(artwork.authors[0].user.first_name[0]+artwork.authors[0].user.last_name).toLowerCase()
-    #search :  Archipel gabbruzzese
+    search = slug(artwork.authors[0].user.first_name[0]+artwork.authors[0].user.last_name).toLowerCase()
+    #search with Author :  gabbruzzese
     AmeRestangular.all("api_search/").get("",{"search": search, "flvfile": "true", "previewsize":"scr"}).then((ame_artwork) ->
       for archive in ame_artwork
-        if archive.flvpath
+        # valid reference id Fresnoy => id AME

-          $scope.ame_artwork_gallery.media.push({
-            picture : archive.flvthumb
-            medium_url : $sce.trustAsResourceUrl(archive.flvpath)
-            description : archive.field8 #media ame title
-         })
+        if parseInt(archive.field201) == $scope.artwork.id
+
+          if archive.flvpath
+
+            $scope.ame_artwork_gallery.media.push({
+              picture : archive.flvthumb
+              medium_url : $sce.trustAsResourceUrl(archive.flvpath)
+              description : archive.field8 #media ame title
+           })

     , (response) ->
       #erreur service


Code tidy and lint

Code tidy and lint

--- picksel.coffee
+++ picksel.coffee
@@ -48,6 +48,10 @@
 ]

+# Redacts the user's API key from the given URL.
+#
+# @param [String] url the URL to redact the API key from
+#
 redactApiKey = (url) -> url.replace user.apiKey, '[NOPE]'

@@ -58,14 +62,12 @@
 # @param [Number] res the resolution code of the image to get
 #
 buildUrl = (apiKey, id, res) ->
-  'https://pixabay.com/api/?key=' \
-    + user.apiKey \
+  "https://pixabay.com/api/?key=#{user.apiKey}" \
     + (if res > 1 then '&response_group=high_resolution' else '') \
-    + '&id=' \
-    + id
-
-
-# Checks whether or not a resolution code is valid.
+    + "&id=#{id}"
+
+
+# Checks whether or not a human-readable resolution name is valid.
 #
 # @param [Number] res the resolution code to check.
 #
@@ -91,7 +93,7 @@
 # @param [Object] error any error returned with the response
 # @param [Object] body the body of the response
 #
-isSuccessful = (error, body) ->
+isSuccessful = (error, body) ->
   !error \
   && body \
   && body.hits \
@@ -106,64 +108,62 @@
 # @param [String] destination the path of the destination file
 #
 download = (id, resolution, destination) ->
+
   # Build URL for API request.
   url = buildUrl(user.apiKey, id, resolution)

-  log.info 'Requesting information for image' \
-    + ' from ' \
-    + redactApiKey url
-
+  log.info "Requesting information for image from '#{redactApiKey(url)}'"
+
+  # Call out to Pixabay for JSON.
   options =
     url: url
     json: true
-
-  # Call out to Pixabay.
   request options, (error, response, body) ->

     # On success.
     if isSuccessful error, body
       url = body.hits[0][resolutions[resolution]]
-      log.info 'Downloading file from \'' \
-        + url \
-        + '\'...'
+
+      log.info "Downloading image file from '#{url}'"
+
+      # Request image from Pixabay.
       tempDestination = destination + '.pickseltemp'
       file = fs.createWriteStream tempDestination
       req = https.get url, (response) ->
+
+        # Write to temporary file.
         stream = response.pipe file
         stream.on 'finish', () ->
-          if existsFile.sync(destination) && md5File.sync(tempDestination) != md5File.sync(destination)
-            log.warning 'Looks like \'' \
-              + destination \
-              + '\' has been modified (MD5 hashes ' \
-              + md5File.sync(tempDestination) \
-              + ', ' \
-              + md5File.sync(destination) \
-              + ') so not gonna overwrite it.'
-            filedel tempDestination
+
+          # Does file currently exist at destination?
+          exists = existsFile.sync(destination)
+
+          # Hash temp file (and existing file if possible).
+          existingHash = if exists then md5File.sync(destination) else ''
+          tempHash = md5File.sync(tempDestination)
+
+          # If file currently exists and hashes don't match, don't overwrite.
+          if exists && existingHash != tempHash
+            log.warning "Looks like '#{destination}' has been modified (MD5" \
+              + " hashes #{tempHash} != #{existingHash}) so not gonna" \
+              + ' overwrite it'
+            filedel tempDestination # Remove temporary file.
           else
+            # Move file to final destination.
             fileMove tempDestination, destination, (err) ->
-              log.info 'Finished installing image with ID \'' \
-                + id \
-                + '\''
+              log.info "Finished installing image with ID '#{id}'"
     else
       # Download failed.
-      log.error 'Couldn\'t get information about image with ID \'' \
-        + id \
-        + '\''
+      log.error "Couldn't get information about image with ID '#{id}'"

 # Installs a single image.
 #
 # @param [Object] image the image to grab
 grab = (image) ->
-  path = './' + config.directory + '/' + image.destination
-  log.info 'Installing image with ID ' \
-    + image.id \
-    + ' at resolution \'' \
-    + humanResolutions[image.resolution] \
-    + '\' to \'' \
-    + path \
-    + '\'.'
+  path = "./#{config.directory}/#{image.destination}"
+  log.info "Installing image with ID #{image.id} at resolution" \
+    + "#{humanResolutions[image.resolution]} to #{path}"
   download image.id, image.resolution, path

@@ -179,14 +179,14 @@
 add = (args) ->
   # Check ID.
   id = args[3]
-  if !isValidId id
-    log.error 'That ID \'' + id + '\' isn\'t valid.'
-    return false
+#  if !isValidId id
+#    log.error "That ID '#{id}' isn't valid."
+#    return false

   # Check resolution.
   res = args[4]
   if !isValidResolution res
-    log.error 'That resolution \'' + res + '\' isn\'t valid.'
+    log.error "That resolution '#{res}' isn't valid."
     return false

   # Add new image to configuration file.
@@ -198,9 +198,7 @@

   persistConfig() # Update configuration file.

-  log.info 'Added image with ID \'' \
-    + image.id \
-    + '\' as asset.'
+  log.info "Added image with ID '#{image.id}' as asset."

   install() # Freshly install all images.

@@ -214,9 +212,8 @@
   images = config.images
   filteredImages = images.filter (obj) -> obj.id != args[3]
   if images.length == filteredImages.length
-    log.warning 'Couldn\'t uninstall image with ID ' \
-      + id \
-      + ' becuse it\'s not installed in the first place.'
+    log.warning "Couldn't uninstall image with ID '#{id}' because it's not" \
+      + ' installed in the first place.'
   else
     config.images = filteredImages
     persistConfig()


Revise test suite to work with node packages

--- graphedit/tests/spec/coffee/graphedit-spec.coffee
+++ graphedit/tests/spec/coffee/graphedit-spec.coffee
@@ -1,4 +1,6 @@
 # Jasmine tests
+
+$ = window.jQuery;

 describe 'GraphEdit', ->

@@ -19,12 +21,12 @@
     ge = new GraphEdit()
     expect(ge).toBeDefined()

-  it 'Gives back the right nodes when added on construction', ->
+  it 'Saves nodes provided on construction', ->
     ge = new GraphEdit(@element, {nodes: test_nodes})
     nodes = ge.getNodes()
     expect(nodes).toEqual test_nodes

-  it 'Gives back the right nodes when added with "addNode"', ->
+  it 'Saves nodes added via "addNode"', ->
     ge = new GraphEdit(@element)

     for node in test_nodes
@@ -33,7 +35,7 @@
     nodes = ge.getNodes()
     expect(nodes).toEqual test_nodes

-  it 'Gives back the right edges when added with "addEdge"', ->
+  it 'Saves edges added via "addEdge"', ->
     ge = new GraphEdit(@element, {nodes: test_nodes})

     for edge in test_edges
@@ -41,42 +43,3 @@

     edges = ge.getEdges()
     expect(edges).toEqual test_edges
-
-  it 'Renders the right number of nodes and edges', ->
-    ge = new GraphEdit(@element)
-    for node in test_nodes
-      ge.addNode(node)
-    for edge in test_edges
-      ge.addEdge(edge)
-
-    expect($(@element).find('.node').length).toBe test_nodes.length
-    expect($(@element).find('.link').length).toBe test_edges.length
-
-  it 'Save new node properties', ->
-    ge = new GraphEdit(@element, {nodes: test_nodes, edges: test_edges})
-    test_nodes[0]
-
-    #click first node
-    svg_elt = $(@element).find('.node').first()
-
-    event = document.createEvent("MouseEvents");
-    event.initMouseEvent("mousedown",true,true)
-    svg_elt.get(0).dispatchEvent(event)
-
-    #add property in form
-
-    $(@element).find('.graphedit-add-property').trigger('click')
-
-    $(@element).find('.graphedit-new-property').find('input').first().val("test_prop")
-    $(@element).find('.graphedit-new-property').find('input').last().val("test_val")
-
-    console.log $(@element).find('.graphedit-save-properties')
-
-    $(@element).find('.graphedit-save-properties').trigger('click')
-    #form = $(@element).find('form.graphedit-property-form')
-    #submitCallback = jasmine.createSpy()
-    #form.submit(submitCallback);
-    #expect(submitCallback).toHaveBeenCalled()
-
-    #check results
-    console.log ge.getNodes()


fix(tasks): Report slow performLocal only when it takes > 500ms

--- src/flux/stores/task-queue.coffee
+++ src/flux/stores/task-queue.coffee
@@ -141,8 +141,8 @@
     task.runLocal()
     .then =>
       runLocalTime = Date.now() - runLocalStart
-      if runLocalTime >= 150
-        err = new Error("Task peformLocal took more than 150ms")
+      if runLocalTime >= 500
+        err = new Error("Task peformLocal took more than 500ms")
         NylasEnv.reportError(err, {task: task.toJSON(), duration: runLocalTime, taskName: task.constructor.name})
       @_queue.push(task)
       @_updateSoon()


disable datepicker for uneditable scheduler dates

test plan:
* enable scheduler
* create an appointment group with a date
* edit the appointment group
* the disabled date fields should not have a datepicker button

closes #CNVS-22816

Change-Id: I962e7d5a0ff0c358e86e21db505e1eea69cdf144
Reviewed-on: https://gerrit.instructure.com/65093
Tested-by: Jenkins
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
Reviewed-by: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
QA-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
Product-Review: James Williams  <3a5f4ed991dbfa5c9ef76ad729d240ca12f62180@instructure.com>

--- app/coffeescripts/calendar/TimeBlockRow.coffee
+++ app/coffeescripts/calendar/TimeBlockRow.coffee
@@ -21,6 +21,9 @@
       @$date.date_field().change(@validate)
       @$start_time.time_field().change(@validate)
       @$end_time.time_field().change(@validate)
+
+      if @locked
+        @$row.find('button').attr('disabled', true)

       @$row.find('.delete-block-link').click(@remove)


proper status rendering

proper status rendering

--- imports/ui/pages/InstanceDetailPage.cjsx
+++ imports/ui/pages/InstanceDetailPage.cjsx
@@ -6,7 +6,7 @@
 {PrismCode}   = require 'react-prism'
 ansi_up       = require('ansi_up')

-{ Anchor, Header, Split, Sidebar, Notification, Section, List, ListItem, Heading, Menu, Button, Icons } = require 'grommet'
+{ Anchor, Box, Header, Split, Sidebar, Notification, Section, List, ListItem, Heading, Menu, Button, Icons } = require 'grommet'

 InstanceControls = require '../menus/InstanceControls.cjsx'
 Loading          = require '../Loading.cjsx'
@@ -45,6 +45,20 @@
     appWithLink = =>
       iconLink "#{@props.instance.app.name}:#{@props.instance.app.version}", @props.onOpenAppPage

+    renderStatus = (s) ->
+      status = if (stat = s.health?.status) is 'unknown' then 'waiting for container to become healthy' else stat
+
+        {s.state} {if status then "(#{status})"}
+
+
+    renderNetwork = (net) ->
+      if net
+        ip = if net.ip? then ", ip: #{net.ip}" else ''
+        status = if (stat = net.health?.status) is 'unknown' then 'waiting for container to become healthy' else stat
+
+          {net.state} ({status}{ip})
+
+
     instanceHelper = Helpers.withInstance @props.instance
     <Split flex='left' priority='left'>
       <DetailPage title={@props.title} >
@@ -54,7 +68,9 @@

           <List>
             {li 'Application', appWithLink()}
-            {li 'Storage bucket', iconLink @props.instance.storageBucket, @props.onOpenBucketPage}
+            {if bucket = @props.instance.storageBucket
+              li 'Storage bucket', iconLink bucket, @props.onOpenBucketPage
+            }
             {li 'Started by', avatarAndName()}
           </List>

@@ -69,9 +85,9 @@

         {unless @props.instance?.services
-

+          <Box alignContent='center' pad='large' align='center'>

Service information will be displayed here as soon as the instance starts...

-

+          </Box>
         }

         {_.map @props.instance.services, (service, name) ->
@@ -79,11 +95,15 @@
             <Heading tag='h2'>{name}</Heading>
             <List>
               {li 'Created', moment(service.container?.created).fromNow()}
-              {li 'State', service.state}
+              {li 'State', renderStatus service}
               {li 'FQDN', service.fqdn}
               {li 'Container name', service.container?.name}
               {li 'Ports', service.ports}
-              {li 'Network', service.aux?.net?.state}
+              {li 'Network', renderNetwork service.aux?.net}
+              {if service.aux.ssh
+                console.log 'ssh', service.aux.ssh
+                li 'SSH', renderStatus service.aux.ssh
+              }
             </List>

         }


Wip on simple macros, leaving for later

Wip on simple macros, leaving for later

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -836,12 +836,7 @@
     # For now expect the curried constructor call
     args = _arguments call
     [paramList, defs...] = args
-    if not paramList or not isTuple paramList
-      malformed call, 'Missing paramater list'
-      params = []
-    else
-      params = _terms paramList
-      map (syntaxNewName 'Parameter name expected'), params
+    paramTuple paramList
     defs ?= []
     if defs.length is 0
       malformed call, 'Missing function result'
@@ -963,6 +958,21 @@
     replicate call,
       (call_ (token_ 'data'), [(token_ ctx._name()), (tuple_ args)])

+  # TODO:
+  # For now support the simplest function macros, just compiling down to source
+  # strings
+  # macro: (ctx, call) ->
+  #   args = _arguments call
+  #   [paramList, body] = args
+  #   paramTuple paramList
+  #   if not body
+  #     malformed call, 'Missing macro definition'
+
+  #   ctx.
+  #   # then in assign compile:
+  #   ctx.macros[ctx._name()]
+
+
   # match
   #   subject
   #   listing of
@@ -1038,6 +1048,15 @@
       }""").join '') + """ else {
         #{elseCase}
       }"""
+
+paramTuple = (call, expression) ->
+  if not expression or not isTuple expression
+    malformed call, 'Missing paramater list'
+    params = []
+  else
+    params = _terms expression
+    map (syntaxNewName 'Parameter name expected'), params
+  params

 # From precs, find caches and the LHS are declarable variables
 findDeclarables = (precs) ->


Fixed type errors and completion

Fixed type errors and completion

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -4379,7 +4379,7 @@

 unify = (ctx, t1, t2) ->
   throw new Error "invalid args to unify" unless ctx instanceof Context and t1 and t2
-  mostGeneralUnifier t1, t2
+  ctx.extendSubstitution mostGeneralUnifier t1, t2

 mostGeneralUnifier = (t1, t2) ->
   if t1.TypeVariable and t2.TypeVariable and t1.name is t2.name
@@ -4837,7 +4837,7 @@
   typeOrConstraint

 mutateMarkingOrigin = (typeOrConstraint, expression) ->
-  # typeOrConstraint.origin = expression
+  typeOrConstraint.origin = expression

 # Clones constrained types and parts of them
 # Class constraint arguments are not cloned
@@ -5541,11 +5541,13 @@
     #   not isFailed mostGeneralUnifier (freshInstance ctx, def.type).type, type.type
     # [typed, notTyped] = partitionMap typesUnify, validDefinitions
     scoreAndPrint = (def) ->
-      sub = mostGeneralUnifier (freshInstance ctx, def.type).type, type.type
+      freshedType = substitute newMap(), type.type
+      checkedType = (freshInstance ctx, def.type).type
+      sub = mostGeneralUnifier checkedType, freshedType
       if isFailed sub
         score = -Infinity
       else
-        score = -((subMagnitude sub) + i * 100)
+        score = -((subMagnitude checkedType, freshedType) + i * 100)
       type: plainPrettyPrint def.type#score + ' ' +
       arity: def.arity
       docs: def.docs
@@ -5559,9 +5561,10 @@
   # allDefs = concatMaps typed, notTyped # TODO: don't use object key ordering for ordering
   # values mapMap (__ plainPrettyPrint, _type), allDefs

-subMagnitude = (sub) ->
+subMagnitude = (candidate, subject) ->
+  subs = join (findSubstitutions candidate), (findSubstitutions subject)
   magnitude = 0
-  for s in sub.vars when s
+  for s in subs
     magnitude +=
       if s.TypeApp
         opName = actualOpName s.op
@@ -5572,6 +5575,14 @@
       else
         2
   magnitude
+
+findSubstitutions = (type) ->
+  if type.TypeVariable and type.ref.val
+    [type.ref.val]
+  else if type.TypeApp
+    join (findSubstitutions type.op), (findSubstitutions type.arg)
+  else
+    []

 actualOpName = (type) ->
   type.name ? actualOpName type.op


Finally req doesnt have to be first

Finally req doesnt have to be first

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -678,6 +678,9 @@

   arity: (name) ->
     (@_declaration name)?.arity
+
+  tempType: (name) ->
+    (@_declaration name)?.tempType

   freshTypeVariable: (kind) ->
     if not kind
@@ -2357,11 +2360,13 @@
     for arg in reqs
       if (isName arg)
         name = arg.symbol
-        if ctx.isFinallyCurrentlyDeclared name
+        if ctx.isFinallyTyped name, ctx.currentScopeIndex()
           malformed ctx, arg, "#{name} already declared"
         else if not ctx.isCurrentlyDeclared arg.symbol
+          # TODO: this will not trigger if we define in current/other module
           malformed ctx, arg, "#{name} was not declared in #{moduleName}"
         else
+          ctx.assignType name, (ctx.tempType name)
           ctx.declareAsFinal name, ctx.currentScopeIndex()
       else
         malformed ctx, arg, 'Name required'
@@ -5846,7 +5851,7 @@
     else
       addToMap topScope.macros, name, macro
   for name, {type, arity, docs, source, isClass, virtual, final} of values definitions when (shouldImport name) and final
-    addToMap topScope, name, {type, arity, docs, source, isClass, virtual, final: shouldDeclare}
+    addToMap topScope, name, {arity, docs, source, isClass, virtual, final, type: (type if shouldDeclare), tempType: type}
   topScope.typeNames = concatMaps topScope.typeNames, typeNames
   topScope.classes = concatMaps topScope.classes, classes
   ctx.scopeIndex += compiledModule.savedScopes.length


Adjust scores

Adjust scores

--- webapp/components/about-me/skills/skills.coffee
+++ webapp/components/about-me/skills/skills.coffee
@@ -37,7 +37,7 @@
 			2017: 90
 			2018: 90
 		domains: ["WebApp", "Server"]
-		scoreWeight: 2
+		scoreWeight: 3
 	"ES2015":
 		type: "Language"
 		experience:
@@ -47,7 +47,7 @@
 			2017: 80
 			2018: 90
 		domains: ["WebApp", "Server"]
-		scoreWeight: 1.3
+		scoreWeight: 2
 	"CoffeeScript":
 		type: "Language"
 		experience:
@@ -58,7 +58,7 @@
 			2016: 95
 			2017: 0
 		domains: ["WebApp", "Server"]
-		scoreWeight: 3
+		scoreWeight: 1
 	"Meteor.js":
 		type: "Platform"
 		experience:
@@ -69,7 +69,7 @@
 			2017: 65
 			2018: 70
 		domains: ["WebApp", "Server"]
-		scoreWeight: 2
+		scoreWeight: 1.5
 	"Node.js":
 		type: "Platform"
 		experience:
@@ -83,6 +83,16 @@
 			2018: 75
 		domains: ["WebApp", "Server"]
 		scoreWeight: 2
+	"React":
+		type: "Platform"
+		experience:
+			2014: 0
+			2015: 10
+			2016: 60
+			2017: 80
+			2018: 80
+		domains: ["WebApp"]
+		scoreWeight: 2
 	"AngularJS 1.5":
 		type: "Platform"
 		experience:
@@ -93,7 +103,6 @@
 			2016: 90
 			2017: 0
 		domains: ["WebApp"]
-		scoreWeight: 2
 	"AngularJS 2.0":
 		type: "Platform"
 		experience:
@@ -114,10 +123,10 @@
 			2017: 90
 			2018: 60
 		domains: ["WebApp"]
-	"Material":
-		type: "Platform"
-		experience:
-			2017: 0
+	"Material-UI":
+		type: "Platform"
+		experience:
+			2016: 0
 			2017: 20
 			2018: 70
 		domains: ["WebApp"]


Handling runtime values in macros fixed

Handling runtime values in macros fixed

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -569,7 +569,7 @@
   if isTranslated expanded
     expanded
   else
-    macroResultCompile ctx, expanded
+    expressionCompile ctx, expanded

 isTranslated = (result) ->
   (isSimpleTranslated result) or (Array.isArray result) and (isSimpleTranslated result[0])
@@ -745,29 +745,6 @@

 expressionsCompile = (ctx, list) ->
   expressionCompile ctx, expression for expression in list
-
-macroResultCompile = (ctx, value) ->
-  assign = yes
-  translated = switch typeof value
-    when 'boolean', 'number', 'string' then JSON.stringify value
-    when 'object'
-      kind = Object.prototype.toString.call(value).slice(8, -1)
-      switch kind
-        when 'Date' then (jsNew 'Date', [+value])
-        when 'RegExp' then (jsNew 'RegExp', [value.source])
-        else
-          #TODO: rest of immutable
-          if Immutable.Iterable.isIterable value
-            (jsCallMethod 'Immutable', 'List',
-              [(jsCallMethod 'JSON', 'parse', [JSON.stringify(value.toJS())])])
-          else
-            assign = no
-            expressionCompile ctx, value
-  if assign
-    value = tea: toConstrained typeConstant 'Val'
-    assignCompile ctx, value, translated
-  else
-    translated

 tupleCompile = (ctx, form) ->
   elems = _terms form
@@ -1709,7 +1686,7 @@
       # operatorCompile ctx, call
       # args = termsCompile ctx, (_arguments call)[0..macroFn.length]
       #callTyping ctx, call
-      macroFn (_arguments call)...
+      constantToSource macroFn (_arguments call)...

     # ctx.addMacro macroName, (ctx, call) ->
     #   compiled = termCompile ctx, (fn_ params, body)
@@ -1735,7 +1712,25 @@
   serializeAst res

 ms[','] = ms_comma = (ctx, call) ->
-  (expressionCompile ctx, (_arguments call)[0])
+  (jsCall 'constantToSource', [expressionCompile ctx, (_arguments call)[0]])
+
+constantToSource = (value) ->
+  switch typeof value
+    when 'boolean' then (tokenize (if value then 'True' else 'False'))[0]
+    when 'number', 'string' then (tokenize "#{value}")[0]
+    when 'object'
+      kind = Object.prototype.toString.call(value).slice(8, -1)
+      value
+      # switch kind
+        # when 'Date' then (jsNew 'Date', [+value])
+        # when 'RegExp' then (jsNew 'RegExp', [value.source])
+        # else
+          #TODO: rest of immutable
+          # if Immutable.Iterable.isIterable value
+          #   (jsCallMethod 'Immutable', 'List',
+          #     [(jsCallMethod 'JSON', 'parse', [JSON.stringify(value.toJS())])])
+          # else
+            # expressionCompile ctx, value

 ms.macro = ms_macro = (ctx, call) ->
   hasName = requireName ctx, 'Name required to declare a new instance'


Don't show drag helper if connected

Don't show drag helper if connected

--- static/coffee/flows/services.coffee
+++ static/coffee/flows/services.coffee
@@ -1210,9 +1210,10 @@
         Flow.flow.action_sets.push(actionset)

       if Flow.flow.action_sets.length == 1
-        $timeout ->
-          DragHelper.showSaveResponse($('#' + Flow.flow.action_sets[0].uuid + ' .source'))
-        ,0
+        if not Flow.flow.action_sets[0].destination
+          $timeout ->
+            DragHelper.showSaveResponse($('#' + Flow.flow.action_sets[0].uuid + ' .source'))
+          ,0

       @checkTerminal(actionset)
       @markDirty()


Fix for page manager.

Fix for page manager.

--- src/api/page_manager.coffee
+++ src/api/page_manager.coffee
@@ -2,7 +2,7 @@
 Path           = require('path')
 FS             = require('fs')
 {EventEmitter} = require('events')
-{dfs}          = require('../../shared/utils')
+{dfs}          = require('../shared/utils')

 class PageManager extends EventEmitter
     constructor : (container) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -8,7 +8,7 @@

 @name average
 @methodOf Array#
-@returns {N} A new array that contains only the non-null values.
+@returns {Number} A new array that contains only the non-null values.
 ###
 Array::average = ->
   @sum()/@length


fixed current bugs where forms are not opened correctly or displaying things from other forms

--- src/javascripts/contact_us.coffee
+++ src/javascripts/contact_us.coffee
@@ -2,9 +2,9 @@
   $("aside.contact-us .write-to-us").click (event) ->
     event.preventDefault()
     $trigger = $(@)
-
+    $that = $trigger.parent()
     # Clone form template
-    $form = $("#contact-us-form-template").clone()
+    $form = $("aside.contact-us.basic > form.write-to-us-form").clone()
     $form.removeAttr("id")

     # Replace the trigger w/ the form
@@ -22,8 +22,8 @@
           success: (data) ->
             console.log("success")
             console.log($form)
-            $form.replaceWith(data)
-            $('.contact-us').find('form:first').show()
+            $form.replaceWith(data)
+            $that.find('form').show()
           error: (x, y, z) ->
             # Server error or timeout, nothing to do
             # FIXME: Uncomment the real error message


use instance var; revert in between changes

--- client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -3,7 +3,6 @@
   hiddenItems               = []
   hiddenNewMemberItemGroups = [[]]
   hiddenItemCount           = 0
-  itemsIndexed              = {}

   prepareNewMemberGroup = ->

@@ -136,16 +135,12 @@
     dataId = activity.getId()

     if dataId?
-      if itemsIndexed[dataId]
+      if @itemsIndexed[dataId]
         console.log "duplicate entry", activity.bongo_?.constructorName, dataId
         _rollbar.push "duplicate entry", activity.bongo_?.constructorName, dataId
       else
-        itemsIndexed[dataId] = activity
+        @itemsIndexed[dataId] = activity
         super(activity, index, animation)
-
-  removeAllItems: ->
-    itemsIndexed = {}
-    super()

   ownActivityArrived:(activity)->


Hooking rethinkdb directory support into backbone

--- admin/static/coffee/cluster.coffee
+++ admin/static/coffee/cluster.coffee
@@ -17,6 +17,9 @@
 class Event extends Backbone.Model

 class Issue extends Backbone.Model
+
+# this is a hook into the directory
+class MachineAttributes extends Backbone.Model

 class ConnectionStatus extends Backbone.Model

@@ -63,7 +66,7 @@
         if name is 'usa_1' and @.get('name') is 'mem_usage_data'
             console.log name, "in dataset #{@.get('name')} now includes the data", _.map cached_data["01f04592-e403-4abc-a845-83d43f6fd967"], (data_point) -> data_point.get('value')
         ###
-
+
         # Make sure the new value is non-negative
         new_val = existing_val + delta
         new_val = existing_val if new_val <= 0
@@ -105,13 +108,18 @@
     model: Issue
     url: '/ajax/issues'

+# hook into directory
+class Directory extends Backbone.Collection
+    model: MachineAttributes
+    url: '/ajax/directory'
+
 class DashboardView extends Backbone.View
     className: 'dashboard-view'
     template: Handlebars.compile $('#dashboard_view-template').html()

     update_data_streams: (datastreams) ->
         timestamp = new Date(Date.now())
-
+
         for stream in datastreams
            stream.update(timestamp)

@@ -355,6 +363,7 @@
     datacenters.reset()
     machines.reset()
     issues.reset()
+    directory.reset()

 # Process updates from the server and apply the diffs to our view of the data. Used by our version of Backbone.sync and POST / PUT responses for form actions
 apply_diffs = (updates) ->
@@ -385,6 +394,14 @@
 set_issues = (issue_data_from_server) ->
     issues.reset(_.map(issue_data_from_server, (issue) -> {critical: false, type: "master_down", time: ISODateString}))

+set_directory = (attributes_from_server) ->
+    # Convert directory representation from RethinkDB into backbone friendly one
+    dir_machines = []
+    for key, value of attributes_from_server
+        value['id'] = key
+        dir_machines[dir_machines.length] = value
+    directory.reset(dir_machines)
+
 $ ->
     bind_dev_tools()

@@ -393,6 +410,7 @@
     window.namespaces = new Namespaces
     window.machines = new Machines
     window.issues = new Issues
+    window.directory = new Directory
     window.events = new Events
     window.connection_status = new ConnectionStatus

@@ -416,6 +434,7 @@
         if method is 'read'
             $.getJSON('/ajax', apply_diffs)
             $.getJSON('/ajax/issues', set_issues)
+            $.getJSON('/ajax/directory', set_directory)
         else
             legacy_sync method, model, success, error

@@ -434,6 +453,7 @@

     $.getJSON('/ajax', apply_diffs)
     $.getJSON('/ajax/issues', set_issues)
+    $.getJSON('/ajax/directory', set_directory)

     # Set up common DOM behavior
     $('.modal').modal