Handling script load errors in all factory methods.

--- www/src/theoricus/core/factory.coffee
+++ www/src/theoricus/core/factory.coffee
@@ -43,6 +43,9 @@
       model[prop] = value for prop, value of init

       fn model
+    , (err)->
+      console.error 'Model not found: ' + classpath
+      fn null

   ###
   Returns an instantiated [theoricus.mvc.View] View
@@ -75,6 +78,10 @@

       fn view

+    , (err)->
+      console.error 'View not found: ' + classpath
+      fn null
+

   ###
   Returns an instantiated [theoricus.mvc.Controller] Controller
@@ -90,6 +97,7 @@
     if @controllers[ classpath ]?
       fn @controllers[ classpath ]
     else
+
       require [classpath], ( Controller )=>

         unless (controller = new Controller) instanceof Controller
@@ -104,6 +112,10 @@
         @controllers[ classpath ] = controller
         fn @controllers[ classpath ]

+      , (err)->
+        console.error 'Controller not found: ' + classpath
+        fn null
+
   ###
   Returns an AMD compiled template

@@ -111,7 +123,11 @@
   ###
   @template=@::template=( path, fn )->
     # console.log "Factory.template( #{path} )"
-    require ['templates/' + path], ( template )-> fn template
+    require ['templates/' + path], ( template )->
+      fn template
+    , (err)->
+      console.error 'Template not found: ' + classpath
+      fn null


:fire: Remove logs

:fire: Remove logs

--- lib/marker-view.coffee
+++ lib/marker-view.coffee
@@ -17,8 +17,6 @@

   updateDisplay: =>
     return unless @isUpdateNeeded()
-
-    console.log 'update display'

     @updateNeeded = false
     @clearRegions()


Allow specifying the name of the identifier in a belongs_to relation

--- lib/minimongoid.coffee
+++ lib/minimongoid.coffee
@@ -47,7 +47,7 @@
     # set up belongs_to methods, e.g. recipe.user()
     for belongs_to in @constructor.belongs_to
       relation = belongs_to.name
-      identifier = "#{relation}_id"
+      identifier = belongs_to.identifier || "#{relation}_id"
       # set up default class name, e.g. "belongs_to: user" ==> 'User'
       class_name = belongs_to.class_name || _.titleize(relation)


building query

building query

--- lib/phabricator.coffee
+++ lib/phabricator.coffee
@@ -701,33 +701,41 @@
     .then (userPhid) =>
       @taskInfo id
     .then (body) =>
-      commands = @parseAction body.result, commandString
-      if commands.data.length() > 0
-
-      console.log commands
-      { id: id, messages: [ 'ok' ], data: [ 'open' ] }
-
-  parseAction: (item, str, res = [], msg = []) ->
+      results = @parseAction body.result, commandString
+      console.log results
+      if results.data.length > 0
+        query = {
+          'objectIdentifier': "T#{id}",
+          'transactions[0][type]': 'subscribers.remove',
+          'transactions[0][value][0]': "#{@bot_phid}"
+        }
+        for action, i in results.data
+          query['transactions['+(i+1)+'][type]'] = action.type
+          query['transactions['+(i+1)+'][value]'] = action.value
+        console.log query
+      { id: id, messages: results.messages, data: [ 'open' ] }
+
+  parseAction: (item, str, res = { data: [], messages: [] }) ->
     p = new RegExp("^(in|not in|on|is|to) ([^ ]*)")
     r = str.trim().match p
     switch r[1]
       when 'in'
-        res.push({ type: 'projects.add', value: r[2] })
+        res.data.push({ type: 'projects.add', value: r[2] })
       when 'not in'
-        res.push({ type: 'projects.remove', value: r[2] })
+        res.data.push({ type: 'projects.remove', value: r[2] })
       when 'on'
-        res.push({ type: 'owner', value: r[2] })
+        res.data.push({ type: 'owner', value: r[2] })
       when 'to'
-        res.push({ type: 'column', value: r[2] })
+        res.data.push({ type: 'column', value: r[2] })
       when 'is'
         if @statuses[r[2]]?
-          res.push({ type: 'status', value: r[2] })
+          res.data.push({ type: 'status', value: r[2] })
         else if @priorities[r[2]]
-          res.push({ type: 'priority', value: r[2] })
+          res.data.push({ type: 'priority', value: r[2] })
     next = str.trim().replace(p, '')
     if next.trim() isnt ''
-      res = @parseAction(item, next, res, msg)
-    { data: res, messages: msg }
+      res = @parseAction(item, next, res)
+    res

   listTasks: (projphid) ->
     query = {


small optimization

small optimization

--- lib/placeholder.coffee
+++ lib/placeholder.coffee
@@ -43,8 +43,7 @@

   app.get '/placeholder', (req, res) ->
     redis.get 'hits', (err, hits) ->
-      title = 'Placeholder'
-      res.render 'placeholder', Object.assign(config, {hits, title, version})
+      res.render 'placeholder', Object.assign(config, title: 'Placeholder', {hits, title, version})
       return

     return


Removing empty parens from the grammar.

Removing empty parens from the grammar.

--- grammar.coffee
+++ grammar.coffee
@@ -407,7 +407,6 @@
   # the trick.
   Parenthetical: [
     o "( Expression )",                         -> new Parens $2
-    o "( )",                                    -> new Parens new Literal ''
   ]

   # The condition portion of a while loop.


restore cursor position when needed

restore cursor position when needed

--- lib/text-buffer.coffee
+++ lib/text-buffer.coffee
@@ -13,6 +13,21 @@

     # compare goals with indices
     changed: (indices) -> _.isEqual _.pluck(@goals, 'index'), indices
+
+
+    #########################
+    #   Cursor Management   #
+    #########################
+
+    protectCursor: (callback) ->
+        position = @core.editor.getCursorBufferPosition()
+        callback()
+        @getCurrentGoal()
+            .then (goal) =>
+                newPosition = goal.translate goal.getStart(), 3
+                @core.editor.setCursorBufferPosition newPosition
+            .fail =>
+                @core.editor.setCursorBufferPosition position

     #######################
@@ -41,9 +56,13 @@
             if goals.length is 1
                 resolve goals[0]
             else
+                reject()
+
+    getCurrentGoalOrWarn: (cursor = @core.editor.getCursorBufferPosition()) ->
+        @getCurrentGoal cursor
+            .fail =>
                 warn 'Text Buffer', 'out of goal'
                 @core.panel.outputInfo 'For this command, please place the cursor in a goal'
-                reject()

     warnCurrentGoalIfEmpty: (goal, warning) =>
         content = goal.getContent()
@@ -98,32 +117,32 @@
         if positions.length isnt 0
             @core.editor.setCursorBufferPosition previousGoal

-    give: -> @getCurrentGoal().then (goal) =>
+    give: -> @getCurrentGoalOrWarn().then (goal) =>
         @warnCurrentGoalIfEmpty goal, 'Please type in the expression to give'
         @core.executable.give goal

-    goalType: -> @getCurrentGoal().then (goal) =>
+    goalType: -> @getCurrentGoalOrWarn().then (goal) =>
         @core.executable.goalType goal

-    context: -> @getCurrentGoal().then (goal) =>
+    context: -> @getCurrentGoalOrWarn().then (goal) =>
         @core.executable.context goal

-    goalTypeAndContext: -> @getCurrentGoal().then (goal) =>
+    goalTypeAndContext: -> @getCurrentGoalOrWarn().then (goal) =>
         @core.executable.goalTypeAndContext goal

-    goalTypeAndInferredType: -> @getCurrentGoal().then (goal) =>
+    goalTypeAndInferredType: -> @getCurrentGoalOrWarn().then (goal) =>
         @warnCurrentGoalIfEmpty goal, 'Please type in the expression to infer'
         @core.executable.goalTypeAndInferredType goal

-    refine: -> @getCurrentGoal().then (goal) =>
+    refine: -> @getCurrentGoalOrWarn().then (goal) =>
         @warnCurrentGoalIfEmpty goal, 'Please type in the expression to refine'
         @core.executable.refine goal

-    case: -> @getCurrentGoal().then (goal) =>
+    case: -> @getCurrentGoalOrWarn().then (goal) =>
         @warnCurrentGoalIfEmpty goal, 'Please type in the expression to make case'
         @core.executable.case goal

-    auto: -> @getCurrentGoal().then (goal) =>
+    auto: -> @getCurrentGoalOrWarn().then (goal) =>
         @core.executable.auto goal

@@ -131,7 +150,7 @@
     #   Command Handlers   #
     ########################

-    goalsAction: (indices) ->
+    goalsAction: (indices) -> @protectCursor =>
         unless @changed indices
             log 'Text Buffer', "setting goals #{indices}"
             @removeGoals()
@@ -147,7 +166,7 @@
                 goal  = new Goal @core.editor, index, pos.start, pos.end - 2
                 @goals.push goal

-    giveAction: (index, content) ->
+    giveAction: (index, content) -> @protectCursor =>
         log 'Text Buffer', "handling give-action #{content}"
         goal = @findGoal index
         if content
@@ -155,7 +174,8 @@
         goal.removeBoundary()
         @removeGoal index

-    makeCaseAction: (content) -> @getCurrentGoal().then (goal) =>
-        goal.writeLines content
+    makeCaseAction: (content) ->  @protectCursor =>
+        @getCurrentGoalOrWarn().then (goal) =>
+            goal.writeLines content

 module.exports = TextBuffer


sending all events to particles

sending all events to particles

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -14,7 +14,7 @@
   self.bind "update", (elapsedTime) ->
     for event in I.everyEvents
       while event.lastFired < I.age
-        event
+        event.fn()

   ###*
   Execute fn every n frames.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -2,8 +2,8 @@
 The TimedEvents module allows arbitrary code to be executed at set intervals.

-player = GameObject
-
+player = GameObject()
+player.

 @see GameObject


working on snippets

working on snippets

--- snippets/iridium.cson
+++ snippets/iridium.cson
@@ -11,3 +11,63 @@
   '4':
     'prefix': 'click the element found by alias \"identifier\" if it exists'
     'body': 'click the element found by alias \"$1\" if it exists'
+  '5':
+    'prefix': 'click the element with the ID of \"identifier\"'
+    'body': 'click the element with the ID of \"$1\"'
+  '6':
+    'prefix': 'click the element with the class of \"identifier\"'
+    'body': 'click the element with the class of \"$1\"'
+  '7':
+    'prefix': 'click the element with the xpath of \"identifier\"'
+    'body': 'click the element with the xpath of \"$1\"'
+  '8':
+    'prefix': 'click the element with the name of \"identifier\"'
+    'body': 'click the element with the name of \"$1\"'
+  '9':
+    'prefix': 'click the element with the css selector of \"identifier\"'
+    'body': 'click the element with the css selector of \"$1\"'
+  '10':
+    'prefix': 'click the element with the ID of \"identifier\" if it exists'
+    'body': 'click the element with the ID of \"$1\" if it exists'
+  '11':
+    'prefix': 'click the element with the class of \"identifier\" if it exists'
+    'body': 'click the element with the class of \"$1\" if it exists'
+  '12':
+    'prefix': 'click the element with the xpath of \"identifier\" if it exists'
+    'body': 'click the element with the xpath of \"$1\" if it exists'
+  '13':
+    'prefix': 'click the element with the name of \"identifier\" if it exists'
+    'body': 'click the element with the name of \"$1\" if it exists'
+  '14':
+    'prefix': 'click the element with the css selector of \"identifier\" if it exists'
+    'body': 'click the element with the css selector of \"$1\" if it exists'
+  '15':
+    'prefix': 'click the element with the ID alias of \"identifier\"'
+    'body': 'click the element with the ID alias of \"$1\"'
+  '16':
+    'prefix': 'click the element with the class alias of \"identifier\"'
+    'body': 'click the element with the class alias of \"$1\"'
+  '17':
+    'prefix': 'click the element with the xpath alias of \"identifier\"'
+    'body': 'click the element with the xpath alias of \"$1\"'
+  '18':
+    'prefix': 'click the element with the name alias of \"identifier\"'
+    'body': 'click the element with the name alias of \"$1\"'
+  '19':
+    'prefix': 'click the element with the css selector alias of \"identifier\"'
+    'body': 'click the element with the css selector alias of \"$1\"'
+  '20':
+    'prefix': 'click the element with the ID alias of \"identifier\" if it exists'
+    'body': 'click the element with the ID alias of \"$1\" if it exists'
+  '21':
+    'prefix': 'click the element with the class alias of \"identifier\" if it exists'
+    'body': 'click the element with the class alias of \"$1\" if it exists'
+  '22':
+    'prefix': 'click the element with the xpath alias of \"identifier\" if it exists'
+    'body': 'click the element with the xpath alias of \"$1\" if it exists'
+  '23':
+    'prefix': 'click the element with the name alias of \"identifier\" if it exists'
+    'body': 'click the element with the name alias of \"$1\" if it exists'
+  '24':
+    'prefix': 'click the element with the css selector alias of \"identifier\" if it exists'
+    'body': 'click the element with the css selector alias of \"$1\" if it exists'


cannon prefix

cannon prefix

--- lib/Cycler.coffee
+++ lib/Cycler.coffee
@@ -7,7 +7,7 @@
   constructor: ({@source, @prefix}) ->
     #@source ?= ['localhost']
     if @source?
-      exec "ifconfig eth0:#{idx} #{ip}" for ip, idx in @source
+      exec "ifconfig cannon:#{idx} #{ip}" for ip, idx in @source
     @range = 8 - @prefix.split(':').length if @prefix?

   # TODO: Support for ipv4 ranges


Add another error check.

Add another error check.

--- src/tracker/reki.coffee
+++ src/tracker/reki.coffee
@@ -229,23 +229,24 @@
       if kvp[0...9] is "info_hash"
         info_hashes.push decodeURLtoHex kvp[10...]

-    # Require at least one info_hash and don't support sitewide scraping.
+    # Require at least one info_hash; no support for sitewide scraping.
     return simple_response res, {'failure reason': 'Invalid Request'} if info_hashes.length == 0

     multi = @redis.multi()
     files = {}
     @torrents.find({ 'infohash' : {$in:info_hashes} }).toArray (err, docs) =>
+      return simple_response res, {'failure reason': 'No valid hashes requested'} if docs.length is 0
       infohash = []
       snatches = [] # downloaded
-      for i in [0...docs.length]
+      for i in [0...docs.length] # not sure why but docs.length-1 skips the last item
         ih = docs[i].infohash
+        key = 'torrent:' + ih
         infohash.push ih
-        key = 'torrent:' + ih
         snatches.push docs[i].snatches
         multi
           .ZCARD(key + ':peers') # incomplete
           .ZCARD(key + ':seeds') # complete
-        #console.log infohash + ': ' + docs[i].snatches
+
       multi.exec (err, reply) -> # no real error checking here because the hashes returned by mongo should all be valid.
         j = 0
         for hash,i in infohash


lodheatmap:  remove some debugging code

lodheatmap:  remove some debugging code

--- src/lodheatmap.coffee
+++ src/lodheatmap.coffee
@@ -3,9 +3,7 @@
 lodheatmap = (chartOpts) ->
     # chartOpts begin
     colors = chartOpts?.colors ? ["slateblue", "white", "crimson"]  # vector of three colors for the color scale (negative - zero - positive)
-    rectcolor = chartOpts?.rectcolor ? "white" # color of background rectangle
-    altrectcolor = chartOpts?.altrectcolor ? "white" # color of background rectangle for alternate chromosomes
-    nullcolor = chartOpts?.nullcolor ? "pink" # "#e6e6e6" # color for empty cells
+    nullcolor = chartOpts?.nullcolor ? "#e6e6e6" # color for empty cells
     xlab = chartOpts?.xlab ? "Chromosome" # x-axis label
     ylab = chartOpts?.ylab ? ""           # y-axis label
     ylim = chartOpts?.ylim ? null # y-axis limits (if null take from data)


Added modifyCheck method

Added modifyCheck method

--- src/updown.class.coffee
+++ src/updown.class.coffee
@@ -47,16 +47,17 @@

 	###
 	Posts data to an URL with the Updown API key header
+	@param {string} method - Method for the HTTP request
 	@param {string} url - The API URL to post to
 	@param {object} params - Parameters to be sent in the POST body
 	@param {function} callback - Is called when the request completes
 	###
-	_post: (url, params, callback) ->
+	_req: (method, url, params, callback) ->

 		# Initiate POST request
 		request
 			url: url
-			method: 'POST'
+			method: method
 			json: yes
 			form: params
 			headers:
@@ -65,6 +66,8 @@
 		# Handle result
 		, (err, response, data) ->
 			callback(err, data)
+
+

 	###
@@ -171,15 +174,51 @@
 			params.name = name if name?

 			# Create request
-			@_post 'https://updown.io/api/checks/', params, (err, data) ->
-
-				# Reject if request errored
-				return reject(err) if err
-
-				# Resolve with response data
-				resolve(data)
-
-
+			@_req 'POST', 'https://updown.io/api/checks/', params, (err, data) ->
+
+				# Reject if request errored
+				return reject(err) if err
+
+				# Resolve with response data
+				resolve(data)
+
+
+
+	###
+	Adds new check
+	@param {string} token - The token
+	@param {string} url - (optional) The new URL
+	@param {number} interval - (optional) The new interval in seconds (30, 60, 120, 300 or 600)
+	@param {string} name - (optional) A new alias for the check
+	###
+	modifyCheck: (token, url, interval, name) ->
+
+		# Create promise
+		new Promise (resolve, reject) =>
+
+			# Reject if we're in read-only mode
+			return reject new Error('Updown is set to read-only mode') if @readOnly
+
+			# Check if parameters are valid
+			return reject new TypeError('token parameter should be a string') if typeof token isnt 'string'
+			return reject new TypeError('url parameter should be a string') if url? and typeof url isnt 'string'
+			return reject new TypeError('interval parameter should be a number') if interval? and typeof interval isnt 'number'
+			return reject new RangeError('interval must be 30, 60, 120, 300 or 600') if interval? and interval not in [30, 60, 120, 300, 600]
+
+			# Assemble params
+			params = {}
+			params.url = url if url?
+			params.period = interval if interval?
+			params.name = name if name?
+
+			# Create request
+			@_req 'PUT', 'https://updown.io/api/checks/' + token, params, (err, data) ->
+
+				# Reject if request errored
+				return reject(err) if err
+
+				# Resolve with response data
+				resolve(data)

 # Export class
 module.exports = Updown


Remove historic layers based for each resource

--- src/main/assets/javascripts/simple.coffee
+++ src/main/assets/javascripts/simple.coffee
@@ -1,4 +1,4 @@
-TopLayers = []
+TopLayers = {}

 configureFeedBehaviour = ->
   $('ul.breadcrumb li.sub-link').click ->
@@ -68,10 +68,12 @@
     onEachFeature: (feature, layer) ->
       layer.bindPopup feature.properties.description + "
(#{time_diff})"
   })
-  TopLayers.unshift(layer)
+  if( !TopLayers[v.id] )
+    TopLayers[v.id] = []
+  TopLayers[v.id].unshift(layer)
   layer.addTo(map)
-  if TopLayers.length > 3
-    l = TopLayers.pop()
+  if TopLayers[v.id].length > 3
+    l = TopLayers[v.id].pop()
     console.log l
     map.removeLayer(l);


Fix the path to the default stylesheet (#297)

--- lib/asciidoc-preview-view.coffee
+++ lib/asciidoc-preview-view.coffee
@@ -247,7 +247,7 @@
       .then (html) =>
         model =
           content: html
-          style: fs.readFileSync path.join(packPath, 'node_modules/asciidoctor.js/dist/css/asciidoctor.css'), 'utf8'
+          style: fs.readFileSync path.join(packPath, 'node_modules/@asciidoctor/core/dist/css/asciidoctor.css'), 'utf8'
           title: $(@html).find('h1').text() or path.basename htmlFilePath, '.html'
       .then (model) ->
         template = fs.readFileSync templatePath, 'utf8'


Allow withArguments to call .get() for anything that has it defined

--- src/view/filters.coffee
+++ src/view/filters.coffee
@@ -172,11 +172,11 @@

   # allows you to curry arguments to a function via a filter
   withArguments: (block, curryArgs..., binding) ->
-    return if not block
+    return unless block
     if typeof block is "function"
-      return (regularArgs...) -> block.call @, curryArgs..., regularArgs...
-    else if block.isTerminalAccessible
-      return block.get(curryArgs...)
+      (regularArgs...) -> block.call @, curryArgs..., regularArgs...
+    else if typeof block.get is "function"
+      block.get(curryArgs...)

   escape: buntUndefined(Batman.escapeHTML)


Speed up autocomplete

Speed up autocomplete

--- lib/autocomplete-provider.coffee
+++ lib/autocomplete-provider.coffee
@@ -15,7 +15,36 @@
         return text
             .replace /"([A-Za-z_]+)"\(\)/, "$1()"

+    timeout = (ms) ->
+        return new Promise((fulfil) ->
+            setTimeout(() ->
+                fulfil()
+            , ms)
+        )
+
     getSuggestions: (options) ->
+        window.lastrun = (new Date()).getTime()
+        lastrun_myself = window.lastrun
+        dis = @
+        retries = 0
+        lolz = (time, retries) ->
+            return timeout(time).then () ->
+                if (retries > 100)
+                    return
+                retries = retries + 1
+                if (window.requesting == 1)
+                    #console.log('retrying', lastrun_myself)
+                    return lolz(time, retries)
+                if window.lastrun == lastrun_myself
+                    console.log('match! doing another request', lastrun_myself)
+                    window.requesting = 0
+                    return dis.getSuggestions(options)
+                return lolz(time, retries)
+
+        if window.requesting == 1
+            return lolz(5, 0)
+        window.requesting = 1
+
         if not atom.config.get('pg-hoff.autocompletionEnabled')
             return []
         if not atom.workspace.getActivePaneItem().alias
@@ -33,9 +62,9 @@
             pos: pos
             query: text
             alias: atom.workspace.getActivePaneItem().alias
-
         return PgHoffServerRequest.Post('completions', request)
             .then (response) ->
+                window.requesting = 0
                 pascalize = atom.config.get('pg-hoff.pascaliseAutocompletions')
                 unQuoteFunctionNames = atom.config.get('pg-hoff.unQuoteFunctionNames')
                 suggestions = response.map (value) ->


added a replacePrefix to help with mounted directories

--- src/middleware.coffee
+++ src/middleware.coffee
@@ -47,6 +47,9 @@
         pathname = pathname.substring options.replacePrefix.length
       if options.prefix and 0 is pathname.indexOf options.prefix
         pathname = pathname.substring options.prefix.length
+    if /\.js$/.test pathname
+      if options.replacePrefix and 0 is pathname.indexOf options.replacePrefix
+        pathname = pathname.substring options.replacePrefix.length
       jsPath = path.join dest, pathname
       coffeePath = path.join src, pathname.replace '.js', '.coffee'
       # Ignore ENOENT to fall through as 404


rename methods to be send<Action>

rename methods to be send<Action>

--- src/model/user.coffee
+++ src/model/user.coffee
@@ -13,7 +13,7 @@
     _.extend(@, attrs)
     super()

-  verify: (pin, successCallBack) ->
+  sendVerification: (pin, successCallBack) ->
     @makeRequest('confirm_by_pin', {pin}).then (resp) =>
       if resp.status is 204
         delete @error
@@ -27,9 +27,7 @@
       @emit('change')

-
-
-  sendVerification: ->
+  sendConfirmation: ->
     @makeRequest('resend_confirmation').then (resp) =>
       @verifyInProgress = (resp.status is 204)
       @emit('change')


Fix Bacon.spy spec with combineTemplate

Fix Bacon.spy spec with combineTemplate

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -3239,7 +3239,7 @@
     it "map", ->
       testSpy 2, -> Bacon.once(1).map(->)
     it "combineTemplate (also called for the intermediate combineAsArray property)", ->
-      testSpy 4, -> Bacon.combineTemplate(Bacon.once(1), Bacon.constant(2))
+      testSpy 5, -> Bacon.combineTemplate([Bacon.once(1), Bacon.constant(2)])

 describe "Infinite synchronous sequences", ->
   describe "Limiting length with take(n)", ->


Use attachToDom instead of appending directly to #jasmine-content

--- spec/atom-spec.coffee
+++ spec/atom-spec.coffee
@@ -12,7 +12,7 @@
   describe "base stylesheet loading", ->
     beforeEach ->
       rootView.append $("

")
-      $("#jasmine-content").append(rootView)
+      rootView.attachToDom()
       atom.themes.load()
       atom.watchThemes()


:art: Store MessageRegexp on instance

:art: Store MessageRegexp on instance

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -2,7 +2,6 @@
 path = require 'path'
 {CompositeDisposable, Range, Point, BufferedProcess} = require 'atom'
 XRegExp = null
-MessageRegexp = null
 {deprecate} = require('grim')

 # These are NOOPs in linter-plus
@@ -224,8 +223,8 @@
   processMessage: (message, callback) ->
     messages = []
     XRegExp ?= require('xregexp').XRegExp
-    MessageRegexp ?= XRegExp @regex, @regexFlags
-    XRegExp.forEach message, MessageRegexp, (match, i) =>
+    @MessageRegexp ?= XRegExp @regex, @regexFlags
+    XRegExp.forEach message, @MessageRegexp, (match, i) =>
       msg = @createMessage match
       messages.push msg if msg.range?
     , this


Use cwd in exec instead of passing it as an argument

hh_client is unable to properly find the .hhconfig file when passing the directory as an argument

See https://github.com/facebook/hhvm/issues/3643
(The commit is not in a stable release yet)

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -69,12 +69,12 @@
     self = this
     if @config.type is 'local'
       dir = path.dirname(localPath).replace(' ', '\\ ') #escaping spaces
-      exec "hh_client --json --from atom #{localPath}",(_,__,stderr)->
+      exec "hh_client --json --from atom", {"cwd": dir}, (_,__,stderr)->
         self.setErrors(stderr)
         self.redraw()
     else if @config.type is 'remote'
       dir = path.dirname(localPath).replace(@config.localDir,@config.remoteDir).split('\\').join(path.sep).replace(' ','\\ ')
-      @ssh.exec("hh_client --json --from atom #{dir}").then (result)->
+      @ssh.exec("hh_client --json --from atom", {"cwd": dir}).then (result)->
         self.setErrors(result.stderr)
         self.redraw()
   setErrors:(output)->
@@ -151,4 +151,4 @@
     for error in errorsOthers
       file = error[1]
       msgPanel.otherFile error,'text-warning'
-module.exports = Linter+module.exports = Linter


Improve HTTP logger

Improve HTTP logger

--- lib/logger.coffee
+++ lib/logger.coffee
@@ -4,7 +4,16 @@
 logger = new Logger timestamp: false, level: logLevel

 logger.http = (req, res, next) ->
-  logger.log "http", req.method, req.url, req.params or ""
+  end = res.end
+  req._startTime = new Date
+  res.end = (chunk, encoding) ->
+    status   = res.statusCode
+    len      = parseInt(res.getHeader('Content-Length'), 10)
+    duration = new Date - req._startTime;
+    line = "#{req.method} #{req.originalUrl} #{res.statusCode} #{duration}ms #{len}"
+    logger.log "http", line
+    res.end = end
+    res.end chunk, encoding
   next()

 module.exports = logger


Fix move to next subword throw error at some end word of line, move-to-next-whole-word now vimCompatibile for blank row behavior

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -345,6 +345,7 @@
   getPoint: (pattern, from) ->
     wordRange = null
     found = false
+    vimEOF = @getVimEofBufferPosition(@editor)

     @scanForward pattern, {from}, ({range, matchText, stop}) ->
       wordRange = range
@@ -356,7 +357,7 @@

     if found
       point = wordRange.start
-      if pointIsAtEndOfLineAtNonEmptyRow(@editor, point) and not point.isEqual(scanRange.end)
+      if pointIsAtEndOfLineAtNonEmptyRow(@editor, point) and not point.isEqual(vimEOF)
         point.traverse([1, 0])
       else
         point
@@ -452,11 +453,11 @@
 # -------------------------
 class MoveToNextWholeWord extends MoveToNextWord
   @extend()
-  wordRegex: /^\s*?$|\S+/g
+  wordRegex: /^$|\S+/g

 class MoveToPreviousWholeWord extends MoveToPreviousWord
   @extend()
-  wordRegex: /^\s*$|\S+/
+  wordRegex: /^$|\S+/

 class MoveToEndOfWholeWord extends MoveToEndOfWord
   @extend()


Rewrote and extended the log parser. (#171)

* Rewrote and extended the log parser.

* Added compatibility for Windows File recognition

* Added check for .aux files in input file identification

--- lib/parser.coffee
+++ lib/parser.coffee
@@ -1,12 +1,13 @@
 { Disposable } = require 'atom'
 path = require 'path'

-latexFile = /^.*?\(\.\/(.*?\.\w+)/
+fileName = /^.*?\(\.?\/?([^\)\s]*?)\.\w+/
+latexFile = /^.*?\(\.?\/?([^\)\s]*?\.\w+)/
 latexPattern = /^Output\swritten\son\s(.*)\s\(.*\)\.$/gm
 latexFatalPattern = /Fatal error occurred, no output PDF file produced!/gm
-latexError = /^(?:(.*):(\d+):|!)(?: (.+) Error:)? (.+?)\.?$/
-latexBox = /^((?:Over|Under)full \\[vh]box \([^)]*\)) in paragraph at lines (\d+)--(\d+)$/
-latexWarn = /^((?:(?:Class|Package) \S+)|LaTeX) (Warning|Info):\s+(.*?)(?: on input line (\d+))?\.$/
+latexError = /^(?:(.*):(\d+):|!)(?: (.+) Error:)? (.+?)\.?$/gm
+latexBox = /^((?:Over|Under)full \\[vh]box \([^)]*\)) (?:in paragraph at lines|has occurred while \\output is active) \[?(\d+)[-.]+(\d+)\]?\)?$/gm
+latexWarn = /^((?:(?:Class|Package) \S+)|LaTeX(?: Font)?) (Warning|Info):\s+([\s\S]*?)(?: on input line (\d+))?\.$/gm

 latexmkPattern = /^Latexmk:\sapplying\srule/gm
 latexmkPatternNoGM = /^Latexmk:\sapplying\srule/
@@ -36,7 +37,6 @@
     @lastFile = @latex.mainFile

   trimLatexmk: (log) ->
-    log = log.replace(/(.{78}(\w|\s|\d|\\|\/))(\r\n|\n)/g, '$1')
     lines = log.replace(/(\r\n)|\r/g, '\n').split('\n')
     finalLine = -1
     for index of lines
@@ -64,32 +64,51 @@
     return lines.slice(araraRunIdx.slice(-1)[0]).join('\n')

   parseLatex: (log) ->
-    log = log.replace(/(.{78}(\w|\s|\d|\\|\/))(\r\n|\n)/g, '$1')
-    lines = log.replace(/(\r\n)|\r/g, '\n').split('\n')
+    files = []
+    fileInds = []
+    i = 0
+    while i < log.length
+      if log[i] == "("
+        name = log.slice(i).match fileName
+        if name && log.match ///#{name[1]}\.aux///g
+          fileInds.push [files.length, 0]
+          files.push "("
+        else
+          if fileInds.length > 0
+            fileInds[fileInds.length - 1][1] += 1
+            files[fileInds[fileInds.length - 1][0]] += "("
+      else
+        if log[i] == ")"
+          if fileInds.length > 0
+            files[fileInds[fileInds.length - 1][0]] += ")"
+            if fileInds[fileInds.length - 1][1] > 0
+              fileInds[fileInds.length - 1][1] -= 1
+            else
+              fileInds.pop()
+        else
+          if fileInds.length > 0
+            files[fileInds[fileInds.length - 1][0]] += log[i]
+      i += 1
+
     items = []
-    for line in lines
-      file = line.match latexFile
-      if file
-        @lastFile = path.resolve(path.dirname(@latex.mainFile), file[1])
+    for file in files
+      @lastFile = path.resolve(path.dirname(@latex.mainFile), file.match(latexFile)[1])

-      result = line.match latexBox
-      if result
+      while result = latexBox.exec file
         items.push
           type: 'typesetting',
-          text: result[1]
+          text: result[1].replace /\s\s+/g, ' '
           file: @lastFile
-          line: parseInt(result[2], 10)
-        continue
-      result = line.match latexWarn
-      if result
+          line: parseInt result[2], 10
+
+      while result = latexWarn.exec file
         items.push
           type: 'warning',
-          text: result[3]
+          text: result[3].replace /\s\s+/g, ' '
           file: @lastFile
           line: parseInt result[4]
-        continue
-      result = line.match latexError
-      if result
+
+      while result = latexError.exec file
         items.push
           type: 'error',
           text: if result[3] and result[3] != 'LaTeX' then \
@@ -98,7 +117,6 @@
             path.resolve(path.dirname(@latex.mainFile), result[1]) else \
             @latex.mainFile
           line: if result[2] then parseInt result[2], 10 else undefined
-        continue

     types = items.map((item) -> item.type)
     if types.indexOf('error') > -1


Start style extraction after initialize()

Wrapping the initialize() function might not be the best solution but it works

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -192,17 +192,22 @@

       textEditorStyles = document.createElement("atom-styles")
       textEditorStyles.setAttribute "context", "atom-text-editor"
+
+      # Rewrap initialize so we know when it's finished
+      initialInitialize = textEditorStyles.initialize
+
+      textEditorStyles.initialize = ->
+
+        # Call original initialize method
+        initialInitialize.apply(this)
+
+        # Extract style afterward
+        resolve Array.prototype.slice.apply(textEditorStyles.childNodes).map (styleElement) ->
+          styleElement.innerText
+
+        textEditorStyles.remove()
+
       document.body.appendChild textEditorStyles
-
-      extractStyles = ->
-        styles = Array.prototype.slice.apply(textEditorStyles.childNodes).map (styleElement) ->
-          styleElement.innerText
-        textEditorStyles.remove()
-        styles
-
-      setTimeout ->
-        resolve(extractStyles())
-      , 1000

   getMarkdownPreviewCSS: () ->
     markdowPreviewRules = []


[Report] Refactored to use calculator instead of DB

--- lib/report.coffee
+++ lib/report.coffee
@@ -1,76 +1,125 @@
 Table = require 'cli-table'
 moment = require 'moment'
 _s = require 'underscore.string'
+Promise = require 'bluebird'

 class Report

-  constructor: (db) ->
-    @_db = db
+  constructor: (calculator) ->
+    @_calculator = calculator

+  #
+  # Example of data type:
+  #
+  #   records_by_dish_type = {
+  #     meat: [{
+  #       date: new Date()
+  #       count: 5
+  #       title: 'Куриный биток'
+  #     }, {
+  #       date: new Date()
+  #       count: 7
+  #       title: 'Свиной биток'
+  #     }, {
+  #       date: new Date()
+  #       count: 15
+  #       title: 'Котлета по-киевски'
+  #     }, {
+  #       date: new Date()
+  #       count: 15
+  #       title: 'Рыба, фаршированная порциями'
+  #     }, {
+  #       date: new Date()
+  #       count: 15
+  #       title: 'Мясо по-французски с ананасом'
+  #     }],
+  #
+  #     salad: [{
+  #       date: new Date()
+  #       count: 5
+  #       title: 'Салат из краснокочанной капусты'
+  #     }, {
+  #       date: new Date()
+  #       count: 7
+  #       title: 'Салат из свежей капусты со свежим огурцом'
+  #     }, {
+  #       date: new Date()
+  #       count: 15
+  #       title: 'Салат из соленых огурцов с репчатым луком'
+  #     }, {
+  #       date: new Date()
+  #       count: 15
+  #       title: 'Салат из квашеной капусты'
+  #     }, {
+  #       date: new Date()
+  #       count: 15
+  #       title: 'Салат из свежих огурцов и помидоров с ялтинским луком'
+  #     }]
+  #  }
+  # TODO: Handle reject promise
   records_for_week: (from_date, to_date) ->
-    table = new Table head: ['', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
+    new Promise (resolve, reject) =>

-    @_db.records_for_week 'meat', from_date, to_date
+      table = new Table head: ['', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']

-      .then (records) =>
-        table.push 'Meat': @_compose_values_for records, table.options.head
+      @_calculator.records_for_week from_date, to_date

-        return @_db.records_for_week 'garnish', from_date, to_date
-      .then (records) =>
-        table.push 'Garnish': @_compose_values_for records, table.options.head
+        .then (records_by_dish_type) =>

-        return @_db.records_for_week 'salad', from_date, to_date
-      .then (records) =>
-        table.push 'Salad': @_compose_values_for records, table.options.head
+          for dish_type, records of records_by_dish_type
+            row = {}
+            row[dish_type] = @_compose_values_using records, table.options.head
+            table.push row

-        return @_db.records_for_week 'main', from_date, to_date
-      .then (records) =>
-        table.push 'Main': @_compose_values_for records, table.options.head
-
-        return @_db.records_for_week 'meat', from_date, to_date, half: true
-      .then (records) =>
-        table.push 'Meat/2': @_compose_values_for records, table.options.head
-
-        return @_db.records_for_week 'garnish', from_date, to_date, half: true
-      .then (records) =>
-        table.push 'Garnish/2': @_compose_values_for records, table.options.head
-
-        return @_db.records_for_week 'salad', from_date, to_date, half: true
-      .then (records) =>
-        table.push 'Salad/2': @_compose_values_for records, table.options.head
-
-        return @_db.records_for_week 'main', from_date, to_date, half: true
-      .then (records) =>
-        table.push 'Main/2': @_compose_values_for records, table.options.head
-      .then ->
-        console.log 'Total recorded paying servings by dish type'
-        return console.log table.toString()
+        .then -> resolve table

   total_prime_cost: (from_date, to_date) ->
-    table = new Table head: ['Dish type', 'Count'];
+    @_total_by_dish_type from_date, to_date, prime_cost: true

-    @_db.total_prime_cost '2014-12-15', '2014-12-19', prime_cost: false
+  total_usual_cost: (from_date, to_date) ->
+    @_total_by_dish_type from_date, to_date, prime_cost: false

-      .then (records) =>
-        for record in records
-          table.push [record.type, record.count]
+  # TODO: Handle reject promise
+  _total_by_dish_type: (from_date, to_date, {prime_cost} = {}) ->
+    prime_cost ?= false

-        console.log 'Total paying servings'
-        console.log table.toString()
+    new Promise (resolve, reject) =>
+      table = new Table head: ['Dish type', 'Count'];

-  total_usual_price: (from_date, to_date) ->
-    table = new Table head: ['Dish type', 'Count'];
+      @_calculator.total_by_dish_type from_date, to_date, prime_cost: prime_cost

-    @_db.total_prime_cost '2014-12-15', '2014-12-19', prime_cost: true
+        .then (records) =>
+          for record in records
+            table.push [record.type, record.count]

-      .then (records) =>
-        for record in records
-          table.push [record.type, record.count]
+        .then -> resolve table

-        console.log 'Total prime cost servings'
-        console.log table.toString()
-
-  _compose_values_for: (records, table_head) ->
+  #
+  # Example of data type:
+  #
+  #   records = [{
+  #       date: new Date()
+  #       count: 5
+  #       title: 'Куриный биток'
+  #     }, {
+  #       date: new Date()
+  #       count: 7
+  #       title: 'Свиной биток'
+  #     }, {
+  #       date: new Date()
+  #       count: 15
+  #       title: 'Котлета по-киевски'
+  #     }, {
+  #       date: new Date()
+  #       count: 15
+  #       title: 'Рыба, фаршированная порциями'
+  #     }, {
+  #       date: new Date()
+  #       count: 15
+  #       title: 'Мясо по-французски с ананасом'
+  #     }
+  #   ]
+  _compose_values_using: (records, table_head) ->
     values = [0, 0, 0, 0, 0]

     for record in records
@@ -80,6 +129,4 @@

     values

-
-
 module.exports = Report


Now it's a class... also something about diamonds

--- src/processing.coffee
+++ src/processing.coffee
@@ -1,13 +1,16 @@
-Sketch = (canvas, descriptor) ->
-  if typeof canvas is 'function'
-    descriptor = canvas
-    canvas = null
+class Sketch
+  constructor: (canvas, descriptor) ->
+    if typeof canvas is 'function'
+      descriptor = canvas
+      canvas = null
+
+    if canvas is null or document.getElementById canvas is null
+      canvasElement = document.createElement 'canvas'
+      canvasElement.id = canvas
+      document.body.appendChild canvasElement
+    else
+      canvasElement = document.getElementById canvas

-  if canvas is null or document.getElementById canvas is null
-    canvasElement = document.createElement 'canvas'
-    canvasElement.id = canvas
-    document.body.appendChild canvasElement
-  else
-    canvasElement = document.getElementById canvas
-
-  processing = new Processing canvasElement, (p) -> descriptor.call p
+    @canvas = canvasElement
+
+    @processing = new Processing canvasElement, (p) -> descriptor.call p


* pass the dest, src, name arguments to assign smart set

--- src/properties.coffee
+++ src/properties.coffee
@@ -72,7 +72,7 @@
         defineProperty dest, nonExported1stChar+k, value
         ((name, assign, dest)->
           vAttr.get = ->@[name]
-          vAttr.set = (v)->@[name] = assign(v)
+          vAttr.set = (v)->@[name] = assign(v, @, @, name)
         )(nonExported1stChar+k, vAttr.assign)
       defineProperty dest, k, value, vAttr
   getRealAttrName: (name)->


fix stdout/err forwarding

fix stdout/err forwarding

--- spec/juno-spec.coffee
+++ spec/juno-spec.coffee
@@ -2,10 +2,14 @@

 # Testing-specific settings
 juno.misc.paths.jlpath = -> "julia"
-juno.connection.process.pipeConsole = true

 if process.platform is 'darwin'
   process.env.PATH += ':/usr/local/bin'
+
+client = juno.connection.client
+
+client.onStdout (s) -> console.log s
+client.onStderr (s) -> console.log s

 describe "the package", ->
   it "activates without errors", ->
@@ -15,7 +19,6 @@
       atom.packages.activatePackage 'julia-client'

 describe "managing the client", ->
-  client = juno.connection.client
   clientStatus = -> [client.isConnected(), client.isActive(), client.isWorking()]
   {echo, evalsimple} = client.import ['echo', 'evalsimple']
   client.onConnected (connectSpy = jasmine.createSpy 'connect')


:lipstick: Update specs to use ::function() format

--- spec/pane-spec.coffee
+++ spec/pane-spec.coffee
@@ -28,11 +28,11 @@
   afterEach ->
     atom.deserializers.remove(TestView)

-  describe ".initialize(items...)", ->
+  describe "::initialize(items...)", ->
     it "displays the first item in the pane", ->
       expect(pane.itemViews.find('#view-1')).toExist()

-  describe ".showItem(item)", ->
+  describe "::showItem(item)", ->
     it "hides all item views except the one being shown and sets the activeItem", ->
       expect(pane.activeItem).toBe view1
       pane.showItem(view2)
@@ -138,7 +138,7 @@
         expect(pane.itemViews.find('#view-2')).toExist()
         expect(pane.activeView).toBe view2

-  describe ".destroyItem(item)", ->
+  describe "::destroyItem(item)", ->
     describe "if the item is not modified", ->
       it "removes the item and tries to call destroy on it", ->
         pane.destroyItem(editor2)
@@ -196,7 +196,7 @@
           expect(pane.getItems().indexOf(editor2)).not.toBe -1
           expect(editor2.destroyed).toBeFalsy()

-  describe ".removeItem(item)", ->
+  describe "::removeItem(item)", ->
     it "removes the item from the items list and shows the next item if it was showing", ->
       pane.removeItem(view1)
       expect(pane.getItems()).toEqual [editor1, view2, editor2]
@@ -244,7 +244,7 @@
         pane.removeItem(editor1)
         expect(pane.itemViews.find('.editor')).not.toExist()

-  describe ".moveItem(item, index)", ->
+  describe "::moveItem(item, index)", ->
     it "moves the item to the given index and emits a 'pane:item-moved' event with the item and the new index", ->
       itemMovedHandler = jasmine.createSpy("itemMovedHandler")
       pane.on 'pane:item-moved', itemMovedHandler
@@ -267,7 +267,7 @@
       expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [editor1, 1]
       itemMovedHandler.reset()

-  describe ".moveItemToPane(item, pane, index)", ->
+  describe "::moveItemToPane(item, pane, index)", ->
     [pane2, view3] = []

     beforeEach ->
@@ -314,7 +314,7 @@
       expect(editor2.destroyed).toBeTruthy()
       expect(pane.getItems()).toEqual [editor1]

-  describe "saveActiveItem", ->
+  describe "::saveActiveItem()", ->
     describe "when the current item has a uri", ->
       describe "when the current item has a save method", ->
         it "saves the current item", ->
@@ -350,7 +350,7 @@
           pane.saveActiveItem()
           expect(atom.showSaveDialogSync).not.toHaveBeenCalled()

-  describe "saveActiveItemAs", ->
+  describe "::saveActiveItemAs()", ->
     beforeEach ->
       spyOn(atom, 'showSaveDialogSync').andReturn('/selected/path')

@@ -420,7 +420,7 @@
       waitsFor ->
         pane.items.length == 4

-  describe ".remove()", ->
+  describe "::remove()", ->
     it "destroys all the pane's items", ->
       pane.remove()
       expect(editor1.destroyed).toBeTruthy()
@@ -489,7 +489,7 @@
           pane.remove()
           expect(atom.workspaceView.focus).not.toHaveBeenCalled()

-  describe ".getNextPane()", ->
+  describe "::getNextPane()", ->
     it "returns the next pane if one exists, wrapping around from the last pane to the first", ->
       pane.showItem(editor1)
       expect(pane.getNextPane()).toBeUndefined
@@ -670,7 +670,7 @@
       expect(container.children('.pane').length).toBe 1
       expect(pane1.outerWidth()).toBe container.width()

-  describe ".itemForUri(uri)", ->
+  describe "::itemForUri(uri)", ->
     it "returns the item for which a call to .getUri() returns the given uri", ->
       expect(pane.itemForUri(editor1.getUri())).toBe editor1
       expect(pane.itemForUri(editor2.getUri())).toBe editor2


Kill legacy bindings which are broken anyway

--- lib/views/pagination_view.coffee
+++ lib/views/pagination_view.coffee
@@ -45,18 +45,18 @@
         info = @collection.information
         if info.totalPages > 1
             pages = for page in info.pageSet
-                p =
+                {
                     active: if page is info.currentPage then @options.pagination.pagination_active
                     number: page
-                p
+                }

             html = _.template @template,
                 pagination_class: @options.pagination.pagination_class
                 pagination_disabled: @options.pagination.pagination_disabled
                 pagination_active: @options.pagination.pagination_active
-                prev_disabled: info.previous is false or info.hasPrevious is false
-                next_disabled: info.next is false or info.hasNext is false
+                prev_disabled: info.previous is false
+                next_disabled: info.next is false
                 pages: pages

             @$el.html(html)
@@ -76,9 +76,9 @@

     gotoLast: (e) =>
         e.preventDefault()
-        @collection.goTo(this.collection.information.lastPage)
+        @collection.goTo(@collection.information.lastPage)

     gotoPage: (e) =>
         e.preventDefault()
         page = @$(e.target).text()
-        @collection.goTo(page)+        @collection.goTo(page)


don't try and get room name for dm or livechat

--- src/rocketchat.coffee
+++ src/rocketchat.coffee
@@ -164,10 +164,13 @@
 						user = @robot.brain.userForId newmsg.u._id, name: newmsg.u.username, alias: newmsg.alias

 						@chatdriver.checkMethodExists("getRoomNameById").then(() =>
-							return @chatdriver.getRoomName(newmsg.rid).then((roomName) =>
-								@robot.logger.info("setting roomName: #{roomName}")
-								user.room = roomName
-							)
+							if not isDM and not isLC
+								return @chatdriver.getRoomName(newmsg.rid).then((roomName) =>
+									@robot.logger.info("setting roomName: #{roomName}")
+									user.room = roomName
+								)
+							else
+								return Q()
 						).catch((err) =>
 							return Q()
 						).then(() =>


Workaround for electron browser window setResizable bug

--- lib/window.coffee
+++ lib/window.coffee
@@ -62,6 +62,7 @@
       windows[i] = null
   rememberMain: ->
     win = global.mainWindow
+    win.setResizable true # Remove after https://github.com/atom/electron/issues/4483 is fixed
     isFullScreen = win.isFullScreen()
     win.setFullScreen(false) if isFullScreen
     isMaximized = win.isMaximized() # This must be checked AFTER exiting full screen


rename notes to remark

rename notes to remark

--- library/membrane/skeleton.coffee
+++ library/membrane/skeleton.coffee
@@ -67,7 +67,7 @@
     @lazy -> @OPTIONS (method, service, kernel) ->
         @relevant "ts33kr.github.io/granite/membrane/skeleton.html"
         @github "ts33kr", "granite", "library/membrane/skeleton.coffee"
-        @notes "This method is default implemented for each service"
+        @remark "This method is default implemented for each service"
         @synopsis "Get a set of HTTP methods supported by service"
         @outputs "An array of supported methods, JSON or string"
         @markings framework: "critical", stable: "positive"


implement formular reset protocol

implement formular reset protocol

--- library/semantic/formular.coffee
+++ library/semantic/formular.coffee
@@ -84,6 +84,29 @@
         @container.appendTo(hosting); scoped @container
         @hosting = hosting; @reference = reference; @

+    # This method is part of the formular core protocol. It should
+    # be invoked once a formular is required to reset its state and
+    # all the values, that is making a formular prestine. The method
+    # is implemented within the terms of the upload and the download
+    # pieces of the protocol, as well as within the internal chnages.
+    prestine: (cleaners = new Array()) ->
+        cleaners.push (handle) -> handle.error = null
+        cleaners.push (handle) -> handle.value = null
+        cleaners.push (handle) -> handle.warning = null
+        cleaners.push (handle) -> handle.checked = null
+        @errors.empty() if _.isObject @errors or null
+        @warnings.empty() if _.isObject @warnings or 0
+        try this.container.removeClass "warning error"
+        transform = (x) -> _.each cleaners, (f) -> f(x)
+        assert _.isArray downloaded = try this.download()
+        assert fields = @container.find(".field") or []
+        _.each downloaded, transform; @upload downloaded
+        sieve = (seq) -> _.filter seq, (value) -> value
+        sieve _.map fields, (value, index, iteratee) =>
+            assert _.isObject value = $(value) or null
+            assert not value.find("input").val() or 0
+            try value.removeClass "warning error"
+
     # This method is intended for rendering error messages attached
     # to the fields. It traverses all of the fields and see if any
     # of the fields have warning metadata attached to it. If so, a
@@ -125,7 +148,7 @@
             return unless _.isPlainObject handle or 0
             return unless _.isString identity or null
             $(input).attr checked: handle.checked or 0
-            $(input).val try handle.value or undefined
+            $(input).val try handle.value or String()
             value.data "warning", handle.warning or 0
             value.data "error", handle.error; handle


auto pin mode

auto pin mode

--- grovepi.coffee
+++ grovepi.coffee
@@ -116,7 +116,7 @@

 	ranger: ( port, callback ) ->
 		@sendWithMode CMD.ranger, port, [0,0], =>
-			@receiveWithMode 3, (data) ->
+			@receiveWithMode port, 3, (data) ->

 			 	callback data[2] + data[1]


register profile

register profile

--- client/register.coffee
+++ client/register.coffee
@@ -14,6 +14,9 @@
   Accounts.createUser
     email: $('#email').val()
     password: $('#password').val()
+    profile:
+      name_first: $('#name_first').val()
+      name_last: $('#name_last').val()
   , (err) ->
     if err
       Session.set 'error', err.reason


avoid catch-rethrow to preserve exception stack traces. Close #146

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -767,6 +767,7 @@
     addWaiter = (listener) -> waiters = (waiters or []).concat([listener])
     @push = (event) =>
       if not pushing
+        success = false
         try
           pushing = true
           event.onDone = addWaiter
@@ -774,11 +775,11 @@
           for sink in tmpSinks
             reply = sink event
             removeSink sink if reply == Bacon.noMore or event.isEnd()
-        catch e
-          queue = null # ditch queue to allow recovery from exceptions
-          throw e
+          success = true
         finally
           pushing = false
+          queue = null if not success # ditch queue in case of exception to avoid unexpected behavior
+        success = true
         while queue?.length
           event = _.head(queue)
           queue = _.tail(queue)


Removed semicolon

Removed semicolon

--- src/blobs.coffee
+++ src/blobs.coffee
@@ -160,7 +160,7 @@
             out += "</hyperlinks>"
             return out
         else
-            return "";
+            return ""

     # Printed in xl/worksheets/_rels/sheet1.xml.rels
     externalWorksheetRels: (relationships) ->


emit 'included' event when include ends

emit 'included' event when include ends

--- src/lib/base-model.coffee
+++ src/lib/base-model.coffee
@@ -276,6 +276,8 @@
     ###
     include: (options = {}) ->

-        new Includer(@).include(options)
+        new Includer(@).include(options).then =>
+            @emit('included')
+            return @

 module.exports = BaseModel


Added geocoding service

Added geocoding service

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -134,8 +134,6 @@

        $("body").on "hidden", ".modal", ->
           $(this).removeData "modal"
-
-

       isValid: ->
         return true if !@field.isValid


CSS text size measuring bug fixed

CSS text size measuring bug fixed

--- impl/css/level0/text.coffee
+++ impl/css/level0/text.coffee
@@ -56,7 +56,7 @@
 				if item._impl.containsHTML
 					updateHTMLTextSize item
 				else
-					updatePlainTextSize item
+					updatePlainTextSize item
 			if implUtils.loadedFonts[name]
 				loadingTextsByFonts[name] = null
 		return
@@ -207,7 +207,7 @@
 	COLOR_RESOURCE_REQUEST =
 		property: 'color'

-	CONTAINS_HTML_RE = /<|&#x/
+	CONTAINS_HTML_RE = /<|&#/

 	DATA =
 		stylesheet: null


featuredactivities timing fixed

featuredactivities timing fixed

--- core/utils.coffee
+++ core/utils.coffee
@@ -32,9 +32,9 @@

   getUniqueId: do -> i = 0; -> "kd-#{i++}"

-  getRandomNumber :(range)->
-    range = range or 1000000
-    Math.floor Math.random()*range+1
+  getRandomNumber :(range=1e6, min=0)->
+    res = Math.floor Math.random()*range+1
+    return if res > min then res else res + min

   uniqueId : (prefix)->
     id = __utils.idCounter++


appManager 1st draft

appManager 1st draft

--- core/utils.coffee
+++ core/utils.coffee
@@ -306,48 +306,6 @@
   getYearOptions  : (min = 1900,max = Date::getFullYear())->
     ({ title : "#{i}", value : i} for i in [min..max])

-  getFileExtension: (path) ->
-    fileName = path or ''
-    [name, extension...]  = fileName.split '.'
-    extension = if extension.length is 0 then '' else extension[extension.length-1]
-
-  getFileType: (extension)->
-
-    fileType = "unknown"
-
-    _extension_sets =
-      code    : [
-        "php", "pl", "py", "jsp", "asp", "htm","html", "phtml","shtml"
-        "sh", "cgi", "htaccess","fcgi","wsgi","mvc","xml","sql","rhtml"
-        "js","json","coffee"
-        "css","styl","sass"
-      ]
-      text    : [
-        "txt", "doc", "rtf", "csv", "docx", "pdf"
-      ]
-      archive : [
-        "zip","gz","bz2","tar","7zip","rar","gzip","bzip2","arj","cab"
-        "chm","cpio","deb","dmg","hfs","iso","lzh","lzma","msi","nsis"
-        "rpm","udf","wim","xar","z","jar","ace","7z","uue"
-      ]
-      image   : [
-        "png","gif","jpg","jpeg","bmp","svg","psd","qt","qtif","qif"
-        "qti","tif","tiff","aif","aiff"
-      ]
-      video   : [
-        "avi","mp4","h264","mov","mpg","ra","ram","mpg","mpeg","m4a"
-        "3gp","wmv","flv","swf","wma","rm","rpm","rv"
-      ]
-      sound   : ["aac","au","gsm","mid","midi","snd","wav","3g2","mp3","asx","asf"]
-      app     : ["kdapp"]
-
-
-    for own type,set of _extension_sets
-      for ext in set
-        if extension is ext
-          fileType = type
-
-    return fileType

   _permissionMap: ->
     map =
@@ -370,12 +328,6 @@

   getNameFromFullname :(fullname)->
     fullname.split(' ')[0]
-
-  getParentPath :(path)->
-    path = path.substr(0, path.length-1) if path.substr(-1) is "/"
-    parentPath = path.split('/')
-    parentPath.pop()
-    return parentPath.join('/')

   removeBrokenSymlinksUnder:(path)->
     kiteController = KD.getSingleton('kiteController')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

-

+

     hslColor = Color('hsl(60, 0.5, 0.5)')

     hslColor.saturation()


Improve image resizing by cropping (better for small images) and centeting around faces, b/c it's just people...

--- lib/model/user.coffee
+++ lib/model/user.coffee
@@ -45,7 +45,7 @@
   photoUrl: (height) ->
     uploaded = @uploadedImage()
     if uploaded
-      Cdn.cdnify("#{uploaded}/convert?w=#{height}&h=#{height}")
+      Cdn.cdnify("#{uploaded}/convert?w=#{height}&h=#{height}&fit=crop&align=faces")
     else
       @photoUrlFromService(height)


declare 'relisten' from '@relisten'

declare 'relisten' from '@relisten'

--- lib/relistener.coffee
+++ lib/relistener.coffee
@@ -5,7 +5,8 @@

   # if they didn't specify a relisten function, then, we'll change listen()
   # to remember its args so we can reuse them.
-  unless @relisten?
+  relisten = @relisten
+  unless relisten?
     # remember the args in this var
     listenArgs = null


:lipstick: Shorten commands registration

--- lib/table-view.coffee
+++ lib/table-view.coffee
@@ -26,38 +26,38 @@
     @bodyComponent = React.renderComponent(TableComponent(props), @body[0])
     @headComponent = React.renderComponent(TableHeaderComponent(props), @head[0])

-    @subscriptions = new CompositeDisposable
-    @subscriptions.add @table.onDidChangeRows @requestUpdate
-    @subscriptions.add @table.onDidAddColumn @onColumnAdded
-    @subscriptions.add @table.onDidRemoveColumn @onColumnRemoved
-
-    @subscriptions.add @asDisposable @hiddenInput.on 'textInput', (e) =>
+    subs = @subscriptions = new CompositeDisposable
+    sub = (o,e,c) => subs.add @asDisposable o.on e, c
+
+    subs.add @table.onDidChangeRows @requestUpdate
+    subs.add @table.onDidAddColumn @onColumnAdded
+    subs.add @table.onDidRemoveColumn @onColumnRemoved
+
+    sub @hiddenInput, 'textInput', (e) =>
       unless @isEditing()
         @startEdit()
         @editView.setText(e.originalEvent.data)

-    @subscriptions.add @asDisposable @on 'core:confirm', => @startEdit()
-    @subscriptions.add @asDisposable @on 'core:undo', => @table.undo()
-    @subscriptions.add @asDisposable @on 'core:redo', => @table.redo()
-    @subscriptions.add @asDisposable @on 'core:move-left', => @moveLeft()
-    @subscriptions.add @asDisposable @on 'core:move-right', => @moveRight()
-    @subscriptions.add @asDisposable @on 'core:move-up', => @moveUp()
-    @subscriptions.add @asDisposable @on 'core:move-down', => @moveDown()
-    @subscriptions.add @asDisposable @on 'core:move-to-top', => @moveToTop()
-    @subscriptions.add @asDisposable @on 'core:move-to-bottom', => @moveToBottom()
-    @subscriptions.add @asDisposable @on 'core:page-up', => @pageUp()
-    @subscriptions.add @asDisposable @on 'core:page-down', => @pageDown()
-    @subscriptions.add @asDisposable @on 'core:select-right', => @expandSelectionRight()
-    @subscriptions.add @asDisposable @on 'core:select-left', => @expandSelectionLeft()
-    @subscriptions.add @asDisposable @on 'core:select-up', => @expandSelectionUp()
-    @subscriptions.add @asDisposable @on 'core:select-down', => @expandSelectionDown()
-    @subscriptions.add @asDisposable @on 'mousedown', (e) =>
-      e.preventDefault()
-      @focus()
-
-    @subscriptions.add @asDisposable @body.on 'scroll', @requestUpdate
-    @subscriptions.add @asDisposable @body.on 'dblclick', (e) => @startEdit()
-    @subscriptions.add @asDisposable @body.on 'mousedown', (e) =>
+    sub @, 'core:confirm', => @startEdit()
+    sub @, 'core:undo', => @table.undo()
+    sub @, 'core:redo', => @table.redo()
+    sub @, 'core:move-left', => @moveLeft()
+    sub @, 'core:move-right', => @moveRight()
+    sub @, 'core:move-up', => @moveUp()
+    sub @, 'core:move-down', => @moveDown()
+    sub @, 'core:move-to-top', => @moveToTop()
+    sub @, 'core:move-to-bottom', => @moveToBottom()
+    sub @, 'core:page-up', => @pageUp()
+    sub @, 'core:page-down', => @pageDown()
+    sub @, 'core:select-right', => @expandSelectionRight()
+    sub @, 'core:select-left', => @expandSelectionLeft()
+    sub @, 'core:select-up', => @expandSelectionUp()
+    sub @, 'core:select-down', => @expandSelectionDown()
+    sub @, 'mousedown', (e) => e.preventDefault(); @focus()
+
+    sub @body, 'scroll', @requestUpdate
+    sub @body, 'dblclick', (e) => @startEdit()
+    sub @body, 'mousedown', (e) =>
       e.preventDefault()

       @stopEdit() if @isEditing()
@@ -68,20 +68,20 @@
       @focus()

     @configUndefinedDisplay = atom.config.get('table-edit.undefinedDisplay')
-    @subscriptions.add @asDisposable atom.config.observe 'table-edit.undefinedDisplay', (@configUndefinedDisplay) =>
+    subs.add @asDisposable atom.config.observe 'table-edit.undefinedDisplay', (@configUndefinedDisplay) =>
       @requestUpdate()

     @configPageMovesAmount = atom.config.get('table-edit.pageMovesAmount')
-    @subscriptions.add @asDisposable atom.config.observe 'table-edit.pageMovesAmount', (@configPageMovesAmount) =>
+    subs.add @asDisposable atom.config.observe 'table-edit.pageMovesAmount', (@configPageMovesAmount) =>
       @requestUpdate()

     @configRowHeight = atom.config.get('table-edit.rowHeight')
-    @subscriptions.add @asDisposable atom.config.observe 'table-edit.rowHeight', (@configRowHeight) =>
+    subs.add @asDisposable atom.config.observe 'table-edit.rowHeight', (@configRowHeight) =>
       @computeRowOffsets()
       @requestUpdate()

     @configRowOverdraw = atom.config.get('table-edit.rowOverdraw')
-    @subscriptions.add @asDisposable atom.config.observe 'table-edit.rowOverdraw', (@configRowOverdraw) =>
+    subs.add @asDisposable atom.config.observe 'table-edit.rowOverdraw', (@configRowOverdraw) =>
       @requestUpdate()

     @setSelectionFromActiveCell()


Restore tag-finder.coffee changes

Restore tag-finder.coffee changes

Sorry about the mess with the rebase. Here's a summary of commit messages:

- fixed self-closing tag matching
    self-closing tags no longer get matched as opening tags
- fixed multi-line tag finding
    multi-line tags get matched properly again
- fixed an issue with go-to-matching-pair
    go-to-matching-pair was going to the closing tag when the cursor was on the text inside the tag
- simplify wordregex
- tags with / inside attribute
    fixed regex for tags like <foo bar=/></foo>

--- lib/tag-finder.coffee
+++ lib/tag-finder.coffee
@@ -8,14 +8,14 @@
 module.exports =
 class TagFinder
   constructor: (@editor) ->
-    @tagPattern = /(<(\/?))([^\s>]+)([\s>]|$)/
-    @wordRegex = /[^>\r\n]*/
+    @tagPattern = /(<(\/?))([^\/\s>]+)((?:[^\/>]|[^>](?!>))*)(\/?)(>)/
+    @wordRegex = /(?!$)[^>]*>/
     @tagSelector = SelectorCache.get('meta.tag | punctuation.definition.tag')
     @commentSelector = SelectorCache.get('comment.*')

   patternForTagName: (tagName) ->
     tagName = _.escapeRegExp(tagName)
-    new RegExp("(<#{tagName}([\\s>]|$))|(</#{tagName}>)", 'gi')
+    new RegExp("(<#{tagName}((?:[^\/>]|[^>](?!>))*)(>))|(<\/#{tagName}>)", 'gi')

   isRangeCommented: (range) ->
     scopes = @editor.scopeDescriptorForBufferPosition(range.start).getScopesArray()
@@ -39,7 +39,10 @@
       if match[1]
         unpairedCount--
         if unpairedCount < 0
-          startRange = range.translate([0, 1], [0, -match[2].length]) # Subtract < and tag name suffix from range
+          startRange = range.translate([0, 1], [0, -(match[2].length + match[3].length)]) # Subtract < and tag name suffix from range
+          until startRange.end.column > 0 # if the tag spans multiple lines, the end.column is negative, so iterate until it's not.
+            startRange.end.row -= 1 # move the end up one row
+            startRange.end.column = @editor.getBuffer().lineLengthForRow(startRange.end.row) + startRange.end.column + 2 # and set the column
           stop()
       else
         unpairedCount++
@@ -70,17 +73,20 @@
     @editor.backwardsScanInBufferRange @tagPattern, [[0, 0], endPosition], ({match, range, stop}) =>
       stop()

-      [entireMatch, prefix, isClosingTag, tagName, suffix] = match
+      [entireMatch, prefix, isClosingTag, tagName, attributes, isSelfClosingTag, suffix] = match

       if range.start.row is range.end.row
-        startRange = range.translate([0, prefix.length], [0, -suffix.length])
+        startRange = range.translate([0, prefix.length], [0, -(attributes.length + isSelfClosingTag.length + suffix.length)])
       else
-        startRange = Range.fromObject([range.start.translate([0, prefix.length]), [range.start.row, Infinity]])
+        startRange = Range.fromObject([range.start.translate([0, prefix.length]), [range.start.row, tagName.length+1]])

       if isClosingTag
         endRange = @findStartTag(tagName, startRange.start)
       else
         endRange = @findEndTag(tagName, startRange.end)
+
+      if isSelfClosingTag
+        endRange = startRange

       ranges = {startRange, endRange} if startRange? and endRange?
     ranges
@@ -88,6 +94,10 @@
   findEnclosingTags: ->
     if ranges = @findStartEndTags()
       if @isTagRange(ranges.startRange) and @isTagRange(ranges.endRange)
+        if ranges.startRange.start.row > ranges.endRange.start.row # if the endRange occurs after the startRange in the buffer, switch them
+          tempRange = ranges.startRange
+          ranges.startRange = ranges.endRange
+          ranges.endRange = tempRange
         return ranges

     null


Fixed bug displaying the correct command when using spawn.

--- lib/target_ssh.coffee
+++ lib/target_ssh.coffee
@@ -143,7 +143,7 @@

 	spawn: (command, args, failproof_or_handler) ->
 		task =
-			desc: command + (if args.length ? ' ' + args.join(' ') else '')
+			desc: command + args.join(' ').trim()
 			run: (callback) => @do_spawn command, args, helpers.create_exec_handler(callback, failproof_or_handler)

 		@step task


supervisor writeConfig, programsStatus

supervisor writeConfig, programsStatus

--- plugin/supervisor/supervisor.coffee
+++ plugin/supervisor/supervisor.coffee
@@ -1,3 +1,7 @@
+{child_process, fs, _} = app.libs
+
+supervisor_plugin = require './index'
+
 exports.removePrograms = (account, callback) ->

 exports.programSummary = (program) ->
@@ -5,9 +9,37 @@
 exports.updateProgram = (account, program, callback) ->

 exports.writeConfig = (account, program, callback) ->
+  program_name = "@#{account.username}-#{program.name}"
+
+  supervisor_plugin.renderTemplate 'program.conf',
+    name_prefix: '@'
+    account: account
+    program: program
+  , (configure) ->
+    supervisor_plugin.writeConfigFile "/etc/supervisor/conf.d/#{program_name}.conf", configure, ->
+      callback()

 exports.removeConfig = (account, program, callback) ->

 exports.programControl = (account, program, action, callback) ->

 exports.programsStatus = (callback) ->
+  child_process.exec 'sudo supervisor status', (err, stdout) ->
+    callback _.map stdout.split('\n'), (line) ->
+      [name, status] = line.split '\s'
+
+      status_mapping =
+        STOPPED: 'stopped'
+        STARTING: 'running'
+        RUNNING: 'running'
+        BACKOFF: 'stopped'
+        STOPPING: 'running'
+        EXITED: 'stopped'
+        FATAL: 'stopped'
+        UNKNOWN: 'stopped'
+
+      return {
+        name: name
+        original_status: status
+        status: status_mapping[status]
+      }


This guy was lonely, not he's not anymore

--- lib/y-richtext.coffee
+++ lib/y-richtext.coffee
@@ -53,7 +53,6 @@

     if noneFound
       @_setCursor @editor.getCursorPosition()
-    else

     delete @_characters
     delete @_selections


implement super-, subscript (only with HTML markup)


Automatically register handlers in the @events object

--- src/trix/input.coffee
+++ src/trix/input.coffee
@@ -1,5 +1,4 @@
 class Trix.Input
-  @events: "keydown keypress dragenter dragstart dragover dragend drop cut paste compositionstart compositionend input".split(" ")
   @keys:
     0x08: "backspace"
     0x0D: "return"
@@ -8,84 +7,85 @@
     0x4f: "o"

   constructor: (@element, @responder) ->
-    for event in @constructor.events
-      @element.addEventListener(event, @[event], true)
+    for event, handler of @events
+      @element.addEventListener(event, handler.bind(this), true)

-  keydown: (event) =>
-    if keyName = @constructor.keys[event.keyCode]
-      context = switch
-        when event.ctrlKey then @control
-        when event.altKey then @alt
-        else this
+  events:
+    keydown: (event) ->
+      if keyName = @constructor.keys[event.keyCode]
+        context = switch
+          when event.ctrlKey then @control
+          when event.altKey then @alt
+          else this

-      context[keyName]?.call this, event
+        context[keyName]?.call this, event

-  keypress: (event) =>
-    return if (event.metaKey or event.ctrlKey) and not event.altKey
+    keypress: (event) ->
+      return if (event.metaKey or event.ctrlKey) and not event.altKey

-    if event.which is null
-      character = String.fromCharCode event.keyCode
-    else if event.which isnt 0 and event.charCode isnt 0
-      character = String.fromCharCode event.charCode
+      if event.which is null
+        character = String.fromCharCode event.keyCode
+      else if event.which isnt 0 and event.charCode isnt 0
+        character = String.fromCharCode event.charCode

-    if character
-      @responder?.insertString(character)
+      if character
+        @responder?.insertString(character)
+        event.preventDefault()
+
+    dragenter: (event) ->
       event.preventDefault()

-  dragenter: (event) =>
-    event.preventDefault()
+    dragstart: (event) ->
+      target = event.target
+      if range = @responder?.getSelectedRange()
+        @draggedRange = range
+      else if Trix.DOM.within(@element, target) and target.trixPosition?
+        position = target.trixPosition
+        @draggedRange = [position, position + 1]

-  dragstart: (event) =>
-    target = event.target
-    if range = @responder?.getSelectedRange()
-      @draggedRange = range
-    else if Trix.DOM.within(@element, target) and target.trixPosition?
-      position = target.trixPosition
-      @draggedRange = [position, position + 1]
+    dragover: (event) ->
+      event.preventDefault() unless @draggedRange

-  dragover: (event) =>
-    event.preventDefault() unless @draggedRange
-
-  dragend: (event) =>
-    delete @draggedRange
-
-  drop: (event) =>
-    event.preventDefault()
-    point = [event.pageX, event.pageY]
-    @responder?.positionAtPoint(point)
-
-    if @draggedRange
-      @responder?.moveTextFromRange(@draggedRange)
+    dragend: (event) ->
       delete @draggedRange

-    else if id = event.dataTransfer.getData("id")
-      element = document.getElementById(id)
-      attachment = { type: "image" }
-      attachment[key] = element[key] for key in ["src", "width", "height"]
-      @responder?.insertAttachment(attachment)
+    drop: (event) ->
+      event.preventDefault()
+      point = [event.pageX, event.pageY]
+      @responder?.positionAtPoint(point)

-  cut: (event) =>
-    @responder?.deleteBackward()
+      if @draggedRange
+        @responder?.moveTextFromRange(@draggedRange)
+        delete @draggedRange

-  paste: (event) =>
-    if text = event.clipboardData.getData("text/plain")
-      @responder?.insertString(text)
-    event.preventDefault()
+      else if id = event.dataTransfer.getData("id")
+        element = document.getElementById(id)
+        attachment = { type: "image" }
+        attachment[key] = element[key] for key in ["src", "width", "height"]
+        @responder?.insertAttachment(attachment)

-  compositionstart: (event) =>
-    @responder?.beginComposing()
+    cut: (event) ->
+      @responder?.deleteBackward()

-  compositionend: (event) =>
-    @composedString = event.data
+    paste: (event) ->
+      if text = event.clipboardData.getData("text/plain")
+        @responder?.insertString(text)
+      event.preventDefault()

-  input: (event) =>
-    if @responder?.isComposing()
-      if @composedString?
-        @responder?.endComposing(@composedString)
-        delete @composedString
-    else
-      @responder?.render()
-      @logAndCancel(event)
+    compositionstart: (event) ->
+      @responder?.beginComposing()
+
+    compositionend: (event) ->
+      @composedString = event.data
+
+    input: (event) ->
+      if @responder?.isComposing()
+        if @composedString?
+          @responder?.endComposing(@composedString)
+          delete @composedString
+      else
+        @responder?.render()
+        @logAndCancel(event)

   backspace: (event) ->
     @responder?.deleteBackward()


Simplified constructor in Elyssa.Rect

Simplified constructor in Elyssa.Rect

--- src/types/rect.coffee
+++ src/types/rect.coffee
@@ -7,7 +7,7 @@
       w: 0
       h: 0

-    constructor: ({@x, @y, @w, @h} = {x: defaultValue.x, y: defaultValue.y, w: defaultValue.w, h: defaultValue.h}) ->
+    constructor: ({@x, @y, @w, @h} = defaultValue) ->
       @x = defaultValue.x unless @x?
       @y = defaultValue.y unless @y?
       @w = defaultValue.w unless @w?


work it on the browser too

work it on the browser too


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -30,7 +30,9 @@
   enemy.flicker
     alpha: 0.4

-
+  equals player.I.flickerAlpha, 0.4
+  equals player.I.flickerFrequency, 3
+  equals player.I.flickerDuration, 30

 test 'it should change the alpha value when the flicker duration has elapsed', ->
   player = GameObject


[test] RedisModel##jobsByQueue

[test] RedisModel##jobsByQueue

--- test/redis-model.coffee
+++ test/redis-model.coffee
@@ -74,6 +74,60 @@
           expect(jobWithData[property]).to.be.a typeof value

       qCleaner(queue).asCallback done
+
+  describe "##jobsByQueue", ->
+    it "should return from all queues all jobs", (done) ->
+      ideally = errify done
+      data    = {name: "testjob", foo: "bar"}
+      state   = "active"
+
+      await fakeJob queuename, data, state, ideally defer {queue, job}
+      await fakeJob "test2",   data, state, ideally defer queueAndJob2
+
+      await instance.jobsByQueue null, ideally defer jobs
+      jobInterface =
+        id:         0
+        progress:   0
+        queue:      "name"
+
+      expect(jobs.length).to.equal 2
+      for jobWithData in jobs
+        expect(jobWithData.state).to.equal state
+        expect(jobWithData.data).to.deep.equal data
+        for property, value of jobInterface
+          expect(jobWithData[property]).to.be.a typeof value
+
+      qCleaner queue
+        .then -> qCleaner queueAndJob2.queue
+        .asCallback done
+
+    it "should return from a specific queue all jobs", (done) ->
+      ideally = errify done
+      data    = {name: "testjob", foo: "bar"}
+      state   = "active"
+
+      await fakeJob queuename, data, state, ideally defer {queue, job}
+      await fakeJob queuename, data, state, ideally defer _
+      await fakeJob "test2",   data, state, ideally defer queueAndJob2
+
+      await instance.jobsByQueue queuename, ideally defer jobs
+      jobInterface =
+        id:         0
+        progress:   0
+        queue:      "name"
+
+      expect(jobs.length).to.equal 2
+      for jobWithData in jobs
+        expect(jobWithData.queue).to.equal queuename
+        expect(jobWithData.state).to.equal state
+        expect(jobWithData.data).to.deep.equal data
+        for property, value of jobInterface
+          expect(jobWithData[property]).to.be.a typeof value
+
+      qCleaner queue
+        .then -> qCleaner queueAndJob2.queue
+        .asCallback done
+

   describe "##idsAndCountByState", ->
     queue     = null


Adds initial unit test for GET /objekttyper

--- test/server-spec.coffee
+++ test/server-spec.coffee
@@ -27,6 +27,16 @@
       .get('/?api_key=dnt')
       .expect(200, done)

+describe.skip '/objekttyper', ->
+  it 'should return a list of the different types', (done) ->
+    request(app)
+      .get('/objekttyper?api_key=dnt')
+      .expect(200)
+      .end (err, res) ->
+        throw err if err
+        console.log res.body
+        done()
+
 describe.skip '/turer', ->
   describe 'HTTP GET', ->
     it 'should return objects', (done) ->


Shortcut for assert.equal

Shortcut for assert.equal

--- test/xml2js.test.coffee
+++ test/xml2js.test.coffee
@@ -40,23 +40,26 @@
     throw new xml2js.ValidationError("Validation error!")
   return newValue

+# shortcut, because it is quite verbose
+equ = assert.equal
+
 module.exports =
   'test parse with defaults': skeleton(undefined, (r) ->
     console.log 'Result object: ' + util.inspect(r, false, 10)
-    assert.equal r.sample.chartest[0].$.desc, 'Test for CHARs'
-    assert.equal r.sample.chartest[0]._, 'Character data here!'
-    assert.equal r.sample.cdatatest[0].$.desc, 'Test for CDATA'
-    assert.equal r.sample.cdatatest[0].$.misc, 'true'
-    assert.equal r.sample.cdatatest[0]._, 'CDATA here!'
-    assert.equal r.sample.nochartest[0].$.desc, 'No data'
-    assert.equal r.sample.nochartest[0].$.misc, 'false'
-    assert.equal r.sample.listtest[0].item[0]._, '\n            This  is\n            \n            character\n            \n            data!\n            \n        '
-    assert.equal r.sample.listtest[0].item[0].subitem[0], 'Foo(1)'
-    assert.equal r.sample.listtest[0].item[0].subitem[1], 'Foo(2)'
-    assert.equal r.sample.listtest[0].item[0].subitem[2], 'Foo(3)'
-    assert.equal r.sample.listtest[0].item[0].subitem[3], 'Foo(4)'
-    assert.equal r.sample.listtest[0].item[1], 'Qux.'
-    assert.equal r.sample.listtest[0].item[2], 'Quux.')
+    equ r.sample.chartest[0].$.desc, 'Test for CHARs'
+    equ r.sample.chartest[0]._, 'Character data here!'
+    equ r.sample.cdatatest[0].$.desc, 'Test for CDATA'
+    equ r.sample.cdatatest[0].$.misc, 'true'
+    equ r.sample.cdatatest[0]._, 'CDATA here!'
+    equ r.sample.nochartest[0].$.desc, 'No data'
+    equ r.sample.nochartest[0].$.misc, 'false'
+    equ r.sample.listtest[0].item[0]._, '\n            This  is\n            \n            character\n            \n            data!\n            \n        '
+    equ r.sample.listtest[0].item[0].subitem[0], 'Foo(1)'
+    equ r.sample.listtest[0].item[0].subitem[1], 'Foo(2)'
+    equ r.sample.listtest[0].item[0].subitem[2], 'Foo(3)'
+    equ r.sample.listtest[0].item[0].subitem[3], 'Foo(4)'
+    equ r.sample.listtest[0].item[1], 'Qux.'
+    equ r.sample.listtest[0].item[2], 'Quux.')

   'test parse with explicitCharkey': skeleton(explicitCharkey: true, (r) ->
     assert.equal r['chartest']['@']['desc'], 'Test for CHARs'


switch to browserify

switch to browserify

--- matrixSpec.coffee
+++ matrixSpec.coffee
@@ -1,60 +1,60 @@
-require ['math/matrix'], (m) ->
+m = require 'math/matrix'

-  describe 'Matrix module', ->
+describe 'Matrix module', ->

-    describe 'map() function', ->
+  describe 'map() function', ->

-      it 'should be able to map a matrix in place', ->
-        A = [[1, 2], [3, 4], [5, 6]]
-        B = m.map ((x) -> x*x), A
-        expect(B).toBe A # in place ?
-        expect(B).toEqual [[1, 4], [9, 16], [25, 36]] # adapted correctly ?
+    it 'should be able to map a matrix in place', ->
+      A = [[1, 2], [3, 4], [5, 6]]
+      B = m.map ((x) -> x*x), A
+      expect(B).toBe A # in place ?
+      expect(B).toEqual [[1, 4], [9, 16], [25, 36]] # adapted correctly ?

-      it 'should be able to map to another matrix', ->
-        A = [[1, 2], [3, 4], [5, 6]]
-        B = [[], [], []]
-        C = m.map ((x) -> x-1), A, B
-        expect(C).toBe B # B returned ?
-        expect(A).toEqual [[1, 2], [3, 4], [5, 6]] # A not changed ?
-        expect(B).toEqual [[0, 1], [2, 3], [4, 5]] # B adapted correctly ?
+    it 'should be able to map to another matrix', ->
+      A = [[1, 2], [3, 4], [5, 6]]
+      B = [[], [], []]
+      C = m.map ((x) -> x-1), A, B
+      expect(C).toBe B # B returned ?
+      expect(A).toEqual [[1, 2], [3, 4], [5, 6]] # A not changed ?
+      expect(B).toEqual [[0, 1], [2, 3], [4, 5]] # B adapted correctly ?

-    describe 'times() function', ->
+  describe 'times() function', ->

-      it 'should be able to multiply a matrix in place', ->
-        A = [[1, 2]]
-        B = m.times(3, A)
-        expect(B).toBe A # in place ?
-        expect(B).toEqual [[3, 6]] # adapted correctly ?
+    it 'should be able to multiply a matrix in place', ->
+      A = [[1, 2]]
+      B = m.times(3, A)
+      expect(B).toBe A # in place ?
+      expect(B).toEqual [[3, 6]] # adapted correctly ?

-      it 'should be able to multiply to another matrix', ->
-        A = [[1, 2]]
-        B = [[]]
-        C = m.times(3, A, B)
-        expect(C).toBe B # B returned ?
-        expect(A).toEqual [[1, 2]] # A not changed ?
-        expect(B).toEqual [[3, 6]] # B adapted correctly ?
+    it 'should be able to multiply to another matrix', ->
+      A = [[1, 2]]
+      B = [[]]
+      C = m.times(3, A, B)
+      expect(C).toBe B # B returned ?
+      expect(A).toEqual [[1, 2]] # A not changed ?
+      expect(B).toEqual [[3, 6]] # B adapted correctly ?

-    describe 'height() function', ->
+  describe 'height() function', ->

-      it 'should give correct height for an empty matrix', ->
-        expect(m.height([])).toEqual 0
+    it 'should give correct height for an empty matrix', ->
+      expect(m.height([])).toEqual 0

-      it 'should fail for empty 2 dim matrix', ->
-        expect(-> m.height([[]])).toThrow()
+    it 'should fail for empty 2 dim matrix', ->
+      expect(-> m.height([[]])).toThrow()

-      it 'should give correct height for a rectangular matrix', ->
-        expect(m.height([[1, 2], [3, 4], [5, 6]])).toEqual 2
+    it 'should give correct height for a rectangular matrix', ->
+      expect(m.height([[1, 2], [3, 4], [5, 6]])).toEqual 2

-    describe 'width() function', ->
+  describe 'width() function', ->

-      it 'should give correct width for an empty matrix', ->
-        expect(m.width([])).toEqual 0
+    it 'should give correct width for an empty matrix', ->
+      expect(m.width([])).toEqual 0

-      it 'should fail for empty 2 dim matrix', ->
-        expect(-> m.width([[]])).toThrow()
+    it 'should fail for empty 2 dim matrix', ->
+      expect(-> m.width([[]])).toThrow()

-      it 'should give correct width for a rectangular matrix', ->
-        expect(m.width([[1, 2], [3, 4], [5, 6]])).toEqual 3
+    it 'should give correct width for a rectangular matrix', ->
+      expect(m.width([[1, 2], [3, 4], [5, 6]])).toEqual 3


JDomain: parseDomain: remove shared|vm prefix check

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -144,8 +144,8 @@
     return err()  unless ///([a-z0-9\-]+)\.#{userSitesDomain}$///.test domain

     # Check for shared|vm prefix
-    if /^shared|vm[\-]?([0-9]+)?/.test prefix
-      return err "Domain name cannot start with shared|vm"
+    # if /^shared|vm[\-]?([0-9]+)?/.test prefix
+    #   return err "Domain name cannot start with shared|vm"

     # Parse domain
     match = domain.match ///([a-z0-9\-]+)\.([a-z0-9\-]+)\.#{userSitesDomain}$///


JDomain: createDomain: move group/nickname checks to parseDomain

use it in both client and backend version of createDomain. So, domains provided
from JStackTemplates will also be parsed before creating.

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -124,7 +124,7 @@
       callback null, filterDomains domains, delegate, group

-  parseDomain = (domain)->
+  parseDomain = (domain, { nickname, group })->

     { userSitesDomain } = KONFIG

@@ -152,8 +152,15 @@
     return err "Invalid domain: #{domain}"  unless match
     [rest..., prefix, slug] = match

+    if slug isnt nickname
+      return err "Creating root domains is not allowed"
+
+    slug = "koding"  if nickname is slug
+    if group? and group isnt slug
+      return err "Invalid group"
+
     # Return type as internal and slug and domain
-    return {type: 'internal', slug, prefix, domain}
+    return { type: 'internal', slug, prefix, domain }

   resolveDomain = (domain, callback, check)->
@@ -185,6 +192,11 @@

     { domain, account, group, stack, hostnameAlias } = options

+    { nickname } = account.profile
+
+    {err, domain, type, slug, prefix} = parseDomain domain, { nickname, group }
+    return callback err  if err
+
     domainData = { proposedDomain: domain, group }
     domainData.hostnameAlias = hostnameAlias  if hostnameAlias?

@@ -214,21 +226,11 @@
       return error "Domain is not provided"

     {delegate} = client.connection
-    {err, domain, type, slug, prefix} = parseDomain domain
-
-    return callback err  if err
-
     {group}    = client.context
     {nickname} = delegate.profile

-    if type is 'internal'
-
-      if slug isnt nickname
-        return error "Creating root domains is not allowed"
-
-      slug = "koding"  if nickname is slug
-      unless group is slug
-        return error "Invalid group"
+    {err, domain, type, slug, prefix} = parseDomain domain, { nickname, group }
+    return callback err  if err

     JDomain.one {domain}, (err, model)->
       return callback err  if err


Revert "remove spinner"

Revert "remove spinner"

This reverts commit 231246d734a2c1a839fa29e25b6e1a8cecb3917c.

--- app/assets/javascripts/dashboard.js.coffee
+++ app/assets/javascripts/dashboard.js.coffee
@@ -17,6 +17,15 @@
   $(document).on "page:change", ->
     $("form[data-validate]").validate()

+  $(document).on("page:fetch", ->
+    AlertMessage.show
+      type: 'loading',
+      text: I18n.t('loading')
+      keep: true
+      scope: 'ajax-loading'
+  ).on "page:change", ->
+    AlertMessage.remove('ajax-loading')
+
   $(document).on("ajax:before", ->
     AlertMessage.show
       type: 'loading'


Remove unneeded js because we don't have selects for divisions anymore

--- app/assets/javascripts/divisions.js.coffee
+++ app/assets/javascripts/divisions.js.coffee
@@ -3,12 +3,5 @@
 # You can use CoffeeScript in this file: http://coffeescript.org/

 $ ->
-  $('select#r').change ->
-    window.location = $('select#r').val()
-  $('select#r2').change ->
-    window.location = $('select#r2').val()
-  $('select#r3').change ->
-    window.location = $('select#r3').val()
-
   $("form.edit_division a#preview_link").on "shown.bs.tab", (e) ->
     $("#preview").html(marked($("#edit textarea").val()))


OX-120 little fix

OX-120 little fix

--- app/assets/javascripts/model/binary.coffee
+++ app/assets/javascripts/model/binary.coffee
@@ -210,7 +210,7 @@
         index--
         if buckets[index]
           # priority = Math.max(Math.abs(buckets[index][0] - @positionBucket[0]), Math.abs(buckets[index][1] - @positionBucket[1]), Math.abs(buckets[index][2] - @positionBucket3[2]))
-          PullQueue.insert [buckets[index][0] + direction_x, buckets[index][1] + direction_y, buckets[index][2] + direction_z], zoomSteps[0], @PRIORITIES8[index % @PRIORITIES.length] + @PRELOADING[level]
+          PullQueue.insert [buckets[index][0] + direction_x, buckets[index][1] + direction_y, buckets[index][2] + direction_z], zoomSteps[0], @PRIORITIES8[index % @PRIORITIES8.length] + @PRELOADING[level]

         unless i % buckets.length
           index = buckets.length


Remove commented out code

Remove commented out code

--- app/assets/javascripts/organization.coffee
+++ app/assets/javascripts/organization.coffee
@@ -18,8 +18,3 @@
   count = $("[id^='organization_addresses_attributes']:text").length
   addAddressRow(count)

-# $(document).on "click", ".delete-row", (event) ->
-#   event.preventDefault()
-#   $(@).parents("tr:first").remove()
-#   addOrderRow() if $("#order-table tbody tr").length == 0
-


Scroll jump fix.

Scroll jump fix.

--- app/assets/javascripts/web/_site.js.coffee
+++ app/assets/javascripts/web/_site.js.coffee
@@ -429,11 +429,11 @@
     if animate
       timeout = 200
       feedbin.panelScrollComplete = false
-      $(containerClass).css {'scroll-snap-type': 'unset'}
+      $(containerClass).css {'scroll-snap-type': 'unset'} if feedbin.smoothScroll
       $(containerClass).animate({scrollLeft: offset}, {duration: timeout})
       setTimeout ( ->
         feedbin.panelScrollComplete = true
-        $(containerClass).css {'scroll-snap-type': 'x mandatory'}
+        $(containerClass).css {'scroll-snap-type': 'x mandatory'} if feedbin.smoothScroll
       ), timeout
     else
       $(containerClass).prop 'scrollLeft', offset


displayData override

displayData override

--- app/assets/javascripts/zvg/verbatim.coffee
+++ app/assets/javascripts/zvg/verbatim.coffee
@@ -9,7 +9,7 @@

   render: (options = {}, callback) ->
     dataFilter = @constructDataFilter(options)
-    filtered = dataFilter(@_data)
+    filtered = dataFilter(@displayData())
     @paginate(options.page, filtered.length)
     rows = @question_table.selectAll('tr')
       .data(filtered.slice((@_page - 1) * @_perPage, @_perPage * @_page))
@@ -40,6 +40,13 @@

     tag_spans.text((d) -> d)
     @renderCallback(@)
+
+  renderTags: (container, tag_list) ->
+    tag_spans = tags.selectAll('span.tag').data((d) -> d.tags or [])
+    tag_spans.remove()
+
+  displayData: ->
+    @_data

   selectAllResponses: ->
     @question_table.selectAll('tr.response').classed('selected', true)


Remove unnecesary console.log

Remove unnecesary console.log

--- app/coffee/modules/common/analytics.coffee
+++ app/coffee/modules/common/analytics.coffee
@@ -86,7 +86,6 @@
         })

     trackEvent: (category, action, label, value) ->
-        console.log("TRACK EVENT: ", category, action, label, value)
         return if not @.initialized
         return if not @win.ga


adjust userpilot ID condition

adjust userpilot ID condition

--- app/coffee/modules/common/userpilot.coffee
+++ app/coffee/modules/common/userpilot.coffee
@@ -42,28 +42,30 @@
                 @.identified = true
                 id = @.getUserPilotId(data)
                 timestamp = Date.now()
+                userpilot_data = {
+                    name: data["full_name_display"], # Full name
+                    email: data["email"], # Email address
+                    created_at: timestamp, # Signup date as a Unix timestamp
+                    # Additional user properties
+                    taiga_id: parseInt(data["id"], 10),
+                    taiga_username: data["username"],
+                    taiga_date_joined: data["date_joined"],
+                    taiga_lang: data["lang"],
+                    taiga_max_private_projects: parseInt(data["max_private_projects"], 10),
+                    taiga_max_memberships_private_projects: parseInt(data["max_memberships_private_projects"], 10),
+                    taiga_verified_email: data["verified_email"],
+                    taiga_total_private_projects: parseInt(data["total_private_projects"], 10),
+                    taiga_total_public_projects: parseInt(data["total_public_projects"], 10),
+                    taiga_roles: data["roles"].toString()
+                }
+
                 @win.userpilot.identify(
                     id, # Used to identify users
-                    {
-                        name: data["full_name_display"], # Full name
-                        email: data["email"], # Email address
-                        created_at: timestamp, # Signup date as a Unix timestamp
-                        # Additional user properties
-                        taiga_id: id,
-                        taiga_username: data["username"],
-                        taiga_date_joined: data["date_joined"],
-                        taiga_lang: data["lang"],
-                        taiga_max_private_projects: parseInt(data["max_private_projects"], 10),
-                        taiga_max_memberships_private_projects: parseInt(data["max_memberships_private_projects"], 10),
-                        taiga_verified_email: data["verified_email"],
-                        taiga_total_private_projects: parseInt(data["total_private_projects"], 10),
-                        taiga_total_public_projects: parseInt(data["total_public_projects"], 10),
-                        taiga_roles: data["roles"].toString()
-                    }
+                    userpilot_data
                 )

     getUserPilotId: (data) ->
         maxPrivateProjects = parseInt(data["max_private_projects"], 10)
-        if (maxPrivateProjects && maxPrivateProjects > 1) then return parseInt(data["id"], 10) else 1
+        if (maxPrivateProjects == 1) then return 1 else parseInt(data["id"], 10)

 module.service("$tgUserPilot", UserPilotService)


binary decision on outcome

binary decision on outcome

--- src/circle-ci.coffee
+++ src/circle-ci.coffee
@@ -62,7 +62,7 @@
         for project in response
           build_branch = project.branches[project.default_branch]
           last_build = build_branch.recent_builds[0]
-          if last_build.status is status
+          if last_build.outcome is status
             projects.push project
         if action is 'list'
           listProjectsByStatus(msg, projects, status)
@@ -84,7 +84,7 @@
       for project in projects
         build_branch = project.branches[project.default_branch]
         last_build = build_branch.recent_builds[0]
-        msg.send "#{toDisplay(last_build.status)} in build https://circleci.com/gh/#{project.username}/#{project.reponame}/#{last_build.build_num} of #{project.vcs_url} [#{project.default_branch}]"
+        msg.send "#{toDisplay(last_build.outcome)} in build https://circleci.com/gh/#{project.username}/#{project.reponame}/#{last_build.build_num} of #{project.vcs_url} [#{project.default_branch}]"

 checkToken = (msg) ->
   unless process.env.HUBOT_CIRCLECI_TOKEN?


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -1,5 +1,5 @@
 ###*
-The `Clampable` module provides helper methods to clamp object properties. This module is included by default in `GameObject
+The `Clampable` module provides helper methods to clamp object properties. This module is included by default in `GameObject`

     player = GameObject
       x: 40


Add Open Your Keymap item to View menu

Add Open Your Keymap item to View menu

--- menus/darwin.cson
+++ menus/darwin.cson
@@ -125,6 +125,7 @@
       { label: 'Reload', command: 'window:reload' }
       { label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
       { label: 'Open Your Stylesheet', command: 'window:open-your-stylesheet' }
+      { label: 'Open Your Keymap', command: 'window:open-your-keymap' }
       {
         label: 'Developer'
         submenu: [


CSS Fixes

CSS Fixes

Hover effects!

--- jeffreel/views/index.coffee
+++ jeffreel/views/index.coffee
@@ -11,7 +11,8 @@
 			a {color: #D18026; text-decoration: none}
 			h1 {font-size: 48px; text-align: center; color: #E0A666}
 			#button {width: 600px; margin: 0 auto}
-			#broadcast {background-color: #34414C; width: 600px; margin: 0 auto 10px auto; padding: 0 0 18px 4px}
+			#broadcast {transition: all .3s ease; background-color: #34414C; width: 600px; margin-bottom: 10px; padding: 0 0 18px 4px}
+			#broadcast:hover {background-color: #536779}
 			#title {font-size: 22px; margin-bottom: 0px}
 			#date {display: inline-box; float: left}
 			#duration {display: inline-box; float: right; padding: 0 4px 0 0}


bongo-client: use existential operator

bongo-client: use existential operator

--- node_modules_koding/bongo-client/src/model.coffee
+++ node_modules_koding/bongo-client/src/model.coffee
@@ -88,7 +88,7 @@

   getSubscribable:->
     subscribable = @bongo_.subscribable
-    return true  if typeof subscribable is "undefined"
+    return true  if subscribable?
     return subscribable

   equals:(model)->


allow context objects within batches

allow context objects within batches

--- src/vows/index.coffee
+++ src/vows/index.coffee
@@ -89,15 +89,21 @@
                 return @end('done') if not @content.length

                 # run each item synchronously
-                children = @content.slice()
-                next = new vows.Context(null, children.pop(), this)
-                next.on 'end', () => @end('done')
-
-                while children.length
-                    next = new vows.Context(null, children.pop(), this)
-                               .on 'end', do (next) -> () -> next.run(topics)
-
-                next.run(@topics)
+                batch = @content.slice()
+                while batch.length
+                    cur = batch.pop()
+                    if cur not instanceof vows.Context
+                        cur = new vows.Context(null, cur, this)
+
+                    if next?
+                        cur.on 'end', do (next) -> () -> next.run(topics)
+                    else
+                        # base case: end after the last child context ends
+                        cur.on 'end', () => @end('done')
+
+                    next = cur
+
+                cur.run(@topics)

             when 'group'
                 return @end('end') if not (key for key of @content).length
@@ -124,7 +130,9 @@
                             @on 'run', () =>
                                 context = this
                                 parts = [@description]
-                                parts.unshift(context.description) while (context = context.parent) and context.description
+                                while (context = context.parent) and context.description
+                                    parts.unshift(context.description)
+
                                 @report(['context', parts.join(' ')])

                         @on 'topic', () =>
@@ -137,11 +145,12 @@
                             if child.type == 'test' and @_expectsError(child.content)
                                 child.run(arguments)
                             else
-                                # force the child to error
+                                # unexpected error
                                 child.exception = e
                                 child.end('errored')

                         child.on 'end', (result) =>
+                            # end if this was the last test of the group
                             @end('done') if not --@results.running

                 # teardown