reformatted for readability and better comments

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -1,23 +1,64 @@
 Browserify = Npm.require 'browserify'
+
+# get 'stream' to use PassThrough to provide a Buffer as a Readable stream
 stream     = Npm.require 'stream'

-getString = (bundle, cb) -> # async function reading entire bundle
-  string = '' # holds all as a string
+# async function for reading entire bundle output into a string
+getString = (bundle, cb) ->
+
+  # holds all data read from bundle
+  string = ''
+
+  # concatenate data chunk to string
   bundle.on 'data', (data) -> string += data
+
+  # when we reach the end, call Meteor.wrapAsync's callback with string result
   bundle.once 'end', -> cb undefined, string  # undefined = error

-Plugin.registerSourceHandler 'browserify.js', (step) ->
-  # TODO: how to know if it's production or dev? change value of debug...
-  # TODO: inputPath may include directories we need to strip for basedir
-  readable = new stream.PassThrough()   # hold Buffer as Readable stream
-  readable.end step.read()              # put Buffer into stream
+# TODO: how to know if it's production or dev? change value of debug...
+# TODO: inputPath may include directories we need to strip for basedir
+processFile = (step) ->
+
+  # Meteor's CompileStep provides the file as a Buffer
+  buffer = step.read()
+
+  # Browserify accepts a Readable stream as input, so, we'll use a PassThrough
+  # stream to hold the Buffer
+  readable = new stream.PassThrough()
+
+  # add the buffer into the stream and end the stream with one call to end()
+  readable.end buffer
+
+  # must tell Browserify where to find npm modules.
+  # ComppileStep has the absolute path to the file in `fullInputPath`
+  # CompileStep has the name of the file in `inputPath`
+  # so basedir is the fullInputPath with the file removed and '.npm/package' added
   basedir = step.fullInputPath.slice(0, -(step.inputPath.length)) + '.npm/package'
+
+  # create a browserify instance passing our readable stream as input,
+  # and options with debug set to true for a dev build, and the basedir
   browserify = Browserify [readable], debug:true, basedir:basedir
+
+  # have browserify process the file and include all required modules.
+  # we receive a readable stream as the result
   bundle = browserify.bundle()
-  bundle.setEncoding('utf8')          # work with strings
-  string = Meteor.wrapAsync(getString)(bundle) # wrap for sync use and call it
-  step.addJavaScript                  # add result as new JS file
-    path:       step.inputPath
-    sourcePath: step.inputPath
-    data:       string
+
+  # set the readable stream's encoding so we read strings from it
+  bundle.setEncoding('utf8')
+
+  # use Meteor.wrapAsyn to wrap `getString` so it's done synchronously and
+  # returns the result right here when it's done
+  wrappedFn = Meteor.wrapAsync getString
+
+  # call our wrapped function with the readable stream as its argument
+  string = wrappedFn bundle
+
+  # now that we have the compiled result as a string we can add it using CompileStep
+  step.addJavaScript
+    path:       step.inputPath  # name of the file
+    sourcePath: step.inputPath  # use same name, we've just browserified it
+    data:       string          # the actual browserified results
     bare:       step?.fileOptions?.bare
+
+# add our function as the handler for files ending in 'browserify.js'
+Plugin.registerSourceHandler 'browserify.js', processFile


add isAuth policy to route 'POST /api/user'

--- config/policies.coffee
+++ config/policies.coffee
@@ -2,7 +2,7 @@
 	policies:
 		UserController:
 			'*':		false
-			create:		['isAdmin']
+			create:		['isAuth', 'isAdmin']
 			find:		['isAuth']
 			findOne:	['isAuth', 'user/me']
 			profile:	true


calling drsh via bash

calling drsh via bash

--- src/scripts/drush.coffee
+++ src/scripts/drush.coffee
@@ -14,7 +14,7 @@
       return

     sitelist = 'Site Aliases Available: \n'
-    sitealias = spawn("drush", ["sa"],
+    sitealias = spawn("bash", ["drush", "sa"],
       cwd: undefined,
       env: process.env,
       uid: Number process.env.DRUSH_UID


don't crash if no tweets are found

don't crash if no tweets are found

--- src/scripts/tweet.coffee
+++ src/scripts/tweet.coffee
@@ -10,5 +10,9 @@
       .query(q: search)
       .get() (err, res, body) ->
         tweets = JSON.parse(body)
-        tweet  = msg.random tweets.results
-        msg.send "http://twitter.com/#!/#{tweet.from_user}/status/#{tweet.id_str}"
+
+        if tweets.results? and tweets.results.length > 0
+          tweet  = msg.random tweets.results
+          msg.send "http://twitter.com/#!/#{tweet.from_user}/status/#{tweet.id_str}"
+        else
+          msg.reply "No one is tweeting about that."


Adapta o código para Coffee mais nativo

Adapta o código para Coffee mais nativo

--- src/coffee/portal-minicart.coffee
+++ src/coffee/portal-minicart.coffee
@@ -1,22 +1,14 @@
 $ = window.jQuery

-pluginName = 'vtexMinicart'
-defaults = {
-	timeoutToHide: null
-	cartData: null
-	$template: null
-}
-
+#
+# Class
+#
 class vtexMinicart
 	constructor: (@element, options) ->
-		@options = $.extend {}, defaults, options
-
-		@_defaults = defaults
-		@_name = pluginName
-
+		@options = $.extend {}, $.fn.vtexMinicart.defaults, options
 		@init()

-	init: ->
+	init: =>
 		self = this

 		self.options.$template = $ """
@@ -50,9 +42,9 @@

 		"""

-		$(self.element).after self.options.$template
-
-		$(self.options.$template)
+		$(@element).after @options.$template
+
+		$(@options.$template)
 		.mouseover ->
 			$(window).trigger "miniCartMouseOver"
 		.mouseout ->
@@ -80,159 +72,150 @@
 				, 3000

 		$(window).on 'productAddedToCart', ->
-			promiseAdd = self.getData()
-			promiseAdd.success (data) ->
+			self.getData().success (data) ->
 				self.updateItems data
 				self.changeCartValues data
 				$(window).trigger "cartUpdated", [null, true]

-		promise = @getData()
-		promise.success (data) ->
-			self.insertCartItems data
-			self.changeCartValues data
-
-
-	getData: ->
-		self = this
-
-		promise = $.ajax {
+		@getData().success (data) =>
+			@insertCartItems data
+			@changeCartValues data
+
+
+	getData: =>
+		$.ajax({
 			url: "/api/checkout/pub/orderForm/"
 			data: JSON.stringify(expectedOrderFormSections: ["items", "paymentData", "totalizers"])
 			dataType: "json"
 			contentType: "application/json; charset=utf-8"
 			type: "POST"
-		}
-
-		promise.done (data) ->
-			self.options.cartData = data
-
-		promise.fail (jqXHR, textStatus, errorThrown) ->
+		})
+		.done (data) =>
+			@options.cartData = data
+		.fail (jqXHR, textStatus, errorThrown) ->
 			# console.log "Error Message: " + textStatus
 			# console.log "HTTP Error: " + errorThrown

-		promise
-
-	insertCartItems: (data) ->
-		self = this
-
-		if data
-			total = 0
-
-			for subtotal in data.totalizers
-				total += subtotal.value if subtotal.id is 'Items'
-
-			$('.vtexsc-text', self.options.$template).text 'R$' + self.formatCurrency(total)
-
-			self.updateItems data
-
-	deleteItem: (item) ->
-		self = this
-
+	insertCartItems: (data) =>
+		return unless data
+
+		total = 0
+		for subtotal in data.totalizers when subtotal.id is 'Items'
+			total += subtotal.value
+
+		$('.vtexsc-text', @options.$template).text 'R$' + formatCurrency(total)
+		@updateItems data
+
+	deleteItem: (item) =>
 		$(item).parent().find('.vtexsc-overlay').show()

-		data = JSON.stringify(
+		data = JSON.stringify
 			expectedOrderFormSections: ["items", "paymentData", "totalizers"]
 			orderItems: [
 				index: $(item).data("index")
 				quantity: 0
 			]
-		)
-
-		promise = $.ajax {
+
+		$.ajax({
 			url: "/api/checkout/pub/orderForm/" + self.options.cartData.orderFormId + "/items/update/"
 			data: data
 			dataType: "json"
 			contentType: "application/json; charset=utf-8"
 			type: "POST"
-		}
-
-		promise.success (data) ->
-			self.options.cartData = data
-			self.changeCartValues data
-			self.updateItems data
+		})
+		.success (data) =>
+			@options.cartData = data
+			@changeCartValues data
+			@updateItems data
 			$(window).trigger "cartUpdated", [data]
-
-		promise.done ->
+		.done ->
 			$(item).parent().find('.vtexsc-overlay').hide()
-
-		promise.fail (jqXHR, textStatus, errorThrown) ->
+		.fail (jqXHR, textStatus, errorThrown) ->
 			# console.log "Error Message: " + textStatus
 			# console.log "HTTP Error: " + errorThrown

-	updateItems: (data) ->
+	updateItems: (data) =>
+		return unless data
+
+		items = ''
+
+		for item, i in data.items
+			items += """
+

+
+
+
+
+
"#{item.name}"

+
+
+

+									R$ #{formatCurrency(item.price)}
+

+
+
+

+									#{item.quantity}
+

+
+
+
+									excluir
+
+

+
+

+			"""
+
+		$(".vtexsc-productList tbody", @options.$template).html items
+
 		self = this
-		$template = self.options.$template
-
-		if data
-			items = ''
-
-			$(".vtexsc-productList tbody", $template).html ""
-
-			for item, i in data.items
-				item = """
-

-
-
-
-
-
"#{item.name}"

-
-
-

-										R$ #{self.formatCurrency(item.price)}
-

-
-
-

-										#{item.quantity}
-

-
-
-
-										excluir
-
-

-
-

-				"""
-				items += item
-
-			$(".vtexsc-productList tbody", $template).html items
-
-			$(".vtexsc-productList .cartSkuRemove", $template).click ->
-				self.deleteItem(this)
-
-	changeCartValues: (data) ->
-		self = this;
-
-		if data
-			total = 0
-			for subtotal in data.totalizers
-				total += subtotal.value if subtotal.id is 'Items'
-
-			$(".vtexsc-text", self.options.$template).text "R$ " + self.formatCurrency total
-
-	formatCurrency: (value) ->
-		if value is '' or not value? or isNaN value
-			num = 0.00
-		else
-			num = value / 100
-		parseFloat(num).toFixed(2).replace('.', ',').toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1.')
-
-	showMinicart: (value) ->
-		promise = @getData()
-		promise.done ->
-			self.updateItems data
+		$(".vtexsc-productList .cartSkuRemove", @options.$template).click ->
+			self.deleteItem(this)
+
+	changeCartValues: (data) =>
+		return unless data
+
+		total = 0
+		for subtotal in data.totalizers when subtotal.id is 'Items'
+			total += subtotal.value
+
+		$(".vtexsc-text", @options.$template).text "R$ " + formatCurrency total
+
+	showMinicart: (value) =>
+		@getData().done =>
+			@updateItems data
 			$(".vtexsc-cart").slideDown()
 			clearTimeout @options.timeoutToHide
 			@options.timeoutToHide = setTimeout ->
 				$(".vtexsc-cart").slideUp()
 			, 3000

-	$.fn[pluginName] = (options) ->
-		@each ->
-			if !$.data(this, "plugin_#{pluginName}")
-				$.data(@, "plugin_#{pluginName}", new vtexMinicart(@, options))
+
+#
+# Utils
+#
+formatCurrency: (value) ->
+	if value is '' or not value? or isNaN value
+		num = 0.00
+	else
+		num = value / 100
+	parseFloat(num).toFixed(2).replace('.', ',').toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1.')
+
+
+#
+# Plugin
+#
+$.fn.vtexMinicart = (options) ->
+	@each ->
+		return 'ja tem' if $.data(this, "plugin_vtexMinicart")
+		$.data(@, "plugin_vtexMinicart", new vtexMinicart(@, options))
+	return this
+
+$.fn.vtexMinicart.defaults =
+	timeoutToHide: null
+	cartData: null
+	$template: null

 $ -> $('.portal-minicart-ref').vtexMinicart()


simplified move command

simplified move command

--- commands/move.coffee
+++ commands/move.coffee
@@ -122,9 +122,5 @@
           $pull:
             people:
               _id: character._id
-              name: {$exists: true}
-              slug: {$exists: true}
-              hp: {$exists: true}
-              hp_max: {$exists: true}
         db.tiles.update query, update, false, true, cb
     ], cb


[fix] Fixed working with multiple comments (3m)

--- app/assets/javascripts/houston/feedback/views/comments_view.coffee
+++ app/assets/javascripts/houston/feedback/views/comments_view.coffee
@@ -260,8 +260,8 @@
       tags: []
       read: _.all comments, (comment)-> comment.get('read')

-    tags = (comment.get('tags') for comment in comments).flatten()
-    for tag, array of tags.groupBy()
+    tags = _.flatten(comment.get('tags') for comment in comments)
+    for tag, array of _.groupBy(tags)
       tag.count = array.length
       percent = array.length / context.count
       percent = 0.2 if percent < 0.2


Fixed javascript error when paginating feeds, in the case in which the user is not subscribed to any feeds.

--- app/assets/javascripts/ng-services/ng-FeedsPaginationSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FeedsPaginationSvc.js.coffee
@@ -65,8 +65,9 @@
       # Set to zero the unread count of feeds that have not been refreshed during the refresh cycle
       # (this means those feeds are in the root scope but have not been returned by the server, which means
       # their unread count is zero if only unread feeds are being retrieved)
-      for feed in $rootScope.feeds
-        feed.unread_entries = 0 if !feed.refreshed
+      if $rootScope.feeds && $rootScope.feeds?.length>0
+        for feed in $rootScope.feeds
+          feed.unread_entries = 0 if !feed.refreshed

   return service
 ]


payment: use constant for default provider

--- client/Main/payment/modal.coffee
+++ client/Main/payment/modal.coffee
@@ -6,8 +6,8 @@
   getInitialState: -> {
     planInterval : PaymentWorkflow.planInterval.MONTH
     planTitle    : PaymentWorkflow.planTitle.HOBBYIST
+    provider     : PaymentWorkflow.provider.KODING
     isUpgrade    : yes
-    provider     : 'koding'
   }


dont require 'mount' as an option. remove console

--- tasks/browserify2.coffee
+++ tasks/browserify2.coffee
@@ -5,7 +5,7 @@

 module.exports = (grunt)->
   @registerTask 'browserify2', 'commonjs modulez', ->
-    required = ['entry', 'mount']
+    required = ['entry']
     done = @async()
     browserify = require 'browserify'
     {entry, mount, server, debug, compile, beforeHook} = grunt.config.get(@name)
@@ -17,7 +17,6 @@

     # build bundle
     bundle.bundle {debug}, (err, src)->
-      console.log 'src=', src
       if (err) then throw err

       if not server and not compile


Use array matching to match user keystrokes with binding keystrokes

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -7,7 +7,7 @@
 {Emitter, Disposable, CompositeDisposable} = require 'event-kit'
 KeyBinding = require './key-binding'
 CommandEvent = require './command-event'
-{normalizeKeystrokes, keystrokeForKeyboardEvent, isAtomModifier, keydownEvent, keyupEvent, characterForKeyboardEvent} = require './helpers'
+{normalizeKeystrokes, keystrokeForKeyboardEvent, isAtomModifier, keydownEvent, keyupEvent, characterForKeyboardEvent, keystrokesMatch} = require './helpers'

 Platforms = ['darwin', 'freebsd', 'linux', 'sunos', 'win32']
 OtherPlatforms = Platforms.filter (platform) -> platform isnt process.platform
@@ -411,18 +411,15 @@
   # * `event` A `KeyboardEvent` of type 'keydown'
   handleKeyboardEvent: (event) ->
     keystroke = @keystrokeForKeyboardEvent(event)
-
-    if @queuedKeystrokes.length > 0 and isAtomModifier(keystroke)
+    keystroke = "^#{keystroke}" if event.type is 'keyup'
+
+    if event.type isnt 'keyup' and @queuedKeystrokes.length > 0 and isAtomModifier(keystroke)
       event.preventDefault()
       return

-    if event.type is 'keyup'
-      keystrokes = "#{@lastKeydownKeystrokes}^#{keystroke}"
-    else
-      @queuedKeyboardEvents.push(event)
-      @queuedKeystrokes.push(keystroke)
-      keystrokes = @queuedKeystrokes.join(' ')
-      @lastKeydownKeystrokes = keystrokes
+    @queuedKeyboardEvents.push(event)
+    @queuedKeystrokes.push(keystroke)
+    keystrokes = @queuedKeystrokes.join(' ')

     # If the event's target is document.body, assign it to defaultTarget instead
     # to provide a catch-all element when nothing is focused.
@@ -432,7 +429,7 @@
     # First screen for any bindings that match the current keystrokes,
     # regardless of their current selector. Matching strings is cheaper than
     # matching selectors.
-    {partialMatchCandidates, exactMatchCandidates} = @findMatchCandidates(keystrokes)
+    {partialMatchCandidates, exactMatchCandidates} = @findMatchCandidates(@queuedKeystrokes)
     partialMatches = @findPartialMatches(partialMatchCandidates, target)

     # Determine if the current keystrokes match any bindings *exactly*. If we
@@ -536,16 +533,15 @@

   # Finds all key bindings whose keystrokes match the given keystrokes. Returns
   # both partial and exact matches.
-  findMatchCandidates: (keystrokes) ->
+  findMatchCandidates: (keystrokeArray) ->
     partialMatchCandidates = []
     exactMatchCandidates = []

-    keystrokesWithSpace = keystrokes + ' '
-
     for binding in @keyBindings when binding.enabled
-      if binding.keystrokes is keystrokes
+      doesMatch = keystrokesMatch(binding.keystrokeArray, keystrokeArray)
+      if doesMatch is 'exact'
         exactMatchCandidates.push(binding)
-      else if binding.keystrokes.indexOf(keystrokesWithSpace) is 0
+      else if doesMatch is 'partial'
         partialMatchCandidates.push(binding)
     {partialMatchCandidates, exactMatchCandidates}


fix server box model unit test

fix server box model unit test

--- test/unit/model/box.coffee
+++ test/unit/model/box.coffee
@@ -77,5 +77,5 @@
       @servers.should.include 'free-ec2.scraperwiki.com'
       @servers.should.include 'ds-ec2.scraperwiki.com'

-    it 'gives me three servers (because that is how many there currently are)', ->
-      @servers.length.should.equal 3
+    it 'gives me four servers (because that is how many there currently are)', ->
+      @servers.length.should.equal 4


Login: appIsShown event's handleRoute temp. disabled

--- client/Login/AppController.coffee
+++ client/Login/AppController.coffee
@@ -25,7 +25,7 @@
     super options, data

   appIsShown: (params)->
-    @handleRoute "/#{params.label}"
+    # @handleRoute "/#{params.label}"

   handleQuery: ->
     {currentPath} = KD.getSingleton 'router'


combine private fns that were meant more for a multiple/not flag

--- client/common/multi-select.coffee
+++ client/common/multi-select.coffee
@@ -47,11 +47,7 @@
     if not Ember.isArray(selection)
       @set('selection', Ember.A([selection]))
       return
-    @_selectionDidChangeMultiple()
-  ).observes 'selection.@each'

-  # todo: consider consolidating all private "multiple" fns that are needed
-  _selectionDidChangeMultiple: ->
     content = @get('content')
     selection = @get('selection')
     selectedIndexes = if content then Ember.EnumerableUtils.indexesOf(content, selection) else [-1]
@@ -62,6 +58,7 @@
         # todo: what is @index for?? and does this function need bound?
         adjusted = if @index > -1 then @index else -1  # todo: better name (this is for if prompt offset)
         @selected = Ember.EnumerableUtils.indexOf(selectedIndexes, adjusted) > -1
+  ).observes 'selection.@each'

   _triggerChange: (->
     selection = @get('selection')
@@ -71,11 +68,6 @@
   ).on('didInsertElement')

   _change: (->
-    # todo: get rid of this proxy fn
-    @_changeMultiple()
-  ).on('change')
-
-  _changeMultiple: ->
     options = this.$('input[type=checkbox]:checked')
     content = @get('content')
     selection = @get('selection')
@@ -90,3 +82,4 @@
         Ember.EnumerableUtils.replace selection, 0, Ember.get(selection, 'length'), newSelection
       else
         @set('selection', newSelection)
+  ).on('change')


RealTimeManager: Fix event binding for lists.

--- client/ide/RealTimeManager.coffee
+++ client/ide/RealTimeManager.coffee
@@ -111,7 +111,7 @@
       @bindMapListeners data

     else if data instanceof gapi.drive.realtime.CollaborativeList
-      @bindMapListeners data
+      @bindListListeners data

   bindStringListeners: (string) ->


fix reset

fix reset

--- src/layout/sprites.coffee
+++ src/layout/sprites.coffee
@@ -37,14 +37,15 @@
 exports.sprites = {
   _LABEL: 'sprites'
   _KIND: 'sprite'
-  _EXPORTS: ['inspect', 'reset']
   _SETUP: (world) ->
     shapes = world.get 'shapes'
     sprites = world.get 'sprites'
     for sprite_dict in sprites.all()
       set_shape(sprite_dict, shapes)
       child = world.add_child sprite_dict
-      # child.handle_event 'apply'
+      child.put 'dict', sprite_dict
+      child.handle_event 'reset'
+      child.handle_event 'inspect'
       world.send 'inspect', child

   # selection
@@ -103,5 +104,7 @@
     true # always a valid move

   reset: (world, args) ->
-    ['position', 'direction'].map (key) -> world.put key, undefined
+    dict = world.get 'dict'
+    ['position', 'direction'].map (key) ->
+      world.put key, dict[key]
 }


lots of work on background page

lots of work on background page

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -257,21 +257,31 @@
 # Inject and execute the `content.coffee` and `install.coffee` scripts within
 # all of the tabs (where valid) of each Chrome window.
 executeScriptsInExistingWindows = ->
-  chrome.windows.getAll null, (windows) ->
+  log.trace()
+  # Create a runner to help manage the asynchronous aspect.
+  runner = new utils.Runner()
+  runner.push chrome.windows, 'getAll', null, (windows) ->
+    log.info 'Retrieved the following windows...', windows
     for win in windows
-      # Retrieve all tabs open in `win`.
-      chrome.tabs.query windowId: win.id, (tabs) ->
-        # Check tabs are not displaying a *protected* page (i.e. one that will
-        # cause an error if an attempt is made to execute content scripts).
-        for tab in tabs when not isProtectedPage tab
-          chrome.tabs.executeScript tab.id, file: 'lib/content.js'
-          # Only execute inline installation content script for tabs displaying
-          # a page on Template's homepage domain.
-          if tab.url.indexOf(HOMEPAGE_DOMAIN) isnt -1
-            chrome.tabs.executeScript tab.id, file: 'lib/install.js'
+      do (win) ->
+        runner.push chrome.tabs, 'query', windowId: win.id, (tabs) ->
+          log.info 'Retrieved the following tabs...', tabs
+          # Check tabs are not displaying a *protected* page (i.e. one that
+          # will cause an error if an attempt is made to execute content
+          # scripts).
+          for tab in tabs when not isProtectedPage tab
+            chrome.tabs.executeScript tab.id, file: 'lib/content.js'
+            # Only execute inline installation content script for tabs
+            # displaying a page on Template's homepage domain.
+            if tab.url.indexOf(HOMEPAGE_DOMAIN) isnt -1
+              chrome.tabs.executeScript tab.id, file: 'lib/install.js'
+          runner.next()
+    runner.next()
+  runner.run()

 # Attempt to derive the current version of the user's browser.
 getBrowserVersion = ->
+  log.trace()
   str = navigator.userAgent
   idx = str.indexOf browser.title
   if idx isnt -1
@@ -281,6 +291,7 @@

 # Derive the path of the image used by `template`.
 getImagePathForTemplate = (template, relative) ->
+  log.trace()
   path = if relative then '../' else ''
   for image in ext.IMAGES when image is template.image
     path += "images/#{image}.png"
@@ -290,6 +301,7 @@

 # Derive the operating system being used by the user.
 getOperatingSystem = ->
+  log.trace()
   str = navigator.platform
   for os in OPERATING_SYSTEMS when str.indexOf(os.substring) isnt -1
     str = os.title
@@ -298,52 +310,63 @@

 # Attempt to retrieve the template with the specified `key`.
 getTemplateWithKey = (key) ->
+  log.trace()
   ext.queryTemplate (template) ->
     template.key is key

 # Attempt to retrieve the template with the specified `menuId`.
 getTemplateWithMenuId = (menuId) ->
+  log.trace()
   ext.queryTemplate (template) ->
     template.menuId is menuId

 # Attempt to retrieve the template with the specified keyboard `shortcut`.
 # Exclude disabled templates from this query.
 getTemplateWithShortcut = (shortcut) ->
+  log.trace()
   ext.queryTemplate (template) ->
     template.enabled and template.shortcut is shortcut

-# Determine whether or not `sender` is a blacklisted extension.
-isBlacklisted = (sender) ->
-  return yes for extension in BLACKLIST when extension is sender.id
+# Determine whether or not the specified extension is blacklisted.
+isBlacklisted = (extensionId) ->
+  log.trace()
+  return yes for extension in BLACKLIST when extension is extensionId
   no

 # Determine whether or not the specified extension is active on `tab`.
 isExtensionActive = (tab, extensionId) ->
+  log.trace()
+  log.debug "Checking activity of #{extensionId}"
   isSpecialPage(tab) and tab.url.indexOf(extensionId) isnt -1

 # Determine whether or not `tab` is currently displaying a page on the Chrome
 # Extension Gallery (i.e. Web Store).
 isExtensionGallery = (tab) ->
+  log.trace()
   tab.url.indexOf('https://chrome.google.com/webstore') is 0

 # Determine whether or not `tab` is currently displaying a *protected* page
 # (i.e. a page that content scripts cannot be executed on).
 isProtectedPage = (tab) ->
+  log.trace()
   isSpecialPage(tab) or isExtensionGallery tab

 # Determine whether or not `tab` is currently displaying a *special* page (i.e.
 # a page that is internal to the browser).
 isSpecialPage = (tab) ->
+  log.trace()
   tab.url.indexOf('chrome') is 0

 # Ensure `null` is returned instead of `object` if it is *empty*.
 nullIfEmpty = (object) ->
+  log.trace()
   if $.isEmptyObject object then null else object

 # Listener for internal requests to the extension.
 # External requests are also routed through here, but only after being checked
 # that they do not originate from a blacklisted extension.
 onRequest = (request, sender, sendResponse) ->
+  log.trace()
   # Don't allow shortcut requests when shortcuts are disabled.
   if request.type is 'shortcut' and not store.get 'shortcuts'
     return sendResponse?()
@@ -362,10 +385,12 @@
   # Create a runner to manage this asynchronous mess.
   runner = new utils.Runner()
   runner.push chrome.windows, 'getCurrent', (win) ->
+    log.info 'Retrieved the following window...', win
     windowId = win.id
     runner.next()
   runner.pushPacked chrome.tabs, 'query', ->
     [active: yes, windowId: windowId, (tabs) ->
+      log.info 'Retrieved the following tabs...', tabs
       tab = tabs[0]
       runner.next()
     ]
@@ -377,6 +402,7 @@
     shortCallback = (text, render) ->
       text = if text then render text else @url
       url  = text.trim()
+      log.debug 'Following is the contents of a shorten tag...', text
       # If `url` doesn't appear to be a valid URL so just return the rendered
       # `text`.
       return text unless R_VALID_URL.test url
@@ -388,6 +414,7 @@
         # placeholder the replaces itself with itself so that it still when it
         # comes to replacing with the shortened URL.
         @[placeholder] = "{#{placeholder}}"
+      log.debug "Replacing shorten tag with #{placeholder} placeholder"
       "{#{placeholder}}"
     # If the popup is currently displayed, hide the template list and show a
     # loading animation.
@@ -409,6 +436,7 @@
           template = getTemplateWithShortcut request.data.key
       if data? and template? then runner.next() else runner.finish()
     catch error
+      log.error error
       # Oops! Something went wrong so we should probably let the user know.
       runner.finish
         message: i18n.get if error instanceof URIError
@@ -422,8 +450,10 @@
       transformData data
       # To complete the data, simply extend it using `template`.
       $.extend data, template: template
+      log.debug "Using the following data to render #{template.title}...", data
       if template.content
         output = Mustache.to_html template.content, data
+        log.debug 'Following string is the rendered result...', output
         # If any `shorten` (or old `short`) tags are found and replaced, the
         # URL shortener service needs to be called in order to replace any
         # placeholders with their corresponding short URL.
@@ -443,17 +473,21 @@
           template: template
     ]
   runner.push null, callUrlShortener, shortenMap, (response) ->
+    log.info 'URL shortener service was called one or more times'
     if response.success
       updateUrlShortenerUsage response.service.name, response.oauth
       # Request to the URL shortener service was successful so re-render the
       # output.
+      newOutput = Mustache.to_html output, shortenMap
+      log.debug 'Following string is the re-rendered result...', newOutput
       runner.finish
-        contents: Mustache.to_html output, shortenMap
+        contents: newOutput
         success:  yes
         template: template
     else
       # Aw man, something went wrong. Let the user down gently.
       response.message ?= i18n.get 'shortener_error', response.service.title
+      log.warn response.message
       runner.finish
         message:  response.message
         success:  no
@@ -475,12 +509,14 @@
         ext.notification.description = result.message ?
           i18n.get 'result_good_description', result.template.title
         ext.copy result.contents
+        sendResponse? contents: result.contents
       else
         ext.notification.title       = i18n.get 'result_bad_title'
         ext.notification.titleStyle  = 'color: #A00'
         ext.notification.description = result.message ?
           i18n.get 'result_bad_description', result.template.title
         showNotification()
+        sendResponse?()
       if result.template?
         updateTemplateUsage result.template.key
         updateStatistics()
@@ -495,20 +531,24 @@
             popupLoader.hide().dequeue()
           popupItems.queue ->
             popupItems.show().dequeue()
+    log.debug "Finished handling #{type} request"

 # Attempt to select a tab in the current window displaying a page whose
 # location begins with `url`.
 # If no existing tab exists a new one is simply created.
 selectOrCreateTab = (url, callback) ->
+  log.trace()
   tab      = null
   windowId = null
   # Create a runner to mange the asynchronous pattern.
   runner = new utils.Runner()
   runner.push chrome.windows, 'getCurrent', (win) ->
+    log.debug 'Retrieved the following window...', win
     windowId = win.id
     runner.next()
   runner.pushPacked chrome.tabs, 'query', ->
     [windowId: windowId, (tabs) ->
+      log.debug 'Retrieved the following tabs...', tabs
       result = yes
       # Try to find an existing tab.
       for tab in tabs
@@ -531,6 +571,7 @@
 # Also, ensure that `ext` is *reset* and that notifications are only displayed
 # if the user has enabled the corresponding option (enabled by default).
 showNotification = ->
+  log.trace()
   if store.get 'notifications.enabled'
     webkitNotifications.createHTMLNotification(
       utils.url 'pages/notification.html'
@@ -552,6 +593,8 @@

 # Update the statistical information.
 updateStatistics = ->
+  log.trace()
+  log.info 'Updating statistics'
   store.init 'stats', {}
   store.modify 'stats', (stats) =>
     # Determine which template has the greatest usage.
@@ -569,20 +612,24 @@
 # Increment the usage for the template with the specified `key` and persist the
 # changes.
 updateTemplateUsage = (key) ->
+  log.trace()
   for template in ext.templates when template.key is key
     template.usage++
     break
   store.set 'templates', ext.templates
+  log.info "Used #{template.title} template"
   analytics.track 'Templates', 'Used', template.title,
     if template.readOnly then 1 else 0

 # Increment the usage for the URL shortener service with the specified `name`
 # and persist the changes.
 updateUrlShortenerUsage = (name, oauth) ->
+  log.trace()
   store.modify name, (shortener) ->
     shortener.usage++
   shortener = ext.queryUrlShortener (shortener) ->
     shortener.name is name
+  log.info "Used #{shortener.title} URL shortener"
   analytics.track 'Shorteners', 'Used', shortener.title, if oauth then 1 else 0

 # Data functions
@@ -590,9 +637,11 @@

 # Extract additional information from `tab` and add it to `data`.
 addAdditionalData = (tab, data, callback) ->
+  log.trace()
   # Create a runner to simplify this process.
   runner = new utils.Runner()
   runner.push navigator.geolocation, 'getCurrentPosition', (position) ->
+    log.debug 'Retrieved the following geolocation information...', position
     coords = {}
     for own prop, value of position.coords
       coords[prop.toLowerCase()] = if value? then "#{value}" else ''
@@ -602,6 +651,7 @@
     log.error error.message
     runner.next()
   runner.push chrome.cookies, 'getAll', url: data.url, (cookies = []) ->
+    log.debug 'Retrieved the followign cookies...', cookies
     names = []
     # Extract the names of each cookie.
     names.push cookie.name for cookie in cookies
@@ -617,6 +667,8 @@
     # executed.
     if isProtectedPage tab then runner.finish() else runner.next()
   runner.push chrome.tabs, 'sendRequest', tab.id, {}, (response) ->
+    log.debug 'Retrieved the following data from the content script...',
+      response
     runner.finish response
   runner.run (result = {}) ->
     lastModified = if result.lastModified?
@@ -640,11 +692,12 @@
       # Deprecated since 1.0.0, use `selectedLinks` instead.
       selectionlinks: result.selectedLinks ? []
       stylesheets:    result.styleSheets   ? []
-    callback?()
+    callback()

 # Creates an object containing data based on information derived from the
 # specified tab and menu item data.
 buildDerivedData = (tab, onClickData, shortCallback) ->
+  log.trace()
   data =
     title: tab.title
     url:   ''
@@ -665,6 +718,7 @@
 # `shortCallback` is called to handle this as we don't want to call a URL
 # shortener service unless it is actually required.
 buildStandardData = (tab, shortCallback) ->
+  log.trace()
   compatibility = no
   data          = {}
   title         = ''
@@ -672,6 +726,7 @@
   # Check for any support extensions running on the current tab by simply
   # checking the tabs URL.
   for extension in SUPPORT when isExtensionActive tab, extension.id
+    log.debug "Making data compatible for #{exension.id}"
     title         = extension.title tab.title
     url           = $.url extension.url tab.url
     compatibility = yes
@@ -686,6 +741,7 @@
   anchor        = store.get 'anchor'
   bitly         = store.get 'bitly'
   googl         = store.get 'googl'
+  menu          = store.get 'menu'
   notifications = store.get 'notifications'
   stats         = store.get 'stats'
   toolbar       = store.get 'toolbar'
@@ -704,37 +760,30 @@
     bitlyusername:         bitly.username
     browser:               browser.title
     browserversion:        browser.version
-    contextmenu:           store.get 'contextMenu'
+    # Deprecated since 1.0.0, use `menu` instead.
+    contextmenu:           -> @menu
     cookiesenabled:        navigator.cookieEnabled
     count:                 stats.count
     customcount:           stats.customCount
-    datetime:              ->
-      (text, render) ->
-        new Date().format(render(text) or undefined) ? ''
-    decode:                ->
-      (text, render) ->
-        decodeURIComponent(render text) ? ''
+    datetime:              -> (text, render) ->
+      new Date().format(render(text) or undefined) ? ''
+    decode:                -> (text, render) ->
+      decodeURIComponent(render text) ? ''
     depth:                 screen.colorDepth
     # Deprecated since 1.0.0, use `anchorTarget` instead.
-    doanchortarget:        ->
-      @anchortarget
+    doanchortarget:        -> @anchortarget
     # Deprecated since 1.0.0, use `anchorTitle` instead.
-    doanchortitle:         ->
-      @anchortitle
-    encode:                ->
-      (text, render) ->
-        encodeURIComponent(render text) ? ''
+    doanchortitle:         -> @anchortitle
+    encode:                -> (text, render) ->
+      encodeURIComponent(render text) ? ''
     # Deprecated since 0.1.0.2, use `encode` instead.
-    encoded:               ->
-      encodeURIComponent @url
+    encoded:               -> encodeURIComponent @url
     favicon:               tab.favIconUrl
-    fparam:                ->
-      (text, render) ->
-        url.fparam(render text) ? ''
+    fparam:                -> (text, render) ->
+      url.fparam(render text) ? ''
     fparams:               nullIfEmpty url.fparam()
-    fsegment:              ->
-      (text, render) ->
-        url.fsegment(parseInt render(text), 10) ? ''
+    fsegment:              -> (text, render) ->
+      url.fsegment(parseInt render(text), 10) ? ''
     fsegments:             url.fsegment()
     googl:                 googl.enabled
     googlaccount:          ->
@@ -742,21 +791,20 @@
         shortener.name is 'googl'
       shortener.oauth.hasToken()
     # Deprecated since 1.0.0, use `googlAccount` instead.
-    googloauth:            ->
-      @googlaccount()
+    googloauth:            -> @googlaccount()
     java:                  navigator.javaEnabled()
+    menu:                  menu.enabled
+    menuoptions:           menu.options
     notifications:         notifications.enabled
     notificationduration:  notifications.duration * .001
     offline:               not navigator.onLine
     # Deprecated since 0.1.0.2, use `originalUrl` instead.
-    originalsource:        ->
-      @originalurl
+    originalsource:        -> @originalurl
     originaltitle:         tab.title or url.attr 'source'
     originalurl:           tab.url
     os:                    operatingSystem
-    param:                 ->
-      (text, render) ->
-        url.param(render text) ? ''
+    param:                 -> (text, render) ->
+      url.param(render text) ? ''
     params:                nullIfEmpty url.param()
     plugins:               (
       array   = []
@@ -772,27 +820,21 @@
       template.key is stats.popular
     screenheight:          screen.height
     screenwidth:           screen.width
-    segment:               ->
-      (text, render) ->
-        url.segment(parseInt render(text), 10) ? ''
+    segment:               -> (text, render) ->
+      url.segment(parseInt render(text), 10) ? ''
     segments:              url.segment()
     # Deprecated since 1.0.0, use `shorten` instead.
-    short:                 ->
-      @shorten()
+    short:                 -> @shorten()
     shortcuts:             store.get 'shortcuts'
-    shorten:               ->
-      shortCallback
+    shorten:               -> shortCallback
     title:                 title or url.attr 'source'
     toolbarclose:          toolbar.close
     # Deprecated since 1.0.0, use the inverse of `toolbarPopup` instead.
-    toolbarfeature:        ->
-      not @toolbarpopup
+    toolbarfeature:        -> not @toolbarpopup
     # Deprecated since 1.0.0, use `toolbarStyle` instead.
-    toolbarfeaturedetails: ->
-      @toolbarstyle
+    toolbarfeaturedetails: -> @toolbarstyle
     # Deprecated since 1.0.0, use `toolbarKey` instead.
-    toolbarfeaturename:    ->
-      @toolbarkey
+    toolbarfeaturename:    -> @toolbarkey
     toolbarkey:            toolbar.key
     toolbaroptions:        toolbar.options
     toolbarpopup:          toolbar.popup
@@ -809,6 +851,7 @@
 # Ensure there is a lower case variant of all properties of `data`, optionally
 # removing the original non-lower-case property.
 transformData = (data, deleteOld) ->
+  log.trace()
   for own prop, value of data when R_UPPER_CASE.test prop
     data[prop.toLowerCase()] = value
     delete data[prop] if deleteOld
@@ -818,6 +861,7 @@

 # Build the HTML to populate the popup with to optimize popup loading times.
 buildPopup = ->
+  log.trace()
   item     = $ '
'
   itemList = $ '
'
   loadDiv  = $ '
'
@@ -856,6 +900,8 @@
 # The element should then be inserted in to the `ul` element in the popup page
 # but is created here to optimize display times for the popup.
 buildTemplate = (template) ->
+  log.trace()
+  log.debug "Creating popup list item for #{template.title}"
   image = getImagePathForTemplate template, yes
   item  = $ '
',
     'data-key':  template.key
@@ -881,6 +927,7 @@
 # Handle the conversion/removal of older version of settings that may have been
 # stored previously by `ext.init`.
 init_update = ->
+  log.trace()
   # Update the update progress indicator itself.
   if store.exists 'update_progress'
     store.modify 'updates', (updates) ->
@@ -893,6 +940,7 @@
   # Define the processes for all required updates to the `settings`
   # namespace.
   updater.update '0.1.0.0', ->
+    log.info 'Updating general settings for 0.1.0.0'
     store.rename 'settingNotification',      'notifications',        on
     store.rename 'settingNotificationTimer', 'notificationDuration', 3000
     store.rename 'settingShortcut',          'shortcuts',            on
@@ -900,6 +948,7 @@
     store.rename 'settingTitleAttr',         'doAnchorTitle',        off
     store.remove 'settingIeTabExtract',      'settingIeTabTitle'
   updater.update '1.0.0', ->
+    log.info 'Updating general settings for 1.0.0'
     if store.exists 'options_active_tab'
       optionsActiveTab = store.get 'options_active_tab'
       store.set 'options_active_tab', switch optionsActiveTab
@@ -910,6 +959,9 @@
       anchor.target = store.get('doAnchorTarget') ? off
       anchor.title  = store.get('doAnchorTitle') ? off
     store.remove 'doAnchorTarget', 'doAnchorTitle'
+    store.modify 'menu', (menu) ->
+      menu.enabled = store.get('contextMenu') ? yes
+    store.remove 'contextMenu'
     store.set 'notifications',
       duration: store.get('notificationDuration') ? 3000
       enabled:  store.get('notifications') ? yes
@@ -917,20 +969,24 @@

 # Initialize the settings related to statistical information.
 initStatistics = ->
+  log.trace()
   updateStatistics()

 # Initialize `template` and its properties, before adding it to `templates` to
 # be persisted later.
 initTemplate = (template, templates) ->
+  log.trace()
   # Derive the index of `template` to determine whether or not it already
   # exists.
   idx = templates.indexOf utils.query templates, yes, (tmpl) ->
     tmpl.key is template.key
   if idx is -1
     # `template` doesn't already exist so add it now.
+    log.debug 'Adding the following predefined template...', template
     templates.push template
   else
     # `template` exists so modify the properties to ensure they are reliable.
+    log.debug 'Ensuring following template adheres to structure...', template
     if template.readOnly
       # `template` is read-only so certain properties should always be
       # overriden and others only when they are not already available.
@@ -960,6 +1016,7 @@

 # Initialize the persisted managed templates.
 initTemplates = ->
+  log.trace()
   initTemplates_update()
   store.modify 'templates', (templates) ->
     # Initialize all default templates to ensure their properties are as
@@ -972,12 +1029,14 @@
 # Handle the conversion/removal of older version of settings that may have been
 # stored previously by `initTemplates`.
 initTemplates_update = ->
+  log.trace()
   # Create updater for the `features` namespace and then rename it to
   # `templates`.
   updater = new store.Updater 'features'
   updater.rename 'templates'
   # Define the processes for all required updates to the `templates` namespace.
   updater.update '0.1.0.0', ->
+    log.info 'Updating template settings for 0.1.0.0'
     store.rename 'copyAnchorEnabled',  'feat__anchor_enabled',  yes
     store.rename 'copyAnchorOrder',    'feat__anchor_index',    2
     store.rename 'copyBBCodeEnabled',  'feat__bbcode_enabled',  no
@@ -989,6 +1048,7 @@
     store.rename 'copyUrlEnabled',     'feat__url_enabled',     yes
     store.rename 'copyUrlOrder',       'feat__url_index',       0
   updater.update '0.2.0.0', ->
+    log.info 'Updating template settings for 0.2.0.0'
     names = store.get('features') ? []
     for name in names when typeof name is 'string'
       store.rename "feat_#{name}_template", "feat_#{name}_content"
@@ -1005,6 +1065,7 @@
                 break
         else store.set "feat_#{name}_image", 0
   updater.update '1.0.0', ->
+    log.info 'Updating template settings for 1.0.0'
     names              = store.remove('features') ? []
     templates          = []
     toolbarFeatureName = store.get 'toolbarFeatureName'
@@ -1042,6 +1103,7 @@

 # Initialize the settings related to the toolbar/browser action.
 initToolbar = ->
+  log.trace()
   initToolbar_update()
   store.modify 'toolbar', (toolbar) ->
     toolbar.close   ?= yes
@@ -1054,10 +1116,12 @@
 # Handle the conversion/removal of older version of settings that may have been
 # stored previously by `initToolbar`.
 initToolbar_update = ->
+  log.trace()
   # Create updater for the `toolbar` namespace.
   updater = new store.Updater 'toolbar'
   # Define the processes for all required updates to the `toolbar` namespace.
   updater.update '1.0.0', ->
+    log.info 'Updating toolbar settings for 1.0.0'
     store.modify 'toolbar', (toolbar) ->
       toolbar.popup = store.get('toolbarPopup') ? yes
       toolbar.style = store.get('toolbarFeatureDetails') ? no
@@ -1066,6 +1130,7 @@

 # Initialize the settings related to the supported URL Shortener services.
 initUrlShorteners = ->
+  log.trace()
   store.init
     bitly:  {}
     googl:  {}
@@ -1090,24 +1155,29 @@
 # Handle the conversion/removal of older version of settings that may have been
 # stored previously by `initUrlShorteners`.
 initUrlShorteners_update = ->
+  log.trace()
   # Create updater for the `shorteners` namespace.
   updater = new store.Updater 'shorteners'
   # Define the processes for all required updates to the `shorteners`
   # namespace.
   updater.update '0.1.0.0', ->
+    log.info 'Updating URL shortener settings for 0.1.0.0'
     store.rename 'bitlyEnabled',       'bitly',         off
     store.rename 'bitlyXApiKey',       'bitlyApiKey',   ''
     store.rename 'bitlyXLogin',        'bitlyUsername', ''
     store.rename 'googleEnabled',      'googl',         on
     store.rename 'googleOAuthEnabled', 'googlOAuth',    on
   updater.update '1.0.0', ->
+    log.info 'Updating URL shortener settings for 1.0.0'
+    bitly = store.get 'bitly'
     store.set 'bitly',
       apiKey:    store.get('bitlyApiKey') ? ''
-      enabled:   store.get('bitly') ? no
+      enabled:   if typeof bitly is 'boolean' then bitly else no
       username:  store.get('bitlyUsername') ? ''
     store.remove 'bitlyApiKey', 'bitlyUsername'
+    googl = store.get 'googl'
     store.set 'googl',
-      enabled: store.get('googl') ? yes
+      enabled: if typeof googl is 'boolean' then googl else yes
     store.remove 'googlOAuth'
     yourls = store.get 'yourls'
     store.set 'yourls',
@@ -1127,6 +1197,7 @@
 # `callback` will be called with the result once all URLs have been shortened
 # or an error is encountered.
 callUrlShortener = (map, callback) ->
+  log.trace()
   service  = getActiveUrlShortener()
   endpoint = service.url()
   title    = service.title
@@ -1177,21 +1248,24 @@

 # Retrieve the active URL shortener service.
 getActiveUrlShortener = ->
+  log.trace()
   # Attempt to lookup enabled URL shortener service.
   shortener = ext.queryUrlShortener (shortener) ->
     shortener.isEnabled()
-  return shortener if shortener?
-  # Should never reach here but we'll return goo.gl service by default after
-  # ensuring it's the active URL shortener service from now on to save some
-  # time.
-  store.modify 'googl', (googl) ->
-    googl.enabled = yes
-  getActiveUrlShortener()
+  unless shortener?
+    # Should never reach here but we'll return goo.gl service by default after
+    # ensuring it's the active URL shortener service from now on to save some
+    # time in the future.
+    store.modify 'googl', (googl) ->
+      googl.enabled = yes
+    shortener = getActiveUrlShortener()
+  log.debug "Getting details for #{shortener.title} URL shortener"
+  shortener

 # Background page setup
 # ---------------------

-ext = window.ext =
+ext = window.ext = new class Extension extends utils.Class

   # Public constants
   # ----------------
@@ -1253,8 +1327,10 @@
   # All successful copy requests should, at some point, call this function.
   # If `str` is empty the contents of the system clipboard will not change.
   copy: (str, hidden) ->
+    log.trace()
     sandbox = $('#sandbox').val(str).select()
     document.execCommand 'copy'
+    log.debug 'Copied the following string...', str
     sandbox.val ''
     showNotification() unless hidden

@@ -1262,7 +1338,8 @@
   # Since only the names of predefined templates are known, return a newly
   # generated key if it does not match any of their names.
   getKeyForName: (name, generate = yes) ->
-    switch name
+    log.trace()
+    key = switch name
       when '_url'      then 'PREDEFINED.00001'
       when '_short'    then 'PREDEFINED.00002'
       when '_anchor'   then 'PREDEFINED.00003'
@@ -1270,16 +1347,21 @@
       when '_bbcode'   then 'PREDEFINED.00005'
       when '_markdown' then 'PREDEFINED.00006'
       else utils.keyGen() if generate
+    log.debug "Associating #{key} key with #{name} template"
+    key

   # Initialize the background page.
   # This will involve initializing the settings and adding the request
   # listeners.
   init: ->
+    log.trace()
+    log.info 'Initializing extension controller'
     # Add support for analytics if the user hasn't opted out.
     analytics.add() if store.get 'analytics'
     # Begin initialization.
     store.init
       anchor:        {}
+      menu:          {}
       notifications: {}
       stats:         {}
       templates:     []
@@ -1288,12 +1370,13 @@
     store.modify 'anchor', (anchor) ->
       anchor.target ?= off
       anchor.title  ?= off
+    store.modify 'menu', (menu) ->
+      menu.enabled ?= yes
+      menu.options ?= yes
     store.modify 'notifications', (notifications) ->
       notifications.duration ?= 3000
       notifications.enabled  ?= yes
-    store.init
-      contextMenu: on
-      shortcuts:   on
+    store.init 'shortcuts', on
     initTemplates()
     initToolbar()
     initStatistics()
@@ -1307,10 +1390,15 @@
     # Add listeners for internal and external requests.
     chrome.extension.onRequest.addListener onRequest
     chrome.extension.onRequestExternal.addListener (req, sender, cb) ->
-      blocked = isBlacklisted sender
+      blocked = isBlacklisted sender.id
       analytics.track 'External Requests', 'Started', sender.id,
         if blocked then 0 else 1
-      if blocked then cb?() else onRequest req, sender, cb
+      if blocked
+        log.debug "Blocking external request from #{sender.id}"
+        cb?()
+      else
+        log.debug "Accepting external request from #{sender.id}"
+        onRequest req, sender, cb
     # Derive the browser and OS information.
     browser.version = getBrowserVersion()
     operatingSystem = getOperatingSystem()
@@ -1325,23 +1413,28 @@

   # Determine whether or not `os` matches the user's operating system.
   isThisPlatform: (os) ->
+    log.trace()
     navigator.userAgent.toLowerCase().indexOf(os) isnt -1

   # Attempt to retrieve the contents of the system clipboard as a string.
   paste: ->
+    log.trace()
     result  = ''
     sandbox = $('#sandbox').val('').select()
     result  = sandbox.val() if document.execCommand 'paste'
+    log.debug 'Pasted the following string...', result
     sandbox.val ''
     result

   # Retrieve the first template that passes the specified `filter`.
   queryTemplate: (filter, singular = yes) ->
+    log.trace()
     utils.query @templates, singular, filter

   # Retrieve the first URL shortener service that passes the specified
   # `filter`.
   queryUrlShortener: (filter, singular = yes) ->
+    log.trace()
     utils.query SHORTENERS, singular, filter

   # Reset the notification information associated with the current copy
@@ -1349,6 +1442,7 @@
   # This should be called when a copy request is completed regardless of its
   # outcome.
   reset: ->
+    log.trace()
     @notification =
       description:      ''
       descriptionStyle: ''
@@ -1362,32 +1456,49 @@
   # If the context menu option has been disabled by the user, just remove all
   # of the existing menu items.
   updateContextMenu: ->
+    log.trace()
     # Ensure that any previously added context menu items are removed.
     chrome.contextMenus.removeAll =>
-      # Called whenever a menu item is clicked.
+      # Called whenever a template menu item is clicked.
       # Send a self request, passing along the available information.
-      onMenuClick = (info, tab) ->
-        onRequest
-          data: info
-          type: 'menu'
-      if store.get 'contextMenu'
+      onMenuClick = (info, tab) -> onRequest data: info, type: 'menu'
+      menu = store.get 'menu'
+      if menu.enabled
         # Create and add the top-level Template menu.
         parentId = chrome.contextMenus.create
           contexts: ['all']
           title:    i18n.get 'name'
         # Create and add a sub-menu item for each enabled template.
         for template in @templates when template.enabled
-          menuId = chrome.contextMenus.create
+          notEmpty = yes
+          menuId   = chrome.contextMenus.create
             contexts: ['all']
             onclick:  onMenuClick
             parentId: parentId
             title:    template.title
           template.menuId = menuId
+        unless notEmpty
+          chrome.contextMenus.create
+            contexts: ['all']
+            parentId: parentId
+            title:    i18n.get 'empty'
+        # Add an item to open the options page if the user doesn't mind.
+        if menu.options
+          chrome.contextMenus.create
+            contexts: ['all']
+            parentId: parentId
+            type:     'separator'
+          chrome.contextMenus.create
+            contexts: ['all']
+            onclick:  (info, tab) -> onRequest type: 'options'
+            parentId: parentId
+            title:    i18n.get 'options'

   # Update the local list of templates to reflect those persisted.
   # It is very important that this is called whenever templates may have been
   # changed in order to prepare the popup HTML and optimize performance.
   updateTemplates: ->
+    log.trace()
     @templates = store.get 'templates'
     @templates.sort (a, b) ->
       a.index - b.index
@@ -1397,18 +1508,21 @@

   # Update the toolbar/browser action depending on the current settings.
   updateToolbar: ->
+    log.trace()
     image    = 'images/icon_19.png'
     key      = store.get 'toolbar.key'
     template = getTemplateWithKey key if key
     title    = i18n.get 'name'
     buildPopup()
     if not template or store.get 'toolbar.popup'
+      log.info 'Configuring toolbar to display popup'
       # Use Template's details to style the browser action.
       chrome.browserAction.setIcon  path:  utils.url image
       chrome.browserAction.setTitle title: title
       # Show the popup when the browser action is clicked.
       chrome.browserAction.setPopup popup: 'pages/popup.html'
     else
+      log.info 'Configuring toolbar to activate specified template'
       # Replace Template's details with that of the selected template.
       if store.get 'toolbar.style'
         image = getImagePathForTemplate template if template.image isnt 0


fixed comment header in background.coffee

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -237,6 +237,7 @@
 operatingSystem = ''

 # Private functions
+# -----------------

 # Inject and execute the `content.coffee` and `install.coffee` scripts within
 # all of the tabs (where valid) of each Chrome window.


integrated the functionality for the new toolbarPopupClose option

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -419,12 +419,18 @@
           else
             copyOutput template, output
       else
-        # Close the popup if it's still open.
-        popup?.close()
+        # Close the popup if it's still open and the user wants it closed.
+        # Otherwise, reset the popup so that it can be used further.
+        if popup
+          if utils.get 'toolbarPopupClose'
+            popup.close()
+          else
+            $('#loadDiv', popup.document).hide()
+            $('#item', popup.document).show()

 # Display a desktop notification informing the user on whether or not the copy
 # request was successful.
-# Also ensure that `ext` is *reset* and that notifications are only displayed
+# Also, ensure that `ext` is *reset* and that notifications are only displayed
 # if the user has enabled the corresponding option (enabled by default).
 showNotification = ->
   if utils.get 'notifications'
@@ -433,8 +439,14 @@
     ).show()
   else
     ext.reset()
-  # Close the popup if it's still open.
-  chrome.extension.getViews(type: 'popup')[0]?.close()
+  # Close the popup if it's still open and the user wants it closed. Otherwise,
+  # reset the popup so that it can be used further.
+  if popup = chrome.extension.getViews(type: 'popup')[0]
+    if utils.get 'toolbarPopupClose'
+      popup.close()
+    else
+      $('#loadDiv', popup.document).hide()
+      $('#item', popup.document).show()

 # Update the context menu items to reflect the currently enabled templates.
 # If the context menu option has been disabled by the user, just remove all of
@@ -607,10 +619,11 @@
     toolbarfeaturedetails:  utils.get 'toolbarTemplateDetails'
     # Deprecated since 1.0.0, use `toolbarTemplateName` instead.
     toolbarfeaturename:     utils.get 'toolbarTemplateName'
+    toolbarpopup:           utils.get 'toolbarPopup'
+    toolbarpopupclose:      utils.get 'toolbarPopupClose'
     toolbartemplate:        utils.get 'toolbarTemplate'
     toolbartemplatedetails: utils.get 'toolbarTemplateDetails'
     toolbartemplatename:    utils.get 'toolbarTemplateName'
-    toolbarpopup:           utils.get 'toolbarPopup'
     url:                    url.attr 'source'
     version:                ext.version
     yourls:                 utils.get 'yourls'
@@ -836,6 +849,7 @@
   initToolbar_update()
   utils.init
     toolbarPopup:           on
+    toolbarPopupClose:      on
     toolbarTemplate:        off
     toolbarTemplateDetails: off
     toolbarTemplateName:    ''


Added suggestion shuffling

Added suggestion shuffling

--- client/src/app/model/games.coffee
+++ client/src/app/model/games.coffee
@@ -103,4 +103,6 @@

   recordLike: (gameId) -> $http.post("/api/like/#{gameId}")

-  getRecommendations: -> return $http.get('/api/recommend').then((result) -> return result.data)
+  getRecommendations: ->
+    # Shuffle recommendation results
+    return $http.get('/api/recommend').then (result) -> _.shuffle(result.data)


Setting block TaPoS fields in transactions. #131

--- dl/src/chain/transaction_operations.coffee
+++ dl/src/chain/transaction_operations.coffee
@@ -81,12 +81,16 @@
             if(@expiration == 0)
                 @expiration = Math.round(Date.now()/1000) + (chain_config.expire_in_min * 60)

-            resolve lookup.resolve().then ()=>
-                for op in @operations
-                    if op[1]["finalize"]
-                        op[1].finalize()
-                @tr_buffer = type.transaction.toBuffer @
-                return
+            resolve api.db_api().exec("get_objects", [["2.1.0"]]).then (r) =>
+                @ref_block_num = r[0].head_block_number
+                @ref_block_prefix =  new Buffer(r[0].head_block_id, 'hex').readUInt32LE(4)
+                #DEBUG console.log("ref_block",@ref_block_num,@ref_block_prefix,r)
+                lookup.resolve().then ()=>
+                    for op in @operations
+                        if op[1]["finalize"]
+                            op[1].finalize()
+                    @tr_buffer = type.transaction.toBuffer @
+                    return
             return

     get_required_signatures:(available_keys)->


Abstracted get_search_regex for future testing

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -131,9 +131,8 @@

     searchText = this.get_search_text()
     escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
-    regexAnchor = if @search_contains then "" else "^"
-    regex = new RegExp(regexAnchor + escapedSearchText, 'i')
     zregex = new RegExp(escapedSearchText, 'i')
+    regex = this.get_search_regex(escapedSearchText)

     for option in @results_data

@@ -176,6 +175,10 @@
     else
       this.update_results_content this.results_option_build()
       this.winnow_results_set_highlight()
+
+  get_search_regex: (escaped_search_string) ->
+    regex_anchor = if @search_contains then "" else "^"
+    new RegExp(regex_anchor + escaped_search_string, 'i')

   search_string_match: (search_string, regex) ->
     if regex.test search_string


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -206,13 +206,15 @@
             #    return arc b
         #)
         path.transition().duration(2000).attrTween('d', (d,i) ->
-            console.log('1. in tween - window.oldPieData[' + myInstanceDataId + '][' + i + ']: ',window.oldPieData[myInstanceDataId][i])
-            console.log('1. in tween - d: ',d)
+            if myInstanceId = 'resolved' and i = 0 then
+                console.log('in tween - OLD DATA - ' + myInstanceDataId + '[' + i + ']: ',window.oldPieData[myInstanceDataId][i])
+            #console.log('1. in tween - d: ',d)
             myInterpolater = d3.interpolate(window.oldPieData[myInstanceDataId][i] ? d, d)
-            console.log('myInterpolater(0): ',myInterpolater(0))
+            #console.log('myInterpolater(0): ',myInterpolater(0))
             window.oldPieData[myInstanceDataId][i] = myInterpolater(0) ? d
-            console.log('2. in tween - window.oldPieData[' + myInstanceDataId + '][' + i + ']: ',window.oldPieData[myInstanceDataId][i])
-            console.log('2. in tween - d: ',d)
+            if myInstanceId = 'resolved' and i = 0 then
+                console.log('in tween - NEW DATA - ' + myInstanceDataId + '[' + i + ']: ',window.oldPieData[myInstanceDataId][i])
+            #console.log('2. in tween - d: ',d)
             return (t) ->
                 return arc(myInterpolater(t))
         )
@@ -405,6 +407,6 @@
         #window.oldPieData[@instanceDataId] = piedata
         #window.oldPieData = window.oldPieData or {}
         #window.oldPieData[@instanceDataId] = window.oldPieData[@instanceDataId] or []
-        window.oldPieData[@instanceDataId] = piedata
+        #window.oldPieData[@instanceDataId] = piedata
         #console.log('piedata',piedata)
         return


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -62,7 +62,7 @@
     else
       arcs.append("svg:text")
         .attr('fill', "#fff")
-        .attr("text-anchor", "middle").attr("alignment-base", "middle").text(sum).attr('font-size', radiusi + 'px')
+        .attr("text-anchor", "middle").attr("alignment-baseline", "middle").text(sum).attr('font-size', radiusi + 'px')
       arcs.append("svg:text").attr("transform", (d, i) ->
         procent_val = Math.round(data[i].value/sum * 100)
         #d.innerRadius = (radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -1,6 +1,15 @@
 class Dashing.FullpieAgent extends Dashing.Widget
   #@accessor 'value'
   @accessor 'data'
+  @width = 750
+  @height = 450
+  @radius = Math.min(width, height) / 2
+  @radiuso = 135
+  @radiusi = 90
+  @color = d3.scale.category20()
+  @pie = d3.layout.pie().value((d) -> d.value)
+  @arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
+  @svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
   onData: (data) ->
     #$(@node).fadeOut().fadeIn()
     #@buildPieStructure()
@@ -431,17 +440,26 @@
     if !dataSet
         return
     $(@node).children("svg").remove();
-    width = 750
-    height = 450
-    radius = Math.min(width, height) / 2
-    radiuso = 135
-    radiusi = 90
-    color = d3.scale.category20()
-    pie = d3.layout.pie().value((d) -> d.value)
-    arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
-    svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
+    #width = 750
+    #height = 450
+    #radius = Math.min(width, height) / 2
+    #radiuso = 135
+    #radiusi = 90
+    #color = d3.scale.category20()
+    #pie = d3.layout.pie().value((d) -> d.value)
+    #arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
+    #svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
+    width = @width
+    height = @height
+    radius = @radius
+    radiuso = @radiuso
+    radiusi = @radiusi
+    color = @color
+    pie = @pie
+    arc = @arc
+    svg = @svg
     console.log 'update pie', dataSet
-    that = this
+    #that = this
     piedata = pie(dataSet)
     console.log 'update pie', piedata
     #create a marker element if it doesn't already exist


Bug fix: instruction pointer scrolling was sometimes going too slow and sometimes overshooting its destination.

--- coffee/DebuggerController.coffee
+++ coffee/DebuggerController.coffee
@@ -64,13 +64,14 @@
     else if @countOutputChars(props.interpreter?.output) >= props.numCharsToOutput
       @numCharsToOutput += 1
       window.setTimeout (=> @render()), MILLIS_FOR_OUTPUT_CHAR
-    else if currentLine && currentLine != props.currentScrollTop
-      delta =
-        if currentLine > @currentScrollTop
-          _.max [0.125, (currentLine - @currentScrollTop) / 2]
-        else
-          _.min [0.125, (currentLine - @currentScrollTop) / 2]
-      @currentScrollTop += delta
+    else if currentLine && currentLine != @currentScrollTop
+      difference = currentLine - @currentScrollTop
+      if currentLine > @currentScrollTop
+        @currentScrollTop += _.max [0.125, difference / 3]
+        @currentScrollTop = currentLine if @currentScrollTop > currentLine
+      else
+        @currentScrollTop += _.min [-0.125, difference / 3]
+        @currentScrollTop = currentLine if @currentScrollTop < currentLine
       window.setTimeout (=> @render()), MILLIS_FOR_SCROLL_INSTRUCTIONS
     else if @features.showPartialCalls && props.numPartialCallExecuting != null &&
        props.numPartialCallExecuting != @prevProps.numPartialCallExecuting


Reliability fix

Reliability fix

--- components/CalculateCenter.coffee
+++ components/CalculateCenter.coffee
@@ -20,6 +20,7 @@
     startX = []
     startY = []
     for id, touch of gesture
+      continue unless touch
       continue unless touch.startpoint
       startX.push touch.startpoint.x
       startY.push touch.startpoint.y


About page change

About page change

--- site/app.coffee
+++ site/app.coffee
@@ -754,7 +754,7 @@
         wx = w/5
       ctx.fillText('NOTE 1: %ILI is not meaningfully comparable between states,', wx, h-24-24 * Math.max(0.7, w / 1000))
       ctx.fillText('               due to differences in reporter types.', wx, h-24-12 * Math.max(0.7, w / 1000))
-      ctx.fillText('NOTE 2: Color intensity is similar to, but not identical to, CDC’s Flu Activity Level.', wx, h-24)
+      ctx.fillText('NOTE 2: Color intensity is similar to, but not the same as, CDC’s Flu Activity Level.', wx, h-24)
     return 0

   renderChart: () ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp_bounds.coffee
+++ test/clamp_bounds.coffee
@@ -12,6 +12,6 @@
   player.I.x = 800
   player.update()

-  equals player.I.x, player.I.
+  equals player.I.x, player.I.width

 module()


add visible label to trigger invisible checkbox

--- core/app/backbone/views/evidence/react_share_fact_selection.coffee
+++ core/app/backbone/views/evidence/react_share_fact_selection.coffee
@@ -11,9 +11,10 @@
     ],
       if @model().serviceConnected @props.provider_name
         [
-          _input [type: 'checkbox', checked: @props.checked,
-            onChange: (event) => @props.onChange? event.target.checked]
-          _span ["share-button share-button-#{@props.provider_name}"]
+          _label [],
+            _input [type: 'checkbox', checked: @props.checked,
+              onChange: (event) => @props.onChange? event.target.checked]
+            _span ["share-button share-button-#{@props.provider_name}"]
           if @state.hovered
             ReactPopover {},
               _div [],


Send examples also as 'main'

Send examples also as 'main'

--- components/ConnectRuntime.coffee
+++ components/ConnectRuntime.coffee
@@ -73,7 +73,7 @@
       id: graph.properties.id
       name: graph.name
       library: graph.properties.project
-      main: (@project and graph.properties.id is @project.main)
+      main: ((@project and graph.properties.id is @project.main) or graph is @example)
     for node in graph.nodes
       runtime.sendGraph 'addnode',
         id: node.id


updated message schema

updated message schema

--- src/models/message.coffee
+++ src/models/message.coffee
@@ -1,6 +1,6 @@
 module.exports = (sequelize, DataTypes) ->
   Message = sequelize.define "Message",
-    body: DataTypes.STRING
+    body: DataTypes.STRING(65536)
   , classMethods:
     associate: (models) ->
       Message.belongsTo(models.User)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -15,7 +15,9 @@

   equals player.I.x, 9

-  player.
+  player.update()
+
+  eq

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -96,6 +96,6 @@

   equals player.I.y, 23

-test "should "
+test "should stay in play"

 module()


adds file from image selector and cleanup

- allows to add image from file selector, in ubuntu it only allows
   for one file at a time
- cleanup some leftover code I forgot to remove once moved to
   input.coffee

--- src/ui/dispatcher.coffee
+++ src/ui/dispatcher.coffee
@@ -181,21 +181,34 @@
     conv_id = viewstate.selectedConv
     # sense check that client is in good state
     return unless viewstate.state == viewstate.STATE_NORMAL and conv[conv_id]
-    # ship it
-    for file in files
-        # only images please
+    # if only one file is selected, then it shows as preview before sending
+    #  otherwise, it will upload all of them immediatly
+    if files.length == 1
+        file = files[0] # get first and only file
+        element = document.getElementById 'preview-img'
+        # show error message and return if is not an image
         unless isImg file.path
             [_, ext] = file.path.match(/.*(\.\w+)$/) ? []
             notr "Ignoring file of type #{ext}"
-            continue
-        # message for a placeholder
-        msg = userinput.buildChatMessage entity.self, 'uploading image…'
-        msg.uploadimage = true
-        {client_generated_id} = msg
-        # add a placeholder for the image
-        conv.addChatMessagePlaceholder entity.self.id, msg
-        # and begin upload
-        ipc.send 'uploadimage', {path:file.path, conv_id, client_generated_id}
+            return
+        # store image in preview-container and open it
+        element.src = nativeImage.createFromPath(file.path).toDataURL()
+        document.querySelector('#preview-container').classList.toggle('open')
+    else
+        for file in files
+            # only images please
+            unless isImg file.path
+                [_, ext] = file.path.match(/.*(\.\w+)$/) ? []
+                notr "Ignoring file of type #{ext}"
+                continue
+            # message for a placeholder
+            msg = userinput.buildChatMessage entity.self, 'uploading image…'
+            msg.uploadimage = true
+            {client_generated_id} = msg
+            # add a placeholder for the image
+            conv.addChatMessagePlaceholder entity.self.id, msg
+            # and begin upload
+            ipc.send 'uploadimage', {path:file.path, conv_id, client_generated_id}

 handle 'onpasteimage', ->
     element = document.getElementById 'preview-img'
@@ -218,15 +231,7 @@
     document.querySelector('#emoji-container').classList.remove('open')
     element.src = ''
     #
-    element = document.getElementById "message-input"
-    if models.viewstate.convertEmoji
-        # before sending message, check for emoji
-        # Converts emojicodes (e.g. :smile:, :-) ) to unicode
-        element.value = convertEmoji(element.value)
-    #
-    msg = userinput.buildChatMessage entity.self, element.value
-    #
-    ipc.send 'uploadclipboardimage', {pngData, segs: msg.segs, otr: msg.otr, conv_id, client_generated_id}
+    ipc.send 'uploadclipboardimage', {pngData, conv_id, client_generated_id}

 handle 'uploadingimage', (spec) ->
     # XXX this doesn't look very good because the image


Make esc work as search unfocus key.

Make esc work as search unfocus key.

--- frontend/script.coffee
+++ frontend/script.coffee
@@ -801,9 +801,11 @@
   $(document).bind "keypress.j", ->
     searchField.focus()
     return false
-  $(searchField).bind "keyup.return", ->
+  unfocusSearch = ->
     searchField.blur()
     return false
+  $(searchField).bind "keyup.return", unfocusSearch
+  $(searchField).bind "keyup.esc", unfocusSearch

   # TODO Might be a better idea to have a relative volume control instead
   # of absolute.


Use Utils.isFirefox for link hints fix

Use Utils.isFirefox for link hints fix

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -11,7 +11,6 @@
 # The "name" property below is a short-form name to appear in the link-hints mode's name.  It's for debug only.
 #
 isMac = KeyboardUtils.platform == "Mac"
-simulateClickDefaultAction = true
 OPEN_IN_CURRENT_TAB =
   name: "curr-tab"
   indicator: "Open link in current tab"
@@ -387,6 +386,7 @@
           else
             clickActivator = (modifiers) -> (link) ->
               defaultActionsTriggered = DomUtils.simulateClick link, modifiers
+              simulateClickDefaultAction = Utils.isFirefox()
               if simulateClickDefaultAction and
                   defaultActionsTriggered[3] and link.tagName?.toLowerCase() == "a" and
                   modifiers? and modifiers.metaKey == isMac and modifiers.ctrlKey == not isMac


Add test for gen and retrieving user credentials

--- test/routes/users.coffee
+++ test/routes/users.coffee
@@ -75,3 +75,15 @@
     request (base "/users/not-an-id"), options(), (e,r,b)->
       r.statusCode.should.be.equal 404
       done()
+
+  it 'can generate api credentials and retrieve them', (done) ->
+    t = "testUser#{uuid.v1()}@testuser.com"
+    makeUser t, 200, (user)->
+      request.post (base "/users/#{user.id}/credentials"), options(), (e,r,b)->
+        r.statusCode.should.be.equal 200
+        b.should.have.property 'secret'
+        s = b.secret
+        request (base "/users/#{user.id}/credentials"), options(), (e,r,b)->
+          r.statusCode.should.be.equal 200
+          b.should.have.property 'secret', s
+          done()


Close discovered channels in npm start script.

--- src/boot.coffee
+++ src/boot.coffee
@@ -4,6 +4,7 @@
 module.exports = ->
   Discovery.on 'channel', (channel) ->
     console.log "Discovered Sphero: #{channel.sourceId}"
+    channel.close()
   Discovery.on 'error', (error) ->
     console.error error
   Discovery.start()


fixed tests for node 0.12.x

fixed tests for node 0.12.x

--- _src/test/mocha_test.coffee
+++ _src/test/mocha_test.coffee
@@ -444,9 +444,8 @@

 		before () ->
 			# don't override state because we still need `state.keys`
-			Object.assign state,
-				n: 0
-				startKeys: localCache.getStats().keys
+			state.n = 0
+			state.startKeys = localCache.getStats().keys
 			return

 		it "delete all previously set keys", () ->
@@ -742,7 +741,7 @@
 					true.should.eql res
 					(innerState.startKeys + 1).should.eql localCache.getStats().keys
 					# event handler should have been fired
-					0.should.eql localCache.listenerCount "set"
+					0.should.eql localCache.listeners("set").length
 					done()
 					return
 				return


Use regex to check for 'mailto:' link

Use regex to check for 'mailto:' link

--- src/scripts/loader.coffee
+++ src/scripts/loader.coffee
@@ -19,8 +19,7 @@
       href = $(this).attr('href')

       # Only handle links intended to be processed by Backbone
-      if e.isDefaultPrevented() or href.charAt(0) is '#' or
-        (href.length > 9 and href.substring(0,7) is 'mailto:') then return
+      if e.isDefaultPrevented() or href.charAt(0) is '#' or /^mailto:.+/.test(link) then return

       e.preventDefault()


Rolling back the previous commit

Rolling back the previous commit

--- stl/core/src/list.coffee
+++ stl/core/src/list.coffee
@@ -123,50 +123,16 @@
 # the rest of the standard Array methods and their replacements for old browsers
 Array_slice   = Array_proto.slice
 Array_splice  = Array_proto.splice
-Array_forEach = Array_proto.forEach || `function(callback, scope) {
-  for (var i=0, l=this.length; i < l; i++) {
-    callback.call(scope, this[i], i, this);
-  }}`
-Array_map     = Array_proto.map || `function(callback, scope) {
-  for (var result=[], i=0, l=this.length; i < l; i++) {
-    result[i] = callback.call(scope, this[i], i, this);
-  }return result;}`
+Array_forEach = Array_proto.forEach
+Array_map     = Array_proto.map
+Array_filter  = Array_proto.filter
+Array_reject  = (callback, scope)->
+  Array_filter.call @, ->
+    !callback.apply scope, arguments

-Array_filter  = Array_proto.filter || `function(callback, scope) {
-  for (var result=[], j=0, i=0, l=this.length; i < l; i++) {
-    if (callback.call(scope, this[i], i, this)) {
-      result[j++] = this[i];
-  }} return result;}`
+Array_some    = Array_proto.some
+Array_every   = Array_proto.every

-Array_reject  = `function(callback, scope) {
-  for (var result=[], j=0, i=0, l=this.length; i < l; i++) {
-    if (!callback.call(scope, this[i], i, this)) {
-      result[j++] = this[i];
-  }} return result;}`
-
-Array_some   = Array_proto.some || `function(callback, scope) {
-  for (var i=0, l=this.length; i < l; i++) {
-    if (callback.call(scope, this[i], i, this)) {
-      return true;
-  }} return false;}`
-
-Array_every = Array_proto.every || `function(callback, scope) {
-  for (var i=0, l=this.length; i < l; i++) {
-    if (!callback.call(scope, this[i], i, this)) {
-      return false;
-  }} return true;}`
-
-# adding those for old browsers
-unless List.prototype.indexOf
-  ext List.prototype,
-    indexOf: `function(value, from) {
-      for (var i=(from<0) ? Math.max(0, this.length+from) : from || 0, l=this.length; i < l; i++) {
-        if (this[i] === value) { return i; }
-      } return -1;}`
-    lastIndexOf: `function(value) {
-      for (var i=this.length-1; i > -1; i--) {
-        if (this[i] === value) { return i; }
-      } return -1;}`

 #
 # calls the array method on the list with the arguments


Add another test

Add another test

--- test/tabelog_test.coffee
+++ test/tabelog_test.coffee
@@ -97,3 +97,14 @@
           done e
       adapter.receive new TextMessage(user, "hubot tabelog lunch")

+    it 'replies tabelog dinner', (done)->
+      adapter.on "send", (envelope, strings)->
+        try
+          expect(strings).to.have.length(2)
+          expect(strings[0]).to.match /^http:\/\/.*\.jpg/
+          expect(strings[1]).to.match /tabelog.com/
+          do done
+        catch e
+          done e
+      adapter.receive new TextMessage(user, "hubot tabelog dinner")
+


Moving dynamic ColumnsController function into its own service

--- src/services/table.coffee
+++ src/services/table.coffee
@@ -49,20 +49,26 @@
       return new Cell(row, proto)
 ]

+angular.module("Mac").factory "dynamicColumnsFunction", ->
+  (models) ->
+    first   = models[0]
+    columns = []
+    for key, model of first
+        columns.push key
+    @set(columns)
+
 angular.module("Mac").factory "ColumnsController", [
   "tableComponents"
+  "dynamicColumnsFunction"
   (
     tableComponents
+    dynamicColumnsFunction
   ) ->
     class ColumnsController
       constructor: (@table) ->

-      dynamic: (models) ->
-        first = models[0]
-        columns = []
-        for key, model of first
-            columns.push key
-        @set(columns)
+      # Our overridable dynamic columns function
+      dynamic: dynamicColumnsFunction

       blank: ->
         # Makes a blank object with our colNames as keys


Grouping timetables by day

Grouping timetables by day

--- src/script/portal-minicart.coffee
+++ src/script/portal-minicart.coffee
@@ -152,6 +152,20 @@
 					break
 		@cartData.deliveryWindows = @cartData.slas[scheduledDeliverySlaId].availableDeliveryWindows
 		@cartData.firstAvailableDeliveryWindow = @cartData.deliveryWindows[0]
+
+		# Groups timetables by days
+		availableMonths = _.toArray _.groupBy @cartData.deliveryWindows, (dw) ->
+			date = new Date(dw.startDateUtc)
+			day = date.getDate()
+			month = date.getMonth()
+			return month
+
+		@cartData.availableMonths = _.map availableMonths, (month) ->
+			days = _.groupBy month, (timetable) ->
+				date = new Date(timetable.startDateUtc)
+				day = date.getDate()
+
+			_.toArray days

 		# Resolve available timetables on the same day of first available delivery window
 		firstAvailableDay = new Date(@cartData.firstAvailableDeliveryWindow.startDateUtc).getDate()


bucegi region

bucegi region


sending all events to particles

sending all events to particles

--- test/timed_events.coffee
+++ test/timed_events.coffee
@@ -8,7 +8,7 @@

   4.times ->
     gameObject.trigger "update", 1
-    gameObject.trigger ""
+    gameObject.trigger "after"

 # Clear out the module
 module()


adding more tests

adding more tests

--- test/giphy.spec.coffee
+++ test/giphy.spec.coffee
@@ -107,6 +107,25 @@
       giphyPluginInstance = hubotGiphy robot
       @fakes.stub giphyPluginInstance.api, '_request', (options, callback) -> callback 'XHR Attempted', null
       @fakes.stub giphyPluginInstance, 'respond'
+
+    it 'api instance should default to http', ->
+      giphyPluginInstance = hubotGiphy robot
+      giphyPluginInstance.api.httpService.globalAgent.protocol.should.match /^http:$/
+
+    it 'api instance supports enabling https via HUBOT_GIPHY_HTTPS', ->
+      process.env.HUBOT_GIPHY_HTTPS = 'true'
+      giphyPluginInstance = hubotGiphy robot
+      giphyPluginInstance.api.httpService.globalAgent.protocol.should.match /^https:$/
+
+    it 'api instance supports overriding the timeout via HUBOT_GIPHY_TIMEOUT', ->
+      process.env.HUBOT_GIPHY_TIMEOUT = '123'
+      giphyPluginInstance = hubotGiphy robot
+      giphyPluginInstance.api.timeout.should.eql 123
+
+    it 'api instance supports setting the api key via HUBOT_GIPHY_API_KEY', ->
+      process.env.HUBOT_GIPHY_API_KEY = 'testing'
+      giphyPluginInstance = hubotGiphy robot
+      giphyPluginInstance.api.apiKey.should.eql 'testing'

     it 'has an active respond trigger', ->
       robot.respond.should.have.been.called.once
@@ -307,6 +326,13 @@
         state.endpoint.should.eql 'test1'
         state.args.should.eql 'test2'

+      it 'handles only endpoint match', ->
+        state = { }
+        @fakes.stub @giphy, 'match', -> [ null, 'test1', '' ]
+        @giphy.getEndpoint state
+        state.endpoint.should.eql 'test1'
+        state.args.should.eql ''
+
       it 'handles only args match', ->
         state = { }
         @fakes.stub @giphy, 'match', -> [ null, null, 'test2' ]
@@ -755,7 +781,7 @@
         @fakes.stub @giphy, 'getUri'
         @fakes.stub @giphy, 'error'

-      it 'handles a valid msg', ->
+      it 'handles non-empty matched args', ->
         msg = { match: [ null, 'testing' ] }
         state = 'state'
         @fakes.stub @giphy, 'createState', -> state
@@ -765,6 +791,16 @@
         @giphy.getOptions.should.have.been.calledWith state
         @giphy.getUri.should.have.been.calledWith state

+      it 'handles empty matched args', ->
+        msg = { match: [ null, '' ] }
+        state = 'state'
+        @fakes.stub @giphy, 'createState', -> state
+        @giphy.respond msg
+        @giphy.createState.should.have.been.calledWith msg
+        @giphy.getEndpoint.should.have.been.calledWith state
+        @giphy.getOptions.should.have.been.calledWith state
+        @giphy.getUri.should.have.been.calledWith state
+
       it 'handles null msg', ->
         @giphy.respond()
         @giphy.respond null
@@ -778,3 +814,98 @@
         @giphy.respond { match: [ null ] }
         @giphy.getUri.should.not.have.been.called
         @giphy.error.should.have.callCount 4
+
+  describe 'plugin api integration', ->
+    giphyPluginInstance = null
+    regex = null
+    callback = null
+    msg = null
+
+    testInput = (fakes, input, result, options) ->
+      fakes.stub giphyPluginInstance.api, '_request', (options, callback) -> callback null, result
+      msg.match = regex.exec input
+      callback.call null, msg
+      if typeof options is 'function'
+        options.call null
+      else
+        giphyPluginInstance.api._request.should.have.been.called.once
+        requestOptions = giphyPluginInstance.api._request.getCalls()[0].args[0]
+        should.exist requestOptions
+        requestOptions.should.eql options
+        msg.send.should.have.been.called.once
+        msg.send.should.have.been.calledWith sampleUri
+
+    beforeEach ->
+      robot = { respond: @fakes.spy() }
+      giphyPluginInstance = hubotGiphy robot
+      responder = robot.respond.getCalls()[0]
+      regex = responder.args[0]
+      callback = responder.args[1]
+      msg = { send: @fakes.spy() }
+
+    it 'sends a response for "giphy search"', ->
+      testInput @fakes, 'giphy search', sampleResult, { api: 'gifs', endpoint: 'random', query: { } }
+
+    it 'sends a response for "giphy search test"', ->
+      testInput @fakes, 'giphy search test', sampleCollectionResult, { api: 'gifs', endpoint: 'search', query: { q: 'test' } }
+
+    it 'sends a response for "giphy search test1 test2"', ->
+      testInput @fakes, 'giphy search test1 test2', sampleCollectionResult, { api: 'gifs', endpoint: 'search', query: { q: 'test1 test2' } }
+
+    it 'sends a response for "giphy id"', ->
+      testInput @fakes, 'giphy id', sampleCollectionResult, ->
+        msg.send.should.have.been.calledWith 'No Id Provided'
+
+    it 'sends a response for "giphy id test"', ->
+      testInput @fakes, 'giphy id test', sampleCollectionResult, { api: 'gifs', query: { ids: 'test' } }
+
+    it 'sends a response for "giphy id test1 test2"', ->
+      testInput @fakes, 'giphy id test1 test2', sampleCollectionResult, { api: 'gifs', query: { ids: 'test1,test2' } }
+
+    it 'sends a response for "giphy translate"', ->
+      testInput @fakes, 'giphy translate', sampleResult, { api: 'gifs', endpoint: 'translate', query: { } }
+
+    it 'sends a response for "giphy translate test"', ->
+      testInput @fakes, 'giphy translate test', sampleResult, { api: 'gifs', endpoint: 'translate', query: { s: 'test' } }
+
+    it 'sends a response for "giphy translate test1 test2"', ->
+      testInput @fakes, 'giphy translate test1 test2', sampleResult, { api: 'gifs', endpoint: 'translate', query: { s: 'test1 test2' } }
+
+    it 'sends a response for "giphy random"', ->
+      testInput @fakes, 'giphy random', sampleResult, { api: 'gifs', endpoint: 'random', query: { } }
+
+    it 'sends a response for "giphy random test"', ->
+      testInput @fakes, 'giphy random test', sampleResult, { api: 'gifs', endpoint: 'random', query: { tag: 'test' } }
+
+    it 'sends a response for "giphy random test1 test2"', ->
+      testInput @fakes, 'giphy random test1 test2', sampleResult, { api: 'gifs', endpoint: 'random', query: { tag: 'test1 test2' } }
+
+    it 'sends a response for "giphy trending"', ->
+      testInput @fakes, 'giphy trending', sampleCollectionResult, { api: 'gifs', endpoint: 'trending' }
+
+    it 'sends a response for "giphy trending test"', ->
+      testInput @fakes, 'giphy trending test', sampleCollectionResult, { api: 'gifs', endpoint: 'trending' }
+
+    it 'sends a response for "giphy trending test1 test2"', ->
+      testInput @fakes, 'giphy trending test1 test2', sampleCollectionResult, { api: 'gifs', endpoint: 'trending' }
+
+    it 'sends a response for "giphy"', ->
+      testInput @fakes, 'giphy', sampleResult, { api: 'gifs', endpoint: 'random', query: { } }
+
+    it 'sends a response for "giphy test"', ->
+      testInput @fakes, 'giphy test', sampleCollectionResult, { api: 'gifs', endpoint: 'search', query: { q: 'test' } }
+
+    it 'sends a response for "giphy test1 test2"', ->
+      testInput @fakes, 'giphy test1 test2', sampleCollectionResult, { api: 'gifs', endpoint: 'search', query: { q: 'test1 test2' } }
+
+    it 'sends a response for "giphy search /api:stickers test"', ->
+      testInput @fakes, 'giphy search /api:stickers test', sampleCollectionResult, { api: 'stickers', endpoint: 'search', query: { api: 'stickers', q: 'test' } }
+
+    it 'sends a response for "giphy search /rating:pg test"', ->
+      testInput @fakes, 'giphy search /rating:pg test', sampleCollectionResult, { api: 'gifs', endpoint: 'search', query: { rating: 'pg', q: 'test' } }
+
+    it 'sends a response for "giphy search /limit:123 test"', ->
+      testInput @fakes, 'giphy search /limit:123 test', sampleCollectionResult, { api: 'gifs', endpoint: 'search', query: { limit: '123', q: 'test' } }
+
+    it 'sends a response for "giphy search /limit:123 /offset:25 test"', ->
+      testInput @fakes, 'giphy search /limit:123 /offset:25 test', sampleCollectionResult, { api: 'gifs', endpoint: 'search', query: { limit: '123', offset: '25', q: 'test' } }


Correct typo [skip ci]

Correct typo [skip ci]

--- test/goban.core.coffee
+++ test/goban.core.coffee
@@ -268,7 +268,7 @@

           expect(attempt).to.have.deep.property('color', color)

-          expect(attempt).to.have.deep.property('coord[0]', second)
+          expect(attempt).to.have.deep.property('coord[0]', first)
           expect(attempt).to.have.deep.property('coord[1]', second)

           return cb && cb()


separated tests

separated tests

--- test/js/totodoo.coffee
+++ test/js/totodoo.coffee
@@ -21,14 +21,25 @@

 	describe 'Init', ->

-		it 'has the title of the application in the window\'s title', (done) ->
-			expect(@driver.getTitle()).to.eventually.contain 'Totodoo App'
-			@timeout 4000, done()
+		describe 'Title', ->

-		it 'is the lenght of the default list items = 2', (done) ->
-			lis = @driver.findElements(js: 'return document.getElementById("todo-list").getElementsByTagName("li")')
-			expect(lis.length).to.eventually.equal 2
-			@timeout 4000, done()
+			it 'has the title of the application in the window\'s title', (done) ->
+				expect(@driver.getTitle()).to.eventually.contain 'Totodoo App'
+				@timeout 4000, done()
+
+		describe 'Default list', ->
+
+			it 'is the name of the default list', (done) ->
+				text = @driver.findElement(js: 'return document.getElementById("listName")').getText()
+				expect(text).to.eventually.equal 'Public list 1'
+				@timeout 4000, done()
+
+		describe 'Default list items count', ->
+
+			it 'is the lenght of the default list items = 2', (done) ->
+				lis = @driver.findElements(js: 'return document.getElementById("todo-list").getElementsByTagName("li")')
+				expect(lis.length).to.eventually.equal 2
+				@timeout 4000, done()

 	describe 'List actions', ->


this might fix #4630 in case of cc-in-cc dev (and on cocalc-docker if it happened there/)... though definitely not on cocalc.com, which doesn't use this code path.

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -13,9 +13,9 @@

 # id='eb5c61ae-b37c-411f-9509-10adb51eb90b';require('smc-hub/compute-client').compute_server(cb:(e,s)->console.log(e);global.s=s; s.project(project_id:id,cb:(e,p)->global.p=p;cidonsole.log(e)))
-#
+#
 # Another example with database on local host
-#
+#
 # id='7fffd5b4-d140-4a34-a960-9f71fa7fc54b';require('smc-hub/compute-client').compute_server(cb:(e,s)->console.log(e);global.t=s; s.project(project_id:id,cb:(e, p)->global.p=p))

 # obviously don't want to trigger this too quickly, since it may mean file loss.
@@ -1846,7 +1846,7 @@
             cb(err, address)
         )

-    # This will keep trying for up to an hour to get the address, with exponential
+    # This will keep trying for up to 5 minutes to get the address, with exponential
     # decay backing off up to 15s between attempts.
     # If/when it works, the returned address object will definitely have the
     # host, port and secret_token set.
@@ -1857,6 +1857,7 @@
             cb : required
         if @_address_cbs?
             @_address_cbs.push(opts.cb)
+            dbg("address already running, so adding callback to queue; it now has length #{@_address_cbs.length}")
             return
         @_synctable?.connect()
         @_address_cbs = [opts.cb]
@@ -1870,13 +1871,14 @@
                     cb(err)
             start_delay : 3000
             max_delay   : 15000
-            max_time    : 3600*1000
+            max_time    : 5*60*1000
             cb          : (err) =>
                 if not address and not err
                     err = "failed to get address"
-                for cb in @_address_cbs
+                v = @_address_cbs
+                delete @_address_cbs
+                for cb in v
                     cb(err, address)
-                delete @_address_cbs

     copy_path: (opts) =>
         opts = defaults opts,


Fix bug

Fix bug

--- scripts/amazon-ec.coffee
+++ scripts/amazon-ec.coffee
@@ -104,7 +104,7 @@
   stores = robot.brain.get(user) ? []

   # 重複を除く
-  if !stores.filter((item) -> item.title is message).length
+  if !stores.filter((item) -> item.title is query).length
     stores.push({title: query, kindle: isKindle})

   # user のID は連番で登録する


Upgrade greeting patterns

Upgrade greeting patterns

--- scripts/nostalgia.coffee
+++ scripts/nostalgia.coffee
@@ -400,7 +400,7 @@
     robot.respond /give up/i, giveUpRespond

     robot.hear /.*that's a bug.*/i, bobRossRespond
-    robot.hear /.*(((good)? +morning)|greetings),? +@?nostalgiabot!?.*/i, greetingRespond
+    robot.hear /.*((good morning)|morning|sup|hey|hello|howdy|greetings),? +@?nostalgiabot!?.*/i, greetingRespond

 `// All code below from http://www.atrixnet.com/bs-generator.html, I take no credit for it
 function randomarray(a) {


Added selectedInput.

Added selectedInput.

--- src/form.coffee
+++ src/form.coffee
@@ -211,12 +211,15 @@
                         choices: EnumModel.choices
                         choiceid: "options#" + optionid
                     }
-                get_val: ->
+                selectedInput: ->
                     for radiobtn in @el.find("input")
                         $radiobtn = $ radiobtn
                         if $radiobtn.is(':checked')
-                            return $radiobtn.attr "value"
+                            return $radiobtn
                     return null
+                get_val: ->
+                    selectedInput = @selectedInput()
+                    selectedInput?.attr "value"

                 set_val: (val)->
                     if val != @get_val()


fix problem up down left right button will move the whole page

--- src/game.coffee
+++ src/game.coffee
@@ -116,9 +116,11 @@
         when 13
           # ENTER
           @game.switch_scene('stage')
+          event.preventDefault()
         when 32
           # SPACE
           @toggle_players()
+          event.preventDefault()

   disable_selection_control: () ->
     $(document).unbind "keydown"
@@ -328,13 +330,16 @@
           # UP, LEFT
           @current_stage = @game.prev_stage()
           @update_stage_label()
+          event.preventDefault()
         when 39, 40
           # RIGHT, DOWN
           @current_stage = @game.next_stage()
           @update_stage_label()
+          event.preventDefault()
         when 13
           # ENTER
           @game.switch_scene('game')
+          event.preventDefault()
       false

   disable_stage_control: () ->
@@ -749,6 +754,7 @@
         @map.p1_tank().commander.add_key_event("keyup", event.which)
       if @map.p2_tank()
         @map.p2_tank().commander.add_key_event("keyup", event.which)
+      event.preventDefault()
       false

     $(document).bind "keydown", (event) =>
@@ -756,6 +762,7 @@
         @map.p1_tank().commander.add_key_event("keydown", event.which)
       if @map.p2_tank()
         @map.p2_tank().commander.add_key_event("keydown", event.which)
+      event.preventDefault()
       false

   enable_system_control: () ->
@@ -764,9 +771,11 @@
         when 13
           # ENTER
           if @running then @pause() else @rescue()
+          event.preventDefault()
         when 27
           # ESC
           @game.reset()
+          event.preventDefault()

   disable_controls: () ->
     if @map.p1_tank()


modify: gulp.config.coffee :D

modify: gulp.config.coffee :D

--- tasks/gulp.config.coffee
+++ tasks/gulp.config.coffee
@@ -777,17 +777,19 @@
   }

 # json-server watch & refresh
-g.task 'watch-api', ->
+g.task 'api', ->
   g.src paths.api.watch
   .pipe apiServer.pipe()
+
+g.task 'watch-api', ['api'], ->
+  $.watch paths.api.watch, ->
+    g.start 'api' # json ファイルが変更または追加されたらビルド出力

 # watch
 g.task 'watch', ['bs'], ->
   g.watch [paths.common.js.plugin, paths.common.js.javascript, paths.common.js.coffee], ['coffee']
   $.watch paths.common.img.src, ->
     g.start 'img' # img ファイルが変更または追加されたらビルド出力
-  $.watch paths.api.watch, ->
-    g.start 'watch-api' # json ファイルが変更または追加されたらビルド出力

 # watch rp
 g.task 'watch-rp', ['bs'], ->


Restored the `done` callback to the promise initProcess

--- src/hull.coffee
+++ src/hull.coffee
@@ -1,8 +1,17 @@
 define ['aura/aura', 'lib/hullbase', 'underscore'], (Aura, HullDef, _) ->
+
+  evtPool = {}
+  emittedEvts = []
+  HullDef.on = (evt, fn)->
+    evtPool[evt] ?= []
+    evtPool[evt].push fn
+
+  HullDef.emit = (evt, data)->
+    emittedEvts.push({evt: evt, data: data})

   hull = null

-  myApp = (cb)->
+  myApp = ()->
     name: 'Hull'
     initialize: (app)->
       app.core.mediator.setMaxListeners(100)
@@ -18,8 +27,6 @@
           _h[k] = window.Hull[k]
         window.Hull = _h

-      cb(window.Hull) if cb
-
   if window.opener && window.opener.Hull
     try
       window.opener.Hull.emit("hull.authComplete")
@@ -33,7 +40,7 @@
     config.namespace = "hull"
     hull.app = Aura(config)
     initProcess = hull.app
-        .use(myApp(cb))
+        .use(myApp())
         .use('aura-extensions/aura-handlebars')
         .use('aura-extensions/aura-backbone')
         .use('aura-extensions/hull-utils')
@@ -53,4 +60,12 @@
       delete hull.app
       throw err if !errcb

+    initProcess.done ()->
+      cb(window.Hull) if cb
+      for evt, cbArray in evtPool
+        for cb in cbArray
+          Hull.on(evt, cbArray)
+      for emitted in emittedEvts
+        Hull.emit(emitted.evt, emitted.data)
+
     return hull