fix window some detection for IE

fix window some detection for IE

--- app/assets/javascripts/gricer.js.coffee
+++ app/assets/javascripts/gricer.js.coffee
@@ -63,12 +63,13 @@
     if typeof( window.innerWidth ) == 'number'
       # Non-IE
       return {width: window.innerWidth, height: window.innerHeight}
-    else if _ref = document.documentElement and ( _ref.clientWidth or _ref.clientHeight )
+    else if _ref = document.documentElement?.clientWidth
       # IE 6+ in 'standards compliant mode'
       return {width: document.documentElement.clientWidth, height: document.documentElement.clientHeight}
-    else if _ref = document.body and ( _ref.clientWidth or _ref.clientHeight )
+    else if _ref = document.body?.clientWidth
       # IE 4 compatible
       return {width: document.body.clientWidth, height: document.body.clientHeight}
+
   prepareValues: ->
     windowsize = Gricer.getWindowSize()


Fix loading of image modal on turbolinks change.

--- app/assets/javascripts/images.js.coffee
+++ app/assets/javascripts/images.js.coffee
@@ -1,3 +1,6 @@
+$imagesContainer = $('#images')
+$modalContainer = $('#gallery-modal')
+
 window.tmpl = (id) ->
   if id is 'template-upload'
     return (data) -> JST['templates/file'](data)
@@ -74,13 +77,6 @@

   $modalContainer.data 'gallery', gallery

-$imagesContainer = $('#images')
-$modalContainer = $('#gallery-modal')
-
-$imagesContainer.on 'click', '.image a', (e) ->
-  loadSlide(this)
-  e.preventDefault()
-
 handleHashChange = (newHash) ->
   if imageId = newHash.match(/images\/(\d+)/)?[1]
     newIndex = $("#image_#{imageId}").index('.image a')
@@ -95,6 +91,13 @@
 hasher.init()

 $ ->
+  $imagesContainer = $('#images')
+  $modalContainer = $('#gallery-modal')
+
+  $imagesContainer.on 'click', '.image a', (e) ->
+    loadSlide(this)
+    e.preventDefault()
+
   $('#toggle_selection').click (e) ->
     e.preventDefault()
     if $(this).hasClass('active')


Add a guard to pushState for friendly browsers only

--- app/assets/javascripts/router.js.coffee
+++ app/assets/javascripts/router.js.coffee
@@ -21,6 +21,7 @@
         @route('new')
         @route('edit', path: '/:template_id/edit')

-ETahi.Router.reopen
-  rootURL: '/'
-  location: 'history'
+if window.history and window.history.pushState
+  ETahi.Router.reopen
+    rootURL: '/'
+    location: 'history'


Make the each metric animation finish at different times

The animation of a number finishes 1 second after the one before it.

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

 $ ->
-  animateMetric = $(".metric-box strong").each ->
+  animateMetric = $(".metric-box strong").each (index) ->
     $this = $(this)
     starting_point = 0
     $target_count = parseInt($this.text().replace(/\,/g, ''))
@@ -11,7 +11,7 @@
     # set the test to 0
     $this.counter = starting_point

-    tween = createjs.Tween.get($this).to( {counter: $target_count}, 2000 , createjs.Ease.quintOut)
+    tween = createjs.Tween.get($this).to( {counter: $target_count}, (1000 + index * 1000) , createjs.Ease.quintOut)

     tween.addEventListener 'change', (event) ->
       $this.text(Math.round($this.counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","))


Fix mousewheel and double click handling on map controls

--- src/map.coffee
+++ src/map.coffee
@@ -64,21 +64,16 @@
         return map

     class MapView extends Backbone.Marionette.View
-        events:
-            # Disable wheel events to map controls so that the map won't zoom
-            # if we try to scroll in a control.
-            'mousewheel .leaflet-control-container': 'onControlMouseWheel'
         tagName: 'div'
         render: ->
             @$el.attr 'id', 'map'
             this

-        onControlMouseWheel: ->
+        stopEventPropagation: (ev) ->
             ev.stopPropagation()
             return

         onShow: ->
-            console.log $('#app-container').get 0
             # The map is created only after the element is added
             # to the DOM to work around Leaflet init issues.
             @map = create_map @$el.get 0
@@ -94,6 +89,15 @@
             _.each @map_controls, (control, key) =>
                 control.addTo @map

+            # Disable wheel events to map controls so that the map won't zoom
+            # if we try to scroll in a control.
+            @$el.find($('.leaflet-control-container')).on 'dblclick', @stopEventPropagation
+            @$el.find($('.leaflet-control-container')).on 'mousewheel', @stopEventPropagation
+
+        onHide: ->
+            @$el.find($('.leaflet-control-container')).off 'dblclick', @stopEventPropagation
+            @$el.find($('.leaflet-control-container')).off 'mousewheel', @stopEventPropagation
+
         addControl: (name, control) ->
             @map_controls[name] = control
             control.addTo @map


[upd]  added auto livereload snippet

[upd]  added auto livereload snippet

--- app/templates/_gruntfile.coffee
+++ app/templates/_gruntfile.coffee
@@ -66,13 +66,9 @@
     connect:
       all:
         options:
+          hostname: "0.0.0.0"
           port: grunt.option('port') || 0
-          hostname: "0.0.0.0",
-          middleware: (connect, options) ->
-            return [
-              # Serve the project folder
-              connect.static(options.base)
-            ]
+          livereload: grunt.option('liveport') || 35729

     open:
       all:


Change file search to look at DOM labels instead of filename, for snapshots

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -496,7 +496,7 @@
             if not fullpath?
                 entry.show()  # this is the "Parent directory" link.
                 continue
-            filename = misc.path_split(fullpath).tail
+            filename = entry.find(".project-file-name").text() + entry.find(".project-file-name-extension").text()
             if match(filename, entry.hasClass('project-directory-link'))
                 if first and event?.keyCode == 13 # enter -- select first match (if any)
                     entry.click()


Expand sample test.

Expand sample test.

--- wp/test/login.coffee
+++ wp/test/login.coffee
@@ -5,3 +5,14 @@
   it 'displays the login form', ->
     casper.then ->
       '#user_login'.should.be.inDOM
+
+  it 'redirects to dashboard on successful login', ->
+    casper.fill '#loginform', {
+      log: 'admin',
+      pwd: 'p'
+    }
+
+    casper.click '#wp-submit'
+
+    casper.then ->
+      (/Dashboard/).should.matchTitle


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -27,7 +27,7 @@

 someOtherObject.meter 'turbo'
 # => Sets up a turbo meter that will be drawn during the someOtherObject overlay event.
-# Since maxHealt wasn't provided, it is set to the value of I.health (500)
+# Since neither maxTurbo wasn't provided, it is set to the value of I.health (500)

 # now player has all the methods provided by this module


Enforce plugin fields compliance. Deny tampering with settings and init when defining rules.

--- src/plugins.coffee
+++ src/plugins.coffee
@@ -69,11 +69,10 @@
       catch e
         throw new Error "Could not find plugin '#{foundPlugin}' in NPM modules"

-      # If we have something but seems empty, throw
-      if pContext instanceof Object
-        unless pContext.settings or pContext.rules
-          throw new Error "Plugin empty or bad: #{pluginName} (#{foundPlugin})"
-
+      # If plugin does not return an Object, tell user
+      unless pContext instanceof Object
+        throw new Error "Plugin empty or unparseable: #{pluginName} (#{foundPlugin})"
+      else
         if pContext.settings
           settings = if typeof pContext.settings is 'function'
             pContext.settings()
@@ -87,8 +86,8 @@
           if 'string' is Utils.toType rules
             rules = yaml.safeLoad rules, yaml.JSON_SCHEMA

-          # Make sure settings is not overwritten by rules.
-          Utils.recursiveMerge context, Utils.omit(rules, 'settings'), false
+          # Make sure neither settings nor init are overwritten by rules.
+          Utils.recursiveMerge context, Utils.omit(rules, ['settings', 'init']), false
         return Q context

     Q.Promise (resolve, reject, notify) ->
@@ -106,18 +105,28 @@
             when 'yaml'
               logging.debug "Loading YAML file #{foundPlugin}"
               pluginCtx = yaml.safeLoad code, yaml.JSON_SCHEMA
-              resolve Utils.recursiveMerge context, pluginCtx
+              resolve Utils.recursiveMerge context, pluginCtx, false
             else
-              # No ext => let's try to 'require' it (might be an installed module)
               throw new Error "Could not load #{pluginName}: #{foundPlugin} not found!"

+          # Enforce actions to be a function.
           if pContext.actions
-            Dispatch.extend pContext.actions logging, Config, Utils
+            if typeof pContext.actions is 'function'
+              Dispatch.extend pContext.actions logging, Config, Utils
+            else
+              throw new TypeError "Plugin #{pluginName} has actions defined in the wrong type #{typeof actions}, should be a function!"
+
+          # Settings defined in plugins can be overwritten by existing settings
+          # If defined as a function, pass current settings object as to the function.
           if pContext.settings
             settings = if typeof pContext.settings is 'function'
               pContext.settings context.settings
             else pContext.settings or {}
+
             Utils.recursiveMerge context.settings, settings, false
+
+          # If rules are defined as a function, call it with settings as argument.
+          # Either case, make sure we do not overwrite 'core' rules.
           if pContext.rules
             rules = if typeof pContext.rules is 'function'
               pContext.rules context.settings
@@ -126,8 +135,8 @@
             if 'string' is Utils.toType rules
               rules = yaml.safeLoad rules, yaml.JSON_SCHEMA

-            # Make sure settings is not overwritten by rules.
-            Utils.recursiveMerge context, Utils.omit(rules, 'settings'), false
+            # Make sure neither settings nor init are overwritten by rules.
+            Utils.recursiveMerge context, Utils.omit(rules, ['settings', 'init']), false

           resolve context
         catch e


Slogan, not slug

Slogan, not slug

--- api/apps/verticals/model.coffee
+++ api/apps/verticals/model.coffee
@@ -31,7 +31,7 @@
   featured_article_ids: @array().items(@objectId()).allow(null)
   start_at: @date().allow(null)
   end_at: @date().allow(null)
-  slug: @string().allow('',null)
+  slogan: @string().allow('',null)
 ).call Joi

 querySchema = (->


include a version to break cache when loading swf

--- src/sdk.coffee
+++ src/sdk.coffee
@@ -3,6 +3,7 @@
 #############################

 window.SC ||=
+  _version: "1.0.2"
   options:
     site: "soundcloud.dev",
   connectCallbacks: {}
@@ -233,7 +234,7 @@
       callback()
     else
       Recorder.initialize({
-        swfSrc: "http://" + this.hostname("connect") + "/recorder.js/recorder.swf",
+        swfSrc: "http://" + this.hostname("connect") + "/recorder.js/recorder.swf?" + SC._version,
         initialized: () ->
           callback()
       })


Debugger removed

Debugger removed

--- app/authenticators/irene.coffee
+++ app/authenticators/irene.coffee
@@ -28,7 +28,6 @@
           resolve Ember.$.extend response
       _rejected = (xhr, status, error) ->
         Ember.run ->
-          debugger
           loginBtn.restoreSignIn()
           reject xhr.responseJSON || xhr.responseText
       url = config['simple-auth']['loginEndpoint']


Don't consume partial parts of delete when inserting a change

--- app/coffee/RangesTracker.coffee
+++ app/coffee/RangesTracker.coffee
@@ -206,12 +206,12 @@
 					# If this is an insert op at the end of an existing insert with a delete following, and it cancels out the following
 					# delete then we shouldn't append it to this insert, but instead only cancel the following delete.
 					# E.g.
-					#                   foo|<--- about to insert 'b' here
+					#                   foo|<--- about to insert 'bar' here
 					#  inserted 'foo'  --^ ^-- deleted 'bar'
-					# should become just 'foo' not 'foob' (with the delete marker becoming just 'ar'), .
+					# should become just 'foo' not 'foobar' (with the delete marker disappearing), .
 					next_change = @changes[i+1]
 					is_op_adjacent_to_next_delete = next_change? and next_change.op.d? and op.p == change_end and next_change.op.p == op.p
-					will_op_cancel_next_delete = is_op_adjacent_to_next_delete and next_change.op.d.slice(0, op.i.length) == op.i
+					will_op_cancel_next_delete = is_op_adjacent_to_next_delete and next_change.op.d == op.i

 					# If there is a delete at the start of the insert, and we're inserting
 					# at the start, we SHOULDN'T merge since the delete acts as a partition.


Fix broken chat test because comments are now distinguished by author.

--- app/coffee/tests/browser.coffee
+++ app/coffee/tests/browser.coffee
@@ -375,7 +375,7 @@
     casper.thenClick (@lastGameSelector false), ->
       # on the game page is a game chat.
       test.assertExists '.game-chat', 'The game chat does exist.'
-      comments_selector = '.game-chat .chat-comments .chat-comment'
+      comments_selector = '.game-chat .chat-comments .chat-comment-black'
       comments = test.assertSelectorHasText comments_selector, MY_CHAT[0],
                  "Player one's comment has appeared in the chat area."


fix(service): fix service.isValidSession()

--- src/service.coffee
+++ src/service.coffee
@@ -236,7 +236,7 @@
       readyState : wrapped.getConnectionState
       isEnabled : -> enabled
       getBufferCount: -> messageQueueHolder.size()
-      isValidSession : -> wrapped.validSession
+      isValidSession : -> validSession
       login : wrapped.login
     return api


Fix bug to use fs

Fix bug to use fs

--- src/tub.coffee
+++ src/tub.coffee
@@ -1,5 +1,6 @@
 # Tub class
 YAML = require 'yamljs'
+fs = require 'fs'
 class TubConfig
   constructor: (tub, appdir, yml) ->
     # body...


WIP: i think this is closer to what i want.

I need to interate thorugh the array then check against it

--- src/welcome.coffee
+++ src/welcome.coffee
@@ -21,11 +21,12 @@
   robot.enter (msg) ->
     welcome = robot.brain.get('data.welcome')
     stored_users = robot.brain.get('data.users')
-    now_users = robot.brain.usersForFuzzyName(name)
-    if now_users ~= stored_users
-    else
-      msg.send "Welcome, #{msg.message.user.name}, #{welcome}"
-      robot.brain.set 'data.users', msg.message.user.name
+    now_user = robot.brain.usersForFuzzyName(name)
+    for name in stored_users
+            if now_user = name
+            else
+              msg.send "Welcome, #{msg.message.user.name}, #{welcome}"
+              robot.brain.set 'data.users', msg.message.user.name

   robot.respond /welcome (.*)$/i, (msg) ->


Added test case for issue #7

Added test case for issue #7

https://github.com/tarruda/bootstrap-datetimepicker/issues/7

--- test/issues.coffee
+++ test/issues.coffee
@@ -26,3 +26,15 @@
     @widget.find('.datepicker-days .next').click()
     expect(@widget.find('.datepicker-days .switch').text())
       .to.equal 'June 1905'
+
+  test '7', ->
+    # https://github.com/tarruda/bootstrap-datetimepicker/issues/7
+    # first set it to a december date
+    @input.val('12/25/2012 00:00:00')
+    @input.change()
+    # Click on 2 january
+    @dateWidget.find('.day.old:contains(2)').click()
+    @dateShouldEqual(2013, 1, 2)
+
+
+


removing the mkdirp test

removing the mkdirp test

--- test/mkdirp.coffee
+++ test/mkdirp.coffee
@@ -1,27 +0,0 @@
-command = require '../lib/coffee-script/command'
-path = require 'path'
-fs = require 'fs'
-
-test "mkdirp", ->
-  deepDir = 'test/mkdirp/deep/dir'
-
-  process.argv = [
-    'node', 'coffee'
-    '-o', deepDir
-    '-c', __filename
-  ]
-  command.run()
-
-  name = path.basename __filename, '.coffee'
-  compiledPath = path.join deepDir, "#{name}.js"
-
-  process.on 'exit', ->
-    exists = fs.existsSync compiledPath
-
-    try
-      fs.unlinkSync compiledPath
-      fs.rmdirSync 'test/mkdirp/deep/dir'
-      fs.rmdirSync 'test/mkdirp/deep'
-      fs.rmdirSync 'test/mkdirp'
-
-    ok exists


Update sticky.coffee

Update sticky.coffee

fix syntax error: missing "," between parameters

--- test/sticky.coffee
+++ test/sticky.coffee
@@ -10,7 +10,7 @@
     loadFixtures 'sticky.html'
     $sticky = $ '.sticky'
     handlerSpy = jasmine.createSpy 'on handler'
-    $return = $sticky.waypoint 'sticky'
+    $return = $sticky.waypoint 'sticky',
       handler: handlerSpy

   it 'returns the same jQuery object for chaining', ->


Clean up hash.coffee

Clean up hash.coffee

--- src/hash/hash.coffee
+++ src/hash/hash.coffee
@@ -2,36 +2,44 @@
 #= require ../object

 class Batman.Hash extends Batman.Object
+
   class @Metadata extends Batman.Object
     Batman.extend @prototype, Batman.Enumerable
+
     constructor: (@hash) ->
+
     @accessor 'length', ->
       @hash.registerAsMutableSource()
       @hash.length
+
     @accessor 'isEmpty', 'keys', 'toArray', (key) ->
       @hash.registerAsMutableSource()
       @hash[key]()
+
     forEach: -> @hash.forEach(arguments...)

   constructor: ->
     @meta = new @constructor.Metadata(this)
-    Batman.SimpleHash.apply(@, arguments)
+    Batman.SimpleHash.apply(this, arguments)
     super

   Batman.extend @prototype, Batman.Enumerable
   propertyClass: Batman.Property

   @defaultAccessor =
+    cache: false
     get: Batman.SimpleHash::get
+
     set: @mutation (key, value) ->
-      result = Batman.SimpleHash::set.call(@, key, value)
-      @fire 'itemsWereAdded', key
+      result = Batman.SimpleHash::set.call(this, key, value)
+      @fire('itemsWereAdded', key)
+
       result
+
     unset: @mutation (key) ->
-      result = Batman.SimpleHash::unset.call(@, key)
-      @fire 'itemsWereRemoved', key if result?
+      result = Batman.SimpleHash::unset.call(this, key)
+      @fire('itemsWereRemoved', key) if result?
       result
-    cache: false

   @accessor @defaultAccessor

@@ -39,25 +47,31 @@
     @prevent 'change'
     @prevent 'itemsWereAdded'
     @prevent 'itemsWereRemoved'
+
     try
       block.call(this)
     finally
       @allow 'change'
       @allow 'itemsWereAdded'
       @allow 'itemsWereRemoved'
+
   clear: @mutation ->
     keys = @keys()
     @_preventMutationEvents -> @forEach (k) => @unset(k)
-    result = Batman.SimpleHash::clear.call(@)
-    @fire 'itemsWereRemoved', keys...
+    result = Batman.SimpleHash::clear.call(this)
+
+    @fire('itemsWereRemoved', keys...)
     result
+
   update: @mutation (object) ->
     addedKeys = []
     @_preventMutationEvents ->
-      Batman.forEach object, (k,v) =>
+      Batman.forEach object, (k, v) =>
         addedKeys.push(k) unless @hasKey(k)
         @set(k,v)
+
     @fire('itemsWereAdded', addedKeys...) if addedKeys.length > 0
+
   replace: @mutation (object) ->
     addedKeys = []
     removedKeys = []
@@ -66,9 +80,11 @@
         unless Batman.objectHasKey(object, k)
           @unset(k)
           removedKeys.push(k)
+
       Batman.forEach object, (k,v) =>
         addedKeys.push(k) unless @hasKey(k)
         @set(k,v)
+
     @fire('itemsWereAdded', addedKeys...) if addedKeys.length > 0
     @fire('itemsWereRemoved', removedKeys...) if removedKeys.length > 0

@@ -79,4 +95,4 @@
     do (k) =>
       @prototype[k] = ->
         @registerAsMutableSource()
-        Batman.SimpleHash::[k].apply(@, arguments)
+        Batman.SimpleHash::[k].apply(this, arguments)


Removed sub channel functionality from backbone model

--- core/app/backbone/models/channel.coffee
+++ core/app/backbone/models/channel.coffee
@@ -16,9 +16,6 @@
   cached: (field, retrieval) ->
     @cache ||= {}
     @cache[field] = (@cache[field] || retrieval())
-
-  subchannels: ->
-    @cached 'subchannels', => fetched(new Subchannels([], channel: this))

   topic: ->
     new Topic
@@ -41,42 +38,6 @@
   getUsername: ->
     @get("created_by")?.username ? @get("username")

-  addToChannel: (sub_channel, options={}) ->
-    @_changeFollowingChannel('add', sub_channel, options)
-
-  removeFromChannel: (sub_channel, options={}) ->
-    @_changeFollowingChannel('remove', sub_channel, options)
-
-  _changeFollowingChannel: (action, sub_channel, options) ->
-    changeUrl = "#{@normal_url()}/subchannels/#{action}/#{sub_channel.id}"
-
-    Backbone.ajax
-      url: changeUrl
-      type: 'post'
-      error: -> options.error?()
-      success: =>
-        mp_track "Channel: #{action} subchannel",
-          channel_id: @id
-          subchannel_id: sub_channel.id
-
-        options.success?()
-
   toJSON: ->
     _.extend super(),
       link: @normal_url()
-
-  follow: ->
-    followUrl = "#{@normal_url()}/follow"
-    @set('followed?', true)
-    Backbone.ajax
-      url: followUrl
-      type: 'post'
-      error: => @set('followed?', false)
-
-  unfollow: ->
-    unfollowUrl = "#{@normal_url()}/unfollow"
-    @set('followed?', false)
-    Backbone.ajax
-      url: unfollowUrl
-      type: 'post'
-      error: => @set('followed?', true)


Finishing up the very early version of the Expression object.

--- coffeequate/src/Expression.coffee
+++ coffeequate/src/Expression.coffee
@@ -15,4 +15,16 @@
 		toString: ->
 			@expr.toString()

+		solve: (variable) ->
+			# TODO: Equivalencies.
+			@expr.solve(variable)
+
+		sub: (substitutions) ->
+			# TODO: Uncertainties, equivalencies, options.
+			# TODO: Seems that the way I implemented substituting expressions was different last time for no real reason. Fix.
+			@expr.sub(substitutions, null, null).simplify()
+
+		simplify: ->
+			@expr.simplify()
+
 	return Expression


Fixed line too long Fixed backticks

Fixed line too long
Fixed backticks

--- coffeescript/feedlyconsole.coffee
+++ coffeescript/feedlyconsole.coffee
@@ -93,17 +93,21 @@
   preferences:
     autoMarkAsReadOnSelect: "50"
     "category/reviews/entryOverviewSize": "0"
-    "subscription/feed/http://feeds.engadget.com/weblogsinc/engadget/entryOverviewSize": "4"
-    "subscription/feed/http://www.yatzer.com/feed/index.php/hideReadArticlesFilter": "off"
+    "subscription/feed/http://
+feeds.engadget.com/weblogsinc/engadget/entryOverviewSize": "4"
+    "subscription/feed/http://
+www.yatzer.com/feed/index.php/hideReadArticlesFilter": "off"
     "category/photography/entryOverviewSize": "6"
-    "subscription/feed/http://feeds.feedburner.com/venturebeat/entryOverviewSize.mobile": "1"
+    "subscription/feed/http://
+feeds.feedburner.com/venturebeat/entryOverviewSize.mobile": "1"

 #//////////////////////////////////////////////////////////
 # based on josh.js:gh-pages githubconsole
 ((root, $, _) ->
   Josh.FeedlyConsole = ((root, $, _) ->

-    # Enable console debugging, when Josh.Debug is set and there is a console object on the document root.
+    # Enable console debugging, when Josh.Debug is set and there is a
+    # console object on the document root.
     _console = (if (Josh.Debug and window.console) then window.console else
       log: ->
       debug: ->
@@ -152,10 +156,17 @@
     # **templates.ls**
     # Override of the pathhandler ls template to create a multi-column listing.
     _self.shell.templates.ls = _.template """
-
<% _.each(nodes, function(node) { %>
<%- node.name %>
<% }); %>

"""
+

+      <% _.each(nodes, function(node) { %>
+
<%- node.name %>

+      <% }); %>
+

"""

     # **templates.not_found**
-    # Override of the pathhandler *not_found* template, since we will throw *not_found* if you try to access a valid file. This is done for the simplicity of the tutorial.
+
+    # Override of the pathhandler *not_found* template, since we will
+    # throw *not_found* if you try to access a valid file. This is
+    # done for the simplicity of the tutorial.
     _self.shell.templates.not_found = _.template """

<%=cmd%>: <%=path%>: No such directory

       """
@@ -193,10 +204,12 @@
       exec: (cmd, args, callback) ->
         get command_name, null, (data) ->
           return err("api request failed to get data")  unless data
-          template = _self.shell.templates[command_name] or _self.shell.templates.default_template
+          template = _self.shell.templates[command_name]
+          template = template or _self.shell.templates.default_template
           template_args = {}
           template_args[command_name] = template_args["data"] = data
-          _console.debug "[Josh.FeedlyConsole] data %O cmd %O args %O", data, cmd, args
+          _console.debug "[Josh.FeedlyConsole] data %O cmd %O args %O",
+          data, cmd, args
           callback template(template_args)

@@ -248,7 +261,8 @@
       # If the first part of path parts isn't empty, the path is a
       # relative path, which can be turned into an *absolutish* path
       # by pre-pending the parts of the current pathnode.
-      parts = getPathParts(_self.pathhandler.current.path).concat(parts)  if parts[0] isnt ""
+      parts = getPathParts(_self.pathhandler.current.path)
+      .concat(parts)  if parts[0] isnt ""
       # At this point the path is *absolutish*, i.e. looks absolute, but
       # all `.` and `..` mentions need to removed and resolved before it
       # is truly absolute.
@@ -284,7 +298,8 @@
       # Otherwise, if the child nodes have already been initialized,
       # which is done lazily, return them.
       if node.children
-        _console.debug "[Josh.FeedlyConsole] got children, let's turn them into nodes %O", node
+        _console.debug "[Josh.FeedlyConsole]
+ got children, let's turn them into nodes %O", node
         return callback(makeNodes(node.children))
       _console.debug "[Josh.FeedlyConsole] no children, fetch them %O", node
       # Finally, use `getDir` to fetch and populate the child nodes.
@@ -352,9 +367,11 @@
             limit: parseInt(xhr.getResponseHeader("X-RateLimit-Limit"))
             authenticated: xhr.getResponseHeader("Authenticated") is "true"

-          $("#ratelimit").html _self.shell.templates.rateLimitTemplate(ratelimit)
+          $("#ratelimit").html _self
+          .shell.templates.rateLimitTemplate(ratelimit)
           if ratelimit.remaining is 0
-            alert "Whoops, you've hit the github rate limit. You'll need to authenticate to continue"
+            alert "Whoops, you've hit the github rate limit. You'll need
+ to authenticate to continue"
             _self.shell.deactivate()
             return null

@@ -417,10 +434,13 @@
           attr = target.attributes.getNamedItem(name)
           value = ""
           value = attr.value  if attr isnt null
-          _console.debug "[feedlyconsole/observer] %s: [%s]=%s on %O", type, name, value, target
+          _console.debug "[feedlyconsole/observer] %s: [%s]=%s on %O",
+            type, name, value, target

           # not sure if wide will always be set, so trigger on the next mod
-          if not _found and ((name is "class" and value.indexOf("wide") isnt -1) or (name is "_pageid" and value.indexOf("rot21") isnt -1))
+          wide = name is "class" and value.indexOf("wide") isnt -1
+          page = name is "_pageid" and value.indexOf("rot21") isnt -1
+          if not _found and (wide or page)
             _console.debug "[feedlyconsole] mutation observer end %O", observer
             _found = true
             doInsertShellUI()
@@ -440,21 +460,22 @@
         _console.debug observer
         _console.debug config
         observer.observe target, config
-
+    ###
     #

     # getDir
     # ------

-    # This function function fetches the directory listing for a path on a given repo and branch.
+    # This function function fetches the directory listing for a path
+    # on a given repo and branch.
+    ###
     getDir = (path, callback) ->
       node = undefined
       name = undefined

       # remove trailing '/' for API requests.
-      path = path.substr(0, path.length - 1)  if path and path.length > 1 and path[path.length - 1] is "/"
+      path = path[...-1] if path and path.length > 1 and _.last path is "/"
       if not path or (path.length is 1 and path is "/")
-
         # item 0, root, each command a subdir
         name = "/"
         node =
@@ -471,16 +492,14 @@
         parts = parts.slice(1)  if parts[0] is ""
         name = parts[0]
         handler = _self.root_commands[name]
-        if handler is `undefined`
+        unless handler?
           callback()
         else if parts.length is 1
-
           # item 1, commands
           node =
             name: name
             path: path
             children: null
-

           # implicitly call command as part of path
           command = handler.exec
@@ -489,20 +508,20 @@
             _console.debug "[Josh.FeedlyConsole] json to nodes %O.", json
             node.children = makeJSONNodes(path, json, name)
             callback node
-
         else
-
           # item 2+, details
-          _console.debug "[Josh.FeedlyConsole] not implemented, path: %s, name %s", path, name
+          _console.debug "[Josh.FeedlyConsole]
+ not implemented, path: %s, name %s", path, name
           get "streams/"
-          callback `undefined`
-
+          callback null
+    ###
     #

     # getPathParts
     # ------------

     # This function splits a path on `/` and removes any empty trailing element.
+    ###
     getPathParts = (path) ->
       parts = path.split("/")
       return parts.slice(0, parts.length - 1)  if parts[parts.length - 1] is ""
@@ -596,7 +615,8 @@
         $consoletab.slideDown()
       _console.log "[Josh.FeedlyConsole] initializeUI."
       $consoletab = $("#consoletab")
-      console.error "failed to find %s", $consoletab.selector  if $consoletab.length is 0
+      console.error "failed to find %s",
+        $consoletab.selector  if $consoletab.length is 0
       $consoletab.hover (->
         $consoletab.addClass "consoletab-hover"
         $consoletab.removeClass "consoletab"
@@ -647,7 +667,7 @@
           _console.debug "[feedlyconsole] set api url: %s.", url
           _self.api = url
         else if request.action is "toggle_console"
-          if _self.ui.toggleActivateAndShow is `undefined`
+          unless _self.ui.toggleActivateAndShow?
             window.console.warn "[feedlyconsole] ui not yet ready"
           else
             _self.ui.toggleActivateAndShow()
@@ -655,7 +675,8 @@
           _self.OAuth = request.feedlytoken
           _console.debug "[feedlyconsole] token %s...", _self.OAuth.slice(0, 8)
         else
-          _console.debug "[feedlyconsole] unknown action %s request %O.", request.action, request
+          _console.debug "[feedlyconsole] unknown action %s request %O.",
+          request.action, request
         sendResponse action: "ack"

     else


Reached equivalent functionally to pre-class code.

Removed caching from ApiRequest, its now implicit in node structure.
Changed ajax callback from done to always, so that an error in ajax
request will appear as an invalid directory
FeedlyApiRequest validates against array of valid feedly calls.
FeedlyNode now tracks type - leaf type has no children.
Cleaned up debug messages.

--- coffeescript/feedlyconsole.coffee
+++ coffeescript/feedlyconsole.coffee
@@ -108,11 +108,8 @@

 FEEDLY_API_VERSION = "v3"

-
-
 class ApiRequest
   constructor: (@url) ->
-    @_cache = {}

   url: ->
     @url
@@ -123,24 +120,18 @@
       ).join("&")

   get: (resource, args, callback) ->
-    cache_entry = @_cache[resource]
-    if cache_entry
-      _console.debug "[Josh.FeedlyConsole] %s cached.", resource
-      return callback(cache_entry)
-
     unless chrome.extension?
       # not embedded, demo mode
       demo = demo_data[resource]
       demo = {} unless demo?
-      @_cache[resource] = demo
-      callback demo, null, null
+      callback demo, 'ok', null
     else
       url = [ @url, resource ].join('/') + @_url_parameters args
-      _console.debug "[Josh.FeedlyConsole] fetching %s at %s.", resource, url
+      _console.debug "[apirequest] fetching %s at %s.", resource, url
       request = @build_request url
-
-      $.ajax(request).done (response, status, xhr) =>
-        @_cache[resource] = response
+      $.ajax(request).always (response, status, xhr) ->
+        _console.debug "[apirequest] '%s' status '%s' response %O xhr %O.",
+          resource, status, response, xhr
         return callback response, status, xhr

   build_request: (url) ->
@@ -153,13 +144,25 @@
 class FeedlyApiRequest extends ApiRequest
   constructor: (@url, @api_version, @oauth) ->
     super([@url, @api_version].join('/'))
+    @resources = [
+      "profile"
+      "tags"
+      "subscriptions"
+      "preferences"
+      "categories"
+      "topics"
+    ]

   build_request: (url) ->
     request = super(url)
     request.headers = Authorization: "OAuth #{@oauth}"
     return request

+  validate_resource: (resource, args) ->
+    return resource in @resources
+
   get: (resource, args, callback) ->
+    return callback null, 'error', null unless @validate_resource resource, args
     return super resource, args, (response, status, xhr) ->
       if response? and not status?
         # Every response from the API includes rate limiting
@@ -182,10 +185,10 @@
         # For simplicity, this tutorial trivially deals with request
         # failures by just returning null from this function via the
         # callback.
-        callback(response)
+        callback(response, status)
       else
         if response? and response
-          return callback(response)
+          return callback(response, status)
         else
           return callback()

@@ -204,22 +207,27 @@
   @_NODES: {}
   @_NODES[@_ROOT_PATH] = null

-  constructor: (@name, @path, @json_data) ->
+  constructor: (@name, @path, @type, @json_data) ->
     @children = null
     FeedlyNode._NODES[@path] = @
+    @type ?= 'node'

   @_initRootNode: =>
     @_ROOT_NODE ?= new RootFeedlyNode()

-  @_getPathParts = (path) ->
+  @_getPathParts: (path) ->
     parts = path.split("/")
     #remove empty trailing element
     return parts.slice(0, parts.length - 1)  if parts[parts.length - 1] is ""
     return parts

-  @_current = ->
+  @_current: ->
     return Josh.config.pathhandler.current

+  @_api:  ->
+    return Josh.FeedlyConsole.api
+
+  # normalize path
   @_resolvePath: (path) =>
     parts = @_getPathParts(path)

@@ -228,7 +236,7 @@
     parts = @_getPathParts(@_current().path).concat(parts) if parts[0] isnt ""

     # resolve `.` and `..`
-    resolved = ['']
+    resolved = []
     _.each parts, (x) ->
       return if x is "."
       if x is ".."
@@ -236,150 +244,86 @@
       else
         resolved.push x

+    return "/" if not resolved? or resolved.length is 1
     return resolved.join("/")
+

   @getNode: (path, callback) =>
     @_initRootNode()
     _console.debug "[feedlyconsole:FeedlyNode] looking for node at '%s'.", path
     return callback @_ROOT_NODE unless path?
-    absolute = @_resolvePath path
-    _console.debug "[Josh.FeedlyConsole]path to fetch: " + absolute
-    return callback @_NODES[path] if path of @_NODES
-
-    #temporary hack
-    return callback @_ROOT_NODE
-
+    normalized = @_resolvePath path
+    _console.debug "[feedlyconsole:FeedlyNode] normalized path '%s'", normalized
+    return callback @_NODES[normalized] if normalized of @_NODES
+
+    name = _.last normalized.split('/')
+    @call_api_by_path normalized, (json, status) ->
+      if status is 'error'
+        callback null
+      else
+        node = new FeedlyNode(name, normalized, 'name', json)
+        return callback node
+
+  @call_api_by_path: (path, callback) ->
+    parts = path.split('/')
+    @_api().get _.last(parts), [], (response, status) ->
+      callback(response, status)

   @getChildNodes: (node, callback) =>
     @_initRootNode()
     node.getChildNodes callback

   getChildNodes: (callback) ->
-    # If the given node is a file node, no further work is required.
-    if node.isFile
-      _console.debug "[Josh.FeedlyConsole] it's a file, no children %O", node
-      return callback()
-    # Otherwise, if the child nodes have already been initialized,
-    # which is done lazily, return them.
-    if node.children
-      _console.debug """
-      [Josh.FeedlyConsole] got children, let's turn them into nodes %O"""
-      , node
-      return callback(makeNodes(node.children))
-    _console.debug "[Josh.FeedlyConsole] no children, fetch them %O", node
-    # Finally, use `getDir` to fetch and populate the child nodes.
-    getDir node.path, (detailNode) ->
-      node.children = detailNode.children
-      callback node.children
-
-  getDir = (path, callback) ->
-    node = undefined
-    name = undefined
-
-    # remove trailing '/' for API requests.
-    path = path[...-1] if path and path.length > 1 and _.last path is "/"
-    if not path or (path.length is 1 and path is "/")
-      # item 0, root, each command a subdir
-      name = "/"
-      node =
-        name: "/"
-        path: path
-        children: makeRootNodes()
-
-      _console.debug "[Josh.FeedlyConsole] root node %O.", node
-      callback node
+    if @type is 'leaf'
+      @children = null
     else
-      parts = getPathParts(path)
-
-      # leading '/' produces empty item
-      parts = parts.slice(1)  if parts[0] is ""
-      name = parts[0]
-      handler = _self.root_commands[name]
-      unless handler?
-        callback()
-      else if parts.length is 1
-        # item 1, commands
-        node =
-          name: name
-          path: path
-          children: null
-
-        # implicitly call command as part of path
-        command = handler.exec
-        command "", "", (map) ->
-          json = _self[name]
-          _console.debug "[Josh.FeedlyConsole] json to nodes %O.", json
-          node.children = makeJSONNodes(path, json, name)
-          callback node
-      else
+      @children ?= @makeJSONNodes()
+    return callback @children
+
+  makeJSONNodes: (type) ->
+    if _.isArray(@json_data)
+      nodes = _.map @json_data, (item) =>
+        name = item.label or item.title or item.id
+        path = [@path, name].join '/'
+        json = item
+        return new FeedlyNode name, path, 'node', json
+      nodes
+    else
+      # its a map, so all children are leaves
+      _.map @json_data, (value, key) =>
+        name = [
+          key
+          value
+        ].join(":")
+        json =
+          key: value
+        return new FeedlyNode name, @path, 'leaf', json
+
+###
         # item 2+, details
         _console.debug """
         [Josh.FeedlyConsole]
         not implemented, path: %s, name %s""", path, name
         get "streams/"
         callback null
-  ###
-  #

-
-  # getPathParts
-  # ------------
-
-  # This function splits a path on `/` and removes any empty trailing element.
-  ###
-
-
-  #

-
-  # makeNodes
-  # ---------
-
-  # This function builds child pathnodes from the directory
-  # information returned by getDir.
-  makeNodes = (children) ->
-    _console.debug "[Josh.FeedlyConsole] makeNodes %O.", children
-    _.map children, (node) ->
-      name: node.name
-      path: "/" + node.path
-      isFile: node.type is "leaf"
-
-  makeJSONNodes = (path, children, type) ->
-    if _.isArray(children)
-      nodes = _.map(children, (item) ->
-        name = item.label or item.title or item.id
-        $.extend
-          name: name
-          path: path + "/" + name
-          isFile: type is "leaf"
-        , item
-      )
-      nodes
-    else
-      _.map children, (value, key) ->
-        name = [
-          key
-          value
-        ].join(":")
-        name: name
-        path: path + "/" + name
-        isFile: type is "leaf"
-
-  makeRootNodes = ->
-    _.map _self.root_commands, (value, key, list) ->
-      name: key
-      path: "/" + key
-      type: "command"
-      isFile: "command" is "leaf"
+###

 class RootFeedlyNode extends FeedlyNode
   constructor: () ->
     super FeedlyNode._ROOT_PATH,
       FeedlyNode._ROOT_PATH,
       { cmds: FeedlyNode._ROOT_COMMANDS }
+    @type = 'root'

   getChildNodes: (callback) ->
-    @children ?= (new FeedlyNode(name,
-      [@path, name].join('/'),
-      null) for name in FeedlyNode._ROOT_COMMANDS)
+    unless @children?
+      @children =[]
+      for name in FeedlyNode._ROOT_COMMANDS
+        child_path = [@path, name].join('/')
+        if child_path of FeedlyNode._NODES
+          @children.push FeedlyNode._NODES.child_path
+        else
+          @children.push new FeedlyNode(name, child_path, 'leaf', null)
     return callback @children

   toString: ->
@@ -409,6 +353,7 @@
       root_commands: {}
       pathhandler: Josh.config.pathhandler
       api: null
+      observer: null

     ###
     # Custom Templates
@@ -500,11 +445,12 @@
             template = template or _self.shell.templates.default_template
             template_args = {}
             template_args[command_name] = template_args["data"] = data
-            _console.debug "[Josh.FeedlyConsole] data %O cmd %O args %O",
+            _console.debug "[feedlyconsole/handler] data %O cmd %O args %O",
             data, cmd, args
             callback template(template_args)
         else
-          callback _self.shell.templates.not_ready({cmd})
+          path = _self.pathahndler.current.path
+          callback _self.shell.templates.not_ready({cmd, path})

     simple_commands = [
@@ -535,7 +481,7 @@
         node: _self.pathhandler.current
       )

-
+    ###
     # Wiring up PathHandler
     # =====================

@@ -547,9 +493,10 @@
     # `getNode` is required by `Josh.PathHandler` to provide
     # filesystem behavior. Given a path, it is expected to return a
     # pathnode or null;
+    ###
     _self.pathhandler.getNode = FeedlyNode.getNode

-
+    ###
     #

     # getChildNodes
@@ -560,12 +507,15 @@
     # child pathnodes. This is used by `Tab` completion to resolve a
     # partial path, after first resolving the nearest parent node
     # using `getNode
+    ###
     _self.pathhandler.getChildNodes = FeedlyNode.getChildNodes

+    ###
     #

-
+    #
     # initalize
     # --------------
+    ###

     # This function sets the node
     initialize = (evt) -> #err, callback) {
@@ -575,7 +525,7 @@
         _self.pathhandler.current = node

     insertCSSLink = (name) ->
-      console.debug "[feedlyconsole] inserting css #{name}"
+      _console.debug "[feedlyconsole/init] inserting css #{name}"
       # insert css into head
       $("head").append $ "<link/>",
         rel: "stylesheet"
@@ -583,9 +533,9 @@
         href: chrome.extension.getURL(name)

     doInsertShellUI = ->
-      observer.disconnect()
+      _self.observer.disconnect() if _self.observer?
       file = "feedlyconsole.html"
-      _console.debug "[feedlyconsole] injecting %s.", file
+      _console.debug "[feedlyconsole/init] injecting shell ui from %s.", file
       #insertCSSLink "stylesheets/styles.css"
       #insertCSSLink("stylesheets/source-code-pro.css");
       insertCSSLink "stylesheets/jquery-ui.css"
@@ -593,12 +543,30 @@
       feedlyconsole = $("
",
         id: "feedlyconsole"
       ).load(chrome.extension.getURL(file), ->
-        _console.log "[feedlyconsole] loaded %s %O readline.attach %O.",
+        _console.log "[feedlyconsole/init]
+ loaded shell ui %s %O readline.attach %O.",
         file, $("#feedlyconsole"), this
         Josh.config.readline.attach $("#shell-panel").get(0)
         initializeUI()
       )
       $("body").prepend feedlyconsole
+
+    insertShellUI = ->
+      if $("#feedlyconsole").length is 0
+        target = document
+        config =
+          attributes: true
+          subtree: true
+        _console.debug "[feedlyconsole/init/observer]
+ mutation observer start target %O config %O",
+          target,
+          config
+        _self.observer.observe target, config if _self.observer
+      else
+        _console.debug "[feedlyconsole/init/shell] #feedlyconsole found."
+        initializeUI()
+
+    # watch body writing until its stable enough to doInsertShellUI
     mutationHandler = (mutationRecords) ->
       _found = false
       mutationRecords.forEach (mutation) ->
@@ -609,64 +577,27 @@
           attr = target.attributes.getNamedItem(name)
           value = ""
           value = attr.value  if attr isnt null
-          _console.debug "[feedlyconsole/observer] %s: [%s]=%s on %O",
+          _console.debug "[feedlyconsole/init/observer] %s: [%s]=%s on %O",
             type, name, value, target

           # not sure if wide will always be set, so trigger on the next mod
           wide = name is "class" and value.indexOf("wide") isnt -1
           page = name is "_pageid" and value.indexOf("rot21") isnt -1
           if not _found and (wide or page)
-            _console.debug "[feedlyconsole] mutation observer end %O", observer
+            _console.debug "[feedlyconsole/init/observer]
+ mutation observer end %O", _self.observer
             _found = true
             doInsertShellUI()

             # found what we were looking for
             null

-    insertShellUI = ->
-      if $("#feedlyconsole").length is 0
-        _console.debug "[feedlyconsole] mutation observer start"
-        target = document
-        config =
-          attributes: true
-          subtree: true
-
-        _console.debug target
-        _console.debug observer
-        _console.debug config
-        observer.observe target, config
-      else
-        _console.debug "[feedlyconsole] #feedlyconsole found."
-        initializeUI()
-
-
-    # UI setup and initialization
-    # ===========================
-
-    #

-
-    # initializationError
-    # -------------------
-
-    # This function is a lazy way with giving up if some request
-    # failed during intialization, forcing the user to reload to
-    # retry.
-    initializationError = (context, msg) ->
-      _console.error "[%s] failed to initialize: %s.", context, msg
-
-
-    #

-
-    # intializeUI
-    # -----------

     # this function
     # initializes the UI state to allow the shell to be shown and
     # hidden.
     initializeUI = ->
-
       # We grab the `consoletab` and wire up hover behavior for it.
-
       # We also wire up a click handler to show the console to the `consoletab`.
       toggleActivateAndShow = ->
         if _self.shell.isActive()
@@ -683,7 +614,7 @@
         $consolePanel.slideUp()
         $consolePanel.blur()
         $consoletab.slideDown()
-      _console.log "[Josh.FeedlyConsole] initializeUI."
+      _console.log "[feedlyconsole/init] initializeUI."
       $consoletab = $("#consoletab")
       if $consoletab.length is 0
         console.error "failed to find %s", $consoletab.selector
@@ -705,59 +636,60 @@
           event.preventDefault()
           activateAndShow()
       )
+
+      _self.shell.onEOT hideAndDeactivate
+      _self.shell.onCancel hideAndDeactivate
+
+      # export ui functions so they can be called by background page
+      # via page action icon
       _self.ui.toggleActivateAndShow = toggleActivateAndShow
       _self.ui.activateAndShow = activateAndShow
       _self.ui.hideAndDeactivate = hideAndDeactivate
-      _self.shell.onEOT hideAndDeactivate
-      _self.shell.onCancel hideAndDeactivate
-
-
-
-
-
-    # wire up pageAction to toggle console
-    # kind of a mess, but we only want to create one listener,
-    # but initializeUI can be called multiple times because
-    # feedly will blow away console that are added to early
+
     if chrome.runtime?  # running in extension
-      _console.log "[Josh.FeedlyConsole] extension, initialize."
+      _console.log "[feedlyconsole/init] in extension contex, initializing."
+
+      _console.debug "[feedlyconsole/init] mutationHandler installed."
+      _self.observer = new MutationObserver(mutationHandler)
       initialize()
-      observer = new MutationObserver(mutationHandler)
+
+      # listener to messages from background page
       chrome.runtime.onMessage.addListener (request, sender, sendResponse) ->
-        _console.debug "[feedlyconsole] msg: %s.", request.msg
+        _console.debug "[feedlyconsole/msg] msg: %s.", request.msg
         if request.action is "icon_active"
-          _console.debug "[feedlyconsole/icon_active] ignore."
+          _console.debug "[feedlyconsole/msg/icon_active] ignore."
         else if request.action is "toggle_console"
           unless _self.ui.toggleActivateAndShow?
-            window.console.warn "[feedlyconsole] ui not yet ready"
+            window.console.warn "[feedlyconsole/msg] ui not ready."
           else
             _self.ui.toggleActivateAndShow()
         else if request.action is "cookie_feedlytoken"
           unless _self.api
-            OAuth = request.feedlytoken
-
+            oauth = request.feedlytoken
             url_array = request.url.split("/")
             url = url_array[0] + "//" + url_array[2]
-            _console.debug """
-            [feedlyconsole/cookie_feedlytoken] url: %s oauth: %s.""",
+            _console.debug "[feedlyconsole/msg/cookie_feedlytoken]
+ api init, url: %s oauth: %s.",
               url,
-              _self.OAuth.slice(0, 8)
+              oauth.slice(0, 8)
             _self.api = new FeedlyApiRequest(url,
               FEEDLY_API_VERSION,
-              OAuth)
+              oauth)
           else
-            _console.debug """
-            [feedlyconsole/cookie_feedlytoken]
-             ignoreing, already initialized."""
+            _console.debug "[feedlyconsole/msg/cookie_feedlytoken]
+ ignoring, api, url, oauth already initialized."
         else
-          _console.debug "[feedlyconsole] unknown action %s request %O.",
+          _console.debug "[feedlyconsole/msg] unknown action %s request %O.",
           request.action, request
         sendResponse action: "ack"

     else  # running in webpage
       $(document).ready ->
-        _console.log "[Josh.FeedlyConsole] webpage, initialize."
+        _console.log "[feedlyconsole/init] webpage context, initialize."
         initialize()
+        _self.api = new FeedlyApiRequest("",
+          FEEDLY_API_VERSION,
+          null)
         _self.ui.activateAndShow()

     _self


Add missing unique tags.

Add missing unique tags.

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -7305,6 +7305,7 @@
         }
         {
             name: 'Saw Gerrera'
+            unique: true
             id: 261
             slot: 'Crew'
             faction: 'Rebel Alliance'
@@ -7312,6 +7313,7 @@
         }
         {
             name: 'Director Krennic'
+            unique: true
             id: 262
             slot: 'Crew'
             faction: 'Galactic Empire'


Cast Sale as an actual Auction

Cast Sale as an actual Auction

--- apps/feature/client/view.coffee
+++ apps/feature/client/view.coffee
@@ -9,6 +9,7 @@
 CurrentUser = require '../../../models/current_user.coffee'
 ArtworkColumnsView = require '../../../components/artwork_columns/view.coffee'
 Artworks = require '../../../collections/artworks.coffee'
+Auction = require '../../../models/auction.coffee'
 ShareView = require '../../../components/share/view.coffee'
 AuctionArtworksView = require '../../../components/auction_artworks/view.coffee'
 artworkColumns = -> require('../../../components/artwork_columns/template.jade') arguments...
@@ -50,7 +51,7 @@
         @auctionArtworks.add artworks.models
       else
         view = new AuctionArtworksView
-          model: @sale
+          model: new Auction @sale.toJSON()
           collection: @auctionArtworks = artworks
           user: @currentUser
         @$('#feature-artworks').html view.render().$el


Ignore errors, for now

Ignore errors, for now

--- app/config.coffee
+++ app/config.coffee
@@ -36,7 +36,7 @@
       getDeviceToken createdDevice?.uuid

     deviceFindCallback = (error, foundDevice) =>
-      return done error if error?
+      # return done error if error?
       return getDeviceToken foundDevice.uuid if foundDevice?
       deviceModel.create query, device, profileId, fakeSecret, deviceCreateCallback


Fixing JS handling top menu highlighting

--- src/js/src/ui.coffee
+++ src/js/src/ui.coffee
@@ -3,5 +3,5 @@
   $('.ui.dropdown').dropdown()

   # Mark 'About' button as active if page is About
-  if document.URL.includes 'about'
+  if document.URL.indexOf('about') > -1
     $('.menu-about').addClass('active')


yesterday, for brian

yesterday, for brian

--- app/assets/javascripts/init.coffee
+++ app/assets/javascripts/init.coffee
@@ -88,7 +88,7 @@
       if time > window.today
         return time.format("h:mma")
       else if time > window.yesterday
-        return "Yday #{time.format('h:mma')}"
+        return "Yesterday #{time.format('h:mma')}"
     if time.year() == moment().year()
       if preciseTime then time.format("MMM Do[,] h:mma") else time.format("MMM Do[,] ha")
     else


chart debug

chart debug

--- app/assets/javascripts/poll.coffee
+++ app/assets/javascripts/poll.coffee
@@ -74,6 +74,7 @@
     Q( $.post "/polls/#{app.pollId}/options/#{optionId}/vote")
     .then(
       (poll) =>
+        @chart.destroy()
         @setupChart(poll)
         $('.voted').removeClass('voted')
         $(".option[data-id=#{optionId}]").addClass('voted')


Forgot to start at the current node in the tree

--- scripts/mixins/tree.coffee
+++ scripts/mixins/tree.coffee
@@ -64,7 +64,8 @@

     getRoot: () ->
       root = null
-      while parent = getParent()
+      parent = @
+      while parent = parent.getParent()
         root = parent
       return root


Create tag on core:confirm

Create tag on core:confirm

--- lib/views/tag-create-view.coffee
+++ lib/views/tag-create-view.coffee
@@ -27,6 +27,7 @@
     @panel.show()
     @tagName.focus()
     @on 'core:cancel', => @destroy()
+    @on 'core:confirm', => @createTag()

   createTag: ->
     tag = name: @tagName.getModel().getText(), message: @tagMessage.getModel().getText()


simplify code

simplify code

--- src/nvim/nvim.coffee
+++ src/nvim/nvim.coffee
@@ -1,4 +1,3 @@
-async = require 'async'
 child_process = require 'child_process'
 Session = require 'msgpack5rpc'
 remote = require 'remote'
@@ -26,14 +25,9 @@
     @session.on 'notification', (method, args) =>
       if method == 'redraw' then @ui.handle_redraw args

-    async.series [
-      (_) => @ui_attach(_)
-    ], =>
+    @session.request 'ui_attach', [80, 40, true], =>
       @ui.on 'key', (e) =>
         @session.request 'vim_input', [e], =>

-  ui_attach: (callback) ->
-    @session.request 'ui_attach', [80, 40, true], callback
-

 module.exports = NVim


Add missing support to deprecation message

--- src/pane-view.coffee
+++ src/pane-view.coffee
@@ -162,7 +162,7 @@
       deprecate 'Please return a Disposable object from your ::onDidChangeTitle method!' unless disposable?.dispose?
       @activeItemDisposables.add(disposable) if disposable?.dispose?
     else if item.on?
-      deprecate '::on methods for items are no longer supported. If you would like your item to title change behavior, please implement a ::onDidChangeTitle() method.'
+      deprecate '::on methods for items are no longer supported. If you would like your item to support title change behavior, please implement a ::onDidChangeTitle() method.'
       disposable = item.on('title-changed', @activeItemTitleChanged)
       @activeItemDisposables.add(disposable) if disposable?.dispose?


subscription to make all user info available.

--- server/publications.coffee
+++ server/publications.coffee
@@ -7,6 +7,14 @@
 Meteor.publish 'events', -> Events.find {}, limit: 20, sort: timestamp: -1
 Meteor.publish 'storage', -> StorageBuckets.find {}, sort: name: 1
 Meteor.publish 'datastores', -> Datastores.find {}, sort: name: 1
+
+Meteor.publish 'allUserInfo', ->
+  if loggedInUser
+    Meteor.users.find {}, fields:
+      profile:
+        firstname: 1
+        lastname: 1
+        email: 1

 Meteor.publish 'allUsers', ->
   loggedInUser = @userId


fix Property.scan spec

fix Property.scan spec

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -750,11 +750,11 @@
 describe "Property.scan", ->
   it "with Init value, starts with f(seed, init)", ->
     expectPropertyEvents(
-      -> series(2,3).toProperty(1).scan(0, add)
+      -> series(1, [2,3]).toProperty(1).scan(0, add)
       [1, 3, 6])
   it "without Init value, starts with seed", ->
     expectPropertyEvents(
-      -> series(2,3).toProperty().scan(0, add)
+      -> series(1, [2,3]).toProperty().scan(0, add)
       [0, 2, 5])
   it "treats null seed value like any other value", ->
     expectPropertyEvents(


re-order tests

re-order tests

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1044,27 +1044,6 @@
   it "toString", ->
     expect(Bacon.later(1, "a").takeUntil(Bacon.later(2, "b")).toString()).to.equal("Bacon.later(1,a).takeUntil(Bacon.later(2,b))")

-describe "When an Event triggers another one in the same stream, while dispatching", ->
-  it "Delivers triggered events correctly", ->
-    bus = new Bacon.Bus
-    values = []
-    bus.take(2).onValue (v) ->
-      bus.push "A"
-      bus.push "B"
-    bus.onValue (v) ->
-      values.push(v)
-    bus.push "a"
-    bus.push "b"
-    expect(values).to.deep.equal(["a", "A", "A", "B", "B", "b"])
-  it "EventStream.take(1) works correctly (bug fix)", ->
-    bus = new Bacon.Bus
-    values = []
-    bus.take(1).onValue (v) ->
-      bus.push("onValue triggers a side-effect here")
-      values.push(v)
-    bus.push("foo")
-    expect(values).to.deep.equal(["foo"])
-
 describe "EventStream.awaiting(other)", ->
   describe "indicates whether s1 has produced output after s2 (or only the former has output so far)", ->
     expectPropertyEvents(
@@ -1701,6 +1680,28 @@
     expectStreamEvents(
       -> Bacon.repeatedly(t(1), [1, 2, 3]).toProperty().changes().take(1)
       [1])
+
+describe "When an Event triggers another one in the same stream, while dispatching", ->
+  it "Delivers triggered events correctly", ->
+    bus = new Bacon.Bus
+    values = []
+    bus.take(2).onValue (v) ->
+      bus.push "A"
+      bus.push "B"
+    bus.onValue (v) ->
+      values.push(v)
+    bus.push "a"
+    bus.push "b"
+    expect(values).to.deep.equal(["a", "A", "A", "B", "B", "b"])
+  it "EventStream.take(1) works correctly (bug fix)", ->
+    bus = new Bacon.Bus
+    values = []
+    bus.take(1).onValue (v) ->
+      bus.push("onValue triggers a side-effect here")
+      values.push(v)
+    bus.push("foo")
+    expect(values).to.deep.equal(["foo"])
+
 describe "independent observables created while dispatching", ->
   it "with combineAsArray", ->
     calls = 0


Only render if there are artworks

Only render if there are artworks

--- apps/artwork/client/view.coffee
+++ apps/artwork/client/view.coffee
@@ -51,6 +51,7 @@
   renderArtistArtworks: ->
     # Ensure the current artwork is not in the collection
     @artist.artworks.remove @artwork
+    return unless @artist.artworks.length
     @$('.artwork-artist').attr 'data-state', 'complete'
     @$('#artwork-artist-artworks-container').
       addClass('is-loaded').


Mocha 3 compat

Mocha 3 compat

--- spec/runner.coffee
+++ spec/runner.coffee
@@ -36,6 +36,7 @@
         obj[k] = item
       return obj
     .nodeify callback
+  return

 describe 'Runner', ->
   solver = null
@@ -415,7 +416,7 @@
     it 'each job should return correct result', (done) ->
       @timeout 9000
       options = {}
-      jobs =
+      jobs =
         returnerror:
           url: local 'return-error'
           payload: null


Tile circular dependency fixed

Tile circular dependency fixed

--- globe-geometry/mercator/tile.coffee
+++ globe-geometry/mercator/tile.coffee
@@ -4,7 +4,6 @@
 globeGeometry.mercator = globeGeometry.mercator || {}
 goog.provide 'globeGeometry.mercator.Tile'

-goog.require 'globeGeometry.mercator'
 goog.require 'globeGeometry.math'

 class globeGeometry.mercator.Tile


Fix typo

Fix typo

--- grunt/build-fontawesome-data.coffee
+++ grunt/build-fontawesome-data.coffee
@@ -3,7 +3,7 @@
 # Custom task, creates a yml file with all the Font Awesome classes
 # Based on the bs-glyphicons-data-generator.js from Bootstrap
 # https://github.com/twbs/bootstrap/blob/master/grunt/bs-glyphicons-data-generator.js
-module.expors = (grunt) ->
+module.exports = (grunt) ->

   iconsFile    = fs.readFileSync('vendor/font-awesome-stylus/stylus/icons.styl', 'utf8')
   iconsYml     = 'doc-src/data/fontawesome-icons.yml'


Manage error when retrieving email template

--- controller.coffee
+++ controller.coffee
@@ -54,6 +54,7 @@
                 (cb) -> mailSubject emailOptions, cb
                 (cb) -> mailTemplate emailOptions, cb
             ], (err, results) ->
+                return cb err if err
                 [subject, htmlContent] = results
                 emailInfo =
                     to: rule.email


default to only three pugs per bomb

default to only three pugs per bomb

--- scripts/pugme.coffee
+++ scripts/pugme.coffee
@@ -25,7 +25,7 @@
     msg.send pugArray[Math.floor(Math.random()*pugArray.length)]

   robot.respond /pug bomb( (\d+))?/i, (msg) ->
-    count = msg.match[2] || 5
+    count = msg.match[2] || 3
     msg.send pug for pug in pugArray.slice(0, count)

   robot.respond /how many pugs are there/i, (msg) ->


forgot to disable mail confirm again.

forgot to disable mail confirm again.

--- server/server.coffee
+++ server/server.coffee
@@ -2,7 +2,7 @@
 notes = new Meteor.Collection "notes"

 Accounts.config {
-  sendVerificationEmail: true
+  sendVerificationEmail: false
   loginExpirationInDays: 1
 }


Finalized email options for now

Finalized email options for now

--- server/server.coffee
+++ server/server.coffee
@@ -22,15 +22,18 @@
 module.exports = app = express()

 smtpTransport = nodemailer.createTransport('SMTP',
-  service: 'Gmail')
+  service: 'Gmail'
+  auth:
+    user: 'server.tranzit@gmail.com'
+    pass: 'dev@tranzit')

 # function for sending out emails
 sendEmails = ->
   mailOptions =
-    from: 'Tranzit Server'
+    from: 'Tranzit Server <server.tranzit@gmail.com>'
     to: 'aottinge@purdue.edu'
     subject: 'Package Pickup'
-    html: 'Come pick up your package you lazy asshole.'
+    html: 'Come pick up your package(s) you lazy asshole'
   smtpTransport.sendMail mailOptions, (error, response) ->
     if error
       console.log error
@@ -63,7 +66,7 @@
   schedule.scheduleJob({
     hour: 9
     minute: 0
-  }, sendEmails)
+  }, sendEmails())

   # begin listening for connections
   app.listen 3000, ->


Supervisord: Support multiple nginx upstream server creation

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -2,12 +2,15 @@

 createUpstreams = (workers={}) ->
   upstreams = "# add global upstreams\n"
-  for name, options of workers when options.nginx?.ports?
+  for name, options of workers when options.port?
     servers = ""
-    for port in options.nginx.ports
+    {port} = options
+    options.instances or= 1
+    for index in [0...options.instances]
       servers += "\n" if servers isnt ""
+      port = parseInt(port, 10)

-      servers += "\tserver 127.0.0.1:#{port};"
+      servers += "\tserver 127.0.0.1:#{port + index};"

     upstreams += """
       upstream #{name} {
@@ -46,7 +49,8 @@

 createLocations = (workers={}) ->
   locations = ""
-  for name, options of workers when options.nginx?.ports?
+  for name, options of workers when options.port?
+    options.nginx = {}  unless options.nginx
     location = ""

     options.nginx.locations or= ["/#{name}"]


Avoiding duplicate compileJoins on directory deletion

--- command.coffee
+++ command.coffee
@@ -172,6 +172,7 @@

   watchErr = (e) ->
     if e.code is 'ENOENT'
+      return if sources.indexOf source is -1
       removeSource source, base, yes
       compileJoin()
     else throw e
@@ -206,11 +207,6 @@

 # Watch a directory of files for new additions.
 watchDir = (source, base) ->
-  watchErr = (e) ->
-    toRemove = (file for file in sources when file.indexOf(source) >= 0)
-    removeSource file, base, yes for file in toRemove
-    compileJoin()
-
   try
     watcher = fs.watch source, ->
       fs.readdir source, (err, files) ->


engine: fix plugin tests

engine: fix plugin tests

--- packages/engine/src/plugins.coffee
+++ packages/engine/src/plugins.coffee
@@ -65,14 +65,14 @@
           # This check assume the plugin has the same hooks which is not always the case
           unless index[after]
             throw errors.PLUGINS_HOOK_AFTER_INVALID
-              event: event, module: module, after: after
+              event: event, module: hook.module, after: after
           [index[after], hook]
       edges_before = for hook in hooks
         continue unless hook.before
         for before in hook.before
           unless index[before]
             throw errors.PLUGINS_HOOK_BEFORE_INVALID
-              event: event, module: module, after: after
+              event: event, module: hook.module, before: before
           [hook, index[before]]
       edges = [...edges_after, ...edges_before]
       edges = array.flatten edges, 0
@@ -119,7 +119,7 @@
       'references an after dependency'
       "in plugin #{JSON.stringify after} which does not exists"
     ]
-  PLUGINS_HOOK_BEFORE_INVALID: ({event, module, after}) ->
+  PLUGINS_HOOK_BEFORE_INVALID: ({event, module, before}) ->
     throw error 'PLUGINS_HOOK_BEFORE_INVALID', [
       "the hook #{JSON.stringify event}"
       "in plugin #{JSON.stringify module}" if module


Don't translate screen to pixel positions if Editor isn't on the DOM

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -1191,6 +1191,7 @@
     @pixelPositionForScreenPosition(@screenPositionForBufferPosition(position))

   pixelPositionForScreenPosition: (position) ->
+    return { top: 0, left: 0 } unless @isOnDom()
     {row, column} = Point.fromObject(position)
     [lineElement] = @buildLineElementsForScreenRows(row, row)
     @renderedLines.append(lineElement)


Allow stylesheets with ' characters in their filename

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -159,7 +159,7 @@
   $(rootViewParentSelector).append(configView)

 window.stylesheetElementForId = (id) ->
-  $("head style[id='#{id}']")
+  $("""head style[id="#{id}"]""")

 window.resolveStylesheet = (path) ->
   if fsUtils.extension(path).length > 0


Adds a scale hacks to make the find & replace match the redacted char width

--- lib/minimap-find-results-view.coffee
+++ lib/minimap-find-results-view.coffee
@@ -15,7 +15,7 @@
   class MinimapFindResultsView extends FindResultsView
     attach: ->
       @getMinimap()?.miniOverlayer.append(this)
-      @width @getEditor().width()
+      @css '-webkit-tranform', 'scale3d(0.7,1,1)'

     getMinimap: ->
       minimapInstance.minimapForEditorView(@getEditor())


Converted dos to unix newlines

Converted dos to unix newlines

--- shared.common.coffee
+++ shared.common.coffee
@@ -1,25 +1,25 @@
-# Distribute cents to users deterministically
-exports.remainderDistribution = (users, remainder, transactionNumber) ->
-	result = {}
-	remaining = Math.round(remainder*100)
-	usersArray = []
-	for user, dummy of users
-		usersArray.push user
-	userCount = usersArray.length
-	return {} if remaining > userCount or remaining is 0
-	transactionNumberOffset = 0
-	while remaining isnt 0
-		selected = Math.floor(randomFromSeed(transactionNumber)*userCount)
-		selected-- if selected is userCount
-		toSet = remaining/Math.abs(remaining)
-		result[usersArray[selected]] = (result[usersArray[selected]]||0)+toSet
-		usersArray.splice selected, 1
-		remaining -= toSet
-		transactionNumberOffset += 10
-	log "[remainderDistribution] users=", JSON.stringify(users), "usersLeft=", usersArray, ", remainder="+remainder+", transactionNumber="+transactionNumber+", distributed="+transactionNumberOffset/10+", result="+JSON.stringify(result)
-	return result
-
-# Pseudo-random number from a seed
-randomFromSeed = (seed) ->
-	x = Math.sin(seed) * 10000
-	x-Math.floor(x)+# Distribute cents to users deterministically
+exports.remainderDistribution = (users, remainder, transactionNumber) ->
+	result = {}
+	remaining = Math.round(remainder*100)
+	usersArray = []
+	for user, dummy of users
+		usersArray.push user
+	userCount = usersArray.length
+	return {} if remaining > userCount or remaining is 0
+	transactionNumberOffset = 0
+	while remaining isnt 0
+		selected = Math.floor(randomFromSeed(transactionNumber)*userCount)
+		selected-- if selected is userCount
+		toSet = remaining/Math.abs(remaining)
+		result[usersArray[selected]] = (result[usersArray[selected]]||0)+toSet
+		usersArray.splice selected, 1
+		remaining -= toSet
+		transactionNumberOffset += 10
+	log "[remainderDistribution] users=", JSON.stringify(users), "usersLeft=", usersArray, ", remainder="+remainder+", transactionNumber="+transactionNumber+", distributed="+transactionNumberOffset/10+", result="+JSON.stringify(result)
+	return result
+
+# Pseudo-random number from a seed
+randomFromSeed = (seed) ->
+	x = Math.sin(seed) * 10000
+	x-Math.floor(x)


Clear entry error when leaving route

Clear entry error when leaving route

--- shared/router.coffee
+++ shared/router.coffee
@@ -5,6 +5,8 @@
     onBeforeAction: ->
       Session.set('buttonText', 'in')
       @next()
+    onStop: ->
+      Session.set('entryError', undefined)
     onRun: ->
       if Meteor.userId()
         Router.go AccountsEntry.settings.dashboardRoute
@@ -33,9 +35,10 @@
   @route "entrySignUp",
     path: "/sign-up"
     onBeforeAction: ->
-      Session.set('entryError', undefined)
       Session.set('buttonText', 'up')
       @next()
+    onStop: ->
+      Session.set('entryError', undefined)
     onRun: ->
       if AccountsEntry.settings.signUpTemplate
         @template = AccountsEntry.settings.signUpTemplate
@@ -60,39 +63,40 @@

   @route "entryForgotPassword",
     path: "/forgot-password"
-    onBeforeAction: ->
+    onStop: ->
       Session.set('entryError', undefined)
-      @next()

   @route 'entrySignOut',
     path: '/sign-out'
     onBeforeAction: ()->
-      Session.set('entryError', undefined)
       if not AccountsEntry.settings.homeRoute
         @next()
       else
         Meteor.logout () ->
           Router.go AccountsEntry.settings.homeRoute
+    onStop: ->
+      Session.set('entryError', undefined)

   @route 'entryVerificationPending',
     path: '/verification-pending'
-    onBeforeAction: ->
+    onStop: ->
       Session.set('entryError', undefined)
-      @next()

   @route 'entryResetPassword',
     path: 'reset-password/:resetToken'
     onBeforeAction: ->
-      Session.set('entryError', undefined)
       Session.set('resetToken', @params.resetToken)
       @next()
+    onStop: ->
+      Session.set('entryError', undefined)

   @route 'entryEnrollAccount',
     path: 'enroll-account/:resetToken'
     onBeforeAction: ->
-      Session.set('entryError', undefined)
       Session.set('resetToken', @params.resetToken)
       @next()
+    onStop: ->
+      Session.set('entryError', undefined)

 # Get all the accounts-entry routes one time
 exclusions = []


:bug: Fix region hack not testing the right screen line

--- lib/renderers/region-renderer.coffee
+++ lib/renderers/region-renderer.coffee
@@ -22,8 +22,7 @@
           column: Infinity
         },
         colorMarker,
-        displayBuffer.screenLines[range.start.row],
-        displayBuffer.screenLines[range.start.row + 1]
+        displayBuffer.screenLines[range.start.row]
       )
       if rowSpan > 1
         for row in [range.start.row + 1...range.end.row]
@@ -31,21 +30,19 @@
             {row, column: 0},
             {row, column: Infinity},
             colorMarker,
-            displayBuffer.screenLines[row],
-            displayBuffer.screenLines[row + 1]
+            displayBuffer.screenLines[row]
           )

       regions.push @createRegion(
         {row: range.end.row, column: 0},
         range.end,
         colorMarker,
-        displayBuffer.screenLines[range.end.row],
-        displayBuffer.screenLines[range.end.row + 1]
+        displayBuffer.screenLines[range.end.row]
       )

     regions

-  createRegion: (start, end, colorMarker, screenLine, nextScreenLine) ->
+  createRegion: (start, end, colorMarker, screenLine) ->
     displayBuffer = colorMarker.marker.displayBuffer
     lineHeight = displayBuffer.getLineHeightInPixels()
     charWidth = displayBuffer.getDefaultCharWidth()
@@ -64,8 +61,9 @@
       end: clippedEnd
     })

-    if nextScreenLine?.isSoftWrapped() and end.column is Infinity
-      bufferRange.end.column++
+    needAdjustment = screenLine?.isSoftWrapped() and end.column >= screenLine?.text.length - screenLine?.softWrapIndentationDelta
+
+    bufferRange.end.column++ if needAdjustment

     startPosition = displayBuffer.pixelPositionForScreenPosition(clippedStart)
     endPosition = displayBuffer.pixelPositionForScreenPosition(clippedEnd)
@@ -76,7 +74,7 @@
     css.left = startPosition.left
     css.top = startPosition.top
     css.width = endPosition.left - startPosition.left
-    css.width += charWidth if nextScreenLine?.isSoftWrapped() and end.column is Infinity
+    css.width += charWidth if needAdjustment
     css.height = lineHeight

     region = document.createElement('div')


Shorten commands tree

Shorten commands tree

--- src/cmd/abbrev.coffee
+++ src/cmd/abbrev.coffee
@@ -9,5 +9,15 @@
       z = tree
       for c in name.split ''
         z = z[c] ||= {}
-      z['.'] = name
+      z['!'] = x.name
+  do shorten = (rec = tree)->
+    for k, v of rec
+      if '!' == k or v = shorten v
+        val = v unless val?
+        many = true if val != v
+      else
+        many = true
+    return if many
+    delete rec[k] for k of rec
+    rec['!'] = val
   echo yaml.dump tree


resend when 301

resend when 301

--- src/codinglove.coffee
+++ src/codinglove.coffee
@@ -43,7 +43,7 @@
   message.http(url).get() (error, response, body)->
     return response_handler "Sorry, something went wrong" if error

-    if response.statusCode == 302
+    if response.statusCode == 302 || response.statusCode == 301
       location = response.headers['location']
       return send_meme(message, location, response_handler)


defensive programming before variable loading

--- client/coffee/helpers.coffee
+++ client/coffee/helpers.coffee
@@ -204,7 +204,7 @@
     tbl = Session.get('Tbl')
     thisId = Meteor.userId()
     if not thisId? or not tbl? then return false
-    if "superuser" in Meteor.user().roles then return true
+    if Meteor.user() and "superuser" in Meteor.user().roles then return true
     if thisId is tbl.user_id then return true
     for user in tbl.user_roles
         if thisId is user.user_id and user.role isnt "reviewers" then return true


Set keepAlive on connected socket.

Set keepAlive on connected socket.

#198

--- src/connection.coffee
+++ src/connection.coffee
@@ -449,7 +449,6 @@

   connectOnPort: (port) ->
     @socket = new Socket({})
-    @socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY)

     connectOpts =
       host: @config.server
@@ -536,6 +535,7 @@
     @dispatchEvent('socketError', error)

   socketConnect: =>
+    @socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY)
     @closed = false
     @debug.log("connected to #{@config.server}:#{@config.options.port}")
     @dispatchEvent('socketConnect')


fix caret position error after inserting

--- src/controller.coffee
+++ src/controller.coffee
@@ -138,7 +138,9 @@
       range.setStart(range.endContainer, Math.max(pos,0))
       range.setEnd(range.endContainer, range.endOffset)
       range.deleteContents()
-      range.insertNode $(wrapped_content, @app.document)[0]
+      content_node = $(wrapped_content, @app.document)[0]
+      range.insertNode content_node
+      range.setEndAfter content_node
       range.collapse(false)
       sel = @app.window.getSelection()
       sel.removeAllRanges()


Hack to avoid integration bug with extra Ace editor lines

--- src/controller.coffee
+++ src/controller.coffee
@@ -2302,8 +2302,8 @@
             y: textElement.bounds[0].y - @scrollOffsets.main.y
           vector: translationVectors[i]

-      top = @aceEditor.getFirstVisibleRow() #@findLineNumberAtCoordinate @scrollOffsets.main.y
-      bottom = @aceEditor.getLastVisibleRow() #@findLineNumberAtCoordinate @scrollOffsets.main.y + @mainCanvas.height
+      top = Math.max @aceEditor.getFirstVisibleRow(), 0
+      bottom = Math.min @aceEditor.getLastVisibleRow(), @view.getViewFor(@tree).lineLength - 1
       aceScrollTop = @aceEditor.session.getScrollTop()

       treeView = @view.getViewNodeFor @tree
@@ -2448,8 +2448,8 @@
               y: textElement.bounds[0].y - @scrollOffsets.main.y + translationVectors[i].y
             vector: translationVectors[i]

-        top = @aceEditor.getFirstVisibleRow() #@findLineNumberAtCoordinate @scrollOffsets.main.y
-        bottom = @aceEditor.getLastVisibleRow() #@findLineNumberAtCoordinate @scrollOffsets.main.y + @mainCanvas.height
+      top = Math.max @aceEditor.getFirstVisibleRow(), 0
+      bottom = Math.min @aceEditor.getLastVisibleRow(), @view.getViewFor(@tree).lineLength - 1

         treeView = @view.getViewNodeFor @tree
         lineHeight = @aceEditor.renderer.layerConfig.lineHeight


Fix missing moment

Fix missing moment

--- client/source/helpers.coffee
+++ client/source/helpers.coffee
@@ -1,6 +1,8 @@
 Handlebars = require 'hbsfy/runtime'
 mediator = require('chaplin').mediator
 _ = require 'underscore'
+moment = require 'moment'
+
 require 'helpers/forms'

 Swag.registerHelpers Handlebars


Refs #68; Began writing option listeners.

--- src/core/chart.coffee
+++ src/core/chart.coffee
@@ -63,6 +63,10 @@
     width: 320
     height: 240

+  optionListeners =
+    'option:width': 'dimensionsChanged'
+    'option:height': 'dimensionsChanged'
+
   # Creates a new base chart.
   # @param [Object] options Options to set for this chart.
   # @option options [Integer] width Sets an explicit width for the visualization (optional).
@@ -81,6 +85,8 @@
     else
       @width = defaults.width unless @width?
       @height = defaults.height unless @height?
+
+    @onAll optionListeners

   # @return [Object] A copy of this charts options.
   _getAllOptions: ->
@@ -201,6 +207,12 @@
       d3.min(@data, (layer) -> d3.min(layer.values, cmp)),
       d3.max(@data, (layer) -> d3.max(layer.values, cmp))
     ]
+
+  # Updates the width and height members and container dimensions in response to an
+  # 'option:width' or 'option:height' event.
+  dimensionsChanged: ->
+    [@width, @height] = [@option('width'), @option('height')]
+    @el.attr('width', @width).attr('height', @height) if @el?

 # Base class for all SVG charts (via d3).
@@ -221,6 +233,11 @@
         width: @width,
         height: @height

+  # Resizes the svg element in response to a 'option:width' or 'option:height' event.
+  dimensionsChanged: ->
+    super()
+    @svg.attr('width', @width).attr('height', @height)
+
 # Base Class for all Canvas based charts.
 class Epoch.Chart.Canvas extends Epoch.Chart.Base
   # Initializes the chart and places the rendering canvas in the specified


Test to ensure that creator is valid

Test to ensure that creator is valid

Providing a better error message is always useful

--- src/core/store.coffee
+++ src/core/store.coffee
@@ -102,6 +102,9 @@
     return obj if creator.models_only
     return observable if observable = @find(obj, creator)

+    unless _.isFunction(creator.create or creator)
+      throw new Error "Invalid factory for \"#{options.path}\""
+
     observable = kb.ignore =>
       options = _.defaults({store: @, creator: creator}, options) # set our own creator so we can register ourselves above
       observable = if creator.create then creator.create(obj, options) else new creator(obj, options)


added comments

added comments

--- src/Watcher.coffee
+++ src/Watcher.coffee
@@ -7,9 +7,13 @@

 module.exports = class Watcher extends events.EventEmitter

+	# Constructor
+	# @param {RegExp} ignore
 	constructor: (@ignore = /^\./) ->
 		@watchers = {}

+	# Watch a 'source' file or directory for changes
+	# @param {String} source
 	watch: (source) ->
 		unless @ignore.test(path.basename(source))
 			stats = fs.statSync(source)
@@ -40,6 +44,8 @@
 					@unwatch(source)
 					@emit('delete', source)

+	# Stop watching a 'source' file or directory for changes
+	# @param {String} source
 	unwatch: (source) ->
 		if @watchers[source]?
 			try
@@ -47,5 +53,6 @@
 			catch err
 			delete @watchers[source]

+	# Stop watching all sources for changes
 	clean: ->
 		@unwatch(source) for source of @watchers


fixing some typos and bugs

fixing some typos and bugs

--- src/highlights.coffee
+++ src/highlights.coffee
@@ -100,11 +100,19 @@
   # cb(err) - The callback so you know when it's done
   #
   requireGrammars: ({modulePath}={},cb) ->
-    @loadGrammars((err)->
+    console.log('REQUIRE GRAMMARS: start')
+    @loadGrammars((err)=>
+
+      console.log('REQURE GRAMMARS: load grammars error? ',err)
+
       if err
         return cb(err)

-      fs.stat(modulePath,(err,stat)->
+      fs.stat(modulePath,(err,stat)=>
+
+        console.log('REQUIRE GRAMMARS: stat')
+        console.log('fs.stats on modulePath!!!!!! -------->',stat)
+
         if err
           return cb(err)

@@ -113,10 +121,13 @@
         else if stat.isDirectory()
           packageDir = modulePath
         else
-          # return with no error at all if i cant find the module dir...... copying the sync impl. =(
+          # return with no error at all if i cant find the module dir.
           return cb()

-        @_registryLoadGrammarsDir(packageDir,cb)
+
+        grammarsDir = path.resolve(packageDir, 'grammars')
+        console.log('grammarsDir  --------> ',grammarsDir)
+        @_registryLoadGrammarsDir(grammarsDir,cb)

       )

@@ -131,21 +142,23 @@
       if !--todo
         cb()

-    fs.readdir(dir, (err, files)->
+    fs.readdir(dir, (err, files)=>
       if err
         return cb(err)
       todo = files.length

-      grammarPath = path.join(dir, file)
-      # CSON.resolve uses fs.isFileSync we'll have to check it in the next step but only on valid files.
-      if CSON.isObjectPath(grammarPath)
-        @_registryLoadGrammar(grammarPath,(err)->
-          done(err)
-        )
+      while files.length
+        file = files.shift()
+        grammarPath = path.join(dir, file)
+        # CSON.resolve uses fs.isFileSync we'll have to check it in the next step but only on valid files.
+        if CSON.isObjectPath(grammarPath)
+          @_registryLoadGrammar(grammarPath,(err)->
+            done(err)
+          )
     )

   _registryLoadGrammar:(grammarPath,cb)->
-    fs.stat(grammarPath,(err,stat)->
+    fs.stat(grammarPath,(err,stat)=>
       if err
         return cb(err)

@@ -211,7 +224,11 @@
       return @_loadingGrammars.push(cb)

     @_loadingGrammars = [cb]
-    callbacks = (err)=>
+    callbacks = (err) =>
+      console.log('LOADGRAMMARS: finished loading. unrolling ',@_loadingGrammars)
+      if err
+        console.log(err+''+err.stack)
+
       cbs = @_loadingGrammars
       @_loadingGrammars = true
       while cbs.length
@@ -238,14 +255,15 @@
     )

   _populateGrammars: (grammarsFromJSON,grammarsArray,cb) ->
-    toLoad = grammarsArray.length
+    toLoad = (grammarsArray||[]).length
     grammars = []

     done = (err,grammar)=>
       if err
         return cb(err)

-      grammars.push(grammar)
+      if grammar
+        grammars.push(grammar)

       if !--toLoad
         # complete loading from grammars.json
@@ -256,6 +274,10 @@

         cb(false,true)

+    if(!toLoad)
+      toLoad = 1
+      return done()
+
     while grammarsArray.length
       @registry.loadGrammar(grammarsArray.shift(),done)


Send image URL after 3 seconds

Send image URL after 3 seconds

--- src/datadog.coffee
+++ src/datadog.coffee
@@ -11,9 +11,6 @@
 #
 # Author
 #   tombell
-
-fs    = require 'fs'
-https = require 'https'

 dog    = require 'dogapi'
 moment = require 'moment'
@@ -48,15 +45,9 @@
     client.add_snapshot snapshot, (err, result, status) ->
       return msg.send "Could not generate the graph snapshot: #{err}" if err?

-      imageUrl = result['snapshot_url']
-      file = fs.createWriteStream('file.jpg')
-
-      request = https.get imageUrl, (response) ->
-        response.pipe(file)
-
-        file.on 'finish', ->
-          file.close ->
-            msg.send imageUrl
+      setTimeout ->
+        msg.send result['snapshot_url']
+      , 3000

   robot.respond /metric(s)? search (.*)/i, (msg) ->
     metric = msg.match[2]


reset HTML page title according to Docset guidelines

--- docset.coffee
+++ docset.coffee
@@ -32,6 +32,7 @@
 for title, file of FILES
   $ = cheerio.load fs.readFileSync("html/#{file}")
   docset.Guide[title] = "#{file}"
+  $('title').text(title)
   $('h2').each populateEntry(file, 'Section')
   $('h3').each populateEntry(file, 'Function')
   fs.writeFileSync "handlebars.docset/Contents/Resources/Documents/#{file}", $.html()


added interval

added interval

--- src/socialist.coffee
+++ src/socialist.coffee
@@ -1,3 +1,5 @@
+
+
 class Socialist
 	constructor : (@_func)->
 	CBs : {}
@@ -5,6 +7,20 @@
 		self = {}
 		self.send = (m)->
 			cb null, m
+		self.setTimeout = (vCallback, nDelay) -> #, argumentToPass1, argumentToPass2, etc.
+			__nativeST__ = window.setTimeout
+			oThis = this
+			aArgs = Array::slice.call(arguments_, 2)
+			__nativeST__ (if vCallback instanceof Function then ->
+				vCallback.apply oThis, aArgs
+			else vCallback), nDelay
+		self.setInterval = (vCallback, nDelay) -> #, argumentToPass1, argumentToPass2, etc.
+			__nativeSI__ = window.setInterval
+			oThis = this
+			aArgs = Array::slice.call(arguments_, 2)
+			__nativeSI__ (if vCallback instanceof Function then ->
+				vCallback.apply oThis, aArgs
+			else vCallback), nDelay
 		try
 			cb null, @_func.apply(self,data)
 			return true


Only look up linkHintNumbers once.

Only look up linkHintNumbers once.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -456,6 +456,7 @@
 # Use numbers (usually) for hints, and also filter links by their text.
 class FilterHints
   constructor: ->
+    @linkHintNumbers = Settings.get "linkHintNumbers"
     @activateOnEnter = true
     @hintKeystrokeQueue = []
     @linkTextKeystrokeQueue = []
@@ -476,7 +477,7 @@
         @labelMap[forElement] = labelText

   generateHintString: (linkHintNumber) ->
-    (numberToHintString linkHintNumber + 1, Settings.get "linkHintNumbers").toUpperCase()
+    numberToHintString linkHintNumber + 1, @linkHintNumbers.toUpperCase()

   generateLinkText: (element) ->
     linkText = ""
@@ -536,7 +537,7 @@
     { linksMatched: linksMatched, delay: delay }

   pushKeyChar: (keyChar, keydownKeyChar) ->
-    if 0 <= Settings.get("linkHintNumbers").indexOf keyChar
+    if 0 <= @linkHintNumbers.indexOf keyChar
       @hintKeystrokeQueue.push keyChar
     else
       # Since we might renumber the hints, we should reset the current hintKeyStrokeQueue.


update free access level (#5554)

update free access level (#5554)

--- app/views/play/CampaignView.coco.coffee
+++ app/views/play/CampaignView.coco.coffee
@@ -996,7 +996,8 @@
     levelOriginal = levelElement.data('level-original')
     level = _.find _.values(@getLevels()), slug: levelSlug

-    requiresSubscription = level.requiresSubscription or (me.isOnPremiumServer() and not (level.slug in ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith', 'signs-and-portents', 'true-names']))
+    freeAccessLevels =  ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'enemy-mine', 'cell-commentary', 'kithgard-librarian', 'the-prisoner', 'fire-dancing', 'haunted-kithmaze', 'signs-and-portents', 'true-names']
+    requiresSubscription = level.requiresSubscription or (me.isOnPremiumServer() and not (level.slug in freeAccessLevels))
     canPlayAnyway = _.any([
       not @requiresSubscription
       level.adventurer


gamestate

gamestate

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -26,6 +26,17 @@
       requestedState = null

   # TODO: Drawing, cameras??
+  # Cameras should be components of GameStates
+  # Draw will pass through and trigger all the
+  # events in the game state
+  # Engine will still respond to beforeDraw
+  # and overlay to add overlays that apply to any
+  # game state
+
+  self.bind "draw", (canvas) ->
+    I.currentState.trigger "beforeDraw", canvas
+    I.currentState.trigger "draw", canvas
+    I.currentState.trigger "overlay", canvas

   # We must always return self as the last line
   return {


Added functions for modifying XP and recalc stats

--- src/entities/creature.coffee
+++ src/entities/creature.coffee
@@ -51,6 +51,20 @@

 	isPlayer: ->
 		@ is game.player.creature
+
+	setXp: (xp) ->
+		oldLvl = @level
+		@xp = xp
+		newLvl = @level
+
+		if newLvl isnt oldLvl
+			dlvl = newLvl - oldLvl
+
+			@recalculateStats()
+			game.emit 'game.creature.level-change', @, newLvl, oldLvl
+
+	addXp: (dxp) ->
+		@setXp @xp + dxp

 	baseStat: (stat, params...) ->
 		if stat in ['strength', 'endurance', 'agility']


removed unnecessary require's in update_indicator_data.coffee

--- server/mixins/update_indicator_data.coffee
+++ server/mixins/update_indicator_data.coffee
@@ -1,8 +1,5 @@
-async = require('async')
 Q = require('q')
-request = require('request')
 _ = require('underscore')
-

 module.exports =
   statics: {}


fix time format in chat text.

fix time format in chat text.

--- src/frontend/textChat.coffee
+++ src/frontend/textChat.coffee
@@ -59,7 +59,7 @@
         else
             elem       = ($ '<div>', { class : 'msgBox', rel : do time.getTime })
             authorLine = ($ ('<p>')).append ($ '<span>', { class : 'author' }).html message.from
-            authorLine.append ($ '<span>', { class : 'time' }).html (do time.getHours + ':' + do time.getMinutes)
+            authorLine.append ($ '<span>', { class : 'time' }).html ((do time.toTimeString).substr 0, 5)
             elem.append authorLine
             elem.append ($ '<p>', {class : 'message'}).html message.text
             (do @scrollPane.getContentPane).append elem


Fix post rendering

Fix post rendering

--- assets/javascripts/views/post.js.coffee
+++ assets/javascripts/views/post.js.coffee
@@ -134,6 +134,7 @@
   render: (context = @context()) =>
     html = @template.render(context, @parentView.partials)
     el = ($ html)
-    @$el.html(el.html())
+    @$el.replaceWith(el)
+    @setElement el
     @trigger 'ready'


Read from Error message

Read from Error message

--- spec/Thenable.coffee
+++ spec/Thenable.coffee
@@ -1140,10 +1140,8 @@
         Root()
         .deliver()
         .contest [loser, loser, loser, loser, loser]
-          , (c, results) ->
-            results[0]
         .else (c, res) ->
-          chai.expect(res.reason).to.eql 'loser'
+          chai.expect(res.message).to.eql 'loser'
           done()

@@ -1630,7 +1628,6 @@
           ])

         sections = [videoTextSection, imageSection, textSection]
-
         layout = (n) ->
           n.tree 'layout'
           .deliver()
@@ -1796,7 +1793,6 @@
         ]
       .then layout
       .finally (n, results) ->
-        console.log results
         chai.expect(results.length).to.equal 3
         pathStrings = results.map (r) ->
           r.pathString


simple test tweak

simple test tweak


Remove unneded focus() call

Remove unneded focus() call

focus() is called from attach()

--- src/tree-view.coffee
+++ src/tree-view.coffee
@@ -135,7 +135,6 @@

   revealActiveFile: ->
     @attach()
-    @focus()

     return unless activeFilePath = @rootView.getActiveEditor()?.getPath()


Improve messaging when installing Git package

--- src/install.coffee
+++ src/install.coffee
@@ -498,7 +498,6 @@
         next(err, data)

     tasks.push (data, next) =>
-      console.log()
       @installGitPackageDependencies cloneDir, options, (err) ->
         next(err, data)

@@ -525,7 +524,13 @@
     tasks.push (data, next) =>
       {name} = data.metadata
       targetDir = path.join(@atomPackagesDirectory, name)
-      fs.cp cloneDir, targetDir, next
+      process.stdout.write "Moving #{name} to #{targetDir} "
+      fs.cp cloneDir, targetDir, (err) =>
+        if err
+          next(err)
+        else
+          @logSuccess()
+          next()

     iteratee = (currentData, task, next) -> task(currentData, next)
     async.reduce tasks, {}, iteratee, callback


Fix inconsistent indentation

Fix inconsistent indentation

--- views/env.coffee
+++ views/env.coffee
@@ -53,21 +53,21 @@
     detail: details
   window.dispatchEvent event
 window.log = (msg, options) -> poiAlert window.jQuery.extend({
-    message: msg,
-    type: 'default',
-    priority: 0}, options)
+  message: msg,
+  type: 'default',
+  priority: 0}, options)
 window.success = (msg, options) -> poiAlert window.jQuery.extend({
-    message: msg,
-    type: 'success',
-    priority: 1}, options)
+  message: msg,
+  type: 'success',
+  priority: 1}, options)
 window.warn = (msg, options) -> poiAlert window.jQuery.extend({
-    message: msg,
-    type: 'warning',
-    priority: 2}, options)
+  message: msg,
+  type: 'warning',
+  priority: 2}, options)
 window.error = (msg, options) -> poiAlert window.jQuery.extend({
-    message: msg,
-    type: 'warning',
-    priority: 4}, options)
+  message: msg,
+  type: 'warning',
+  priority: 4}, options)

 NOTIFY_DEFAULT_ICON = path.join(ROOT, 'assets', 'icons', 'icon.png')
 NOTIFY_NOTIFICATION_API = true


fix createItem bug

fix createItem bug

--- views/index.cjsx
+++ views/index.cjsx
@@ -518,7 +518,7 @@
         dataItem.push postBody.api_item1, postBody.api_item2, postBody.api_item3, postBody.api_item4
         @_ships = window._ships
         @_decks = window._decks
-        dataItem.push "#{@_ships[_decks[0].api_ship[0]].api_name}(Lv.#{@_ships[_decks[0].api_ship[0]].api_lv})"
+        dataItem.push "#{@_ships[@_decks[0].api_ship[0]].api_name}(Lv.#{@_ships[@_decks[0].api_ship[0]].api_lv})"
         dataItem.push window._teitokuLv
         {createItemData} = @state
         createItemData.unshift dataItem


:white_check_mark: Add tests for images and archives

--- spec/pane-view-spec.coffee
+++ spec/pane-view-spec.coffee
@@ -7,7 +7,7 @@
 temp = require 'temp'

 describe "PaneView", ->
-  [container, containerModel, view1, view2, editor1, editor2, pane, paneModel, deserializerDisposable] = []
+  [container, containerModel, view1, view2, editor1, editor2, image, archive, pane, paneModel, deserializerDisposable] = []

   class TestView extends View
     @deserialize: ({id, text}) -> new TestView({id, text})
@@ -37,10 +37,16 @@
     waitsForPromise ->
       atom.workspace.open('sample.txt').then (o) -> editor2 = o

+    waitsForPromise ->
+      atom.workspace.open('sample.png').then (o) -> image = o
+
+    waitsForPromise ->
+      atom.workspace.open('sample.zip').then (o) -> archive = o
+
     runs ->
       pane = container.getRoot()
       paneModel = pane.getModel()
-      paneModel.addItems([view1, editor1, view2, editor2])
+      paneModel.addItems([view1, editor1, view2, editor2, image, archive])

   afterEach ->
     deserializerDisposable.dispose()
@@ -118,10 +124,14 @@
         paneModel.activateItem(view2)
         expect(pane.itemViews.find('#view-2').length).toBe 1

-    describe "when the active item implements ::getPath", ->
+    describe "when the new activeItem implements ::getPath", ->
       fit "adds .has-file-path to the active item element", ->
         paneModel.activateItem(editor1)
         expect(pane.itemViews.find('atom-text-editor')).toHaveClass('has-file-path')
+        paneModel.activateItem(image)
+        expect(pane.itemViews.find('.image-view')).toHaveClass('has-file-path')
+        paneModel.activateItem(archive)
+        expect(pane.itemViews.find('.archive-editor')).toHaveClass('has-file-path')

   describe "when an item is destroyed", ->
     it "triggers the 'pane:item-removed' event with the item and its former index", ->


Fix Windows error message when failing to open externally

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -2874,7 +2874,11 @@

       runs ->
         expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Opening folder in Finder failed'
-        expect(atom.notifications.getNotifications()[0].getDetail()).toContain 'ENOENT'
+
+        if process.platform isnt 'win32'
+          expect(atom.notifications.getNotifications()[0].getDetail()).toContain 'ENOENT'
+        else # Typical Windows
+          expect(atom.notifications.getNotifications()[0].getDetail()).toContain 'The system cannot find the path specified.'

   describe "when reloading a directory with deletions and additions", ->
     it "does not throw an error (regression)", ->


fix bug where two parent blocks couldn't point to the same child block


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -19,7 +19,7 @@

 enemy.meter 'health'
 # => Sets up a health meter that will be drawn during the enemy overlay event.
-# Since maxHealth
+# Since maxHealth wasn't provided

 # now player has all the methods provided by this module
 player.position()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -197,7 +197,6 @@

   # creates a health meter but disables visibility
   player.meter 'health'
-    show: false

   # enables visibility for the meter named 'health'
   player.showMeter 'health'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -109,7 +109,7 @@
   player.meter
     borderColor: 'brown'
     color: 'pink'
-    font: '30px Com'
+    font: '30px Comic Sans'
     name: 'health'
     position:
       x: 5


Fix lint error

Fix lint error

--- src/app.coffee
+++ src/app.coffee
@@ -295,7 +295,7 @@
       filename = config.get("customFields:filesById:#{field}:downloadAs") || field
       filename = filename.replace('#{id}', req.params.id)

-      res.setHeader('content-disposition', "attachment; filename=\"#{filename}\"");
+      res.setHeader('content-disposition', "attachment; filename=\"#{filename}\"")
       res.send(contents)

   breakpad.use(busboy())


move browser fail to top

move browser fail to top

--- src/app.coffee
+++ src/app.coffee
@@ -4,6 +4,9 @@

 root = @
 if not root.app? then app = root.app = {} else app = root.app
+
+# Deal with old browsers
+app.views.badModalView({el: "body"}).render()

 #Initialize the map
 center = new L.LatLng 34.11180455556899, -111.7144775390625
@@ -530,7 +533,4 @@
   el: $ "#geocoder"

 # Add a scalebar
-app.map.addControl new L.Control.Scale()
-
-# Deal with old browsers
-app.views.badModalView({el: "body"}).render()+app.map.addControl new L.Control.Scale()