a bunch of code reorderings to the scaled kernel

--- library/nucleus/scaled.coffee
+++ library/nucleus/scaled.coffee
@@ -288,9 +288,11 @@
         nh = "no Seaport host found in the configuration"
         np = "no Seaport port found in the configuration"
         nt = "no Seaport opts found in the configuration"
+        brokenLib = "the Seaport library seems to be broken"
         assert _.isString(host = nconf.get "hub:host"), nh
         assert _.isNumber(port = nconf.get "hub:port"), np
         assert _.isObject(opts = nconf.get "hub:opts"), nt
+        assert _.isFunction(seaport?.connect), brokenLib
         this.seaport = seaport.connect host, port, opts
         assert _.isObject(@seaport), "seaport failed"
         assert @seaport.register?, "a broken seaport"
@@ -304,12 +306,16 @@
     # an actual application. This method gets passes continuation
     # that does that. The method can either invoke it or omit it.
     kernelPreemption: (continuation) ->
+        assert identify = try @constructor.identify()
         assert _.isObject(@options), "got no options"
         either = @options.master or @options.instance
         assert either, "no master and no instance mode"
+        nmast = "incorrect master configuration object"
+        message = "Kernel preemption at %s being invoked"
+        logger.silly message.red, (try identify.bold)
         return continuation() unless @options.master
         logger.warn "The kernel is booting as master"
-        assert nconf.get("master"), "no master config"
+        assert _.isObject(nconf.get "master"), nmast
         assert not _.isEmpty @createSeaportServer()
         assert not _.isEmpty @startupHttpsMaster()
         assert not _.isEmpty @startupHttpMaster()


Disable bet buttons when you don't have that much to bet

--- app/controllers/actions.coffee
+++ app/controllers/actions.coffee
@@ -1,4 +1,6 @@
 Spine = require('spine')
+Player = require('models/player')
+$ = Spine.$

 class Actions extends Spine.Controller

@@ -16,6 +18,7 @@
     super
     @html require('views/actions')()
     Spine.bind 'result', @finished
+    Player.bind 'change', @bank_check
     @setup()

   setup: =>
@@ -32,6 +35,11 @@
     hide @action_buttons
     show @continue_button

+  bank_check: (player) =>
+    for el in @bet_buttons.find('button')
+      bet_button = $(el)
+      (disable bet_button) if bet_button.data('amount') > player.pot.size
+
   # Private

   hide = (selection) ->


Adding a proper date format because http://xkcd.com/1179/

--- src/sensu.coffee
+++ src/sensu.coffee
@@ -16,7 +16,7 @@
 #   hubot sensu remove stash <stash> - remove a stash from sensu
 #   hubot sensu clients - show all clients
 #   hubot sensu client <client>[ history] - show a specific client['s history]
-#   hubot sensu sensu remove client <client> - remove a client from sensu
+#   hubot sensu remove client <client> - remove a client from sensu
 #   hubot sensu events[ for <client>] - show all events or for a specific client
 #   hubot sensu resolve event <client>/<service> - resolve a sensu event
 #
@@ -99,7 +99,7 @@
         output = []
         for result,value of results
           console.log value
-          message = value['path'] + ' added on ' + moment.unix(value['content']['timestamp']).format('HH:MM M/D/YY')
+          message = value['path'] + ' added on ' + moment.unix(value['content']['timestamp']).format('HH:MM YY/M/D')
           if value['content']['reason']
             message = message + ' reason: ' + value['content']['reason']
           if value['expire'] and value['expire'] > 0


add option to allow self signed ssl certificates

--- src/sensu.coffee
+++ src/sensu.coffee
@@ -31,8 +31,13 @@

 config =
   sensu_api: process.env.HUBOT_SENSU_API_URL
-
+  allow_invalid_certs: process.env.HUBOT_SENSU_API_ALLOW_INVALID_CERTS
 moment = require('moment')
+
+if config.allow_invalid_certs
+  http_options = rejectUnauthorized: false
+else
+  http_options = {}

 module.exports = (robot) ->

@@ -62,7 +67,8 @@
   robot.respond /sensu info/i, (msg) ->
     validateVars
     credential = createCredential()
-    req = robot.http(config.sensu_api + '/info')
+
+    req = robot.http(config.sensu_api + '/info', http_options)
     if credential
       req = req.headers(Authorization: credential)
     req
@@ -87,7 +93,7 @@
   robot.respond /(?:sensu)? stashes/i, (msg) ->
     validateVars
     credential = createCredential()
-    req = robot.http(config.sensu_api + '/stashes')
+    req = robot.http(config.sensu_api + '/stashes', http_options)
     if credential
       req = req.headers(Authorization: credential)
     req
@@ -150,7 +156,7 @@
     data['path'] = 'silence/' + path

     credential = createCredential()
-    req = robot.http(config.sensu_api + '/stashes')
+    req = robot.http(config.sensu_api + '/stashes', http_options)
     if credential
       req = req.headers(Authorization: credential)
     req
@@ -169,7 +175,7 @@
     unless stash.match /^silence\//
       stash = 'silence/' + stash
     credential = createCredential()
-    req = robot.http(config.sensu_api + '/stashes/' + stash)
+    req = robot.http(config.sensu_api + '/stashes/' + stash, http_options)
     if credential
       req = req.headers(Authorization: credential)
     req
@@ -190,7 +196,7 @@
   robot.respond /sensu clients/i, (msg) ->
     validateVars
     credential = createCredential()
-    req = robot.http(config.sensu_api + '/clients')
+    req = robot.http(config.sensu_api + '/clients', http_options)
     if credential
       req = req.headers(Authorization: credential)
     req
@@ -215,7 +221,7 @@
     client = msg.match[1]

     credential = createCredential()
-    req = robot.http(config.sensu_api + '/clients/' + client + '/history')
+    req = robot.http(config.sensu_api + '/clients/' + client + '/history', http_options)
     if credential
       req = req.headers(Authorization: credential)
     req
@@ -249,7 +255,7 @@
       return

     credential = createCredential()
-    req = robot.http(config.sensu_api + '/clients/' + client)
+    req = robot.http(config.sensu_api + '/clients/' + client, http_options)
     if credential
       req = req.headers(Authorization: credential)
     req
@@ -271,7 +277,7 @@
     client= msg.match[1]

     credential = createCredential()
-    req = robot.http(config.sensu_api + '/clients/' + client)
+    req = robot.http(config.sensu_api + '/clients/' + client, http_options)
     if credential
       req = req.headers(Authorization: credential)
     req
@@ -297,7 +303,7 @@
       client = ''

     credential = createCredential()
-    req = robot.http(config.sensu_api + '/events' + client)
+    req = robot.http(config.sensu_api + '/events' + client, http_options)
     if credential
       req = req.headers(Authorization: credential)
     req
@@ -329,7 +335,7 @@
     data['check'] = msg.match[2]

     credential = createCredential()
-    req = robot.http(config.sensu_api + '/resolve')
+    req = robot.http(config.sensu_api + '/resolve', http_options)
     if credential
       req = req.headers(Authorization: credential)
     req


added a detailed comment in prefs-ui.coffee

--- client/prefs-ui.coffee
+++ client/prefs-ui.coffee
@@ -1,5 +1,14 @@
 prefs = require './prefs'
 {join, delay} = require './util'
+
+# This module implements the Preferences dialog.
+# The actual content of tabs is in separate modules: prefs-*.coffee
+# Each of them can export the following properties:
+#   name     a string used as the tab's title
+#   init()   called only once, when Preferences is opened for the first time
+#   load()   called every time Preferences is opened
+#   save()   called when Save is pressed
+#   resize() called when the Preferences dialog is resized or the tab is selected

 tabs = [
   require './prefs-glyphs'
@@ -19,7 +28,7 @@
       $.alert v.message, 'Error', if v.element then -> v.element.focus(); return
       return
     return
-  for t in tabs then t.save()
+  for t in tabs then t.save?()
   $d.dialog 'close'; false

 module.exports = (tabName) ->
@@ -42,7 +51,7 @@
           {text: 'OK', click: ok}
           {text: 'Cancel', click: -> $d.dialog 'close'; return}
         ]
-    for t in tabs then t.init $ "#prefs-tab-#{safe t.name}"
+    for t in tabs then t.init? $ "#prefs-tab-#{safe t.name}"
   $d.dialog('option', 'position', at: 'center').dialog 'open'
   if tabName then $d.tabs active: $("#prefs-tabs-nav a[href='#prefs-tab-#{tabName}']").parent().index()
   for t in tabs then t.load?()


fix(lists): Commented out during testing, landed accidentally

--- src/components/contenteditable/list-manager.coffee
+++ src/components/contenteditable/list-manager.coffee
@@ -13,24 +13,24 @@
     @_collapseAdjacentLists(editor)

   @onKeyDown: ({editor, event}) ->
-    # @_spaceEntered = event.key is " "
-    # if DOMUtils.isInList()
-    #   if event.key is "Backspace" and DOMUtils.atStartOfList()
-    #     event.preventDefault()
-    #     @outdentListItem(editor)
-    #   else if event.key is "Tab" and editor.currentSelection().isCollapsed
-    #     event.preventDefault()
-    #     if event.shiftKey
-    #       @outdentListItem(editor)
-    #     else
-    #       editor.indent()
-    #   else
-    #     # Do nothing, let the event through.
-    #     @originalInput = null
-    # else
-    #   @originalInput = null
-    #
-    # return event
+    @_spaceEntered = event.key is " "
+    if DOMUtils.isInList()
+      if event.key is "Backspace" and DOMUtils.atStartOfList()
+        event.preventDefault()
+        @outdentListItem(editor)
+      else if event.key is "Tab" and editor.currentSelection().isCollapsed
+        event.preventDefault()
+        if event.shiftKey
+          @outdentListItem(editor)
+        else
+          editor.indent()
+      else
+        # Do nothing, let the event through.
+        @originalInput = null
+    else
+      @originalInput = null
+
+    return event

   @bulletRegex: -> /^[*-]\s/


Table config should have `key` as primary key

--- src/db.coffee
+++ src/db.coffee
@@ -9,8 +9,7 @@
 knex.schema.hasTable('config').then((exists) ->
 	if not exists
 		knex.schema.createTable('config', (t) ->
-			t.increments('id').primary()
-			t.string('key')
+			t.string('key').primary()
 			t.string('value')
 		)
 )


APIV1 Student show detail fix

APIV1 Student show detail fix

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -177,7 +177,7 @@
       )

     # Fetch artworks
-    for artwork_uri in $scope.student.artworks
+    for artwork_uri in $scope.student.artist.artworks
       matches = artwork_uri.match(/\d+$/)
       if matches
         artwork_id = matches[0]


Default regional dashboard to Lindi

Default regional dashboard to Lindi

--- app/scripts/dcplotsctrl.coffee
+++ app/scripts/dcplotsctrl.coffee
@@ -83,7 +83,7 @@
         #$scope.geojson = results[2]

         $scope.regions = regs.sort()
-        $scope.region = $scope.regions[3]
+        $scope.region = $scope.regions[7]

         # FIXME:
         # unfortunately, for some reason, not all dc charts manage to pickup the


Marca con la posición que se quiere mostrar en el mapa.

--- app/scripts/google_maps.coffee
+++ app/scripts/google_maps.coffee
@@ -1,8 +1,14 @@
 $(document).ready ->
   $("

").appendTo("#main")
+  position = new google.maps.LatLng(37.345107,-5.932638)
   options =
     zoom:       14
-    center:     new google.maps.LatLng(37.345107,-5.932638)
+    center:     position
     mapTypeId:  google.maps.MapTypeId.ROADMAP

   map = new google.maps.Map(document.getElementById("map"), options)
+
+  marker = new google.maps.Marker
+    position:  position
+    map:       map
+    title:     "Calesur"


Sunrise

Sunrise

--- app/scripts/scenes/City.coffee
+++ app/scripts/scenes/City.coffee
@@ -4,7 +4,7 @@
   Game = window.Game

   # constructor
-  Crafty.background('#8080FF')
+  Crafty.background('#602020')

   level = Game.levelGenerator.createLevel
     stage: data.stage
@@ -14,6 +14,30 @@
   level.generateBlocks amount: 10
   level.addBlock('GameplayDemo.End')
   level.start()
+
+  v = 0
+  co =
+    r: 0x60
+    g: 0x20
+    b: 0x20
+  cd =
+    r: 0x80
+    g: 0x80
+    b: 0xFF
+  steps = 80
+  Crafty.e('Delay').delay(
+    =>
+      v += 1
+      p = v * 1.0 / steps
+      c =
+        r: co.r * (1 - p) + cd.r * p
+        b: co.b * (1 - p) + cd.g * p
+        g: co.g * (1 - p) + cd.b * p
+      cs = (i.toString(16).slice(0, 2) for k, i of c)
+
+      Crafty.background("##{cs.join('')}")
+      #console.log "##{cs.join('')}"
+  , 500, steps - 1)

   Crafty.bind 'EndOfLevel', ->
     level.stop()


Minor optimization

Minor optimization

--- src/state.coffee
+++ src/state.coffee
@@ -17,9 +17,10 @@
   addObject: (obj) ->
     if obj not instanceof Game.Object
       throw new Game.GameError(Game.GameError.ErrorType.NOT_OBJECT)
-    if obj.getId() of @objects
+    oid = obj.getId()
+    if oid of @objects
       throw new Game.GameError(Game.GameError.ErrorType.ALREADY_ADDED)
-    @objects[obj.getId()] = obj
+    @objects[oid] = obj
     @listen(obj, @onEvent)
     # call object added method
     obj.added()


comment docker utility functions

comment docker utility functions

--- src/docker-utils.coffee
+++ src/docker-utils.coffee
@@ -88,6 +88,7 @@
 					.catch (err) ->
 						console.log('Error deleting image:', image.Id, image.RepoTags, err)

+	# Return true if an image exists in the local docker repository, false otherwise.
 	exports.imageExists = imageExists = (imageId) ->
 		image = docker.getImage(imageId)
 		image.inspectAsync().then ->
@@ -95,6 +96,7 @@
 		.catch (e) ->
 			return false

+	# Get the id of an image on a given registry and tag.
 	exports.getImageId = getImageId = (registry, imageName, tag='latest') ->
 		request("http://#{registry}/v1/repositories/#{imageName}/tags")
 		.spread (res, data) ->
@@ -105,6 +107,7 @@
 			tags = JSON.parse(data)
 			return tags[tag]

+	# Return the ids of the layers of an image.
 	exports.getImageHistory = getImageHistory = (registry, imageId) ->
 		request("http://#{registry}/v1/images/#{imageId}/ancestry")
 		.spread (res, data) ->
@@ -113,6 +116,8 @@
 			history = JSON.parse(data)
 			return history

+	# Return the number of bytes docker has to download to pull this image (or layer).
+	# If the image is already downloaded, then 0 is returned.
 	exports.getImageDownloadSize = getImageDownloadSize = (registry, imageId) ->
 		imageExists(imageId)
 		.then (exists) ->
@@ -124,6 +129,10 @@
 					throw new Error("Failed to get image download size of #{imageId} from #{registry}. Status code: #{res.statusCode}")
 				return parseInt(res.headers['x-docker-size'])

+	# Get download size of the layers of an image.
+	# The object returned has layer ids as keys and their download size as values.
+	# Download size is the size that docker will download if the image will be pulled now.
+	# If some layer is already downloaded, it will return 0 size for that layer.
 	exports.getLayerDownloadSizes = getLayerDownloadSizes = (image, tag='latest') ->
 		{registry, imageName} = getRegistryAndName(image)
 		imageSizes = {}
@@ -135,6 +144,8 @@
 				imageSizes[layerId] = size
 		.return(imageSizes)

+	# Return percentage from current completed/total, handling edge cases.
+	# Null total is considered an unknown total and 0 percentage is returned.
 	calculatePercentage = (completed, total) ->
 		if not total?
 			percentage = 0 # report 0% if unknown total size
@@ -144,10 +155,13 @@
 			percentage = (100 * completed) // total
 		return percentage

+	# Separate string containing registry and image name into its parts.
+	# Example: registry.staging.resin.io/resin/rpi
+	#          { registry: "registry.staging.resin.io", imageName: "resin/rpi" }
 	getRegistryAndName = (image) ->
 		slashPos = image.indexOf('/')
 		if slashPos < 0
-			throw new Error("Expected image name including registry domain name")
+			throw new Error("Expected image name to include registry domain name")
 		registry = image.substr(0,slashPos)
 		imageName = image.substr(slashPos+1)
 		return {registry, imageName}
@@ -156,6 +170,11 @@
 	# to include total progress metrics.
 	#
 	# The docker pull output should be piped to this stream as separate javascript objects.
+	#
+	# Stream:
+	# { status: "status", progressDetail: { current } }
+	#                     =>
+	# { status: "status", progressDetail: { current }, totalProgress: { downloadedSize, totalSize, percentage } }
 	pullProgressStream = (image) ->
 		totalSize = 0
 		completedSize = 0
@@ -180,5 +199,5 @@
 				percentage = calculatePercentage(downloadedSize, totalSize)
 				pull.totalProgress = { downloadedSize, totalSize, percentage }
 				callback(null, pull)
-			.catch (error) =>
-				callback(null, { error })
+			.catch (e) ->
+				callback(null, { error: e.message })


fixed typo

fixed typo

--- main.coffee
+++ main.coffee
@@ -1,4 +1,4 @@
-fexpress = require 'express'
+express = require 'express'
 https = require 'https'
 httpProxy = require 'http-proxy'
 process = require 'child_process'


tighten up the database reconnect options, since not having the db kills our whole app...

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -129,7 +129,12 @@
         )

     _init: (authKey) =>
-        @r = rethinkdbdash(servers:({host:h, authKey:authKey} for h in @_hosts))
+        opts =
+            maxExponent : 4
+            timeout     : 8
+            #discovery   : true  # this option conflicts with password auth -- https://github.com/neumino/rethinkdbdash/issues/133
+            servers: ({host:h, authKey:authKey} for h in @_hosts)
+        @r = rethinkdbdash(opts)
         @db = @r.db(@_database)

     table: (name) => @db.table(name)


storage: make replicate_all in testing start/stop mode work more sensibly.

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -2363,7 +2363,7 @@
                 database.select
                     table   : 'projects'
                     columns : ['project_id']
-                    limit   : if opts.stop? then opts.stop else 1000000       # TODO: change to use paging...
+                    limit   : 1000000       # TODO: change to use paging...
                     cb      : (err, result) ->
                         projects = result
                         cb(err)


fix for json crap

fix for json crap

--- lib/lt3.coffee
+++ lib/lt3.coffee
@@ -705,7 +705,6 @@
               hide_from_search_engines: ''
               icons:
                 favicon: ''
-                apple:
                 apple57x57: ''
                 apple72x72: ''
                 apple114x114: ''


make onItemClick optional

make onItemClick optional


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -22,4 +22,3 @@
       activeTweens[property] =
         target: target
         start: I[property]
-


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -8,7 +8,6 @@
 @param {Core} self Reference to including object
 ###
 Tween = (I={}, self) ->
-  # Set some default properties
   Object.reverseMerge I,
     activeTweens: {}


Fix missed var rename isPluginsFile -> isPackageFile

--- src/watch.coffee
+++ src/watch.coffee
@@ -415,7 +415,7 @@
       isConfigFile = possibleConfigFiles[path]
       isPackageFile = path is packageConfig
       if isConfigFile or isPackageFile
-        reload isPluginsFile
+        reload isPackageFile
       else if path is bowerConfig
         helpers.install config.paths.root, 'bower'
       else


Make erlangHome optional - fallback to system's default

--- lib/alchemide/wrapper.coffee
+++ lib/alchemide/wrapper.coffee
@@ -31,13 +31,15 @@
   #line 1  #line 2

   erlPath = atom.config.get("#{name}.erlangHome").trim()
-  if !erlPath
-    atom.notifications.addError('Erlang home configuration setting missing')
+  env = if erlPath
+      extend({
+        ERL_HOME: erlPath,
+        ERL_PATH: path.join(erlPath, 'erl')
+      }, process.env)
+  else
+      process.env
   options = {
-    env: extend({
-      ERL_HOME: erlPath,
-      ERL_PATH: path.join(erlPath, 'erl')
-    }, process.env)
+    env: env
   }

   console.log(setting)


added nko config

added nko config

--- server.coffee
+++ server.coffee
@@ -1,6 +1,8 @@
 express = require 'express'
 games = {}
 gameIDs = []
+
+require('nko')('LqNOy7Hy1JHYB4yU')

 pusher_key = (process.env.PUSHER_KEY || 'af77425a09a90cbee51c')


Added _runInside method.

Added _runInside method.

Needed by reactive publish so that observe callbacks can be fake-run
inside a computation.

--- server.coffee
+++ server.coffee
@@ -291,19 +291,28 @@
           callback @
       @_onStopCallbacks = []

+  # Runs an arbitrary function inside the computation. This allows breaking many assumptions, so use it very carefully.
+  _runInside: (func) ->
+    # TODO: Why reactive publish tests freeze if we wrap this method into FiberUtils.synchronize?
+    Meteor._nodeCodeMustBeInFiber()
+    previousTrackerInstance = Tracker._trackerInstance()
+    Fiber.current._trackerInstance = @_trackerInstance
+    previousComputation = @_trackerInstance.currentComputation
+    @_trackerInstance.setCurrentComputation @
+    previousInCompute = @_trackerInstance.inCompute
+    @_trackerInstance.inCompute = true
+    try
+      func @
+    finally
+      Fiber.current._trackerInstance = previousTrackerInstance
+      @_trackerInstance.setCurrentComputation previousComputation
+      @_trackerInstance.inCompute = previousInCompute
+
   _compute: ->
     FiberUtils.synchronize guard, @_id, =>
       @invalidated = false

-      previous = @_trackerInstance.currentComputation
-      @_trackerInstance.setCurrentComputation @
-      previousInCompute = @_trackerInstance.inCompute
-      @_trackerInstance.inCompute = true
-      try
-        @_func @
-      finally
-        @_trackerInstance.setCurrentComputation previous
-        @_trackerInstance.inCompute = previousInCompute
+      @_runInside @_func

   _needsRecompute: ->
     @invalidated and not @stopped


handle remaining regexes in grammar's main part

--- lib/atom-syntax-tools.coffee
+++ lib/atom-syntax-tools.coffee
@@ -195,6 +195,11 @@

     if 'macros' of G
       delete G.macros
+
+    # resolve final macros
+    for k,v of G
+      if v instanceof RegExp or typeof v is 'string'
+        G[k] = @resolveMacros(v)

     if print
       if print.match /\.cson$/


git ssh fails when no cert exists

git ssh fails when no cert exists

Builds fail on heroku b/c no SSH cert is sent to github. HTTPS should
work, however.

--- lib/cli/lineman-fetch.coffee
+++ lib/cli/lineman-fetch.coffee
@@ -3,7 +3,7 @@

 module.exports = (recipes = [], cb) ->
   return displayHelp() if recipes.length == 0
-  fetcher recipes, {recipeRepo: "git@github.com:linemanjs/fetcher-recipes.git"}, (er) ->
+  fetcher recipes, {recipeRepo: "https://github.com/linemanjs/fetcher-recipes.git"}, (er) ->
     grunt.warn(er) if er?
     return cb?(er) if er?
     cb?(null)


tweak keymap

tweak keymap

--- keymaps/julia-client.cson
+++ keymaps/julia-client.cson
@@ -1,11 +1,13 @@
 # TODO: don't apply all these commands to the console

-'.platform-darwin atom-text-editor[data-grammar="source julia"]:not([mini])':
+'.platform-darwin .item-views > atom-text-editor[data-grammar="source julia"]:not([mini])':
   'cmd-enter': 'julia-client:evaluate'
   'cmd-shift-enter': 'julia-client:evaluate-all'
   'cmd-j cmd-m': 'julia-client:set-working-module'
   'cmd-shift-j cmd-shift-m': 'julia-client:reset-working-module'
   'ctrl-`': 'julia-client:toggle-console'
+
+'.platform-darwin .console':
   'ctrl-c': 'julia-client:interrupt-julia'

 '.platform-darwin atom-workspace':
@@ -16,13 +18,16 @@
   'cmd-j cmd-l': 'julia-client:reset-loading-indicator'
   'cmd-j cmd-k': 'julia-client:kill-julia'

-'.platform-win32 atom-text-editor[data-grammar="source julia"]:not([mini]),
- .platform-linux atom-text-editor[data-grammar="source julia"]:not([mini])':
+'.platform-win32 .item-views > atom-text-editor[data-grammar="source julia"]:not([mini]),
+ .platform-linux .item-views > atom-text-editor[data-grammar="source julia"]:not([mini])':
   'ctrl-enter': 'julia-client:evaluate'
   'ctrl-shift-enter': 'julia-client:evaluate-all'
   'ctrl-j ctrl-m': 'julia-client:set-working-module'
   'ctrl-alt-j ctrl-alt-m': 'julia-client:reset-working-module'
   'ctrl-`': 'julia-client:toggle-console'
+
+'.platform-win32 .console,
+ .platform-linux .console':
   'ctrl-shift-c': 'julia-client:interrupt-julia'

 '.platform-win32 atom-workspace,


Replace .editor -> atom-text-editor in keymap

--- keymaps/symbols-view.cson
+++ keymaps/symbols-view.cson
@@ -1,12 +1,12 @@
-'.platform-darwin .editor':
+'.platform-darwin atom-text-editor':
   'cmd-r': 'symbols-view:toggle-file-symbols'
   'cmd-alt-down': 'symbols-view:go-to-declaration'
   'cmd-alt-up': 'symbols-view:return-from-declaration'

-'.platform-win32 .editor':
+'.platform-win32 atom-text-editor':
   'ctrl-r': 'symbols-view:toggle-file-symbols'

-'.platform-linux .editor':
+'.platform-linux atom-text-editor':
   'ctrl-r': 'symbols-view:toggle-file-symbols'
   'ctrl-alt-down': 'symbols-view:go-to-declaration'
   'ctrl-alt-up': 'symbols-view:return-from-declaration'


Made barcode fill whole space

Made barcode fill whole space

--- src/jquery-ean13.coffee
+++ src/jquery-ean13.coffee
@@ -20,10 +20,10 @@
     onInvalid: ->
     onSuccess: ->
     onError: ->
-
+
   class Plugin
     constructor: (@element, @number, options) ->
-
+
       # create settings object
       @settings = $.extend {}, defaults, options

@@ -66,7 +66,7 @@
       x = ["0001101", "0011001", "0010011", "0111101", "0100011", "0110001", "0101111", "0111011", "0110111", "0001011"]
       y = ["0100111", "0110011", "0011011", "0100001", "0011101", "0111001", "0000101", "0010001", "0001001", "0010111"]
       z = ["1110010", "1100110", "1101100", "1000010", "1011100", "1001110", "1010000", "1000100", "1001000", "1110100"]
-
+
       # countries table
       countries = ["xxxxxx", "xxyxyy", "xxyyxy", "xxyyyx", "xyxxyy", "xyyxxy", "xyyyxx", "xyxyxy", "xyxyyx", "xyyxyx"]

@@ -99,7 +99,7 @@

       #return result
       return code
-
+
     clear: (context) ->
       # clear canvas
       context.clearRect(0, 0, @element.width, @element.height)
@@ -117,7 +117,7 @@
         text_offset: 4.5

       # get width of barcode element
-      width = (if @settings.prefix then @element.width * 0.8 else @element.width)
+      width = (if @settings.prefix then @element.width-(@element.width * layout.prefix_offset) else @element.width)

       # check if number should be printed
       if @settings.number


don't cascade new editors if first one is maximized

--- client/ide.coffee
+++ client/ide.coffee
@@ -235,7 +235,7 @@
     editorOpts = id: w, name: ee.name, tracer: ee.debugger, emit: @emit.bind @
     if prefs.floating() && !D.floating && !@dead
       pos = if ee.debugger then prefs.posTracer() else prefs.posEditor()
-      delta = 32 * (ee.token - 1); pos[0] += delta; pos[1] += delta
+      if !pos[4] then delta = 32 * (ee.token - 1); pos[0] += delta; pos[1] += delta
       posH = x: pos[0], y: pos[1], width: pos[2], height: pos[3]
       url = "index.html?win=#{w}&x=#{pos[0]}&y=#{pos[1]}&width=#{pos[2]}&" +
             "height=#{pos[3]}&maximized=#{pos[4] || 0}&token=#{w}&tracer=#{+!!ee.debugger}"


the angular position is printed out to screen

--- src/tasks/watch-analytics-ftdi.coffee
+++ src/tasks/watch-analytics-ftdi.coffee
@@ -2,6 +2,25 @@
 module.exports = ->
   SerialPort = require('serialport').SerialPort
   through = require 'through2'
+
+  analytics =
+    time: 0
+    errors: 0
+    angularPosition:
+      x: 0
+      y: 0
+      z: 0
+
+
+  printResults = ->
+    console.log "#{analytics.time}:
+                 angular position (#{analytics.angularPosition.x},
+                                   #{analytics.angularPosition.y},
+                                   #{analytics.angularPosition.z}),
+                 errors: #{analytics.errors}"
+
+  setInterval printResults, 500
+

   concatenate = (bufferA, bufferB) ->
     newBuffer = new Buffer(bufferA.length + bufferB.length)
@@ -26,7 +45,11 @@
           timeInSeconds = data.readUInt32BE(12)
           value = data.readInt32BE(16) / 1000000

-          console.log "#{name}, #{timeInSeconds}, #{value}"
+          analytics.time = timeInSeconds
+          analytics.angularPosition.x = value if name == 'angu.posx'
+          analytics.angularPosition.y = value if name == 'angu.posy'
+          analytics.angularPosition.z = value if name == 'angu.posz'
+
         else
           data.errors = data.errors + 1
       else


Config: fix wrong interpolation for main.dev

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -421,7 +421,7 @@
       ports             :
         incoming        : "#{KONFIG.kloud.port}"
       supervisord       :
-        command         : "#{GOBIN}/kloud -networkusageendpoint http://localhost:#{KONFIG.vmwatcher.port} -planendpoint #{socialapi.proxyUrl}/payments/subscriptions -hostedzone #{userSitesDomain} -region #{region} -environment #{environment} -port #{KONFIG.kloud.port}  -userprivatekey #{KONFIG.kloud.userPrivateKeyFile} -userpublickey #{KONFIG.kloud.userPublicKeyfile}  -publickey #{kontrol.publicKeyFile} -privatekey #{kontrol.privateKeyFile} -kontrolurl #{kontrol.url}  -registerurl #{KONFIG.kloud.registerUrl} -mongourl #{KONFIG.mongo} -prodmode=#{configName is "prod"} -awsaccesskeyid=#{awsKeys.vm_kloud.accessKeyId} -awssecretaccesskey=#{awsKeys.vm_kloud.secretAccessKey} -slusername=#{slKeys.vm_kloud.username} -slapikey=#{slKeys.vm_kloud.apiKey} -janitorsecretkey=#{socialapi.janitor.secretKey} -vmwatchersecretkey=#{KONFIG.vmwatcher.secretKey} -paymentwebhooksecretkey=#{paymentwebhook.secretKey} -kloudsecretkey=${KONFIG.kloud.secretKey} -tunnelurl #{KONFIG.kloud.tunnelUrl}"
+        command         : "#{GOBIN}/kloud -networkusageendpoint http://localhost:#{KONFIG.vmwatcher.port} -planendpoint #{socialapi.proxyUrl}/payments/subscriptions -hostedzone #{userSitesDomain} -region #{region} -environment #{environment} -port #{KONFIG.kloud.port}  -userprivatekey #{KONFIG.kloud.userPrivateKeyFile} -userpublickey #{KONFIG.kloud.userPublicKeyfile}  -publickey #{kontrol.publicKeyFile} -privatekey #{kontrol.privateKeyFile} -kontrolurl #{kontrol.url}  -registerurl #{KONFIG.kloud.registerUrl} -mongourl #{KONFIG.mongo} -prodmode=#{configName is "prod"} -awsaccesskeyid=#{awsKeys.vm_kloud.accessKeyId} -awssecretaccesskey=#{awsKeys.vm_kloud.secretAccessKey} -slusername=#{slKeys.vm_kloud.username} -slapikey=#{slKeys.vm_kloud.apiKey} -janitorsecretkey=#{socialapi.janitor.secretKey} -vmwatchersecretkey=#{KONFIG.vmwatcher.secretKey} -paymentwebhooksecretkey=#{paymentwebhook.secretKey} -kloudsecretkey=#{KONFIG.kloud.secretKey} -tunnelurl #{KONFIG.kloud.tunnelUrl}"
       nginx             :
         websocket       : yes
         locations       : [


config: switch to using version as suffix for RabbitMQ queue names

--- config/main.dyn.coffee
+++ config/main.dyn.coffee
@@ -16,7 +16,7 @@
 ).trim())+"-dev-#{version}"
 rabbitPrefix = rabbitPrefix.split('.').join('-')

-socialQueueName = "koding-social-prod-#{rabbitPrefix}"
+socialQueueName = "koding-social-prod-#{version}"

 webPort          = 3040
 brokerPort       = 8010 + (version % 10)


Consider tagName when parsing for attributes

--- src/trix/utilities/html_parser.coffee
+++ src/trix/utilities/html_parser.coffee
@@ -119,10 +119,13 @@
     attributes = {}
     style = window.getComputedStyle(element)

-    for attribute, config of Trix.attributes when config.parser
-      if value = config.parser({element, style})
-        attributes[attribute] = value
-
+    for attribute, config of Trix.attributes
+      if config.parser
+        if value = config.parser({element, style})
+          attributes[attribute] = value
+      else if config.tagName
+        if element.tagName?.toLowerCase() is config.tagName
+          attributes[attribute] = true
     attributes

   getMetadata = (element) ->


removed workspaceView elements

removed workspaceView elements

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -712,8 +712,9 @@

   initialize: ->
     @elements.body = qr 'body'
-    @elements.workspace = qr 'atom-workspace'
-    @elements.editor = atom.workspaceView.panes.find('atom-text-editor')[0]
+    @elements.workspace = atom.views.getView(atom.workspace)
+    activeEditor = workspace.getActiveTextEditor()
+    @elements.editor = atom.views.getView(activeEditor)
     @elements.treeView = qr '.tree-view'
     @elements.left = qr '.left'
     @elements.leftPanel = qr '.panel-left'
@@ -887,7 +888,7 @@
         inline @elements.bg,"background-image: url('#{imageData}') !important"

   applyBackground: ->
-    atom.workspaceView.addClass 'editor-background'
+    atom.views.getView(atom.workspace).addClass 'editor-background'
     if @packagesLoaded
       conf = atom.config.get 'editor-background'
       opacity = 100 - conf.opacity


Frames; just add new frames to the end.

Frames; just add new frames to the end.

We don't need to worry where frames are added, nextFrame will find the
right frame to start from anyway.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -600,9 +600,9 @@
     chrome.tabs.create({ url: chrome.runtime.getURL("pages/options.html"), index: tab.index + 1 }))

 registerFrame = (request, sender) ->
-  frames = frameIdsForTab[sender.tab.id] ?= []
-  unless request.is_frameset # Don't store frameset containers; focusing them is no use.
-    if request.is_top then frames.unshift request.frameId else frames.push request.frameId
+  frameIdsForTab[sender.tab.id] ?= []
+  # Don't store frameset containers; focusing them is no use.
+  frameIdsForTab[sender.tab.id].push request.frameId unless request.is_frameset

 unregisterFrame = (request, sender) ->
   tabId = sender.tab.id


fixing pan tools for cdx map plots

fixing pan tools for cdx map plots

--- static/coffee/common/gmap_plot.coffee
+++ static/coffee/common/gmap_plot.coffee
@@ -104,14 +104,10 @@
       domain_mapper: @xmapper
       codomain_mapper: @ymapper
     })
-    coll = Collections('PanTool')
-    m = coll.model
-    pantool = new m(
-      dataranges: [@x_range.ref(), @y_range.ref()]
-      dimensions: ['width', 'height']
-    )
-    coll.add(pantool)
-    @mget('tools').push(pantool.ref())
+    for tool in @mget_obj('tools')
+      if tool.type == "PanTool"
+        tool.set_obj('dataranges', [@x_range, @y_range])
+        tool.set('dimensions', ['width', 'height'])
     @requested_padding = {
       top: 0
       bottom: 0


ace: add snippets setting getter

ace: add snippets setting getter

--- client/ace/lib/ace.coffee
+++ client/ace/lib/ace.coffee
@@ -359,6 +359,10 @@
     @appStorage.getValue('enableAutocomplete') ? yes

+  getEnableSnippets: ->
+    @appStorage.getValue('enableSnippets') ? yes
+
+
   getSettings: ->

     theme               : @getTheme()
@@ -375,6 +379,7 @@
     scrollPastEnd       : @getScrollPastEnd()
     openRecentFiles     : @getOpenRecentFiles()
     enableAutocomplete  : @getEnableAutocomplete()
+    enableSnippets      : @getEnableSnippets()

   ###


Highlight things correctly if we insert a flag at the end.

This fixes the highlighting of deployment sections.

--- js/inner/config_diagnostic.coffee
+++ js/inner/config_diagnostic.coffee
@@ -147,4 +147,6 @@
       else
         splitting.push kid
     @kids = splitting
-    return ix || @kids.length - 1
+    # Return the index of the place to insert things, or the place at the
+    # end of the list if it's not contained in the list.
+    return ix || @kids.length


Client: Activity:Added followChannel and unfollowChannel handlers to channelstore

--- client/activity/lib/flux/stores/channelsstore.coffee
+++ client/activity/lib/flux/stores/channelsstore.coffee
@@ -17,6 +17,14 @@
     @on actions.LOAD_CHANNELS_SUCCESS, @handleLoadChannelListSuccess
     @on actions.LOAD_POPULAR_CHANNELS_SUCCESS, @handleLoadChannelListSuccess

+    @on actions.FOLLOW_CHANNEL_BEGIN, @handleFollowChannelBegin
+    @on actions.FOLLOW_CHANNEL_SUCCESS, @handleFollowChannelSuccess
+    @on actions.FOLLOW_CHANNEL_FAIL, @handleFollowChannelFail
+
+    @on actions.UNFOLLOW_CHANNEL_BEGIN, @handleUnfollowChannelBegin
+    @on actions.UNFOLLOW_CHANNEL_SUCCESS, @handleUnfollowChannelSuccess
+    @on actions.UNFOLLOW_CHANNEL_FAIL, @handleUnfollowChannelFail
+

   handleLoadChannelSuccess: (channels, { channel }) ->

@@ -28,6 +36,34 @@
     return currentChannels.withMutations (map) ->
       map.set channel.id, toImmutable channel for channel in  channels
       return map
+
+
+  handleFollowChannelBegin: (channels, { channelId }) -> channels
+
+
+  handleFollowChannelFail: (channels, { channelId }) -> channels
+
+
+  handleFollowChannelSuccess: (channels, { channelId }) ->
+
+    if channels.has channelId
+      channels = channels.setIn [channelId, 'isParticipant'], yes
+
+    return channels
+
+
+  handleUnfollowChannelBegin: (channels, { channelId }) -> channels
+
+
+  handleUnfollowChannelFail: (channels, { channelId }) -> channels
+
+
+  handleUnfollowChannelSuccess: (channels, { channelId }) ->
+
+    if channels.has channelId
+      channels = channels.setIn [channelId, 'isParticipant'], no
+
+    return channels

 initChannel = (channels, id) ->


Team Settings: Members: Use AvatarStaticView instead of AvatarView

--- client/admin/lib/views/members/memberitemview.coffee
+++ client/admin/lib/views/members/memberitemview.coffee
@@ -1,7 +1,7 @@
 kd                     = require 'kd'
 JView                  = require 'app/jview'
 isKoding               = require 'app/util/isKoding'
-AvatarView             = require 'app/commonviews/avatarviews/avatarview'
+AvatarStaticView       = require 'app/commonviews/avatarviews/avatarstaticview'
 getFullnameFromAccount = require 'app/util/getFullnameFromAccount'

@@ -44,7 +44,7 @@
     @loggedInUserRole = @getRole data.loggedInUserRoles
     @memberRole       = @getRole data.roles

-    @avatar = new AvatarView
+    @avatar = new AvatarStaticView
       size : { width: 40, height : 40 }
     , @getData()


Added more default colours for groups (based on http://colorbrewer2.org/index.php?type=qualitative&scheme=Set2&n=8 )

--- coffee/vis.coffee
+++ coffee/vis.coffee
@@ -1,4 +1,3 @@
-
 class BubbleChart
   constructor: (data) ->
     @data = data
@@ -30,8 +29,8 @@

     # nice looking colors - no reason to buck the trend
     @fill_color = d3.scale.ordinal()
-      .domain(["0", "1", "2", "3", "4", "5"])
-      .range(["#66C2A5", "#FC8D62", "#8DA0CB", "#E78AC3", "#A6D854" ])
+      .domain(["0", "1", "2", "3", "4", "5" ,"6", "7"])
+      .range(["#66C2A5", "#FC8D62", "#8DA0CB", "#E78AC3", "#A6D854", "#FFD92F", "#E5C494", "#B3B3B3" ])

     # use the max total_amount in the data as the max in the scale's domain
     max_amount_bc = d3.max(@data, (d) -> parseInt(d.betweenness_centrality))


Return a copy from MarkerIndex::findContaining

--- src/marker-index.coffee
+++ src/marker-index.coffee
@@ -117,7 +117,7 @@
       @rootNode = new Node(splitNodes)

   findContaining: (start, end=start) ->
-    @rootNode.findContaining(start, end)
+    new Set(@rootNode.findContaining(start, end))

 setIntersection = (a, b) ->
   intersection = new Set


* [bug] filter the invalid roles

* [bug] filter the invalid roles

--- src/mixins/roles.coffee
+++ src/mixins/roles.coffee
@@ -130,7 +130,7 @@
   Model.getPerms = (aRoles)->
     if isArray aRoles
       Promise.map aRoles, (aId)->
-        if isPerm then aId else findRoleById aId
+        if isPerm(aId) then aId else findRoleById aId
       .reduce (aResult, aRole)->
         _merge aResult, aRole
       , []
@@ -153,6 +153,13 @@
         throw err
     else
        vGetPerms
+
+  getValidRoles = (aRoles)->
+    if isArray aRoles
+      result = aRoles.filter (item)-> isString(item) and item.length
+    else
+      result = []
+    result

   getDiffRoles = (aInstance, ctx)->
     if ctx.instance # full save of a single model
@@ -175,7 +182,7 @@
           vDelRoles.push i if vNewRoles.indexOf(i) is -1
       else
         vAddedRoles = aInstance[rolesFieldName]
-    [vAddedRoles, vDelRoles]
+    [getValidRoles(vAddedRoles), getValidRoles(vDelRoles)]

   indexOfRef = (aRefs, aModel, aId)->
     for aRef,result in aRefs
@@ -227,6 +234,7 @@
       delete vInstance[permsFieldName]

     return Promise.resolve() unless vInstance[rolesFieldName]?
+    vInstance[rolesFieldName] = getValidRoles vInstance[rolesFieldName]
     calcPerms vInstance, ctx
     .then (aPerms)->
       vInstance[permsFieldName] = aPerms


Implement async operations queue in the watcher!

--- library/nucleus/watch.coffee
+++ library/nucleus/watch.coffee
@@ -29,6 +29,7 @@
 logger = require "winston"
 colors = require "colors"
 assert = require "assert"
+async = require "async"
 nconf = require "nconf"
 paths = require "path"
 https = require "https"
@@ -145,16 +146,37 @@
     reviewServices: (resolved) ->
         cached = require.cache[resolved]
         services = @collectServices cached
+        assert queue = @obtainOperationsQueue()
         registry = (router = @kernel.router)?.registry or []
         originate = (s) -> s.constructor.origin?.id
         predicate = (s) -> originate(s) is resolved
         previous = _.filter registry, predicate
-        router.unregister prev for prev in previous
-        srv.origin = cached for srv in services
-        register = @kernel.router.register
-        register = register.bind @kernel.router
-        s.spawn @kernel, register for s in services
-        @attemptForceHotswap cached
+        _.each services, (s) -> s.origin = cached
+        queue.push _.map(previous, (s) -> instance: s)
+        queue.push _.map(services, (s) -> service: s)
+        @attemptForceHotswap cached; return this
+
+    # Obtain an operations queue of this watcher instance. This queue
+    # is responsible for processing either a service registration or
+    # a service unregistration. The queueing mechanism is necessary in
+    # order to make sure of correct sequencing of all the operations.
+    # It aids is avoiding the race conditions during modules changes.
+    obtainOperationsQueue: ->
+        assert router = @kernel.router
+        return @queue if _.isObject @queue
+        register = router.register.bind router
+        unregister = router.unregister.bind router
+        collides = "use either register or unregister"
+        missingOperation = "specify an operation to do"
+        @queue = async.queue (operation, callback) =>
+            acknowledge = -> callback.apply this
+            applicate = (i) -> register i, acknowledge
+            opService = operation.service or undefined
+            opInstance = operation.instance or undefined
+            assert opService or opInstance, missingOperation
+            assert not (opService and opInstance), collides
+            unregister opInstance, acknowledge if opInstance
+            opService.spawn @kernel, applicate if opService

     # If enabled by the scoping configuration, this method will try
     # to hotswap and reload all modules and services that have been
@@ -185,29 +207,13 @@
         exports = _.values(required.exports or {})
         hasProto = (s) -> _.isObject(s) and s.prototype
         isService = (s) -> try s.inherits service.Service
+        isTyped = (s) -> hasProto(s) and isService(s)
         isFinal = (s) -> not s.abstract()
-        isTyped = (s) -> hasProto(s) and isService(s)
         unscoped = _.filter globals, isTyped
         services = _.filter exports, isTyped
         services = _.merge services, unscoped
         services = _.filter services, isFinal
-        _.unique services
-
-    # Directory tracking routine keeps inventory of all directories
-    # that have been added to the watcher. Besides that it is called
-    # upon an addition of a new directory to ensure that the directory
-    # does not intersect with any directories that already present.
-    directoryTracking: (directory) ->
-        tracked = @tracked ?= []
-        pattern = /^(?:\.{2}\/?)+$/
-        resolved = paths.resolve directory
-        return undefined if resolved in tracked
-        relA = (p) -> paths.relative(p, resolved)
-        relB = (p) -> paths.relative(resolved, p)
-        matches = (f) -> (p) -> pattern.test f(p)
-        return no if _.any tracked, matches(relA)
-        return no if _.any tracked, matches(relB)
-        tracked.push resolved.toString()
+        return _.unique services

     # Watch the specified directory for addition and changing of
     # the files, looking for modules with services there and then
@@ -228,3 +234,19 @@
         watcher.on "unlink", @hotSwappingUnlink.bind @
         watcher.on "change", @hotSwappingChange.bind @
         watcher.on "add", @hotSwappingAdd.bind @
+
+    # Directory tracking routine keeps inventory of all directories
+    # that have been added to the watcher. Besides that it is called
+    # upon an addition of a new directory to ensure that the directory
+    # does not intersect with any directories that already present.
+    directoryTracking: (directory) ->
+        tracked = @tracked ?= []
+        pattern = /^(?:\.{2}\/?)+$/
+        resolved = paths.resolve directory
+        return undefined if resolved in tracked
+        relA = (p) -> paths.relative(p, resolved)
+        relB = (p) -> paths.relative(resolved, p)
+        matches = (f) -> (p) -> pattern.test f(p)
+        return no if _.any tracked, matches(relA)
+        return no if _.any tracked, matches(relB)
+        tracked.push resolved.toString()


Added test for issue 94

Added test for issue 94

--- test/knockback/view-model/test.coffee
+++ test/knockback/view-model/test.coffee
@@ -870,3 +870,43 @@

     assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
     done()
+
+  it '23. Issue 94', (done) ->
+    kb.statistics = new kb.Statistics() # turn on stats
+
+    Child = Backbone.Model.extend()
+
+    Parent = Backbone.Model.extend({
+        defaults: {
+            child: new Child({name: "SingleChild"}),
+            children: new Backbone.Collection([new Child({name: "Child1"}), new Child({name: "Child2"})], {model: Child})
+        }
+    })
+
+    ChildViewModel = (model) ->
+      assert.ok(!!model, 'model is null?')
+      view_model = kb.viewModel(model)
+
+      view_model.nameComputed = ko.computed =>
+        ret = "no name function!"
+        ret = "Hello, " + view_model.name() if view_model.name
+        return ret
+
+      return view_model
+
+    ParentViewModel = (model) ->
+      view_model = kb.viewModel(model, {
+        factories: {
+          'children.models': ChildViewModel,
+          'child': ChildViewModel
+        }
+      })
+      view_model.nameComputed = ko.computed => return "Hello, " + view_model.name()
+      return view_model
+
+    parent_view_model = new ParentViewModel(new Parent({name: "TestParent"}))
+
+    kb.release(parent_view_model)
+
+    assert.equal(kb.statistics.registeredStatsString('all released'), 'all released', "Cleanup: stats"); kb.statistics = null
+    done()


Update en.coffee

Update en.coffee

--- t9n/en.coffee
+++ t9n/en.coffee
@@ -127,6 +127,7 @@

       #---- Misc...
       "Unknown error": "Unknown error"
+      "Error, too many requests. Please slow down. You must wait 1 seconds before trying again."

 T9n.map "en", en


expose bokeh server utils

expose bokeh server utils

--- bokehjs/src/coffee/main.coffee
+++ bokehjs/src/coffee/main.coffee
@@ -17,6 +17,10 @@
 define (require, exports, module) ->

   Bokeh = {}
+
+  Bokeh.Config = {
+    prefix : ''
+  }

   # common
   Bokeh.Collections       = require("common/base").Collections
@@ -99,6 +103,11 @@

   Bokeh.Properties = require("renderer/properties")

+  # server tools
+  Bokeh.embed_core  = require("server/embed_core")
+  Bokeh.serverrun   = require("server/serverrun")
+  Bokeh.serverutils = require("server/serverutils")
+
   # data sources
   Bokeh.ColumnDataSource      = require("source/column_data_source")
   Bokeh.ObjectArrayDataSource = require("source/object_array_data_source")


Revert "use script elem instead of eval"

This reverts commit c6e6adf9eb37da77fa01b0c856ead2e426463530.

--- editor-app/main.coffee
+++ editor-app/main.coffee
@@ -31,10 +31,7 @@

 evalCode = (code) ->
   window.frameLoaded = (frame) ->
-    scriptElem = frame.document.createElement "script"
-    scriptElem.type = "text/javascript"
-    scriptElem.text = code
-    frame.document.body.appendChild scriptElem
+    frame.eval(code)
   $("#game").attr("src", "game.html")

 evalE.onValue(evalCode)


Fixed Toxcore not working under Windows

Fixed Toxcore not working under Windows

--- lib/atox-toxWorker.coffee
+++ lib/atox-toxWorker.coffee
@@ -1,5 +1,6 @@
 toxcore = require 'toxcore'
 fs = require 'fs'
+os = require 'os'

 module.exports =
 class ToxWorker
@@ -7,7 +8,10 @@
     @event = event

   startup: ->
-    @TOX = new toxcore.Tox
+    if os.platform().indexOf('win') > -1
+      @TOX = new toxcore.Tox({av: false, path: __dirname + "\\libtoxcore.dll"})
+    else
+      @TOX = new toxcore.Tox({av: false})

     @err "Failed to load TOX" unless @TOX.checkHandle (e) =>


fix typo

fix typo

--- kites/databases/mongodbApi.coffee
+++ kites/databases/mongodbApi.coffee
@@ -87,7 +87,7 @@
                 else
                   log.debug res
                   log.info "[OK] user #{dbConf.dbUser} and database #{dbConf.dbName} has been created"
-                  log.indo dbConf
+                  log.info dbConf
                   callback? null,dbConf

   changePassword : (options,callback)->


Enable turbolinks transitions

Enable turbolinks transitions

--- app/assets/javascripts/buckets.js.coffee
+++ app/assets/javascripts/buckets.js.coffee
@@ -1,3 +1,5 @@
+Turbolinks.enableTransitionCache();
+
 makeBucketsSortable = ->
   $(".js-bucket-list").sortable({
     connectWith: ".js-bucket-list"


make sure title is correct on pushstate

make sure title is correct on pushstate

--- app/assets/javascripts/entries.js.coffee
+++ app/assets/javascripts/entries.js.coffee
@@ -12,7 +12,7 @@
   return false if $(this).data('disabled')

   href = $(this).attr('href')
-  History.pushState({ tracked: true, href: href }, null, href)
+  History.pushState({ tracked: true, href: href }, 'RubyReader', href)

 statechange = (evt) ->
   # evt.preventDefault()


Refactor coffeescript behavior

Refactor coffeescript behavior

--- app/assets/javascripts/task_lists.coffee
+++ app/assets/javascripts/task_lists.coffee
@@ -1,3 +1,51 @@
+# TaskList Behavior
+#
+#= provides tasklist:enabled
+#= provides tasklist:disabled
+#= provides tasklist:updateSend
+#= provides tasklist:updateComplete
+#
+#= require jquery
+#= require crema/events/pageupdate
+#= require rails/remote
+#
+# Enables Task List update behavior.
+#
+# ### Expects
+#
+# TaskLists MUST be contained in a `div.js-task-list-container`.
+#
+# TaskList Items SHOULD be an a list (`UL`/`OL`) element.
+#
+# Task list items MUST match `input.task-list-item-checkbox[type=checkbox]`,
+# MUST be `disabled`, and MUST define `data-item-index`. The Item's contents
+# SHOULD be wrapped in a `LABEL` element.
+#
+# TaskLists MUST have a `(form).js-task-list-form` containing a
+# `(textarea).js-task-list-field` form element whose `value` attribute is the
+# source (Markdown) to be udpated. The source MUST follow the syntax guidelines.
+#
+# TaskList updates SHOULD be handled asynchronosly with `data-remote`.
+# See: https://github.com/josh/rails-behaviors
+#
+# jQuery is required and the `$.pageUpdate` behavior MUST be available.
+#
+# ### Events
+#
+# When an update succeeds, a `tasklist:updated' event is triggered, passing
+# along whatever data the request responded with. Handle this event if you
+# need additional behavior.
+#
+# Updating a task list item by clicking on the check box will send an
+# `tasklist:updateSend` event. Following the `ajaxComplete` event,
+# `tasklist:updateComplete` is triggered.
+#
+# ### NOTE
+#
+# Task list checkboxes are rendered as disabled by default because rendered
+# user content is cached without regard for the viewer. We enable checkboxes
+# on `pageUpdate` if the container has a `form.js-task-list-form`.
+
 incomplete = "[ ]"
 complete   = "[x]"

@@ -62,40 +110,41 @@
 # because rendered user content is cached without regard
 # for the viewer. Enables the checkboxes and applies the
 # correct list style, if the viewer is able to edit the comment.
-enableTaskList = (comment) ->
-  if comment.find('.js-comment-field').length > 0
-    comment.
+enableTaskList = ($container) ->
+  if $container.find('.js-task-list-form').length > 0
+    $container.
       find('.task-list-item').addClass('enabled').
       find('.task-list-item-checkbox').attr('disabled', null)
-    comment.trigger 'tasklist:enabled'
+    $container.trigger 'tasklist:enabled'

-disableTaskList = (comment) ->
-  comment.
+disableTaskList = ($container) ->
+  $container.
     find('.task-list-item').removeClass('enabled').
     find('.task-list-item-checkbox').attr('disabled', 'disabled')
-  comment.trigger 'tasklist:disabled'
+  $container.trigger 'tasklist:disabled'

 # Submit updates to task list items asynchronously.
 # Successful updates won't require re-rendering to represent reality.
-updateTaskList = (item) ->
-  comment = item.closest '.js-comment'
-  form    = comment.find 'form.js-comment-update'
-  field   = form.find '.js-comment-field'
-  index   = parseInt item.attr('data-item-index')
-  checked = item.prop 'checked'
+updateTaskList = ($item) ->
+  $container = $item.closest '.js-task-list-container'
+  $form      = $container.find '.js-task-list-form'
+  $field     = $form.find '.js-task-list-field'
+  index      = parseInt $item.attr('data-item-index')
+  checked    = $item.prop 'checked'

-  disableTaskList comment
+  disableTaskList $container

-  field.val updateTaskListItem(field.val(), index, checked)
-  form.trigger 'tasklist:updateSend', [index, checked]
-  form.one 'ajaxComplete', ->
-    form.trigger 'tasklist:updateComplete'
-  form.submit()
+  $field.val updateTaskListItem($field.val(), index, checked)
+  $form.trigger 'tasklist:updateSend', [index, checked]
+  $form.one 'ajaxComplete', ->
+    enableTaskList $container
+    $form.trigger 'tasklist:updateComplete'
+  $form.submit()

 # When the task list item checkbox is updated, submit the change
-$(document).on 'change', 'input.task-list-item-checkbox[type=checkbox]', ->
+$(document).on 'change', '.task-list-item-checkbox', ->
   updateTaskList $(this)

 $.pageUpdate ->
-  $('.js-comment').each ->
+  $('.js-task-list-container').each ->
     enableTaskList $(this)


Fix MinMaxEditor

Fix MinMaxEditor

--- app/classifier/tasks/generic-editor.cjsx
+++ app/classifier/tasks/generic-editor.cjsx
@@ -10,7 +10,7 @@
 MarkdownHelp = require '../../partials/markdown-help'

 # `import MinMaxEditor from './drawing/min-max-editor';`
-MinMaxEditor = require './drawing/min-max-editor'
+MinMaxEditor = require('./drawing/min-max-editor').default

 module.exports = React.createClass
   displayName: 'GenericTaskEditor'


Autofocus the characteristics filters on first render

--- app/classifier/tasks/survey/chooser.cjsx
+++ app/classifier/tasks/survey/chooser.cjsx
@@ -80,12 +80,12 @@

-        {for characteristicID in @props.task.characteristicsOrder
+        {for characteristicID, i in @props.task.characteristicsOrder
           characteristic = @props.task.characteristics[characteristicID]
           selectedValue = characteristic.values[@props.filters[characteristicID]]
           hasBeenAutoFocused = false

-          <TriggeredModalForm key={characteristicID} ref="#{characteristicID}-dropdown" className="survey-task-chooser-characteristic-menu" trigger={
+          <TriggeredModalForm key={characteristicID} ref="#{characteristicID}-dropdown" className="survey-task-chooser-characteristic-menu" triggerProps={autoFocus: i is 0 and not @props.focusedChoice} trigger={

               {selectedValue?.label ? characteristic.label}


Add url formatting to the markdown wysiwyg editor

--- app/coffee/modules/common/wisiwyg.coffee
+++ app/coffee/modules/common/wisiwyg.coffee
@@ -214,14 +214,18 @@
                 {
                     name: $tr.t("markdown-editor.picture")
                     key: "P"
-                    replaceWith: '![[![Alternative text]!]]([![Url:!:http://]!] "[![Title]!]")'
+                    replaceWith: '![[![Alternative text]!]](<<<[![Url:!:http://]!]>>> "[![Title]!]")'
+                    beforeInsert:(markItUp) -> prepareUrlFormatting(markItUp)
+                    afterInsert:(markItUp) -> urlFormatting(markItUp)
                 },
                 {
                     name: $tr.t("markdown-editor.link")
                     key: "L"
                     openWith: "["
-                    closeWith: ']([![Url:!:http://]!] "[![Title]!]")'
+                    closeWith: '](<<<[![Url:!:http://]!]>>> "[![Title]!]")'
                     placeHolder: $tr.t("markdown-editor.link-placeholder")
+                    beforeInsert:(markItUp) -> prepareUrlFormatting(markItUp)
+                    afterInsert:(markItUp) -> urlFormatting(markItUp)
                 },
                 {
                     separator: "---------------"
@@ -256,6 +260,45 @@
                 target = angular.element(event.textarea)
                 $model.$setViewValue(target.val())

+        prepareUrlFormatting = (markItUp) ->
+            console.log(markItUp)
+            regex = /(<<<|>>>)/gi
+            result = 0
+            indices = []
+            (indices.push(result.index)) while ( (result = regex.exec(markItUp.textarea.value)) )
+            markItUp.donotparse = indices
+            console.log(indices)
+
+        urlFormatting = (markItUp) ->
+            console.log(markItUp.donotparse)
+            regex = /<<</gi
+            result = 0
+            startIndex = 0
+
+            loop
+                result = regex.exec(markItUp.textarea.value)
+                break if !result
+                if result.index not in markItUp.donotparse
+                    startIndex = result.index
+                    break
+
+            regex = />>>/gi
+            endIndex = 0
+            loop
+                result = regex.exec(markItUp.textarea.value)
+                break if !result
+                if result.index not in markItUp.donotparse
+                    endIndex = result.index
+                    break
+
+            value = markItUp.textarea.value
+            url = value.substring(startIndex, endIndex).replace('<<<', '').replace('>>>', '')
+            url = url.replace('(', '%28').replace(')', '%29')
+            url = url.replace('[', '%5B').replace(']', '%5D')
+            value = value.substring(0, startIndex) + url + value.substring(endIndex+3, value.length)
+            markItUp.textarea.value = value
+            markItUp.donotparse = undefined
+
         markdownTitle = (markItUp, char) ->
             heading = ""
             n = $.trim(markItUp.selection or markItUp.placeHolder).length


Case insensitive search of keys in headers

--- lib/devices/Device.coffee
+++ lib/devices/Device.coffee
@@ -124,9 +124,17 @@
   # Parse SSDP headers using the HTTP module parser.
   parseRequest: (msg, rinfo, cb) ->
     req = parser.parseRequest(msg)
-    { method, headers: { mx, st, nt, nts, usn } } = req
+    { method, headers } = req
+    mx = null
+    st = null
+    for header of headers
+      switch header.toLowerCase()
+        when 'mx'
+          mx = headers[header]
+        when 'st'
+          st = headers[header]
     { address, port } = rinfo
-    cb null, { method, mx, st, nt, nts, usn, address, port }
+    cb null, { method, mx, st, address, port }

   # Attempt UUID persistance of devices across restarts.


Force the z-index to 2.

Force the z-index to 2.

TextEditorView.appendToLinesView locks the z-index to 1, which lets the editor
text bleed through the CoverViews. Reset it to 2 after the fact.

Kind of a dirty hack, but it'll do until I rework for the shadow DOM :wrench:

--- lib/editor-adapter.coffee
+++ lib/editor-adapter.coffee
@@ -16,7 +16,9 @@

 class ReactAdapter extends EditorAdapter

-  append: (child) -> @view.appendToLinesView(child)
+  append: (child) ->
+    @view.appendToLinesView child
+    child.css('z-index', 2)

   linesElement: -> @view.find('.lines')


Forward ip address on device creation

Forward ip address on device creation

--- app/controllers/device-controller.coffee
+++ app/controllers/device-controller.coffee
@@ -7,6 +7,8 @@

   create: (request, response) =>
     {device, pin} = request.body
+    device ?= {}
+    device.ipAddress ?= request.headers['x-forwarded-for'] ? request.connection.remoteAddress
     @pinController.createDevice pin, device, (error, uuid) =>
       return response.status(500).send error: error.message if error?
       response.status(201).send uuid: uuid


Add test

Add test

--- test/test.coffee
+++ test/test.coffee
@@ -135,3 +135,8 @@
 				item not in matchedBefore.concat matchedAfter
 			assert.deepEqual unmatched,
 				sorted[matchedBefore.length...sorted.length-matchedAfter.length]
+
+		it 'should respect separate order definition', ->
+			sortedWithUnmatched = anysort.grouped sortable, [before, 'unmatched', after]
+			sortedWithOrder     = anysort.grouped sortable, [before, after], [0, 2, 1]
+			assert.deepEqual sortedWithUnmatched, sortedWithOrder


adjusted test case for minimum length

adjusted test case for minimum length

--- test/test.coffee
+++ test/test.coffee
@@ -22,12 +22,20 @@
 					assert.ok topic.alphabet.length == 222

 		"when setting the length to":
-			"1":
+			"0":
 				topic: () ->
 					strgen

 				"an error is thrown": (topic) ->
-					assert.throws () -> topic.setLength 1
+					assert.throws () -> topic.setLength 0
+
+			"1":
+				topic: () ->
+					strgen.setLength 1
+					strgen.generate()
+
+				"we get a string of length 1": (topic) ->
+					assert.equal topic.length,1

 			"76":
 				topic: () ->


summarize test results at end

summarize test results at end

on smaller screens, full test output scrolls, meaning you have to scroll back
a bit to see if you actually passed. A summary at the end is nice.

--- test/test.coffee
+++ test/test.coffee
@@ -28,6 +28,8 @@
       console.log quote(control)
       false

+num_errors = 0
+num_failures = 0
 _.each tests, (test, name) ->
   try
     result = test()
@@ -35,7 +37,14 @@
       console.log "[ OK ] #{name}"
     else
       console.log "[FAIL] #{name}"
+      num_failures++

   catch e
     console.log "[ERR ] #{name}"
     console.log e.message
+    num_errors++
+
+if num_errors + num_failures == 0
+  console.log "All tests OK"
+else
+  console.log "Tests failed with #{num_failures} failures and #{num_errors} errors."


DevTools: Editor: Support to loadFile by given path

--- client/DevTools/AppView.coffee
+++ client/DevTools/AppView.coffee
@@ -226,21 +226,26 @@
     @_lastFileKey = "lastFileOn#{@_mode}"
     @storage = KD.singletons.localStorageController.storage "DevTools"

+  loadFile:(path, callback = noop)->
+
+    file = FSHelper.createFileFromPath path
+    file.fetchContents (err, content)=>
+      return callback err  if err
+
+      file.path = path
+      @openFile file, content
+
+      KD.utils.defer -> callback null, {file, content}
+
   loadLastOpenFile:->

     path = @storage.getAt @_lastFileKey
     return  unless path

-    lastOpenFile = FSHelper.createFileFromPath path
-    lastOpenFile.fetchContents (err, content)=>
+    @loadFile path, (err, data)=>
       if err
-        KD.showError err, "Failed to load last open file: #{path}"
+        KD.showError "Failed to load last open file: #{path}"
         @storage.unsetKey @_lastFileKey
-      else
-        # Override the path to keep VM Info
-        lastOpenFile.path = path
-
-        @openFile lastOpenFile, content

   createEditor: (callback)->


refactor unless elses

refactor unless elses

--- vendor/assets/javascripts/brainstem/brainstem-collection.coffee
+++ vendor/assets/javascripts/brainstem/brainstem-collection.coffee
@@ -133,7 +133,7 @@
   getPageIndex: ->
     return 1 unless @lastFetchOptions

-    unless _.isUndefined(@lastFetchOptions.offset)
+    if @lastFetchOptions.offset?
       Math.ceil(@lastFetchOptions.offset / @lastFetchOptions.limit) + 1
     else
       @lastFetchOptions.page
@@ -157,7 +157,7 @@

     index = 1 if index < 1

-    unless _.isUndefined(@lastFetchOptions.offset)
+    if @lastFetchOptions.offset?
       max = @_maxOffset()
       offset = @lastFetchOptions.limit * index - @lastFetchOptions.limit
       options.offset = if offset < max then offset else max
@@ -170,7 +170,7 @@
   hasNextPage: ->
     return false unless @_canPaginate()

-    unless _.isUndefined(@lastFetchOptions.offset)
+    if @lastFetchOptions.offset?
       if @_maxOffset() > @lastFetchOptions.offset then true else false
     else
       if @_maxPage() > @lastFetchOptions.page then true else false
@@ -178,7 +178,7 @@
   hasPreviousPage: ->
     return false unless @_canPaginate()

-    unless _.isUndefined(@lastFetchOptions.offset)
+    if @lastFetchOptions.offset?
       if @lastFetchOptions.offset > @lastFetchOptions.limit then true else false
     else
       if @lastFetchOptions.page > 1 then true else false


Fix typo in arguments collection

Fix typo in arguments collection

--- lib/argumenta/collections/arguments.coffee
+++ lib/argumenta/collections/arguments.coffee
@@ -69,7 +69,7 @@
   # @param [Array<Argument>] arguments
   get: (hashes, callback) ->
     unless _.isArray hashes
-      return new @Error "Hashes must be an array."
+      return callback new @Error "Hashes must be an array."

     if hashes.length is 0
       return callback null, []


Update copy to suggest hashtag usage

Update copy to suggest hashtag usage

- Closes #1419

--- app/talk/quick-subject-comment-form.cjsx
+++ app/talk/quick-subject-comment-form.cjsx
@@ -94,7 +94,7 @@
         validationCheck={@commentErrors}
         validationErrors={@state.commentValidationErrors}
         submitFeedback={"Comment successfully added"}
-        placeholder={"Add a note about this subject."}
+        placeholder={"Add a note about this subject, or mark with a #hashtag"}
         onSubmitComment={@onSubmitComment}
         subject={@props.subject}
         submit="Add Your comment"/>


event.key is not always defined

event.key is not always defined

Fixes #2453.

--- lib/keyboard_utils.coffee
+++ lib/keyboard_utils.coffee
@@ -43,6 +43,9 @@
   getKeyCharUsingKey: (event) ->
     if event.keyCode of @keyNames
       @keyNames[event.keyCode]
+    # It appears that event.key is not always defined (see #2453).
+    else if not event.key?
+      ""
     else if event.key.length == 1
       event.key
     else if event.key.length == 2 and "F1" <= event.key <= "F9"


GroupStackSettings: fix test issues

GroupStackSettings: fix test issues

--- client/admin/lib/views/stacks/groupstacksettings.coffee
+++ client/admin/lib/views/stacks/groupstacksettings.coffee
@@ -23,8 +23,6 @@
     @createInitialView()

     kd.singletons.appStorageController.storage 'Ace', '1.0.1'
-
-    @_canEditGroup = kd.singletons.groupsController.canEditGroup()

     @on 'SubTabRequested', (action, identifier) ->
       return  unless action
@@ -71,7 +69,7 @@

     @initialView.on 'CreateNewStack', @lazyBound 'setRoute', '/new'

-    if @_canEditGroup
+    if kd.singletons.groupsController.canEditGroup()
       @initialView.on 'NoTemplatesFound', @lazyBound 'setRoute', '/welcome'


remove an annoying log

remove an annoying log

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -152,7 +152,6 @@
   putGlobalEventListeners:()->

     @on "NavigationLinkTitleClick", (pageInfo) =>
-      console.log pageInfo
       if pageInfo.path
         @router.handleRoute pageInfo.path
       else if pageInfo.isWebTerm


minor: removed whitespace

minor: removed whitespace

--- client/landing/site.landing/coffee/login/AppView.coffee
+++ client/landing/site.landing/coffee/login/AppView.coffee
@@ -68,7 +68,6 @@
       title       : 'Sign In'
       href        : '/Login'
       click       : ->
-

     @goToRecoverLink = new CustomLinkView
       cssClass    : 'forgot-link'


Added comment for two internal functions.

--- src/chalkboard.coffee
+++ src/chalkboard.coffee
@@ -94,10 +94,27 @@
   join:   null
   header: false

+#
+# @function
+# @name _captialize
+# @description
+# Capitalize the first letter of a string
+# @param {String} str String to be capitalized
+# @returns {String} Capitalized string
+#
 _capitalize = (str = "") ->
   return str unless str
   str[0].toUpperCase() + str[1..]

+#
+# @function
+# @nam _repeatChar
+# @description
+# Repeat a character multiple times
+# @param {Sting} char Character to repeat
+# @param {Integer} count Number of times to repeat the character
+# @returns {String} Processed string
+#
 _repeatChar = (char, count = 0) ->
   Array(count+1).join char


Ignore path suggestions if user does not have access permissions.

When compiling lists of completions, an uncaught exception may be thrown
if the current user does not have permissions to access a particular
directory.  Such paths frequently occur when typing "/" on a Windows
system.

Signed-off-by: Timothy McCoy <fdae9775016c60581dfc855ffe302fba0ffa7602@gmail.com>

--- lib/paths-provider.coffee
+++ lib/paths-provider.coffee
@@ -32,8 +32,11 @@
     return [] unless exists

     # Is this actually a directory?
-    stat = fs.statSync directory
-    return [] unless stat.isDirectory()
+    try
+      stat = fs.statSync directory
+      return [] unless stat.isDirectory()
+    catch e
+      return []

     # Get files
     try
@@ -47,7 +50,10 @@
       resultPath = path.resolve directory, result

       # Check for type
-      stat = fs.statSync resultPath
+      try
+        stat = fs.statSync resultPath
+      catch e
+        continue
       if stat.isDirectory()
         label = "Dir"
         result += "/"


oops...

oops...

--- src/client/api.coffee
+++ src/client/api.coffee
@@ -194,16 +194,11 @@
       onRemoteMessage = -> console.warn("RPC Message", arguments)

       onRemoteReady = (data)->
-        console.warn("Data: ", data)
-        try
-          for m in ['me', 'app', 'org']
-            attrs = data[m]
-            if attrs
-              attrs._id = m
-              console.warn("Settings model: ", attrs._id, attrs)
-              api.model(attrs)
-        catch e
-          console.warn("Et maintenant ?", e)
+        for m in ['me', 'app', 'org']
+          attrs = data[m]
+          if attrs && attrs._id
+            attrs._id = m
+            api.model(attrs)

         initialized.resolve(data)


client tests: adding some project testing

--- src/smc-webapp/test-client/project.coffee
+++ src/smc-webapp/test-client/project.coffee
@@ -1,5 +1,6 @@
 misc   = require('smc-util/misc')
 expect = require('expect')
+async  = require('async')

 describe 'create a project and make some changes using projects action', ->
     project_id = undefined
@@ -17,6 +18,11 @@
             project_id = _project_id
             done(err)

+    it 'opens project tab', ->
+        actions.open_project
+            project_id : project_id
+            switch_to  : true        # so user can watch :-)
+
     it 'confirms that we have info about this project', ->
         expect(actions.have_project(project_id)).toEqual(true)

@@ -27,6 +33,9 @@
         store = store
         expect(store.get_title(project_id)).toEqual("TEST project")
         expect(store.get_description(project_id)).toEqual("TEST description")
+
+    it 'confirms the document title at the top changes', ->
+        expect(document.title).toEqual("TEST project - SageMathCloud")

     it 'changes the title', ->
         actions.set_project_title(project_id, "TEST project -- Better Title")
@@ -56,5 +65,124 @@

     it 'deletes the project', ->
         # TODO: needs to be part of teardown and be destructive!
+        smc.redux.getActions('page').close_project_tab(project_id)
+        actions.stop_project(project_id)
         actions.delete_project(project_id)

+describe 'starting and stopping a project and getting directory listing', ->
+    project_id = undefined
+    actions    = smc.redux.getActions('projects')
+    store      = smc.redux.getStore('projects')
+
+    it 'creates a project and waits until created', (done) ->
+        @timeout(5000)
+        token = misc.uuid()
+        actions.create_project
+            title       : "TEST start/stop project"
+            description : "Test starting and stopping a project"
+            token       : token
+        store.wait_until_project_created token, 3, (err, _project_id) =>
+            project_id = _project_id
+            done(err)
+
+    it 'opens project tab', ->
+        actions.open_project
+            project_id : project_id
+            switch_to  : true        # so user can watch :-)
+        expect(document.title).toEqual("TEST start/stop project - SageMathCloud")
+        expect(misc.endswith(window.location.href, "/projects/#{project_id}/")).toEqual(true)
+
+    it 'starts the NEW project running and waits until running', (done) ->
+        @timeout(25000)
+        actions.start_project(project_id)
+        store.wait
+            until : => store.get_project(project_id).state?.state == 'running'
+            cb    : done
+
+    it 'stops project running and waits until stopped', (done) ->
+        @timeout(10000)
+        actions.stop_project(project_id)
+        store.wait
+            until : => store.get_project(project_id).state?.state == 'opened'
+            cb    : done
+
+    it 'starts project running again', (done) ->
+        @timeout(10000)
+        actions.start_project(project_id)
+        store.wait
+            until : => store.get_project(project_id).state?.state == 'running'
+            cb    : done
+
+    it 'gets directory listing (and confirm is empty list)', (done) ->
+        @timeout(5000)
+        smc.redux.getProjectActions(project_id).fetch_directory_listing()
+        s = smc.redux.getProjectStore(project_id)
+        s.wait
+            until : => s.getIn(['directory_listings', ""])?.toJS()?.length == 0
+            cb    : done
+
+    it 'gets directory listing including hidden (and confirm some dirs)', (done) ->
+        @timeout(5000)
+        smc.redux.getProjectActions(project_id).fetch_directory_listing('', true, true)
+        s = smc.redux.getProjectStore(project_id)
+        s.wait
+            until : => s.getIn(['directory_listings', ""])?.toJS()?.length > 0
+            cb    : done
+
+    it 'gets .smc directory listing', (done) ->
+        @timeout(5000)
+        smc.redux.getProjectActions(project_id).fetch_directory_listing('.smc')
+        s = smc.redux.getProjectStore(project_id)
+        s.wait
+            until : -> s.getIn(['directory_listings', '.smc'])?.toJS()?.length > 0
+            cb    : (err) ->
+                if err
+                    done(err)
+                    return
+                v = s.getIn(['directory_listings', '.smc'])?.toJS()
+                for x in v
+                    delete x.mtime  # too random to test
+                    delete x.size
+                expect(v).toEqual([{"isdir":true,"name":"local_hub"},{"isdir":true,"name":"root"},{"name":"secret_token"},{"name":"info.json"}])
+                done()
+
+    it 'deletes the project', ->
+        # TODO: needs to be destructive!
+        smc.redux.getActions('page').close_project_tab(project_id)
+        actions.stop_project(project_id)
+        actions.delete_project(project_id)
+
+###
+describe 'create and start project and edit a file in it', ->
+    project_id = undefined
+    actions    = smc.redux.getActions('projects')
+    store      = smc.redux.getStore('projects')
+
+    it 'creates a project and waits until created', (done) ->
+        @timeout(5000)
+        token = misc.uuid()
+        actions.create_project
+            title       : "TEST editing project"
+            description : "Test creating and editing a file"
+            token       : token
+        actions.open_project
+            project_id : project_id
+            switch_to  : true        # so user can watch :-)
+        store.wait_until_project_created token, 3, (err, _project_id) =>
+            project_id = _project_id
+            done(err)
+
+    it 'starts the new project running and waits until running', (done) ->
+        @timeout(25000)
+        actions.start_project(project_id)
+        store.wait
+            until : => store.get_project(project_id).state?.state == 'running'
+            cb    : done
+
+    it 'deletes the project', ->
+        # TODO: needs to be destructive!
+        smc.redux.getActions('page').close_project_tab(project_id)
+        actions.stop_project(project_id)
+        actions.delete_project(project_id)
+###
+


Method for resaving existing user owned objects

--- models/logged_out_user.coffee
+++ models/logged_out_user.coffee
@@ -54,3 +54,11 @@
     new Backbone.Model()
       .save @pick('email'), _.extend {}, options,
         url: "#{API_URL}/api/v1/users/send_reset_password_instructions"
+
+  repossess: ->
+    # Only valid for recently logged in, LoggedOutUsers
+    return Q.resolve() unless @isLoggedIn()
+    Q.all _.flatten [
+      @user.related().userInterests.invoke 'save'
+      @user.related().collectorProfile.instantiate()
+    ]


Prepare for logrotation.

Prepare for logrotation.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -22,6 +22,9 @@
     { level: 'error', stream: process.stderr }
     { level: argv.logLevel, path: "#{argv.logDir}/sphere-order-distribution_#{argv.projectKey}.log" }
   ]
+
+process.on 'SIGUSR2', ->
+  logger.reopenFileStreams()

 options =
   baseConfig:


remove isCorePackage

remove isCorePackage

--- lib/installed-package-view.coffee
+++ lib/installed-package-view.coffee
@@ -85,9 +85,6 @@
     repoName = url.parse(repoUrl).pathname
     chunks = repoName.match '/(.+?)/'
     chunks?[1]
-
-  isCorePackage: ->
-    @getAuthorUserName() is 'atom'

   handleButtonEvents: ->
     @disableButton.on 'click', =>


Revert "Fix ASTLinter to report lineNumber correctly with 1.6.2+ CoffeeScript"

This reverts commit 2ecefd03356bd4a23d2b5bb4d12fc76a99fe67bd.

--- src/coffeelint.coffee
+++ src/coffeelint.coffee
@@ -781,10 +781,7 @@
         # Parse the line number
         lineNumber = -1
         match = /line (\d+)/.exec message
-        if coffeeError.location?
-          lineNumber = coffeeError.location.first_line
-        else
-          lineNumber = parseInt match[1], 10 if match?.length > 1
+        lineNumber = parseInt match[1], 10 if match?.length > 1
         attrs = {
             message: message
             level: rule.level


Reorganization.

Reorganization.

--- src/coffeelint.coffee
+++ src/coffeelint.coffee
@@ -6,61 +6,41 @@
 ###

-# The coffeelint namespace.
 coffeelint = if exports?
   exports
 else
   this.coffeelint = {}

-# The current CoffeeLint version.
 coffeelint.VERSION = "0.0.2"

-#
+
 # A set of sane default lint rules.
-#
-
 DEFAULT_CONFIG =
     tabs : false        # Allow tabs for indentation.
     trailing : false    # Allow trailing whitespace.
     lineLength : 80     # The maximum length of each line.
-
-#
-# Error messages shown to users
-#
-
-MESSAGES =
-    NO_TABS : 'Tabs are forbidden'
-    TRAILING_WHITESPACE : 'Contains trailing whitespace'
-    LINE_LENGTH_EXCEEDED : 'Maximum line length exceeded'
-
-
-# Regular expressions
+# Regexes that are used repeatedly.
 regexes =
     trailingWhitespace : /\s+$/
     indentation: /\S/

-# Utility functions.
+# Patch the source properties onto the destination.
 extend = (destination, sources...) ->
     for source in sources
         (destination[k] = v for k, v of source)
     return destination

-
+# Return a map of config options with any unspecified options
+# patched with defaults.
 defaults = (userConfig) ->
     extend({}, DEFAULT_CONFIG, userConfig)

-#
-# Linting code.
-#
-
-
-# Lint the given source text with given user configuration and return a list
-# of any errors encountered.
-coffeelint.lint = (source, userConfig={}) ->
-    config = defaults(userConfig)
+# Return a list of errors found by performing "line"
+# checks on the source.
+checkLines = (source, config) ->
     errors = []
     lines = source.split('\n')
     for line, lineNumber in lines
@@ -72,8 +52,21 @@
                 errors.push(error)
     return errors

+# Return a list of errors found by performing "lex"
+# checks on the source.
+checkTokens = (source, config) ->
+    return []

+
+# Lint the given source text with given user configuration and return a list
+# of any errors encountered.
+coffeelint.lint = (source, userConfig={}) ->
+    config = defaults(userConfig)
+    checkLines(source, config).concat(checkTokens(source, config))
+
+#
 # A set of checks that should be performed on every line.
+#
 lineChecks =

     checkTabs : (line, config) ->
@@ -97,3 +90,12 @@
             reason: MESSAGES.LINE_LENGTH_EXCEEDED
         else
             null
+
+#
+# Messages shown to users.
+#
+
+MESSAGES =
+    NO_TABS : 'Tabs are forbidden'
+    TRAILING_WHITESPACE : 'Contains trailing whitespace'
+    LINE_LENGTH_EXCEEDED : 'Maximum line length exceeded'


Slim down ManagedAttachment

Slim down ManagedAttachment

--- src/trix/models/managed_attachment.coffee
+++ src/trix/models/managed_attachment.coffee
@@ -1,15 +1,13 @@
 #= require trix/models/attachment
-
-# TODO: ManagedAttachment should merge intrinsic and visual attributes into a single hash

 class Trix.ManagedAttachment
   # Forward all Attachment methods
-  for name, value of Trix.Attachment.prototype when typeof value is "function" then do (name, value) =>
-    @::[name] = -> @attachment[name].call(@attachment, arguments)
+  for own name, value of Trix.Attachment.prototype when name isnt "constructor" and typeof value is "function"
+    do (name, value) =>
+      @::[name] = -> @attachment[name].call(@attachment, arguments)

   constructor: (@attachmentManager, @attachment) ->
-    {@id, @file} = @attachment
-    {@document} = @attachmentManager
+    {@id} = @attachment

   setUploadProgress: (value) ->
     document.getElementById("trix-progress-#{@id}")?.setAttribute("value", value)


Updated rob time

Updated rob time

--- src/scripts/main.coffee
+++ src/scripts/main.coffee
@@ -6,7 +6,7 @@

 class RobCountdown

-  date_rob_arrives: (new Date(2015, 8, 4, 12, 0, 0, 0))
+  date_rob_arrives: (new Date(2015, 8, 4, 10, 0, 0, 0))

   start: ->
     @isHeBack()


if group is empty don't show the group title

--- lib/settings-panel.coffee
+++ lib/settings-panel.coffee
@@ -286,6 +286,8 @@
       @subview keyPath.replace(/\./g, ''), new TextEditorView(mini: true, attributes: {id: keyPath, type: 'array'})

 appendObject = (namespace, name, value) ->
+  return unless _.keys(value).length
+
   keyPath = "#{namespace}.#{name}"
   title = getSettingTitle(keyPath, name)
   @div class: 'sub-section', =>


Fix default directory when not specified

--- lib/skeleton/index.coffee
+++ lib/skeleton/index.coffee
@@ -46,7 +46,7 @@
       return

     if @options.appName
-      files = fs.readdir "#{@options.directory}/#{@options.appName}", (err, files) =>
+      files = fs.readdir "#{@options.directory || '.'}/#{@options.appName}", (err, files) =>
         throw err if err && 'ENOENT' != err.code
         empty = !files?.length > 0

@@ -55,7 +55,7 @@
           callback() if callback
         else
           this.displayLine 'Folder not empty. Use the --force flag to overrite'.grey
-          this.displayLine "#{'$'.cyan} skeleton -f #{@options.directory}/#{@options.appName}"
+          this.displayLine "#{'$'.cyan} skeleton -f #{@options.directory || '.'}/#{@options.appName}"
           callback() if callback

   ###


:art: Why semicolon?

:art: Why semicolon?

--- lib/snippet-parser.coffee
+++ lib/snippet-parser.coffee
@@ -78,4 +78,4 @@
         body = removeCharFromString(body, bodyIndex - bodyStart - i)
     body

-removeCharFromString = (str, index) -> str.slice(0, index) + str.slice(index + 1);
+removeCharFromString = (str, index) -> str.slice(0, index) + str.slice(index + 1)


:fire: old implementation

:fire: old implementation

--- lib/old_move-selected-text.coffee
+++ lib/old_move-selected-text.coffee
@@ -66,154 +66,6 @@
       disposable = switchToLinewise(selection)
     fn(selection)
     disposable?.dispose()
-
-# Up/Down
-class MoveSelectedTextUp extends MoveSelectedText
-  direction: 'up'
-
-  initialize: ->
-    stateManager.set(@editor) unless stateManager.has(@editor)
-    unless disposableByEditor.has(@editor)
-      disposable = @vimState.modeManager.onDidDeactivateMode ({mode}) =>
-        stateManager.remove(@editor) if mode is 'visual'
-
-      disposableByEditor.set @editor, @editor.onDidDestroy =>
-        disposable.dispose()
-        disposableByEditor.delete(@editor)
-        stateManager.remove(@editor)
-
-    super
-
-  getInitialOverwrittenBySelection: ->
-    overwrittenBySelection = new Map
-
-    getInitalAreaForSelection = (selection) =>
-      text = if @isLinewise()
-        height = swrap(selection).getRowCount()
-        "\n".repeat(height)
-      else
-        width = selection.getBufferRange().getExtent().column
-        ' '.repeat(width)
-      new Area(text, @isLinewise())
-
-    for selection in @editor.getSelections()
-      area = getInitalAreaForSelection(selection)
-      overwrittenBySelection.set(selection, area)
-    overwrittenBySelection
-
-  withUndoJoin: (fn) ->
-    state = stateManager.get(@editor)
-    state.init() unless state.isSequential()
-    unless state.overwrittenBySelection?
-      state.overwrittenBySelection = @getInitialOverwrittenBySelection()
-
-    fn()
-    state.updateSelectedTexts()
-    state.groupChanges()
-
-  # Used by
-  # - up: linewise
-  # - down: linewise
-  # - right: characterwise
-  # - left: characterwise
-  rotateTextForSelection: (selection) ->
-    reversed = selection.isReversed()
-    translation = switch @direction
-      when 'up' then [[-1, 0], [0, 0]]
-      when 'down' then [[0, 0], [1, 0]]
-      when 'right' then [[0, 0], [0, +1]]
-      when 'left' then [[0, -1], [0, 0]]
-
-    if @direction is 'down'
-      if selection.getBufferRange().end.row is @editor.getLastBufferRow()
-        ensureBufferEndWithNewLine(@editor)
-
-    range = selection.getBufferRange().translate(translation...)
-    if @direction is 'down' # auto insert new linew at last row
-      extendLastBufferRowToRow(@editor, range.end.row)
-
-    if @isOverwriteMode()
-      overwrittenArea = stateManager.get(@editor).overwrittenBySelection.get(selection)
-    area = new Area(@editor.getTextInBufferRange(range), @isLinewise(), overwrittenArea)
-    range = @editor.setTextInBufferRange(range, area.getTextByRotate(@direction))
-    range = range.translate(translation.reverse()...)
-    selection.setBufferRange(range, {reversed})
-
-  # Return 0 when no longer movable
-  getCount: ->
-    count = super
-    if @direction is 'up'
-      topSelection = @editor.getSelectionsOrderedByBufferPosition()[0]
-      startRow = topSelection.getBufferRange().start.row
-      Math.min(startRow, count)
-    else
-      count
-
-  execute: ->
-    @withUndoJoin =>
-      selections = @editor.getSelectionsOrderedByBufferPosition()
-      selections.reverse() if @direction is 'down'
-      @editor.transact =>
-        @countTimes @getCount(), =>
-          for selection in selections
-            if @isLinewise()
-              @moveLinewise(selection)
-            else
-              @moveCharacterwise(selection)
-
-  moveLinewise: (selection) ->
-    @withLinewise selection, =>
-      @rotateTextForSelection(selection)
-    @editor.scrollToCursorPosition({center: true})
-
-  moveCharacterwise: (selection) ->
-    reversed = selection.isReversed()
-    translation = switch @direction
-      when 'up' then [[-1, 0]]
-      when 'down' then [[+1, 0]]
-
-    fromRange = selection.getBufferRange()
-    toRange = fromRange.translate(translation...)
-
-    extendLastBufferRowToRow(@editor, toRange.end.row)
-    # Swap text from fromRange to toRange
-    insertSpacesToPoint(@editor, toRange.end)
-    movingText = @editor.getTextInBufferRange(fromRange)
-    replacedText = @editor.getTextInBufferRange(toRange)
-
-    if @isOverwriteMode()
-      area = stateManager.get(@editor).overwrittenBySelection.get(selection)
-      replacedText = area.pushOut(replacedText, opposite(@direction))
-    @editor.setTextInBufferRange(fromRange, replacedText)
-    @editor.setTextInBufferRange(toRange, movingText)
-
-    selection.setBufferRange(toRange, {reversed})
-
-class MoveSelectedTextDown extends MoveSelectedTextUp
-  direction: 'down'
-
-# Left/Right
-class MoveSelectedTextLeft extends MoveSelectedTextUp
-  direction: 'left'
-
-  moveLinewise: (selection) ->
-    switch @direction
-      when 'left' then selection.outdentSelectedRows()
-      when 'right' then selection.indentSelectedRows()
-
-  moveCharacterwise: (selection) ->
-    if @direction is 'left'
-      return unless selection.getBufferRange().start.column > 0
-
-    if @direction is 'right'
-      endPoint = selection.getBufferRange().end
-      insertTextAtPoint(@editor, endPoint, " ") if pointIsAtEndOfLine(@editor, endPoint)
-
-    @rotateTextForSelection(selection)
-    @editor.scrollToCursorPosition(center: true)
-
-class MoveSelectedTextRight extends MoveSelectedTextLeft
-  direction: 'right'

 # Duplicate
 # -------------------------
@@ -267,10 +119,7 @@

   execute: ->
     if @isLinewise() or @direction in ['right', 'left']
-      return super
-
-    @editor.transact =>
-      return if (count = @getCount()) is 0
+      return super @editor.transact => return if (count = @getCount()) is 0
       @withBlockwise =>
         if @direction is 'down'
           bottom = _.last(@editor.getSelectionsOrderedByBufferPosition())
@@ -392,9 +241,6 @@
   direction: 'right'

 module.exports = {
-  MoveSelectedTextUp, MoveSelectedTextDown
-  MoveSelectedTextLeft, MoveSelectedTextRight
-
   DuplicateSelectedTextUp, DuplicateSelectedTextDown
   DuplicateSelectedTextLeft, DuplicateSelectedTextRight
 }


Replaced http initialization with a new style using bloops instead of pie

--- http.coffee
+++ http.coffee
@@ -1,193 +1,34 @@
-Q = require 'q'
-pie = require './lib/pie'
+# API endpoints setup

-# API endpoints setup
-init = (api, port) ->
-  votingApi = new VotingHttpApi(port)
-  votingApi.init(api)
+express = require 'express'

-class VotingHttpApi extends pie.HttpApi
-  endpoints: -> [
-    ['/user/:userId', 'GET', listUser]
-    ['/users', 'GET', listUsers]
-    ['/user', 'POST', createUser]
-    ['/competition/:competitionId', 'GET', listCompetition]
-    ['/competitions/by-owner/:ownerId', 'GET', listCompetitionsByOwner]
-    ['/competitions/by-membership/:userId', 'GET', listCompetitionsByMembership]
-    ['/competitions/memberships', 'GET', listCompetitionMemberships]
-    ['/competitions/all', 'GET', listCompetitions]
-    ['/competitions', 'GET', listCompetitions]
-    ['/competition/:competitionId/end', 'POST', endCompetition]
-    ['/competition/:competitionId/join', 'POST', joinCompetition]
-    ['/competition/:competitionId/withdraw', 'POST', withdrawFromCompetition]
-    ['/competition/:competitionId/memberships', 'GET', listCompetitionMemberships]
-    ['/competition', 'POST', startCompetition]
-  ]
+init = (port) ->
+  console.log "[HTTP] initializing express"

-# Endpoint handler methods
+  app = express()
+  app.use(express.logger())
+  app.use(express.compress())
+  app.use(express.json())
+  app.use(express.urlencoded())
+  app.use(express.methodOverride())
+  app.use(app.router)

-# Lists one user
-listUser = (req, res) ->
-  userId = req.params.userId
-  console.log "[HTTP] request to list user (userId=#{userId})"
-  if not userId
-    res.json(400, {message: "must provide userId"})
-    return
+  resources = getResources()
+  console.log "[HTTP] found #{resources.length} resource types"
+  for resource in resources
+    console.log "[HTTP] initializing resource: #{resource.resourceName}"
+    resource.initialize(app)

-  @listSingle(
-    res,
-    @api.listUsers(userId),
-    { typeName: 'user', query: { user_id: userId } }
-  )
+  app.listen(port)
+  console.log "[HTTP] listening on port #{port}"

-# Lists all users
-listUsers = (req, res) ->
-  @listMultiple(
-    res,
-    @api.listUsers(),
-    { typeName: 'users' }
-  )
-
-createUser = (req, res) ->
-  name = req.body.name
-
-  @api.createUser(name)
-  .then((user) ->
-    res.json(201, user)
-    console.log("[HTTP] 201: created user: #{user}")
-  )
-  .fail((err) =>
-    @handleApiError(
-      res,
-      err,
-      errorMsg: "failed to create user (name=#{name})"
-    )
-  )
-  .done()
-
-listCompetition = (req, res) ->
-  if req.query.showClosed == 'true'
-    showClosed = true
-
-  competitionId = req.params.competitionId
-  if not competitionId
-    res.json(400, {message: "must provide competitionId"})
-    return
-
-  console.log "[HTTP] request to list competition (competitionId=#{competitionId}, showClosed=#{showClosed})"
-  @listSingle(
-    res,
-    @api.listCompetitions(competitionId, showClosed),
-    { typeName: 'competition', query: { competition_id: competitionId } }
-  )
-
-listCompetitions = (req, res) ->
-  if req.query.showClosed == 'true'
-    showClosed = true
-  console.log "[HTTP] request to list competitions (showClosed=#{showClosed})"
-  @listMultiple(
-    res,
-    @api.listCompetitions(null, showClosed),
-    { typeName: 'competitions' }
-  )
-
-listCompetitionsByOwner = (req, res) ->
-  if req.query.showClosed == 'true'
-    showClosed = true
-
-  @listMultiple(
-    res,
-    @api.listCompetitionsByOwner(req.params.ownerId, showClosed),
-    { typeName: 'competitions' }
-  )
-
-listCompetitionsByMembership = (req, res) ->
-  if req.query.showClosed == 'true'
-    showClosed = true
-
-  @listMultiple(
-    res,
-    @api.listCompetitionsByMembership(req.params.userId, showClosed),
-    { typeName: 'competitions' }
-  )
-
-listCompetitionMemberships = (req, res) ->
-  if req.params.competitionId?
-    competitionId = req.params.competitionId
-  @listMultiple(
-    res,
-    @api.listCompetitionMemberships(competitionId),
-    { typeName: 'competition memberships' }
-  )
-
-startCompetition = (req, res) ->
-  ownerId = req.body.ownerId
-  name = req.body.name
-
-  @api.startCompetition(ownerId, name)
-  .then((competition) ->
-    res.json(201, competition)
-    console.log("[HTTP] 201: started competition: #{competition}")
-  )
-  .fail((err) =>
-    @handleApiError(res, err, errorMsg: "failed to start competition (ownerId=#{ownerId}, name=#{name})")
-  )
-  .done()
-
-endCompetition = (req, res) ->
-  competitionId = req.params.competitionId
-
-  if not competitionId?
-    res.json(400, {error: "must provide competitionId"})
-    return
-
-  @api.endCompetition(competitionId)
-  .then((competition) ->
-    res.json(200, competition)
-    console.log "[HTTP] 200: ended competition: #{competition}"
-  )
-  .fail((err) =>
-    @handleApiError(res, err, errorMsg: "failed to end competition (competitionId=#{competitionId})")
-  )
-  .done()
-
-joinCompetition = (req, res) ->
-  userId = req.body.userId
-  competitionId = req.params.competitionId
-
-  if not userId? and competitionId?
-    res.json(400, {error: "must provide userId and competitionId"})
-    return
-
-  @api.joinCompetition(userId, competitionId)
-  .then((competitionMembership) ->
-    res.json(201, competitionMembership)
-    console.log("[HTTP] 201: joined competition: #{competitionMembership}")
-  )
-  .fail((err) =>
-    @handleApiError(res, err,
-      errorMsg: "failed to join competition (userId=#{userId}, competitionId=#{competitionId})"
-    )
-  )
-  .done()
-
-withdrawFromCompetition = (req, res) ->
-  userId = req.body.userId
-  competitionId = req.params.competitionId
-
-  if not userId? and competitionId?
-    res.json(400, {error: "must provide userId and competitionId"})
-    return
-
-  @api.withdrawFromCompetition(userId, competitionId)
-  .then((props) ->
-    res.json(200, props)
-    console.log "[HTTP] 200: withdrew from competition (userId=#{props.userId}, competitionId=#{props.competitionId})"
-  )
-  .fail((err) =>
-    @handleApiError(res, err,
-      errorMsg: "failed to withdraw from competition (userId=#{userId}, competitionId=#{competitionId})"
-    )
-  )
+getResources = -> [
+  require('./resources/votes')
+  require('./resources/users')
+  require('./resources/competitions')
+  require('./resources/competition_candidates')
+  require('./resources/competition_memberships')
+  require('./resources/choices')
+]

 module.exports = {init}


set up double-tap of shift key as prefix

--- init.coffee
+++ init.coffee
@@ -11,3 +11,22 @@
   'user:insert-date': (event) ->
     return unless editor = @getModel()
     editor.insertText(new Date().toLocaleString())
+
+keyPressed = false;
+
+# neat trick to allow Atom to interpret double-tapping the shift key as a prefix key
+# from www.atom-tweaks.com
+# Adding this to your init.coffee should allow you to use shift-double in keymaps
+# to execute commands by double-tapping either shift key.
+# Credit goes to @spaceribs for the code: https://gist.github.com/spaceribs/76b62d6cedb1d0892ec10a8b35d38683
+
+atom.keymaps.addKeystrokeResolver ({event}) ->
+  if event.key == "Shift" && event.type == "keydown"
+    if keyPressed
+      keyPressed = false;
+      console.log('keyPressed');
+      return 'shift-double';
+    else
+      keyPressed = true;
+      setTimeout (-> keyPressed = false), 200;
+      return null;


Fixed BRK instruction execution.

Fixed BRK instruction execution.

--- emulator/core/units/cpu.coffee
+++ emulator/core/units/cpu.coffee
@@ -132,7 +132,7 @@
     readNextProgramWord: ->
         @$readWord @$moveProgramCounter 2

-    moveProgramCounter: (size = 1) ->
+    moveProgramCounter: (size) ->
         previousValue = @programCounter
         @programCounter = (@programCounter + size) & 0xFFFF
         previousValue
@@ -539,6 +539,7 @@
     ###########################################################

     BRK: =>
+        @$moveProgramCounter 1 # BRK is 2 byte instruction
         @$pushWord @programCounter
         @$pushByte @$getStatus() | 0x10 # It pushes status with bit 4 on (break command flag)
         @interruptDisable = 1