Color support

Color support

For now, the colors are browser specific (its not a shared
attribute). It is however possible to create a new shared object with
all the colors in it.

--- lib/y-richtext.coffee
+++ lib/y-richtext.coffee
@@ -15,6 +15,8 @@
   # @param author [String] the name of the local author
   constructor: (editor_name, editor_instance) ->
     @locker = new Locker()
+    @_colors = {length: 0}
+    @_graphicsPalette = ['#837DFA', '#FA7D7D','#7DFA87', '#DCE874']

     if editor_name? and editor_instance?
       @_bind_later =
@@ -202,8 +204,12 @@
     @_model.getContent("cursors").observe (events) => @locker.try ()=>
       for event in events
         if event.type is "update" or event.type is "add"
-          author = event.changedBy
-          ref_to_char = event.object.val(author)
+          authorId = event.changedBy
+          ref_to_char = event.object.val(authorId)
+          author = (authorId) =>
+            mod = @_model.getContent('authors').val()
+            mod[authorId] or "Default user"
+
           if ref_to_char is null
             position = @editor.getLength()
           else if ref_to_char?
@@ -214,7 +220,7 @@
               # in the future, we could replace the cursors, with a y-selections
               #
               window.setTimeout(()->
-                  event.object.delete(author)
+                  event.object.delete(authorId)
                 , 0)
               return
             else
@@ -223,10 +229,20 @@
             console.warn "ref_to_char is undefined"
             return

+          # never knows…
+          if authorId == "length"
+            authorId = "_length"
+          if authorId in @_colors
+            color = @_colors[authorId]
+          else
+            @_colors.length++
+            color = @_graphicsPalette[@_colors.length%@_graphicsPalette.length]
+            @_colors[authorId]
+
           params =
             id: author
             index: position
-            text: author
+            text: author(authorId)
             color: "grey"
           @editor.setCursor params
         else


code formatting

code formatting

--- gilbox-kinetic-slider.coffee
+++ gilbox-kinetic-slider.coffee
@@ -44,9 +44,8 @@

   gilbox.directive 'kineticSlider', ['$window', '$document', 'browserHelper', ($window, $document, browserHelper) ->
     restrict: 'A'
-    scope: {
+    scope:
       contentWidth: '=?'
-    }
     replace: true
     transclude: true
     template: '<div ng-transclude msd-wheel="wheel($event, $delta, $deltaX, $deltaY)"></div>'


use of to test object key presence

use of to test object key presence

--- maskew.coffee
+++ maskew.coffee
@@ -15,9 +15,9 @@
 transform = (y, angle) -> "translate3d(0, #{ y }px, 0) rotate3d(0, 0, 1, #{ angle }deg)"

 testProp  = (prop) ->
-  return prop if testEl.style[prop]?
+  return prop if prop of testEl.style
   for prefix in prefixList
-    return key if testEl.style[(key = prefix + prop.charAt(0).toUpperCase() + prop.slice 1)]?
+    return key if (key = prefix + prop.charAt(0).toUpperCase() + prop.slice 1) of testEl.style
   false


Cycle detection

Cycle detection

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -284,6 +284,7 @@
   Messages.findOne(message).group

 @findMessageParent = (message) ->
+  message = message._id if message._id?
   parents = Messages.find
     children: message
   .fetch()
@@ -376,77 +377,89 @@
   if parent?
     pmsg = Messages.findOne parent
     unless pmsg
-      console.log 'Missing parent', parent, 'for child', child
+      console.warn 'Missing parent', parent, 'for child', child
       return  ## This should only happen in client simulation
     group = pmsg.group
     root = pmsg.root ? parent
+
+    ## Check before creating a cycle in the parent pointers!
+    ancestor = pmsg
+    while ancestor?
+      if ancestor._id == child
+        throw new Meteor.Error 'messageParent.cycle',
+          "Attempt to make #{child} its own ancestor (via #{parent})"
+      ancestor = findMessageParent ancestor
   else
     group = child.group  ## xxx can't specify other group...
     root = null
-  if canEdit(child) and canPost group, parent
-    cmsg = Messages.findOne child
-    oldPosition = null
-    if oldParent
-      oldParentMsg = findMessageParent child
-      if oldParentMsg?
-        oldParent = oldParentMsg._id
-        oldPosition = oldParentMsg.children.indexOf child
-        return if parent == oldParent and (
-          (position? and position == oldPosition) or
-          (not position? and oldPosition == oldParentMsg.children.length-1)
-        )  ## no-op
-        Messages.update oldParent,
-          $pull: children: child
-      else
-        oldParent = null
-    return if parent == oldParent == null  ## no-op, root case
-    if parent?
-      if position?
-        Messages.update parent,
-          $push: children:
-            $each: [child]
-            $position: position
-      else
-        Messages.update parent,
-          $push: children: child
-    if root != cmsg.root
-      Messages.update child,
-        $set: root: root
-      if cmsg.root?
-        ## If we move a nonroot message to have new root, update descendants.
-        descendants = descendantMessageIds child
-        if descendants.length > 0
-          Messages.update
-            _id: $in: descendants
-          ,
-            $set: root: root ? child
-          ,
-            multi: true
-      else
-        ## To reparent root message, change the root of all descendants.
+
+  unless canEdit(child) and canPost group, parent
+    throw new Meteor.Error 'messageParent.unauthorized',
+      "Insufficient privileges to reparent message #{child} into #{parent}"
+
+  cmsg = Messages.findOne child
+  oldPosition = null
+  if oldParent
+    oldParentMsg = findMessageParent child
+    if oldParentMsg?
+      oldParent = oldParentMsg._id
+      oldPosition = oldParentMsg.children.indexOf child
+      return if parent == oldParent and (
+        (position? and position == oldPosition) or
+        (not position? and oldPosition == oldParentMsg.children.length-1)
+      )  ## no-op
+      Messages.update oldParent,
+        $pull: children: child
+    else
+      oldParent = null
+  return if parent == oldParent == null  ## no-op, root case
+  if parent?
+    if position?
+      Messages.update parent,
+        $push: children:
+          $each: [child]
+          $position: position
+    else
+      Messages.update parent,
+        $push: children: child
+  if root != cmsg.root
+    Messages.update child,
+      $set: root: root
+    if cmsg.root?
+      ## If we move a nonroot message to have new root, update descendants.
+      descendants = descendantMessageIds child
+      if descendants.length > 0
         Messages.update
-          root: child
+          _id: $in: descendants
         ,
-          $set: root: root ? child  ## actually must be root
+          $set: root: root ? child
         ,
           multi: true
-        _noLongerRoot child if root?
-      _submessagesChanged cmsg.root     ## old root
-      _submessagesChanged root ? child  ## new root
-    doc =
-      child: child
-      parent: parent
-      position: position
-      oldParent: oldParent
-      oldPosition: oldPosition
-    if importing
-      #cmsg = Messages.findOne child
-      doc.updator = cmsg.creator
-      doc.updated = cmsg.created
     else
-      doc.updator = Meteor.user().username
-      doc.updated = new Date
-    MessagesParent.insert doc
+      ## To reparent root message, change the root of all descendants.
+      Messages.update
+        root: child
+      ,
+        $set: root: root ? child  ## actually must be root
+      ,
+        multi: true
+      _noLongerRoot child if root?
+    _submessagesChanged cmsg.root     ## old root
+    _submessagesChanged root ? child  ## new root
+  doc =
+    child: child
+    parent: parent
+    position: position
+    oldParent: oldParent
+    oldPosition: oldPosition
+  if importing
+    #cmsg = Messages.findOne child
+    doc.updator = cmsg.creator
+    doc.updated = cmsg.created
+  else
+    doc.updator = Meteor.user().username
+    doc.updated = new Date
+  MessagesParent.insert doc

 if Meteor.isServer
   editorTimers = {}


Improve reported source container information in autocomplete selection list.

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -121,6 +121,7 @@
             label: citation.entryTags.prettyTitle
             by: citation.entryTags.prettyAuthors
             type: citation.entryTags.type
+            in: citation.entryTags.in or citation.entryTags.journal or citation.entryTags.booktitle
           }

           if citation.entryTags.url?


Browser compatibility for background animation.

--- client/lib/background.coffee
+++ client/lib/background.coffee
@@ -20,6 +20,9 @@
     b = hue2rgb(p, q, h - 1 / 3)

   ((r * 255) << 16) + ((g * 255) << 8) + (b * 255)
+
+# If not supported use an identity function which will never call a callback to disable animation
+frame = requestAnimationFrame or webkitRequestAnimationFrame or mozRequestAnimationFrame or (->)

 class Vector
   constructor: (@x, @y) ->
@@ -95,11 +98,11 @@
   computeRatio: =>
     devicePixelRatio = window.devicePixelRatio or 1
     context = @renderer.gl or @renderer.context
-    backingStoreRatio = context.webkitBackingStorePixelRatio or
+    backingStoreRatio = context.backingStorePixelRatio or
+                        context.webkitBackingStorePixelRatio or
                         context.mozBackingStorePixelRatio or
                         context.msBackingStorePixelRatio or
-                        context.oBackingStorePixelRatio or
-                        context.backingStorePixelRatio or 1
+                        context.oBackingStorePixelRatio or 1

     @ratio = devicePixelRatio / backingStoreRatio

@@ -107,7 +110,7 @@
     @resizeView()
     @generateTriangles()

-    requestAnimationFrame @draw
+    frame @draw

     @renderer.view

@@ -182,4 +185,4 @@

     @renderer.render @stage

-    requestAnimationFrame @draw
+    frame @draw


add item type

add item type

--- lib/simulate.coffee
+++ lib/simulate.coffee
@@ -47,10 +47,11 @@
     for id, i in sortieShip.equipment
       if id == 42
         sortieShip.nowHP = Math.floor(sortieShip.maxHP / 5)
-        break
+        return 42
       else if id == 43
         sortieShip.nowHP = sortieShip.maxHP
-        break
+        return 43
+  return 0

 simulateAerialCombat = (sortieShip, enemyShip, kouku) ->
   list = []


Bug fix: chibug & yukibug

Bug fix: chibug & yukibug

--- lib/simulate.coffee
+++ lib/simulate.coffee
@@ -186,9 +186,9 @@
     sortiePos = req.api_deck_id - 1 if !isCombined
     for i in [0..5]
       sortieShip.push new Ship ShipOwner.Ours, req.poi_sortie_fleet[i], i + 1, req.poi_sortie_equipment[i], req.api_nowhps[i + 1], req.api_maxhps[i + 1]
-      enemyShip.push new Ship ShipOwner.Enemy, req.api_ship_ke[i + 1], i + 1, req.poi_combined_equipment[i], req.api_nowhps[i + 7], req.api_maxhps[i + 7]
-      if isCombined
-        combinedShip.push new Ship ShipOwner.Ours, req.poi_combined_fleet[i], i + 1, req.api_nowhps[i + 13], req.api_maxhps[i + 13]
+      enemyShip.push new Ship ShipOwner.Enemy, req.api_ship_ke[i + 1], i + 1, null, req.api_nowhps[i + 7], req.api_maxhps[i + 7]
+      if isCombined
+        combinedShip.push new Ship ShipOwner.Ours, req.poi_combined_fleet[i], i + 1, req.poi_combined_equipment[i], req.api_nowhps_combined[i + 1], req.api_maxhps_combined[i + 1]
     sortieProgress = []

     # Air battle


Use onDidChangeModified

Use onDidChangeModified

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -17,7 +17,7 @@
     @item.on? 'icon-changed', =>
       @updateIcon()

-    @item.on? 'modified-status-changed', =>
+    @modifiedSubscription = @item.onDidChangeModified? =>
       @updateModifiedStatus()

     @subscribe atom.config.observe 'tabs.showIcons', => @updateIconVisibility()
@@ -49,6 +49,7 @@

   beforeRemove: ->
     @titleSubscription?.dispose()
+    @modifiedSubscription?.dispose()
     @destroyTooltip() if @hasBeenMousedOver

   updateDataAttributes: ->


stop using dependency in same line as require statement

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -19,7 +19,7 @@
 fs = require 'fs'
 os = require 'os'
 path = require 'path'
-execFile = require('child_process').execFile
+child_process = require 'child_process'
 request = require 'request'
 rimraf = require 'rimraf'
 ini = require 'ini'
@@ -249,7 +249,7 @@
       return
     pattern = /\d+\.\d+/
     location = locations[0]
-    execFile(location, args, (error, stdout, stderr) ->
+    child_process.execFile(location, args, (error, stdout, stderr) ->
       if not error?
         if stdout? and stdout.match(pattern) or stderr? and stderr.match(pattern)
           global.cachedPythonLocation = location
@@ -290,7 +290,7 @@
   pythonLocation((python) ->
     if python?
       args = [cliLocation(), '--version']
-      execFile(python, args, (error, stdout, stderr) ->
+      child_process.execFile(python, args, (error, stdout, stderr) ->
         if not error?
           currentVersion = stderr.trim()
           log.debug 'Current wakatime-cli version is ' + currentVersion
@@ -428,7 +428,7 @@

       log.debug python + ' ' + args.join(' ')

-      proc = execFile(python, args, (error, stdout, stderr) ->
+      proc = child_process.execFile(python, args, (error, stdout, stderr) ->
         if error?
           if stderr? and stderr != ''
             log.warn stderr


Remove App._renderContext

Remove App._renderContext

--- src/app.coffee
+++ src/app.coffee
@@ -25,8 +25,6 @@
   @classAccessor 'routeMapBuilder', -> new Batman.RouteMapBuilder(@, @get('routeMap'))
   @classAccessor 'dispatcher', -> new Batman.Dispatcher(@, @get('routeMap'))
   @classAccessor 'controllers', -> @get('dispatcher.controllers')
-
-  @classAccessor '_renderContext', -> Batman.RenderContext.base.descend(@)

   # Layout is the base view that other views can be yielded into. The
   # default behavior is that when `app.run()` is called, a new view will


Change the name of the EQ layer

Change the name of the EQ layer

--- src/app.coffee
+++ src/app.coffee
@@ -97,7 +97,7 @@
   ,
     new app.models.GeoJSONLayer
       id: "earthquakes"
-      layerName: "Earthquake Hypocenters"
+      layerName: "Earthquake Epicenters"
       serviceUrl: app.geoserverUrl
       serviceType: "WFS"
       typeName: "azgs:earthquakedata"


Fix like button

Fix like button

--- assets/js/app.coffee
+++ assets/js/app.coffee
@@ -52,22 +52,12 @@
         console.log 'New photos added, and photos.length = ' + photos.length
         disable_buttons(false)
         if $('#photo').attr('src') == ''
-          next_photo(->
-            VK.Widgets.Like("vk_like", {
-            type: "mini",
-            height: 20,
-            pageTitle: "Угадай страну: " + curr_photo.country,
-            pageUrl: 'https://vk.com/app' + app_id,
-            pageImage: curr_photo.url,
-            text: curr_photo.res_url
-            },
-            id)
-          )
+          next_photo()
         else if $('#photo').is(':hidden')
           $('#photo').show()

-  next_photo = (onFinish)->
+  next_photo = ()->

     $('#photo').hide()
     $('#circular').show()
@@ -112,15 +102,23 @@
         rand_button.html possible_countries[c_index]
         possible_countries.splice c_index, 1

+      # if the first time when we load pic, then add like widget
+      if $('#photo').attr('src') == ''
+        id = curr_photo.res_url.match(/\d+$/)[0]
+        VK.Widgets.Like("vk_like", {
+        type: "mini",
+        height: 20,
+        pageTitle: "Угадай страну: " + curr_photo.country,
+        pageUrl: 'https://vk.com/app' + app_id,
+        pageImage: curr_photo.url,
+        text: curr_photo.res_url
+        },
+        id)

       #load new photo to the img element
       $('#photo').attr('src', curr_photo.url).on 'load', ->
         $('#circular').hide()
         $('#photo').show()
-        id = curr_photo.res_url.match(/\d+$/)[0]
-
-        #$('#vk_like').empty()
-

         $('#photo_url').prop 'href', curr_photo.res_url
         #center the image
@@ -134,9 +132,6 @@
       if active_thumb == -1
         $('#thumbs_down').css 'background', 'url(/img/thumbs_down20.png)'
         active_thumb = 0
-
-      if onFinish
-        onFinish()

   show_right_answere = (how_long) ->
     #show the right answere


Added onLoad() callback.

Added onLoad() callback.

--- src/tree.coffee
+++ src/tree.coffee
@@ -27,7 +27,6 @@
   items = @items
   settings = getSettings()
   template = @
-  # loadTree(@)
   refreshTree(@)

 TemplateClass.destory = ->
@@ -60,15 +59,13 @@
     autoOpen: settings.autoExpand
     selectable: settings.selectable
   treeArgs.onCreateLi = onCreateNode.bind(null, template)
-
   # Check all new items if needed.
   if settings.checked
     template.check.setIds(_.keys(treeData.visited.ids))
-
   $tree.tree(treeArgs)
+  settings.onLoad?(template)
   setUpReactiveUpdates(template)

-# refreshTree = Functions.debounceLeft(loadTree, 1000)
 refreshTree = _.debounce(loadTree, 1000)

 setUpReactiveUpdates = (template) ->


Fix socket highlight areas for large paddings

--- src/view.coffee
+++ src/view.coffee
@@ -1947,8 +1947,8 @@

     computeOwnDropArea: ->
       return unless @model.parent?.type in ['indent', 'document']
-      # Our drop area is a rectangle of
-      # height dropAreaHeight and a width
+      # Our drop area is a puzzle-piece shaped path
+      # of height opts.highlightAreaHeight and width
       # equal to our last line width,
       # positioned at the bottom of our last line.
       if @carriageArrow is CARRIAGE_ARROW_INDENT
@@ -2141,7 +2141,7 @@
         @highlightArea.setPoints @path._points
         @highlightArea.style.strokeColor = '#FF0'
         @highlightArea.style.fillColor = 'none'
-        @highlightArea.style.lineWidth = @view.opts.padding
+        @highlightArea.style.lineWidth = @view.opts.highlightAreaHeight / 2
         @highlightArea.update()
         @highlightArea.deactivate()


cleaning

cleaning

--- src/scripts/githooks/coreos_restart/status.coffee
+++ src/scripts/githooks/coreos_restart/status.coffee
@@ -6,7 +6,6 @@

 {async, shell, sleep, collect, map, keys} = require "fairmont"
 api = require "./api"
-

 get_status = async (cluster, services) ->
   # Query the cluster's fleetctl tool for information about services.


Validate may now receive countryCode and ndc as param

 - Improve perfomance
 - 2 new aux functions for regexes

--- src/coffee/vtex-phone.coffee
+++ src/coffee/vtex-phone.coffee
@@ -16,7 +16,7 @@

 class Phone
 	constructor: ->
-		# Like so: +55 (21) 9898-6565
+		# Like so: +55 21 9898 6565
 		@INTERNATIONAL = 0

 		# Like so: (21) 9898-6565
@@ -25,32 +25,33 @@
 		# And: 9898-6565
 		@LOCAL = 2

+	validateInternational: (originalNumber, givenCountryCode, givenNationalDestinationCode) =>
+		number = originalNumber
+		number = @normalize(number) # Clean up number

-	validateInternational: (originalNumber) =>
-		number = originalNumber
-
-		# Clean up number
-		number = @normalize(number)
-
-		for countryCode, countryObj of vtex.phone.countries
-			countryCodeRegex = new RegExp "^"+countryCode
-
-			if countryCodeRegex.test(number)
-				withoutCountryCode = number.replace(countryCodeRegex, "")
-				foundCountryCode = true
-				break
+		if givenCountryCode
+			countryCode = givenCountryCode
+			[foundCountryCode, countryCodeRegex] = @testCountryCode(countryCode, number)
+		else
+			for countryCode, countryObj of vtex.phone.countries
+				[foundCountryCode, countryCodeRegex] = @testCountryCode(countryCode, number)
+				break if foundCountryCode is true

 		if foundCountryCode
-			for nationalDestinationCode in countryObj.nationalDestinationCode
-				ndcPattern = "^("+countryObj.optionalTrunkPrefix+"|)"+nationalDestinationCode
-				ndcRegex = new RegExp ndcPattern
+			withoutCountryCode = number.replace(countryCodeRegex, "")
+			countryObj = root.vtex.phone.countries[countryCode]

-				if ndcRegex.test(withoutCountryCode)
-					withoutNDC = withoutCountryCode.replace(ndcRegex, "")
-					foundNationalDestinationCode = true
-					break
+			if givenNationalDestinationCode
+				nationalDestinationCode = givenNationalDestinationCode
+				[foundNDC, ndcRegex] = @testNDC(nationalDestinationCode, countryObj, withoutCountryCode)
+			else
+				for nationalDestinationCode in countryObj.nationalDestinationCode
+					[foundNDC, ndcRegex] = @testNDC(nationalDestinationCode, countryObj, withoutCountryCode)
+					break if foundNDC is true

-		if foundNationalDestinationCode
+		if foundNDC
+			withoutNDC = withoutCountryCode.replace(ndcRegex, "")
+
 			phoneNumber = countryObj.specialRules(originalNumber, withoutCountryCode,
 												withoutNDC, nationalDestinationCode)

@@ -63,6 +64,17 @@
 	normalize: (number) =>
 		# Remove whitespaces, parenthesis, slashes, dots, plus sign and letters
 		return number.replace(/\ |\(|\)|\-|\.|[A-z]|\+/g, "")
+
+	testCountryCode: (countryCode, number) =>
+		countryCodeRegex = new RegExp "^"+countryCode
+
+		if countryCodeRegex.test(number) then [true, countryCodeRegex] else [false, null]
+
+	testNDC: (nationalDestinationCode, countryObj, number) =>
+		ndcPattern = "^("+countryObj.optionalTrunkPrefix+"|)"+nationalDestinationCode
+		ndcRegex = new RegExp ndcPattern
+
+		if ndcRegex.test(number) then [true, ndcRegex] else [false, null]

 	format: (phone, format = vtex.phone.INTERNATIONAL) =>
 		if vtex.phone.countries[phone.countryCode].format
@@ -78,16 +90,16 @@
 				if phone.nationalDestinationCode
 					resultString += phone.nationalDestinationCode + " "
 				resultString += splitNumber.join(" ")
+
 			when vtex.phone.NATIONAL
 				if phone.nationalDestinationCode
 					resultString += "(" + phone.nationalDestinationCode + ") "
 				separator = vtex.phone.countries[phone.countryCode].nationalNumberSeparator
 				resultString += splitNumber.join(separator)
+
 			when vtex.phone.LOCAL
 				separator = vtex.phone.countries[phone.countryCode].nationalNumberSeparator
 				resultString = splitNumber.join(separator)
-			else
-				resultString = ""

 		return resultString


Added square for 0 speed maneuver.

Added square for 0 speed maneuver.

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -868,114 +868,118 @@
                 outTable += ""
                 if maneuvers[speed][turn] > 0

-                    # default values are for a straight line
-                    startx = 100
-                    starty = 180
-                    smoothx = 100
-                    smoothy = 100
-                    endx = 100
-                    endy = 80
-                    turnType = "S" # S = smooth, L = line, C = cubic bezier (for k-turn)
-                    extraPoint = "" # extra inflection point for cubic bezier
-
-                    arrowStartx = 70
-                    arrowStarty = 80
-                    arrowMove = "H130"
-                    arrowEndx = 100
-                    arrowEndy = 30
-                    transform = ""
-
-                    switch turn
-                        when 0
-                            # turn left
-                            startx = 160
-                            smoothx = 160
-                            smoothy = 70
-                            endx = 80
-                            endy = 70
-                            turnType = "L"
-
-                            arrowStartx = 80
-                            arrowStarty = 100
-                            arrowMove = "V40"
-                            arrowEndx = 30
-                            arrowEndy = 70
-
-                        when 1
-                            # bank left
-                            startx = 150
-                            smoothx = 150
-                            smoothy = 120
-                            endx = 80
-                            endy = 60
-
-                            arrowStartx = 80
-                            arrowStarty = 100
-                            arrowMove = "V40"
-                            arrowEndx = 30
-                            arrowEndy = 70
-                            transform = "transform='translate(-5 -15) rotate(45 70 90)' "
-
-                        # when 2 # straight - this is the default value, don't need to do anything
-
-                        when 3
-                            # bank right
-                            startx = 50
-                            smoothx = 50
-                            smoothy = 120
-                            endx = 120
-                            endy = 60
-
-                            arrowStartx = 120
-                            arrowStarty = 100
-                            arrowMove = "V40"
-                            arrowEndx = 170
-                            arrowEndy = 70
-                            transform = "transform='translate(5 -15) rotate(-45 130 90)' "
-
-                        when 4
-                            # turn right
-                            startx = 40
-                            smoothx = 40
-                            smoothy = 70
-                            endx = 120
-                            endy = 70
-                            turnType = "L"
-
-                            arrowStartx = 120
-                            arrowStarty = 100
-                            arrowMove = "V40"
-                            arrowEndx = 170
-                            arrowEndy = 70
-
-                        when 5
-                            # k-turn/u-turn
-                            startx = 150
-                            smoothx = 150
-                            smoothy = -10
-                            endx = 60
-                            endy = 120
-                            turnType = "C"
-                            extraPoint = "60,-10 "
-
-                            arrowStartx = 30
-                            arrowStarty = 120
-                            arrowMove = "H90"
-                            arrowEndx = 60
-                            arrowEndy = 180
-
-
                     color = switch maneuvers[speed][turn]
                         when 1 then "white"
                         when 2 then "green"
                         when 3 then "red"

-                    outTable += $.trim """
-                      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 #{viewWidth} #{viewHeight}">
-                        <path d='M#{arrowStartx},#{arrowStarty} #{arrowMove} L#{arrowEndx},#{arrowEndy} Z' fill='#{color}' #{transform}/>
-                        <path stroke-width='15' fill='none' stroke='#{color}' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />
-                      </svg>
-                    """
+                    outTable += """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 #{viewWidth} #{viewHeight}">"""
+
+                    if speed == 0
+                        outTable += """<rect x="50" y="50" width="100" height="100" style="fill:#{color}" />"""
+                    else
+
+                        # default values are for a straight line
+                        startx = 100
+                        starty = 180
+                        smoothx = 100
+                        smoothy = 100
+                        endx = 100
+                        endy = 80
+                        turnType = "S" # S = smooth, L = line, C = cubic bezier (for k-turn)
+                        extraPoint = "" # extra inflection point for cubic bezier
+
+                        arrowStartx = 70
+                        arrowStarty = 80
+                        arrowMove = "H130"
+                        arrowEndx = 100
+                        arrowEndy = 30
+                        transform = ""
+
+                        switch turn
+                            when 0
+                                # turn left
+                                startx = 160
+                                smoothx = 160
+                                smoothy = 70
+                                endx = 80
+                                endy = 70
+                                turnType = "L"
+
+                                arrowStartx = 80
+                                arrowStarty = 100
+                                arrowMove = "V40"
+                                arrowEndx = 30
+                                arrowEndy = 70
+
+                            when 1
+                                # bank left
+                                startx = 150
+                                smoothx = 150
+                                smoothy = 120
+                                endx = 80
+                                endy = 60
+
+                                arrowStartx = 80
+                                arrowStarty = 100
+                                arrowMove = "V40"
+                                arrowEndx = 30
+                                arrowEndy = 70
+                                transform = "transform='translate(-5 -15) rotate(45 70 90)' "
+
+                            # when 2 # straight - this is the default value, don't need to do anything
+
+                            when 3
+                                # bank right
+                                startx = 50
+                                smoothx = 50
+                                smoothy = 120
+                                endx = 120
+                                endy = 60
+
+                                arrowStartx = 120
+                                arrowStarty = 100
+                                arrowMove = "V40"
+                                arrowEndx = 170
+                                arrowEndy = 70
+                                transform = "transform='translate(5 -15) rotate(-45 130 90)' "
+
+                            when 4
+                                # turn right
+                                startx = 40
+                                smoothx = 40
+                                smoothy = 70
+                                endx = 120
+                                endy = 70
+                                turnType = "L"
+
+                                arrowStartx = 120
+                                arrowStarty = 100
+                                arrowMove = "V40"
+                                arrowEndx = 170
+                                arrowEndy = 70
+
+                            when 5
+                                # k-turn/u-turn
+                                startx = 150
+                                smoothx = 150
+                                smoothy = -10
+                                endx = 60
+                                endy = 120
+                                turnType = "C"
+                                extraPoint = "60,-10 "
+
+                                arrowStartx = 30
+                                arrowStarty = 120
+                                arrowMove = "H90"
+                                arrowEndx = 60
+                                arrowEndy = 180
+
+                        outTable += $.trim """
+                          <path d='M#{arrowStartx},#{arrowStarty} #{arrowMove} L#{arrowEndx},#{arrowEndy} Z' fill='#{color}' #{transform}/>
+                          <path stroke-width='15' fill='none' stroke='#{color}' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />
+                        """
+                    outTable += "</svg>"
                 outTable += "	"
             outTable += "
"
         outTable += "

"


Correctly toggle black and white mode based on checkbox.

fixes #194

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -704,7 +704,7 @@
                     for ship in @ships
                         @printable_container.find('.printable-body').append ship.toHTML() if ship.pilot?
                     @printable_container.find('.fancy-ship').toggleClass 'tall', @list_modal.find('.toggle-vertical-space').prop('checked')
-                    @printable_container.find('.printable-body').toggleClass 'bw', @list_modal.find('.toggle-print-color').prop('checked')
+                    @printable_container.find('.printable-body').toggleClass 'bw', not @list_modal.find('.toggle-color-print').prop('checked')
             # Add List Juggler QR code
             query = @permalink.attr('href').split(/\?/)[1]
             if query? and @list_modal.find('.toggle-juggler-qrcode').prop('checked')


Bind httpServer to specific ip and udp socket to ssdp address

This change allows to run a http server for each ip address of the
device, making upnp to work even with multiple network interfaces.
Binding to 0.0.0.0 does not work because this address is reported back
in the ssdp packet as the address to use to retrieve the xml, witch is
incorrect.

--- lib/devices/Device.coffee
+++ lib/devices/Device.coffee
@@ -45,13 +45,13 @@
       uuid: (cb) => @getUuid cb
       port: (cb) =>
         @httpServer = http.createServer(@httpListener)
-        @httpServer.listen (err) -> cb err, @address().port
+        @httpServer.listen 0, @address, (err) -> cb err, @address().port
       (err, res) =>
         return @emit 'error', err if err?
         @uuid = "uuid:#{res.uuid}"
         @address = res.address
         @httpPort = res.port
-        @broadcastSocket.bind @ssdp.port, @address, =>
+        @broadcastSocket.bind @ssdp.port, @ssdp.address, =>
           @broadcastSocket.addMembership @ssdp.address
           @broadcastSocket.setMulticastTTL @ssdp.ttl
           @addServices()


bitbucket: fix error 500

bitbucket: fix error 500

Basically, the plugin wasn't even working.

--- plugins/bitbucket-issues/index.coffee
+++ plugins/bitbucket-issues/index.coffee
@@ -4,9 +4,8 @@

 class BitbucketIssue extends NotificationPlugin
   BASE_URL = "https://bitbucket.org"
-  @receiveEvent: (config, event, callback) ->
-    return if event?.trigger?.type == "reopened"

+  @openIssue: (config, event, callback) ->
     query_object =
       "title": @title(event)
       "content": @markdownBody(event)
@@ -15,7 +14,7 @@

     # Send the request
     @request
-      .post(url.resolve(BASE_URL, "/api/1.0/repositories/#{config.repo}/issues"))
+      .post(url.resolve(BASE_URL, "/api/1.0/repositories/#{config.username}/#{config.repo}/issues"))
       .timeout(4000)
       .auth(config.username, config.password)
       .set('Accept', 'application/json')
@@ -29,4 +28,12 @@
           id: res.body.local_id
           url: url.resolve(BASE_URL, "#{config.repo}/issue/#{res.body.local_id}")

+  @receiveEvent: (config, event, callback) ->
+    if event?.trigger?.type == "reopened"
+      if event?.error?.createdIssue?.id
+        @ensureIssueOpen(config, event.error.createdIssue.id, callback)
+        @addCommentToIssue(config, event.error.createdIssue.id, @markdownBody(event))
+    else
+      @openIssue(config, event, callback)
+
 module.exports = BitbucketIssue


Fix dispose

Fix dispose

--- lib/file-info-view.coffee
+++ lib/file-info-view.coffee
@@ -12,10 +12,8 @@
     @bufferModified.classList.add('buffer-modified')
     @appendChild(@bufferModified)

-    @absolutePath = @getActiveItem()?.getPath?()
-
     @myTip = atom.tooltips.add(this,
-      title: 'Copied: '+@absolutePath
+      title: 'Copied: '+@getActiveItem()?.getPath?()
       trigger: 'click'
       delay:
         show: 0
@@ -26,16 +24,17 @@
     @subscribeToActiveItem()

     clickHandler = ->
-      atom.clipboard.write(@absolutePath)
+      getPath = @getActiveItem()?.getPath?()
+      atom.clipboard.write(getPath)
       setTimeout =>
         @myTip.dispose()
         @myTip = atom.tooltips.add(this,
-          title: 'Copied: '+@absolutePath
+          title: 'Copied: '+getPath
           trigger: 'click'
           delay:
             show: 0
         )
-      , 1000
+      , 1500

     @addEventListener('click', clickHandler)
     @clickSubscription = new Disposable => @removeEventListener('click', clickHandler)


debugging :zap:

debugging :zap:

--- lib/fold-functions.coffee
+++ lib/fold-functions.coffee
@@ -100,7 +100,7 @@
         autofold = false

     if autofold
-      console.log('fold functions: start autofolding')
+      @debugMessage('fold functions: start autofolding')
       @fold('autofold', editor)

   # Figure out the number of functions in this file.
@@ -138,7 +138,6 @@
         if @indentLevel == null
           @indentLevel = thisIndentLevel
         functionCount++
-        console.log(editor.lineTextForBufferRow(row))
     functionCount

   debugMessage: ->


Identify VS2015 for node-gyp etc.

Identify VS2015 for node-gyp etc.

--- src/apm.coffee
+++ src/apm.coffee
@@ -88,14 +88,13 @@
     # Use the explictly-configured version when set
     return process.env.GYP_MSVS_VERSION if process.env.GYP_MSVS_VERSION

-    vs2013Path = path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio 12.0", "Common7", "IDE")
-    return '2013' if fs.existsSync(vs2013Path)
+    return '2015' if @visualStudioIsInstalled("14.0")
+    return '2013' if @visualStudioIsInstalled("12.0")
+    return '2012' if @visualStudioIsInstalled("11.0")
+    return '2010' if @visualStudioIsInstalled("10.0")

-    vs2012Path = path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio 11.0", "Common7", "IDE")
-    return '2012' if fs.existsSync(vs2012Path)
-
-    vs2010Path = path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio 10.0", "Common7", "IDE")
-    return '2010' if fs.existsSync(vs2010Path)
+  visualStudioIsInstalled: (version) ->
+    fs.existsSync(path.join(@x86ProgramFilesDirectory(), "Microsoft Visual Studio #{version}", "Common7", "IDE"))

   loadNpm: (callback) ->
     npmOptions =


Guard against mdfind errors and empty locations

Fall back to /Application/Atom.app if the mdfind output is empty
rather than just null/undefined.

/cc @thomasjo

--- src/apm.coffee
+++ src/apm.coffee
@@ -31,7 +31,8 @@
     switch process.platform
       when 'darwin'
         child_process.exec 'mdfind "kMDItemCFBundleIdentifier == \'com.github.atom\'"', (error, stdout='', stderr) ->
-          appLocation = stdout.split('\n')[0] ? '/Applications/Atom.app'
+          [appLocation] = stdout.split('\n') unless error
+          appLocation = '/Applications/Atom.app' unless appLocation
           callback("#{appLocation}/Contents/Resources/app.asar")
       when 'linux'
         process.nextTick -> callback('/usr/local/share/atom/resources/app.asar')


removing stray console.log

removing stray console.log

--- src/app.coffee
+++ src/app.coffee
@@ -12,7 +12,6 @@
       for seqIdx in [0...6]
         seq = @_gameOfLife.sequencerAt seqIdx
         do (seqIdx, seq) ->
-          console.log seqIdx
           seq.setTriggerCallback () ->
             console.log "Sequencer #{seqIdx} triggered"
     , 3000


Remove unused buildbot.

Remove unused buildbot.

--- src/app.coffee
+++ src/app.coffee
@@ -1,4 +1,3 @@
-buildbot = require('./buildbot')
 player = require('./player')
 settings = require('./settings')
 unzip = require('unzip')


Add route to show raw stackwalk

Add route to show raw stackwalk

--- src/app.coffee
+++ src/app.coffee
@@ -123,6 +123,16 @@
           fields: fields
         }

+  breakpad.get '/crashreports/:id/stackwalk', (req, res, next) ->
+    # give the raw stackwalk
+    Crashreport.findById(req.params.id).then (report) ->
+      if not report?
+        return res.send 404, 'Crash report not found'
+      Crashreport.getStackTrace report, (err, stackwalk) ->
+        return next err if err?
+        res.set('Content-Type', 'text/plain')
+        res.send(stackwalk.toString('utf8'))
+
   breakpad.get '/crashreports/:id/:filefield', (req, res, next) ->
     # download the file for the given id
     Crashreport.findById(req.params.id).then (crashreport) ->


Update command line parsing.

Update command line parsing.

--- src/cli.coffee
+++ src/cli.coffee
@@ -21,6 +21,10 @@
   """
   process.exit 0

+version = ->
+  console.log (require '../package').version
+  process.exit 0
+
 opts =
   browser:   false
   checkLeaks: false
@@ -35,18 +39,20 @@

 while opt = args.shift()
   switch opt
-    when '--help', '-v'
+    when '-?', '--help'
       usage()
+    when '-v', '--version'
+      version()
     when '--open', '-o'
       opts.open = true
     when '--host', '-h'
       opts.host = args.shift()
     when '--port', '-p'
       opts.port = parseInt args.shift(), 10
-    when '--compilers', '-c'
-      opts.compilers = args.shift()
+    when '--compilers'
+      opts.compilers = args.shift().split ','
     else
-      error 'Unrecognized option' if opt.charAt(0) is '-'
+      opts.files.push opt

 server = (require './server') opts
 server.listen opts.port, ->


return a hardcode new version variable

return a hardcode new version variable

--- src/cli.coffee
+++ src/cli.coffee
@@ -8,7 +8,7 @@
   parseInt(value) || null

 program = require('commander')
-program.version('1.0.0')
+program.version('2.0.0')

 runNpm = (args, env_args = {}) ->
   runCmd('npm', [args..., '--silent'], env_args)


cli: Allow to specify --start-timeout, increase default to 10 sec

--- src/cli.coffee
+++ src/cli.coffee
@@ -15,6 +15,7 @@
     .option('--address <URL>', 'Address of runtime to connect to', String, 'ws://localhost:3569')
     .option('--secret <secret>', 'Runtime secret', String, null)
     .option('--command <command>', 'Command to launch runtime under test', String, null)
+    .option('--start-timeout <seconds>', 'Time to wait for runtime to start', Number, 10)
     .parse(process.argv)

   return program
@@ -25,7 +26,8 @@
   if not options.command # we're not responsible for starting it
     callback null
     return null
-  subprocessOptions = {}
+  subprocessOptions =
+    timeout: options.startTimeout*1000
   return subprocess.start options.command, subprocessOptions, callback

 hasErrors = (suites) ->


event.code is not always defined.

event.code is not always defined.

While looking into #2841, I observed that we are seeing keyboard events
for which neither `event.key` not `event.code` is defined.  They might
be being generated by the page.

--- lib/keyboard_utils.coffee
+++ lib/keyboard_utils.coffee
@@ -18,6 +18,8 @@
   getKeyChar: (event) ->
     unless Settings.get "ignoreKeyboardLayout"
       key = event.key
+    else unless event.code
+      key = ""
     else if event.code[...6] == "Numpad"
       # We cannot correctly emulate the numpad, so fall back to event.key; see #2626.
       key = event.key


engine#add

engine#add

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -39,7 +39,7 @@
       if entityData?
         entityData.class = className
       else
-
+        entityData =
       self.trigger "beforeAdd", entityData
       object = I.currentState.add(entityData)
       self.trigger "afterAdd", object


view: fix - `this` => `@`

view: fix - `this` => `@`

--- coffee/src/views/view.coffee
+++ coffee/src/views/view.coffee
@@ -19,7 +19,7 @@
     _.defaults @options, @defaults

     # If no eventhub is provided, this object is used as the eventhub.
-    this.eventhub = @options.eventhub || @
+    @eventhub = @options.eventhub || @

     # Bind all functions within this object (including functions defined in
     # derived classes) to `this`. This exempts you from having to bind


Support some mathematical functions

Support some mathematical functions

--- src/formula_evaluator.coffee
+++ src/formula_evaluator.coffee
@@ -299,6 +299,24 @@
     @checkArgumentSize(args, 1)
     Math.log10 @expectNumber(args[0])

+  MOD: (args)->
+    @checkArgumentSize(args, 2)
+    n = @expectNumber(args[1])
+    if n == 0
+      @error(ERROR_DIV0)
+    Math.sign(n) * Math.abs(@expectNumber(args[0]) % Math.abs(n))
+
+  MROUND: (args)->
+    @checkArgumentSize(args, 2)
+    m = @expectNumber(args[0])
+    n = @expectNumber(args[1])
+    if m == 0 || n == 0
+      return 0
+    if Math.sign(m) != Math.sign(n)
+      @error(ERROR_NUM)
+    Math.round(m/n) * n
+
+
   ODD: (args)->
     @checkArgumentSize(args, 1)
     num = @expectNumber(args[0])
@@ -444,6 +462,10 @@
         return
     throw new Error("Sizes of arguments do not match.")

+  __fact__: (n, r)->
+    return x if y == 0
+    @__fact__(y, x % y)
+
   __gcd__: (x, y)->
     return x if y == 0
     @__gcd__(y, x % y)


Fix DSV parsing, particularly SSV

Fix DSV parsing, particularly SSV

--- svgtiler.coffee
+++ svgtiler.coffee
@@ -1,7 +1,7 @@
 path = require 'path'
 fs = require 'fs'
 CoffeeScript = require 'coffee-script'
-csvParse = require 'csv-parse'
+csvParse = require 'csv-parse/lib/sync'
 xmldom = require 'xmldom'
 DOMParser = xmldom.DOMParser
 domImplementation = new xmldom.DOMImplementation()
@@ -294,20 +294,30 @@

 class DSVDrawing extends Drawing
   @parse: (data) ->
+    ## Remove trailing newline / final blank line.
+    if data[-2..] == '\r\n'
+      data = data[...-2]
+    else if data[-1..] in ['\r', '\n']
+      data = data[...-1]
+    ## CSV parser.
     new @ csvParse data,
-      delimeter: @delimeter
+      delimiter: @delimiter
+      relax_column_count: true

 class SSVDrawing extends DSVDrawing
-  @title: "Space-delimeter drawing (one word per symbol)"
-  @delimeter: ' '
+  @title: "Space-delimiter drawing (one word per symbol)"
+  @delimiter: ' '
+  @parse: (data) ->
+    ## Coallesce non-newline whitespace into single space
+    super data.replace /[ \t\f\v]+/g, ' '

 class CSVDrawing extends DSVDrawing
   @title: "Comma-separated drawing (spreadsheet export)"
-  @delimeter: ','
+  @delimiter: ','

 class TSVDrawing extends DSVDrawing
   @title: "Tab-separated drawing (spreadsheet export)"
-  @delimeter: '\t'
+  @delimiter: '\t'

 class Context
   constructor: (@symbols, @i, @j) ->


[neo4j] removed enabled parameter

[neo4j] removed enabled parameter

--- config/main.newkoding.coffee
+++ config/main.newkoding.coffee
@@ -42,7 +42,6 @@
     enabled     : yes
     port        : sourceServerPort
   neo4j         :
-    enabled     : no
     host        : "http://kgraphdb1.in.koding.com"
     port        : 7474
   mongo         : mongo


Add mark commands to README, more simple refactoring.

--- content_scripts/marks.coffee
+++ content_scripts/marks.coffee
@@ -54,16 +54,15 @@
       indicator: "Go to mark..."
       suppressAllKeyboardEvents: true
       keypress: (event) =>
-        keyChar = String.fromCharCode event.charCode
-        if event.shiftKey
-          @exit ->
+        @exit =>
+          keyChar = String.fromCharCode event.charCode
+          if event.shiftKey
             chrome.runtime.sendMessage
               handler: 'gotoMark'
               markName: keyChar
-        else
-          markString =
-            if keyChar == previousPositionKey then @previousPosition else localStorage[@getLocationKey keyChar]
-          @exit =>
+          else
+            markString =
+              if keyChar == previousPositionKey then @previousPosition else localStorage[@getLocationKey keyChar]
             if markString?
               @setPreviousPosition()
               position = JSON.parse markString


observer, needs fixing :(

observer, needs fixing :(

--- core/Collection.class.coffee
+++ core/Collection.class.coffee
@@ -1,3 +1,67 @@
+class Observe
+  constructor: (root, callback, curr=null, path = null)->
+    curr = curr || root
+
+    type_of_curr = curr.constructor.name
+    base = path
+    if type_of_curr is "Array"
+      for item, key in curr
+        if typeof item is "object"
+          path = "#{base}[#{key}]" if base
+          path = "#{key}" unless base
+          new Observe root, callback, item, path
+          path = ""
+
+    if type_of_curr is "Object"
+      for key, item of curr
+        # if item.constructor.name is "Object"
+        if typeof item is "object"
+          path = "#{base}.#{key}" if base
+          path = "#{key}" unless base
+          new Observe root, callback, item, path
+          path = ""
+
+
+    if curr.constructor.name is "Array"
+      curr_path = path
+      Array.observe curr, (changes) ->
+        result = {}
+        original = {}
+        base = path
+        changes.forEach (change,i) ->
+          path = "#{base}[#{change.index}]"
+
+          # console.log change
+          part =
+            path: curr_path
+            value : change.object
+            # json : JSON.stringify(change.object)
+          # if change.type is "add" and typeof part.value is "object"
+          if change.addedCount > 0 and typeof part.value is "object"
+            new Observe root, callback, part.value, part.path
+          result[i] = part
+          original[i] = change
+        callback result, original
+    if curr.constructor.name is "Object"
+      base = "#{path}"
+      Object.observe curr, (changes)->
+        result = {}
+        original = {}
+
+        changes.forEach (change,i) ->
+          curr_path = path
+          path = "#{base}.#{change.name}" if base
+          path = "#{change.name}" unless base
+
+          part =
+            path: path
+            value : change.object[change.name]
+
+          if change.type is "add" and typeof part.value is "object"
+            new Observe root, callback, part.value, part.path
+          result[i] = part
+          original[i] = change
+        callback result, original
 class Collection
   get: (name)->
     JOM.Collection[name]
@@ -29,7 +93,8 @@
     $.getJSON src
     .done (response)->
       JOM.Collection[name] = response
-      Observe response, (changes) ->
-        console.log changes
       $ "body"
       .trigger "collection:#{name}.ready", -> response
+
+Observe JOM.Collection, (changes) ->
+  console.log changes


Fix hubot-auth logic

Fix hubot-auth logic

--- src/hubot-slack-relay.coffee
+++ src/hubot-slack-relay.coffee
@@ -60,11 +60,16 @@
     #re-assign relays for deep copy after save
     relays = temp

+  isAuthorized = (res) =>
+    if robot.auth and not robot.auth.isAdmin(res.message.user) and robot.auth.hasRole(res.message.user, "relay")
+      res.send "Sorry, #{res.message.user.name} you don't have permission to perform this relay action."
+      return false
+    return true
+
+
   robot.respond /relay add\s+(\S+)\s+(\S+)\s+(\S+)/i, (res) =>
     robot.logger.info 'relay add ' + res.match[1] + ' ' + res.match[2] + ' ' + res.match[3]
-    if robot.auth and (robot.auth.isAdmin(res.message.user) or robot.auth.hasRole(res.message.user, "relay"))
-      res.send "Sorry, #{res.message.user.name} you don't have permission to perform this relay action."
-      return
+    if not isAuthorized res then return

     localRoom = S(res.match[1]).chompLeft('#').s
     remoteRoom = S(res.match[2]).chompLeft('#').s
@@ -124,9 +129,7 @@

   robot.respond /relay addignore\s+(\S+)/i, (res) =>
     robot.logger.info 'relay addignore ' + res.match[1]
-    if robot.auth and (robot.auth.isAdmin(res.message.user) or robot.auth.hasRole(res.message.user, "relay"))
-      res.send "Sorry, #{res.message.user.name} you don't have permission to perform this relay action."
-      return
+    if not isAuthorized res then return

     ignoreUsers.push S(res.match[1]).chompLeft('@').s
     saveBrain()
@@ -134,9 +137,7 @@

   robot.respond /relay remove\s+(\S+)\s*(\S*)\s*(\S*)/i, (res) =>
     robot.logger.info 'relay remove ' + res.match[1] + ' ' + res.match[2] + ' ' + res.match[3]
-    if robot.auth and (robot.auth.isAdmin(res.message.user) or robot.auth.hasRole(res.message.user, "relay"))
-      res.send "Sorry, #{res.message.user.name} you don't have permission to perform this relay action."
-      return
+    if not isAuthorized res then return

     localRoom = S(res.match[1]).chompLeft('#').s
     remoteRoom = if res.match[2] then S(res.match[2]).chompLeft('#').s
@@ -191,9 +192,7 @@

   robot.respond /relay removeignore\s+(\S+)/i, (res) =>
     robot.logger.info 'relay removeignore ' + res.match[1]
-    if robot.auth and (robot.auth.isAdmin(res.message.user) or robot.auth.hasRole(res.message.user, "relay"))
-      res.send "Sorry, #{res.message.user.name} you don't have permission to perform this relay action."
-      return
+    if not isAuthorized res then return

     removeUser = S(res.match[1]).chompLeft('@').s

@@ -203,17 +202,13 @@

   robot.respond /relay list$/i, (res) =>
     robot.logger.info 'relay list'
-    if robot.auth and (robot.auth.isAdmin(res.message.user) or robot.auth.hasRole(res.message.user, "relay"))
-      res.send "Sorry, #{res.message.user.name} you don't have permission to perform this relay action."
-      return
+    if not isAuthorized res then return

     res.send blockList(relays)

   robot.respond /relay (listignore|ignorelist)$/i, (res) =>
     robot.logger.info 'relay listignore'
-    if robot.auth and (robot.auth.isAdmin(res.message.user) or robot.auth.hasRole(res.message.user, "relay"))
-      res.send "Sorry, #{res.message.user.name} you don't have permission to perform this relay action."
-      return
+    if not isAuthorized res then return

     res.send blockList(ignoreUsers)


Fix initialization of relays

Fix initialization of relays

Without this change, newly set up instances fail when adding the first relay with the error: Note, relay list may need a change to show the right thing when the list is empty.

ERROR TypeError: undefined is not a function
  at RemoteSlack.newRemoteConnected (/home/hubot/node_modules/hubot-slack-relay/src/hubot-slack-relay.coffee:95:7, <js>:80:16)

--- src/hubot-slack-relay.coffee
+++ src/hubot-slack-relay.coffee
@@ -19,7 +19,7 @@
 module.exports = (robot) ->

   BRAIN_KEY = 'hubot-slack-relay.storage'
-  relays = {}
+  relays = []

   brainLoaded = () =>
     #load brain data


added ! to work with jspm

added ! to work with jspm

--- src/index.cjsx
+++ src/index.cjsx
@@ -27,12 +27,12 @@

     # Require the fade-in css.
     unless @props.noFadeIn
-      require '../css/fade-in.css'
+      require '../css/fade-in.css!'

     switch @props.spinnerName

       when "three-bounce"
-        require '../css/three-bounce.css'
+        require '../css/three-bounce.css!'
         return (
           (
             <div {... @props} className={"three-bounce " + classes}>
@@ -44,7 +44,7 @@
         )

       when "double-bounce"
-        require '../css/double-bounce.css'
+        require '../css/double-bounce.css!'
         return (
           (
             <div {... @props} className={"double-bounce " + classes}>
@@ -55,7 +55,7 @@
         )

       when "rotating-plane"
-        require '../css/rotating-plane.css'
+        require '../css/rotating-plane.css!'
         return (
           (
             <div {... @props} className={classes}>
@@ -65,7 +65,7 @@
         )

       when "wave"
-        require '../css/wave.css'
+        require '../css/wave.css!'
         return (
           (
             <div {... @props} className={"wave " + classes}>
@@ -79,7 +79,7 @@
         )

       when "wandering-cubes"
-        require '../css/wandering-cubes.css'
+        require '../css/wandering-cubes.css!'
         return (
           (
             <div {... @props} className={"wandering-cubes " + classes}>
@@ -90,7 +90,7 @@
         )

       when "pulse"
-        require '../css/pulse.css'
+        require '../css/pulse.css!'
         return (
           (
             <div {... @props} className={classes}>
@@ -100,7 +100,7 @@
         )

       when "chasing-dots"
-        require '../css/chasing-dots.css'
+        require '../css/chasing-dots.css!'
         return (
           (
             <div {... @props} className={classes}>
@@ -113,7 +113,7 @@
         )

       when "circle"
-        require '../css/circle.css'
+        require '../css/circle.css!'
         return (
           (
             <div {... @props} className={"circle-wrapper " + classes}>
@@ -134,7 +134,7 @@
         )

       when "cube-grid"
-        require '../css/cube-grid.css'
+        require '../css/cube-grid.css!'
         return (
           (
             <div {... @props} className={"cube-grid " + classes}>
@@ -152,7 +152,7 @@
         )

       when "wordpress"
-        require '../css/wordpress.css'
+        require '../css/wordpress.css!'
         return (
           (
             <div {... @props} className={classes}>


bump version 1.0.3

bump version 1.0.3

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

 window.SC ||=
-  _version: "1.0.2"
+  _version: "1.0.3"
   options:
     site: "soundcloud.dev",
   connectCallbacks: {}


prevent up from returning undefined

prevent up from returning undefined

--- src/xml.coffee
+++ src/xml.coffee
@@ -188,8 +188,9 @@

     up: (opts = {}) =>
         opts.end ?= true
+        parent = @parent # might get removed
         @end arguments... if opts.end
-        @parent
+        parent

     show: () =>
         @hidden = no


Hide rnd.char()

Hide rnd.char()

--- src/tools/rnd.coffee
+++ src/tools/rnd.coffee
@@ -4,10 +4,9 @@
 module.exports =
 rnd = (N = 12)->
   s = ''
-  s += char() while s.length < N
+  s += chr() while s.length < N
   s

-rnd.char =
-char = ->
+chr = ->
   n = Math.floor 62 * Math.random()
   String.fromCharCode n % 26 + 'Aa0'.charCodeAt n / 26


Added check to see if keystore exists

Added check to see if keystore exists

--- src/upgradeDB.coffee
+++ src/upgradeDB.coffee
@@ -15,6 +15,8 @@
     defer = Q.defer()

     Keystore.findOne (err, keystore) ->
+      return defer.resolve() if not keystore
+
       # convert server cert
       pem.getFingerprint keystore.cert.data, (err, obj) ->
         keystore.cert.fingerprint = obj.fingerprint


Check for the file descriptor

Check for the file descriptor

--- src/chunked-line-reader.coffee
+++ src/chunked-line-reader.coffee
@@ -78,5 +78,5 @@
       @push(remainder) if remainder

     finally
-      fs.closeSync(fd)
+      fs.closeSync(fd) if fd?
       @push(null)


converted event tile options to cjsx

converted event tile options to cjsx

--- src/components/life_app.coffee
+++ src/components/life_app.coffee
@@ -722,59 +722,37 @@
       return "navigation-expand-more"

   renderCollapsed: () ->
-    return React.createElement("div", className: "event-header",
-      React.createElement(Icon, {
-        icon: "navigation-more-horiz", onClick: @handleExpand
-      })
-    )
+

+      <Icon icon="navigation-more-horiz" onClick={@handleExpand} />
+

   renderExpanded: (type) ->
     eventExpandIcon = @getEventExpandIcon()
     if type == 'active'
       buttons = [
-        React.createElement(Icon, {
-          key: "archive"
-          icon: "content-archive", 'data-event_id': @props.eventId,
-          onClick: @handleArchive
-        })
-        React.createElement(Icon, {
-          key: "edit"
-          icon: "content-create", 'data-event_id': @props.eventId,
-          onClick: @handleBeginEdit
-        })
+        <Icon key="archive" icon="content-archive" data-event_id={@props.eventId} onClick={@handleArchive}/>
+        <Icon key="edit" icon="content-create" data-event_id={@props.eventId} onClick={@handleBeginEdit}/>
       ]
     else if type == 'archived'
       buttons = [
-        React.createElement(Icon, {
-          key: "restore"
-          icon: "content-reply", 'data-event_id': @props.eventId,
-          onClick: @handleRestore
-        })
-        React.createElement(Icon, {
-          key: "delete"
-          icon: "content-clear", 'data-event_id': @props.eventId,
-          onClick: @handleDelete
-        })
+        <Icon key="restore" icon="content-reply" data-event_id={@props.eventId} onClick={@handleRestore}/>
+        <Icon key="delete" icon="content-clear" data-event_id={@props.eventId} onClick={@handleDelete}/>
       ]
     else
       throw Error "Unknown event type"

     buttons = buttons.concat [
-      React.createElement(Icon,
-        {key: "ee", icon: eventExpandIcon, onClick: @handleEventExpand})
-      React.createElement(Icon, {key: "oe", icon: "navigation-more-horiz", onClick: @handleExpand})
+      <Icon key="ee" icon={eventExpandIcon} onClick={@handleEventExpand}/>
+      <Icon key="oe" icon="navigation-more-horiz" onClick={@handleExpand}/>
     ]

-    return React.createElement("div", {key: "buttons", className: "event-header"}, buttons)
+

+      {buttons}
+

   render: () ->
     if not @state.optionsExpanded
       return @renderCollapsed()
     return @renderExpanded @props.type

-TimelineBar = React.createClass
-  displayName: 'TimelineBar'
-  render: () ->
-    return React.createElement("div", {id: "timeline-bar"})
-
 module.exports = {LifeApp}


Fix selector in tabs spec

Fix selector in tabs spec

--- spec/javascripts/tabs_spec.js.coffee
+++ spec/javascripts/tabs_spec.js.coffee
@@ -15,9 +15,9 @@
     describe "tabNavigation", ->

       it "It sets the first tab to active", ->
-        expect($('#homeTabs li').attr('class')).toBeUndefined()
+        expect($('#homeTabs a:first').attr('class')).toBeUndefined()
         tabNavigation();
-        expect($('#homeTabs li').attr('class')).toBe('active')
+        expect($('#homeTabs a:first').attr('class')).toBe('active')

   describe "dashboard tabs", ->
     beforeEach ->


CSRF shouldn't break all the tests.

CSRF shouldn't break all the tests.

--- src/extras/batman.rails.coffee
+++ src/extras/batman.rails.coffee
@@ -70,9 +70,11 @@
   @::before 'all', (env, next) ->
     return next() if not Batman.config.protectFromCSRF

-    if not @constructor.CSRF_TOKEN
-      tag = Batman.DOM.querySelector(null, "meta[name=\"#{Batman.config.metaNameForCSRFToken}\"]")
-      @constructor.CSRF_TOKEN = tag.getAttribute('content')
+    if not @constructor.CSRF_TOKEN?
+      if tag = Batman.DOM.querySelector(document.head, "meta[name=\"#{Batman.config.metaNameForCSRFToken}\"]")
+        @constructor.CSRF_TOKEN = tag.getAttribute('content')
+      else
+        @constructor.CSRF_TOKEN = null

     if token = @constructor.CSRF_TOKEN
       headers = env.options.headers ||= {}


bind functions correctly

bind functions correctly

--- src/frontend/js/message.coffee
+++ src/frontend/js/message.coffee
@@ -1,16 +1,16 @@
-getID = ->
+@getID = ->
   $(".active .id").val()

-getMessage = ->
+@getMessage = ->
   $(".active .message").val()

-setID = (value) ->
+@setID = (value) ->
   $(".active .id").val value

-setMessage = (value) ->
+@setMessage = (value) ->
   $(".active .message").val value

-select = (tab) ->
+@select = (tab) ->
   # update the nav bar and the displayed tab
   $(".tab-pane").removeClass "active"
   $("##{tab}-pane").addClass "active"


How to draw a line (see src/glyphs/lowercases/a.coffee)

--- src/glyphs/lowercases/a.coffee
+++ src/glyphs/lowercases/a.coffee
@@ -1,7 +1,10 @@
 exports.glyphs['a'] =
 	unicode: 'a'
 	ot:
-		advanceWidth: 0
+		advanceWidth: contours[0].nodes[0].x + spacingRight
+	parameters:
+		spacingLeft: 10 * spacing + (20)
+		spacingRight: 10 * spacing
 	tags: [
 		'all',
 		'latin',
@@ -13,5 +16,22 @@
 			closed: false
 			nodes:
 				0:
-					x: 0
+					x: spacingLeft
 					y: 0
+					dirOut: 0 + 'deg'
+					typeOut: 'line'
+					expand: Object({
+						width: ( 80 / 100 ) * thickness
+						angle: 0 + 'deg'
+						distr: 0.25
+					})
+				1:
+					x: contours[0].nodes[0].x
+					y: 0
+					dirOut: 0 + 'deg'
+					typeOut: 'line'
+					expand: Object({
+						width: ( 20 / 100 ) * thickness
+						angle: 0 + 'deg'
+						distr: 0.25
+					})


Fixed bug with link containing params (issue #1)

--- src/hubot-archive-today.coffee
+++ src/hubot-archive-today.coffee
@@ -22,7 +22,7 @@
         NOT_FOUND: /Invalid URL:/i

     robot.respond /archive (.+)/i, (msg) ->
-        data = "url=#{msg.match[1]}"
+        data = "url=#{encodeURIComponent(msg.match[1])}"
         msg.http(ARCHIVE_TODAY_SUBMIT_URL)
             .header("content-type", "application/x-www-form-urlencoded")
             .post(data) (err, res, body) ->


Spawn monsters with full HP.

Spawn monsters with full HP.

--- init.coffee
+++ init.coffee
@@ -266,13 +266,14 @@

 	dbConnection.query.sync(dbConnection, "TRUNCATE monsters", [])
 	for i in [0...50]
+		soul = prototypes.pickRandom()
 		dbConnection.query.sync(
 			dbConnection
 			"INSERT INTO monsters "+
 				"(id, prototype, location, health, mana, effects, attack_chance) "+
 				"VALUES "+
 				"($1, $2, $3, $4, $5, $6, $7)"
-			[i, prototypes.pickRandom()[0], locs.pickRandom().id, 1, 1, null, Math.random()*25|0]
+			[i, soul[0], locs.pickRandom().id, soul[8], soul[9], null, Math.random()*25|0]
 		)

 	console.log('Done.')


Basic, non-invalidating response cache

Basic, non-invalidating response cache

--- lib/response-cache.coffee
+++ lib/response-cache.coffee
@@ -1,18 +1,56 @@
+cache = {}
+
+NOT_MODIFIED = 304
+
 exports.create = ->
   (req, res, next) ->
+    head = undefined
+    headerInfo = undefined
+    data = []
+
+    if cacheEntry = cache[req.url]
+      res.writeHead cacheEntry.head.statusCode, cacheEntry.head.headers
+      for value in cacheEntry.data
+        res.write value
+      res.end()
+      return
+
     writeHead = res.writeHead
     res.writeHead = (statusCode, reasonPhrase, headers) ->
+      # If the cache response is not modified, ignore this whole mess
+      if statusCode != NOT_MODIFIED
+        headers = headers ? reasonPhrase ? @_headers ? {}
+        headerInfo = exports.getResponseCacheInfo headers
+
+        head = {statusCode, headers}
+
       writeHead.apply @, arguments

     write = res.write
     res.write = (chunk, encoding) ->
       write.apply @, arguments

+      # Tail call, write header should have been called by now
+      if headerInfo
+        data.push if encoding then new Buffer chunk, encoding else chunk
+
     end = res.end
     res.end = (chunk, encoding) ->
       end.apply @, arguments

+      # Tail call, write header should have been called by now
+      if headerInfo
+        if chunk
+          data.push if encoding then new Buffer chunk, encoding else chunk
+        cache[req.url] = {
+          url: req.url
+          head
+          headerInfo
+          data
+        }
+
     next()
+
 # getRequestCacheInfo : Determines the cache parameters for the given request or header set
 #
 # @param req HTTP request object or header object


another bugfix

another bugfix

--- src/cli.coffee
+++ src/cli.coffee
@@ -61,7 +61,7 @@
       when '--help', '-h' then help = yes
       when '--only', '-o' then only = (i for i in raw.shift().split(',') when i isnt '' )
       else command.push arg
-  command.join ' '
+  command = command.join ' '
   {help,command,only}

 logInfo = (msg) ->


Support multiple files/directories (closes #70)

Accept multiple files or directories through the CLI, aggregating
results in the same way as a directory had been aggregated
previously.

--- src/cli.coffee
+++ src/cli.coffee
@@ -39,18 +39,13 @@
     res.stats = sloc code, options.alias[ext] or ext
     cb null, res

-print = (err, result, opts, fmtOpts) ->
-  return console.error "Error: #{err}" if err
+print = (result, opts, fmtOpts) ->
   f = programm.format or 'simple'
   unless (fmt = fmts[f])?
     return console.error "Error: format #{f} is not supported"
   out = fmt result, opts, fmtOpts
   out = out.replace colorRegex, '' if programm.stripColors
   console.log out if typeof out is "string"
-
-addResult = (res, all) ->
-  if res.badFile then all.brokenFiles++
-  all.files.push res

 filterFiles = (files) ->
   res =
@@ -60,7 +55,7 @@
     else
       files

-  (path.join p, r.path for r in res)
+  (r.path for r in res)

 options = {}
 fmtOpts = []
@@ -101,8 +96,6 @@

 return programm.help() if programm.args.length < 1

-result = files: []
-
 groupByExt = (data) ->
   map = {}
   for f in data.files
@@ -114,28 +107,34 @@
     d.summary = helpers.summarize d.files.map (x) -> x.stats
   map

-readSingleFile = (f) -> parseFile p, (err, res) ->
-  addResult res, result
-  result.summary = res.stats
-  print err, result, options, fmtOpts
+readSingleFile = (f, done) -> parseFile f, (err, res) ->
+  done err, [res]

-readDir = (dir) ->
-
-  finish = (err, x) ->
-    result.summary = helpers.summarize result.files.map (x) -> x.stats
-    result.byExt = groupByExt result
-    print err, result, options, fmtOpts
-
-  processFile = (f, next) -> parseFile f, (err, r) ->
-    addResult r, result
-    next()
+readDir = (dir, done) ->
+  processFile = (f, next) -> parseFile (path.join dir, f), next

   readdirp { root: dir, fileFilter: exts }, (err, res) ->
-    async.forEach (filterFiles res.files), processFile, finish
+    return done(err) if err
+    async.map (filterFiles res.files), processFile, done

-p = programm.args[0]
+readSource = (p, done) ->
+  fs.lstat p, (err, stats) ->
+    if err
+      console.error "Error: invalid path argument #{p}"
+      return done(err)
+    if stats.isDirectory() then return readDir p, done
+    else if stats.isFile() then return readSingleFile p, done

-fs.lstat p, (err, stats) ->
-  return console.error "Error: invalid path argument" if err
-  if stats.isDirectory() then readDir p
-  else if stats.isFile() then readSingleFile p
+async.map programm.args, readSource, (err, parsed) ->
+  return console.error "Error: #{err}" if err
+
+  result = files: []
+
+  parsed.forEach (files) ->
+    files.forEach (f) ->
+      if f.badFile then result.brokenFiles++
+      result.files.push f
+
+  result.summary = helpers.summarize result.files.map (x) -> x.stats
+  result.byExt = groupByExt result
+  print result, options, fmtOpts


fixed a routing problem #1250

fixed a routing problem #1250

--- app/assets/javascripts/admin/views/pagination_view.coffee
+++ app/assets/javascripts/admin/views/pagination_view.coffee
@@ -37,7 +37,7 @@

         <% if (addButtonText) { %>
-
+
             <%- addButtonText %>

         <% } %>


Make a faster update of server side changes.

--- app/assets/javascripts/app/controllers/ticket_zoom.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom.coffee
@@ -50,7 +50,7 @@
       update = =>
         @fetch(@ticket_id, false)
       if !@ticketUpdatedAtLastCall || ( new Date(data.updated_at).toString() isnt new Date(@ticketUpdatedAtLastCall).toString() )
-        @delay(update, 1100, "ticket-zoom-#{@ticket_id}")
+        @delay(update, 500, "ticket-zoom-#{@ticket_id}")
     )

     # rerender view, e. g. on langauge change


#1436 Don't use a real address for null bucket placeholder

--- app/assets/javascripts/oxalis/model/binary/plane2d.coffee
+++ app/assets/javascripts/oxalis/model/binary/plane2d.coffee
@@ -33,7 +33,7 @@
   W : 0

   NOT_LOADED_BUCKET_INTENSITY : 100
-  NOT_LOADED_BUCKET_PLACEHOLDER : [-1, -1, -1, 0]
+  NOT_LOADED_BUCKET_PLACEHOLDER : {}

   cube : null
   queue : null


Prep NodeJs for decaffeination

Prep NodeJs for decaffeination

--- src/lib/loader/NodeJs.coffee
+++ src/lib/loader/NodeJs.coffee
@@ -10,11 +10,17 @@
   CoffeeScript.register()

 registerCustomLoaders = (loader, componentLoaders, callback) ->
-  return callback null unless componentLoaders.length
+  unless componentLoaders.length
+    callback null
+    return
   customLoader = require componentLoaders.shift()
   loader.registerLoader customLoader, (err) ->
-    return callback err if err
+    if err
+      callback err
+      return
     registerCustomLoaders loader, componentLoaders, callback
+    return
+  return

 registerModules = (loader, modules, callback) ->
   compatible = modules.filter (m) -> m.runtime in ['noflo', 'noflo-nodejs']
@@ -30,6 +36,7 @@
       loader.registerComponent m.name, c.name, path.resolve loader.baseDir, c.path

   registerCustomLoaders loader, componentLoaders, callback
+  return

 manifestLoader =
   writeCache: (loader, options, manifest, callback) ->
@@ -37,10 +44,12 @@
     fs.writeFile filePath, JSON.stringify(manifest, null, 2),
       encoding: 'utf-8'
     , callback
+    return

   readCache: (loader, options, callback) ->
     options.discover = false
     manifest.load.load loader.baseDir, options, callback
+    return

   prepareManifestOptions: (loader) ->
     loader.options = {} unless loader.options
@@ -49,33 +58,52 @@
     options.runtimes.push 'noflo' if options.runtimes.indexOf('noflo') is -1
     options.recursive = if typeof loader.options.recursive is 'undefined' then true else loader.options.recursive
     options.manifest = loader.options.manifest or 'fbp.json'
-    options
+    return options

   listComponents: (loader, manifestOptions, callback) ->
     @readCache loader, manifestOptions, (err, manifest) =>
       if err
-        return callback err unless loader.options.discover
+        unless loader.options.discover
+          callback err
+          return
         dynamicLoader.listComponents loader, manifestOptions, (err, modules) =>
-          return callback err if err
+          if err
+            callback err
+            return
           @writeCache loader, manifestOptions,
             version: 1
             modules: modules
           , (err) ->
-            return callback err if err
+            if err
+              callback err
+              return
             callback null, modules
+            return
         return
       registerModules loader, manifest.modules, (err) ->
-        return callback err if err
+        if err
+          callback err
+          return
         callback null, manifest.modules
+        return
+      return
+    return

 dynamicLoader =
   listComponents: (loader, manifestOptions, callback) ->
     manifestOptions.discover = true
     manifest.list.list loader.baseDir, manifestOptions, (err, modules) =>
-      return callback err if err
+      if err
+        callback err
+        return
       registerModules loader, modules, (err) ->
-        return callback err if err
+        if err
+          callback err
+          return
         callback null, modules
+        return
+      return
+    return

 registerSubgraph = (loader) ->
   # Inject subgraph component
@@ -84,21 +112,29 @@
   else
     graphPath = path.resolve __dirname, '../../components/Graph.coffee'
   loader.registerComponent null, 'Graph', graphPath
+  return

 exports.register = (loader, callback) ->
   manifestOptions = manifestLoader.prepareManifestOptions loader

   if loader.options?.cache
     manifestLoader.listComponents loader, manifestOptions, (err, modules) ->
-      return callback err if err
+      if err
+        callback err
+        return
       registerSubgraph loader
       callback null, modules
+      return
     return

   dynamicLoader.listComponents loader, manifestOptions, (err, modules) ->
-    return callback err if err
+    if err
+      callback err
+      return
     registerSubgraph loader
     callback null, modules
+    return
+  return

 exports.dynamicLoad = (name, cPath, metadata, callback) ->
   try
@@ -124,6 +160,7 @@
     return
   instance.componentName = name if typeof name is 'string'
   callback null, instance
+  return

 exports.setSource = (loader, packageId, name, source, language, callback) ->
   Module = require 'module'
@@ -132,7 +169,8 @@
       source = CoffeeScript.compile source,
         bare: true
     catch e
-      return callback e
+      callback e
+      return
   try
     # Use the Node.js module API to evaluate in the correct directory context
     modulePath = path.resolve loader.baseDir, "./components/#{name}.js"
@@ -142,11 +180,14 @@
     moduleImpl._compile source, modulePath
     implementation = moduleImpl.exports
   catch e
-    return callback e
+    callback e
+    return
   unless typeof implementation is 'function' or typeof implementation.getComponent is 'function'
-    return callback new Error 'Provided source failed to create a runnable component'
+    callback new Error 'Provided source failed to create a runnable component'
+    return

   loader.registerComponent packageId, name, implementation, callback
+  return

 exports.getSource = (loader, name, callback) ->
   component = loader.components[name]
@@ -158,7 +199,8 @@
         name = componentName
         break
     unless component
-      return callback new Error "Component #{name} not installed"
+      callback new Error "Component #{name} not installed"
+      return

   nameParts = name.split '/'
   if nameParts.length is 1
@@ -174,9 +216,12 @@
           code: JSON.stringify component.toJSON()
           language: 'json'
         return
-      return callback new Error "Can't provide source for #{name}. Not a file"
+      callback new Error "Can't provide source for #{name}. Not a file"
+      return
     fbpGraph.graph.loadFile component, (err, graph) ->
-      return callback err if err
+      if err
+        callback err
+        return
       return callback new Error 'Unable to load graph' unless graph
       callback null,
         name: nameParts[1]
@@ -186,12 +231,17 @@
     return

   if typeof component isnt 'string'
-    return callback new Error "Can't provide source for #{name}. Not a file"
+    callback new Error "Can't provide source for #{name}. Not a file"
+    return

   fs.readFile component, 'utf-8', (err, code) ->
-    return callback err if err
+    if err
+      callback err
+      return
     callback null,
       name: nameParts[1]
       library: nameParts[0]
       language: utils.guessLanguageFromFilename component
       code: code
+    return
+  return


Connect signals

Connect signals

--- bokehjs/src/coffee/models/renderers/graph_renderer.coffee
+++ bokehjs/src/coffee/models/renderers/graph_renderer.coffee
@@ -17,11 +17,15 @@
   connect_signals: () ->
     super()
     @connect(@model.change, () -> @request_render())
+    @connect(@model.edges.change, () -> @set_data())
+    @connect(@model.nodes.change, () -> @set_data())
+    @connect(@model.graph_source.change, () -> @set_data())
+    @connect(@model.graph_source.layout_provider.change, () -> @set_data())

   build_glyph_view: (model) ->
     return new model.default_view({model: model, renderer: @, plot_view: @plot_view, parent: @})

-  set_data: () ->
+  set_data: (request_render=true) ->
     # TODO (bev) this is a bit clunky, need to make sure glyphs use the correct ranges when they call
     # mapping functions on the base Renderer class
     @edges.model.setv({x_range_name: @model.x_range_name, y_range_name: @model.y_range_name}, {silent: true})
@@ -35,6 +39,9 @@
     [@edges._xs, @edges._ys] = @model.graph_source.layout_provider.get_edge_coordinates(@model.graph_source)
     [@nodes._x, @nodes._y] = @model.graph_source.layout_provider.get_node_coordinates(@model.graph_source)

+    if request_render
+      @request_render()
+
   render: () ->
     @edges.map_data()
     @nodes.map_data()
@@ -45,6 +52,7 @@

     @edges.render(ctx, edge_indices, @edges)
     @nodes.render(ctx, node_indices, @nodes)
+

 export class GraphRenderer extends Renderer
   default_view: GraphRendererView


Adjust option name to work with ruby-beautify

--- src/beautifiers/ruby-beautify.coffee
+++ src/beautifiers/ruby-beautify.coffee
@@ -17,7 +17,7 @@
   beautify: (text, language, options) ->
     @run("rbeautify", [
       if options.indent_char is '\t' then "--tabs" else "--spaces"
-      "--indent_count", options.indent_count
+      "--indent_count", options.indent_size
       @tempFile("input", text)
       ], help: {
       link: "https://github.com/erniebrodeur/ruby-beautify"


Send node names when we open graphs

Send node names when we open graphs

When we open a CODAP graph using the node's graph button, we call
`createCollection` with the current node names and the time unit,
so that we can open up a useful graph even without data.

[#113104171]

--- src/code/models/codap-connect.coffee
+++ src/code/models/codap-connect.coffee
@@ -68,30 +68,8 @@

   _openNewCase: (nodeNames) ->
     @currentCaseID = null
-    nodes = @graphStore.getNodes()
-
-    # First column definition is the time index
-    timeUnit = TimeUnits.toString SimulationStore.store.settings.stepUnits, true
-    sampleDataAttrs = [
-      {
-        name: timeUnit
-        type: "numeric"
-      }
-    ]
-
-    # Append node names to column descriptions.
-    _.each nodeNames, (name) ->
-      node = (nodes.filter (n) -> n.title is name)[0]
-      type = if node.valueDefinedSemiQuantitatively then 'qualitative' else 'numeric'
-      sampleDataAttrs.push
-        name: name
-        type: type
-
-    @codapPhone.call
-      action: 'createCollection'
-      args:
-        name: 'Samples'
-        attrs: sampleDataAttrs
+
+    @_createCollection(nodeNames)

     @codapPhone.call {
       action: 'openCase'
@@ -109,6 +87,39 @@
       else
         log.info "CODAP returned an error on 'openCase'"

+  _createCollection: (nodeNames) ->
+    nodes = @graphStore.getNodes()
+
+    # First column definition is the time index
+    timeUnit = TimeUnits.toString SimulationStore.store.settings.stepUnits, true
+    sampleDataAttrs = [
+      {
+        name: timeUnit
+        type: "numeric"
+      }
+    ]
+
+    addSampleDataAttr = (node) ->
+      type = if node.valueDefinedSemiQuantitatively then 'qualitative' else 'numeric'
+      sampleDataAttrs.push
+        name: node.title
+        type: type
+
+    # Append node names to column descriptions.
+    if (nodeNames)
+      _.each nodeNames, (name) ->
+        node = (nodes.filter (n) -> n.title is name)[0]
+        addSampleDataAttr(node)
+    else
+      _.each nodes, (node) ->
+        addSampleDataAttr(node)
+
+    @codapPhone.call
+      action: 'createCollection'
+      args:
+        name: 'Samples'
+        attrs: sampleDataAttrs
+
   _sendSimulationData: (data) ->
     if not @currentCaseID
       # openNewCase may not have completed yet, so we queue these up
@@ -162,6 +173,29 @@

   createGraph: (yAttributeName)->
     timeUnit = TimeUnits.toString SimulationStore.store.settings.stepUnits, true
+    nodes = @graphStore.getNodes()
+
+    # First column definition is the time index
+    sampleDataAttrs = [
+      {
+        name: timeUnit
+        type: "numeric"
+      }
+    ]
+
+    # Append node names to column descriptions.
+    _.each nodes, (node) ->
+      type = if node.valueDefinedSemiQuantitatively then 'qualitative' else 'numeric'
+      sampleDataAttrs.push
+        name: node.title
+        type: type
+
+    @codapPhone.call
+      action: 'createCollection'
+      args:
+        name: 'Samples'
+        attrs: sampleDataAttrs
+
     @codapPhone.call
       action: 'createComponent'
       args: {


fix(user-agent): add missing part in generated user-agent

fixes #29

--- src/coffee/helpers/user-agent.coffee
+++ src/coffee/helpers/user-agent.coffee
@@ -8,4 +8,4 @@
   runtimeInfo = "#{platform()}; #{arch()}"
   moduleInfo = "#{pkgName}/#{pkgVersion}"

-  "#{sdkPkgVersion} #{nodeInfo} (#{runtimeInfo}) #{moduleInfo}"
+  "node-sdk/#{sdkPkgVersion} #{nodeInfo} (#{runtimeInfo}) #{moduleInfo}"


Updated add-log name

Updated add-log name

--- games/tutorial.coffee
+++ games/tutorial.coffee
@@ -254,7 +254,7 @@
     }

     {
-      name: 'Click Blue Pond to Add and Select Logs'
+      name: 'Click "Edit" & Blue Pond to Add & Select Logs'
       editing: true
       comment: '
       Can create arbitrary sprites and program them if in edit mode.


FIX: Reinstate plagiarism tab in tutor view

--- src/app/units/states/teacher-view/teacher-view.coffee
+++ src/app/units/states/teacher-view/teacher-view.coffee
@@ -43,13 +43,10 @@
       title: 'Groups'
       icon:  'fa-users'
       seq:   3
-    ###
-    Remove Plagiarism Tab until we fix the CSS for it
     plagiarismTab:
       title: 'Plagiarism'
       icon:  'fa-eye'
       seq:   4
-    ###
     analyticsTab:
       title: 'Analytics'
       icon:  'fa-bar-chart'


Fix USNWS adapter fetching

Fix USNWS adapter fetching

--- lib/adapters/usnws/USNWSAdapter.coffee
+++ lib/adapters/usnws/USNWSAdapter.coffee
@@ -22,8 +22,12 @@

   fetchConditionsForStation: (station) ->
     @log 'verbose', "Fetching conditions for #{station}"
-    url = "http://w1.weather.gov/xml/current_obs/#{station}.xml"
-    request url, (err, res, body) =>
+    requestOptions =
+      url: "http://w1.weather.gov/xml/current_obs/#{station}.xml"
+      headers:
+        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0'
+
+    request requestOptions, (err, res, body) =>
       if err?
         @log 'warn', "Failed to fetch XML for #{station} (#{err})"
         return


Remove scroll event listener on unmounting. Fixes #11

--- src/index.cjsx
+++ src/index.cjsx
@@ -46,6 +46,9 @@
     @setState height: @refs.inner.getDOMNode().offsetHeight
     unless @props.disable
       window.addEventListener('scroll', @handleScroll)
+
+  componentWillUnmount: ->
+    window.removeEventListener('scroll', @handleScroll)

   componentWillReceiveProps: (nextProps) ->
     if nextProps.disable and not @props.disable


deployment: update kontrol url for `feature` configuration

--- config/main.feature.coffee
+++ config/main.feature.coffee
@@ -27,7 +27,7 @@
   customDomain        = { public:   "http://#{hostname}"                     , public_:            "#{hostname}"                         , local:           "http://localhost"     , local_:          "localhost"                          , port:     80                   }
   sendgrid            = { username: "koding"                                 , password:           "DEQl7_Dr"                          }
   email               = { host:     "#{customDomain.public_}"                , protocol:           'https:'                              , defaultFromMail: 'hello@koding.com'     , defaultFromName: 'koding'                             , username: sendgrid.username      , password:      sendgrid.password                                     , templateRoot:   "workers/sitemap/files/"                              , forcedRecipient: undefined }
-  kontrol             = { url:      "https://kontrol.koding.com/kite"        , port:               4000                                  , useTLS:          no                     , certFile:        ""                                   , keyFile:  ""                     , publicKeyFile: "#{projectRoot}/certs/test_kontrol_rsa_public.pem"    , privateKeyFile: "#{projectRoot}/certs/test_kontrol_rsa_private.pem" }
+  kontrol             = { url:      "#{customDomain.public_}/kontrol/kite"   , port:               4000                                  , useTLS:          no                     , certFile:        ""                                   , keyFile:  ""                     , publicKeyFile: "#{projectRoot}/certs/test_kontrol_rsa_public.pem"    , privateKeyFile: "#{projectRoot}/certs/test_kontrol_rsa_private.pem" }
   broker              = { name:     "broker"                                 , serviceGenericName: "broker"                              , ip:              ""                     , webProtocol:     "http:"                              , host:     customDomain.public    , port:          8008                                                  , certFile:       ""                                                    , keyFile:         ""          , authExchange: "auth"                , authAllExchange: "authAll" , failoverUri: customDomain.public }
   regions             = { kodingme: "#{configName}"                          , vagrant:            "vagrant"                             , sj:              "sj"                   , aws:             "aws"                                , premium:  "vagrant"            }
   algolia             = { appId:    'DYVV81J2S1'                             , apiKey:             '303eb858050b1067bcd704d6cbfb977c'    , indexSuffix:     '.feature'           }
@@ -91,7 +91,7 @@
     sourcemaps                     : {port          : 3526 }
     appsproxy                      : {port          : 3500 }

-    kloud                          : {port          : 5500                        , privateKeyFile : kontrol.privateKeyFile , publicKeyFile: kontrol.publicKeyFile                        , kontrolUrl: "#{"reads from kite.key by default."}"    , registerUrl : "#{customDomain.public}/kloud/kite" }
+    kloud                          : {port          : 5500                        , privateKeyFile : kontrol.privateKeyFile , publicKeyFile: kontrol.publicKeyFile                        , kontrolUrl: "#{customDomain.public}/kontrol/kite"    , registerUrl : "#{customDomain.public}/kloud/kite" }
     emailConfirmationCheckerWorker : {enabled: no                                 , login : "#{rabbitmq.login}"             , queueName: socialQueueName+'emailConfirmationCheckerWorker' , cronSchedule: '0 * * * * *'                           , usageLimitInMinutes  : 60}

     kontrol                        : kontrol


#178: make form builder data circular for tag input fields

--- modules/form-builder/main/index.coffee
+++ modules/form-builder/main/index.coffee
@@ -325,9 +325,7 @@
         switch it.type
           when 'checkbox' then hx.select(node).prop('checked', value)
           when 'radio' then hx.select(node).selectAll('input').filter((d) -> d.value() is value).prop('checked', true)
-          when 'tagInput'
-            tagInput = hx.component(it.extras.componentNode or node)
-            value.forEach((e) -> tagInput.add(e))
+          when 'tagInput' then hx.component(it.extras.componentNode or node).items(value)
           when 'select' then hx.component(it.extras.componentNode or node).value(value)
           when 'datepicker', 'timepicker', 'datetimepicker' then it.extras.setValue(value)
           else hx.select(node).value(value)


Fix opaskartta layer name.

Fix opaskartta layer name.

--- src/map.coffee
+++ src/map.coffee
@@ -282,7 +282,7 @@
                 continuousWorld: true
                 tms: false

-            guide_map_url = geoserver_url("hel:Opaskartta", "gif")
+            guide_map_url = geoserver_url("hel:Karttasarja", "gif")
             guide_map_options =
                 maxZoom: 12
                 minZoom: 2


bind show,hide,remove to the builder

bind show,hide,remove to the builder

--- src/xml.coffee
+++ src/xml.coffee
@@ -280,6 +280,10 @@

 class Builder extends EventEmitter
     constructor: (@opts = {}) ->
+        # methods
+        @show = @show.bind(this)
+        @hide = @hide.bind(this)
+        @remove = @remove.bind(this)
         # values
         @builder = this
         @checkers = {} # all the middlewares that have to approve a new tag


Added functionality to trigger 'enter' event for searchable select input fields.

--- app/assets/javascripts/app/lib/app_post/searchable_select.coffee
+++ app/assets/javascripts/app/lib/app_post/searchable_select.coffee
@@ -322,6 +322,9 @@
       if @shadowInput.val() is ''
         event.preventDefault()
         @toggle()
+      else
+        @trigger 'enter'
+        @el.trigger 'enter'
       return

     event.preventDefault()


minor changes to the background page

minor changes to the background page

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -607,7 +607,10 @@
       time = Date.parse result.lastModified
       new Date time unless isNaN time
     $.extend data,
+      author:         result.author        ? ''
       characterset:   result.characterSet  ? ''
+      description:    result.description   ? ''
+      keywords:       result.keywords      ? []
       lastmodified:   ->
         (text, render) ->
           lastModified?.format(render(text) or undefined) ? ''
@@ -781,7 +784,7 @@
   loadDiv  = $ '
'
   loadDiv.append [
     $ ''
-    $ '
', text: i18n.get 'shortening'
+    $ '
', text: i18n.get 'please_wait'
   ]...
   # Generate the HTML for each template.
   for template in ext.templates when template.enabled


removed duplicated logic

removed duplicated logic

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -832,8 +832,6 @@
           when 'features_nav' then 'templates_nav'
           when 'toolbar_nav' then 'general_nav'
           else optionsActiveTab
-      if store.get('options_active_tab') is 'features_nav'
-        store.set 'options_active_tab', 'templates_nav'
       store.modify 'anchor', (anchor) ->
         anchor.target = store.get('doAnchorTarget') ? off
         anchor.title  = store.get('doAnchorTitle') ? off


should set alert box text to ok

should set alert box text to ok

--- app/assets/javascripts/type_station/editables/new_page.js.coffee
+++ app/assets/javascripts/type_station/editables/new_page.js.coffee
@@ -68,6 +68,7 @@
                       data: JSON.stringify(json)
                       success: (data, status) -> window.location.reload()
             else
+              vex.dialog.buttons.YES.text = 'Ok'
               vex.dialog.alert
                 message: 'Please fill in all required fields'
                 callback: ->


Fix backspace to delete

Fix backspace to delete

--- assets/javascripts/views/002_permissions_fields_picker.js.coffee
+++ assets/javascripts/views/002_permissions_fields_picker.js.coffee
@@ -266,8 +266,11 @@

     @options_view = @parentView.parentView.options_view

+    @_keydown_value = ''
+
     $(@elements.input).on 'keydown', (e) =>
       @calibrate(e)
+      @_keydown_value = @elements.input.value
       switch e.keyCode
         when 13 # enter/return
           e.preventDefault()
@@ -292,15 +295,16 @@
       clearTimeout @_fetch_timeout
       @_fetch_timeout = setTimeout (=> @parentView.fetchResults(@elements.input.value)), 60

-      if !@getValue().length
+      if !@_keydown_value.length
         option_view = _.last(@options_view.option_views)
-        if e.keyCode == 8 # backspace
-          if option_view.marked_delete
-            option_view.remove()
+        if option_view
+          if e.keyCode == 8 # backspace
+            if option_view.marked_delete
+              option_view.remove()
+            else
+              option_view.markDelete()
           else
-            option_view.markDelete()
-        else
-          option_view.unmarkDelete()
+            option_view.unmarkDelete()

   calibrate: (e) =>
     el = @elements.input


Add on method to u()

Add on method to u()

--- js/u.coffee
+++ js/u.coffee
@@ -46,25 +46,26 @@
       @each (i, el) -> dom.toggleClass el, cls
       this

-    click: (cb) ->
-      @bind "click", cb
-      this
+    click: (cb) -> @bind "click", cb
+
+    on: (n,cb) ->
+      m = n.charAt(0).toUpperCase() + n.substr(1)
+      @bind(m,cb)

     bind: (n, cb) ->
       @each (i, el) -> el.addEventListener n, cb
-      this

     style: (computed) ->
-      return unless this.element
-      return window.getComputedStyle(this.element) if computed
-      this.element.style
+      return unless @element
+      return window.getComputedStyle(@element) if computed
+      @element.style

     height: (a) ->
       if a
-        this.each (i,e) -> e.style.height = parseInt(a) + 'px'
+        @each (i,e) -> e.style.height = parseInt(a) + 'px'
       else
-        return unless this.element
-        return parseInt(this.element.style.height) if this.element.style.height
+        return unless @element
+        return parseInt(@element.style.height) if @element.style.height
         parseInt(@style(1).height)


Use 4 char long for PINs

Use 4 char long for PINs

--- server/app/bongo/models/verify.coffee
+++ server/app/bongo/models/verify.coffee
@@ -48,8 +48,8 @@
             if count > 0 then console.log "#{count} expired PIN removed."
             else console.log "No such waiting PIN found."

-            # Create a random pin 7 char length
-            plainPin = Math.floor Math.random()*10000001
+            # Create a random pin 4 char length
+            plainPin = Math.floor Math.random()*10001
             pin      = crypto.createHash('sha1').update(plainPin+'').digest('hex')

             # Create and send new pin


Improve matching of chained DSR commands

--- grammars/runoff.cson
+++ grammars/runoff.cson
@@ -44,17 +44,53 @@
 		match: "[^_]\x08(?=_)|(?<=_)\x08[^_]"

+	# Parameters passed to control lines
+	arguments:
+		patterns: [{
+			name: "constant.numeric.runoff"
+			match: "[-+]?\\d+(?:\\.\\d+)?"
+		},{
+			# Double-quoted string
+			name: "string.quoted.runoff"
+			begin: '"'
+			end:   '"'
+			beginCaptures: 0: name: "punctuation.definition.string.begin.runoff"
+			endCaptures:   0: name: "punctuation.definition.string.end.runoff"
+		},{
+			# Unquoted string
+			name: "variable.parameter.runoff"
+			match: "(?!\\.)[^\\s;]+"
+			captures:
+				0: name: "string.unquoted.runoff"
+		}]
+
+
 	# Control lines
 	commands:
+		name: "meta.control-line.runoff"
+		begin: "^\x0C*(?=\\.)"
+		end: "$|;(?!\\.)"
+		endCaptures:
+			0: name: "punctuation.terminator.statement.runoff"
 		patterns: [{

+			# Trailing comment
+			name: "comment.line.runoff"
+			begin: "!"
+			end:   "$"
+			beginCaptures:
+				0: "punctuation.definition.comment.runoff"
+		},{
 			# .if/.endif: Control flow
 			name: "meta.condition.runoff"
-			match: "(?i)^\x0C*((\\.)(?:IF|ELSE|ENDIF))(?:$|[ \t]+(\\S*))"
-			captures:
+			begin: "(?:^|(?<=;))((\\.)(?:IF|ELSE|ENDIF))(?=$|\\s|;)"
+			end: "$|(;)"
+			beginCaptures:
 				1: name: "keyword.control.runoff"
 				2: name: "punctuation.definition.function.runoff"
-				3: name: "variable.parameter.runoff"
+			endCaptures:
+				1: name: "punctuation.terminator.statement.runoff"
+			patterns: [include: "#arguments"]
 		},{

 			# .var: Variable declaration
@@ -68,13 +104,12 @@

 			# Generic command
 			name: "meta.function.runoff"
-			begin: "^\x0C*((\\.)[^;\\s]+)"
-			end: "$|(;)(?!\\.)|((!).*)"
+			begin: "(?i)(?:^|(?<=;))((\\.)(?:END\\s+)?[^;\\s\\-+]+)"
+			end:   "$|(;)(?=\\.)"
+			patterns: [include: "#arguments"]
 			beginCaptures:
-				1: name: "entity.function.name.runoff"
-				2: name: "punctuation.definition.macro.runoff"
+				1: name: "keyword.function.name.runoff"
+				2: name: "punctuation.definition.function.runoff"
 			endCaptures:
 				1: name: "punctuation.terminator.statement.runoff"
-				2: name: "comment.line.runoff"
-				3: name: "punctuation.definition.comment.runoff"
 		}]


Add snippets to insert console.info statements

--- .config/snippets.cson
+++ .config/snippets.cson
@@ -51,6 +51,9 @@
 	Babel:
 		prefix: "babel"
 		body: '"use babel";\n"use strict";\n\n'
+	Info:
+		prefix: "info"
+		body: 'console.info($1);$0'

 # CoffeeScript/CSON
@@ -82,6 +85,11 @@
 		prefix: "\\\\"
 		body: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"

+	# Snippets not related to grammar-development
+	Info:
+		prefix: "info"
+		body: "console.info $0"
+
 ".source.coffee .oniguruma":
 	"Include":
 		prefix: "inc"
@@ -112,3 +120,10 @@
 	"Boilerplate":
 		prefix: "htmldoc"
 		body: '<!DOCTYPE html>\n\n\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\n<meta http-equiv="X-UA-Compatible" content="IE=edge" />\n<meta name="viewport" content="initial-scale=1, minimum-scale=1" />\n<title>$1</title>\n\n\n\n\n\n\n\t\n\t$2\n\t\n\n'
+
+
+# Shell-scripts
+".source.shell":
+	"Getopts":
+		prefix: "opt"
+		body: "while getopts ${1:opt} ${2:option}; do\n\tcase $${2:option} in\n\t\t${3:o) SOME=THING;;}\n\tesac\ndone\nshift $((OPTIND - 1))"


fix for #125

fix for #125

Accounts.ui._options.requestOfflineToken is sometimes undefined

--- client/views/social/social.coffee
+++ client/views/social/social.coffee
@@ -48,7 +48,7 @@
     if (Accounts.ui._options.requestPermissions[serviceName])
       options.requestPermissions = Accounts.ui._options.requestPermissions[serviceName]

-    if (Accounts.ui._options.requestOfflineToken[serviceName])
+    if (Accounts.ui._options.requestOfflineToken and Accounts.ui._options.requestOfflineToken[serviceName])
       options.requestOfflineToken = Accounts.ui._options.requestOfflineToken[serviceName]

     loginWithService(options, callback)


Refactor gpg form encryptor

Refactor gpg form encryptor