:bug: Use Map instead of Object to store EditorLinters Reference: AtomLinter/linter-plus#12

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -13,19 +13,19 @@
     @LintOnFly = true
     @Emitter = new Emitter
     @Subscriptions = new CompositeDisposable
-    @EditorLinters = {} # An object of Editor <--> Linter
+    @EditorLinters = new Map # An object of Editor <--> Linter
     @Linters = [] # I </3 coffee-script
     @Subscriptions.add atom.workspace.onDidChangeActivePaneItem =>
       ActiveLinter = @getActiveEditorLinter()
       return unless ActiveLinter
-      ActiveLinter.lint(false)
+      ActiveLinter.lint false
     @Subscriptions.add atom.workspace.observeTextEditors (Editor) =>
       CurrentEditorLinter = new EditorLinter @, Editor
-      @EditorLinters[ Editor ] = CurrentEditorLinter
+      @EditorLinters.set Editor, CurrentEditorLinter
       @Emitter.emit 'linters-observe', CurrentEditorLinter
       Editor.onDidDestroy =>
         CurrentEditorLinter.destroy()
-        delete @EditorLinters[ CurrentEditorLinter ]
+        @EditorLinters.delete CurrentEditorLinter

   render: ->
     # Update `LeftTile` of `StatusBar`
@@ -41,13 +41,17 @@
   getActiveEditorLinter: ->
     ActiveEditor = atom.workspace.getActiveTextEditor()
     return ActiveEditor unless ActiveEditor
-    return @EditorLinters[ ActiveEditor ]
+    return @EditorLinters.get ActiveEditor

   getLinter: (Editor) ->
-    return @EditorLinters[ Editor ]
+    return @EditorLinters.get Editor

   observeLinters: (Callback) ->
-    Callback(Linter) for Linter of @EditorLinters
+    `
+    for(Linter of this.EditorLinters){
+      Callback(Linter[1])
+    }
+    `
     @Emitter.on 'linters-observe', Callback

 module.exports = Linter


:gift: moveToFirstCharacterOnVerticalMotion cover `ctrl-f, b, d, u`

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -134,11 +134,11 @@
     # to select @inclusive-ly
     swrap(selection).translateSelectionEndAndClip('forward')

-  setCursorBuffeRow: (cursor, row) ->
+  setCursorBuffeRow: (cursor, row, options) ->
     if @isVerticalMotion() and settings.get('moveToFirstCharacterOnVerticalMotion')
-      cursor.setBufferPosition(@getFirstCharacterPositionForBufferRow(row))
-    else
-      setBufferRow(cursor, row)
+      cursor.setBufferPosition(@getFirstCharacterPositionForBufferRow(row), options)
+    else
+      setBufferRow(cursor, row, options)

 # Used as operator's target in visual-mode.
 class CurrentSelection extends Motion
@@ -780,25 +780,32 @@
   getAmountOfRows: ->
     Math.ceil(@amountOfPage * @editor.getRowsPerPage() * @getCount())

-  getPoint: (cursor) ->
-    row = getValidVimScreenRow(@editor, cursor.getScreenRow() + @getAmountOfRows())
-    getFirstCharacterScreenPositionForScreenRow(@editor, row)
-
-  moveCursor: (cursor) ->
-    cursor.setScreenPosition(@getPoint(cursor), autoscroll: false)
+  getBufferRow: (cursor) ->
+    screenRow = getValidVimScreenRow(@editor, cursor.getScreenRow() + @getAmountOfRows())
+    @editor.bufferRowForScreenRow(screenRow)
+
+  moveCursor: (cursor) ->
+    bufferRow = @getBufferRow(cursor)
+    @setCursorBuffeRow(cursor, @getBufferRow(cursor), autoscroll: false)

     if cursor.isLastCursor()
       if @isSmoothScrollEnabled()
         @vimState.finishScrollAnimation()

-      currentTopRow = @editor.getFirstVisibleScreenRow()
-      finalTopRow = currentTopRow + @getAmountOfRows()
-      done = => @editor.setFirstVisibleScreenRow(finalTopRow)
+      firstVisibileScreenRow = @editor.getFirstVisibleScreenRow()
+      newFirstVisibileBufferRow = @editor.bufferRowForScreenRow(firstVisibileScreenRow + @getAmountOfRows())
+      newFirstVisibileScreenRow = @editor.screenRowForBufferRow(newFirstVisibileBufferRow)
+      done = =>
+        @editor.setFirstVisibleScreenRow(newFirstVisibileScreenRow)
+        # [FIXME] sometimes, scrollTop is not updated, calling this fix.
+        # Investigate and find better approach then remove this workaround.
+        @editor.element.component.updateSync()

       if @isSmoothScrollEnabled()
-        @smoothScroll(currentTopRow, finalTopRow, {done})
+        @smoothScroll(firstVisibileScreenRow, newFirstVisibileScreenRow, {done})
       else
         done()
+

 # keymap: ctrl-f
 class ScrollFullScreenDown extends Scroll


updated Odesk prize text

updated Odesk prize text

--- client/landing/site.hackathon/coffee/home/AppViewFull.coffee
+++ client/landing/site.hackathon/coffee/home/AppViewFull.coffee
@@ -426,7 +426,8 @@

-
Teams with an account on oDesk.com or Elance.com get a chance to win an extra $2,500.

+
The top-performing team that has an oDesk.com or Elance.com account will receive an extra $2,500.
+              Be sure to add your oDesk or Elance userID to the questionnaire that we send you."


Inject custom user styles into all frames

This fixes #2594.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -263,10 +263,9 @@
             Math.max 0, tabs.length - count
       chrome.tabs.update tabs[toSelect].id, active: true

-chrome.tabs.onUpdated.addListener (tabId, changeInfo, tab) ->
-  return unless changeInfo.status == "loading" # Only do this once per URL change.
+chrome.webNavigation.onCommitted.addListener ({tabId, frameId}) ->
   cssConf =
-    allFrames: true
+    frameId: frameId
     code: Settings.get("userDefinedLinkHintCss")
     runAt: "document_start"
   chrome.tabs.insertCSS tabId, cssConf, -> chrome.runtime.lastError


adds permalink formatting options

adds permalink formatting options

--- src/plugins/page.coffee
+++ src/plugins/page.coffee
@@ -31,11 +31,26 @@
     constructor: (@filepath, @metadata) ->

     getFilename: ->
-      if @metadata.filename?
-        dirname = path.dirname @filepath.relative
-        return path.join(dirname, @metadata.filename)
-      else
-        return env.utils.stripExtension(@filepath.relative) + '.html'
+
+      regularFilename = =>
+        path.join path.dirname(@filepath.relative), @basename
+
+      permalinkFilename = =>
+
+        dirname = path.dirname regularFilename()
+        basename = path.join path.basename(dirname), path.basename regularFilename()
+        dirname = path.dirname dirname
+
+        basename = @permalinkTemplate
+          .replace(":year", @date.getFullYear())
+          .replace(":month", ('0' + (@date.getMonth()+1)).slice(-2))
+          .replace(":day", ('0' + @date.getDate()).slice(-2))
+          .replace(":title", basename)
+
+        path.join dirname, basename
+
+      if @metadata.permalink? then permalinkFilename() else regularFilename()
+

     getUrl: (base) ->
       # remove index.html for prettier links
@@ -78,6 +93,15 @@
     @property 'rfc822date', ->
       env.utils.rfc822(@date)

+    @property 'permalinkTemplate', ->
+      switch @metadata.permalink
+        when undefined then ":title"
+        when "date"    then ":year/:month/:day/:title"
+        else           @metadata.permalink
+
+    @property 'basename', ->
+      @metadata.filename or path.basename(env.utils.stripExtension(@filepath.relative) + '.html')
+
     @property 'hasMore', ->
       @_html ?= @getHtml()
       @_intro ?= @getIntro()


Proper two step create and initialize

Proper two step create and initialize

--- lib/schema.coffee
+++ lib/schema.coffee
@@ -33,9 +33,10 @@
         else
           throw new Error "Unrecognized relationship: #{util.inspect(options)}"

-      relation.initialize() # initalize in two steps for circular dependencies
       @ids_accessor[relation.ids_accessor] = relation if relation.ids_accessor

+     # initalize in two steps to break circular dependencies
+    relation.initialize() for key, relation of @relations
     return

   relation: (key) -> return @relations[key] or @ids_accessor[key]


phantomJS fiuxup attempt number two

phantomJS fiuxup attempt number two

--- bokehjs/src/coffee/base.coffee
+++ bokehjs/src/coffee/base.coffee
@@ -6,6 +6,13 @@

 # add some useful functions to underscore
 require("./core/util/underscore").patch()
+
+# fixup for PhantomJS deficiency
+#
+# see https://github.com/ariya/phantomjs/issues/10522#issuecomment-66449127
+Function.prototype.bind = Function.prototype.bind || (thisp) ->
+  fn = this
+  return () -> fn.apply(thisp, arguments)

 Config = {}
 url = window.location.href


fixed microsite middleware test

fixed microsite middleware test

--- apps/fair/test/routes.coffee
+++ apps/fair/test/routes.coffee
@@ -249,7 +249,7 @@
 # Microsite middleware test
 #
 sinon = require 'sinon'
-micrositeMiddleware = require '../../../lib/middleware/microsite'
+micrositeMiddleware = require('../routes').microsite

 describe 'microsite middleware', ->
   describe 'does not have the microsite context', ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

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


number_extensions

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -243,11 +243,11 @@
 Floors the number for purposes of factorization.

-   60.primeFactors()
-=> [2, 2, 3, 5]
-
-   37.primeFactors()
-=> [37]
+60.primeFactors()
+# => [2, 2, 3, 5]
+
+37.primeFactors()
+# => [37]

 @name primeFactors


Resolve source path

Resolve source path

--- src/package-converter.coffee
+++ src/package-converter.coffee
@@ -57,6 +57,7 @@
         @copyDirectories(sourcePath, callback)

   copyDirectories: (sourcePath, callback) ->
+    sourcePath = path.resolve(sourcePath)
     for source, target of @directoryMappings
       @convertDirectory(path.join(sourcePath, source), target)
     callback()


Add ligatures to Menlo

Add ligatures to Menlo

--- user/config.cson
+++ user/config.cson
@@ -42,7 +42,7 @@
   docblockr: {}
   editor:
     autoIndentOnPaste: false
-    fontFamily: "Menlo"
+    fontFamily: "Menlig"
     fontSize: 11
     invisibles: {}
     lineHeight: 1.3


Improve emission-logging mechanism

Improve emission-logging mechanism

--- user/init.coffee
+++ user/init.coffee
@@ -17,11 +17,16 @@
 global.traceEmissions = (active) ->
 	if active
 		prot.emit = (name) ->
-			console.trace arguments unless name is "did-update-state"
+			unless name is "did-update-state"
+				emissions.push Array.from arguments
+				console.trace arguments
 			emit.apply @, arguments
 	else
 		prot.emit = emit
 	undefined
+
+global.emissions = Object.defineProperty [], "log",
+	get: -> this.map((e) -> e[0]).join("\n")

 # Disable that useless pending item feature


Sort by name

Sort by name

--- resources/assets/coffee/main.coffee
+++ resources/assets/coffee/main.coffee
@@ -35,8 +35,8 @@
   StoreCheckout.initialize()

 @accountEdit ?= new AccountEdit
+@accountEditAvatar ?= new AccountEditAvatar
 @accountEditPlaystyle ?= new AccountEditPlaystyle
-@accountEditAvatar ?= new AccountEditAvatar
 @beatmapsetDownloadObserver ?= new BeatmapsetDownloadObserver
 @changelogChartLoader ?= new ChangelogChartLoader
 @checkboxValidation ?= new CheckboxValidation
@@ -73,8 +73,8 @@
 @tooltipDefault ?= new TooltipDefault
 @turbolinksReload ?= new TurbolinksReload
 @twitchPlayer ?= new TwitchPlayer
+@userCard ?= new UserCard
 @wiki ?= new Wiki
-@userCard ?= new UserCard

 @formConfirmation ?= new FormConfirmation(@formError)
 @forumPostsSeek ?= new ForumPostsSeek(@forum)


Support .coffee and .js mappings

Support .coffee and .js mappings

--- svgtiler.coffee
+++ svgtiler.coffee
@@ -1,5 +1,6 @@
 path = require 'path'
 fs = require 'fs'
+CoffeeScript = require 'coffee-script'
 csvParse = require 'csv-parse'
 xmldom = require 'xmldom'
 DOMParser = xmldom.DOMParser
@@ -42,10 +43,16 @@
       throw new UserException "svgtiler: unrecognized extension in filename #{filename}"

 class Mapping extends Input
+  constructor: (data) ->
+    if data?
+      for own key, value of data
+        unless value instanceof Symbol
+          value = Symbol.parse key, value
+        @[key] = value

 class ASCIIMapping extends Mapping
   @parse: (data) ->
-    map = new @
+    map = {}
     for line in splitIntoLines data
       separator = whitespace.exec line
       continue unless separator?
@@ -55,9 +62,15 @@
         key = line[...separator.index]
       value = Symbol.parse key, line[separator.index + separator[0].length..]
       map[key] = value
-    map
+    new @ map
+
+class JSMapping extends Mapping
+  @parse: (data) ->
+    new @ eval data

 class CoffeeMapping extends Mapping
+  @parse: (data) ->
+    new @ CoffeeScript.eval data

 class Drawing extends Input
   tileWidth: 50
@@ -151,6 +164,7 @@

 extension_map =
   '.txt': ASCIIMapping
+  '.js': JSMapping
   '.coffee': CoffeeMapping
   '.asc': ASCIIDrawing
   '.ssv': SSVDrawing


Fix /chart bomb

Fix /chart bomb

--- src/scripts/chartbeat.coffee
+++ src/scripts/chartbeat.coffee
@@ -6,7 +6,7 @@
 #
 # Configuration:
 #   HUBOT_CHARTBEAT_SITE
-#   HUBOT_CHARTBEAT_SITES <comma separated string of multiple hosts>
+#   HUBOT_CHARTBEAT_SITES <comma separated string of all
 #   HUBOT_CHARTBEAT_API_KEY <use global key for access to all sites>
 #
 # Commands:
@@ -44,18 +44,16 @@
     if (!process.env.HUBOT_CHARTBEAT_SITE && msg.match[2] == 'me')
       msg.send "You need to set a default site"
       return
+    if (!process.env.HUBOT_CHARTBEAT_SITES && msg.match[2] == 'bomb')
+      msg.send "You need to set a list of sites"
+      return

     sites = switch msg.match[2]
       when "me" then [process.env.HUBOT_CHARTBEAT_SITE]
+      when "bomb" then process.env.HUBOT_CHARTBEAT_SITES.split(",")
       when "*" then process.env.HUBOT_CHARTBEAT_SITES.split(",")
       else [msg.match[2]]

     apiKey = process.env.HUBOT_CHARTBEAT_API_KEY

-    getChart(msg, apiKey, site) for site in sites
-
-  robot.respond /chart bomb/i, (msg) ->
-    if (!process.env.HUBOT_CHARTBEAT_SITES)
-      msg.send "You need to set a list of sites"
-      return
-    getChart(msg, process.env.HUBOT_CHARTBEAT_API_KEY, process.env.HUBOT_CHARTBEAT_SITES.split(","))+    getChart(msg, apiKey, site) for site in sites


Single quotes.

Single quotes.

--- src/scripts/http-info.coffee
+++ src/scripts/http-info.coffee
@@ -45,8 +45,8 @@
     done = (errors, window) ->
       unless errors
         $ = window.$
-        title = $('title').first().text().replace(/(\r\n|\n|\r)/gm,"").replace(/\s{2,}/g,' ').trim()
-        description = $('meta[name=description]').first().attr("content").replace(/(\r\n|\n|\r)/gm,"").replace(/\s{2,}/g,' ').trim() || ""
+        title = $('title').first().text().replace(/(\r\n|\n|\r)/gm,'').replace(/\s{2,}/g,' ').trim()
+        description = $('meta[name=description]').first().attr('content').replace(/(\r\n|\n|\r)/gm,'').replace(/\s{2,}/g,' ').trim() || ''

         if title and description and not process.env.HUBOT_HTTP_INFO_IGNORE_DESC
           msg.send "#{title}\n#{description}"


Add warning that it's deprecated

Add warning that it's deprecated

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -58,6 +58,8 @@
 pagerEndpoint          = process.env.HUBOT_PAGERDUTY_ENDPOINT || "/hook"

 module.exports = (robot) ->
+   robot.logger.warning "pagerduty.coffee has moved from hubot-scripts to its own package. See https://github.com/hubot-scripts/hubot-pager-me installation instructions"
+
   robot.respond /pager( me)?$/i, (msg) ->
     if missingEnvironmentForApi(msg)
       return


switch to wrapper inserter

switch to wrapper inserter

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -248,6 +248,7 @@

 							linker itemScope, (clone) ->
 								wrapper.element = clone
+								wrapper.toBeAppended = toBeAppended
 								if toBeAppended
 									if index == next
 										wrapper.inserter = builder.append
@@ -297,18 +298,16 @@
 						adjustBuffer = (rid, newItems, finalize)->
 							if newItems and newItems.length
 								$timeout ->
-									appended = false
 									prepended = []
-									for wrapper,i in buffer
-										if wrapper.element.parent().length
-											appended = true
-										else
-											if appended
-												buffer[i-1].element.after wrapper.element
+									for wrapper in buffer
+										if wrapper.element.parent().length == 0
+											if wrapper.toBeAppended
+												wrapper.inserter wrapper.element
 											else
-												prepended.push wrapper
-									for wrapper in prepended.reverse()
-										builder.prepend wrapper.element
+												prepended.unshift wrapper
+									# prepended items have to be inserted from the bottom up
+									for wrapper in prepended
+										wrapper.inserter wrapper.element
 										adjustRowHeight false, wrapper
 									doAdjustment(rid, finalize)
 							else


Specify underscore & underscore.string dependencies

The versions are based on the latest compatible version that underscore.string supports.

--- src/scripts/wikipedia.coffee
+++ src/scripts/wikipedia.coffee
@@ -4,6 +4,8 @@
 # Dependencies:
 #   "htmlparser": "1.7.6"
 #   "soupselect: "0.2.0"
+#   "underscore": "1.3.3"
+#   "underscore.string": "2.3.0"
 #
 # Configuration:
 #   None


#bug#0 修复asset单复数不统一的问题 #done

#bug#0 修复asset单复数不统一的问题 #done

--- config.coffee
+++ config.coffee
@@ -63,7 +63,7 @@
     },
     {
       #素材
-      path: 'project/:project_id(\\d+)/asset'
+      path: 'project/:project_id(\\d+)/assets'
       biz: 'asset'
       method: post: 'uploadFile', delete: false, put: false
     },
@@ -81,14 +81,13 @@
     }
     {
       ##查看素材
-      paths:{
-        get: '/asset/:project_id(\\d+)/:filename'
-      },
+      path: 'assets/:project_id(\\d+)/:filename'
       biz: 'asset'
+      id: false
       method: get: 'readFile', put: false, post: false, delete: false
     },{
       #issue相关
-      path: 'project/:project_id(\\d+)/issue',
+      path: 'project/:project_id(\\d+)/issue'
       biz: 'issue'
     },
     {
@@ -104,7 +103,7 @@
     },
     {
       #建立或者解除asset与issue的关系
-      path: 'issue/:issue_id(\\d+)/asset'
+      path: 'project/:project_id/issue/:issue_id(\\d+)/assets'
       biz: 'asset_issue_relation'
       method: put: false
     },


Use notification API on win8 and later

Use notification API on win8 and later

--- views/env.coffee
+++ views/env.coffee
@@ -2,6 +2,7 @@
 path = require 'path-extra'
 notifier = require 'node-notifier'
 fs = require 'fs-extra'
+os = require 'os'

 # Environments
 window.remote = require 'remote'
@@ -76,6 +77,11 @@
   window.dispatchEvent event

 NOTIFY_DEFAULT_ICON = path.join(ROOT, 'assets', 'icons', 'icon.png')
+NOTIFY_NOTIFICATION_API = true
+if process.platform == 'win32'
+  release = os.release().split('.')
+  if (release[0] < 6) or (release[0] == 6 and release[1] <= 1)
+    NOTIFY_NOTIFICATION_API = false
 window.notify = (msg, options) ->
   # Notification config
   enabled = config.get('poi.notify.enabled', true)
@@ -104,16 +110,16 @@
     icon = options.icon if options.icon
   # Send desktop notification
   return unless enabled
-  if process.platform == 'win32'
+  if NOTIFY_NOTIFICATION_API
+    new Notification title,
+      icon: "file://#{icon}"
+      body: msg
+  else
     notifier.notify
       title: title
       icon: icon
       message: msg
       sound: false
-  else
-    new Notification title,
-      icon: "file://#{icon}"
-      body: msg
   # Play notification sound
   #   According to MDN Notification API docs: https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification
   #   Parameter `sound` is not supported in any browser yet, so we play sound manually.


rename lost

rename lost

--- src/renderer/Renderer.coffee
+++ src/renderer/Renderer.coffee
@@ -35,13 +35,13 @@
 		@shapes = []

 		if not @fillParent
-			@dom.style.width = (@width / @pixelRatio) + 'px'
-			@dom.style.height = (@height / @pixelRatio) + 'px'
+			@dom.style.width = Math.floor(@width / @pixelRatio) + 'px'
+			@dom.style.height = Math.floor(@height / @pixelRatio) + 'px'
 			@dom.width = @width
 			@dom.height = @height
 		@dom.style.border = 'solid 1px gray' if options.border? and options.border
 		@dom.style.cursor = 'crosshair'
-		@dom.style.boxSizing = 'border-box'
+		@dom.style.boxSizing = 'content-box'
 		@dom.style.background = '#eee'
 		@dom.oncontextmenu = -> false

@@ -184,7 +184,7 @@
 				@context.dashedLine(
 						shape.points[0].x, shape.points[0].y,
 						shape.points[1].x, shape.points[1].y,
-						shape.dashStyle, shape.dashDelta
+						shape.dashStyle, shape.dashOffset
 				)
 			else
 				@context.lineTo shape.points[0].x, shape.points[0].y
@@ -233,9 +233,9 @@
 			if shape.dashStyle
 				@context.beginPath() # clear prev lineTo
 				pts = shape.points
-				@context.dashedLine pts[0].x, pts[0].y, pts[1].x, pts[1].y, shape.dashStyle, shape.dashDelta
-				@context.dashedLine pts[1].x, pts[1].y, pts[2].x, pts[2].y, shape.dashStyle, shape.dashDelta
-				@context.dashedLine pts[2].x, pts[2].y, pts[0].x, pts[0].y, shape.dashStyle, shape.dashDelta
+				@context.dashedLine pts[0].x, pts[0].y, pts[1].x, pts[1].y, shape.dashStyle, shape.dashOffset
+				@context.dashedLine pts[1].x, pts[1].y, pts[2].x, pts[2].y, shape.dashStyle, shape.dashOffset
+				@context.dashedLine pts[2].x, pts[2].y, pts[0].x, pts[0].y, shape.dashStyle, shape.dashOffset
 			@context.stroke()
 		@

@@ -262,10 +262,10 @@
 				xR = shape.pointRB.x
 				yT = shape.position.y
 				yB = shape.pointRB.y
-				@context.dashedLine xL, yT, xR, yT, shape.dashStyle, shape.dashDelta
-				@context.dashedLine xR, yT, xR, yB, shape.dashStyle, shape.dashDelta
-				@context.dashedLine xR, yB, xL, yB, shape.dashStyle, shape.dashDelta
-				@context.dashedLine xL, yB, xL, yT, shape.dashStyle, shape.dashDelta
+				@context.dashedLine xL, yT, xR, yT, shape.dashStyle, shape.dashOffset
+				@context.dashedLine xR, yT, xR, yB, shape.dashStyle, shape.dashOffset
+				@context.dashedLine xR, yB, xL, yB, shape.dashStyle, shape.dashOffset
+				@context.dashedLine xL, yB, xL, yT, shape.dashStyle, shape.dashOffset
 				@context.stroke()
 		@

@@ -329,8 +329,8 @@
 				@context.beginPath()
 				pts = shape.vertices
 				for i in [0 ... len - 1]
-					@context.dashedLine pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, shape.dashStyle, shape.dashDelta
-				@context.dashedLine pts[len - 1].x, pts[len - 1].y, pts[0].x, pts[0].y, shape.dashStyle, shape.dashDelta
+					@context.dashedLine pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, shape.dashStyle, shape.dashOffset
+				@context.dashedLine pts[len - 1].x, pts[len - 1].y, pts[0].x, pts[0].y, shape.dashStyle, shape.dashOffset
 				@context.stroke()
 			@context.stroke()
 		@
@@ -348,7 +348,7 @@
 			else
 				pts = shape.vertices
 				for i in [ 0 ... pts.length - 1]
-					@context.dashedLine pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, shape.dashStyle, shape.dashDelta
+					@context.dashedLine pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, shape.dashStyle, shape.dashOffset
 			@context.stroke()
 		@

@@ -386,10 +386,10 @@
 	drawBounds: (bounds) ->
 		@context.strokeStyle = bounds.strokeStyle
 		@context.beginPath()
-		@context.dashedLine bounds.x1, bounds.y1, bounds.x2, bounds.y1, bounds.dashStyle, bounds.dashDelta
-		@context.dashedLine bounds.x2, bounds.y1, bounds.x2, bounds.y2, bounds.dashStyle, bounds.dashDelta
-		@context.dashedLine bounds.x2, bounds.y2, bounds.x1, bounds.y2, bounds.dashStyle, bounds.dashDelta
-		@context.dashedLine bounds.x1, bounds.y2, bounds.x1, bounds.y1, bounds.dashStyle, bounds.dashDelta
+		@context.dashedLine bounds.x1, bounds.y1, bounds.x2, bounds.y1, bounds.dashStyle, bounds.dashOffset
+		@context.dashedLine bounds.x2, bounds.y1, bounds.x2, bounds.y2, bounds.dashStyle, bounds.dashOffset
+		@context.dashedLine bounds.x2, bounds.y2, bounds.x1, bounds.y2, bounds.dashStyle, bounds.dashOffset
+		@context.dashedLine bounds.x1, bounds.y2, bounds.x1, bounds.y1, bounds.dashStyle, bounds.dashOffset
 		@context.stroke()
 		@

@@ -398,9 +398,9 @@
 # See: http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
 (=>
 	CP = window.CanvasRenderingContext2D and CanvasRenderingContext2D.prototype
-	CP.dashedLine = (x, y, x2, y2, da, delta) ->
+	CP.dashedLine = (x, y, x2, y2, da, offset) ->
 		da = Bu.DEFAULT_DASH_STYLE if not da?
-		delta = 0 if not delta?
+		offset = 0 if not offset?
 		@save()
 		dx = x2 - x
 		dy = y2 - y
@@ -415,8 +415,8 @@
 		lenU = 0
 		for i in da
 			lenU += i
-		delta %= lenU
-		x = delta
+		offset %= lenU
+		x = offset

 		@moveTo 0, 0
 		# TODO need a small fix


Cache response info

Cache response info

--- src/scripts/hubot-zoi.coffee
+++ src/scripts/hubot-zoi.coffee
@@ -13,10 +13,14 @@

 request = require 'request'
 uri     = process.env.HUBOT_ZOI_SOURCE_URL
+zois    = false

 throw new Error('Please set HUBOT_ZOI_SOURCE_URL env') unless uri?

 getZois = (cb) ->
+  if zois
+    (cb.onZoi || cb.onSuccess)(zois)
+    return
   request(uri, (err, response, body) ->
     if err
       cb.onError(err)


issue #25 progress

issue #25 progress

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -137,6 +137,7 @@
 							loading = (value) ->
 								datasource.loading(value) if datasource.loading

+						ridActual = 0
 						first = 1
 						next = 1
 						buffer = []
@@ -153,6 +154,7 @@
 							buffer.splice start, stop - start

 						reload = ->
+							ridActual++
 							first = 1
 							next = 1
 							removeFromBuffer(0, buffer.length)
@@ -161,7 +163,7 @@
 							pending = []
 							eof = false
 							bof = false
-							adjustBuffer(false)
+							adjustBuffer(ridActual, false)

 						bottomVisiblePos = ->
 							viewport.scrollTop() + viewport.height()
@@ -214,12 +216,12 @@
 								first += overage
 								log "clipped off top #{overage} top padding #{adapter.topPadding()}"

-						enqueueFetch = (direction, scrolling)->
+						enqueueFetch = (rid, direction, scrolling)->
 							if (!isLoading)
 								isLoading = true
 								loading(true)
 							if pending.push(direction) == 1
-								fetch(scrolling)
+								fetch(rid, scrolling)

 						insert = (index, item) ->
 							itemScope = $scope.$new()
@@ -260,14 +262,14 @@
 									# if not, increment scrollTop
 									viewport.scrollTop(viewport.scrollTop() + wrapper.element.outerHeight(true))

-						adjustBuffer = (scrolling, newItems, finalize)->
+						adjustBuffer = (rid, scrolling, newItems, finalize)->
 							doAdjustment = ->
 								log "top {actual=#{adapter.topDataPos()} visible from=#{topVisiblePos()} bottom {visible through=#{bottomVisiblePos()} actual=#{adapter.bottomDataPos()}}"
 								if shouldLoadBottom()
-									enqueueFetch(true, scrolling)
-								else
-									enqueueFetch(false, scrolling) if shouldLoadTop()
-								finalize() if finalize
+									enqueueFetch(rid, true, scrolling)
+								else
+									enqueueFetch(rid, false, scrolling) if shouldLoadTop()
+								finalize(rid) if finalize
 								if pending.length == 0
 									topHeight = 0
 									for item in buffer
@@ -286,66 +288,68 @@
 							else
 								doAdjustment()

-						finalize = (scrolling, newItems)->
-							adjustBuffer scrolling, newItems, ->
+						finalize = (rid, scrolling, newItems)->
+							adjustBuffer rid, scrolling, newItems, ->
 								pending.shift()
 								if pending.length == 0
 									isLoading = false
 									loading(false)
 								else
-									fetch(scrolling)
-
-						fetch = (scrolling) ->
+									fetch(rid, scrolling)
+
+						fetch = (rid, scrolling) ->
 							direction = pending[0]
 							#log "Running fetch... #{{true:'bottom', false: 'top'}[direction]} pending #{pending.length}"
 							if direction
 								if buffer.length && !shouldLoadBottom()
-									finalize(scrolling)
+									finalize(rid, scrolling)
 								else
 									#log "appending... requested #{bufferSize} records starting from #{next}"
 									datasource.get next, bufferSize,
 									(result) ->
+										return if rid isnt ridActual
 										newItems = []
 										if result.length == 0
 											eof = true
 											adapter.bottomPadding(0)
-											log "appended: requested #{bufferSize} records starting from #{next} recieved: eof"
+											#log "appended: requested #{bufferSize} records starting from #{next} recieved: eof"
 										else
 											clipTop()
 											for item in result
 												newItems.push (insert ++next, item)
-											log "appended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
-										finalize(scrolling, newItems)
+										#log "appended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
+										finalize(rid, scrolling, newItems)

 							else
 								if buffer.length && !shouldLoadTop()
-									finalize(scrolling)
+									finalize(rid, scrolling)
 								else
 									#log "prepending... requested #{size} records starting from #{start}"
 									datasource.get first-bufferSize, bufferSize,
 									(result) ->
+										return if rid isnt ridActual
 										newItems = []
 										if result.length == 0
 											bof = true
 											adapter.topPadding(0)
-											log "prepended: requested #{bufferSize} records starting from #{first-bufferSize} recieved: bof"
+											#log "prepended: requested #{bufferSize} records starting from #{first-bufferSize} recieved: bof"
 										else
 											clipBottom() if buffer.length
 											for i in [result.length-1..0]
 												newItems.unshift (insert --first, result[i])
-											log "prepended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
-										finalize(scrolling, newItems)
+										#log "prepended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
+										finalize(rid, scrolling, newItems)

 						resizeHandler = ->
 							if !$rootScope.$$phase && !isLoading
-								adjustBuffer(false)
+								adjustBuffer(null, false)
 								$scope.$apply()

 						viewport.bind 'resize', resizeHandler

 						scrollHandler = ->
 							if !$rootScope.$$phase && !isLoading
-								adjustBuffer(true)
+								adjustBuffer(null, true)
 								$scope.$apply()

 						viewport.bind 'scroll', scrollHandler
@@ -388,7 +392,7 @@
 									next--

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

 						eventListener.$on "insert.item", (event, locator, item)->
 							inserted = []
@@ -412,7 +416,7 @@
 									next++

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

 		])


issue #25 comment tabs fix

issue #25 comment tabs fix

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -317,7 +317,7 @@
 											clipTop()
 											for item in result
 												newItems.push (insert ++next, item)
-										#log "appended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
+											#log "appended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
 										finalize(rid, scrolling, newItems)

 							else
@@ -337,7 +337,7 @@
 											clipBottom() if buffer.length
 											for i in [result.length-1..0]
 												newItems.unshift (insert --first, result[i])
-										#log "prepended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
+											#log "prepended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
 										finalize(rid, scrolling, newItems)

 						resizeHandler = ->


add comment clarifying how course payment works

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -551,6 +551,10 @@

     # If a course payment is required for this project from the signed in user, returns time when
     # it will be required; otherwise, returns undefined.
+    # POLICY: payment is required from the the time set in the .course file until 3 months later.
+    # After the course is (nearly) over, payment is then **no longer** required, and this function
+    # again returns undefined.  This is so students have access to their work even after their
+    # subscription has expired.
     date_when_course_payment_required: (project_id) =>
         account = @redux.getStore('account')
         if not account?


Simplify IC symbol generation

Simplify IC symbol generation

--- src/symbol/default/ic.coffee
+++ src/symbol/default/ic.coffee
@@ -17,8 +17,6 @@
   top = symbol.top
   bottom = symbol.bottom
   pins = symbol.element.pins
-  dualInLine = (top.length is 0) and (bottom.length is 0)
-  quad = not dualInLine

   width = step * (Math.max(top.length, bottom.length) + 2)
   height = step * (Math.max(left.length, right.length) + 2)
@@ -34,15 +32,11 @@
   symbol.addAttribute 'name',
     x: 0
     y: 0.5
-    halign: if quad then 'center' else 'right'
+    halign: 'center'
     valign: 'center'
-    orientation: if quad then 'horizontal' else 'vertical'

   textWidth = symbol.element.longestAlias.length * settings.fontSize.name
   textHeight = settings.fontSize.refDes + settings.fontSize.name + 1
-  if dualInLine
-    textWidth = settings.fontSize.name
-    textHeight = symbol.element.longestAlias.length * settings.fontSize.name

   # Update box size
   width = Math.max width, textWidth + 2*space
@@ -138,10 +132,7 @@
   x = -width/2
   dy = settings.fontSize.pinName/2 + space
   leftPins = []
-  if quad
-    y = -step * left.length/2 + step/2
-  else
-    y = Math.ceil(settings.fontSize.name + step)
+  y = -step * left.length/2 + step/2
   for i in left
     if i is '-'
       y += step
@@ -169,10 +160,7 @@
   # Pins on the right side
   x = width/2
   rightPins = []
-  if quad
-    y = -step * right.length/2 + step/2
-  else
-    y = Math.ceil(settings.fontSize.name + step)
+  y = -step * right.length/2 + step/2
   for i in right
     if i is '-'
       y += step
@@ -202,8 +190,7 @@
   height = bottomY - topY

   # Box
-  y = 0
-  if quad then y = topY
+  y = topY
   symbol.addRectangle
     x: leftX
     y: y


Add new packages to atom list

Add new packages to atom list

--- atom.symlink/packages.cson
+++ atom.symlink/packages.cson
@@ -5,6 +5,16 @@
   version: 1.2.2
 'file-icons':
   version: 1.6.12
+'git-control':
+  version: undefined
+'git-diff-details':
+  version: undefined
+'git-difftool':
+  version: undefined
+'git-history':
+  version: undefined
+'git-log':
+  version: undefined
 'git-plus':
   version: 5.6.5
 'highlight-selected':
@@ -25,5 +35,13 @@
   version: 4.3.1
 'open-recent':
   version: 4.0.1
+'open-gitup':
+  version: 0.0.2
 'react':
   version: 0.12.10
+'show-origin':
+  version: 0.2.0
+'tree-view-git-branch':
+  version: undefined
+'tree-view-git-status':
+  version: undefined


minor static sideeeffect fix on statusupdatewidget

--- client/landing/app/Applications/Activity.kdapplication/widgets/widgetview.coffee
+++ client/landing/app/Applications/Activity.kdapplication/widgets/widgetview.coffee
@@ -2,6 +2,7 @@

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

+    options.domId    = "activity-update-widget"
     options.cssClass = "activity-update-widget-wrapper"

     super options, data
@@ -10,7 +11,7 @@
     @listenWindowResize()

   setMainSections:->
-
+    @updatePartial ''
     @addSubView widgetWrapper = new KDView
       cssClass : 'widget-holder clearfix'

@@ -40,7 +41,7 @@
     @unsetClass "edit-mode"
     @changeTab "update", "Status Update"
     @mainInputTabs.emit "MainInputTabsReset", isHardReset
-
+
     @_windowDidResize()

   addWidgetPane:(options)->


Fix for markdown heading of non Latin characters


Fix accidental recursion on form submit handler

--- views/app.coffee
+++ views/app.coffee
@@ -53,9 +53,7 @@
     disable form
     validation = true
     validate_form()
-    if form.data 'valid'
-      form.submit()
-    else
+    unless form.data 'valid'
       e.preventDefault()
     enable form


Undo accidental addition

Undo accidental addition

--- framer/Color.coffee
+++ framer/Color.coffee
@@ -1,3 +0,0 @@
-{BaseClass} = require "./BaseClass"
-
-exports.Color extends BaseClass


Fix html content moving the border element

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -155,6 +155,8 @@
 		@_element.appendChild(@_elementBorder)
 		@_elementBorder.style = _.extend @_elementBorder.style,
 			position: "absolute"
+			top: 0
+			left: 0
 			width: "100%"
 			height: "100%"
 			boxSizing: "border-box"


Calling options.completion in animationFinished()

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -925,9 +925,8 @@
 		animationFinished = =>
 			for k, v of nonAnimatableProperties
 				@[k] = v
-			# If we changed the state, we send the event that we did
-			if stateName? and _.last(@_previousStates) isnt stateName
-				@states.emit(Events.StateDidSwitch, _.last(@states._previousStates), @states.currentName, @)
+			options.completion?()
+
 		animation.once Events.AnimationStop, animationFinished
 		started = animation.start() if start
 		if not started


fixed config descriptions

fixed config descriptions

--- lib/autohide-tree-view.coffee
+++ lib/autohide-tree-view.coffee
@@ -6,12 +6,12 @@

   config:
     showDelay:
-      description: 'The delay before the tree-view is unfolded in seconds'
+      description: 'The delay  - in seconds - before the tree-view will show'
       type: 'number'
       default: .2
       minimum: 0
     hideDelay:
-      description: 'Rough estimation of the delay - in seconds - before the menu starts hiding'
+      description: 'The delay - in seconds - before the tree-view will hide'
       type: 'number'
       default: .2
       minimum: 0


separate graphviz helpers

separate graphviz helpers

--- src/djinn.coffee
+++ src/djinn.coffee
@@ -5,16 +5,17 @@

 # Graphviz formatting helpers

-dotEdge = (from, to) ->
-  [from, to].join(" -> ")
-
-dotNode = (node, attrs) ->
-  "#{node}#{dotAttrs(attrs)};\n"
-
-dotAttrs = (obj) ->
-  pairs = []
-  pairs = ("#{key}=\"#{val}\"" for own key, val of obj)
-  " [#{pairs.join(", ")}]"
+Graphviz =
+  dotEdge: (from, to) ->
+    [from, to].join(" -> ")
+
+  dotNode: (node, attrs) ->
+    "#{node}#{@dotAttrs(attrs)};\n"
+
+  dotAttrs: (obj) ->
+    pairs = []
+    pairs = ("#{key}=\"#{val}\"" for own key, val of obj)
+    " [#{pairs.join(", ")}]"

 step = (current, val) ->
@@ -78,8 +79,8 @@
     str = ""
     v = @formatTest(@val)
     if @nextState.finalValue
-      str += dotNode(@nextState.id, {shape: "doublecircle"})
-    str += "#{dotEdge(@state.id, @nextState.id)}#{dotAttrs({label: v})};\n"
+      str += Graphviz.dotNode(@nextState.id, {shape: "doublecircle"})
+    str += "#{Graphviz.dotEdge(@state.id, @nextState.id)}#{Graphviz.dotAttrs({label: v})};\n"
     str

   formatTest: (val) ->


fix https://github.com/sagemathinc/smc/issues/1764 -- "emacs-mode find doesn't work with codemirror in SMC"

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -783,7 +783,6 @@
             "Shift-Ctrl-\\" : (editor)   => @action_key(execute:false, toggle_output:true)
             #"Shift-Cmd-y"  : (editor)   => @action_key(execute:false, toggle_output:true)

-            "Ctrl-S"       : (editor)   => @click_save_button()
             "Cmd-S"        : (editor)   => @click_save_button()

             "Ctrl-L"       : (editor)   => @goto_line(editor)
@@ -814,6 +813,10 @@

         if opts.match_xml_tags
             extraKeys['Ctrl-J'] = "toMatchingTag"
+
+        if opts.bindings != 'emacs'
+            # Emacs uses control s for find.
+            extraKeys["Ctrl-S"] = (editor) => @click_save_button()

         # FUTURE: We will replace this by a general framework...
         if misc.filename_extension_notilde(filename).toLowerCase() == "sagews"


chat window for new empty room needs to be vertically bigger

--- src/smc-webapp/smc_chat.cjsx
+++ src/smc-webapp/smc_chat.cjsx
@@ -620,6 +620,7 @@
             padding      : "0"
             paddingRight : "10px"
             background   : 'white'
+            flex         : 1

         chat_input_style =
             margin       : "0"
@@ -627,7 +628,7 @@
             marginTop    : "5px"
             fontSize     : @props.font_size

-        <Grid fluid={true} style={maxWidth: '1200px', display:'flex', flexDirection:'column'}>
+        <Grid fluid={true} className='smc-vfill' style={maxWidth: '1200px', display:'flex', flexDirection:'column'}>
             <Row style={marginBottom:'5px'}>
                 <Col xs={2} mdHidden>
                     <Button className='smc-small-only'
@@ -643,8 +644,8 @@
                     </ButtonGroup>
                 </Col>
             </Row>
-            <Row className='smc-vflex'>
-                <Col className='smc-vflex' md={12} style={padding:'0px 2px 0px 2px'}>
+            <Row className='smc-vfill'>
+                <Col className='smc-vfill' md={12} style={padding:'0px 2px 0px 2px'}>
                     <Well style={chat_log_style} ref='log_container' onScroll={@on_scroll}>
                         <ChatLog
                             messages     = {@props.messages}


Fix hidden send button on thin screens

Fix hidden send button on thin screens

--- src/smc-webapp/smc_chat.cjsx
+++ src/smc-webapp/smc_chat.cjsx
@@ -721,8 +721,8 @@
                     </Well>
                 </Col>
             </Row>
-            <Row>
-                <Col xs={10} md={11} style={padding:'0px 2px 0px 2px'}>
+            <Row style={display:'flex'}>
+                <Col style={flex:'1', padding:'0px 2px 0px 2px'}>
                     <SMC_Dropwrapper
                         project_id     = {@props.project_id}
                         dest_path      = {misc.normalized_path_join(@props.redux.getProjectStore(@props.project_id).get('current_path'), "/.chat-images")}
@@ -743,9 +743,7 @@
                         </FormGroup>
                     </SMC_Dropwrapper>
                 </Col>
-                <Col xs={2} md={1}
-                    style={height:'90px', padding:'0', marginBottom: '0', display:'flex', flexDirection:'column'}
-                    >
+                <Col style={height:'90px', padding:'0', marginBottom: '0', display:'flex', flexDirection:'column'} >
                     {<Button onClick={@button_on_click} disabled={@props.input==''}
                         bsStyle='info' style={height:'50%', width:'100%'}>
                         Preview


Adjust spec to use real CSV input.

Adjust spec to use real CSV input.

--- src/spec/mapping.spec.coffee
+++ src/spec/mapping.spec.coffee
@@ -1,5 +1,6 @@
 _ = require('underscore')._
 Mapping = require('../main').Mapping
+Validator = require('../main').Validator

 describe '#Mapping', ->
   beforeEach ->
@@ -10,11 +11,14 @@

 describe 'header2index', ->
   beforeEach ->
+    @validator = new Validator()
     @map = new Mapping()

   it 'should create mapping', ->
-    h2i = @map.header2index(['a', 'b'])
-    expect(_.size h2i).toBe 2
-    expect(h2i['a']).toBe 0
-    expect(h2i['b']).toBe 1
-
+    csv = 'productType,foo,variantId\n1,2,3'
+    @validator.parse csv, (data, count) =>
+      h2i = @map.header2index(data[0])
+      expect(_.size h2i).toBe 3
+      expect(h2i['productType']).toBe 0
+      expect(h2i['foo']).toBe 1
+      expect(h2i['variantId']).toBe 2


activity: increase timeout to 20000 secs

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -381,6 +381,7 @@
       KD.utils.getTimedOutCallbackOne
         name      : "populateActivity",
         onTimeout : @bound 'recover'
+        timeout   : 20000

   recover:->
     @isLoading = no


neo4j activityfeed: HACK stop race condition in activity feed

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -289,15 +289,18 @@
       error   : (err)->   callback? err
       success : (cache)=>
         cache.overview.reverse()  if cache?.overview
-        @lastTo = cache.to
-        @lastFrom = cache.from
-        console.log @lastFrom
+        #@lastTo = cache.to
+        #@lastFrom = cache.from
+        #console.log "lastFrom", @lastFrom
+        #console.log "lastTo", @lastTo
         callback null, cache

   continueLoadingTeasers:->

-    lastTimeStamp = (new Date @lastFrom or Date.now()).getTime()
-    @populateActivity {slug : "before/#{lastTimeStamp}", to: lastTimeStamp}
+    # HACK: this gets called multiple times if there's no wait
+    KD.utils.wait 1000, =>
+      lastTimeStamp = (new Date @lastFrom or Date.now()).getTime()
+      @populateActivity {slug : "before/#{lastTimeStamp}", to: lastTimeStamp}

   teasersLoaded:->
     # the page structure has changed


Teamwork: Updated firebase url.

Teamwork: Updated firebase url.

--- client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -21,7 +21,7 @@
     unless instanceName
       return warn "CollaborativeWorkspace requires a Firebase instance."

-    @firepadRef   = new Firebase "https://#{instanceName}.firebaseIO.com/"
+    @firepadRef   = new Firebase "https://#{instanceName}.firebaseio.com/"
     @sessionKey   = @getOptions().sessionKey or @createSessionKey()
     @workspaceRef = @firepadRef.child @sessionKey
     @broadcastRef = @workspaceRef.child "broadcast"


minor cleanup

minor cleanup

--- client/landing/app/MainApp/maincontroller/mainviewcontroller.coffee
+++ client/landing/app/MainApp/maincontroller/mainviewcontroller.coffee
@@ -27,15 +27,15 @@
     mainController.on "ToggleChatPanel", =>
       mainView.chatPanel.toggle()

-    mainView.ready ->
-      log "i am ready motherfucker"
-      i = 0
-      r = repeat 5000, ->
-        appManager.open lazyloadQueue[i++],
-          params       :
-            background : yes
-        log "i am opening #{lazyloadQueue[i-1]}"
-        killRepeat r if i is lazyloadQueue.length
+    # mainView.ready ->
+    #   log "i am ready motherfucker"
+    #   i = 0
+    #   r = repeat 5000, ->
+    #     appManager.open lazyloadQueue[i++],
+    #       params       :
+    #         background : yes
+    #     log "i am opening #{lazyloadQueue[i-1]}"
+    #     killRepeat r if i is lazyloadQueue.length

     if KD.checkFlag 'super-admin'
     then $('body').addClass 'super'


fixed indentation

fixed indentation

--- config.coffee
+++ config.coffee
@@ -1,8 +1,8 @@
-os = require "os"
+env = process.env

 module.exports =
-  redis_host: process.env.REDIS_IP or "localhost"
-  redis_port: process.env.REDIS_PORT or 6379
+  redis_host: env.REDIS_IP or "localhost"
+  redis_port: env.REDIS_PORT or 6379
   cache_ttl: 30 * 60
   http_client_timeout: 4
   http_max_sockets: 20
@@ -12,10 +12,7 @@
     site_description: "Craigslist with Dogecoin prices"

   session_key: "much wow"
-  debug: true
-  listen_port: 8888
-  listen_ip: "0.0.0.0"
-
-  # change this to a public hostname on prod:
-  http_url: "http://#{os.hostname()}:8888"
+  debug : ! env.OPENSHIFT_GEAR_DNS
+  listen_port : parseInt(env.OPENSHIFT_INTERNAL_PORT) or 8888
+  listen_ip : (env.OPENSHIFT_INTERNAL_IP or "127.0.0.1")


[ci skip] remove extra space

[ci skip] remove extra space

--- app/assets/javascripts/components/participant-selector-component.js.coffee
+++ app/assets/javascripts/components/participant-selector-component.js.coffee
@@ -1,6 +1,4 @@
 ETahi.ParticipantSelectorComponent = Ember.Component.extend
-
-
   resultsTemplate: (user) ->
     userInfo =
       if user.roles.length


test(cli-test.coffee): Rework E2E Test

test(cli-test.coffee): Rework E2E Test

Added ability to dump individual test results. Each test now in function invoked from `before()`
method. Converted tests to use `expect()`. Reworded some test descriptions. Changed Express app
response method to `.json()` shortening each test by two lines. Replaced existing "--timeout" test
as it didn't actually check whether Mocha stopped the test due to timeout occurring.

--- test/e2e/cli-test.coffee
+++ test/e2e/cli-test.coffee
@@ -1,8 +1,10 @@
-{assert} = require 'chai'
-{exec} = require 'child_process'
+chai = require 'chai'
+child_process = require 'child_process'
 express = require 'express'
-
-pkg = require '../../package.json'
+_ = require 'lodash'
+pkg = require '../../package'
+
+expect = chai.expect

 HOSTNAME = 'localhost'
 PORT = 3333
@@ -19,12 +21,36 @@
 ABAO_BIN = './bin/abao'
 MOCHA_BIN = './node_modules/mocha/bin/mocha'

+mochaJsonReportKeys = [
+  'stats',
+  'tests',
+  'pending',
+  'failures',
+  'passes'
+]
+
 stderr = ''
 stdout = ''
 report = ''
 exitStatus = null

 receivedRequest = {}
+
+#
+# To dump individual raw test results:
+#
+# describe('show me the results', () ->
+#   runTestAsync = (done) ->
+#     cmd = "#{ABAO_BIN}"
+#     execCommand cmd, done
+#   before (done) ->
+#     debugExecCommand = true
+#     runTestAsync done
+#   after () ->
+#     debugExecCommand = false
+#
+debugExecCommand = false
+

 execCommand = (cmd, callback) ->
   stderr = ''
@@ -32,17 +58,24 @@
   report = ''
   exitStatus = null

-  cli = exec CMD_PREFIX + cmd, (error, out, err) ->
+  cli = child_process.exec CMD_PREFIX + cmd, (error, out, err) ->
     stdout = out
     stderr = err
     try
       report = JSON.parse out
+    catch ignore
+      # Ignore issues with creating report from output

     if error
       exitStatus = error.code

   cli.on 'close', (code) ->
     exitStatus = code if exitStatus == null and code != undefined
+    if debugExecCommand
+      console.log "stdout:\n#{stdout}\n"
+      console.log "stderr:\n#{stderr}\n"
+      console.log "report:\n#{report}\n"
+      console.log "exitStatus = #{exitStatus}\n"
     callback()

@@ -50,37 +83,45 @@

   describe 'when run without any arguments', (done) ->

+    runNoArgTestAsync = (done) ->
+      cmd = "#{ABAO_BIN}"
+
+      execCommand cmd, done
+
     before (done) ->
-      cmd = "#{ABAO_BIN}"
-
-      execCommand cmd, done
-
-    it 'should exit with status 1', () ->
-      assert.equal exitStatus, 1
+      runNoArgTestAsync done

     it 'should print usage to stderr', () ->
-      assert.equal stderr.split('\n')[0], 'Usage:'
+      firstLine = stderr.split('\n')[0]
+      expect(firstLine).to.equal('Usage:')

     it 'should print error message to stderr', () ->
-      assert.include stderr, 'must specify path to RAML file'
+      expect(stderr).to.include('must specify path to RAML file')
+
+    it 'should exit due to error', () ->
+      expect(exitStatus).to.equal(1)

   describe 'when run with multiple positional arguments', (done) ->

-    before (done) ->
+    runTooManyArgTestAsync = (done) ->
       ramlFile = "#{RAML_DIR}/single-get.raml"
       cmd = "#{ABAO_BIN} #{ramlFile} #{ramlFile}"

       execCommand cmd, done

-    it 'should exit with status 1', () ->
-      assert.equal exitStatus, 1
+    before (done) ->
+      runTooManyArgTestAsync done

     it 'should print usage to stderr', () ->
-      assert.equal stderr.split('\n')[0], 'Usage:'
+      firstLine = stderr.split('\n')[0]
+      expect(firstLine).to.equal('Usage:')

     it 'should print error message to stderr', () ->
-      assert.include stderr, 'accepts single positional command-line argument'
+      expect(stderr).to.include('accepts single positional command-line argument')
+
+    it 'should exit due to error', () ->
+      expect(exitStatus).to.equal(1)

   describe 'when run with one-and-done options', (done) ->
@@ -91,137 +132,162 @@

         reporters = ''

-        before (done) ->
+        runReportersTestAsync = (done) ->
           execCommand "#{MOCHA_BIN} --reporters", () ->
             reporters = stdout
             execCommand "#{ABAO_BIN} --reporters", done

-        it 'exit status should be 0', () ->
-          assert.equal exitStatus, 0
+        before (done) ->
+          runReportersTestAsync done

         it 'should print same output as `mocha --reporters`', () ->
-          assert.equal stdout, reporters
+          expect(stdout).to.equal(reporters)
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)

       describe 'when invoked with "--version" option', () ->

-        before (done) ->
+        runVersionTestAsync = (done) ->
           cmd = "#{ABAO_BIN} --version"

           execCommand cmd, done

-        it 'should exit with status 0', () ->
-          assert.equal exitStatus, 0
+        before (done) ->
+          runVersionTestAsync done

         it 'should print version number to stdout', () ->
-          assert.equal stdout.trim(), pkg.version
+          expect(stdout.trim()).to.equal(pkg.version)
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)

       describe 'when invoked with "--help" option', () ->

-        before (done) ->
+        runHelpTestAsync = (done) ->
           cmd = "#{ABAO_BIN} --help"

           execCommand cmd, done

-        it 'should exit with status 0', () ->
-          assert.equal exitStatus, 0
+        before (done) ->
+          runHelpTestAsync done

         it 'should print usage to stdout', () ->
-          assert.equal stdout.split('\n')[0], 'Usage:'
+          firstLine = stdout.split('\n')[0]
+          expect(firstLine).to.equal('Usage:')
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)

     describe 'when RAML argument required', () ->

       describe 'when invoked with "--names" option', () ->

-        before (done) ->
+        runNamesTestAsync = (done) ->
           ramlFile = "#{RAML_DIR}/single-get.raml"
           cmd = "#{ABAO_BIN} #{ramlFile} --names"

           execCommand cmd, done

-        it 'exit status should be 0', () ->
-          assert.equal exitStatus, 0
+        before (done) ->
+          runNamesTestAsync done

         it 'should print names', () ->
-          assert.include stdout, 'GET /machines -> 200'
+          expect(stdout).to.include('GET /machines -> 200')

         it 'should not run tests', () ->
-          assert.notInclude stdout, '0 passing'
+          expect(stdout).to.not.include('0 passing')
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)

       describe 'when invoked with "--generate-hooks" option', () ->

         describe 'by itself', () ->

-          before (done) ->
+          runGenHooksTestAsync = (done) ->
             ramlFile = "#{RAML_DIR}/single-get.raml"
             cmd = "#{ABAO_BIN} #{ramlFile} --generate-hooks"

             execCommand cmd, done

-          it 'exit status should be 0', () ->
-            assert.equal exitStatus, 0
+          before (done) ->
+            runGenHooksTestAsync done

           it 'should print skeleton hookfile', () ->
-            assert.include stdout, '// ABAO hooks file'
+            expect(stdout).to.include('// ABAO hooks file')

           it 'should not run tests', () ->
-            assert.notInclude stdout, '0 passing'
+            expect(stdout).to.not.include('0 passing')
+
+          it 'should exit normally', () ->
+            expect(exitStatus).to.equal(0)

         describe 'with "--template" option', () ->

-          before (done) ->
+          runGenHookTemplateTestAsync = (done) ->
             templateFile = "#{TEMPLATE_DIR}/hookfile.js"
             ramlFile = "#{RAML_DIR}/single-get.raml"
             cmd = "#{ABAO_BIN} #{ramlFile} --generate-hooks --template #{templateFile}"

             execCommand cmd, done

-          it 'exit status should be 0', () ->
-            assert.equal exitStatus, 0
+          before (done) ->
+            runGenHookTemplateTestAsync done

           it 'should print skeleton hookfile', () ->
-            assert.include stdout, '// ABAO hooks file'
+            expect(stdout).to.include('// ABAO hooks file')

           it 'should not run tests', () ->
-            assert.notInclude stdout, '0 passing'
+            expect(stdout).to.not.include('0 passing')
+
+          it 'should exit normally', () ->
+            expect(exitStatus).to.equal(0)

       describe 'when invoked with "--template" but without "--generate-hooks" option', () ->

-        before (done) ->
+        runTemplateOnlyTestAsync = (done) ->
           templateFile = "#{TEMPLATE_DIR}/hookfile.js"
           ramlFile = "#{RAML_DIR}/single-get.raml"
           cmd = "#{ABAO_BIN} #{ramlFile} --template #{templateFile}"

           execCommand cmd, done

-        it 'exit status should be 1', () ->
-          assert.equal exitStatus, 1
+        before (done) ->
+          runTemplateOnlyTestAsync done

         it 'should print error message to stderr', () ->
-          assert.include stderr, 'Implications failed:'
-          assert.include stderr, 'template -> generate-hooks'
+          expect(stderr).to.include('Implications failed:')
+          expect(stderr).to.include('template -> generate-hooks')
+
+        it 'should exit due to error', () ->
+          expect(exitStatus).to.equal(1)

   describe 'when RAML file not found', (done) ->

-    before (done) ->
+    runNoRamlTestAsync = (done) ->
       ramlFile = "#{RAML_DIR}/nonexistent_path.raml"
       cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER}"

       execCommand cmd, done

-    it 'should exit with status 1', () ->
-      assert.equal exitStatus, 1
+    before (done) ->
+      runNoRamlTestAsync done

     it 'should print error message to stderr', () ->
       # See https://travis-ci.org/cybertk/abao/jobs/76656192#L479
       # iojs behaviour is different from nodejs
-      assert.include stderr, 'Error: ENOENT'
+      expect(stderr).to.include('Error: ENOENT')
+
+    it 'should exit due to error', () ->
+      expect(exitStatus).to.equal(1)

   describe 'arguments with existing RAML and responding server', () ->
@@ -230,34 +296,37 @@

       describe 'when RAML file does not specify "baseUri"', () ->

-        before (done) ->
+        runUnspecifiedServerTestAsync = (done) ->
           ramlFile = "#{RAML_DIR}/no-base-uri.raml"
           cmd = "#{ABAO_BIN} #{ramlFile} --reporter json"

           execCommand cmd, done

-        it 'should exit with status 1', () ->
-          assert.equal exitStatus, 1
+        before (done) ->
+          runUnspecifiedServerTestAsync done

         it 'should print error message to stderr', () ->
-          assert.include stderr, 'no API endpoint specified'
-
-
-      describe 'when RAML file does specify "baseUri"', () ->
-
-        before (done) ->
+          expect(stderr).to.include('no API endpoint specified')
+
+        it 'should exit due to error', () ->
+          expect(exitStatus).to.equal(1)
+
+
+      describe 'when RAML file specifies "baseUri"', () ->
+
+        resTestTitle = 'GET /machines -> 200 Validate response code and body'
+
+        runBaseUriServerTestAsync = (done) ->
           ramlFile = "#{RAML_DIR}/single-get.raml"
           cmd = "#{ABAO_BIN} #{ramlFile} --reporter json"

           app = express()

           app.get '/machines', (req, res) ->
-            res.setHeader 'Content-Type', 'application/json'
             machine =
               type: 'bulldozer'
               name: 'willy'
-            response = [machine]
-            res.status(200).send response
+            res.status(200).json([machine])

           server = app.listen PORT, () ->
             execCommand cmd, () ->
@@ -265,32 +334,38 @@

           server.on 'close', done

-        it 'exit status should be 0', () ->
-          assert.equal exitStatus, 0
+        before (done) ->
+          runBaseUriServerTestAsync done

         it 'should print count of tests run', () ->
-          assert.equal 1, report.tests.length
-          assert.equal 1, report.passes.length
+          expect(report).to.exist
+          expect(report).to.have.all.keys(mochaJsonReportKeys)
+          expect(report.stats.tests).to.equal(1)
+          expect(report.stats.passes).to.equal(1)

         it 'should print correct title for response', () ->
-          assert.equal report.tests[0].fullTitle, 'GET /machines -> 200 Validate response code and body'
+          expect(report.tests).to.have.length(1)
+          expect(report.tests[0].fullTitle).to.equal(resTestTitle)
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)

     describe 'when executing the command and the server is responding as specified in the RAML', () ->

-      before (done) ->
+      resTestTitle = 'GET /machines -> 200 Validate response code and body'
+
+      runNormalTestAsync = (done) ->
         ramlFile = "#{RAML_DIR}/single-get.raml"
         cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --reporter json"

         app = express()

         app.get '/machines', (req, res) ->
-          res.setHeader 'Content-Type', 'application/json'
           machine =
             type: 'bulldozer'
             name: 'willy'
-          response = [machine]
-          res.status(200).send response
+          res.status(200).json([machine])

         server = app.listen PORT, () ->
           execCommand cmd, () ->
@@ -298,32 +373,36 @@

         server.on 'close', done

-      it 'exit status should be 0', () ->
-        assert.equal exitStatus, 0
+      before (done) ->
+        runNormalTestAsync done

       it 'should print count of tests run', () ->
-        assert.equal 1, report.tests.length
-        assert.equal 1, report.passes.length
+        expect(report).to.exist
+        expect(report).to.have.all.keys(mochaJsonReportKeys)
+        expect(report.stats.tests).to.equal(1)
+        expect(report.stats.passes).to.equal(1)

       it 'should print correct title for response', () ->
-        assert.equal report.tests[0].fullTitle, 'GET /machines -> 200 Validate response code and body'
+        expect(report.tests).to.have.length(1)
+        expect(report.tests[0].fullTitle).to.equal(resTestTitle)
+
+      it 'should exit normally', () ->
+        expect(exitStatus).to.equal(0)

     describe 'when executing the command and RAML includes other RAML files', () ->

-      before (done) ->
+      runRamlIncludesTestAsync = (done) ->
         ramlFile = "#{RAML_DIR}/include_other_raml.raml"
         cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER}"

         app = express()

         app.get '/machines', (req, res) ->
-          res.setHeader 'Content-Type', 'application/json'
           machine =
             type: 'bulldozer'
             name: 'willy'
-          response = [machine]
-          res.status(200).send response
+          res.status(200).json([machine])

         server = app.listen PORT, () ->
           execCommand cmd, () ->
@@ -331,30 +410,31 @@

         server.on 'close', done

-      it 'exit status should be 0', () ->
-        assert.equal exitStatus, 0
+      before (done) ->
+        runRamlIncludesTestAsync done

       it 'should print count of tests run', () ->
-        assert.include stdout, '1 passing'
+        expect(stdout).to.have.string('1 passing')
+
+      it 'should exit normally', () ->
+        expect(exitStatus).to.equal(0)

     describe 'when called with arguments', () ->

       describe 'when invoked with "--reporter" option', () ->

-        before (done) ->
+        runReporterTestAsync = (done) ->
           ramlFile = "#{RAML_DIR}/single-get.raml"
           cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --reporter spec"

           app = express()

           app.get '/machines', (req, res) ->
-            res.setHeader 'Content-Type', 'application/json'
             machine =
               type: 'bulldozer'
               name: 'willy'
-            response = [machine]
-            res.status(200).send response
+            res.status(200).json([machine])

           server = app.listen PORT, () ->
             execCommand cmd, () ->
@@ -362,28 +442,33 @@

           server.on 'close', done

+        before (done) ->
+          runReporterTestAsync done
+
         it 'should print using the specified reporter', () ->
-          assert.include stdout, '1 passing'
+          expect(stdout).to.have.string('1 passing')
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)

       describe 'when invoked with "--header" option', () ->

         receivedRequest = {}

-        before (done) ->
-          ramlFile = "#{RAML_DIR}/single-get.raml"
-          cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --header Accept:application/json"
+        runHeaderTestAsync = (done) ->
+          extraHeader = 'Accept:application/json'
+          ramlFile = "#{RAML_DIR}/single-get.raml"
+          cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --header #{extraHeader}"

           app = express()

           app.get '/machines', (req, res) ->
             receivedRequest = req
-            res.setHeader 'Content-Type', 'application/json'
             machine =
               type: 'bulldozer'
               name: 'willy'
-            response = [machine]
-            res.status(200).send response
+            res.status(200).json([machine])

           server = app.listen PORT, () ->
             execCommand cmd, () ->
@@ -391,34 +476,36 @@

           server.on 'close', done

+        before (done) ->
+          runHeaderTestAsync done
+
         it 'should have an additional header in the request', () ->
-          assert.equal receivedRequest.headers.accept, 'application/json'
-
-        it 'exit status should be 0', () ->
-          assert.equal exitStatus, 0
+          expect(receivedRequest.headers.accept).to.equal('application/json')

         it 'should print count of tests run', () ->
-          assert.include stdout, '1 passing'
+          expect(stdout).to.have.string('1 passing')
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)

       describe 'when invoked with "--hookfiles" option', () ->

         receivedRequest = {}

-        before (done) ->
-          ramlFile = "#{RAML_DIR}/single-get.raml"
-          cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --hookfiles=#{HOOK_DIR}/*_hooks.*"
+        runHookfilesTestAsync = (done) ->
+          pattern = "#{HOOK_DIR}/*_hooks.*"
+          ramlFile = "#{RAML_DIR}/single-get.raml"
+          cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --hookfiles=#{pattern}"

           app = express()

           app.get '/machines', (req, res) ->
             receivedRequest = req
-            res.setHeader 'Content-Type', 'application/json'
             machine =
               type: 'bulldozer'
               name: 'willy'
-            response = [machine]
-            res.status(200).send response
+            res.status(200).json([machine])

           server = app.listen PORT, () ->
             execCommand cmd, () ->
@@ -426,30 +513,34 @@

           server.on 'close', done

+        before (done) ->
+          runHookfilesTestAsync done
+
         it 'should modify the transaction with hooks', () ->
-          assert.equal receivedRequest.headers['header'], '123232323'
-          assert.equal receivedRequest.query['key'], 'value'
+          expect(receivedRequest.headers['header']).to.equal('123232323')
+          expect(receivedRequest.query['key']).to.equal('value')

         it 'should print message to stdout and stderr', () ->
-          assert.include stdout, 'before-hook-GET-machines'
-          assert.include stderr, 'after-hook-GET-machines'
+          expect(stdout).to.include('before-hook-GET-machines')
+          expect(stderr).to.include('after-hook-GET-machines')
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)

       describe 'when invoked with "--hooks-only" option', () ->

-        before (done) ->
+        runHooksOnlyTestAsync = (done) ->
           ramlFile = "#{RAML_DIR}/single-get.raml"
           cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --hooks-only"

           app = express()

           app.get '/machines', (req, res) ->
-            res.setHeader 'Content-Type', 'application/json'
             machine =
               type: 'bulldozer'
               name: 'willy'
-            response = [machine]
-            res.status(200).send response
+            res.status(200).json([machine])

           server = app.listen PORT, () ->
             execCommand cmd, () ->
@@ -457,59 +548,97 @@

           server.on 'close', done

-        it 'exit status should be 0', () ->
-          assert.equal exitStatus, 0
+        before (done) ->
+          runHooksOnlyTestAsync done

         it 'should not run test without hooks', () ->
-          assert.include stdout, '1 pending'
+          expect(stdout).to.have.string('1 pending')
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)

       describe 'when invoked with "--timeout" option', () ->

-        cost = ''
-
-        before (done) ->
-          ramlFile = "#{RAML_DIR}/single-get.raml"
-          cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --timeout 100"
+        timeout = undefined
+        elapsed = -1
+        finished = undefined
+
+        runTimeoutTestAsync = (done) ->
+          ramlFile = "#{RAML_DIR}/single-get.raml"
+          cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --timeout #{timeout}"
+
+          beginTime = undefined
+          finished = false

           app = express()

-          t0 = ''
+          app.use (req, res, next) ->
+            beginTime = new Date()
+            res.on 'finish', () ->
+              finished = true
+            next()
+
+          app.use (req, res, next) ->
+            delay = timeout * 2
+            setTimeout next, delay
+
           app.get '/machines', (req, res) ->
-            t0 = new Date()
-
-          server = app.listen PORT, () ->
-            execCommand cmd, () ->
-              cost = new Date() - t0
-              server.close()
-
-          server.on 'close', done
-
-        it 'exit status should be 1', () ->
-          assert.equal exitStatus, 1
-
-        it 'should exit before timeout', () ->
-          assert.ok cost < 200
-
-        it 'should not run test without hooks', () ->
-          assert.include stdout, '0 passing'
-
-
-      describe 'when invoked with "--schema" option', () ->
-
-        before (done) ->
-          ramlFile = "#{RAML_DIR}/with-json-refs.raml"
-          cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --schemas=#{SCHEMA_DIR}/*.json"
-
-          app = express()
-
-          app.get '/machines', (req, res) ->
-            res.setHeader 'Content-Type', 'application/json'
             machine =
               type: 'bulldozer'
               name: 'willy'
-            response = [machine]
-            res.status(200).send response
+            res.status(200).json([machine])
+
+          server = app.listen PORT, () ->
+            execCommand cmd, () ->
+              endTime = new Date()
+              if finished
+                elapsed = endTime - beginTime
+                console.log "elapsed = #{elapsed} msecs (req/res)"
+              server.close()
+
+          server.on 'close', done
+
+
+        context 'given insufficient time to complete', () ->
+
+          before (done) ->
+            timeout = 20
+            console.log "timeout = #{timeout} msecs"
+            runTimeoutTestAsync done
+
+          after () ->
+            finished = undefined
+
+          it 'should not finish before timeout occurs', () ->
+            expect(finished).to.be.false
+
+          # Errors thrown by Mocha show up in stdout; those by Abao in stderr.
+          it 'Mocha should throw an error', () ->
+            detail = "Error: Timeout of #{timeout}ms exceeded."
+            expect(stdout).to.have.string(detail)
+
+          it 'should run test but not complete', () ->
+            expect(stdout).to.have.string('1 failing')
+
+          it 'should exit due to error', () ->
+            expect(exitStatus).to.equal(1)
+
+
+      describe 'when invoked with "--schema" option', () ->
+
+        runSchemaTestAsync = (done) ->
+          pattern = "#{SCHEMA_DIR}/*.json"
+          ramlFile = "#{RAML_DIR}/with-json-refs.raml"
+          cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --schemas=#{pattern}"
+
+          app = express()
+
+          app.get '/machines', (req, res) ->
+            machine =
+              type: 'bulldozer'
+              name: 'willy'
+            res.status(200).json([machine])

           server = app.listen PORT, () ->
             execCommand cmd, () ->
@@ -517,32 +646,37 @@

           server.on 'close', done

-        it 'exit status should be 0', () ->
-          assert.equal exitStatus, 0
-
-
-      describe 'when invoked with "--schema" option and expecting error', () ->
-
-        before (done) ->
-          ramlFile = "#{RAML_DIR}/with-json-refs.raml"
-          cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --schemas=#{SCHEMA_DIR}/*.json"
-
-          app = express()
-
-          app.get '/machines', (req, res) ->
-            res.setHeader 'Content-Type', 'application/json'
-            machine =
-              typO: 'bulldozer'
-              name: 'willy'
-            response = [machine]
-            res.status(200).send response
-
-          server = app.listen PORT, () ->
-            execCommand cmd, () ->
-              server.close()
-
-          server.on 'close', done
-
-        it 'exit status should be 1', () ->
-          assert.equal exitStatus, 1
-
+        before (done) ->
+          runSchemaTestAsync done
+
+        it 'should exit normally', () ->
+          expect(exitStatus).to.equal(0)
+
+
+        describe 'when expecting error', () ->
+
+          runSchemaFailTestAsync = (done) ->
+            pattern = "#{SCHEMA_DIR}/*.json"
+            ramlFile = "#{RAML_DIR}/with-json-refs.raml"
+            cmd = "#{ABAO_BIN} #{ramlFile} --server #{SERVER} --schemas=#{pattern}"
+
+            app = express()
+
+            app.get '/machines', (req, res) ->
+              machine =
+                typO: 'bulldozer'       # 'type' != 'typ0'
+                name: 'willy'
+              res.status(200).json([machine])
+
+            server = app.listen PORT, () ->
+              execCommand cmd, () ->
+                server.close()
+
+            server.on 'close', done
+
+          before (done) ->
+            runSchemaFailTestAsync done
+
+          it 'should exit due to error', () ->
+            expect(exitStatus).to.equal(1)
+


Refactor ProductsCtrl request params

Refactor ProductsCtrl request params

--- app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee
+++ app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee
@@ -46,27 +46,21 @@

   $scope.loadProducts = ->
     $scope.page = 1
-    params = {
+    Products.update($scope.queryParams())
+
+  $scope.loadMoreProducts = ->
+    Products.update($scope.queryParams($scope.page + 1), true)
+    $scope.page += 1
+
+  $scope.queryParams = (page = null) ->
+    {
       id: $scope.order_cycle.order_cycle_id,
-      page: $scope.page,
+      page: page || $scope.page,
       per_page: $scope.per_page,
       'q[name_or_meta_keywords_or_supplier_name_cont]': $scope.query,
       'q[properites_in_any][]': $scope.activeProperties,
       'q[primary_taxon_id_in_any][]': $scope.activeTaxons
     }
-    Products.update(params)
-
-  $scope.loadMoreProducts = ->
-    params = {
-      id: $scope.order_cycle.order_cycle_id,
-      page: $scope.page + 1,
-      per_page: $scope.per_page,
-      'q[name_or_meta_keywords_or_supplier_name_cont]': $scope.query,
-      'q[properites_in_any][]': $scope.activeProperties,
-      'q[primary_taxon_id_in_any][]': $scope.activeTaxons
-    }
-    Products.update(params, true)
-    $scope.page += 1

   $scope.searchKeypress = (e)->
     code = e.keyCode || e.which


Fix for Safari

Fix for Safari

Not perfect, but we have to deal with various date formats here :(

--- app/assets/javascripts/components/posts.js.cjsx
+++ app/assets/javascripts/components/posts.js.cjsx
@@ -168,7 +168,7 @@

{@props.post.title}

-          {moment((new Date(@props.post.date)).toISOString()).format("LLL")}
+          {moment((new Date(@props.post.date.replace(/-/g, "/"))).toISOString()).format("LLL")}


fixes visibility bug (closes #144)

fixes visibility bug (closes #144)

--- components/channel_visibility/client/channel_visibility_view.coffee
+++ components/channel_visibility/client/channel_visibility_view.coffee
@@ -1,19 +1,20 @@
 Backbone = require 'backbone'
 Backbone.$ = $
+_ = require 'underscore'
 analytics = require '../../../lib/analytics.coffee'

 template = -> require('../templates/channel_visibility.jade') arguments...

 module.exports = class ChannelVisibilityView extends Backbone.View
-  autoRender: true
-  autoSync: false
+  defaults:
+    autoRender: true
+    autoSync: false

   events:
     'click .metadata--selector__option' : 'toggleVisibility'

   initialize: (options) ->
-    @autoRender = options.autoRender || @autoRender
-    @autoSync = options.autoSync || @autoSync
+    { @autoRender, @autoSync } = _.defaults @defaults, options

     @render() if @autoRender


only setting color and size data attrs if elements exist - TODO use existential operatior

--- app/assets/javascripts/mobile/_mobile.js.coffee
+++ app/assets/javascripts/mobile/_mobile.js.coffee
@@ -72,8 +72,11 @@
                 data =
                     product_id: gon.product_id
                     quantity: (+($.trim(($ '#quantity').val()))) or 1
-                    color: $.trim(($ '#color').val())
-                    size: $.trim(($ '#size').val())
+                if ($ '#color').length > 0
+                    data.color = $.trim(($ '#color').val())
+                if ($ '#size').length > 0
+                    data.size = $.trim(($ '#size').val())
+

                 window.location = "#{gon.create_order_url}?#{$.param(data)}"


Add date to ember comment serializers

Add date to ember comment serializers

--- app/assets/javascripts/models/comment.js.coffee
+++ app/assets/javascripts/models/comment.js.coffee
@@ -2,3 +2,4 @@
   commenter: DS.belongsTo('user')
   body: DS.attr('string')
   messageTask: DS.belongsTo('messageTask')
+  createdAt: DS.attr('string')


chat: remove embedly previewing and getting data from embed box itself

--- client/Social/Activity/views/privatemessage/replyinputwidget.coffee
+++ client/Social/Activity/views/privatemessage/replyinputwidget.coffee
@@ -39,6 +39,9 @@
   empty: -> @input.empty()

+  getPayload: ->
+
+
   reset: (unlock = yes) ->

     @input.empty()


Trigger javascrip events

Trigger javascrip events

--- app/assets/javascripts/vitrage/editor.js.coffee
+++ app/assets/javascripts/vitrage/editor.js.coffee
@@ -1,5 +1,7 @@
 $(document).ready ->
   if $(".vtrg-add-new-wrapper").length
+
+    event_provider = $(".vtrg-add-new-wrapper")

     toggleEditorBlocker = ($editWrapper, act) ->
       $editWrapper.toggleClass 'blocked', act
@@ -10,6 +12,7 @@
         $editWrapper.removeClass "editmode"
         $editWrapper.children(".vtrg-edit-body").html viewdata
         toggleEditorBlocker $editWrapper, false
+        event_provider.trigger "vitrageshowed"
       else
         $.ajax
           url: "/vitrage/pieces/" + $editWrapper.data("id")
@@ -30,6 +33,7 @@

       $blockForm.on "ajax:success", (evnt, data, textStatus, jqXHR) ->
         restoreViewState $(@).closest(".vtrg-edit-wrapper"), data
+        event_provider.trigger "vitrageupdated"
       # $blockForm.on "ajax:error"
       # $blockForm.on "ajax:complete"

@@ -59,6 +63,7 @@
             $editWrapper.children(".vtrg-edit-body").html data
             coverEditFormActions $editWrapper
             toggleEditorBlocker $editWrapper, false
+            event_provider.trigger "vitrageedit"
           # error: null
         return
       return
@@ -78,6 +83,7 @@
             dataType: "html"
             success: (data, textStatus, jqXHR) ->
               $editWrapper.remove()
+              event_provider.trigger "vitragedestroyed"
             # error: null
         return
       return
@@ -104,6 +110,7 @@
         clwr.remove()
         initDestroyControl brandNewWrapper
         initEditControl brandNewWrapper
+        event_provider.trigger "vitragecreated"
       # $blockForm.on "ajax:error"
       $blockForm.on "ajax:complete", ->
         toggleEditorBlocker $(@).closest(".vtrg-edit-wrapper"), false
@@ -122,5 +129,6 @@
     $createAnchors.on "ajax:success", (evnt, data, textStatus, jqXHR) ->
       $(".vtrg-add-new-wrapper").before data
       coverNewFormActions $(".vtrg-edit-wrapper:last")
+      event_provider.trigger "vitragenew"
     # $createAnchors.on "ajax:error"
     # $createAnchors.on "ajax:complete"


CHeck favourited topic on 'slug_title'

CHeck favourited topic on 'slug_title'

--- core/app/backbone/views/channels/favourite_topic_button_view.coffee
+++ core/app/backbone/views/channels/favourite_topic_button_view.coffee
@@ -13,8 +13,7 @@
     enabled_label:  Factlink.Global.t.favourited.capitalize()

   buttonEnabled: ->
-    # @model.id is the Topic slug_title
-    @model.id in @user.favourite_topics.pluck('slug_title')
+    @model.get('slug_title') in @user.favourite_topics.pluck('slug_title')

   primaryAction: (e) ->
     @model.favourite()


hotfix

hotfix

--- model/Step.coffee
+++ model/Step.coffee
@@ -9,7 +9,7 @@
     @template or= @cls
     @i18nKey or= @cls
     _.defaults @,
-      domain: Meteor.settings.swf.domain
+      domain: Meteor.settings.swf?.domain
       workflowType:
         name: @cls
         version: @version or "1.0.0"


* [bug] forget to set the $cfgPath when loadConfig

--- src/index.coffee
+++ src/index.coffee
@@ -198,6 +198,7 @@
       return done(err) if err
       if aConfig
         that.assign aConfig, 'contents'
+        that.$cfgPath = aConfig.$cfgPath if aConfig.$cfgPath
         # aConfig.content: the markdown string removing the config.
         that.summary = aConfig.content if aConfig.content and !aConfig.summary
         if vDir = that.isDirectory()


Ensure color manipulation happens in LAB color space

--- src/index.coffee
+++ src/index.coffee
@@ -7,15 +7,14 @@
     contrast: 5.5
   }, options)

-  debug "Passed in color's luminance:", chroma(bg).luminance()
-  debug "Passed in color is", if chroma(bg).luminance() > 0.5 then "light" else "dark"
+  bg = chroma(bg, 'lab')
+  fg = chroma(bg, 'lab')

-  bg = chroma(bg)
-  fg = chroma(bg)
-
+  debug "Passed in color's luminance:", bg.luminance()
+  debug "Passed in color is", if bg.luminance() > 0.5 then "light" else "dark"

   # If the bg color is in the lighter side of the spectrum
-  if chroma(bg).luminance() > 0.5
+  if bg.luminance() > 0.5
     # Bg is dark so immediately brighten the fg to kick things off.
     fg = fg.darken(25)
     cycleCount = 0


:bug: Fix invalid affectation of namespace function

--- src/index.coffee
+++ src/index.coffee
@@ -25,7 +25,7 @@
   for p in path
     obj = obj[p] = obj[p] or {}

-window.namespace if window?
+window.namespace = namespace if window?

 agt.deprecated = (message) ->
   parseLine = (line) ->


* [bug] catch the yaml error 3

* [bug] catch the yaml error 3

--- src/index.coffee
+++ src/index.coffee
@@ -92,7 +92,7 @@
       if aOptions.raiseError
         throw err
       else
-        Logger('isdk').status 'error', folder, err.message
+        Logger('isdk').status 'error', folder.path, err.message
         return

     # check whether the cwd is changed via configuration!
@@ -107,7 +107,7 @@
         if aOptions.raiseError
           throw err
         else
-          Logger('isdk').status 'error', folder, err.message
+          Logger('isdk').status 'error', vFolder.path, err.message
           return
       setPrototypeOf vFolder, folder
       folder = vFolder


Remove unused vars

Remove unused vars

--- src/index.coffee
+++ src/index.coffee
@@ -1,8 +1,5 @@
 utils = require './utils'
-
-mongodb = require 'mongodb'
-client  = mongodb.MongoClient
-
+client = require('mongodb').MongoClient
 Collection = require './collection'

 class Easymongo


Added baseURL option

Added baseURL option

--- src/index.coffee
+++ src/index.coffee
@@ -71,8 +71,15 @@
     unixFilename = filename.replace(/\\/g, '/')
     linkPath = uri.absoluteTo(unixFilename).path()

+    # If baseURL then all internal links should be prefixed by it.
+    if options.baseURL
+      # Given they are not "absolute" we assume they should have the baseURL as a prefix
+      if (linkPath.indexOf options.baseURL) is -1
+        return true
+      linkPath = linkPath.split(options.baseURL).join("");
+
     # Special case for link to root
-    if linkPath is '/'
+    if linkPath is '/'
       return !fileExists(files, 'index.html')

     # Allow links to directories with a trailing slash
@@ -99,6 +106,7 @@
   options.allowRegex ?= null
   options.allowAnchors ?= true
   options.allowAnchors ?= false
+  options.baseURL ?= null

   if options.checkLinks and options.checkImages
     selector = 'a, img'


End init time if we aren’t initializing

End init time if we aren’t initializing

Signed-off-by: Arm1stice <96804f3875bc364d25340989c9f5960c99a00fd9@arm1stice.com>

--- src/index.coffee
+++ src/index.coffee
@@ -8,6 +8,6 @@
 ipcMain = (require 'electron').ipcMain
 loljs = require 'lol-js'
 lolClient = null;
-#console.timeEnd 'init'
+console.timeEnd 'init'

 console.log "hehe xd"


fix(index): save fetched remote settings locally

--- src/index.coffee
+++ src/index.coffee
@@ -40,3 +40,5 @@
 # to be that way.
 # TODO: ensure that this isn't overwritten by local settings.
 settings.fetchRemote()
+.then ->
+  settings.saveLocal()


Nevermind IIFE part

Nevermind IIFE part

--- src/jsonp.coffee
+++ src/jsonp.coffee
@@ -1,72 +1,71 @@
-do ->
-    createElement = (tag) -> window.document.createElement tag
-    encode = window.encodeURIComponent
-    random = Math.random
+createElement = (tag) -> window.document.createElement tag
+encode = window.encodeURIComponent
+random = Math.random

-    JSONP = (options) ->
+JSONP = (options) ->

-        options = if options then options else {}
-        params =
-            data: options.data or {}
-            error: options.error or noop
-            success: options.success or noop
-            beforeSend: options.beforeSend or noop
-            complete: options.complete or noop
-            url: options.url or ''
+    options = if options then options else {}
+    params =
+        data: options.data or {}
+        error: options.error or noop
+        success: options.success or noop
+        beforeSend: options.beforeSend or noop
+        complete: options.complete or noop
+        url: options.url or ''

-        params.computedUrl = computedUrl params
+    params.computedUrl = computedUrl params

-        throw new Error('MissingUrl') if params.url.length is 0
+    throw new Error('MissingUrl') if params.url.length is 0

-        done = false
+    done = false

-        if params.beforeSend({}, params) isnt false
+    if params.beforeSend({}, params) isnt false

-            callbackName = options.callbackName or 'callback'
-            callbackFunc = options.callbackFunc or 'jsonp_' + randomString 15
-            callback = params.data[callbackName] = callbackFunc
+        callbackName = options.callbackName or 'callback'
+        callbackFunc = options.callbackFunc or 'jsonp_' + randomString 15
+        callback = params.data[callbackName] = callbackFunc

-            window[callback] = (data) ->
-                window[callback] = null
-                params.success data, params
-                params.complete data, params
+        window[callback] = (data) ->
+            window[callback] = null
+            params.success data, params
+            params.complete data, params

-            script = createElement 'script'
-            script.src = computedUrl params
-            script.async = true
+        script = createElement 'script'
+        script.src = computedUrl params
+        script.async = true

-            script.onerror = (evt) ->
-                params.error { url: script.src, event: evt }
-                params.complete { url: script.src, event: evt }, params
+        script.onerror = (evt) ->
+            params.error { url: script.src, event: evt }
+            params.complete { url: script.src, event: evt }, params

-            script.onload = script.onreadystatechange = ->
-                if not done and (not this.readyState or this.readyState is 'loaded' or this.readyState is 'complete')
-                    done = true
-                    script.onload = script.onreadystatechange = null
-                    script.parentNode.removeChild script if script and script.parentNode
-                    script = null
-            head = head or window.document.getElementsByTagName('head')[0] or window.document.documentElement
-            # (see jQuery bugs #2709 and #4378)
-            head.insertBefore script, head.firstChild
+        script.onload = script.onreadystatechange = ->
+            if not done and (not this.readyState or this.readyState is 'loaded' or this.readyState is 'complete')
+                done = true
+                script.onload = script.onreadystatechange = null
+                script.parentNode.removeChild script if script and script.parentNode
+                script = null
+        head = head or window.document.getElementsByTagName('head')[0] or window.document.documentElement
+        # (see jQuery bugs #2709 and #4378)
+        head.insertBefore script, head.firstChild

-    noop = -> undefined
+noop = -> undefined

-    computedUrl = (params) ->
-        url = params.url
-        url += if params.url.indexOf('?') < 0 then '?' else '&'
-        url += objectToURI params.data
-        return url
+computedUrl = (params) ->
+    url = params.url
+    url += if params.url.indexOf('?') < 0 then '?' else '&'
+    url += objectToURI params.data
+    return url

-    randomString = (length) ->
-        str = ''
-        str += random().toString(36)[2] while str.length < length
-        return str
+randomString = (length) ->
+    str = ''
+    str += random().toString(36)[2] while str.length < length
+    return str

-    objectToURI = (obj) ->
-        data = []
-        data.push encode(key) + '=' + encode value for key, value of obj
-        return data.join '&'
+objectToURI = (obj) ->
+    data = []
+    data.push encode(key) + '=' + encode value for key, value of obj
+    return data.join '&'

-    if define? and define.amd then define -> JSONP
-    else if module? and module.exports then module.exports = JSONP
-    else window.JSONP = JSONP
+if define? and define.amd then define -> JSONP
+else if module? and module.exports then module.exports = JSONP
+else window.JSONP = JSONP


Don't show add column icons when fb-can-add-col isn't present, fixes #550

--- src/resources/forms/orbeon/builder/resources/section-grid-repeat/grid-repeat-editor.coffee
+++ src/resources/forms/orbeon/builder/resources/section-grid-repeat/grid-repeat-editor.coffee
@@ -91,12 +91,18 @@
         hideIcons = (icons) -> -> _.each (_.values icons), (icon) -> f$.hide icon.el
         showIcons = (icons) -> (rowOrCol) ->
             _.each icons, (icon) ->
-                dontShow = _.any ['delete-row', 'delete-col'], (operation) ->
-                    isDelete = f$.is '.fb-' + operation, icon.el
-                    isDelete and do ->
-                        gridDiv = rowOrCol.grid.el
-                        gridTable = f$.children '.fr-grid', gridDiv
-                        not f$.is '.fb-can-' + operation, gridTable
+                canDo = (operation) ->
+                    gridDiv = rowOrCol.grid.el
+                    gridTable = f$.children '.fr-grid', gridDiv
+                    f$.is '.fb-can-' + operation, gridTable
+                operationRequires =
+                    'delete-row': 'delete-row'
+                    'delete-col': 'delete-col'
+                    'insert-col-left': 'add-col'
+                    'insert-col-right': 'add-col'
+                dontShow = _.any (_.keys operationRequires), (operation) ->
+                    (f$.is '.fb-' + operation, icon.el) and         # Is this an icon for the current operation
+                        (not canDo operationRequires[operation])    # We can't perform the operation
                 unless dontShow
                     f$.show icon.el
                     f$.offset (icon.offset rowOrCol), icon.el


fix for ssh key error

fix for ssh key error

--- client/landing/app/Applications/Account.kdapplication/views/sshkeys.coffee
+++ client/landing/app/Applications/Account.kdapplication/views/sshkeys.coffee
@@ -183,9 +183,12 @@
       @info.$('span.key').text "#{key.substr(0,45)} . . . #{key.substr(-25)}"
       @swappable.swapViews()
       @getDelegate().emit "UpdatedItems"
-    else
+    else unless key
       new KDNotificationView
         title : "Key shouldn't be empty."
+    else unless title
+      new KDNotificationView
+        title : "Title required for SSH key."

   partial:(data)->
     """


Improve Slack config: tighten settings table layout

--- app/assets/javascripts/app/controllers/_integration/slack.coffee
+++ app/assets/javascripts/app/controllers/_integration/slack.coffee
@@ -27,11 +27,11 @@

     configureAttributes = [
       { name: 'types',     display: __('Trigger'),  tag: 'checkbox', options: options, 'null': false, class: 'vertical', note: __('When notification is being sent.') },
-      { name: 'group_ids', display: __('Group'),    tag: 'select', relation: 'Group', multiple: true, 'null': false, note: __('Only for these groups.') },
-      { name: 'webhook',   display: __('Webhook'),  tag: 'input', type: 'url',  limit: 200, 'null': false, placeholder: 'https://hooks.slack.com/services/...' },
-      { name: 'username',  display: __('Username'), tag: 'input', type: 'text', limit: 100, 'null': false, placeholder: 'username' },
-      { name: 'channel',   display: __('Channel'),  tag: 'input', type: 'text', limit: 100, 'null': true, placeholder: '#channel' },
-      { name: 'icon_url',  display: __('Icon URL'), tag: 'input', type: 'url',  limit: 200, 'null': true, placeholder: 'https://example.com/logo.png' },
+      { name: 'group_ids', display: __('Group'),    tag: 'select', relation: 'Group', multiple: true, 'null': false, class: 'form-control--small', note: __('Only for these groups.') },
+      { name: 'webhook',   display: __('Webhook'),  tag: 'input', type: 'url',  limit: 200, 'null': false, class: 'form-control--small', placeholder: 'https://hooks.slack.com/services/...' },
+      { name: 'username',  display: __('Username'), tag: 'input', type: 'text', limit: 100, 'null': false, class: 'form-control--small', placeholder: 'username' },
+      { name: 'channel',   display: __('Channel'),  tag: 'input', type: 'text', limit: 100, 'null': true, class: 'form-control--small', placeholder: '#channel' },
+      { name: 'icon_url',  display: __('Icon URL'), tag: 'input', type: 'url',  limit: 200, 'null': true, class: 'form-control--small', placeholder: 'https://example.com/logo.png' },
     ]

     settings = []


yellow boxes

yellow boxes

--- src/views/OverviewBox.coffee
+++ src/views/OverviewBox.coffee
@@ -85,7 +85,7 @@
     rectWidth = @g.zoomer.get "boxRectWidth"
     rectHeight = @g.zoomer.get "boxRectHeight"
     maxHeight = rectHeight * @model.length
-    @ctx.fillStyle = "#ffff00"
+    @ctx.fillStyle = "#666666"
     @ctx.globalAlpha = 0.9
     for i in [0.. @g.selcol.length - 1] by 1
       sel = @g.selcol.at(i)
@@ -111,7 +111,7 @@
     return if @dragStart.length is 0

     @render()
-    @ctx.fillStyle = "#ffff00"
+    @ctx.fillStyle = "#666666"
     @ctx.globalAlpha = 0.9

     rect = @_calcSelection( mouse.abs e )


Make activity default sorting to prevent loading weirdness

--- static/js/member-list.coffee
+++ static/js/member-list.coffee
@@ -132,7 +132,7 @@

         # Just to have something in the sort order,
         # this will be overriden once we get data
-        @set_sort_order "name"
+        @set_sort_order "recent_activity"

     _calculate_rankings: (collection) =>
         activity_scores = (model.attributes.stats['recent_activity'] \