QueryRegistry: Update it from resurrection, it was broken by merge request

--- workers/social/lib/social/models/graph/queryregistry.coffee
+++ workers/social/lib/social/models/graph/queryregistry.coffee
@@ -47,64 +47,32 @@
     count: (exemptClause)->
       """
       START group=node:koding(id={groupId})
-      MATCH group-[r:member]->members-[:follower]->currentUser
-      WHERE currentUser.id = {currentUserId}
-      RETURN members
-      #{orderByQuery}
-      SKIP {skipCount}
-      LIMIT {limitCount}
-    """
-  follower  : (orderByQuery)->
-    """
-      START group=node:koding(id={groupId})
-      MATCH group-[r:member]->members<-[:follower]-currentUser
-      WHERE currentUser.id = {currentUserId}
-      RETURN members
-      #{orderByQuery}
-      SKIP {skipCount}
-      LIMIT {limitCount}
-    """
-  list: (exemptClause, orderByQuery)->
-    """
-      START group=node:koding(id={groupId})
-      MATCH group-[r:member]->members
+      MATCH group-[:member]->members
       WHERE members.name="JAccount"
       #{exemptClause}
-      RETURN members
-      #{orderByQuery}
-      SKIP {skipCount}
-      LIMIT {limitCount}
-    """
-  count: (exemptClause)->
-    """
-    START group=node:koding(id={groupId})
-    MATCH group-[:member]->members
-    WHERE members.name="JAccount"
-    #{exemptClause}
-    RETURN count(members) as count
-    """
+      RETURN count(members) as count
+      """
+    search: (options)->
+      {seed, firstNameRegExp, lastNameRegexp, blacklistQuery, exemptClause} = options
+      """
+        START koding=node:koding(id={groupId})
+        MATCH koding-[r:member]->members

-  search: (options)->
-    {seed, firstNameRegExp, lastNameRegexp, blacklistQuery, exemptClause} = options
-    """
-      START koding=node:koding(id={groupId})
-      MATCH koding-[r:member]->members
+        WHERE  (
+          members.`profile.nickname` =~ '(?i)#{seed}'
+          and members.type = 'registered'
+          or members.`profile.firstName` =~ '(?i)#{firstNameRegExp}'
+          or members.`profile.lastName` =~ '(?i)#{lastNameRegexp}'
+        )

-      WHERE  (
-        members.`profile.nickname` =~ '(?i)#{seed}'
-        and members.type = 'registered'
-        or members.`profile.firstName` =~ '(?i)#{firstNameRegExp}'
-        or members.`profile.lastName` =~ '(?i)#{lastNameRegexp}'
-      )
+        #{blacklistQuery}
+        #{exemptClause}

-      #{blacklistQuery}
-      #{exemptClause}
-
-      RETURN members
-      ORDER BY members.`profile.firstName`
-      SKIP {skipCount}
-      LIMIT {limitCount}
-    """
+        RETURN members
+        ORDER BY members.`profile.firstName`
+        SKIP {skipCount}
+        LIMIT {limitCount}
+      """
   bucket      :
     newMembers :
       """
@@ -221,4 +189,3 @@
         MATCH group-[:#{relationshipName}]->items
         RETURN count(items) as count
       """
-


JPermissionSet: checkPermission: use superadmin flag to understand if this is for superadmins only permission

if a permission is defined for `superadmin: yes` which means that you can't use this permission from other group context rather than the MAIN_GROUP, until you are an admin on MAIN_GROUP you can't pass  this permissions.

--- workers/social/lib/social/models/group/permissionset.coffee
+++ workers/social/lib/social/models/group/permissionset.coffee
@@ -119,6 +119,16 @@
     kallback = (current, main) ->

       queue = advanced.map ({ permission, validateWith, superadmin }) -> ->
+
+        # if permission requires superadmin and current group is not 'koding'
+        # or if somehow 'koding' group (main) not exists then pass ~ GG
+        if superadmin and current.group.slug isnt MAIN_GROUP or not main
+          return queue.next()
+
+        # if permission requires superadmin then do the permission check on
+        # main group and permissionSet (which is 'koding' group) ~ GG
+        { group, permissionSet } = if superadmin then main else current
+
         # use Validators.any if it's not provided
         validateWith ?= anyValidator


Button label for keywords task 'Next' if there's another illustration to describe. 'Continue' if we're finished.

--- overrides.coffee
+++ overrides.coffee
@@ -96,9 +96,12 @@
   if task.key is 'details'
     LAST_TASK = rect_index == rectangles.length - 1 if rectangles.length
     if LAST_TASK
+      label = 'Continue'
       task.next = 'review'
     else
+      label = 'Next'
       task.next = 'details'
+    decisionTree.currentTask.confirmButton.innerHTML = label if label?

 classify_page.el.on decisionTree.CHANGE, ({originalEvent: {detail}})->
   {key, value} = detail


basic svg rescaling

basic svg rescaling

--- overrides.coffee
+++ overrides.coffee
@@ -8,6 +8,41 @@
 {decisionTree, subjectViewer} = classify_page

 ms = subjectViewer.markingSurface
+
+ms.rescale = (x, y, width, height) ->
+  return if ms.root.el.getBoundingClientRect().width is 0 # don't rescale when surface isn't visible
+  currentViewBox = @svg.attr('viewBox')?.split /\s+/
+  x ?= currentViewBox?[0] ? 0
+  y ?= currentViewBox?[1] ? 0
+  width ?= currentViewBox?[2] ? 0
+  height ?= currentViewBox?[3] ? 0
+  @svg.attr 'viewBox', [x, y, width, height].join ' '
+  @scaleX = ms.root.el.getBoundingClientRect().width / subjectViewer.maxWidth
+  @scaleY = ms.root.el.getBoundingClientRect().height / subjectViewer.maxHeight
+  ms.magnification = ms.root.el.getBoundingClientRect().width / subjectViewer.maxWidth
+  @renderTools()
+
+ms.screenPixelToScale = ({x, y}) ->
+  if @svg.el.viewBox.animVal?
+    x = x / @scaleX
+    y = y / @scaleY
+    viewBox = @svg.el.viewBox.animVal
+    x += viewBox.x
+    y += viewBox.y
+  {x, y}
+
+ms.scalePixelToScreen = ({x, y}) ->
+  if @svg.el.viewBox.animVal?
+    viewBox = @svg.el.viewBox.animVal
+    x -= viewBox.x
+    y -= viewBox.y
+    x = x * @scaleX
+    y = y * @scaleY
+  {x, y}
+
+# set the image scale if not already set
+ms.on 'marking-surface:add-tool', (tool) ->
+  ms.rescale() if ms.magnification is 0

 # moving back and forward through the array of marked SVG rectangles
 classify_page.el.on decisionTree.LOAD_TASK, ({originalEvent: detail: {task}})->


more cleanups

more cleanups

--- src/chrome.iced
+++ src/chrome.iced
@@ -2,7 +2,6 @@
 inp = ( 0x36363636 for i in [0...3])
 wa = CryptoJS.lib.WordArray.create inp

-pp = ""
 $ -> run_test()

 run_test = () ->
@@ -11,10 +10,9 @@
   for i in [0...100]
     sha = CryptoJS.algo.SHA512.create()
     out = sha.finalize wa
-    if not val?
+    if i is 0
       first = out
-      val = out.words[0]
-    else if val isnt out.words[0]
+    else if out.words[0] isnt first.words[0]
       bads.push [i].concat out.words
   display_result first, bads

@@ -28,7 +26,6 @@
     headings = ["Attempt"].concat("Word #{b}" for b in [0...32])
     ths = ("#{h}	" for h in headings)
     headrow = "
" + ths.join(" ") + "
"
-
     tab = "
Errors Found!
\n
#{headrow} #{trs.join('\n')}
"
     $("#shitcan").append tab
   else


Import underscore.js.

Import underscore.js.

--- src/coffee/producttypegenerator.coffee
+++ src/coffee/producttypegenerator.coffee
@@ -1,5 +1,6 @@
 FS = require 'fs'
 Q = require 'q'
+_ = require("underscore")._

 ###
 Class for generating JSON product type representations from CSV files.


0.5.0 Beta

0.5.0 Beta

Planning for a breaking change.

Signed-off-by: Byron Ruth <e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98@devel.io>

--- jsonpatch.coffee
+++ jsonpatch.coffee
@@ -1,4 +1,4 @@
-# jsonpatch.js 0.4.4
+# jsonpatch.js 0.5.0
 # (c) 2011-2012 Byron Ruth
 # jsonpatch may be freely distributed under the BSD license


Remove debug prints.

Remove debug prints.

--- services/web/public/coffee/ide/references-search/ReferencesSearchManager.coffee
+++ services/web/public/coffee/ide/references-search/ReferencesSearchManager.coffee
@@ -2,7 +2,6 @@
 ], () ->
 	class ReferencesSearchManager
 		constructor: (@ide, @$scope) ->
-			console.log ">> yay"

 			@$scope.$on 'document:closed', (e, doc) =>
 				if doc.doc_id
@@ -13,7 +12,6 @@
 						@indexReferences doc.doc_id

 		indexReferences: (doc_id) ->
-			console.log ">> doc id #{doc_id}"
 			$.post(
 				"/project/#{@$scope.project_id}/references",
 				{


Stop deletes overlapping comments (but leads to occasional flickering)

--- services/web/public/coffee/ide/review-panel/directives/reviewPanelSorted.coffee
+++ services/web/public/coffee/ide/review-panel/directives/reviewPanelSorted.coffee
@@ -23,7 +23,8 @@

 				scope.$watch "reviewPanel.entries", (value) ->
 					return if !value?
-					layout()
+					$timeout () ->
+						layout()

 				scope.$on "review-panel:layout", () ->
 					$timeout () ->


Stop using PostScript highlighting for PDF files

--- config.cson
+++ config.cson
@@ -13,9 +13,6 @@
 			]
 			"source.perl.5": [
 				"t"
-			]
-			"source.postscript": [
-				"pdf"
 			]
 			"source.python": [
 				"WATCHLISTS"


Added resize event.

Added resize event.

--- src/documents/drc/live/index.js.coffee
+++ src/documents/drc/live/index.js.coffee
@@ -117,7 +117,7 @@
   stats.domElement.style.position = 'relative'
   stats.domElement.style.float = 'right'

-  $(window).on('orientationchange', () ->
+  $(window).on('orientationchange resize', () ->
     # I added a delay to this effect because I found that the size recalculation
     # is more reliable if we wait a moment after the orientation change.
     setTimeout(adaptCanvasSize, 500)


Use the new hash

Use the new hash

--- resources/assets/coffee/react/beatmap-discussions/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions/main.coffee
@@ -323,7 +323,7 @@
     return callback?() if !id?
     return callback?() if id == @state.currentBeatmap.id

-    beatmap = _.find @state.beatmapset.beatmaps, id: id
+    beatmap = @beatmaps()[id]

     return callback?() if !beatmap?


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -109,39 +109,73 @@

   individualRgb = Color(23, 56, 49, 0.4)

-  individualRgb.toString()
-  # => 'rgba(23, 56, 49, 0.4)'
+  canvas.drawRect
+    x: 20
+    y: 20
+    width: 20
+    height: 20
+    color: individualRgb

   arrayRgb = Color([59, 100, 230])

-  arrayRgb.toString()
-  # => 'rgba(59, 100, 230, 1)'
+  canvas.drawRect
+    x: 50
+    y: 20
+    width: 20
+    height: 20
+    color: individualRgb

   hex = Color('#ff0000')

-  hex.toString()
-  # => 'rgba(255, 0, 0, 1)'
+  canvas.drawRect
+    x: 80
+    y: 20
+    width: 20
+    height: 20
+    color: individualRgb

   rgb = Color('rgb(0, 255, 0)')

-  rgb.toString()
-  # => 'rgba(0, 255, 0, 1)'
+  canvas.drawRect
+    x: 130
+    y: 20
+    width: 20
+    height: 20
+    color: rgb

   hsl = Color('hsl(180, 1, 0.5)')

-  hsl.toString()
-  # => 'rgba(0, 255, 255, 1)'
+  canvas.drawRect
+    x: 160
+    y: 20
+    width: 20
+    height: 20
+    color: hsl

   anotherColor = Color('blue')

   Color(anotherColor)
   # => a new color with the same r, g, b, and alpha values as `anotherColor`
+
+  canvas.drawRect
+    x: 20
+    y: 60
+    width: 20
+    height: 20
+    color: anotherColor

   # You have access to all sorts of weird colors.
   # We give you all the named colors the browser recognizes
   # and the ones from this survey
   # http://blog.xkcd.com/2010/05/03/color-survey-results/
   namedBrown = Color('Fuzzy Wuzzy Brown')
+
+  canvas.drawRect
+    x: 50
+    y: 60
+    width: 20
+    height: 20
+    color: namedBrown

   namedBrown.toHex()
   # => '#c45655'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -737,6 +737,13 @@
     ###*
     A getter / setter for the saturation value of the color. Passing no argument returns the
     current saturation value. Passing a value will set the saturation to that value and return the color.
+
+

+    hslColor = Color('hsl(60, 0.5, 0.5)')
+
+    hslColor.saturation()
+    # => 0.5
+

     @name saturation
     @methodOf Color#


collision with gun smaller circle

collision with gun smaller circle

--- src/lib/Character/Player/Player.coffee
+++ src/lib/Character/Player/Player.coffee
@@ -56,7 +56,7 @@
                     x: @sprite.body.x + @sprite.body.width * @sprite.anchor.x + r * Math.sin(angle)
                     y: @sprite.body.y + @sprite.body.height * @sprite.anchor.y - r * Math.cos(angle)
                 d2 = Math.sqrt(Math.pow(gunLocation.x - v.sprite.body.x - v.sprite.body.width  * 0.5, 2) + Math.pow(gunLocation.y - v.sprite.body.y - v.sprite.body.height * 0.5, 2))
-                if (d < 70 or d2 < 55) and v.alive > 0
+                if (d < 70 or d2 < 45) and v.alive > 0
                     @destroy()


Update variable names to match extensions

--- src/cson.coffee
+++ src/cson.coffee
@@ -124,11 +124,11 @@

     return objectPath if @isObjectPath(objectPath) and fs.isFileSync(objectPath)

-    csonPath = "#{objectPath}.json"
+    jsonPath = "#{objectPath}.json"
+    return jsonPath if fs.isFileSync(jsonPath)
+
+    csonPath = "#{objectPath}.cson"
     return csonPath if fs.isFileSync(csonPath)
-
-    jsonPath = "#{objectPath}.cson"
-    return jsonPath if fs.isFileSync(jsonPath)

     null


more fix for logout

more fix for logout

--- app/authorization.coffee
+++ app/authorization.coffee
@@ -63,6 +63,7 @@

   @logout: ->
     if window.cordova
+      @deleteToken()
       if window.device.platform.indexOf('Android')!=-1
         navigator.app.loadUrl Config.oauthEndpoint.replace('/oauth/','/accounts/sign_out/')
       else


DigitalOcean.Provider: options is mandatory with the arguments

--- workers/social/lib/social/models/computeproviders/digitalocean.coffee
+++ workers/social/lib/social/models/computeproviders/digitalocean.coffee
@@ -2,8 +2,8 @@

 module.exports = class DigitalOcean extends ProviderInterface

-  @ping = (client, callback)->
+  @ping = (client, options, callback)->
     callback null, "DigitalOcean is better #{ client.connection.delegate.profile.nickname }!"

-  @fetchExisting = (client, callback)->
+  @fetchExisting = (client, options, callback)->
     callback null, []


check more machine properties

check more machine properties

--- workers/social/lib/social/models/computeproviders/machine.test.coffee
+++ workers/social/lib/social/models/computeproviders/machine.test.coffee
@@ -88,6 +88,12 @@
               expect(err).to.not.exist
               expect(machine.provider).to.be.equal machineParams.provider
               expect(machine.status.state).to.be.equal 'NotInitialized'
+              expect(machine.label).to.exist
+              expect(machine.users.length).to.be.above 0
+              expect(machine.users[0].sudo).to.be.ok
+              expect(machine.users[0].owner).to.be.ok
+              expect(machine.domain).to.exist
+              expect(machine.slug).to.exist
               queue.next()

           -> done()


default cursor

default cursor

--- app/graph_drawing.coffee
+++ app/graph_drawing.coffee
@@ -128,7 +128,7 @@

   svg = d3.select(container)
     .append('svg')
-    .style({'font-family': '"Helvetica Neue"'})
+    .style({'font-family': '"Helvetica Neue"', cursor: 'default'})
     .on('mousemove', (d, i) -> mouseMoves.push d3.mouse g.node())

   title = svg.append('text')


New questions for Adler After Dark

New questions for Adler After Dark

--- app/lib/questions.coffee
+++ app/lib/questions.coffee
@@ -1,55 +1,31 @@
 module.exports = [{
-  "title": "The Pluto Question",
-  "slug": "pluto_planet",
-  "question": "Should Pluto be called a “planet?”",
+  "title": "Our Expanding Universe",
+  "slug": "expanding_universe",
+  "question": "Why is the universe expanding?",
   "options": [
-    "Yes",
-    "No",
-    "Not sure"
+    "Ate way too much on Taco Tuesday",
+    "Dark energy",
+    "Because it's a pack rat",
+    "Manifest Destiny"
   ]
 }, {
-  "title": "Space Vacation",
-  "slug": "space_vacation",
-  "question": "What planet would you most like to visit/explore?",
+  "title": "A Very Very Dark Hole...",
+  "slug": "black_holes",
+  "question": "What’s really happening at the center of a black hole?",
   "options": [
-    "Mercury",
-    "Venus",
-    "Mars",
-    "Jupiter",
-    "Saturn",
-    "Uranus",
-    "Neptune"
+    "Carl Sagan’s playing bocce ball with Galileo",
+    "2-for-1 sale on vacuums (Oreck XL)"
+    "Matter orgy",
+    "Donald Trump’s hair is fighting itself"
   ]
 }, {
-  "title": "Otherworldly Objects",
-  "slug": "fantastic_features",
-  "question": "Besides planets, what is the coolest astronomical object?",
+  "title": "Opposite of Light Matter",
+  "slug": "dark_matters",
+  "question": "Why is dark matter so dark?",
   "options": [
-    "Black holes",
-    "Comets",
-    "Galaxies",
-    "Nebulae",
-    "Stars",
-    "Other"
-  ]
-}, {
-  "title": "NASA's Pinnacle",
-  "slug": "nasa_pinnacle",
-  "question": "What do you consider to be NASA’s greatest accomplishment so far?",
-  "options": [
-    "Moon landing",
-    "Mars rovers",
-    "Voyager probes",
-    "Hubble Space Telescope",
-    "Other"
-  ]
-}, {
-  "title": "E.T.",
-  "slug": "et",
-  "question": "Do you believe there is life on other planets?",
-  "options": [
-    "Yes",
-    "No",
-    "Not sure"
+    "Overslept in the tanning bed",
+    "Refuses to turn on nightlight",
+    "Troubled childhood (not unlike Batman)",
+    "It’s not dark — it’s just pensive"
   ]
 }]


remove ExtractTextPlugin

remove ExtractTextPlugin

Styles are now loaded as part of the JS chunk so they can be dynamic
and load via System.import

--- configs/webpack/base.coffee
+++ configs/webpack/base.coffee
@@ -3,7 +3,6 @@
 _ = require 'lodash'
 webpack = require 'webpack'
 UglifyJsPlugin = require 'uglifyjs-webpack-plugin'
-ExtractTextPlugin = require 'extract-text-webpack-plugin'
 HappyPack = require 'happypack'
 happyThreadPool = HappyPack.ThreadPool(size: 8);

@@ -41,9 +40,9 @@
   jsx:    RESOLVABLES.jsx
   coffee: RESOLVABLES.coffee
   cjsx:   RESOLVABLES.cjsx
-  css:  { test: /\.css$/,  use: ExtractTextPlugin.extract([ STYLE_LOADERS.css ]) }
-  less: { test: /\.less$/, use: ExtractTextPlugin.extract([ STYLE_LOADERS.css, STYLE_LOADERS.less ]) }
-  scss: { test: /\.scss$/, use: ExtractTextPlugin.extract([ STYLE_LOADERS.css, STYLE_LOADERS.scss ]) }
+  css:  { test: /\.css$/,  use: STYLE_LOADERS.css }
+  less: { test: /\.less$/, use: [ STYLE_LOADERS.css, STYLE_LOADERS.less ] }
+  scss: { test: /\.scss$/, use: [ STYLE_LOADERS.css, STYLE_LOADERS.scss ] }

 DEV_LOADERS = ['react-hot-loader/webpack']

@@ -79,7 +78,6 @@
     ]
     rules: _.values(STATICS)
   plugins: [
-    new ExtractTextPlugin('[name].css'),
     HAPPY_PACK_PLUGINS...
   ]

@@ -104,16 +102,10 @@
   path: "#{projectConfig.basePath}/dist"
   publicPath: "/assets/"

+# This used to return extractText plugin but that's no longer used
+# Method is left in case there's more plugins that only are used in build later
 makeBuildPlugins = (projectConfig) ->
-  {styleFilename} = projectConfig
-
-  styleFilename ?= '[name].css'
-
-  [
-    new ExtractTextPlugin(
-      styleFilename
-    )
-  ]
+  []

 makePathsBase = (projectConfig) ->
   {basePath} = projectConfig


Add oninput to contributions new

Add oninput to contributions new

--- app/assets/javascripts/neighborly/projects/contributions/new.js.coffee
+++ app/assets/javascripts/neighborly/projects/contributions/new.js.coffee
@@ -6,7 +6,7 @@
   init: ->(
     initialize: ->
       $('.maturity-option .header').click this.boxClicked
-      $('.order-size-input').on 'keyup', this.orderSizeChanged
+      $('.order-size-input').on 'keyup input', this.orderSizeChanged

     boxClicked: (event) ->
       $('.maturity-option').removeClass('selected')


don't log out when doc lines are [] as this is a new empty doc

--- app/coffee/DocManager.coffee
+++ app/coffee/DocManager.coffee
@@ -16,7 +16,8 @@
 					if docFromDocCollection?
 						return callback null, docFromDocCollection, mongoPath
 					else if doc?
-						logger.warn project_id:project_id, doc_id:doc_id, "doc just used from project collection, why?"
+						if doc?.lines?.length > 0
+							logger.warn project_id:project_id, doc_id:doc_id, "doc just used from project collection, why?"
 						return callback null, doc, mongoPath
 					else
 						return callback new Errors.NotFoundError("No such doc: #{project_id}")


Only append the version to the url if it exists

--- src/scripts/modules/media/media.coffee
+++ src/scripts/modules/media/media.coffee
@@ -59,10 +59,11 @@

     updateUrl: () ->
       components = linksHelper.getCurrentPathComponents()
+      components.version = "@#{components.version}" if components.version
       title = linksHelper.cleanUrl(@model.get('title'))

       if title isnt components.title
-        router.navigate("contents/#{components.uuid}@#{components.version}/#{title}", {replace: true})
+        router.navigate("contents/#{components.uuid}#{components.version}/#{title}", {replace: true})

     trackAnalytics: () ->
       # Track loading using the media's own analytics ID, if specified


Modified to use Constants

Modified to use Constants

--- Src/coffeescript/cql-execution/src/datatypes/datetime.coffee
+++ Src/coffeescript/cql-execution/src/datatypes/datetime.coffee
@@ -50,19 +50,19 @@

   successor: () ->
     if @millisecond?
-      @add(1,"millisecond")
+      @add(1,DateTime.Unit.MILLISECOND)
     else if @second?
-      @add(1,"second")
+      @add(1,DateTime.Unit.SECOND)
     else if @minute?
-      @add(1,"minute")
+      @add(1,DateTime.Unit.MINUTE)
     else if @hour?
-      @add(1,"hour")
+      @add(1,DateTime.Unit.HOUR)
     else if @day?
-      @add(1,"day")
+      @add(1,DateTime.Unit.DAY)
     else if @month?
-      @add(1,"month")
+      @add(1,DateTime.Unit.MONTH)
     else if @year?
-      @add(1,"year")
+      @add(1,DateTime.Unit.YEAR)

   predecessor: () ->


Reverse conditional logic so error throws correctly

--- app/pages/project/index.cjsx
+++ app/pages/project/index.cjsx
@@ -198,13 +198,13 @@

   selectRandomWorkflow: (project) ->
     linkedActiveWorkflows = project.links.active_workflows
-    if linkedActiveWorkflows.length is 0
-      throw new Error "No workflows for project #{project.id}"
-      project.uncacheLink 'workflows'
-    else
+    if linkedActiveWorkflows.length > 0
       randomIndex = Math.floor Math.random() * linkedActiveWorkflows.length
       # console.log 'Chose random workflow', linkedActiveWorkflows[randomIndex]
       linkedActiveWorkflows[randomIndex]
+    else
+      throw new Error "No workflows for project #{project.id}"
+      project.uncacheLink 'workflows'

   getWorkflow: (selectedWorkflowID, activeFilter = true) ->
     query =


Changed rating calculation to be a bit lower.

--- public/main.coffee
+++ public/main.coffee
@@ -62,7 +62,7 @@

   renderRating: (val, outOf) ->
     if val?
-      stars = Math.ceil(val / outOf * 7)
+      stars = Math.round(val / outOf * 6) + 1
       React.DOM.span className: "rating",
         React.DOM.span className: "stars-#{stars}"
     else


tests: Try longer setup timeout

tests: Try longer setup timeout

Seems to have started failing on nodejs 4.2

--- spec/setup.coffee
+++ spec/setup.coffee
@@ -62,7 +62,7 @@
   participants = null

   beforeEach (done) ->
-    @timeout 4000
+    @timeout 8000
     options =
       graphfile: fixturePath 'simple.fbp'
       libraryfile: fixturePath 'library-simple.json'


removes checkout_controller

removes checkout_controller

--- app/assets/javascripts/spree_checkout/controllers/checkout_controller.js.coffee
+++ app/assets/javascripts/spree_checkout/controllers/checkout_controller.js.coffee
@@ -1,73 +0,0 @@
-# for more details see: http://emberjs.com/guides/controllers/
-
-SpreeCheckout.CheckoutController = Ember.ObjectController.extend
-
-  isCart: ( ->
-    @get('model.state') == 'cart'
-  ).property('model.state')
-
-  isAddress: ( ->
-    @get('model.state') == 'address'
-  ).property('model.state')
-
-  isDelivery: ( ->
-    @get('model.state') == 'delivery'
-  ).property('model.state')
-
-  isPayment: ( ->
-    @get('model.state') == 'payment'
-  ).property('model.state')
-
-  isConfirm: ( ->
-    @get('model.state') == 'confirm'
-  ).property('model.state')
-
-  transitionTo: (state) ->
-    if state == 'complete'
-      window.location.replace("/orders/#{@get('model.number')}")
-    else
-      @transitionToRoute("checkout.#{state}")
-
-  checkQuantity: ->
-    if @get('model.total_quantity') <= 0
-      window.location.replace('/cart')
-
-  reloadCart: ->
-    @get('model').reload().then =>
-      @checkQuantity()
-
-  actions:
-    goToState: (state) ->
-      @get('model').goToState(state).then =>
-        @transitionTo(@get('model.state'))
-
-    apply_coupon_code: ->
-      @get('model').applyCouponCode()
-
-    update_line_item: (line_item) ->
-      line_item.save().then =>
-        @reloadCart()
-
-    destroy_line_item: (line_item) ->
-      line_item.destroyRecord().then =>
-        @reloadCart()
-
-    updateAddresses: ->
-      @get('model').updateAddresses().then =>
-        @transitionTo(@get('model.state'))
-
-    updatePayment: ->
-      @get('model').updatePayment().then =>
-        @transitionTo(@get('model.state'))
-
-    selectShippingRate: ->
-      @get('model').selectShippingRate().then =>
-        @transitionTo(@get('model.state'))
-
-    confirm: ->
-      @get('model').confirm().then =>
-        @transitionTo(@get('model.state'))
-
-    next: ->
-      @get('model').next().then =>
-        @transitionTo(@get('model.state'))


Relative urls 2

Relative urls 2

oops...

--- app/assets/javascripts/comfortable_mexican_sofa/application.js.coffee
+++ app/assets/javascripts/comfortable_mexican_sofa/application.js.coffee
@@ -53,12 +53,12 @@

 window.CMS.wysiwyg = ->
   tinymce.init
-    selector:     'textarea[data-cms-rich-text]'
-    plugins:      ['link', 'image', 'code']
-    toolbar:      'undo redo | styleselect | bullist numlist | link unlink image | code'
-    menubar:      false
-    statusbar:    false
-    relative_url: false
+    selector:       'textarea[data-cms-rich-text]'
+    plugins:        ['link', 'image', 'code']
+    toolbar:        'undo redo | styleselect | bullist numlist | link unlink image | code'
+    menubar:        false
+    statusbar:      false
+    relative_urls:  false

 window.CMS.codemirror = ->
   $('textarea[data-cms-cm-mode]').each (i, element) ->


fixed typo

fixed typo

--- app/assets/javascripts/comfortable_mexican_sofa/application.js.coffee
+++ app/assets/javascripts/comfortable_mexican_sofa/application.js.coffee
@@ -68,7 +68,7 @@
       autoCloseTags:  true
       lineNumbers:    true
     CMS.code_mirror_instances.push(cm)
-    $(element).resizable resize: ->
+    $(cm.display.wrapper).resizable resize: ->
       cm.setSize($(@).width(), $(@).height())
       cm.refresh()
       return


fix weird resizing when moving upper handle

--- app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
@@ -97,6 +97,7 @@
   handleUpperResize: (e, d) ->
     console.log 'HANDLE UPPER RESIZE'
     @props.mark.yUpper += d.y / @props.yScale
+    @props.mark.y += d.y / @props.yScale
     @props.onChange e

   handleLowerResize: (e, d) ->


Improve AffixNav init

Improve AffixNav init

--- app/assets/javascripts/martian_components/components/affix_nav.coffee
+++ app/assets/javascripts/martian_components/components/affix_nav.coffee
@@ -9,15 +9,20 @@

     super

+    @init()
+    @fixWidth()
+    @smoothScroll()
+    $(window).resize @fixWidth
+
+  init: ->
+    @el.css 'margin-left', '-9999px'
     setTimeout =>
       @el.affix
         offset:
           top: @el.parent().offset().top
+
+      @el.css 'margin-left', '0'
     , 500
-
-    @fixWidth()
-    @smoothScroll()
-    $(window).resize @fixWidth

   smoothScroll: ->
     # Based on http://css-tricks.com/snippets/jquery/smooth-scrolling/


Invoke currentSubCat.get one time inside loop as a reactive variable

--- bootstrap-magic.coffee
+++ bootstrap-magic.coffee
@@ -45,8 +45,9 @@
 @currentCat = new ReactiveVar()

 getCurrentSubCat = ->
+  isSubCat = currentSubCat.get()
   for group in bootstrap_magic_variables
-    if group.keyName is currentSubCat.get()
+    if group.keyName is isSubCat
       return group

 initColorPicker = (node) ->


Change facebookLogin variable to facebook and fix generated word variable name in Bing bot

--- bots/Bing.coffee
+++ bots/Bing.coffee
@@ -15,7 +15,7 @@
     userAgent: hexBot.userAgent
 )
 DASHBOARD = 'http://www.bing.com/rewards/dashboard'
-offers = []; executed = 0; facebookLogin = false
+offers = []; executed = 0; facebook = false

 argData = [{name:'email'}, {name:'password'}, {name:'queryCount',default:0},
            {name:'minTime',default:20}, {name:'maxTime',default:40}]
@@ -35,7 +35,7 @@
   require('./libs/Microsoft.coffee').login(@, ARGS[0], ARGS[1])

 casper.thenOpen DASHBOARD, openDashboard = ->
-  [offers, ARGS[2], facebookLogin] = @evaluate (examineDashboard = (offers, queryCount, facebookLogin) ->
+  [offers, ARGS[2], facebook] = @evaluate (examineDashboard = (offers, queryCount, facebook) ->
     for e in document.querySelectorAll 'ul.row li a div.check-wrapper div.open-check'
       elem = e.parentNode.parentNode
       title = elem.querySelector('.title').innerText
@@ -47,11 +47,11 @@
         # remainingQueries = queriesPerPoint * remainingPoints / earnedPerQueries
         queryCount = Math.ceil(per * (upTo - soFar) / earn)
       else if title is 'Connect to Facebook'
-        facebookLogin = true # free points by logging in with facebook
+        facebook = true # free points by logging in with facebook
       else if title not in ['Refer a Friend', 'Bing Newsletter']
         offers.push elem.href # add the link of the offer
-    return [offers, queryCount, facebookLogin]
-  ), offers, ARGS[2], facebookLogin
+    return [offers, queryCount, facebook]
+  ), offers, ARGS[2], facebook

 casper.then openDailyOffers = ->
   @each offers, (self, offer) -> # iterate over offers
@@ -82,7 +82,7 @@
             word = word[0].toUpperCase() + word[1..-1] # randomly capitalize word
         # query bing with the word
         @thenOpen "http://www.bing.com/search?scope=web&setmkt=en-US&q=#{word}", (data) ->
-          @echo "#{if data['status'] is 200 then ++executed else 'Failed'}#{if gener then '-gen' else ''}) #{word}"
+          @echo "#{if data['status'] is 200 then ++executed else 'Failed'}#{if gen then '-gen' else ''}) #{word}"

 casper.thenOpen DASHBOARD, getTotalPoints = -> # get the number of unused points on the account
   totalPoints = @getElementInfo('#user-status .user-balance .data-value-text').text
@@ -90,6 +90,6 @@

 casper.run ->
   @echo "Completed #{executed}/#{ARGS[2]} quer#{if executed is 1 then 'y' else 'ies'}."
-  if facebookLogin # if there was an offer to log in with a facebook account
+  if facebook # if there was an offer to log in with a facebook account
     @echo 'Earn more points by linking your Facebook account (not done through bot).'
   @exit executed - ARGS[2] # exit with code (-failed)


Just trigger of title has changed.

Just trigger of title has changed.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -598,6 +598,12 @@
     @render(@ticket)

   render: (ticket) =>
+
+    # check if render is needed
+    if @lastTitle && @lastTitle is ticket.title
+      return
+    @lastTitle = ticket.title
+
     @html App.view('ticket_zoom/title')(
       ticket:     ticket
       isCustomer: @isRole('Customer')
@@ -613,7 +619,7 @@
     @frontendTimeUpdate()

   update: (e) =>
-    title = $(e.target).ceg({ mode: 'textonly' }) || '-'
+    title = $(e.target).ceg() || ''

     # update title
     if title isnt @ticket.title


Added org edit.

Added org edit.

--- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -229,7 +229,8 @@
             icon: 'group'
             actions: [
               {
-                class: 'glyphicon glyphicon-edit'
+                name:     'Edit Organization'
+                class:    'glyphicon glyphicon-edit'
                 callback: editOrganization
               },
             ]


[ci skip] change title

[ci skip] change title

--- app/assets/javascripts/components/welcomes/top_panel.js.cjsx
+++ app/assets/javascripts/components/welcomes/top_panel.js.cjsx
@@ -22,7 +22,7 @@

-          ☆12参考表(地力表)支援サイト
+          ☆12参考表
           <image src={image_path('letsencrypt-logo.png')} style={height: '40px'} />


fixed coffeelint errors on workers/social/lib/social/models/computeproviders/providerinterface.coffee

--- workers/social/lib/social/models/computeproviders/providerinterface.coffee
+++ workers/social/lib/social/models/computeproviders/providerinterface.coffee
@@ -1,15 +1,14 @@
-
 NOT_IMPLEMENTED = ->

   KodingError   = require '../../error'
-  message       = "Not implemented yet."
+  message       = 'Not implemented yet.'

   if arguments.length > 0 and fn = arguments[arguments.length - 1]
-    if typeof fn is 'function' then fn new KodingError message, "NotImplemented"
+    if typeof fn is 'function' then fn new KodingError message, 'NotImplemented'

   return message

-PASS_THROUGH = (..., callback)-> callback null
+PASS_THROUGH = (..., callback) -> callback null

 module.exports = class ProviderInterface

@@ -27,16 +26,18 @@

   @postCreate     = PASS_THROUGH

-  @fetchCredentialData  = (credential, callback)->
+  @fetchCredentialData  = (credential, callback) ->

     if not credential?.fetchData?
       return callback null, {}

-    credential.fetchData (err, credData)->
+    credential.fetchData (err, credData) ->

       if err?
-        callback new KodingError "Failed to fetch credential"
+        callback new KodingError 'Failed to fetch credential'
       else if credData?
         callback null, credData
       else
         callback null, {}
+
+


Add hi score to intro screen

Add hi score to intro screen

--- app/scripts/scenes/Intro.coffee
+++ app/scripts/scenes/Intro.coffee
@@ -42,6 +42,19 @@
         @tween({ alpha: 1 }, 1000)
     , 2000, -1

+  entry = Game.highscores()[0]
+
+  Crafty.e('2D, DOM, Text')
+    .attr(x: 0, y: (h * .85), w: w)
+    .text("HI SCORE: #{entry.score} #{entry.initials}")
+    .textColor('#FFFF00')
+    .css('textAlign', 'center')
+    .textFont({
+      size: '10px'
+      weight: 'bold'
+      family: 'Press Start 2P'
+    })
+
   Crafty('Player').each ->
     @reset()
     @one 'Activated', ->


webapp/project upgrades: fix exceeding limit warning and take display_factor into account

--- src/smc-webapp/r_upgrade_adjustor.cjsx
+++ src/smc-webapp/r_upgrade_adjustor.cjsx
@@ -103,10 +103,11 @@
         # and finally, we add up what a user can add (with the maybe negative remainder) and cap at 0
         user_limits = misc.map_max(misc.map_sum(limits, user_current), 0)
         return
-            limits    : user_limits
-            remaining : user_remaining
-            current   : user_current
-            totals    : total_upgrades
+            limits         : user_limits
+            remaining      : user_remaining
+            current        : user_current
+            totals         : total_upgrades
+            proj_remainder : proj_remainder

     clear_upgrades: ->
         @set_upgrades('min')
@@ -148,7 +149,7 @@
     render_addon: (misc, name, display_unit, limit) ->

{"#{misc.plural(2,display_unit)}"} {@render_max_button(name, limit)}

-    render_upgrade_row: (name, data, remaining=0, current=0, limit=0, total=0) ->
+    render_upgrade_row: (name, data, remaining=0, current=0, limit=0, total=0, proj_remainder=0) ->
         if not data?
             return

@@ -164,7 +165,7 @@
             if not @is_upgrade_input_valid(Math.max(val, 0), limit)
                 reasons = []
                 if val > remaining + current then reasons.push('you do not have enough upgrades')
-                if val > limit then reasons.push('exceeds the limit')
+                if val > proj_remainder + current then reasons.push('exceeds the limit')
                 reason = reasons.join(' and ')
                 label =
Uncheck this: {reason}

             else
@@ -195,6 +196,7 @@

         else if input_type == 'number'
             remaining = misc.round2(remaining * display_factor)
+            proj_remainder = misc.round2(proj_remainder * display_factor)
             display_current = current * display_factor # current already applied
             if current != 0 and misc.round2(display_current) != 0
                 current = misc.round2(display_current)
@@ -207,12 +209,12 @@
             # the amount displayed remaining subtracts off the amount you type in
             show_remaining = misc.round2(remaining + current - current_input)

-            val = @state["upgrade_#{name}"]
-            if misc.parse_number_input(val)?
+            val = misc.parse_number_input(@state["upgrade_#{name}"])
+            if val?
                 if not @is_upgrade_input_valid(Math.max(val, 0), limit)
                     reasons = []
                     if val > remaining + current then reasons.push('not enough upgrades')
-                    if val > limit then reasons.push('exceeding limit')
+                    if val > proj_remainder + current then reasons.push('exceeding limit')
                     reason = reasons.join(' and ')
                     bs_style = 'error'
                     label =
Value too high: {reason}

@@ -320,7 +322,7 @@
             # user has no upgrades on their account
             <NoUpgrades cancel={@props.cancel_upgrading} />
         else
-            {limits, remaining, current, totals} = @get_quota_info()
+            {limits, remaining, current, totals, proj_remainder} = @get_quota_info()

             <Alert bsStyle='warning' style={@props.style}>
                 {<React.Fragment>
@@ -357,7 +359,7 @@
                 </Row>

-                {@render_upgrade_row(n, @props.quota_params[n], remaining[n], current[n], limits[n], totals[n]) for n in PROJECT_UPGRADES.field_order}
+                {@render_upgrade_row(n, @props.quota_params[n], remaining[n], current[n], limits[n], totals[n], proj_remainder[n]) for n in PROJECT_UPGRADES.field_order}
                 <UpgradeRestartWarning />
                 {@props.children}
                 <ButtonToolbar style={marginTop:'10px'}>


Initial panning infrastructure

Initial panning infrastructure

--- src/hello.coffee
+++ src/hello.coffee
@@ -2,6 +2,8 @@
   console.log "Hello, world!"

   environment = GameState.state.map((x) -> x.world)
+
+  cameraTopLeft = GameState.state.map((x) -> x.camera)

   cameraInfo = Bacon.combineTemplate
     camWidth: K('camera_width')
@@ -29,7 +31,13 @@
     sources = (for row in [0..camHeight-1]
       for col in [0..camWidth-1]
         do (row, col) ->
-          gridDisplay.map((x) -> x[row]?[col] ? ['unknown']))
+          intermediate = Bacon.combineTemplate
+            grid: gridDisplay
+            target: cameraTopLeft
+          intermediate.map (x) ->
+            {grid, target} = x
+            [tx, ty] = target
+            grid[row + ty]?[col + tx] ? ['unknown'])
     GridView '#tiles', sources

   clicks.onValue (pos) ->
@@ -37,6 +45,7 @@
     console.log "Click at #{x}, #{y}"

   GameState.start
+    camera: [0, 0]
     world: [
       [0, 0, 0, 0, 0, 0],
       [0, 1, 2, 1, 0, 0],


inline stitch code

inline stitch code

--- src/index.coffee
+++ src/index.coffee
@@ -6,12 +6,10 @@
 escodegen = require('escodegen')

 class Stitch
-  @identifier: 'require'
   fileList: {}

   bundle: (fileName, packages) ->
     @definitions = ''
-    @fileName = fileName
     @packages = packages
     @rootPath = process.cwd()
     @lineOffset = @getWrapLineOffset()
@@ -80,72 +78,18 @@
     ", '#{name}': function(exports, require, module) {\n#{source}}"

   wrapSources: (sources)->
-    result = """
-          (function(/*! Stitch !*/) {
-            if (!this.#{Stitch.identifier}) {
-              var modules = {}, cache = {}, require = function(name, root) {
-                var path = expand(root, name), module = cache[path], fn;
-                if (module) {
-                  return module.exports;
-                } else if (fn = modules[path] || modules[path = expand(path, './index')]) {
-                  module = {id: path, exports: {}};
-                  try {
-                    cache[path] = module;
-                    fn(module.exports, function(name) {
-                      return require(name, dirname(path));
-                    }, module);
-                    return module.exports;
-                  } catch (err) {
-                    delete cache[path];
-                    throw err;
-                  }
-                } else {
-                  throw 'module \\'' + name + '\\' not found';
-                }
-              }, expand = function(root, name) {
-                var results = [], parts, part;
-                if (/^\\.\\.?(\\/|$)/.test(name)) {
-                  parts = [root, name].join('/').split('/');
-                } else {
-                  parts = name.split('/');
-                }
-                for (var i = 0, length = parts.length; i < length; i++) {
-                  part = parts[i];
-                  if (part == '..') {
-                    results.pop();
-                  } else if (part != '.' && part != '') {
-                    results.push(part);
-                  }
-                }
-                return results.join('/');
-              }, dirname = function(path) {
-                return path.split('/').slice(0, -1).join('/');
-              };
-              this.#{Stitch.identifier} = function(name) {
-                return require(name, '');
-              };
-              this.#{Stitch.identifier}.define = function(bundle) {
-                for (var key in bundle)
-                  modules[key] = bundle[key];
-              };
-            }
-        """
+    result = "(function(/*!Stitch!*/){if (!this.require) { var modules = {}, cache = {}, require = function(name, root) { var path = expand(root, name), module = cache[path], fn; if (module) { return module.exports; } else if (fn = modules[path] || modules[path = expand(path, './index')]) { module = {id: path, exports: {}}; try { cache[path] = module; fn(module.exports, function(name) { return require(name, dirname(path)); }, module); return module.exports; } catch (err) { delete cache[path]; throw err; } } else { throw 'module \\'' + name + '\\' not found'; } }, expand = function(root, name) { var results = [], parts, part; if (/^\\.\\.?(\\/|$)/.test(name)) { parts = [root, name].join('/').split('/'); } else { parts = name.split('/'); } for (var i = 0, length = parts.length; i < length; i++) { part = parts[i]; if (part == '..') { results.pop(); } else if (part != '.' && part != '') { results.push(part); } } return results.join('/'); }, dirname = function(path) { return path.split('/').slice(0, -1).join('/'); }; this.require = function(name) { return require(name, ''); }; this.require.define = function(bundle) { for (var key in bundle) modules[key] = bundle[key];};}"

-    result += "\n  var a = {};"
+    result += "var a = {};"
     for filePath,exportedNames of @fileList
       for exportedName in exportedNames
         if exportedName.match /^[a-z]+[a-z0-9]*$/i
-        # @review, can this be less repititive? isn't the assigned value the same for all (this module/file name)?
           result += "a['#{exportedName}']='#{filePath}';"

-    result += """
-          \n  this.#{Stitch.identifier}.exportsFileMap=a;
-          return this.#{Stitch.identifier}.define;
-        }).call(this)({\n
-        """
-    result += sources + "});"
+    result += "this.require.exportsFileMap=a;return this.require.define;}).call(this)({\n#{sources}});"

   getWrapLineOffset: ->
     @wrapSources('').split('\n').length - 1
+
 module.exports = new Stitch()


Ensure all page dates are Date objects

Ensure all page dates are Date objects

--- src/index.coffee
+++ src/index.coffee
@@ -22,7 +22,7 @@
         url.push loc: "#{locals.url}#{page.url}"
         if page.metadata?
           if page.metadata.date?
-            url.push lastmod: page.metadata.date.toISOString()
+            url.push lastmod: page.date.toISOString()
           if page.metadata.changefreq?
             url.push changefreq: page.metadata.changefreq
           if page.metadata.priority?


Add timeout and optimize download queue

Add timeout and optimize download queue

--- src/index.coffee
+++ src/index.coffee
@@ -41,6 +41,8 @@
   options =
     url: 'https://api.instagram.com/v1/media/popular'
     qs: {access_token}
+    # 30s timeout
+    timeout: 30000

   queue = async.queue worker, CONCURRENT_JOB
   queue.empty = () ->
@@ -66,7 +68,8 @@
         filename = crypto.randomBytes(8).toString('hex')
         filename += path.extname media.images.standard_resolution.url
         task = _.partial fetch, media.images.standard_resolution.url, "#{dest}/#{filename}"
-        queue.push task
+        queue.push task, () ->
+          queue.empty() if queue.length() < 50
         callback()

       , (err) ->


code cleanup based on jslint results

code cleanup based on jslint results

--- src/index.coffee
+++ src/index.coffee
@@ -52,12 +52,13 @@
             timerData.target = timerData.delay
             timerData.startTime = Date.now() - timerData.delay
           else
-            elapsed = Date.now() - timerData.startTime;
-            adjust = timerData.target - elapsed;
+            elapsed = Date.now() - timerData.startTime
+            adjust = timerData.target - elapsed

           timerData.target += timerData.delay
           if common._periodicTimers.hasOwnProperty(id)
-            common._periodicTimers[id] = setTimeout(taskHandler, timerData.delay + adjust)
+            common._periodicTimers[id] =
+              setTimeout(taskHandler, timerData.delay + adjust)
             timerData.func()

         return id
@@ -107,8 +108,8 @@
           @param {Function} reject - function to reject a promise on return,
                                      may be null
           @param {Error} error  - error object
-          @param {String} [customMessage]  - a custom message to be used as prefix
-                                             to the error message
+          @param {String} [customMessage]  - a custom message to be used
+                                             as prefix to the error message
         ###
         rejectWithErrorString: (reject, error="Unknown", customMessage=null) ->
           message = "" + (error.message ? error)
@@ -125,7 +126,8 @@

         ###
           Same as rejectWithErrorString, but has been deprecated
-          @param {Function} reject - function to reject a promise on return, may be null
+          @param {Function} reject - function to reject a promise on return,
+                                     may be null
           @param {Error} error  - error object
           @deprecated
         ###
@@ -207,8 +209,8 @@
           only updated if the value has been changed.
           @param {String} attributeName - the attribute name
           @param {Any} value - the attribute value
-          @param [Boolean} [discrete=false] - True if attribute value is discrete,
-          e.g., a switch state. False, otherwise.
+          @param [Boolean} [discrete=false] - True if attribute value is
+          discrete, e.g., a switch state. False, otherwise.
         ###
         setAttribute: (attributeName, value, discrete=false) ->
           if value? and not discrete or device['_' + attributeName] isnt value
@@ -308,18 +310,22 @@
         ###
           Generates a new device id which is not yet in use by another device
           @param {Object} framework - the pimatic framework object.
-          @param {String} prefix - a prefix string to be used as part of device id.
+          @param {String} prefix - a prefix string to be used as part of
+          the device id.
           @param {String} [lastId] - the lastId returned by generateDeviceId
-          @returns {String} the id generated or undefined if id could not be generated
+          @returns {String} the id generated or undefined
+          if id could not be generated
         ###
         generateDeviceId: (framework, prefix, lastId = null) ->
           start = 1
           if lastId?
             m = lastId.match /.*-([0-9]+)$/
             start = +m[1] + 1 if m? and m.length is 2
+
+          cfg = framework.deviceManager.devicesConfig
           for x in [start...1000] by 1
             result = "#{prefix}-#{x}"
-            matched = framework.deviceManager.devicesConfig.some (element, iterator) ->
+            matched = cfg.some (element, iterator) ->
               element.id is result
             return result if not matched
       }


Default `auth` to `null`. Fixes #24. Thanks @tim-kos

--- src/Airbud.coffee
+++ src/Airbud.coffee
@@ -43,7 +43,7 @@
     headers: []

     # Custom auth to submit in the request
-    auth: {}
+    auth: null

   @getDefaults: ->
     return Airbud._defaults


Client: Activity: Added renderPreviewAvatars method. It renders only preview avatars, no nicknames

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -12,7 +12,7 @@

     super

-    @state = { addNewParticipantMode: no }
+    @state = { addNewParticipantMode: no, showAllParticipants: no }

   PREVIEW_COUNT = 0
   MAX_PREVIEW_COUNT = 4
@@ -43,10 +43,12 @@

   onShowMoreParticipantButtonClick: ->

-    console.log 'show more participants clicked'
+    if @state.showAllParticipants is yes
+    then @setState showAllParticipants: no
+    else @setState showAllParticipants: yes

-  renderAvatars: ->
+  renderPreviewAvatars: ->

     return null  unless @props.participants

@@ -54,7 +56,10 @@

     PREVIEW_COUNT = @getPreviewCount()

-    participants.slice(0, PREVIEW_COUNT).toList().map (participant) ->
+    participants = participants.slice(0, PREVIEW_COUNT)
+
+    @renderAvatars(participants, no)
+

   renderNickname: (participant, isNicknameVisible)->

@@ -139,7 +144,7 @@

   render: ->
     <div className="ChannelParticipantAvatars">
-      {@renderAvatars()}
+      {@renderPreviewAvatars()}
       {@renderMoreCount()}
       {@renderNewParticipantButton()}
       {@renderAddNewParticipantInput()}


Client: Activity: Test: Updated CreatePrivateChannelModal component to use ChannelParticipantsDropdown instead of CreateChannelParticipantsDropdown component

--- client/activity/lib/components/createprivatechannelmodal/index.coffee
+++ client/activity/lib/components/createprivatechannelmodal/index.coffee
@@ -1,25 +1,25 @@
-_                                 = require 'lodash'
-kd                                = require 'kd'
-Link                              = require 'app/components/common/link'
-React                             = require 'kd-react'
-ReactDOM                          = require 'react-dom'
-Portal                            = require('react-portal').default
-Avatar                            = require 'app/components/profile/avatar'
-whoami                            = require 'app/util/whoami'
-AppFlux                           = require 'app/flux'
-classnames                        = require 'classnames'
-toImmutable                       = require 'app/util/toImmutable'
-KeyboardKeys                      = require 'app/constants/keyboardKeys'
-ActivityFlux                      = require 'activity/flux'
-ActivityModal                     = require 'app/components/activitymodal'
-KDReactorMixin                    = require 'app/flux/base/reactormixin'
-isPublicChannel                   = require 'app/util/isPublicChannel'
-DropboxInputMixin                 = require 'activity/components/dropbox/dropboxinputmixin'
-CreateChannelFlux                 = require 'activity/flux/createchannel'
-ProfileLinkContainer              = require 'app/components/profile/profilelinkcontainer'
-PreExistingChannelBox             = require './preexistingchannelbox'
-ChannelParticipantsDropdown       = require 'activity/components/channelparticipantsdropdown'
-CreateChannelParticipantsDropdown = require 'activity/components/createchannelparticipantsdropdown'
+_                           = require 'lodash'
+kd                          = require 'kd'
+Link                        = require 'app/components/common/link'
+React                       = require 'kd-react'
+ReactDOM                    = require 'react-dom'
+Portal                      = require('react-portal').default
+Avatar                      = require 'app/components/profile/avatar'
+whoami                      = require 'app/util/whoami'
+AppFlux                     = require 'app/flux'
+classnames                  = require 'classnames'
+toImmutable                 = require 'app/util/toImmutable'
+KeyboardKeys                = require 'app/constants/keyboardKeys'
+ActivityFlux                = require 'activity/flux'
+ActivityModal               = require 'app/components/activitymodal'
+KDReactorMixin              = require 'app/flux/base/reactormixin'
+isPublicChannel             = require 'app/util/isPublicChannel'
+DropboxInputMixin           = require 'activity/components/dropbox/dropboxinputmixin'
+CreateChannelFlux           = require 'activity/flux/createchannel'
+ProfileLinkContainer        = require 'app/components/profile/profilelinkcontainer'
+PreExistingChannelBox       = require './preexistingchannelbox'
+ChannelParticipantsDropdown = require 'activity/components/channelparticipantsdropdown'
+DropdownItem                = require 'activity/components/createchannelparticipantsdropdownitem'

 module.exports = class CreatePrivateChannelModal extends React.Component

@@ -362,15 +362,26 @@

   renderAddNewChannelParticipantsDropdown: ->

-    <CreateChannelParticipantsDropdown
-      ref             = 'dropdown'
-      query           = { @state.query }
-      value           = { @state.value }
-      visible         = { @state.dropdownVisibility }
-      items           = { @state.dropdownUsers }
-      selectedItem    = { @state.selectedItem }
-      selectedIndex   = { @state.selectedIndex }
-      onItemConfirmed = { @bound 'onDropdownItemConfirmed' }
+    { moveToPrevIndex
+      moveToNextIndex
+      setSelectedIndex
+      setDropdownVisibility } = CreateChannelFlux.actions.user
+
+    <ChannelParticipantsDropdown.Container
+      ref                  = 'dropdown'
+      className            = 'CreateChannel-dropbox'
+      query                = { @state.query }
+      value                = { @state.value }
+      moveToPrevAction     = { moveToPrevIndex }
+      moveToNextAction     = { moveToNextIndex }
+      onItemSelectedAction = { setSelectedIndex }
+      closeAction          = { setDropdownVisibility }
+      items                = { @state.dropdownUsers }
+      selectedItem         = { @state.selectedItem }
+      selectedIndex        = { @state.selectedIndex }
+      visible              = { @state.dropdownVisibility }
+      DropdownItem         = { DropdownItem }
+      onItemConfirmed      = { @bound 'onDropdownItemConfirmed' }
     />


Fix bug with syncing the initial artwork save

--- apps/artwork/client/view.coffee
+++ apps/artwork/client/view.coffee
@@ -58,10 +58,11 @@
         @setupSaveButton @$(".overlay-button-save[data-id='#{artwork.id}']"), artwork
       @syncSavedArtworks artworks

+  # @param {Object or Array or Backbone.Collection}
   syncSavedArtworks: (artworks) ->
     return unless @saved
     @__saved__ ?= new Backbone.Collection
-    @__saved__.add artworks.models
+    @__saved__.add artworks?.models or artworks
     @saved.addRepoArtworks @__saved__
     @saved.syncSavedArtworks()


uses FormSyphon for form serialization #1159

--- app/assets/javascripts/admin/views/project/create_project_modal_view.coffee
+++ app/assets/javascripts/admin/views/project/create_project_modal_view.coffee
@@ -2,6 +2,7 @@
 Marionette     = require("backbone.marionette")
 Toast          = require("libs/toast")
 app            = require("app")
+FormSyphon     = require("form-syphon")
 SelectionView  = require("admin/views/selection_view")
 UserCollection = require("admin/models/user/user_collection")
 TeamCollection = require("admin/models/team/team_collection")
@@ -25,9 +26,9 @@

-              Project Name
+              Project Name

-                <input type="text" class="form-control project-name" name="projectName" value="" required autofocus>
+                <input type="text" class="form-control project-name" name="name" value="" required autofocus>

@@ -60,9 +61,6 @@

   ui :
     "name" : ".project-name"
-    "priority" : ".project-priority"
-    "team" : ".team"
-    "owner" : ".owner"
     "form" : "form"

@@ -74,13 +72,16 @@
       collection : new UserCollection()
       childViewOptions :
         defaultItem : {email : app.currentUser.email}
-        modelValue : -> return "#{@model.get("firstName")} #{@model.get("lastName")} (#{@model.get("email")})"
+        modelValue : -> return @model.id
+        modelLabel : -> return "#{@model.get("firstName")} #{@model.get("lastName")} (#{@model.get("email")})"
+      name : "owner"
     )
     @teamSelectionView = new SelectionView(
       collection : new TeamCollection()
       childViewOptions :
         modelValue: -> return "#{@model.get("name")}"
       data : "amIAnAdmin=true"
+      name : "team"
     )

@@ -98,12 +99,9 @@

     if @ui.form[0].checkValidity()

-      project = new ProjectModel(
-        owner : @ui.owner.find("select :selected").attr("id")
-        name : @ui.name.val()
-        team : @ui.team.find("select :selected").val()
-        priority: parseInt(@ui.priority.val())
-      )
+      formValues = FormSyphon.serialize(@ui.form)
+      project = new ProjectModel(formValues)
+
       @projectCollection.create(project,
         wait : true
         success : _.bind(@destroyModal, @)


Fixed table content group by.

Fixed table content group by.

--- app/assets/javascripts/app/controllers/_application_controller_table.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_table.coffee
@@ -128,42 +128,6 @@
         explanation: @explanation
       return $(table)

-    # group by
-    if @groupBy
-
-      # remove group by attribute from header
-      overview = _.filter(
-        overview
-        (item) =>
-          return item if item isnt @groupBy
-          return
-      )
-
-      # get new order
-      groupObjects = _.groupBy(
-        @objects
-        (item) =>
-          return '' if !item[@groupBy]
-          return item[@groupBy].displayName() if item[@groupBy].displayName
-          item[@groupBy]
-      )
-      groupOrder = []
-      for group, value of groupObjects
-        groupOrder.push group
-
-      # sort new groups
-      groupOrder = _.sortBy(
-        groupOrder
-        (item) ->
-          item
-      )
-
-      # create new data array
-      @objects = []
-      for group in groupOrder
-        @objects = @objects.concat groupObjects[group]
-        groupObjects[group] = [] # release old array
-
     # get header data
     headers = []
     for item in overview
@@ -221,6 +185,42 @@

     headers = @adjustHeaderWidths headers

+    # group by
+    if @groupBy
+
+      # remove group by attribute from header
+      overview = _.filter(
+        overview
+        (item) =>
+          return item if item isnt @groupBy
+          return
+      )
+
+      # get new order
+      groupObjects = _.groupBy(
+        @objects
+        (item) =>
+          return '' if !item[@groupBy]
+          return item[@groupBy].displayName() if item[@groupBy].displayName
+          item[@groupBy]
+      )
+      groupOrder = []
+      for group, value of groupObjects
+        groupOrder.push group
+
+      # sort new groups
+      groupOrder = _.sortBy(
+        groupOrder
+        (item) ->
+          item
+      )
+
+      # create new data array
+      @objects = []
+      for group in groupOrder
+        @objects = @objects.concat groupObjects[group]
+        groupObjects[group] = [] # release old array
+
     # get content
     @log 'debug', 'table', 'header', headers, 'overview', 'objects', @objects
     table = App.view('generic/table')(


Cleaned up.

Cleaned up.

--- client/render-list.coffee
+++ client/render-list.coffee
@@ -5,14 +5,13 @@
   (($ x).attr "name" for x in get_selected_records containment)

 record = (options, xml) ->
-  xml = $ xml
   (tr().addClass "record").append (render "record_selector", options, xml),
                                   (render_record options, xml, SCHEMAS[options.schema]),
                                   (render "button_edit", options, xml),
                                   (render "button_remove", options, xml)

 list = (options, records) ->
-  record options, x for x in records
+  record options, ($ xml) for xml in records

 list_headers = (options) ->
   (th().text desc) for {desc} in SCHEMAS[options.schema]
@@ -22,7 +21,8 @@
     render "xreflist_empty", options
   else
     head = thead().append tr().append th(), list_headers options
-    table().append head, (tbody().addClass "record_list").append list options, records
+    body = tbody().addClass "record_list")
+    table().append head, body.append list options, records

 @render_list = (options, records) ->
   with_views [VIEWS_TABLE, VIEWS_TABLE_EDIT], ->


changed some class variables to instance variables

--- src/file.coffee
+++ src/file.coffee
@@ -2,8 +2,8 @@
 # Parses all header-dataunits, initializes Header instances
 # and appropriate dataunit instances.
 class File
-  @LINEWIDTH   = 80
-  @BLOCKLENGTH = 2880
+  LINEWIDTH: 80
+  BLOCKLENGTH: 2880

   constructor: (buffer) ->
     @offset     = 0
@@ -13,8 +13,6 @@
     @hdus       = []
     @eof        = false

-    # TODO: Remove need to extend DataView.
-    #       Place offset in DataUnit class
     File.extendDataView(@view)

     # Loop until the end of file
@@ -26,10 +24,6 @@
       break if @eof

   # ##Class Methods
-
-  # Determine the number of characters following a header or data unit
-  @excessBytes: (length) ->
-    return (File.BLOCKLENGTH - (length % File.BLOCKLENGTH)) % File.BLOCKLENGTH

   @extendDataView: (view) ->

@@ -47,6 +41,10 @@

   # ##Instance Methods

+  # Determine the number of characters following a header or data unit
+  excessBytes: (length) =>
+    return (@BLOCKLENGTH - (length % @BLOCKLENGTH)) % @BLOCKLENGTH
+
   # Extracts a single header without interpreting each line.
   # Interpretation is slow for large headers.
   readHeader: ->
@@ -60,15 +58,15 @@
       break if done

       # Grab a 2880 block
-      block = @view.getString(@offset, File.BLOCKLENGTH)
-      @offset += File.BLOCKLENGTH
+      block = @view.getString(@offset, @BLOCKLENGTH)
+      @offset += @BLOCKLENGTH

       # Set a line counter
       i = 0
       loop
         # Search for the END keyword starting at the last line of the block
-        start = File.BLOCKLENGTH - File.LINEWIDTH * (i + 1)
-        end   = File.BLOCKLENGTH - File.LINEWIDTH * i
+        start = @BLOCKLENGTH - @LINEWIDTH * (i + 1)
+        end   = @BLOCKLENGTH - @LINEWIDTH * i
         line  = block.slice(start, end) # Is this expensive?

         # Search one line up if white space is matched
@@ -113,7 +111,7 @@
         DU = Image
     data = new DU(header, @view, @offset)

-    excess = File.excessBytes(data.length)
+    excess = @excessBytes(data.length)

     # Forward to the next HDU
     @offset += data.length + excess


Only display late icon on homework

Only display late icon on homework

--- tutor/src/components/student-dashboard/event-info-icon.cjsx
+++ tutor/src/components/student-dashboard/event-info-icon.cjsx
@@ -19,7 +19,8 @@
     pastDue      = event.type is 'homework' and dueAt.isBefore(now, 'd')
     workedLate   = moment(event.last_worked_at).isAfter(dueAt)

-    return null unless workedLate or (pastDue and isIncomplete)
+    if @props.event.type isnt 'homework' or ( workedLate or (pastDue and isIncomplete) )
+      return null

     # use 'day' granularity for checking if the due date is today or after today
     status = if dueAt.isSame(now, 'd') then 'incomplete' else 'late'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -68,7 +68,7 @@

 test "should handle moving diagonally", ->
   window.keydown.left = true
-  window.
+  window.keydown.up = true

 test "should correctly track facing direction", ->
   ok player.I.facing.equal(Point(1, 0))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -33,10 +33,6 @@
   player.update()

   equals player.I.velocity.x, 3
-
-  player.update()
-
-  equals player.I.x, 12

   window.keydown.right = false


add stylus preferences to emmet preferences object

loop through set stylus properties and relay these to emmet

--- lib/emmet.coffee
+++ lib/emmet.coffee
@@ -16,6 +16,9 @@
 ]

 toggleCommentSyntaxes = ['html', 'css', 'less', 'scss']
+
+for k, v of  atom.config.get 'emmet.stylus'
+    emmet.preferences.set('stylus.' + k, v);

 getUserHome = () ->
   if process.platform is 'win32'


NewRelic 622 Fix, some method name changes

--- lib/foaas.coffee
+++ lib/foaas.coffee
@@ -1,7 +1,8 @@
 express = require 'express'
 fs = require 'fs'
 path = require 'path'
-npmPackage = require path.resolve(__dirname,'../package.json')
+npmPackage = require path.resolve(__dirname,'../package.json')
+newrelic = require 'newrelic'

 module.exports = class FOAAS

@@ -42,7 +43,6 @@
       res.header 'Access-Control-Allow-Headers', 'Content-Type'
       next()

-
     # Express Router
     @app.use(@app.router)

@@ -60,13 +60,16 @@
     @app.options "*", (req, res) ->
       res.end()

-    # Final case, send 404 Not Found
-    @app.use @send404
+    # Final case, send 622 All The Fucks
+    @app.use @send622

     # Renderers
     @loadRenderers(renderersPath)

-  send404: (req, res) =>
+  send622: (req, res) =>
+    # NewRelic hasn't yet adopted the HTTP 6xx (Sarcasm) series of responses.
+    newrelic.setIgnoreTransaction(true)
+
     res.status(622)
     @output(req, res, "622 - All The Fucks", 'Server invites you to consider the truly monumental amount of fucks it couldn\'t give about your request.')


add valueless return

add valueless return

--- lib/index.coffee
+++ lib/index.coffee
@@ -146,6 +146,8 @@
       result = chain.add fn
       if result?.error? then return result

+    return
+

   # load a string via require(), return a function, or return an error otherwise
   _load: (arg) ->


Automatically create build/iphone/tiapp.xml to fix Titanium error on first build

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,5 +1,6 @@
 path = require 'path'
 fs = require 'fs'
+util = require 'util'
 { spawn, exec } = require 'child_process'

 printLine = (line) -> process.stdout.write line + '\n'
@@ -30,6 +31,28 @@

   console.log "Watching #{restartFile} for timestamp changes."

+copyTiappIfNeeded = (callback) ->
+
+  buildPath = 'build/iphone'
+  appPath = path.join buildPath, 'tiapp.xml'
+
+  path.exists appPath, (exists) ->
+    if exists
+      callback()
+    else
+      fs.mkdir 'build', (err) ->
+
+        throw err if err
+
+        fs.mkdir buildPath, (err) ->
+
+          throw err if err
+
+          input = fs.createReadStream 'tiapp.xml'
+          output = fs.createWriteStream appPath
+
+          util.pump input, output, callback
+
 module.exports =

   command: require './command'
@@ -40,17 +63,19 @@

     "iphone:run": ->

-      runAndWatch ->
+      copyTiappIfNeeded ->

-        simulator = spawn titaniumPath(), [
-          'run'
-          '--platform=iphone'
-        ]
+        runAndWatch ->

-        simulator.stdout.on 'data', printIt
-        simulator.stderr.on 'data', printIt
+          simulator = spawn titaniumPath(), [
+            'run'
+            '--platform=iphone'
+          ]

-        simulator.on 'exit', (code, signal) ->
-          simulator.stdin.end()
+          simulator.stdout.on 'data', printIt
+          simulator.stderr.on 'data', printIt

-        simulator
+          simulator.on 'exit', (code, signal) ->
+            simulator.stdin.end()
+
+          simulator


Implement item filtering in Model class

Implement item filtering in Model class

--- lib/model.coffee
+++ lib/model.coffee
@@ -1,4 +1,5 @@
 { flatten } = require('sdk/util/array');
+Promise = require("sdk/core/promise")

 class Model
   constructor : (@sources, @filters) ->
@@ -7,11 +8,28 @@
     @requestUpdate()

   onFeedUpdated : (feed) =>
-    for item in feed.items
-      console.log(item.link)
-    # TODO
+    @filter(item) for item in feed.items

   requestUpdate : =>
     feed.requestUpdate() for feed in @feeds

+  filter : (item) =>
+    # Ask every filter to check the item.
+    # Pack it into a promise and let filters
+    # resolve the promise upon acceptance,
+    # or reject it.
+    promises = for filter in @filters
+      { promise, resolve, reject } = Promise.defer()
+      filter.filter(item, resolve, reject)
+      promise
+    # Collect all promises and pack them.
+    # Once all filters are done, display the item or not.
+    Promise.all(promises).then (=> @display(item)), (=> @remove(item))
+
+  display : (item) =>
+    console.log("display:", item.link)
+
+  remove : (item) =>
+    console.log("remove:", item.link)
+
 exports.Model = Model


Access active pane item from atom.workspace

--- lib/panes.coffee
+++ lib/panes.coffee
@@ -4,7 +4,7 @@
   isRecordable: -> false

   focusCursor: ->
-    editor = atom.workspaceView.getActivePaneItem()
+    editor = atom.workspace.getActivePaneItem()
     editorView = atom.workspaceView.getActiveView()
     if editor? and editorView?
       cursorPosition = editor.getCursorBufferPosition()


Fix mentionables

Fix mentionables

--- lineman/app/modules/discussion_page/comment_form/comment_form_controller.coffee
+++ lineman/app/modules/discussion_page/comment_form/comment_form_controller.coffee
@@ -1,23 +1,6 @@
 angular.module('loomioApp').controller 'CommentFormController', ($scope, Records) ->
   $scope.comment = $scope.comment or Records.comments.initialize(discussion_id: $scope.discussion.id)
   group = $scope.discussion.group()
-
-  $scope.mentionables = group.members()
-
-  # im pretty sure this is ment to be baleted
-  #$scope.isExpanded = false
-
-  #$scope.expand = ->
-    #$scope.isExpanded = true
-    #$scope.$broadcast 'expandCommentField'
-
-  #$scope.collapse = (event) ->
-    #event.preventDefault()
-    #$scope.isExpanded = false
-    #$scope.$broadcast 'collapseCommentField'
-
-  #$scope.collapseIfEmpty = ->
-    #$scope.collapse() if $scope.comment.body.length == 0

   $scope.getMentionables = (fragment) ->
     Records.memberships.fetchByNameFragment(fragment, group.key).then ->


Tiny tweak to clarify code a little, from review

--- lib/resin.coffee
+++ lib/resin.coffee
@@ -84,7 +84,7 @@
 		pine
 	}

-	sdk = mapValues(sdkTemplate, (v) -> v(deps, opts))
+	sdk = mapValues(sdkTemplate, (moduleFactory) -> moduleFactory(deps, opts))

 	###*
 	# @typedef Interceptor


Fix LC.LinePathShape API

Fix LC.LinePathShape API

--- coffee/core/shapes.coffee
+++ coffee/core/shapes.coffee
@@ -88,38 +88,24 @@

   className: 'LinePathShape'

-  constructor: ->
-    @points = []
-
-    # Order of the bspline applied to the curve
-    # Higher values make the curve smoother but are more expensive
-    @order = 3
-
+  constructor: (@points = [], @order = 3, @tailSize = 3)->
     # The number of smoothed points generated for each point added
     @segmentSize = Math.pow(2, @order)
-
-    # The number of segments to use as the tail
-    # In other words, when a point is added, how many points do you need to go
-    # back before the slope of the old smoothed curve is the same as the slope
-    # of the new smoothed curve.
-    @tailSize = 3
-
+
     # The number of points used to calculate the bspline to the newest point
     @sampleSize = @tailSize + 1

+    for point in @points
+      @addPoint(point)
+
   jsonContent: ->
     # TODO: make point storage more efficient
-    {@order, @segmentSize, @tailSize, @sampleSize, @points}
+    {@order, @tailSize, @points}

   @fromJSON: (lc, data) ->
-    shape = new LC.LinePathShape()
-    shape.order = data.order
-    shape.segmentSize = data.segmentSize
-    shape.tailSize = data.tailSize
-    shape.sampleSize = data.sampleSize
-    for pointData in data.points
-      shape.addPoint(new LC.Point.fromJSON(lc, pointData))
-    shape
+    points = (new LC.Point.fromJSON(lc, pointData) \
+              for pointData in data.points)
+    new LC.LinePathShape(points, data.order, data.tailSize)

   addPoint: (point) ->
     # Brush Variance Code


first running version - tools aren't used at all

--- platform/src/gamEvolve.coffee
+++ platform/src/gamEvolve.coffee
@@ -69,7 +69,7 @@

 GE.NodeVisitorConstants =  class NodeVisitorConstants
   # The log function defaults to the console
-  constructor: (@modelData, @serviceData, @assets, @actions, @log = GE.logToConsole) ->
+  constructor: (@modelData, @serviceData, @assets, @actions, @tools, @log = GE.logToConsole) ->

 GE.NodeVisitorResult = class NodeVisitorResult
   constructor: (@result = null, @modelPatches = [], @servicePatches = []) ->
@@ -332,7 +332,7 @@
 # Otherwise, if inputServiceData is not null, this data is used instead of asking the services.
 # Returns a list of model patches.
 # TODO: refactor to accept a parameter object rather than a long list of parameters
-GE.stepLoop = (node, modelData, assets, actions, services, log = null, inputServiceData = null, outputServiceData = null) ->
+GE.stepLoop = (node, modelData, assets, actions, tools, services, log = null, inputServiceData = null, outputServiceData = null) ->
   if outputServiceData != null
     modelPatches = []
   else
@@ -341,7 +341,7 @@
       for serviceName, service of services
         inputServiceData[serviceName] = service.provideData(assets)

-    result = GE.visitNode(node, new GE.NodeVisitorConstants(modelData, inputServiceData, assets, actions, log))
+    result = GE.visitNode(node, new GE.NodeVisitorConstants(modelData, inputServiceData, assets, actions, tools, log))

     if GE.doPatchesConflict(result.modelPatches) then throw new Error("Model patches conflict: #{result.modelPatches}")
     modelPatches = result.modelPatches


activity feed: minor fix

activity feed: minor fix

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -207,7 +207,7 @@
       @lastTo   = to
       @lastFrom = from
     else
-      @reachedEndOfActivities = true
+      @reachedEndOfActivities = yes

   # Store first & last cache activity timestamp.
   extractCacheTimeStamps: (cache)->


activity appcontroller fixed

activity appcontroller fixed

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -63,6 +63,10 @@
           "Hi #{account.profile.firstName}! Welcome to the Koding Public Beta." else\
           "Welcome to the Koding Public Beta!
"
         subtitle  : "Warning! when we say beta - we mean it :)"
+
+    if KD.isLoggedIn()
+        # subtitle : "Last login #{$.timeago new Date account.meta.modifiedAt}
+        # ... where have you been?!" # not relevant for now

       mainView.addSubView updateWidget = new ActivityUpdateWidget
         cssClass: 'activity-update-widget-wrapper'
@@ -401,8 +405,6 @@

     @_state = 'public'

-    @scrollView.setClass "scrollable"
-
     @scrollView.$().scroll =>
       if @scrollView.$().scrollTop() > 10
         @activityHeader.setClass "scrolling-up-outset"


async tidy up

async tidy up

--- src/async.coffee
+++ src/async.coffee
@@ -21,53 +21,55 @@
     initRequestBundle = []
     queries = []

-    maybeDone = dvl.group (request) ->
-      for q in request
-        return if q.status isnt 'ready'
+    maybeDone = dvl.group (requestBundle) ->
+      for request in requestBundle
+        return if request.status isnt 'ready'

       notify = []
-      for q in request
-        q.res.value(q.resVal ? null)
-        q.status = ''
-        q.requestBundle = null
-        delete q.resVal
-
-      return
-
-    getData = (q, query, err, resVal) ->
+      for request in requestBundle
+        request.res.value(request.resVal ? null)
+        request.status = ''
+        request.requestBundle = null
+        delete request.resVal
+
+      return
+
+    getData = (request, query, err, resVal) ->
+      throw new Error("getData called outside of a request") unless q.requestBundle
       if err
-        q.resVal = null
-        q.onError(err) if q.onError
+        request.resVal = null
+        request.onError(err) if request.onError
       else
-        q.resVal = if query then resVal else null
-
-      q.status = 'ready'
-      q.curAjax = null
-
-      maybeDone(q.requestBundle)
-      return
-
-    makeRequest = (q) ->
-      _query = q.query.value()
+        request.resVal = if query then resVal else null
+
+      request.status = 'ready'
+      request.curAjax = null
+
+      maybeDone(request.requestBundle)
+      return
+
+    makeRequest = (request) ->
+      throw new Error("invalid request") unless request in request.requestBundle
+      _query = request.query.value()
       if _query?
-        if q.invalidOnLoad.value()
-          q.res.value(null)
+        if request.invalidOnLoad.value()
+          request.res.value(null)

         outstanding.value(outstanding.value() + 1)
-        oldAjax = q.curAjax
-        q.curAjax = q.requester(
+        oldAjax = request.curAjax
+        request.curAjax = request.requester(
           _query
           (err, data) ->
             outstanding.value(outstanding.value() - 1)
             return if err is 'abort'
-            getData(q, _query, err, data)
+            getData(request, _query, err, data)
             return
         )
         # Abort the old call after making the new
         oldAjax?.abort?()

       else
-        getData(q, _query, null, null)
+        getData(request, _query, null, null)

       return

@@ -88,7 +90,7 @@
         else
           q.status = 'requesting'
           if q.requestBundle
-            # Request may have already completed, sadly we need to scrap it.
+            # Query changed mid request which may have already completed, sadly we need to scrap it.
             delete q.resVal
             makeRequestLater.push(q)
           else


added HTTPS support

added HTTPS support

--- src/Server.coffee
+++ src/Server.coffee
@@ -28,14 +28,19 @@

-	init: (apiKey, port, logger) ->
+	init: (apiKey, cert, key, port, logger) ->
 		if not apiKey? then throw new Error 'Missing `apiKey` parameter'
 		@client = vbb apiKey

+		if not cert? then throw new Error 'Missing `cert` parameter'
+		if not key? then throw new Error 'Missing `key` parameter'
 		if not port? then throw new Error 'Missing `port` parameter'
 		@server = new hapi.Server()
 		@server.connection
-			port:	port
+			tls:
+				cert:	cert
+				key:	key
+			port:		port
 		@server.bind this

 		if not logger? then throw new Error 'Missing `logger` parameter'


:new: Add Remove me TODO in utils

:new: Add Remove me TODO in utils

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -1,3 +1,4 @@
+#TODO: Remove me
 findFile = require './util'

 log = (args...) ->


Activity: add time clause into query

Activity: add time clause into query

--- workers/social/lib/social/models/graph/queryregistry.coffee
+++ workers/social/lib/social/models/graph/queryregistry.coffee
@@ -144,7 +144,7 @@
         LIMIT {limitCount}
       """

-    followingnew:(exemptClause="", type="JNewStatusUpdate")->
+    followingnew:(exemptClause="", timeQuery="", type="JNewStatusUpdate")->
       """
         START member=node:koding(id={userId})
         MATCH member<-[:follower]-members-[:author]-content
@@ -152,6 +152,7 @@
         AND content.group = {groupName}
         AND content.name = "#{type}"
         #{exemptClause}
+        #{timeQuery}
         RETURN content.id as id
         ORDER BY content.`meta.createdAtEpoch` DESC
         SKIP {skipCount}


JPermissionSet: fetchGroupAndPermissionSet: use cache-manager with 60 sec. ttl and introduce callback queue

--- workers/social/lib/social/models/group/permissionset.coffee
+++ workers/social/lib/social/models/group/permissionset.coffee
@@ -80,20 +80,40 @@
     [{ permission, validateWith: require('./validators').any }]

-  fetchGroupAndPermissionSet = (groupName, callback) ->
-
-    JGroup = require '../group'
-    JGroup.one { slug: groupName }, (err, group) ->
-      if err then callback err
-      else unless group?
-        callback new KodingError "Unknown group! #{groupName}"
-      else
-        group.fetchPermissionSetOrDefault (err, permissionSet) ->
-          if err then callback err
-          else callback null, { group, permissionSet }
+  fetchGroupAndPermissionSet = do (queue = {}) ->
+
     memCache = cacheManager.caching
       store  : 'memory'
       ttl    : 60 # seconds
+
+    fetcher = (groupName, callback) ->
+      JGroup = require '../group'
+      JGroup.one { slug: groupName }, (err, group) ->
+        if err then callback err
+        else unless group?
+          callback new KodingError "Unknown group! #{groupName}"
+        else
+          group.fetchPermissionSetOrDefault (err, permissionSet) ->
+            if err then callback err
+            else callback null, { group, permissionSet }
+
+    (groupName, callback) ->
+
+      memCache.get groupName, (err, data) ->
+        return callback err, data  if data
+
+        queue[groupName] ?= []
+        return  if (queue[groupName].push callback) > 1
+
+        fetcher groupName, (err, data) ->
+
+          if err
+            cb err  for cb in queue[groupName]
+          else
+            memCache.set groupName, data  if data
+            cb null, data  for cb in queue[groupName]
+
+          queue[groupName] = []

   @fetchMainGroupAndPermissionSet = (callback) ->


Simplified toLocal_noTrans

Simplified toLocal_noTrans

--- src/Vector.coffee
+++ src/Vector.coffee
@@ -116,10 +116,7 @@

     @set(vec1).add(vec2).add(vec3).add(mat.trans)

-  toLocal_noTrans: (mat) ->
-    vec = DivSugar.Vector._tmpVec1
-    vec.set @dot(mat.xAxis) / mat.xAxis.sqNorm(), @dot(mat.yAxis) / mat.yAxis.sqNorm(), @dot(mat.zAxis) / mat.zAxis.sqNorm()
-    @set vec
+  toLocal_noTrans: (mat) -> @set @dot(mat.xAxis) / mat.xAxis.sqNorm(), @dot(mat.yAxis) / mat.yAxis.sqNorm(), @dot(mat.zAxis) / mat.zAxis.sqNorm()

   toGlobal_noTrans: (mat) ->
     vec1 = DivSugar.Vector._tmpVec1


OPCE6: now exporting the Word class

OPCE6: now exporting the Word class

--- lib/y-rte.coffee
+++ lib/y-rte.coffee
@@ -70,6 +70,9 @@
       @left = rte.getWord @startPos.word
       @right = rte.getWord @endPos.word

+      @left.left.push @
+      @right.right.push @
+
       rte._rte.selections.push @

     else throw new Error "Wrong set of parameters #{[start, end, rte, style]}"
@@ -523,7 +526,7 @@
 if window?
   window.Rte = Rte
   window.Selection = Selection
-  window.buildWord = buildWord
+  window.Word = Word

 if module?
-  module.exports = [Rte, Selection]
+  module.exports = [Rte, Selection, Word]


word in inventory gets underlined too

word in inventory gets underlined too

--- src/kick.coffee
+++ src/kick.coffee
@@ -11,6 +11,7 @@
         @point = 0
         @die = false
         @word = null
+        @pair = null
         @game = null
         @longest = []

@@ -44,7 +45,7 @@

     draw: ->
         for word in @words
-            $('#playground').append(word.show)
+            $('#playground').append(word.repr)

 inventory = new class Inventory
@@ -66,6 +67,11 @@
         else
             @name = new ShootingWord('@neizod')

+    get: (fullword) ->
+        for word, i in @words
+            if fullword == word.full
+                return @words[i]
+
     index: (find) ->
         for word, i in @words
             if word.full == find.full
@@ -73,7 +79,7 @@

     show: ->
         sentence = @words.concat(if @name? then [@name] else [])
-        (word.show.html() for word in sentence).join(' ')
+        (word.repr.html() for word in sentence).join(' ')

@@ -82,12 +88,11 @@
         @remain = @full
         @top = stage_height * Math.random()
         @left = stage_width - 100 # FIXME initial outside cause multiline
-        @show = @make_show()
+        @repr = @make_repr()
+        @update()

-    make_show: ->
+    make_repr: ->
         $('<div>').addClass('shooting-word')
-                  .append($('<u>').html(@done()))
-                  .append($('<b>').html(@remain))
                   .css('top', @top)
                   .css('left', @left)

@@ -97,19 +102,23 @@
     shot: (c) ->
         if c == @remain[0]
             @remain = @remain.slice(1)
+        @update()

     move: ->
         @left -= 1 # TODO render smoother w/ word movement speed
-        @show.css('left', @left)
-        @update()
+        @repr.css('left', @left)

     update: ->
-        @show.empty()
-             .append($('<u>').html(@done()))
-             .append($('<b>').html(@remain))
+        if @remain == @full
+            @repr.html($('<b>').html(@full))
+        else
+            @repr.empty()
+                 .append($('<u>').html(@done()))
+                 .append($('<b>').html(@remain))

     reset: ->
         @remain = @full
+        @update()

 pool_words.add('kick')
@@ -133,10 +142,10 @@
         if not player.game?
             player.game = setInterval(draw, 12)
     if event.keyCode in [8, 27, 46] # backspace, escape, delete
-        if player.word?
-            player.word.reset()
-            player.word.update()
-            player.word = null
+        player.word?.reset()
+        player.word = null
+        player.pair?.reset()
+        player.pair = null

 $(document).keypress (event) ->
     c = String.fromCharCode(event.charCode)
@@ -145,13 +154,18 @@
     if not player.word? and inventory.name?
         if c == inventory.name.remain[0]
             player.word = inventory.name
+    if player.word?
+        if player.word.full == player.word.remain
+            player.pair = inventory.get(player.word.full)
     player.word?.shot(c)
+    player.pair?.shot(c)
     if player.word?.remain == ''
         player.point += player.word.full.length
         inventory.add(player.word)
         pool_words.update()
         player.word.reset()
-        player.word.update()
         player.word = null
         player.lvl += 1
-        pool_words.autofill(player.lvl)
+        pool_words.autofill()
+    if player.pair?.remain == ''
+        player.pair = null


start on demand (here: press enter -- fix later)

--- src/kick.coffee
+++ src/kick.coffee
@@ -5,6 +5,7 @@
 stage_height = 300
 stage_width = 1000

+animate_id = null
 pool_words = []
 shoot_word = null
 shoot_name = null
@@ -88,13 +89,15 @@
         player_die = true if word.update()
         $('#playground').append(word.show)
     if player_die
+        clearInterval(animate_id)
+        animate_id = null
         $('#playground').css('background-color', 'darkred')

-$(document).ready ->
-    setInterval(draw, 12)
-
 $(document).keydown (event) ->
+    if event.keyCode == 13 # enter
+        if not animate_id?
+            animate_id = setInterval(draw, 12)
     if event.keyCode in [8, 27, 46] # backspace, escape, delete
         if shoot_word?
             shoot_word.reset()


Pause event docs

Pause event docs

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

+# Internals for `Kona.ready()`
 Kona.readyQueue = []
 Kona.isReady    = false

+
 # Pause logic
+
 # If gamePaused is set to true, the current scene will draw but not update
 # freezing things until gameplay resumes
 Kona.gamePaused = false

+
+# Toggle the pause state, e.g., unpaused -> paused or vice versa
+# Triggers a corresponding event, either onPause or onResume
+# This can be bound directly or using `Kona.onPause` and `Kona.onResume`
 Kona.togglePause = ->
-  # TODO: where to dismiss sign backgrounds?
-  # Can't put it here - signs are not core
-  # for ent in Kona.Scenes.currentScene.getEntities('sign_backgrounds')
-  #   ent.destroy()
   @gamePaused = !@gamePaused
-  if @gamePaused
-    Kona.Events.trigger('onPause')
-  else
-    Kona.Events.trigger('onResume')
+  event = if @gamePaused then 'Pause' else 'Resume'
+  Kona.Events.trigger("on#{event}")

+# Bind a function to the game pause event
+# Ex: `Kona.onPause -> console.log("Game was paused!")`
 Kona.onPause  = (fn) -> Kona.Events.bind('onPause', fn)
+
+
+# Bind a function to the game resume (unpause) event
+# Ex: `Kona.onPause -> console.log("Game was resumed!")`
 Kona.onResume = (fn) -> Kona.Events.bind('onResume', fn)
+

 # Invoke a callback function when the document has fully loaded. Can be invoked


Start VM export only when necessary.

Start VM export only when necessary.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -697,7 +697,12 @@

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

-handleExport = (req, res, { stream }) ->
+handleExport = $coroutine (req, res, {xapi, id, compress, onlyMetadata}) ->
+  stream = yield xapi.exportVm(id, {
+    compress: compress ? true,
+    onlyMetadata: onlyMetadata ? false
+  })
+
   upstream = stream.response

   # Remove the filename as it is already part of the URL.
@@ -716,13 +721,15 @@
   if vm.power_state is 'Running'
     yield checkPermissionOnSrs.call(this, vm)

-  stream = yield @getXAPI(vm).exportVm(vm._xapiId, {
-    compress: compress ? true,
-    onlyMetadata: onlyMetadata ? false
-  })
+  data = {
+    xapi: @getXAPI(vm),
+    id: vm._xapiId,
+    compress,
+    onlyMetadata
+  }

   return {
-    $getFrom: yield @registerHttpRequest(handleExport, { stream }, {
+    $getFrom: yield @registerHttpRequest(handleExport, data, {
       suffix: encodeURI("/#{vm.name_label}.xva")
     })
   }


* update attributes unit test

* update attributes unit test

--- test/attributes-test.coffee
+++ test/attributes-test.coffee
@@ -16,6 +16,7 @@
   constructor: ->return super
   $attributes: attrs = Attributes
     haha: 'Boolean'
+    $hidden: 'Number'
     hidden:
       type: 'Number'
       enumerable: false
@@ -100,6 +101,8 @@
       required:
         "name": "required"
         "type": "Boolean"
+      $hidden:
+        type: 'Number'
       hidden:
         type: 'Number'
         enumerable: false
@@ -114,12 +117,13 @@
         type:
           name: 'Number'
           max: 3
-  it.only "should not export hidden attributes", ->
-    t = TestType min:-1, hidden: 34, max:3
+  it "should not export hidden attributes", ->
+    t = TestType min:-1, hidden: 34, max:3, $hidden:122
     should.exist t
     t.should.be.not.equal TypeInfo('Test')
     t.should.have.property 'min', -1
     t.should.have.property 'hidden', 34
+    t.should.have.property '$hidden', 122
     t.should.have.property 'max', 3
     t.should.have.property 'name', 'Test'
     t.toObject().should.be.deep.equal


groups: wip

groups: wip

--- client/landing/app/MainApp/lazy/staticgroupcontroller.coffee
+++ client/landing/app/MainApp/lazy/staticgroupcontroller.coffee
@@ -177,10 +177,15 @@
         @groupContentView.$('.content-item-scroll-wrapper').css {marginBottom}

       # @groupContentView.on "scroll", =>
-      #   scrollTop = @groupContentView.getScrollTop()
+      #   lastVal = 0
       #   for title, i in titles
-      #     if $(title).position().top > scrollTop
-      #       log items[i].title
+      #     if newVal = $(title).offset().top < 0
+      #       if ~lastVal > ~newVal
+      #         log title
+      #         break
+      #       lastVal = newVal
+
+

   scrollToTitle:(itemData)->


Remove comments and simplify

Remove comments and simplify

--- src/brain.coffee
+++ src/brain.coffee
@@ -18,24 +18,22 @@
   #
   # Returns the instance for chaining.
   set: (key, value) ->
-    # Parse key if key is object.
-    if isObject key
+    if key is Object(key)
       pair = key
     else
       pair = {}
       pair[key] = value

-    # Store key-value pair.
     extend @data._private, pair
     @emit 'loaded', @data
-
     @

   # Public: Get value by key from the private namespace in @data
   # or return null if not found.
   #
   # Returns the value.
-  get: (key) -> @data._private[key] ? null
+  get: (key) ->
+    @data._private[key] ? null

   # Public: Emits the 'save' event so that 'brain' scripts can handle
   # persisting.
@@ -126,8 +124,6 @@
   usersForFuzzyName: (fuzzyName) ->
     matchedUsers = @usersForRawFuzzyName(fuzzyName)
     lowerFuzzyName = fuzzyName.toLowerCase()
-    # We can scan matchedUsers rather than all users since usersForRawFuzzyName
-    # will include exact matches
     for user in matchedUsers
       return [user] if user.name.toLowerCase() is lowerFuzzyName

@@ -139,12 +135,6 @@
 extend = (obj, sources...) ->
   for source in sources
     obj[key] = value for own key, value of source
-
   obj

-# Private: Check if argument is an object.
-#
-# Returns true/false.
-isObject = (obj) -> obj is Object obj
-
 module.exports = Brain


Add ruby support

Add ruby support

--- src/list.coffee
+++ src/list.coffee
@@ -20,7 +20,7 @@
   'meteor-npm': /(^|\/)(packages\.json)$/
   'meteor-meteorite': /(^|\/)(smart\.json)$/
   'python': /(^|\/)(requirements\.txt)$/
-  # 'ruby': /(^|\/)(Gemfile)$/
+  'ruby': /(^|\/)(Gemfile)$/
   # 'java': /(^|\/)(pom\.xml)$/

 githubapi = new GithubApi(
@@ -121,6 +121,16 @@
       lines = lines.map (l) -> l.split('==')[0]
       return lines

+rubyPackager =
+  getPackages: (fileContent) ->
+    if fileContent
+      lines = fileContent.split('\n').filter((line) -> line.indexOf('#') != 0 and line.trim().length > 0)
+      lines = lines.map (l) ->
+        match = l.match(/\s*gem\s+['"]([^'"]+)/)
+        if match then match[1] else null
+      lines = _.compact(lines)
+      return lines
+
 npmPackager =
   getPackages: (fileContent) ->
     if fileContent
@@ -143,3 +153,4 @@
   'npm': npmPackager
   'meteor-meteorite': meteoritePackager
   'python': pythonPackager
+  'ruby': rubyPackager


Build change event consistently

Build change event consistently

--- src/main.coffee
+++ src/main.coffee
@@ -91,18 +91,18 @@
           @emitter.emit('did-change', {event, newFilePath})
         when 'child-rename'
           if @isWatchingParent
-            @onChange('rename', newFilePath) if @path is oldFilePath
+            @onChange({event: 'rename', newFilePath}) if @path is oldFilePath
           else
-            @onChange('change', '')
+            @onChange({event: 'change', newFilePath: ''})
         when 'child-delete'
           if @isWatchingParent
-            @onChange('delete', null) if @path is newFilePath
+            @onChange({event: 'delete', newFilePath: null}) if @path is newFilePath
           else
-            @onChange('change', '')
+            @onChange({event: 'change', newFilePath: ''})
         when 'child-change'
-          @onChange('change', '') if @isWatchingParent and @path is newFilePath
+          @onChange({event: 'change', newFilePath: ''}) if @isWatchingParent and @path is newFilePath
         when 'child-create'
-          @onChange('change', '') unless @isWatchingParent
+          @onChange({event: 'change', newFilePath: ''}) unless @isWatchingParent

     @disposable = @handleWatcher.onDidChange(@onChange)


Bind children loosely

Bind children loosely

--- src/main.coffee
+++ src/main.coffee
@@ -75,8 +75,8 @@
           currentItem.get().data.set(data)
       }
     ]
-    T.svg {class: 'graphics'},[
-     T.rect {x:10, y:20, height:100, width:100, fill:"blue", stroke: "red"}, [
+    T.svg {class: 'graphics'}, bind ->[
+     T.rect {x:10, y:20, height:100, width:100, fill:"blue", stroke: "red"}, bind -> [
        T.animateTransform({
         attributeName: "transform"
         begin: "0s"


Refactor AtomWindow code

Refactor AtomWindow code

--- src/main.coffee
+++ src/main.coffee
@@ -118,36 +118,24 @@
       resourcePath: @resourcePath

 class AtomWindow
+  browserWindow: null
   bootstrapScript: null
   resourcePath: null

   constructor: ({@bootstrapScript, @resourcePath}) ->
-    @window = @open()
+    @browserWindow = new BrowserWindow width: 800, height: 600, show: false, title: 'Atom'
+    @handleEvents()

-    @window.on 'close', (event) =>
-      event.preventDefault()
-      ipc.sendChannel @window.getProcessId(), @window.getRoutingId(), 'close'
+    atomApplication.windows.push @browserWindow

-  open: ->
-    params = [
-      {name: 'bootstrapScript', param: @bootstrapScript},
-      {name: 'resourcePath', param: @resourcePath},
-    ]
+    url = "file://#{@resourcePath}/static/index.html?bootstrapScript=#{@bootstrapScript}&resourcePath=#{@resourcePath}"
+    @browserWindow.loadUrl url
+    @browserWindow.show()

-    @openWithParams(params)
-
-  openWithParams: (pairs) ->
-    win = new BrowserWindow width: 800, height: 600, show: false, title: 'Atom'
-    atomApplication.windows.push win
-    win.on 'destroyed', =>
+  handleEvents: ->
+    @browserWindow.on 'destroyed', =>
       atomApplication.windows.splice atomApplication.windows.indexOf(win), 1

-    url = "file://#{@resourcePath}/static/index.html"
-    separator = '?'
-    for pair in pairs
-      url += "#{separator}#{pair.name}=#{pair.param}"
-      separator = '&' if separator is '?'
-
-    win.loadUrl url
-    win.show()
-    win
+    @browserWindow.on 'close', (event) =>
+      event.preventDefault()
+      ipc.sendChannel @browserWindow.getProcessId(), @browserWindow.getRoutingId(), 'close'


tests: improve user experiment tests

tests: improve user experiment tests

--- test/end_to_end/user.coffee
+++ test/end_to_end/user.coffee
@@ -181,3 +181,10 @@
         .expect 200, Joi.object().unknown().keys {
           override_exp_1: 'blue'
         }
+
+    describe '400', ->
+      it 'rejects invalid keys', ->
+        flare
+          .thru util.createUser()
+          .get '/users/me/experiments', {key: null}
+          .expect 400


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -25,6 +25,6 @@
     x: 4
     y: 20

-  equals engine.first('type="testObj"'), obj1
+  equals engine.first('type="testObj"').length, obj1

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -1,7 +1,7 @@
 module "Engine"

 test "#closest", ->
-
+  engine = Engine()

 test "#each", ->
   engine = Engine()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -1,6 +1,6 @@
 module "EngineSelector"

-test "engine#"
+test "engine#find",

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -23,6 +23,6 @@
     x: 4
     y: 20

-  equals engine.first('type=')
+  equals engine.first('type="test"')

 module()