refactor(app generator): add discussion on `task:target` mapping

--- generators/app/templates/_Gruntfile.coffee
+++ generators/app/templates/_Gruntfile.coffee
@@ -75,6 +75,42 @@
 ##    * The build parts can be processed seperately, but some depend on others:
 ##          * The bootstrap build part needs a brief.
 ##          * The app build part will also trigger builds of the #% if ( i18n ) { %#i18n, #% } %#style, brief, and bootstrap build parts.
+##
+##
+##  Mapping to grunt tasks and targets:
+##
+##  As briefly indicated, the build tools map 1-to-1 onto the npm-loaded grunt tasks.
+##  Where applicable, for these tools, build parts and debugging mode map to their task's targets.
+##
+##  For instance, the `browserify` grunt task is one of the tools needed to build the app part, either in
+##  debugging (app_debug), or non-debugging (app_dist) mode.
+##
+##      browserify:
+##          app_dist:
+##              <specific config>
+##
+##          app_debug:
+##              <specific config>
+##
+##
+##  Build parts may map to more than one tool. In fact, generally, part builds have three phases:
+##
+##      * clean phase           - maps to clean task, one target per build part
+##      * copy phase            - maps to copy task, one target per build part
+##      * construction phase    - maps to part specific tasks, one target per build part and possibly, debugging mode
+##
+##
+##  Some part builds have more phases than these; verification and testing phases for instance.
+##
+##  Grunt tasks per build part exist, controlling these phases.
+##
+##
+##  Finally, this is how the main grunt commandline tasks are mapped to all of the above:
+##
+##      * grunt [default]   - does a production, non-debugging, all-parts, minified build plus artifacts
+##      * grunt debug       - does a testing, debugging, all-parts except documentation, as-is build
+##      * grunt dev         - does a local, debugging, all-parts except documentation, as-is build and keeps a close
+##                            watch on filesystem changes, selectively re-triggering part builds as needed
 ##
 ##  ====
 ##


fix bug in `scan`

fix bug in `scan`

--- src/scan.coffee
+++ src/scan.coffee
@@ -7,6 +7,7 @@
 Bacon.Observable :: scan = (seed, f) ->
   f = toCombinator(f)
   acc = toOption(seed)
+  initHandled = false
   subscribe = (sink) =>
     initSent = false
     unsub = nop
@@ -14,20 +15,22 @@
     sendInit = ->
       unless initSent
         acc.forEach (value) ->
-          initSent = true
+          initSent = initHandled = true
           reply = sink(new Initial(-> value))
           if (reply == Bacon.noMore)
             unsub()
             unsub = nop
     unsub = @dispatcher.subscribe (event) ->
       if (event.hasValue())
-        if (initSent and event.isInitial())
+        if (initHandled and event.isInitial())
+          #console.log "skip INITIAL"
           Bacon.more # init already sent, skip this one
         else
           sendInit() unless event.isInitial()
-          initSent = true
+          initSent = initHandled = true
           prev = acc.getOrElse(undefined)
           next = f(prev, event.value())
+          #console.log prev , ",", event.value(), "->", next
           acc = new Some(next)
           sink (event.apply(-> next))
       else


this one goes out to @julien-f too ;)

this one goes out to @julien-f too ;)

--- src/spec.coffee
+++ src/spec.coffee
@@ -419,7 +419,7 @@

           SR_type: get('type')

-          physical_usage: number get 'physical_utilization'
+          physical_usage: number get 'physical_utilisation'

           usage: number get 'virtual_allocation'


add response data to error

add response data to error

--- src/sync.coffee
+++ src/sync.coffee
@@ -51,7 +51,7 @@

     backboneSync method, model or @event_emitter, _.extend({url: url, beforeSend: @beforeSend}, options, {
       success: (res) -> callback(null, JSONUtils.parse(res))
-      error: (res) -> return callback(_.extend(new Error("Ajax failed with status #{res.status} for #{method}"), {status: res.status}))
+      error: (res) -> return callback(_.extend(new Error("Ajax failed with status #{res.status} for #{method}"), {status: res.status, json: res.responseJSON, text: res.responseText}))
     })

 module.exports = (type, sync_options) ->


Use 16 as font size default

Use 16 as font size default

--- src/editor-view.coffee
+++ src/editor-view.coffee
@@ -27,7 +27,7 @@
   @characterWidthCache: {}
   @configDefaults:
     fontFamily: ''
-    fontSize: 20
+    fontSize: 16
     showInvisibles: false
     showIndentGuide: false
     showLineNumbers: true


engine data module

engine data module

--- src/engine_data.coffee
+++ src/engine_data.coffee
@@ -15,4 +15,5 @@
     I.data[key]

   set: (key, value) ->
-    I.data+    I.data[key] = value
+


Ignore edited messages

Ignore edited messages

Closes #4

--- src/fleepClient.coffee
+++ src/fleepClient.coffee
@@ -65,24 +65,44 @@
       Util.debug 'Updating last seen event horizon to '+resp.event_horizon
     Util.debug 'Finished handling long poll response'

+  # Processes a single Event object in a list of Fleep events
   handleStreamEvent: (event) =>

+    # Event does not have a rec_type, API error?
     if not event.mk_rec_type?
       Util.logError 'Invalid response from the server'
+      return

+    # New contact information, currently we don't do anything with it
     if event.mk_rec_type is 'contact'
       Util.debug event
       return
+
+    # Skip everything but text message events
     if event.mk_rec_type isnt 'message'
       Util.debug 'Skipping stream item '+event.mk_rec_type+', not a message type of event'
       return

+    # Detected a new conversation
     if event.conversation_id not in @conversations
       Util.debug "New conversation! Conversation #{event.conversation_id} was not in the list of monitored conversations, adding it now"
       @conversations.push event.conversation_id

+    # This message is an echo of our own message, ignore
     if event.account_id is @profile.account_id
       Util.debug 'It is my own message, ignore it'
+      return
+
+    # Ignore edited messages (messages that were posted, then edited)
+    # See https://github.com/anroots/hubot-fleep/issues/4
+    if event.revision_message_nr?
+      Util.debug 'This is an edited message, skipping...'
+      return
+
+    # Ignore messages without the 'message' key - some invalid state
+    if not event.message?
+      Util.logError 'Invalid response from the server, expected a message key'
+      Util.debug event
       return

     message = event.message.replace(/(<([^>]+)>)/ig,"")


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -48,7 +48,7 @@
   @param {Number} [frequency=3] The number of frames in between opacity changes
   @param {Number} [alpha=0.5] The alpha value to flicker to
   ###
-  flicker: ({duration, frequency, alpha}) ->
+  flicker: ({duration, frequency, alpha}={}) ->
     Object.reverseMerge I,
       flickerAlpha: 0.5
       flickerDuration: 30


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -108,8 +108,6 @@
     active: true
     created: false
     destroyed: false
-
-    excludedModules: []

   self = Core(I).extend {
     ###*


setup a few users in the database

setup a few users in the database

--- src/hubot/shell.coffee
+++ src/hubot/shell.coffee
@@ -12,11 +12,19 @@
   run: ->
     console.log "Hubot: the Shell."

-    user = new Robot.User 1, 'shell'
+    user = @userForId('1', {name: "Shell"})
+    console.log user
+
     process.stdin.resume()
     process.stdin.on 'data', (txt) =>
       txt.toString().split("\n").forEach (line) =>
         return if line.length == 0
         @receive new Robot.Message user, line

+    setTimeout =>
+      user   = @userForId('1', {name: "Shell"})
+      atmos  = @userForId('2', {name: "atmos"})
+      holman = @userForId('3', {name: "holman"})
+    , 3000
+
 exports.Shell = Shell


fix image urls

fix image urls

--- src/ihavenoidea.coffee
+++ src/ihavenoidea.coffee
@@ -28,20 +28,20 @@
 ]

 images = [
-  'http://imgur.com/XhZzd2h',
-  'http://imgur.com/GtgkJ38',
-  'http://imgur.com/Jv3qULQ',
-  'http://imgur.com/1hrVmZD',
-  'http://imgur.com/xC0JCFK',
-  'http://imgur.com/kFNAAeX',
-  'http://imgur.com/9s62t7y',
-  'http://imgur.com/ugZ2tGn',
-  'http://imgur.com/DeyWrC2',
-  'http://imgur.com/tkbLyTG',
-  'http://imgur.com/g8EbISz',
-  'http://imgur.com/RuFZdPR',
-  'http://imgur.com/YLEM3Ns',
-  'http://imgur.com/1TWbKbW'
+  'http://i.imgur.com/XhZzd2h.jpg',
+  'http://i.imgur.com/GtgkJ38.jpg',
+  'http://i.imgur.com/Jv3qULQ.jpg',
+  'http://i.imgur.com/1hrVmZD.jpg',
+  'http://i.imgur.com/xC0JCFK.jpg',
+  'http://i.imgur.com/kFNAAeX.jpg',
+  'http://i.imgur.com/9s62t7y.jpg',
+  'http://i.imgur.com/ugZ2tGn.jpg',
+  'http://i.imgur.com/DeyWrC2.jpg',
+  'http://i.imgur.com/tkbLyTG.jpg',
+  'http://i.imgur.com/g8EbISz.jpg',
+  'http://i.imgur.com/RuFZdPR.jpg',
+  'http://i.imgur.com/YLEM3Ns.jpg',
+  'http://i.imgur.com/1TWbKbW.jpg'
 ]

 regex = new RegExp gaffes.join('|'), 'ig'


Fix duration handling in Mixer

Fix duration handling in Mixer

--- src/instruments.coffee
+++ src/instruments.coffee
@@ -206,11 +206,11 @@
   process: (nestedProcessors...) ->
     f = (time, globalTime) =>
       sample = 0
-      for processor in nestedProcessors when processor.duration? && time <= processor.duration
+      for processor in nestedProcessors when (!processor.duration? || time <= processor.duration)
         sample += @multiplier * processor(time, globalTime)
       sample

-    # Find longest child duration or leave empty of no child specifies one
+    # Find longest child duration or leave empty if ANY child runs indefinitely
     duration = -1
     for processor in nestedProcessors
       if processor.duration?
@@ -218,6 +218,7 @@
       else
         duration = -1
         break
+
     f.duration = duration if duration > 0
     f


bundle-builder tmp now uses os.tmpdir

bundle-builder tmp now uses os.tmpdir

--- src/bundle-builder/bundle.coffee
+++ src/bundle-builder/bundle.coffee
@@ -4,11 +4,11 @@
 crypto = require 'crypto'
 pathUtils = require 'path'
 coffee = require 'coffee-script'
+os = require 'os'

 {log} = Neft

-CACHE_DIRECTORY = __dirname + '/.tmp'
-fs.ensureDirSync CACHE_DIRECTORY
+CACHE_DIRECTORY = os.tmpdir()

 {stringify} = JSON


Utilized local var to prevent double search

`$navigation.find '[data-role="prev"]'` was being run twice.

Once saved to the local variables `$prev` and `$next`, and again to actually use the elements because the local variables were never used.  This change makes use of them and removes the duplicate search.

--- src/coffee/bootstrap-tour.coffee
+++ src/coffee/bootstrap-tour.coffee
@@ -447,12 +447,13 @@
       $navigation = $template.find '.popover-navigation'
       $prev = $navigation.find '[data-role="prev"]'
       $next = $navigation.find '[data-role="next"]'
+      $resume = $navigation.find '[data-role="pause-resume"]'

       $template.addClass 'orphan' if @_isOrphan step
       $template.addClass "tour-#{@_options.name} tour-#{@_options.name}-#{i}"
-      $navigation.find('[data-role="prev"]').addClass('disabled') if step.prev < 0
-      $navigation.find('[data-role="next"]').addClass('disabled') if step.next < 0
-      $navigation.find('[data-role="pause-resume"]').remove() unless step.duration
+      $prev.addClass('disabled') if step.prev < 0
+      $next.addClass('disabled') if step.next < 0
+      $resume.remove() unless step.duration
       $template.clone().wrap('<div>').parent().html()

     _reflexEvent: (reflex) ->


masson: new id property

masson: new id property

--- src/tree.coffee
+++ src/tree.coffee
@@ -159,8 +159,9 @@
     # Module dependencies
     continue if typeof callback is 'string'
     callback = actions[i] = callback: callback if typeof callback is 'function'
-    callback.hidden ?= true unless callback.name
-    callback.name ?= "#{module}/#{i}"
+    # callback.hidden ?= true unless callback.name
+    callback.id ?= "#{module}/#{i}"
+    callback.name ?= null
     callback.module ?= module
     callback.index ?= i
     callback.skip ?= false


Close #1

Close #1

--- src/main/window.coffee
+++ src/main/window.coffee
@@ -1,9 +1,11 @@
 EventEmitter = require 'events'
 {readFile} = require 'fs'
 {join} = require 'path'
+{watch} = require 'chokidar'
+
 BrowserWindow = require 'browser-window'
 {showOpenDialog} = require 'dialog'
-{watch} = require 'chokidar'
+shell = require 'shell'

 mediator = require './mediator'
 events = require './events'
@@ -25,6 +27,7 @@
         bounds = width: 800, height: 600
       @browserWindow = new BrowserWindow bounds
       @browserWindow.webContents.on 'did-finish-load', @onContentsDidFinishLoad
+      @browserWindow.webContents.on 'will-navigate', @onContentsWillNavigate
       @browserWindow.on 'move', @onMoved
       @browserWindow.on 'resize', @onResized
       @browserWindow.on 'closed', @onClosed
@@ -43,6 +46,10 @@
     @browserWindow.webContents.executeJavaScript js
     @browserWindow.setTitle 'README.md'
     @render readMe
+
+  onContentsWillNavigate: (e, url) =>
+    e.preventDefault()
+    shell.openExternal url

   onMoved: => @registerBounds()


Fix sortable bug in completed and all tasks

--- app/controllers/tasks.coffee
+++ app/controllers/tasks.coffee
@@ -77,6 +77,10 @@

     html = ""

+    # If the list is disabled, make sorting default
+    Setting.toggleSort() if @list.disabled and !Setting.sortMode()
+
+    # Sorting tasks
     if Setting.sortMode()
       tasks = Task.sort(tasks)
       last = tasks[0]?.priority


Add app.getHomeDir() API.

Add app.getHomeDir() API.

--- browser/api/lib/app.coffee
+++ browser/api/lib/app.coffee
@@ -6,6 +6,9 @@

 app = new Application

+app.getHomeDir = ->
+  process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']
+
 app.commandLine =
   appendSwitch: bindings.appendSwitch,
   appendArgument: bindings.appendArgument


feat(core): mid-scrum saving

feat(core): mid-scrum saving

Saves scrum log after every set of answers as well as the very end.

Closes #19

--- src/model/scrum.coffee
+++ src/model/scrum.coffee
@@ -49,6 +49,7 @@
         that._scrumLog['timestamp'] = day.toString() + '/' + month.toString() + '/' + year.toString() + ' at ' +
             hour.toString() + ':' + minutes.toString()
         that._scrumLog['participants'] = []
+        that._robot.brain.data._private.scrumminator.logs[that._id].push( that._scrumLog )

         # Find current room
         gitter.rooms.join(that._room)
@@ -72,6 +73,7 @@
                                     'displayName': user.displayName,
                                     'answers': ['', '', '', '', '']
                                 })
+                        that._robot.brain.save

         parseLog = (response) ->
@@ -106,6 +108,7 @@
                                     that._robot.send
                                         room: that._room,
                                         reply.replace('[username]', displayname.split(' ')[0])
+                                that._robot.brain.save

         endScrum = () ->
@@ -116,7 +119,6 @@
             gitter.rooms.join(that._room)
                 .then (room) ->
                     room.unsubscribe()
-            that._robot.brain.data._private.scrumminator.logs[that._id].push( that._scrumLog )
             that._robot.brain.save

     stopCronJob: ->


Postversion script now checks for tag and pushes from branch

--- src/postversion.coffee
+++ src/postversion.coffee
@@ -23,20 +23,31 @@
     )
   )
   .then(() ->
+    exec("git tag -l #{pkg.version}")
+    .then((out) ->
+      if not out.split(/\r\n|\n/).some((line) -> line.trim() == "v#{pkg.version}")
+        throw new Error("Cannot find tag: v#{pkg.version}")
+      return
+    )
+  )
+  .then(() ->
+    Promise.resolve()
+    .then(() ->
+      if not argv.noGitPush
+        exec("git push origin HEAD:#{argv.branch}")
+    )
+    .then(() ->
+      if not argv.noGitPush
+        exec("git push origin HEAD:#{argv.branch} --tags")
+    )
+  )
+  .then(() ->
     Promise.resolve()
     .then(() ->
       exec("git checkout v#{pkg.version}")
     )
     .then(() ->
       Promise.resolve()
-      .then(() ->
-        if not argv.noGitPush
-          exec("git push origin HEAD:#{argv.branch}")
-      )
-      .then(() ->
-        if not argv.noGitPush
-          exec("git push origin HEAD:#{argv.branch} --tags")
-      )
       .then(() ->
         if not argv.noNpmPublish
           exec('npm publish')


Don't call sadd with blank args

Don't call sadd with blank args

--- services/document-updater/app/coffee/RedisManager.coffee
+++ services/document-updater/app/coffee/RedisManager.coffee
@@ -143,4 +143,7 @@
 	getAndSetProject: (project_id, callback = (error) ->) ->
 		rclient.smembers keys.docsInProject(project_id: project_id), (error, doc_ids) ->
 			return callback(error) if error?
-			rclient.sadd keys.docsInProject(project_id: project_id), doc_ids..., callback
+			if doc_ids.length > 0
+				rclient.sadd keys.docsInProject(project_id: project_id), doc_ids..., callback
+			else
+				callback()


fix bug where live updates were broken

fix bug where live updates were broken

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -12,6 +12,14 @@
   {dash} = Bongo

   activityTypes = [
+    'CStatusActivity'
+    'CCodeSnipActivity'
+    'CFollowerBucketActivity'
+    'CNewMemberBucketActivity'
+    'CDiscussionActivity'
+    'CTutorialActivity'
+    'CInstallerBucketActivity'
+    'CBlogPostActivity'
     'Everything'
   ]


jupyter: bugfix

jupyter: bugfix

--- src/smc-webapp/jupyter/codemirror.cjsx
+++ src/smc-webapp/jupyter/codemirror.cjsx
@@ -203,7 +203,7 @@

     # TODO: this is very ugly -- must rewrite below using React.
     draw_other_cursors: (cm, account_id, locs) ->
-        if not cm?
+        if not cm? or not @props.actions?
             return
         @_cursors ?= {}
         users = @props.actions.redux.getStore('users')


update last_action (for realz)

update last_action (for realz)

--- commands/move.coffee
+++ commands/move.coffee
@@ -78,7 +78,11 @@
         query_character =
           _id: character._id
         update_character =
-          $set: coords
+          $set:
+            x: coords.x
+            y: coords.y
+            z: coords.z
+            last_action: new Date()
           $inc:
             ap: 0 - ap_cost
         db.characters.update query_character, update_character, cb


TOL becomes 1e-3 fix #103

TOL becomes 1e-3
fix #103

--- src/client/PlayerComparator.coffee
+++ src/client/PlayerComparator.coffee
@@ -62,7 +62,7 @@
   forceA = average(a) + experience(a)
   forceB = average(b) + experience(b)
   if gamesA != gamesB
-      if Math.abs(forceA - forceB) / Math.max(forceA, forceB) > 0.01
+      if Math.abs(forceA - forceB) / Math.max(forceA, forceB) > 1e-3
         return if forceA > forceB then aIsGreater else bIsGreater
       else
         return deepCompare(a, b)


use dict

use dict

--- src/yaku.coffee
+++ src/yaku.coffee
@@ -11,7 +11,12 @@
 	 * We can call these functions, once our operation is completed.
 	###
 	constructor: (executor) ->
-		executor genTrigger(@, $resolved), genTrigger(@, $rejected)
+		self = @
+
+		executor (val) ->
+			self._trigger $resolved, val
+		, (val) ->
+			self._trigger $rejected, val

 	###*
 	 * Appends fulfillment and rejection handlers to the promise,
@@ -24,10 +29,18 @@
 	then: (onFulfilled, onRejected) ->
 		self = @

-		newYaku = self._hCount + 4
-
-		self[newYaku] = new Yaku (resolve, reject) ->
-			addHandler self, onFulfilled, onRejected, resolve, reject
+		hGroup = [onFulfilled, onRejected]
+
+		@[@_hCount++] = hGroup
+
+		hGroup[4] = new Yaku (resolve, reject) ->
+			hGroup[2] = resolve
+			hGroup[3] = reject
+
+			if self._state != $pending
+				self._resolveHanlers hGroup
+
+			return

 	###*
 	 * The catch() method returns a Promise and deals with rejected cases only.
@@ -142,7 +155,6 @@
 	 * then these values will be passed to 2 and 3.
 	 * @private
 	###
-	$groupNum = 5

 	$circularErrorInfo = 'circular promise resolution chain'

@@ -188,28 +200,6 @@
 			setTimeout

 	###*
-	 * Push new handler to current promise.
-	 * @private
-	 * @param {Yaku} self
-	 * @param {Function} onFulfilled It will be called when self is resolved.
-	 * @param {Function} onRejected It will be called when self is rejected.
-	 * @param {Function} resolve Call it to make a promise resolve.
-	 * @param {Function} reject Call it to make a promise reject.
-	###
-	addHandler = (self, onFulfilled, onRejected, resolve, reject) ->
-		offset = self._hCount
-		self[offset] = onFulfilled
-		self[offset + 1] = onRejected
-		self[offset + 2] = resolve
-		self[offset + 3] = reject
-		self._hCount += $groupNum
-
-		if self._state != $pending
-			resolveHanlers self, offset
-
-		return
-
-	###*
 	 * Resolve or reject primise with value x. The x can also be a thenable.
 	 * @private
 	 * @param  {Any | Thenable} x A normal value or a thenable.
@@ -258,59 +248,50 @@
 			reject e
 			return $tryErr

-	getX = (self, offset, handler) ->
+	_getX: (hGroup, handler) ->
 		try
-			handler self._value
+			handler @_value
 		catch e
-			self[offset + 3] e
+			hGroup[3] e
 			return $tryErr

 	###*
 	 * Decide how handlers works.
 	 * @private
 	 * @param  {Yaku} self
-	 * @param  {Integer} offset The offset of the handler group.
-	###
-	resolveHanlers = (self, offset) ->
+	###
+	_resolveHanlers: (hGroup) ->
 		# Trick: Reuse the value of state as the handler selector.
 		# The "i + state" shows the math nature of promise.
-		handler = self[offset + self._state]
+		handler = hGroup[@_state]

 		if typeof handler == 'function'
+			self = @
 			nextTick ->
-				x = getX self, offset, handler
+				x = self._getX hGroup, handler
 				return if x == $tryErr

 				# Prevent circular chain.
-				if x == self[offset + 4] and x
-					return x[offset + 1]? new TypeError $circularErrorInfo
-
-				resolveValue x, self[offset + 2], self[offset + 3]
+				if x == hGroup[4] and x
+					return x._trigger $rejected,
+						new TypeError $circularErrorInfo
+
+				resolveValue x, hGroup[2], hGroup[3]
 		else
-			self[offset + 2 + self._state] self._value
+			hGroup[2 + @_state] @_value

 		return

-	###*
-	 * It will produce a trigger function to user.
-	 * Such as the resolve and reject in this `new Yaku (resolve, reject) ->`.
-	 * @private
-	 * @param  {Yaku} self
-	 * @param  {Integer} state The value is one of `$pending`, `$resolved` or `$rejected`.
-	 * @return {Function} `(value) -> undefined` A resolve or reject function.
-	###
-	genTrigger = (self, state) -> (value) ->
-		return if self._state != $pending
-
-		self._state = state
-		self._value = value
-
-		offset = 0
-
-		while offset < self._hCount
-			resolveHanlers self, offset
-
-			offset += $groupNum
+	_trigger: (state, value) ->
+		return if @_state != $pending
+
+		@_state = state
+		@_value = value
+
+		i = 0
+
+		while i < @_hCount
+			@_resolveHanlers @[i++]

 		return


fix: stack trace style

fix: stack trace style

--- src/yaku.coffee
+++ src/yaku.coffee
@@ -210,7 +210,24 @@
 	@onUnhandledRejection: (reason, p) ->
 		return if not isObject console

-		stackInfo = [(
+		stackInfo = []
+
+		if isLongStackTrace and p[$promiseTrace]
+			push = (trace) -> stackInfo.push trace.trim()
+			if p[$settlerTrace]
+				push p[$settlerTrace]
+			while p
+				push p[$promiseTrace]
+				if p._xpre
+					push p._xpre[$promiseTrace]
+				p = p._pre
+
+		stackStr = '\n' + stackInfo.join('\n')
+
+		if typeof __filename == 'string'
+			stackStr = stackStr.replace ///.+#{__filename}.+\n?///g, ''
+
+		console.error 'Unhandled Rejection:', (
 			if reason
 				if reason.stack
 					reason.stack.trim()
@@ -218,21 +235,7 @@
 					reason
 			else
 				reason
-		)]
-
-		if isLongStackTrace and p[$promiseTrace]
-			if p[$settlerTrace]
-				stackInfo.push p[$settlerTrace].trim()
-			while p
-				stackInfo.push p[$promiseTrace].trim()
-				p = p._pre
-
-		stackStr = stackInfo.join('\n')
-
-		if typeof __filename == 'string'
-			stackStr = stackStr.replace ///.+#{__filename}.+\n?///g, ''
-
-		console.error 'Unhandled Rejection:', stackStr
+		), stackStr

 		return stackInfo


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -22,6 +22,14 @@
   equals (-5).clamp(0, 1), 0
   equals 1.clamp(0, null), 1
   equals (-1).clamp(0, null), 0
+

+  player = GameObject()
+
+  player.tween 30,
+    x: 50
+    y: 50
+    easing: "quadratic"
+

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


Date is a past moment that enables an age test.

--- test/unit/clinicalSpec.coffee
+++ test/unit/clinicalSpec.coffee
@@ -1,4 +1,4 @@
-define ['ngmocks', 'clinical'], (ngmocks, clinical) ->
+define ['ngmocks', 'moment', 'clinical'], (ngmocks, moment, clinical) ->
   describe 'Unit Testing Clinical Service', ->
     # The qiprofile Clinical Profile factory.
     Clinical = null
@@ -15,17 +15,18 @@
       # TODO - change mock to conform with REST model and
       # rerun after clinical is refactored per the app TODOs.
       mock =
+        # TODO - Use the resource data model to create the mock subject.
         subject:
-          birth_date: new Date
+          # The anonymized birth date is July 7.
+          birth_date: moment([1985, 6, 7])
           races: ['Black', 'Asian']
           ethnicity: 'Non-Hispanic'
           encounters:
             [
               {
-                date: new Date
+                date: moment([2014, 2, 12])
                 outcomes: [
-                  # TODO - make a pathology outcome here.
-                  # TODO - grade is composite.
+                  # TODO - grade is an aggregate.
                   # tnm: {
                   #   grade: 2
                   #   lymph_status: 3
@@ -36,7 +37,7 @@
                 encounter_type: "Biopsy"
               }
               {
-                date: new Date
+                date: moment([2014, 4, 6])
                 outcomes: [
                   # tnm: {
                   #   #
@@ -57,6 +58,7 @@
         # Validate the configuration.
         # TODO - compare to the mock values for each encounter
         # and each demographic and outcome value.
+        # TODO - test the age property.
         # TODO - these should be broken out into test cases.
         # TODO - add expect message arguments.
         expect(config.encounters).to.exist


Variable naming change

Variable naming change

@_future_previousRoute -> @nextCurrentRoute
@ _future_currentRoute -> @nextPreviousRoute

--- src/chaplin/dispatcher.coffee
+++ src/chaplin/dispatcher.coffee
@@ -92,11 +92,11 @@

   # Handler for the controller lazy-loading.
   controllerLoaded: (route, params, options, Controller) ->
-    @_future_previousRoute = @currentRoute
-    @_future_currentRoute = _.extend {}, route, {previous: utils.beget(@_future_previousRoute)}
+    @nextPreviousRoute = @currentRoute
+    @nextCurrentRoute = _.extend {}, route, {previous: utils.beget(@nextPreviousRoute)}

-    controller = new Controller params, @_future_currentRoute, options
-    @executeBeforeAction controller, @_future_currentRoute, params, options
+    controller = new Controller params, @nextCurrentRoute, options
+    @executeBeforeAction controller, @nextCurrentRoute, params, options

   # Executes controller action.
   executeAction: (controller, route, params, options) ->
@@ -132,12 +132,12 @@

     executeAction = =>
       if controller.redirected or @currentRoute and route is @currentRoute
-        @_future_previousRoute = @_future_currentRoute = null
+        @nextPreviousRoute = @nextCurrentRoute = null
         controller.dispose()
         return
-      @previousRoute = @_future_previousRoute
-      @currentRoute = @_future_currentRoute
-      @_future_previousRoute = @_future_currentRoute = null
+      @previousRoute = @nextPreviousRoute
+      @currentRoute = @nextCurrentRoute
+      @nextPreviousRoute = @nextCurrentRoute = null
       @executeAction controller, route, params, options

     unless before


Fixed bug when using inline hints

Fixed bug when using inline hints

--- src/coffee/jquery.smallipop.coffee
+++ src/coffee/jquery.smallipop.coffee
@@ -677,11 +677,12 @@

       # Get content for the popup
       # If it's inline markup, create a deep copy of the hint html
-      objHint = hint \
-        or self.find(".#{options.infoClass}")
-          .clone(true, true)
-          .removeClass("#{options.infoClass}") \
-        or self.attr('title')
+      objHint = hint or self.attr('title')
+
+      $objInfo = $ ".#{options.infoClass}", self
+      if $objInfo.length
+        objHint = $objInfo.clone(true, true)
+          .removeClass("#{options.infoClass}")

       # Initialize each trigger, create id and bind events
       if objHint and not self.hasClass 'sipInitialized'
@@ -702,7 +703,8 @@
             optionName = optionName.substr(0, 1).toLowerCase() + optionName.substr(1)
             triggerOptions[optionName] = value

-        isFormElement = triggerOptions.handleInputs and tagName in ['input', 'select', 'textarea']
+        isFormElement = triggerOptions.handleInputs \
+          and tagName in ['input', 'select', 'textarea']

         # Activate on blur events if used on inputs and disable hide on click
         if isFormElement
@@ -714,7 +716,8 @@
           triggerEvents['mouseout.smallipop'] = sip._triggerMouseout

         # Check whether the trigger should activate smallipop by click or hover
-        if triggerOptions.triggerOnClick or (triggerOptions.touchSupport and sip._onTouchDevice())
+        if triggerOptions.triggerOnClick or \
+            (triggerOptions.touchSupport and sip._onTouchDevice())
           triggerEvents['click.smallipop'] = sip._showSmallipop
         else
           triggerEvents['click.smallipop'] = sip._triggerMouseout


reorganize to match structure

reorganize to match structure

--- src/coffee/interpreter.coffee
+++ src/coffee/interpreter.coffee
@@ -12,13 +12,6 @@
   questionOption: (option, value) ->
     {tag: 'a', class: 'answer', contents: option, value: value}

-  interpretBody: ->
-    options = _.map @dictionary.select, @questionOption
-    {
-      class:    'body'
-      contents: SwanKiosk.Components.center(options)
-    }
-
   interpretHeader: ->
     class:    'header'
     contents: [{
@@ -28,6 +21,13 @@
       class:    'why-wrapper',
       contents: {tag: 'a', class: 'why', contents: @why, title: @dictionary.why}
     })]
+
+  interpretBody: ->
+    options = _.map @dictionary.select, @questionOption
+    {
+      class:    'body'
+      contents: SwanKiosk.Components.center(options)
+    }

   interpretNavigation: ->
     class:    'navigation',


hub: ablity to disable main hub server by setting port=0

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -3346,6 +3346,8 @@

     async.series([
         (cb) ->
+            if not program.port
+                cb(); return
             init_metrics(cb)
         (cb) ->
             # this defines the global (to this file) database variable.
@@ -3358,18 +3360,26 @@
                     winston.debug("connected to database.")
                     cb()
         (cb) ->
+            if not program.port
+                cb(); return
             if program.dev or program.update
                 winston.debug("updating the database schema...")
                 database.update_schema(cb:cb)
             else
                 cb()
         (cb) ->
+            if not program.port
+                cb(); return
             init_stripe(cb)
         (cb) ->
+            if not program.port
+                cb(); return
             init_support(cb)
         (cb) ->
             init_compute_server(cb)
         (cb) ->
+            if not program.port
+                cb(); return
             # proxy server and http server; this working etc. *relies* on compute_server having been created
             # However it can still serve many things without database.  TODO: Eventually it could inform user
             # that database isn't working.
@@ -3384,6 +3394,8 @@
             winston.debug("starting express webserver listening on #{program.host}:#{program.port}")
             http_server.listen(program.port, program.host, cb)
         (cb) ->
+            if not program.port
+                cb(); return
             async.parallel([
                 (cb) ->
                     # init authentication via passport (requires database)
@@ -3406,12 +3418,12 @@
             # Synchronous initialize of other functionality, now that the database, etc., are working.
             winston.debug("base_url='#{BASE_URL}'")

-            winston.debug("initializing primus websocket server")
-            init_primus_server(http_server)
-
-            winston.debug("initializing the http proxy server")
+            if program.port
+                winston.debug("initializing primus websocket server")
+                init_primus_server(http_server)

             if program.proxy_port
+                winston.debug("initializing the http proxy server on port #{program.proxy_port}")
                 hub_proxy.init_http_proxy_server
                     database       : database
                     compute_server : compute_server
@@ -3419,23 +3431,24 @@
                     port           : program.proxy_port
                     host           : program.host

-            # Start updating stats cache every so often -- note: this is cached in the database, so it isn't
-            # too big a problem if we call it too frequently.
-            # Randomized start to balance between all hubs.
-            # It's important that we call this periodically, or stats will only get stored to the
-            # database when somebody happens to visit /stats
-            d = 5000 + 60 * 1000 * Math.random()
-            setTimeout((-> database.get_stats(); setInterval(database.get_stats, 120*1000)), d)
-
-            # Register periodically with the hub.
-            hub_register.start
-                database   : database
-                clients    : clients
-                host       : program.host
-                port       : program.port
-                interval_s : REGISTER_INTERVAL_S
-
-            winston.info("Started hub. HTTP port #{program.port}; keyspace #{program.keyspace}")
+            if program.port
+                # Start updating stats cache every so often -- note: this is cached in the database, so it isn't
+                # too big a problem if we call it too frequently.
+                # Randomized start to balance between all hubs.
+                # It's important that we call this periodically, or stats will only get stored to the
+                # database when somebody happens to visit /stats
+                d = 5000 + 60 * 1000 * Math.random()
+                setTimeout((-> database.get_stats(); setInterval(database.get_stats, 120*1000)), d)
+
+                # Register periodically with the database.
+                hub_register.start
+                    database   : database
+                    clients    : clients
+                    host       : program.host
+                    port       : program.port
+                    interval_s : REGISTER_INTERVAL_S
+
+                winston.info("Started hub. HTTP port #{program.port}; keyspace #{program.keyspace}")
         cb?(err)
     )

@@ -3470,7 +3483,7 @@
 #############################################

 program.usage('[start/stop/restart/status/nodaemon] [options]')
-    .option('--port <n>', 'port to listen on (default: 5000)', ((n)->parseInt(n)), 5000)
+    .option('--port <n>', 'port to listen on (default: 5000; 0 -- do not start)', ((n)->parseInt(n)), 5000)
     .option('--proxy_port <n>', 'port that the proxy server listens on (default: 0 -- do not start)', ((n)->parseInt(n)), 0)
     .option('--log_level [level]', "log level (default: debug) useful options include INFO, WARNING and DEBUG", String, "debug")
     .option('--host [string]', 'host of interface to bind to (default: "127.0.0.1")', String, "127.0.0.1")
@@ -3534,7 +3547,7 @@
                  console.log("User added to project.")
             process.exit()
     else
-        console.log("Running web server; pidfile=#{program.pidfile}, port=#{program.port}, proxy_port=#{program.proxy_port}")
+        console.log("Running hub; pidfile=#{program.pidfile}, port=#{program.port}, proxy_port=#{program.proxy_port}")
         # logFile = /dev/null to prevent huge duplicated output that is already in program.logfile
         if program.foreground
             start_server (err) ->


Change variable for tick format on axis

Change variable for tick format on axis

--- src/controllers/Scatterplot.coffee
+++ src/controllers/Scatterplot.coffee
@@ -69,11 +69,11 @@

     @graph.xAxis
       .axisLabel(@prettyKey(@xAxisKey))
-      .tickFormat d3.format(@graph_options.x_tick_format)
+      .tickFormat d3.format(options.xAxisFormat)

     @graph.yAxis
       .axisLabel(@prettyKey(@yAxisKey))
-      .tickFormat d3.format(@graph_options.y_tick_format)
+      .tickFormat d3.format(options.yAxisFormat)

   render: =>
     @html require('../views/scatterplot')(@)


fix(patterns): Fix support for numeric values within string inputs

--- src/meshweaver.validators.coffee
+++ src/meshweaver.validators.coffee
@@ -1,10 +1,11 @@
 # Custom Validators for Backbone.Validation
 # Arana Software 2013-2015

+patterns = Backbone.Validation.patterns
 messages = Backbone.Validation.messages

 isNumber = (value) ->
-  return _.isNumber(value) or (_.isString(value) and value.match(@messages.number))
+  return _.isNumber(value) or (_.isString(value) and value.match(patterns.number))

 _.extend Backbone.Validation.validators,
   min: (value, attr, minValue, model, computed) ->


Don’t remove backup in finally block

Don’t remove backup in finally block

If there’s an error copying the backup file contents back to the
original file, we’ll want to leave the backup file in place.

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -1220,11 +1220,12 @@

     try
       @file.writeSync(@getText())
+      fs.removeSync(backupFilePath) if backupFilePath?
     catch error
-      fs.writeFileSync(filePath, fs.readFileSync(backupFilePath)) if backupFilePath?
+      if backupFilePath?
+        fs.writeFileSync(filePath, fs.readFileSync(backupFilePath))
+        fs.removeSync(backupFilePath)
       throw error
-    finally
-      fs.removeSync(backupFilePath) if backupFilePath?

     @cachedDiskContents = @getText()
     @conflict = false


:racehorse: Improve mergeIntersectingSelections performance

Order selections first and intersect only with the last one.
This brings the cost of mergeIntersectingSelections down to O(n log n).

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -2206,11 +2206,18 @@
       result = fn()
       @suppressSelectionMerging = false

+    findIntersectingSelection = (elements, selection) ->
+      return if elements.length == 0
+
+      otherSelection = _.last(elements)
+      exclusive = not selection.isEmpty() and not otherSelection.isEmpty()
+      intersects = otherSelection.intersectsWith(selection, exclusive)
+
+      return otherSelection if intersects
+
+
     reducer = (disjointSelections, selection) ->
-      intersectingSelection = _.find disjointSelections, (otherSelection) ->
-        exclusive = not selection.isEmpty() and not otherSelection.isEmpty()
-        intersects = otherSelection.intersectsWith(selection, exclusive)
-        intersects
+      intersectingSelection = findIntersectingSelection(disjointSelections, selection)

       if intersectingSelection?
         intersectingSelection.merge(selection, options)
@@ -2218,7 +2225,7 @@
       else
         disjointSelections.concat([selection])

-    _.reduce(@getSelections(), reducer, [])
+    _.reduce(@getSelectionsOrderedByBufferPosition(), reducer, [])

   # Add a {Selection} based on the given {Marker}.
   #


only add symbol to drawlist if we can draw it

--- src/ui/features.coffee
+++ src/ui/features.coffee
@@ -70,21 +70,21 @@
       length = segment.properties.length
       for n in d3.range(0, d3.round(length / interval))
         point = app.along(segment.geometry.coordinates, interval * (n + 0.5))
-        point.properties = {symbols: segment.properties.symbols}
-        if app.inside(point.geometry.coordinates, db.bbox)
-          xyproj = map.projection(point.geometry.coordinates)
-          x = d3.round(xyproj[0])
-          y = d3.round(xyproj[1])
-          if app.inside([x, y], [0, 0, map.width, map.height])
-            symbol = {properties: point.properties, x: x, y: y}
-            segmentSymbols.push(symbol)
+        if segment.properties.symbols
+          point.properties = {symbols: segment.properties.symbols}
+          if app.inside(point.geometry.coordinates, db.bbox)
+            xyproj = map.projection(point.geometry.coordinates)
+            x = d3.round(xyproj[0])
+            y = d3.round(xyproj[1])
+            if app.inside([x, y], [0, 0, map.width, map.height])
+              symbol = {properties: point.properties, x: x, y: y}
+              segmentSymbols.push(symbol)

     symbols.selectAll('.segmentSymbol').data(segmentSymbols)
       .enter().append('g')
         .attr('class', 'symbol segmentSymbol')
         .attr('transform', (d) -> "translate(#{d.x},#{d.y})")
         .each (d) ->
-          return unless d.properties.symbols
           for i in d3.range(0, d.properties.symbols.length)
             dx = - d3.round(13 / 2 * (d.properties.symbols.length - 1))
             g = d3.select(@).append('g')
@@ -97,16 +97,16 @@
       x = d3.round(xyproj[0])
       y = d3.round(xyproj[1])
       if x > 0 and x < map.width and y > 0 and y < map.height
-        symbol = {properties: poi.properties, x: x, y: y}
-        poiSymbols.push(symbol)
+        if app.symbol[poi.properties.type]
+          symbol = {properties: poi.properties, x: x, y: y}
+          poiSymbols.push(symbol)

     symbols.selectAll('.poiSymbol').data(poiSymbols)
       .enter().append('g')
         .attr('class', 'symbol poiSymbol')
         .attr('transform', (d) -> "translate(#{d.x},#{d.y})")
         .each (poi) ->
-          if (drawSymbol = app.symbol[poi.properties.type])?
-            drawSymbol(d3.select(@))
+          app.symbol[poi.properties.type](d3.select(@))

   map.dispatch.on 'redraw.features', ->
     render()


better way to handle empty results

better way to handle empty results

--- src/driver/driverCache.coffee
+++ src/driver/driverCache.coffee
@@ -109,10 +109,11 @@
     return

   _filterPutHelper: (condensedQuery, node, filter, level) ->
+    return unless node.prop?
+
     hashValue = @hashmap[filterToHash(filter)] ?= {}
     applies = condensedQuery[level].applies
     for apply in applies
-      continue if not node.prop?
       hashValue[apply.name] = node.prop[apply.name] ? hashValue[apply.name]

     if node.splits?
@@ -262,12 +263,10 @@
     combineOp = condensedQuery[level + 1]?.combine
     filterCacheResult = filterCache.get(filter)

-    return unless filterCacheResult?
-    return if filterCacheResult is {}
-
     prop = {}
-    for apply in applies
-      prop[apply.name] = filterCacheResult[apply.name]
+    if filterCacheResult?
+      for apply in applies
+        prop[apply.name] = filterCacheResult[apply.name]

     if not splitOp? # end case
       return {
@@ -293,7 +292,6 @@
     for value in cachedValues
       newFilter = addToFilter(filter, createFilter(value, splitOp))
       ret = getKnownTreeHelper(condensedQuery, newFilter, level + 1, value)
-      continue unless ret?
       ret.prop[splitOp.name] = value
       splits.push ret

@@ -321,6 +319,12 @@
   getKnownTree = (condensedQuery) ->
     return getKnownTreeHelper(condensedQuery, condensedQuery[0].filter, 0)

+  convertEmptyTreeToEmptyObject = (tree) ->
+    propKeys = (key for key, value of tree.prop)
+    return {} if (propKeys.length is 0 and not tree.splits?)
+    return tree
+
+
   return (request, callback) ->
     throw new Error("request not supplied") unless request
     {context, query} = request
@@ -349,7 +353,8 @@

       splitCache.put(condensedQuery, root)
       filterCache.put(condensedQuery, root)
-      callback(null, getKnownTree(condensedQuery))
+      knownTree = convertEmptyTreeToEmptyObject(getKnownTree(condensedQuery))
+      callback(null, knownTree)
     return


Changement de la couleur de la balle

Changement de la couleur de la balle

--- js/game/entities/projectiles/Bullet.coffee
+++ js/game/entities/projectiles/Bullet.coffee
@@ -47,7 +47,7 @@
             @ctx.translate(@x + @length*.5, @y)
             @ctx.rotate(@angle/3*15 * Math.PI/180)
             @ctx.translate(floor(-@length*.5), 0)
-            @ctx.fillStyle = 'rgba(255, 216, 0, 0.8)'
+            @ctx.fillStyle = 'rgba(255, 157, 0, 0.8)'
             @ctx.fillRect(0, 0, @length, 3)
             @ctx.restore()


Deprecation notices when using Jax.LightSource

--- lib/assets/javascripts/jax/light.js.coffee
+++ lib/assets/javascripts/jax/light.js.coffee
@@ -106,4 +106,22 @@
 # For legacy compatibility
 # TODO remove these
 Jax.Scene or= {}
-Jax.Scene.LightSource = Jax.Light
+class Jax.Scene.LightSource extends Jax.Light
+  notice = ->
+    console.log 'Please note that Jax.LightSource has been deprecated; it is now just Jax.Light.'
+    console.log '(You should also rename `app/assets/jax/resources/light_sources` to ' + \
+                '`app/assets/jax/resources/lights`.)'
+
+  constructor: (options) ->
+    notice()
+    super options
+
+  @find: ->
+    notice()
+    Jax.Light.find arguments...
+
+  @addResources: ->
+    notice()
+    Jax.Light.addResources arguments...
+
+Jax.getGlobal().LightSource = Jax.LightSource = Jax.Scene.LightSource


Fix typo in profile.coffee comment

Fix typo in profile.coffee comment

--- main/static/src/script/site/profile.coffee
+++ main/static/src/script/site/profile.coffee
@@ -1,2 +1,2 @@
 window.init_profile = ->
-  # do something with the admin page here
+  # do something with the profile page here


Fixed timepreserve bug on HCalendarPulldown

--- js/datetime/calendar_pulldown/calendar_pulldown.coffee
+++ js/datetime/calendar_pulldown/calendar_pulldown.coffee
@@ -33,10 +33,21 @@
     if @calendar?
       @calendar.valueObj.release( @calendar )
       @valueObj.bind(@calendar)
+
   refreshValue: ->
-    _date = moment(@value).utc()
+    _date = moment(@value * 1000).utc()
     @_timePreserve = [ _date.hours(), _date.minutes(), _date.seconds() ] if @options.preserveTime
     @calendar.setValue(@value)
+
+  setValue: ( _value )->
+    _date = moment(_value*1000).utc()
+    if @options.preserveTime and @_timePreserve?
+      [ _hours, _minutes, _seconds ] = @_timePreserve
+      _date.hours( _hours )
+      _date.minutes( _minutes )
+      _date.seconds( _seconds )
+    @base( _date.unix() )
+
   drawSubviews: ->
     @menuItemView = HView.new( @calendarRect(), @app,
       visible: false


customscroll: key binding for command+up/down

--- lib/components/scrollview/customscrollviewinner.coffee
+++ lib/components/scrollview/customscrollviewinner.coffee
@@ -13,6 +13,8 @@
   PAGEDOWN  = 34
   END       = 35
   HOME      = 36
+  UPARROW   = 38
+  DOWNARROW = 40

   constructor: (options = {}, data) ->

@@ -166,6 +168,9 @@
     shouldPropagate = no
     if event.which is SPACEBAR and event.shiftKey
       @pageUp()
+    else if event.metaKey or event.ctrlKey
+      @scrollTo top : 0  if event.which is UPARROW
+      @scrollToBottom()  if event.which is DOWNARROW
     else
       switch event.which
         when PAGEUP then @pageUp()


add download url invalidation

add download url invalidation


Stub connection in Doc tests

Stub connection in Doc tests

--- test/doc.coffee
+++ test/doc.coffee
@@ -8,6 +8,7 @@
   textType = ottypes['http://sharejs.org/types/textv1']
   numberType = require('./support/ot_number')

+  # TODO Use the real Connection class and stub things out on demand
   connection =
     state: 'connected'
     sent: []
@@ -17,6 +18,10 @@
       msg.src = @id
       msg.seq = @seq
       @send(msg)
+    sendSubscribe: (collection, name, version)->
+      msg =  c:collection, d:name, src: @id, seq: @seq, a:'sub'
+      msg.v = version if version
+      @send msg
     id: '42'
     seq: 0

@@ -47,7 +52,9 @@

     it 'sends subscription message', ->
       @doc.subscribe()
-      expect(connection.sent[0]).to.deep.equal {c: 'notes', d: 'music', a: 'sub'}
+      expect(connection.sent[0].c).to.equal 'notes'
+      expect(connection.sent[0].d).to.equal 'music'
+      expect(connection.sent[0].a).to.equal 'sub'

     it 'retrives snapshot', ->
       @doc.subscribe()


Reload mediaGroup when sorting / saving media

--- src/persistence.coffee
+++ src/persistence.coffee
@@ -553,6 +553,31 @@
         ).then -> result
       ).callback callback

+
+  # Reload teamMediaGroup when saving teamMedium.
+  wrapMethod sdk, 'saveTeamMedium', (saveTeamMedium) ->
+    (teamMedium, callback) ->
+      teamMediaGroupIds = [teamMedium.teamMediaGroupId]
+      # If we're chaning teamMediaGroupId via save.
+
+      unless teamMedium._state?.teamMediaGroupId? is teamMedium.teamMediaGroupId
+        teamMediaGroupIds.push teamMedium._state.teamMediaGroupId
+
+      saveTeamMedium.call(this, teamMedium).then((result) ->
+        teamId = teamMedium.teamId
+        sdk.loadTeamMediaGroups(id: teamMediaGroupIds)
+      ).callback callback
+
+
+
+  # Reload teamMediaGroup when saving video link.
+  wrapMethod sdk, 'saveTeamVideoLink', (saveTeamVideoLink) ->
+    (teamMedium, callback) ->
+      saveTeamVideoLink.call(this, teamMedium).then((result) ->
+        sdk.loadTeamMediaGroups(id: teamMedium.teamMediaGroupId)
+      ).callback callback
+
+
   # Remove comments when deleting teamMedium, reload team (for file usage quota)
   wrapMethod sdk, 'deleteTeamMedium', (deleteTeamMedium) ->
     (teamMedium, callback) ->
@@ -579,11 +604,30 @@

       bulkDeleteTeamMedia.call(this, teamMediumIds, callback).then((result) ->
         if typeof teamId is 'string' or typeof teamId is 'number'
-          sdk.loadTeam(teamId).then ->
-            result
+          promises.when(
+            sdk.loadTeam teamId
+            sdk.loadTeamMediaGroups(teamId: teamId)
+          ).then -> result
         else
           result
-      )
+      ).callback callback
+
+
+  # Note: For the purposes of persistence, this method will accept a
+  #teamMediaGroupId, even though it isn't accepted in the actual method.
+  wrapMethod sdk, 'reorderTeamMedia', (reorderTeamMedia) ->
+    (teamId, teamMediaIds, teamMediaGroupId, callback) ->
+      params = teamId: teamId
+      if typeof teamMediaGroupId is 'function'
+        callback = teamMediaGroupId
+
+      if typeof teamMediaGroupId is 'string' or
+      typeof teamMediaGroupId is 'number'
+        params = id: teamMediaGroupId
+
+      reorderTeamMedia.call(this, teamId, teamMediaIds).then((result) ->
+        sdk.loadTeamMediaGroups(params).then -> result
+      ).callback callback

   # Remove all records belonging to a team when it is deleted


Updated standup to :standup:

Updated standup to :standup:

--- src/philips-hue.coffee
+++ src/philips-hue.coffee
@@ -174,7 +174,7 @@
         return handleError msg, err if err
         robot.logger.debug status

-  robot.hear /standup$/, (msg) ->
+  robot.hear /:standup:/, (msg) ->
     alertState = lightState.create().alertShort()
     api.setGroupLightState 1, alertState, (err, status) ->
       return handleError msg, err if err


Remove duplicate

Remove duplicate

--- src/lib/InternalSocket.coffee
+++ src/lib/InternalSocket.coffee
@@ -167,8 +167,6 @@

     # Wrap legacy events into appropriate IP objects
     switch event
-      when 'connect'
-        return new IP 'openBracket', null
       when 'connect', 'begingroup'
         return new IP 'openBracket', payload
       when 'disconnect', 'endgroup'


Poorly documenting Theoricus class, as a initial demo/guide for writing the complete docs for everything.

--- www/src/theoricus/theoricus.coffee
+++ www/src/theoricus/theoricus.coffee
@@ -1,3 +1,8 @@
+###*
+  Theoricus module
+  @module theoricus
+###
+
 Config = require 'theoricus/config/config'
 Factory = require 'theoricus/core/factory'
 Processes = require 'theoricus/core/processes'
@@ -6,24 +11,54 @@
 require 'jquery'
 require 'json'

+
+###*
+  Theoricus main class.
+  @class Theoricus
+###
 module.exports = class Theoricus
-  app: null

-  # base path for your application
-  # in case it runs in a subfolder
+  ###*
+    Base path for your application, in case it runs in a subfolder. If not, this
+    can be left blank, meaning your application will run in the `web_root` dir
+    on your server.
+
+    @property {String} base_path
+  ###
   base_path: ''

-  root: null
+  ###*
+    Instance of Fatory class.
+    @property {Factory} factory
+  ###
+  factory  : null

-  factory  : null
+  ###*
+    Instance of Config class, fed by the Settings class.
+    @property {Config} config
+  ###
   config   : null
+
+  ###*
+    Instance of Processes class.
+    @property {Processes} processes
+  ###
   processes: null

-  crawler: (window.crawler = is_rendered: false)
-
+  ###*
+    Theoricus constructor, must to be invoked by the application with a `super`
+    call.
+    @method constructor
+    @param @Settings {Object} App Settings
+    @param @Routes {Object} App Routes
+  ###
   constructor:( @Settings, @Routes )->
     @config  = new Config @, @Settings
     @factory = new Factory @

+  ###*
+    Starts the Theoricus engine, plugging the Processes onto the Router system.
+    @method start
+  ###
   start: ->
     @processes = new Processes @, @Routes


handle uploading errors. fixes #59

handle uploading errors. fixes #59

--- src/server/plugins/upload.coffee
+++ src/server/plugins/upload.coffee
@@ -55,12 +55,15 @@
         fs.unlink(temp_file)
       cleanAndLogIfErr = (err) =>
         if err
-          console.error "Unable to import by URL. Error:", err, "URL:", url_string
+          console.error "Unable to import by URL. Error:", err.stack, "URL:", url_string
         cleanUp()
-      pipe = request(url_string).pipe(fs.createWriteStream(temp_file))
-      pipe.on 'close', =>
+      req = request(url_string)
+      ws = fs.createWriteStream(temp_file)
+      req.pipe(ws)
+      ws.on 'close', =>
         @importFile temp_file, remote_filename, cleanAndLogIfErr
-      pipe.on 'error', cleanAndLogIfErr
+      ws.on 'error', cleanAndLogIfErr
+      req.on 'error', cleanAndLogIfErr

   importFile: (temp_file, remote_filename, cb=->) =>


Got a kind of step system working

Got a kind of step system working

--- client/src/Tutorial.coffee
+++ client/src/Tutorial.coffee
@@ -1,9 +1,13 @@
 class Tutorial
   @window: undefined
   @windowOptions = undefined
+  @steps = undefined
+  @stepIds = undefined
+  @nextStepIndex = 0
   @init : ->
     if $('#tutorial-window').length > 0 then $('#tutorial-window').remove()
-
+    @steps = []
+    @stepIds = []
     @windowOptions =
       backdrop: false
       keyboard: false
@@ -36,4 +40,25 @@

   @show: -> $(@window).modal('show')
   @hide: -> $(@window).modal('hide')
-  @toggle: -> $(@window).modal('toggle')+  @toggle: -> $(@window).modal('toggle')
+
+  @addStep: ({id, header, content, tipselector, tipmessage, modal}) ->
+    index = @steps.push {id: id, header: header, content: content, tipselector: tipselector, tipmessage: tipmessage, modal: modal}
+    if id? then @stepIds[id] = index - 1
+
+  @doStep: (stepId) ->
+    nextIndex = if stepId? then @stepIds[stepId] else @nextStepIndex
+    theStep = @steps[nextIndex]
+    if theStep?
+      {id, header, content, tipselector, tipmessage, modal} = theStep
+      if header? then @changeHeader(header)
+      if content? then @changeContent(content)
+      if tipselector?
+        # TODO: The element needs to be selected
+        $(tipselector).addClass('glow')
+        # TODO: The message needs to be displayed
+        if tipmessage? then alert tipmessage
+      if modal?
+        if modal then @show() else @hide()
+      @steps[nextIndex] = undefined
+      @nextStepIndex++ until @steps[@nextStepIndex]? or @nextStepIndex is @steps.length


Add notification action

Add notification action

--- app/assets/javascripts/actions/notification_actions.coffee
+++ app/assets/javascripts/actions/notification_actions.coffee
@@ -4,5 +4,7 @@
 NotificationActions = Flux.createActions
   removeNotification: (notification) ->
     { actionType: 'REMOVE_NOTIFICATION', notification: notification }
+  addNotification: (notification) ->
+    { actionType: 'ADD_NOTIFICATION', notification: notification }

 module.exports = NotificationActions


[Cartilage.Views.ListView] Fixed multiple selection in list views when using the meta key.

--- app/assets/javascripts/cartilage/views/list_view.js.coffee
+++ app/assets/javascripts/cartilage/views/list_view.js.coffee
@@ -168,7 +168,7 @@

     if event.metaKey
       element = event.target
-      @selectAnother element
+      @selectAnother ($ element).parents("li") || element
       event.preventDefault()

     else if event.shiftKey


Fix issue where meta-click on li in multi-select ListView picks the wrong target element.

--- app/assets/javascripts/cartilage/views/list_view.js.coffee
+++ app/assets/javascripts/cartilage/views/list_view.js.coffee
@@ -242,7 +242,7 @@
   #
   onMouseDown: (event) =>
     # Get the list item element
-    element = ($ event.target).parents("li") || event.target
+    element = if ($ event.target).is("li") then event.target else ($ event.target).parents("li")

     # Clear the selection if the user clicks in the list container.
     @clearSelection() if @allowsDeselection and event.target.tagName == "UL"


Extract `Cart.current` to `order` variable

--- app/assets/javascripts/sprangular/services/checkout.coffee
+++ app/assets/javascripts/sprangular/services/checkout.coffee
@@ -110,13 +110,14 @@
       ga "ecommerce:send"

     put: (params={}, config={}) ->
-      url = "/spree/api/checkouts/#{Cart.current.number}"
+      order = Cart.current
+      url = "/spree/api/checkouts/#{order.number}"

       config.headers =
-        'X-Spree-Order-Token': Cart.current.token
+        'X-Spree-Order-Token': order.token

-      Cart.current.errors = null
-      Cart.current.loading = true
+      order.errors = null
+      order.loading = true

       deferred = $q.defer()


Fix typo in DueDateOverride

Fix typo in DueDateOverride

"selected" instead of "seleted"

Fixes CNVS-22454

No Test plan needed

Change-Id: I17360803349bdf7906151fba79493e2a635d382b
Reviewed-on: https://gerrit.instructure.com/60615
Tested-by: Jenkins
Reviewed-by: Matt Berns <e53c8988cd428dde240324ce2797c8eb7073560f@instructure.com>
Product-Review: Benjamin Porter <fdd60f2e3d506934848b3d0fff79c55f544a2d47@instructure.com>
QA-Review: Benjamin Porter <fdd60f2e3d506934848b3d0fff79c55f544a2d47@instructure.com>

--- app/coffeescripts/views/assignments/DueDateOverride.coffee
+++ app/coffeescripts/views/assignments/DueDateOverride.coffee
@@ -50,7 +50,7 @@

     validateTokenInput: (data, errors) =>
       validRowKeys = _.pluck(data.assignment_overrides, "rowKey")
-      blankOverrideMsg = I18n.t('blank_override', 'You must have a student or section seleted')
+      blankOverrideMsg = I18n.t('blank_override', 'You must have a student or section selected')
       for row in $('.Container__DueDateRow-item')
         rowKey = "#{$(row).data('row-key')}"
         continue if _.contains(validRowKeys, rowKey)


Add options to changePage

Add options to changePage

--- lib/assets/javascripts/turbograft/turbolinks.coffee
+++ lib/assets/javascripts/turbograft/turbolinks.coffee
@@ -114,7 +114,7 @@

     if doc = processResponse(xhr, options.partialReplace)
       reflectNewUrl url
-      nodes = changePage(extractTitleAndBody(doc)..., options.partialReplace, options.onlyKeys, options.exceptKeys)
+      nodes = changePage(extractTitleAndBody(doc)..., options)
       reflectRedirectedUrl(xhr)
       triggerEvent 'page:load', nodes
       options.onLoadFunction?()
@@ -123,19 +123,19 @@

     return

-  changePage = (title, body, csrfToken, runScripts, partialReplace, onlyKeys = [], exceptKeys = []) ->
+  changePage = (title, body, csrfToken, runScripts, options = {}) ->
     document.title = title if title

-    if onlyKeys.length
-      nodesToRefresh = [].concat(getNodesWithRefreshAlways(), getNodesMatchingRefreshKeys(onlyKeys))
+    if options.onlyKeys.length
+      nodesToRefresh = [].concat(getNodesWithRefreshAlways(), getNodesMatchingRefreshKeys(options.onlyKeys))
       nodes = refreshNodes(nodesToRefresh, body)
       setAutofocusElement() if anyAutofocusElement(nodes)
       return nodes
     else
       refreshNodes(getNodesWithRefreshAlways(), body)
       persistStaticElements(body)
-      if exceptKeys.length
-        refreshAllExceptWithKeys(exceptKeys, body)
+      if options.exceptKeys.length
+        refreshAllExceptWithKeys(options.exceptKeys, body)
       else
         deleteRefreshNeverNodes(body)


Removed reference to sys module. (Thanks @dmarcos)

--- src/server/rest.coffee
+++ src/server/rest.coffee
@@ -3,7 +3,6 @@
 # See the docs for details and examples about how the protocol works.

 http = require 'http'
-sys = require 'sys'
 url = require 'url'

 connect = require 'connect'


Fix case when selection is at the beginning or end of a line

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -942,14 +942,15 @@
       for selection in selections
         charToLeftOfSelection = new Range(selection.start.translate(translationDelta), selection.start)

-        insertionPoint = selection.end
-        text = @buffer.getTextInRange(charToLeftOfSelection)
-
-        @buffer.insert(insertionPoint, text)
-        @buffer.delete(charToLeftOfSelection)
-        translatedRanges.push(selection.translate(translationDelta))
-
-      @setSelectedBufferRanges(translatedRanges)
+        unless charToLeftOfSelection.start.column < 0
+          insertionPoint = selection.end
+          text = @buffer.getTextInRange(charToLeftOfSelection)
+
+          @buffer.insert(insertionPoint, text)
+          @buffer.delete(charToLeftOfSelection)
+          translatedRanges.push(selection.translate(translationDelta))
+
+      @setSelectedBufferRanges(translatedRanges) if translatedRanges.length > 0

   moveSelectionRight: ->
     selections = @getSelectedBufferRanges()
@@ -961,14 +962,15 @@
       for selection in selections
         charToRightOfSelection = new Range(selection.end, selection.end.translate(translationDelta))

-        insertionPoint = selection.start
-        text = @buffer.getTextInRange(charToRightOfSelection)
-
-        @buffer.delete(charToRightOfSelection)
-        @buffer.insert(insertionPoint, text)
-        translatedRanges.push(selection.translate(translationDelta))
-
-      @setSelectedBufferRanges(translatedRanges)
+        unless charToRightOfSelection.end.column > @buffer.lineLengthForRow(charToRightOfSelection.end.row)
+          insertionPoint = selection.start
+          text = @buffer.getTextInRange(charToRightOfSelection)
+
+          @buffer.delete(charToRightOfSelection)
+          @buffer.insert(insertionPoint, text)
+          translatedRanges.push(selection.translate(translationDelta))
+
+      @setSelectedBufferRanges(translatedRanges) if translatedRanges.length > 0

   # Duplicate the most recent cursor's current line.
   duplicateLines: ->


set 'for=' attribute on labels

set 'for=' attribute on labels

--- src/ui/features.coffee
+++ src/ui/features.coffee
@@ -169,6 +169,7 @@
           g = symbols.append('g')
               .attr('class', 'symbolLabel')
               .attr('transform', "translate(#{tx - thw},#{ty - thh - size.dy})")
+              .attr('for', symbol.id)
           app.symbol.textWithHalo(g, name)
           qt.add(x: tx, y: ty, hw: thw, hh: thh, labelFor: symbol)


Fix toolbar selection

Fix toolbar selection

--- coffee/core/toolbar.coffee
+++ coffee/core/toolbar.coffee
@@ -104,20 +104,28 @@
       @lc.redo()

   initTools: ->
-    @tools = (new ToolClass(@opts) for ToolClass in @opts.toolClasses)
-    for t in @tools
-      optsEl = $("

")
-      optsEl.html(t.options())
-      optsEl.hide()
-      t.$el = optsEl
-      @$el.find('.tool-options-container').append(optsEl)
+    @tools = []
+    for ToolClass in @opts.toolClasses
+      t = new ToolClass(@opts)
+      @tools.push(t)
+      @addTool(t)

-      buttonEl = $("

")
-      buttonEl.find('.tool-image-wrapper').html(t.button())
-      @$el.find('.tools').append(buttonEl)
+  addTool: (t) ->
+    optsEl = $("

")
+    optsEl.html(t.options())
+    optsEl.hide()
+    t.$el = optsEl
+    @$el.find('.tool-options-container').append(optsEl)

-      buttonEl.click (e) =>
-        @selectTool(t)
+    buttonEl = $("

+

")
+      .appendTo(@$el.find('.tools'))
+      .find('.tool-image-wrapper')
+      .html(t.button())
+
+    buttonEl.click (e) =>
+      @selectTool(t)
+    null

   initZoom: ->
     @$el.find('.zoom-in-button').click (e) =>


Better text selection behavior

Better text selection behavior

--- coffee/core/widgets.coffee
+++ coffee/core/widgets.coffee
@@ -152,14 +152,19 @@

     @$input.change => @updateTool()
     @$input.keyup => @updateTool()
-    $fontSize.change => @updateTool()
+    $fontSize.change =>
+      @updateTool()
+      @$input.focus()
     $fontSize.keyup => @updateTool()
-    @$el.find('input#italic').change => @updateTool()
-    @$el.find('input#bold').change => @updateTool()
+    @$el.find('input#italic').change =>
+      @updateTool()
+      @$input.focus()
+    @$el.find('input#bold').change =>
+      @updateTool()
+      @$input.focus()
     @$el.find('#family').change =>
       @updateTool()
       @$input.focus()
-      @$input.select()

     @$el


store layout, not event

store layout, not event

--- src/tex-writer-mkts-table.coffee
+++ src/tex-writer-mkts-table.coffee
@@ -47,9 +47,7 @@
   L = new_local_state()
   return D.TEE.from_pipeline [
     @$parse_description               S, L
-    @$store_layout_event              S, L
     @$handle_content_events           S, L
-    # D.$observe ( event ) -> info '23993', ( CND.grey '--------->' ), jr event
     @$handle_fields                   S, L
     @$dump_table_layout               S, L
     ]
@@ -61,7 +59,7 @@
 new_local_state = ->
   R =
     selectors_and_content_events:   {}
-    layout_events:                  {}
+    layouts:                        {}
     content_buffer:                 null
     within_field:                   false
     layout_name_stack:              []
@@ -107,31 +105,33 @@

 #-----------------------------------------------------------------------------------------------------------
 @layout_from_name = ( S, L, layout_name ) ->
-  unless ( R = L.layout_events[ layout_name ] )?
+  unless ( R = L.layouts[ layout_name ] )?
     throw new Error "#{badge} µ47753 unknown layout #{rpr layout_name}"
-  return R[ 2 ]
+  return R

 #-----------------------------------------------------------------------------------------------------------
 @push_layout_name = ( S, L, layout_name ) ->
-  unless L.layout_events[ layout_name ]?
+  unless L.layouts[ layout_name ]?
     throw new Error "#{badge} µ42558 unknown layout #{rpr layout_name}"
   L.layout_name_stack.push layout_name
   return null

 #-----------------------------------------------------------------------------------------------------------
-@store_layout_event = ( S, L, event ) ->
-  [ _, _, layout, _, ] = event
-  if L.layout_events[ layout.name ]?
+@store_layout = ( S, L, layout ) ->
+  if L.layouts[ layout.name ]?
     throw new Error "#{badge} µ36339 refusing to re-define layout #{rpr layout.name}"
-  L.layout_events[ layout.name ] = event
+  L.layouts[ layout.name ] = layout
   @_initialize_layout S, L, layout.name
   return null

 #-----------------------------------------------------------------------------------------------------------
 @copy_layout = ( S, L, old_layout_name, new_layout_name ) ->
-  if L.layout_events[ new_layout_name ]?
+  if L.layouts[ new_layout_name ]?
     throw new Error "#{badge} µ36339 refusing to re-define layout #{rpr new_layout_name}"
-  L.layout_events[ new_layout_name ] = event
+  old_layout      = @layout_from_name S, L, old_layout_name
+  new_layout      = CND.deep_copy old_layout
+  new_layout.name = new_layout_name
+  @store_layout S, L, new_layout
   @_initialize_layout S, L, new_layout_name
   return null

@@ -203,23 +203,12 @@
         warn "an error occurred"
         throw error
       send stamp event
-      send [ '.', 'MKTS/TABLE/layout', layout, ( copy meta ), ]
+      layout.meta = copy meta
+      @store_layout S, L, layout
     #.......................................................................................................
     else
       send event
     #.......................................................................................................
-    return null
-
-#-----------------------------------------------------------------------------------------------------------
-@$store_layout_event = ( S, L ) ->
-  #.........................................................................................................
-  return $ ( event, send ) =>
-    if select event, '.', 'MKTS/TABLE/layout'
-      @store_layout_event S, L, event
-      # debug '66533', L; process.exit 1
-      send stamp event
-    else
-      send event
     return null

 #-----------------------------------------------------------------------------------------------------------


fixing filename regexp and a critical bug in the reorder process

--- src/toaster/grains/script.coffee
+++ src/toaster/grains/script.coffee
@@ -214,7 +214,7 @@

 				# assemble some information about the file
 				filepath   = file.replace( @src, "" ).substr 1
-				filename   = /\w+\.\w+/.exec( filepath )[ 0 ]
+				filename   = /[\w-]+\.[\w-]+/.exec( filepath )[ 0 ]
 				filefolder = filepath.replace( "/#{filename}", "") + "/"
 				namespace  = ""
 				# if the file is in the top level
@@ -356,33 +356,28 @@
 		# no other methods call it with cycling = true
 		@missing = {} if cycling is false

-		# initialized files array, will cache every file as it is read
-		initd = []
-
 		# looping through all files
 		for file, i in files

-			# mark file as initialized
-			initd.push file
-
 			# if theres no dependencies, go to next file
 			continue if !file.dependencies.length && !file.baseclasses.length

 			# otherwise loop thourgh all file dependencies
 			for filepath, index in file.dependencies

+				# search for dependency
+				dependency = ArrayUtil.find files, filepath, "filepath"
+				dependency_index = dependency.index if dependency?
+
 				# continue if the dependency was already initialized
-				continue if ArrayUtil.has initd, filepath, "filepath"
-
-				# otherwise search by the dependency
-				found = ArrayUtil.find files, filepath, "filepath"
+				continue if dependency_index < i && dependency?

 				# if it's found
-				if found?
+				if dependency?

 					# if there's some circular dependency loop
 					if ArrayUtil.has(
-						found.item.dependencies,
+						dependency.item.dependencies,
 						file.filepath,
 						"filepath"
 					)
@@ -400,9 +395,10 @@
 					# the specific dependency and run reorder recursively until
 					# everything is beautiful
 					else
-						files.splice index, 0, found.item
-						files.splice found.index + 1, 1
+						files.splice index, 0, dependency.item
+						files.splice dependency.index + 1, 1
 						files = @reorder files, true
+						break

 				# otherwise if the dependency is not found
 				else if @missing[filepath] != true


activity: remove fake item before inserting real item

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -123,10 +123,7 @@

   replaceFakeItemView: (message) ->

-    index = @listController.getListView().getItemIndex @fakeMessageMap[message.clientRequestId]
-    item  = @putMessage message, index
-
-    @removeFakeMessage message.clientRequestId
+    item = super message
     @scrollDown item


Improve cucumber steps a bit.

Improve cucumber steps a bit.

--- features/step_definitions/steps.coffee
+++ features/step_definitions/steps.coffee
@@ -1,16 +1,24 @@
 Qjs          = require '../../lib/qjs'
 should       = require 'should'
-TestSystem   = require '../support/test_system'

 # Global variables for steps below
-result = null
-system = TestSystem
-type   = null
+TestSystem  = null
+result      = null
+system      = null
+type        = null

 module.exports = ->

+  this.Before (callback)->
+    TestSystem ?= require '../support/test_system'
+    system = TestSystem
+    callback()
+
   this.Given /^the System is$/, (source, callback) ->
-    system = TestSystem.parse(source)
+    try
+      system = TestSystem.parse(source)
+    catch e
+      callback.fail(e)

     callback()

@@ -71,7 +79,7 @@

   this.Then /^its '(.*)' attribute should be a Date representation$/, (attr, callback) ->
     unless result[attr] instanceof Date
-      callback.fail new Error("attribute is not a Date")
+      callback.fail new Error("attribute is not a Date, got #{result[attr]}")
     callback()

   this.Then /^the result should be a representation for Nil$/, (callback) ->


MainController: fix mistakenly deleted lines

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -66,6 +66,9 @@
   accountChanged:(account, firstLoad = no)->
     @userAccount             = account
     connectedState.connected = yes
+
+    @on "pageLoaded.as.loggedIn", (account)-> # ignore othter parameters
+      KD.utils.setPreferredDomain account if account

     account.fetchMyPermissionsAndRoles (err, permissions, roles)=>
       return warn err  if err


Adds test

Adds test

--- client/apps/edit/components/thumbnail/test/index.coffee
+++ client/apps/edit/components/thumbnail/test/index.coffee
@@ -50,3 +50,10 @@
       @view.$('.edit-title-textarea').val('foo')
       @view.checkTitleTextarea()
       @view.$('.edit-use-article-title').attr('style').should.containEql 'display: none'
+
+  describe '#updateCharCount', ->
+
+    it 'updates the count when adding text', ->
+      @view.$('.edit-title-textarea').val('Title')
+      @view.updateCharCount()
+      @view.$('.edit-char-count').text().should.containEql '92'


login: provide a hook to set custom data to multiple forms and elements

--- client/landing/site.landing/coffee/login/AppView.coffee
+++ client/landing/site.landing/coffee/login/AppView.coffee
@@ -565,6 +565,14 @@
     @[formName].addCustomData data
     # @resetForm.addCustomData {recoveryToken}

+  setCustomData: (data) ->
+
+    @setCustomDataToForm 'login', data
+    @setCustomDataToForm 'register', data
+
+    @setFormHeaderPartial data
+
+
   getRegisterLink: (data = {}) ->

     queryString = KD.utils.stringifyQuery data


Use dot notation to read hash

Use dot notation to read hash

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -15,7 +15,7 @@
       $('body').scrollTo(@last_profile_status.scrollTop) if view == @last_profile_status?.view
       delete @last_profile_status
     @profile_views.clearUnshowedViews()
-
+
     @profile_views.renderCacheView( username, new_callback() ) unless view?

   # ACTIONS
@@ -93,7 +93,7 @@
         model: channel

   withFact: (fact, params={})->
-    @main.contentRegion.show new DiscussionView(model: fact, tab: params['tab'])
+    @main.contentRegion.show new DiscussionView(model: fact, tab: params.tab)

     username = fact.get('created_by').username
     return_to_text = "#{ username.capitalize() }'s profile"


KDEventEmitter: #emit should return "this"

--- core/KDEventEmitter.coffee
+++ core/KDEventEmitter.coffee
@@ -102,6 +102,8 @@
     listenerStack = listenerStack.concat @_e[eventName].slice(0)
     listenerStack.forEach (listener)=> listener.apply @, args

+    return this
+
   on  :(eventName, listener) ->
     unless 'function' is typeof listener
       throw new Error 'listener is not a function'


Remove redundant argument to biscotto

Remove redundant argument to biscotto

--- tasks/docs-task.coffee
+++ tasks/docs-task.coffee
@@ -10,7 +10,7 @@
     grunt.task.run('markdown:guides')

     done = @async()
-    args = [commonArgs..., '--name', 'Atom', '--title', 'Atom API Documentation', '-o', 'docs/output/api', 'src/']
+    args = [commonArgs..., '--title', 'Atom API Documentation', '-o', 'docs/output/api', 'src/']
     grunt.util.spawn({cmd, args, opts}, done)

   grunt.registerTask 'lint-docs', 'Generate stats about the doc coverage', ->


atom: Disable some keymaps

atom: Disable some keymaps

--- atom/config.cson
+++ atom/config.cson
@@ -6,11 +6,10 @@
       "welcome"
       "metrics"
     ]
+    packagesWithKeymapsDisabled: [
+      "highlight-line"
+    ]
     telemetryConsent: "no"
-    themes: [
-      "one-dark-ui"
-      "solarized-dark-syntax"
-    ]
   editor:
     fontFamily: "Monaco"
     fontSize: 15


Tweak the font size a little for the new font

--- atom/config.cson
+++ atom/config.cson
@@ -59,7 +59,7 @@
   editor:
     backUpBeforeSaving: true
     fontFamily: "Input Mono Narrow"
-    fontSize: 16
+    fontSize: 15
     invisibles:
       cr: "↩"
       eol: " "
@@ -114,7 +114,6 @@
   "tree-view":
     hideIgnoredNames: true
     hideVcsIgnoredFiles: true
-    squashDirectoryNames: true
   welcome:
     showOnStartup: false
   "white-cursor":


open atom search result in right pane

open atom search result in right pane

--- atom/config.cson
+++ atom/config.cson
@@ -28,7 +28,7 @@
   "exception-reporting":
     userId: "0602831a-299e-5b96-11c6-326ec89bbb33"
   "find-and-replace":
-    openProjectFindResultsInRightPane: true
+    projectSearchResultsPaneSplitDirection: "right"
   "fuzzy-finder":
     searchAllPanes: true
   "git-diff":


Add .map to ignored file list

Add .map to ignored file list

--- atom/config.cson
+++ atom/config.cson
@@ -44,6 +44,7 @@
       ".Spotlight-V100"
       ".fseventsd"
       ".Trashes"
+      "*.map"
     ]
     packagesWithKeymapsDisabled: [
       "emmet"


Update config.cson for Atom

Update config.cson for Atom

--- atom/config.cson
+++ atom/config.cson
@@ -1,4 +1,8 @@
 "*":
+  Zen:
+    softWrap: true
+    typewriter: true
+    width: 120
   "activate-power-mode":
     autoToggle: false
   "autocomplete-emojis":


Use default font size

Use default font size

--- atom/config.cson
+++ atom/config.cson
@@ -13,7 +13,6 @@
     'eol': ' '
   'normalizeIndentOnPaste': false
   'showInvisibles': true
-  'fontSize': 15
 'core':
   'projectHome': '/Volumes/Data/Users/Lee/Source'
   'disabledPackages': [


Change keybindings for resizing indentation

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -34,8 +34,8 @@
   # Resizes spaces-per-indent amount for entire document
   'ctrl-{': 'unset!'
   'ctrl-}': 'unset!'
-  'cmd-{': 'resize-indent:halve'
-  'cmd-}': 'resize-indent:double'
+  'ctrl-cmd-[': 'resize-indent:halve'
+  'ctrl-cmd-]': 'resize-indent:double'
   # Convert between tabs and spaces used for indentation
   'ctrl-alt-cmd-]': 'whitespace:convert-spaces-to-tabs'
   'ctrl-alt-cmd-[': 'whitespace:convert-tabs-to-spaces'


Add additional shortcut for go-to-line in Atom

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -26,3 +26,4 @@

 'atom-text-editor':
 	'ctrl-cmd-t': 'whitespace:convert-spaces-to-tabs'
+	'cmd-l': 'go-to-line:toggle'


background: commenting

background: commenting

--- cs/background.coffee
+++ cs/background.coffee
@@ -186,7 +186,7 @@

   loadAffiliationIcon()

-  # Reload the page once every day
+  # Reload the page once every day (in case the extension updates)
   setInterval ( ->
     document.location.reload()
   ), 86400000


added test

added test

--- test/index_test.coffee
+++ test/index_test.coffee
@@ -223,6 +223,18 @@
     )

+  it "should work with a default file loader and keep the relative file names" #, (done) ->
+
+    # checkExpectedFiles(
+    #   ["fuz/ahah.js", "duu.js"]
+    #   amdOptimize.src(
+    #     "duu"
+    #     baseUrl : "test/fixtures/core"
+    #   )
+    #   done
+    # )
+
+
   it "should work with a custom file loader", (done) ->

     checkExpectedFiles(
@@ -572,21 +584,22 @@
   it "should apply prefix paths #2",  (done) ->
     checkExpectedFiles(
       ["path/to/module/foo.js", "index.js"]
-      vinylfs.src(["#{dir}/fixtures/shortcuts/**/*.js"], { base: "#{dir}/fixtures/shortcuts" })
-      .pipe(amdOptimize(
+      vinylfs.src("#{dir}/fixtures/shortcuts/**/*.js")
+        .pipe(amdOptimize(
           "index"
           configFile : "#{dir}/fixtures/shortcuts/config.js"
-      ))
+        ))
       done
     )

   it "should apply prefix paths with loader",  (done) ->
     checkExpectedFiles(
-      ["path/to/module/foo.js", "index.js"]
+      ["foo.js", "index.js"]
       amdOptimize.src(
         "index"
         baseUrl : "#{dir}/fixtures/shortcuts"
+        configFile : "#{dir}/fixtures/shortcuts/config.js"
       )
       done
     )


Comment on speed

Comment on speed

--- test/speed_test.coffee
+++ test/speed_test.coffee
@@ -1,10 +1,9 @@
 redeye_suite = require './support/redeye_suite'
-
-start_time = null

 module.exports = redeye_suite

   # Tests that a pathological 1000-step case is still fast
+  # (usually clocks in at < 1ms per iteration. not bad!)
   'test result and audit log':

     workers:
@@ -15,12 +14,12 @@

     # Request a 1000-step job. Record when the request is made.
     setup: ->
-      start_time = new Date().getTime()
+      @start_time = new Date().getTime()
       @request 'n', 1000

     # Assert that the job didn't take a really long time.
     expect: ->
-      dt = new Date().getTime() - start_time
+      dt = new Date().getTime() - @start_time
       @db.get 'n:1000', (err, str) =>
         @assert.equal str, '1000'
         @assert.equal true,  (dt < 2000)


Changed: favicon is now always the same. Close #221

--- client/app/views/main.coffee
+++ client/app/views/main.coffee
@@ -43,9 +43,6 @@
         @frames = @$ '#app-frames'
         @content = @$ '#content'

-        @favicon = @$ 'fav1'
-        @favicon2 = @$ 'fav2'
-
         $(window).resize @resetLayoutSizes
         @apps.fetch reset: true
         @devices.fetch reset: true
@@ -81,7 +78,6 @@
             $('#home-content').append view.$el
             view.$el.fadeIn()
             @currentView = view
-            @changeFavicon "favicon.ico"
             @resetLayoutSizes()

         if @currentView?
@@ -89,7 +85,6 @@
             if view is @currentView
                 @frames.hide()
                 @content.show()
-                @changeFavicon "favicon.ico"
                 @resetLayoutSizes()
                 return

@@ -152,7 +147,6 @@
         name = '' if not name?
         window.document.title = "Cozy - #{name}"
         $("#current-application").html name
-        @changeFavicon "/apps/#{slug}/favicon.ico"
         @resetLayoutSizes()

     createApplicationIframe: (slug, hash="") ->
@@ -170,14 +164,6 @@
             app?.routers.main.navigate "/apps/#{slug}/#{newhash}", false
         @resetLayoutSizes()

-    changeFavicon: (url) ->
-        @favicon?.remove()
-        @favicon2?.remove()
-        newfav = '<link rel="icon" type="image/x-icon" href="' + url + '" />"'
-        @favicon = $ newfav
-        @favicon2 = @favicon.clone().attr 'rel', 'shortcut icon'
-        $('head').append @favicon, @favicon2
-
     ### Configuration ###

     # Small trick to size properly iframe.


client: home: removed VMs tab

client: home: removed VMs tab

--- client/home/lib/index.coffee
+++ client/home/lib/index.coffee
@@ -20,7 +20,7 @@
   TABS = [
     { title : 'Welcome',          viewClass : HomeWelcome,         role: 'member' }
     { title : 'Stacks',           viewClass : HomeStacks,          role: 'member' }
-    { title : 'Virtual Machines', viewClass : HomeVirtualMachines, role: 'member' }
+    # { title : 'Virtual Machines', viewClass : HomeVirtualMachines, role: 'member' }
     { title : 'My Team',          viewClass : HomeMyTeam,          role: 'member' }
     { title : 'Team Billing',     viewClass : HomeTeamBilling                     }
     { title : 'Koding Utilities', viewClass : HomeUtilities,       role: 'member' }


Remove edge text

Remove edge text

--- app/assets/javascripts/directives.coffee
+++ app/assets/javascripts/directives.coffee
@@ -124,7 +124,7 @@
           attrs: '.marker-target': d: 'M 4 0 L 0 2 L 4 4 z'
           labels: [ {
             position: 0.5
-            attrs: text: text: 'on'
+            attrs: text: text: ''
           } ]
           connector: name: 'smooth')
         #}
@@ -194,8 +194,8 @@
         joint.layout.DirectedGraph.layout graph,
           setLinkVertices: false
           rankDir: 'lr'
-          nodeSep: 100
-          rankSep: 100
+          nodeSep: 80
+          rankSep: 80
         return

       V(paper.viewport).translate 20, 20


fix some filtering bugs

fix some filtering bugs

* . in input now only matches literal periods, not any character
* - without a pattern to negate is ignored

--- app/assets/javascripts/helpers.js.coffee
+++ app/assets/javascripts/helpers.js.coffee
@@ -3,11 +3,9 @@

 String.prototype.matches  or= (exp) ->
-  matchObj = patternsToRegex _.groupBy(exp.split(' '), commandType)
-  take = _(matchObj.bare).some(makeMatch(@))
-  drop = !_(matchObj.except).some(makeMatch(@))
-  console.log take
-  console.log drop
+  matchObj = patternsToRegex _.groupBy(exp.trim().split(' '), commandType)
+  take = matchObj.bare is [] || _(matchObj.bare).some(makeMatch(this))
+  drop = !_(matchObj.except).some(makeMatch(this))
   take and drop

 commandType = (command) ->
@@ -19,13 +17,14 @@
             obj.bare.map toRegex
           else
             []
-    except: if obj.except
-              obj.except.map((c) -> c[1..]).map toRegex
-            else
-              []
+    except:
+      if obj.except
+        toRegex(pattern[1 ..]) for pattern in obj.except when pattern.length > 1
+      else
+        []

 toRegex = (str) ->
-  new RegExp str.replace('.', '\.').replace('*', '[^ \\/:]*').replace(/[^\]]\*\*/, '.*').replace('?', '.')
+  new RegExp str.replace('.', '\\.').replace('*', '[^ \\/:]*').replace(/[^\]]\*\*/, '.*').replace('?', '.')

 makeMatch = (str) ->
   (pattern) ->


Edit Eland text

Edit Eland text

Removed parentheses from scientific name.

--- app/lib/field-guide-content/eland.coffee
+++ app/lib/field-guide-content/eland.coffee
@@ -4,7 +4,7 @@
     The common eland is the world’s largest antelope. It has a cowlike body with a massive neck and large shoulders. Common elands have a smooth coat that is yellowish-brown or tan in color and may h ave white vertical stripes running along the body. They have a long, thin tail that ends in a black tuft. Their ears are small and narrow and their horns are nearly straight, with one or two tight twists.
   '''

-  scientificName: '(Taurotragus oryx)'
+  scientificName: 'Taurotragus oryx'
   mainImage: 'assets/fieldguide-content/mammals/eland/eland-feature.jpg'

   conservationStatus: 'Least Concern' # Options are Least Concern, Near Threatened, Vulnerable, and Endangered; all have their own little icons as well.


Fixes broken tests

Fixes broken tests

--- javascripts/widgets/ui_elements.coffee
+++ javascripts/widgets/ui_elements.coffee
@@ -3,7 +3,7 @@
 #= require jquery

-ttm.define 'widgets/ui_elements', ['lib/class_mixer', 'jquery'], (class_mixer, $)->
+ttm.define 'widgets/ui_elements', ['lib/class_mixer'], (class_mixer)->
   class Button
     initialize: (@opts={})->
     render: (opts={})->


newkoding: fix variable reference

newkoding: fix variable reference

--- config/main.feature.coffee
+++ config/main.feature.coffee
@@ -419,12 +419,12 @@

         docker run -d -p 6379:6379                --name=redis    redis
         docker run -d -p 5432:5432                --name=postgres koding_localbuild/postgres
-        docker run -d -p 4001:4001 -p 7001:7001   --name=etcd     coreos/etcd -peer-addr #{boot2dockerbox}:7001 -addr #{boot2dockerbox}:4001
+        docker run -d -p 4001:4001 -p 7001:7001   --name=etcd     coreos/etcd -peer-addr #{prod_simulation_server}:7001 -addr #{prod_simulation_server}:4001

         cd #{projectRoot}/install/docker-mongo
         echo '#---> CREATING VANILLA KODING DB @gokmen <---#'
         tar jxvf #{projectRoot}/install/docker-mongo/default-db-dump.tar.bz2
-        mongorestore -h#{boot2dockerbox} -dkoding dump/koding
+        mongorestore -h#{prod_simulation_server} -dkoding dump/koding
         rm -rf ./dump

         echo '#---> UPDATING MONGO DATABASE ACCORDING TO LATEST CHANGES IN CODE (UPDATE PERMISSIONS @chris) <---#'


Update DiplomatView.coffee

Update DiplomatView.coffee

Added name (Ayexa) of contributor to Norwegan

--- app/views/contribute/DiplomatView.coffee
+++ app/views/contribute/DiplomatView.coffee
@@ -83,7 +83,7 @@
     fi: []             # suomi, Finnish
     bg: []             # български език, Bulgarian
     nb: ['bardeh', 'ebirkenes', 'matifol', 'mcclane654', 'mogsie', 'torehaug']             # Norsk Bokmål, Norwegian (Bokmål)
-    nn: []             # Norsk Nynorsk, Norwegian (Nynorsk)
+    nn: ['Ayexa']             # Norsk Nynorsk, Norwegian (Nynorsk)
     he: ['OverProgram', 'monetita']             # עברית, Hebrew
     lt: []             # lietuvių kalba, Lithuanian
     sr: []             # српски, Serbian


Use start command to launch atom.exe

Use start command to launch atom.exe

--- tasks/spec-task.coffee
+++ tasks/spec-task.coffee
@@ -49,10 +49,15 @@
     resourcePath = process.cwd()
     coreSpecsPath = path.resolve('spec')

-    options =
-      cmd: appPath
-      args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
-    console.log options
+    if process.platform is 'darwin'
+      options =
+        cmd: appPath
+        args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
+    else if process.platform is 'win32'
+      options =
+        cmd: 'start'
+        args: ['/wait', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
+
     spawn options, (error, results, code) ->
       packageSpecQueue.concurrency = 2
       callback(null, error)


Update doc-tools.coffee

Update doc-tools.coffee

--- data/tags/doc-tools.coffee
+++ data/tags/doc-tools.coffee
@@ -2,4 +2,5 @@
 title: "文档工具"
 parent: "function"
 reference:
-  "Wikipedia: JSDoc": "http://en.wikipedia.org/wiki/JSDoc"+  "Wikipedia: JSDoc": "http://en.wikipedia.org/wiki/JSDoc"
+  "Use JSDoc": "http://usejsdoc.org/"


Coco 38aa762: rewriter: made addImplicitBraces consume multiple leading comments

--- rewriter.coffee
+++ rewriter.coffee
@@ -115,8 +115,8 @@
       return 1 unless tag is ':' and
         ((ago = @tag i - 2) is ':' or stack[stack.length - 1]?[0] isnt '{')
       stack.push ['{']
-      idx = if ago is '@' then i - 2 else i - 1
-      idx -= 2 if @tag(idx - 2) is 'HERECOMMENT'
+      idx =  if ago is '@' then i - 2 else i - 1
+      idx -= 2 while @tag(idx - 2) is 'HERECOMMENT'
       value = new String('{')
       value.generated = yes
       tok = ['{', value, token[2]]


user/delete back in

user/delete back in

--- lib/middleware/proxy_to_gravity.coffee
+++ lib/middleware/proxy_to_gravity.coffee
@@ -15,7 +15,7 @@
   '/oauth2*', '/sitemap*'
 ]
 AUTH_ROUTES = [
-  '/post', "/profile/edit", "/user/edit"
+  '/post', "/profile/edit", "/user/edit", "/user/delete"
 ]
 for route in ROUTES
   app.all route, (req, res) ->


groups lock icon: shorten hover copy

groups lock icon: shorten hover copy

--- client/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
+++ client/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
@@ -80,7 +80,7 @@
       cssClass  : "badge private #{if group.privacy is 'private' then '' else 'hidden'}"
       partial   : "<span class='icon'/>"
       tooltip   :
-        title   : "Restricted access: viewing this groups content requires requesting an invitation"
+        title   : "Restricted access"

     @memberCount = new CustomLinkView
       title       : "#{group.counts?.members or 'No'} Members"


Not diff with super class

Not diff with super class

--- lib/operators/general-operators.coffee
+++ lib/operators/general-operators.coffee
@@ -12,7 +12,6 @@

 class Operator extends Base
   @extend()
-
   vimState: null
   target: null
   complete: null
@@ -60,10 +59,6 @@
 # Public: Generic class for an operator that requires extra input
 class OperatorWithInput extends Operator
   @extend()
-  constructor: (@editor, @vimState) ->
-    @editor = @editor
-    @complete = false
-
   canComposeWith: (operation) -> operation.characters? or operation.select?

   compose: (operation) ->


Fixed email notifications enable/disable

--- common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+++ common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
@@ -261,10 +261,7 @@
         url: url
         type: "GET"
         success: (response, textStatus) =>
-          if response.status
-            $('input.email-setting').attr('checked','checked')
-          else
-            $('input.email-setting').removeAttr('checked')
+          $('input.email-setting').prop('checked', response.status)
       #select all threads

     isBrowseMenuVisible: =>
@@ -516,17 +513,11 @@
       @retrieveFirstPage()

     updateEmailNotifications: () =>
-      if $('input.email-setting').attr('checked')
-        DiscussionUtil.safeAjax
-          url: DiscussionUtil.urlFor("enable_notifications")
-          type: "POST"
-          error: () =>
-            $('input.email-setting').removeAttr('checked')
-      else
-        DiscussionUtil.safeAjax
-          url: DiscussionUtil.urlFor("disable_notifications")
-          type: "POST"
-          error: () =>
-            $('input.email-setting').attr('checked','checked')
-
-
+      checkbox = $('input.email-setting')
+      checked = checkbox.prop('checked')
+      url_name = if checked then "enable_notifications" else "disable_notifications"
+      DiscussionUtil.safeAjax
+        url: DiscussionUtil.urlFor(url_name)
+        type: "POST"
+        error: () =>
+          checkbox.prop('checked', !checked)


[FIX] Do not escaping markdown on message attachments

--- packages/rocketchat-markdown/markdown.coffee
+++ packages/rocketchat-markdown/markdown.coffee
@@ -83,4 +83,4 @@

 if Meteor.isClient
 	Blaze.registerHelper 'RocketChatMarkdown', (text) ->
-		return RocketChat.Markdown text
+		return RocketChat.Markdown _.escapeHTML text


config: enable rate limiter for api

config: enable rate limiter for api

--- config/generateKonfig.coffee
+++ config/generateKonfig.coffee
@@ -253,7 +253,7 @@
     uri                           : { address: options.customDomain.public }
     # TODO: average request count per hour for a user should be measured and a reasonable limit should be set
     nodejsRateLimiter             : { enabled: no, guestRules: [{ interval: 3600, limit: 5000 }], userRules: [{ interval: 3600, limit: 10000 }] } # limit: request limit per rate limit window, interval: rate limit window duration in seconds
-    nodejsRateLimiterForApi       : { enabled: no, guestRules: [{ interval: 60,   limit: 10 }],   userRules: [{ interval: 60,   limit: 300 }] }   # limit: request limit per rate limit window, interval: rate limit window duration in seconds
+    nodejsRateLimiterForApi       : { enabled: yes, guestRules: [{ interval: 60,   limit: 10 }],   userRules: [{ interval: 60,   limit: 300 }] }   # limit: request limit per rate limit window, interval: rate limit window duration in seconds
     webserver                     : { port: 8080 }
     social                        : { port: 3030, login: "#{credentials.rabbitmq.login}", queueName: options.socialQueueName, kitePort: 8760, kiteKey: "#{credentials.kiteHome}/kite.key" }
     boxproxy                      : { port: parseInt(options.publicPort, 10) }


modularization

modularization

--- build/build-timbre.coffee
+++ build/build-timbre.coffee
@@ -28,10 +28,14 @@
             object_files.splice a, 0, j

 # build source code
-source_core = fs.readFileSync("#{SRC_DIR}/core.js", 'utf-8')
+build_timbre = ->
+    source_core = fs.readFileSync("#{SRC_DIR}/core.js", 'utf-8')
+    source = [source_core].concat object_files.map (x)->
+        fs.readFileSync "#{SRC_DIR}/objects/#{x}.js", 'utf-8'
+    source.join ''

-source = [source_core].concat object_files.map (x)->
-    fs.readFileSync "#{SRC_DIR}/objects/#{x}.js", 'utf-8'
-source = source.join ''
-
-fs.writeFileSync "#{DST_DIR}/timbre.dev.js", source, 'utf-8'
+if not module.parent
+    source = build_timbre()
+    fs.writeFileSync "#{DST_DIR}/timbre.dev.js", source, 'utf-8'
+else
+    module.exports = build_timbre


auto save draft when the blur event is triggered

--- app/assets/javascripts/auto_save_draft.js.coffee
+++ app/assets/javascripts/auto_save_draft.js.coffee
@@ -9,11 +9,18 @@
     $('.need-save-draft').each ->
       $(this).val(localStorage.getItem($(this).attr('draft-key')))

+    save_draft_item = ->
+      key = $(this).attr('draft-key')
+      localStorage.setItem(key, $(this).val()) if key != '' and $(this).val() != ''
+      localStorage.removeItem(key) if $(this).val() == ''
+
+    $('.need-save-draft').each ->
+      $(this).blur(save_draft_item)
+
     save_draft_func = ->
-      $('.need-save-draft').each ->
-        key = $(this).attr('draft-key')
-        localStorage.setItem(key, $(this).val()) if interval_id and key != '' and $(this).val() != ''
-        localStorage.removeItem(key) if interval_id and $(this).val() == ''
+      if interval_id
+        $('.need-save-draft').each ->
+          $(this).blur()

     interval_id = setInterval(save_draft_func, 15000)


Update selector binding to model -> view reflection + Ensures selectors reset

--- app/assets/javascripts/backend/animals.js.coffee
+++ app/assets/javascripts/backend/animals.js.coffee
@@ -172,27 +172,20 @@
         @moveAnimalsModal.animals.removeAll()
         @moveAnimalsModal.started_at ''
         @moveAnimalsModal.stopped_at ''
-        @moveAnimalsModal.worker undefined
-        @moveAnimalsModal.variant undefined
+        @moveAnimalsModal.worker.id undefined
+        @moveAnimalsModal.worker.name undefined
+        @moveAnimalsModal.variant.id undefined
+        @moveAnimalsModal.variant.name undefined
         @moveAnimalsModal.group undefined
         @moveAnimalsModal.alert false
         @moveAnimalsModal.checkNature false

-        #TODO CHANGE THIS
-        $("#workers_list").val('')
-        $("input[name='workers_list']").val('')
-
-        $("#natures_list").val('')
-        $("input[name='natures_list']").val('')
-

       @resetNewContainerModal = () =>
         @newContainerModal.show false
         @newContainerModal.group false
-
-        #TODO CHANGE THIS
-        $("#containers_list").val()
-        $("input[name='containers_list']").val()
+        @newContainerModal.container.id undefined
+        @newContainerModal.container.name undefined

       @resetSelectedItems = () =>