Verify that rotation angle is in (-180, 180]

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -664,7 +664,8 @@
     deleted: Match.Optional Boolean
     private: Match.Optional Boolean
     minimized: Match.Optional Boolean
-    rotate: Match.Optional Number
+    rotate: Match.Optional Match.Where (r) ->
+      typeof r == "number" and -180 < r <= 180

   ## Don't update if there aren't any actual differences.
   difference = false
@@ -884,7 +885,8 @@
       deleted: Match.Optional Boolean
       private: Match.Optional Boolean
       minimized: Match.Optional Boolean
-      rotate: Match.Optional Number
+      rotate: Match.Optional Match.Where (r) ->
+        typeof r == "number" and -180 < r <= 180
     user = Meteor.user()
     unless canPost group, parent, user
       throw new Meteor.Error 'messageNew.unauthorized',


Prevent error in Map, although not a great fix

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1813,7 +1813,7 @@
   else
     args = _arguments call
     if args.length % 2 != 0
-      malformed args[args.length - 1], 'Missing value for key'
+      return malformed args[args.length - 1], 'Missing value for key'
     [labels, items] = unzip pairs args
     compiledLabels = uniformCollectionItemsCompile ctx, labels
     keyType = applyKindFn hashmapType, compiledLabels.itemType


Fixed ids on macros

Fixed ids on macros

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -196,7 +196,8 @@
     @_macros

   addMacro: (name, macro) ->
-    @_macros[name] = macro
+    @_macros[name.symbol] = macro
+    name.id = macro.id = @freshId()

   definePattern: (pattern) ->
     if @isDefining()
@@ -565,7 +566,9 @@
 callMacroCompile = (ctx, call) ->
   op = _operator call
   op.label = 'keyword'
-  expanded = ctx.macros()[op.symbol] ctx, call
+  macro = ctx.macros()[op.symbol]
+  op.id = macro.id
+  expanded = macro ctx, call
   if isTranslated expanded
     expanded
   else
@@ -1682,7 +1685,7 @@
     compiledMacro = translateToJs translateIr ctx,
         (termCompile ctx, call_ (token_ 'fn'), (join [paramTuple], rest))
     macroFn = eval compiledMacro
-    ctx.addMacro macroName, (ctx, call) ->
+    ctx.addMacro ctx.definitionPattern(), (ctx, call) ->
       # operatorCompile ctx, call
       # args = termsCompile ctx, (_arguments call)[0..macroFn.length]
       #callTyping ctx, call
@@ -1757,7 +1760,7 @@
     compiledMacro = translateToJs translateIr ctx,
       (termCompile ctx, call_ (token_ 'fn'), (join [paramTuple], rest))
     # log compiledMacro
-    ctx.addMacro macroName, simpleMacro eval compiledMacro
+    ctx.addMacro ctx.definitionPattern(), simpleMacro eval compiledMacro
     params = (map token_, paramNames) # freshen

     fn_ params, call_ (token_ macroName), params


Removed updating of classParams and binding names and tests pass, but dragons await

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -547,7 +547,7 @@
     nestedLookupInMap @classParams, typeNamesOfNormalized constraint

   updateClassParams: ->
-    @classParams = substituteVarNames this, @classParams
+    # @classParams = substituteVarNames this, @classParams

 expressionCompile = (ctx, expression) ->
   throw new Error "invalid expressionCompile args" unless ctx instanceof Context and expression?
@@ -1129,16 +1129,6 @@
         throw new Error "Error when deferring #{name} #{e.message}"
       if _notEmpty retainedConstraints
         ctx.extendSubstitution substituionFail "#{name}'s context is too weak, missing #{listOf (map printType, retainedConstraints)}"
-        console.log "unfiied", map printType, unifiedType.constraints
-        console.log "checked", map printType, checked
-        console.log "not declared", (map printType, notDeclared)
-        # console.log "reduced checked", map printType, substituteList ctx.substitution, reduceConstraints ctx, checked
-        console.log name,
-          "declared", (map printType, (substituteList ctx.substitution, unifiedType.constraints))
-          "inferred", (map printType, (substituteList ctx.substitution, constraints))
-          "not declared", (map printType, (substituteList ctx.substitution, notDeclared))
-          "retaineed", (map printType, retainedConstraints)
-
   else
     [deferredConstraints, retainedConstraints] = deferConstraints ctx,
       ctx.allBoundTypeVariables(),
@@ -1146,7 +1136,7 @@
       (substituteList ctx.substitution, constraints)
     # Finalizing type again after possibly added substitution when defer constraints
     currentType = substitute ctx.substitution, type
-    # log "assign type", name, (printType currentType), retainedConstraints
+    # log "assign type", name, (printType (addConstraints currentType, retainedConstraints)), (printType quantifyUnbound ctx, (addConstraints currentType, retainedConstraints))
     ctx.assignType name,
       if polymorphic
         quantifyUnbound ctx, (addConstraints currentType, retainedConstraints)
@@ -2076,8 +2066,8 @@

 substituteVarNames = (ctx, varNames) ->
   subbed = (name) =>
-    (inSub ctx.substitution, name)?.name or name
-  rehashMap subbed, varNames
+    (inSub ctx.substitution, name) or new TypeVariable name
+  findFreeInList map subbed, setToArray varNames

 # Takes a set of fixed type variables, a set of type variables which
 # should be quantified and a list of constraints
@@ -2273,7 +2263,7 @@
       atom.label = 'name'
       id = (ctx.definitionId()) ? (ctx.declarationId symbol) ? ctx.freshId()
       type = toConstrained ctx.freshTypeVariable star
-      ctx.bindTypeVariables [type.type.name]
+      # ctx.bindTypeVariables [type.type.name]
       ctx.addToDefinedNames {name: symbol, id: id, type: type}
       type: type
       id: id
@@ -5570,16 +5560,20 @@
   """
   "(size (concat-suffix {1} (concat-map (fn [x] {{1}}) {1 2 3})))", 6

-  # 'overloaded subterms'
-  # """
-  # #{concatTest}
-
-  # concat-suffix (fn [suffix what]
-  #   (fold join-suffix e what)
-  #   join-suffix (fn [x joined]
-  #     (concat {joined suffix x})))
-  # """
-  # "(size (concat-suffix {1} (concat-map (fn [x] {{1}}) {1 2 3})))", 6
+  'overloaded subterms'
+  """
+  id (fn [x] x)
+
+  Bag (class [bag item]
+    fold (fn [with initial over]
+      (: (Fn (Fn item a a) a bag a))))
+
+  fold-right (fn [with initial over]
+    ((fold helper id over) initial)
+    helper (fn [x r acc]
+      (r (with x acc))))
+  """
+  "6", 6

   'recursive overloaded functions'
   """


Dont throw when instance not found during translation

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -3266,8 +3266,9 @@
     # TODO: support lookup of composite types, by traversing left depth-first
     if toMatchTypes (freshInstance ctx, type).type.types, constraint.types
       return validIdentifier name
-  throw new Error "no instance for #{safePrintType constraint}"
-  # return {}
+  ctx.extendSubstitution instanceLookupFailed constraint
+  # throw new Error "no instance for #{safePrintType constraint}"
+  return 'undefined'

 irFunction = ({name, params, body}) ->
   {ir: irFunctionTranslate, name, params, body}


:fire: leftover

:fire: leftover

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -189,12 +189,9 @@
       delete @bufferCheckpointByPurpose[purpose]

   groupChangesSinceBufferCheckpoint: (purpose) ->
-    console.log "called groupChangesSinceCheckpoint #{purpose}"
     if checkpoint = @getBufferCheckpoint(purpose)
-      console.log "checkpoint found: #{purpose}"
       @editor.groupChangesSinceCheckpoint(checkpoint)
     else
-      console.log "checkpoint NOT found: #{purpose}"
     @deleteBufferCheckpoint(purpose)

   # Main


delete redundant code

delete redundant code

--- src/classify-page.coffee
+++ src/classify-page.coffee
@@ -208,12 +208,6 @@

   composeAnnotations: ->
     annotations = []
-
-    for {mark} in @subjectViewer.markingSurface.tools
-      # A drawing task's value is the last-selected tool, which is not terribly
-      # useful. Replace it with the task's marks.
-      task = annotations[mark._taskIndex]
-      task.addMark mark if task?

     decisionTreeValues = @decisionTree.getValues()
     for keyAndValue in decisionTreeValues then for key, value of keyAndValue


Removes the default functionality where it tries to determine whether something should be wrapped in a frame based on where the content is coming from.

--- src/components/html.cjsx
+++ src/components/html.cjsx
@@ -14,8 +14,7 @@
     processHtmlAndMath: React.PropTypes.func
   getDefaultProps: ->
     block: false
-    shouldExcludeFrame: (frame) ->
-      /cnx.org\/specials\//.test(frame.src)
+    shouldExcludeFrame: (frame) -> false

   render: ->
     {className, block} = @props


Add tabType config item

Add tabType config item

--- src/config-schema.coffee
+++ src/config-schema.coffee
@@ -143,6 +143,10 @@
       softTabs:
         type: 'boolean'
         default: true
+      tabType:
+        type: 'string'
+        default: 'auto'
+        enum: ['auto', 'soft', 'hard']
       softWrapAtPreferredLineLength:
         type: 'boolean'
         default: false


Make sure comment ids are unique

Make sure comment ids are unique

--- app/coffee/RangesTracker.coffee
+++ app/coffee/RangesTracker.coffee
@@ -35,7 +35,7 @@
 		#   * Inserts by another user will not combine with inserts by the first user. If they are in the
 		#     middle of a previous insert by the first user, the original insert will be split into two.
 		constructor: (@changes = [], @comments = []) ->
-			@setIdSeed("")
+			@setIdSeed(RangesTracker.generateIdSeed())

 		getIdSeed: () ->
 			return @id_seed
@@ -43,6 +43,19 @@
 		setIdSeed: (seed) ->
 			@id_seed = seed
 			@id_increment = 0
+
+		@generateIdSeed: () ->
+			# Generate a the first 18 characters of Mongo ObjectId, leaving 6 for the increment part
+			# Reference: https://github.com/dreampulse/ObjectId.js/blob/master/src/main/javascript/Objectid.js
+			pid = Math.floor(Math.random() * (32767)).toString(16)
+			machine = Math.floor(Math.random() * (16777216)).toString(16)
+			timestamp = Math.floor(new Date().valueOf() / 1000).toString(16)
+			return '00000000'.substr(0, 8 - timestamp.length) + timestamp +
+				'000000'.substr(0, 6 - machine.length) + machine +
+				'0000'.substr(0, 4 - pid.length) + pid
+
+		@generateId: () ->
+			@generateId() + "000001"

 		newId: () ->
 			@id_increment++


Fixup.

Fixup.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -204,7 +204,9 @@
             ]
           }

-    expectError "[a] == [b] !stron"
+    expectError """
+                  [a] == [b] !stron
+                """

   # Stays


formatting fixes

formatting fixes

--- spec/e2e.spec.coffee
+++ spec/e2e.spec.coffee
@@ -53,75 +53,75 @@
          context.port = 8882

       describe 'basics', ->
-        it 'should return a basic GET endpoint', ->
-            context.url = '/basic/get'
-            context.method = 'get'
+         it 'should return a basic GET endpoint', ->
+             context.url = '/basic/get'
+             context.method = 'get'

-            createRequest context
-            waitsFor ( -> context.passed ), 'request to finish', 1000
+             createRequest context
+             waitsFor ( -> context.passed ), 'request to finish', 1000

-        it 'should return a basic PUT endpoint', ->
-            context.url = '/basic/put'
-            context.method = 'put'
+         it 'should return a basic PUT endpoint', ->
+             context.url = '/basic/put'
+             context.method = 'put'

-            createRequest context
-            waitsFor ( -> context.passed ), 'request to finish', 1000
+             createRequest context
+             waitsFor ( -> context.passed ), 'request to finish', 1000

-        it 'should return a basic POST endpoint', ->
-            context.url = '/basic/post'
-            context.method = 'post'
+         it 'should return a basic POST endpoint', ->
+             context.url = '/basic/post'
+             context.method = 'post'

-            createRequest context
-            waitsFor ( -> context.passed ), 'request to finish', 1000
+             createRequest context
+             waitsFor ( -> context.passed ), 'request to finish', 1000

-        it 'should return a basic DELETE endpoint', ->
-            context.url = '/basic/delete'
-            context.method = 'delete'
+         it 'should return a basic DELETE endpoint', ->
+             context.url = '/basic/delete'
+             context.method = 'delete'

-            createRequest context
-            waitsFor ( -> context.passed ), 'request to finish', 1000
+             createRequest context
+             waitsFor ( -> context.passed ), 'request to finish', 1000

       describe 'GET', ->
-        it 'should return a body from a GET endpoint', ->
-            context.url = '/get/body'
-            context.method = 'get'
-            context.body = 'plain text'
+         it 'should return a body from a GET endpoint', ->
+             context.url = '/get/body'
+             context.method = 'get'
+             context.body = 'plain text'

-            createRequest context
-            waitsFor ( -> context.passed ), 'request to finish', 1000
+             createRequest context
+             waitsFor ( -> context.passed ), 'request to finish', 1000

-        it 'should return a body from a json GET endpoint', ->
-            context.url = '/get/json'
-            context.method = 'get'
-            context.body = '{"property":"value"}'
-            context.headers =
-               'content-type': 'application/json'
+         it 'should return a body from a json GET endpoint', ->
+             context.url = '/get/json'
+             context.method = 'get'
+             context.body = '{"property":"value"}'
+             context.headers =
+                'content-type': 'application/json'

-            createRequest context
-            waitsFor ( -> context.passed ), 'request to finish', 1000
+             createRequest context
+             waitsFor ( -> context.passed ), 'request to finish', 1000

-        it 'should return a 420 GET endpoint', ->
-            context.url = '/get/420'
-            context.method = 'get'
-            context.status = 420
+         it 'should return a 420 GET endpoint', ->
+             context.url = '/get/420'
+             context.method = 'get'
+             context.status = 420

-            createRequest context
-            waitsFor ( -> context.passed ), 'request to finish', 1000
+             createRequest context
+             waitsFor ( -> context.passed ), 'request to finish', 1000

       describe 'post', ->
-        it 'should be able to handle authorized posts', ->
-            context.url = '/post/auth'
-            context.method = 'post'
-            context.status = 201
-            context.post = 'some=data'
-            context.requestHeaders =
-               authorization: "Basic c3R1YmJ5OnBhc3N3b3Jk"
-            context.headers =
-               location: '/some/endpoint/id'
-            context.body = 'resource has been created'
+         it 'should be able to handle authorized posts', ->
+             context.url = '/post/auth'
+             context.method = 'post'
+             context.status = 201
+             context.post = 'some=data'
+             context.requestHeaders =
+                authorization: "Basic c3R1YmJ5OnBhc3N3b3Jk"
+             context.headers =
+                location: '/some/endpoint/id'
+             context.body = 'resource has been created'

-            createRequest context
-            waitsFor ( -> context.passed ), 'request to finish', 1000
+             createRequest context
+             waitsFor ( -> context.passed ), 'request to finish', 1000
       describe 'put', ->
          it 'should wait if a 2000ms latency is specified', ->
             context.url = '/put/latency'
@@ -145,7 +145,7 @@
          createRequest context
          waitsFor ( -> context.passed ), 'request to finish', 1000

-      xit 'should be able to edit an endpoint through PUT', ->
+      it 'should be able to edit an endpoint through PUT', ->
          id = 2
          endpoint = ce.clone endpointData[id-1]
          context.url = "/#{id}"
@@ -166,4 +166,3 @@

          createRequest context
          waitsFor ( -> context.passed ), 'get to finish', 1000
-


mock git.cmd in git.reset test

mock git.cmd in git.reset test

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -60,7 +60,7 @@

   describe "git.reset", ->
     it "resets and unstages all files", ->
-      spyOn(git, 'cmd').andCallThrough()
+      spyOn(git, 'cmd').andCallFake () -> Promise.resolve true
       waitsForPromise ->
         repo = git.getSubmodule(pathToSubmoduleFile)
         git.reset(repo).then () ->


load template into body

load template into body

--- app/assets/javascripts/letters.js.coffee
+++ app/assets/javascripts/letters.js.coffee
@@ -3,8 +3,9 @@
 		dataType: 'json'
 		done: (e, data)->
 			$.each data.result.file, (index, file)->
+				window.f = file
 				$('.filename').text file.original_filename
-				$('mail-body').text file.tempfile
+				$('.mail-body').text file.tempfile.join('')

 	$('.open-file').click (e)->
 		$('#fileupload').click()


Round off fractional overlay width

Round off fractional overlay width

--- app/assets/javascripts/overlay.js.coffee
+++ app/assets/javascripts/overlay.js.coffee
@@ -73,7 +73,7 @@
     if heightRatio > widthRatio
       height = maxPhotoHeight * widthRatio
     else if widthRatio > heightRatio
-      width = (maxPhotoWidth * heightRatio) + commentWidth
+      width = Math.round((maxPhotoWidth * heightRatio) + commentWidth)

     margin = (maxWidth - width) / 2


Mouse pan-zoom; portable urls

Mouse pan-zoom; portable urls

--- app/assets/javascripts/tracker.js.coffee
+++ app/assets/javascripts/tracker.js.coffee
@@ -12,6 +12,7 @@

 ###  Parse data from Prediction KML file to show Balloon Burst ###
+host = window.location.host

 parseData = (req) ->
   g = new (OpenLayers.Format.KML)(extractStyles: true)
@@ -50,7 +51,8 @@
   numZoomLevels: 16
   maxResolution: 156543.0339
   maxExtent: new (OpenLayers.Bounds)(-20037508.34, -20037508.34, 20037508.34, 20037508.34)
-  controls: []
+  controls: [new OpenLayers.Control.PanZoomBar,
+             new OpenLayers.Control.Navigation({dragPanOptions: {enableKinetic: true}})]

 ###and create a projection, size, pixel offset and icons for ###

@@ -101,7 +103,6 @@
 map.setCenter cpoint.transform(proj, dst), 11
 LaySwitch = new (OpenLayers.Control.LayerSwitcher)
 map.addControl LaySwitch
-map.addControl new OpenLayers.Control.PanZoom
 map.addControl new (OpenLayers.Control.ScaleLine)(
   geodesic: true
   maxWidth: 200)
@@ -234,7 +235,6 @@

 updateVehicleLocation = ( vehicleId ) ->
   $.getJSON urlVehicleLoc + vehicleId, (data) ->
-
     if data and data['time'] isnt thisVehicleLastTime
       vehicleLayer.destroyFeatures( [ thisVehicleLocation ] ) if thisVehicleLocation
       tmpPoint = new OpenLayers.Geometry.Point( data['lon'], data['lat'] )
@@ -329,7 +329,7 @@
     updateVehicleLocation(thisVehicleId)
     updatePlatformTracks( 0 )
     #clearInterval( timerId ) if count++ is 2
-  , 5000
+  , 10000

 $('#control #locSimStart').click ->
   if isSim
@@ -340,12 +340,12 @@

   speedup = $("#simSpeed input[type='radio']:checked").val()

-  url = 'http://localhost:3000/location_devices/start/' + locDev.id + '/' + speedup
+  url = 'http://' + host + '/location_devices/start/' + locDev.id + '/' + speedup
   $.post url, (data) ->
     $('#control #locDriver').html(data)

 $('#control #locSimEnd').click ->
-  url = 'http://localhost:3000/location_devices/stop/' + locDev.id
+  url = 'http://' + host + '/location_devices/stop/' + locDev.id
   $.post url, (data) ->
     $('#control #locDriver').html(data)

@@ -361,14 +361,14 @@
   speedup = $("#simSpeed input[type='radio']:checked").val()

   for rcvr in beaconRcvrs
-    url = 'http://localhost:3000/beacon_receivers/start/' + rcvr.id + '/' + speedup
+    url = 'http://' + host + '/beacon_receivers/start/' + rcvr.id + '/' + speedup
     $.post url, (data) ->
       $('#control #beaconDriver').html(data)
       #console.log 'Sim Start'

 $('#control #beaconSimEnd').click ->
   for rcvr in beaconRcvrs
-    url = 'http://localhost:3000/beacon_receivers/stop/' + rcvr.id
+    url = 'http://' + host + '/beacon_receivers/stop/' + rcvr.id
     #console.log 'Sim End'
     $.post url, (data) ->
       $('#control #beaconDriver').html(data)


Always get selected options from the annotation

--- app/classifier/tasks/dropdown/index.cjsx
+++ app/classifier/tasks/dropdown/index.cjsx
@@ -81,12 +81,8 @@
       task.selects.every (select, i) ->
         not select.required or (annotation.value[i]?.value? and annotation.value[i]?.value isnt "")

-  getInitialState: ->
-    selectedOptions: []
-
   componentWillMount: ->
     @menus = []
-    @syncAnnotations()

   componentDidMount: ->
     annotationValues = @props.annotation.value
@@ -97,12 +93,10 @@
       @handleFocus()

   componentDidUpdate: (prevProps) ->
-    if prevProps.task isnt @props.task
-      @syncAnnotations()
     if prevProps.task isnt @props.task and @props.autoFocus is true
       @handleFocus()

-  syncAnnotations: () ->
+  selectedOptions: () ->
     # this can only be called after props have been updated
     # as it relies on props.task and props.annotation.
     # clear selections, then check each annotation.value
@@ -115,7 +109,7 @@
         selectedOptions[i] = selected
       else
         selectedOptions[i] = {label: annotation.value, value: annotation.value}
-    @setState {selectedOptions}
+    selectedOptions

   handleFocus: ->
     @menus[0].focus()
@@ -162,7 +156,7 @@
           options = []
           options.push option for option in @getOptions(i)
           disabled = @getDisabledAttribute(i)
-          selectedOption = if @state.selectedOptions[i]?.value then @state.selectedOptions[i] else null
+          selectedOption = if @selectedOptions()[i]?.value then @selectedOptions()[i] else null

             {if select.title isnt @props.task.instruction

{select.title}
}
@@ -195,8 +189,6 @@
     </GenericTask>

   onChangeSelect: (i, option = {label: null, value: null}) ->
-    {selectedOptions} = @state
-    selectedOptions[i] = option
     value = @props.annotation.value

     options = @getOptions(i)
@@ -209,5 +201,4 @@
     @clearRelated(i)

     newAnnotation = Object.assign @props.annotation, {value}
-    @setState {selectedOptions}
     @props.onChange newAnnotation


Fixing comments for anonymous user if any comment was deleted

--- app/coffee/modules/common/history.coffee
+++ app/coffee/modules/common/history.coffee
@@ -276,8 +276,9 @@
                     deleteCommentUser: comment.delete_comment_user.name
                     deleteComment: comment.comment_html
                     activityId: comment.id
-                    canRestoreComment: (comment.delete_comment_user.pk == $scope.user.id or
-                                        $scope.project.my_permissions.indexOf("modify_project") > -1)
+                    canRestoreComment: ($scope.user and
+                                        (comment.delete_comment_user.pk == $scope.user.id or
+                                        $scope.project.my_permissions.indexOf("modify_project") > -1))
                 })

                 html = $compile(html)($scope)


[backport] prevent save icon dissapear in details

--- app/coffee/modules/common/loading.coffee
+++ app/coffee/modules/common/loading.coffee
@@ -61,12 +61,12 @@
                 target = service.settings.target
                 service.settings.classes.map (className) -> target.removeClass(className)

+                if not target.hasClass('loading') && !service.settings.template
+                    service.settings.template = target.html()
+
                 # The loader is shown after that quantity of milliseconds
                 timeoutId = setTimeout (->
                     if not target.hasClass('loading')
-                        if !service.settings.template
-                            service.settings.template = target.html()
-
                         target.addClass('loading')

                         target.html(spinner)


chore (Rollbar) Do not report error about unauthenticated users

--- app/scripts/controllers/elections.coffee
+++ app/scripts/controllers/elections.coffee
@@ -16,7 +16,6 @@

         (failure) =>
           @loadError = failure
-          errorMonitor.error failure, "Failed getting elections"

       ).finally =>
         @loading = false


#676 fix incorrect description

#676 fix incorrect description

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -108,7 +108,7 @@
     description: "Don't move cursor after yank"
   stayOnDelete:
     default: false
-    description: "Don't move cursor after yank"
+    description: "Don't move cursor after delete"
   stayOnOccurrence:
     default: true
     description: "Don't move cursor when operator works on occurrences( when `true`, override operator specific `stayOn` options )"


Use Humanize for the reserve estimation on logging.

--- app/static/script/directive/chart.coffee
+++ app/static/script/directive/chart.coffee
@@ -141,7 +141,7 @@
           }
         , cumulative)

-        # $log.info "Reserves estimation:", mineral, latest, reserveEstimate, reserveNotes
+        # $log.info "Reserves estimation:", mineral, latest, Humanize.compactInteger(reserveEstimate, 3), reserveNotes
         # $log.info "Cumulative world production:", cumulative
         # $log.info "Reserves:", reserves


Update tooltip when title changes

Update tooltip when title changes

Closes #12

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -9,20 +9,27 @@
       @div class: 'close-icon'

   initialize: (@item, @pane) ->
-    @item.on? 'title-changed', => @updateTitle()
-    @item.on? 'modified-status-changed', => @updateModifiedStatus()
+    @item.on? 'title-changed', =>
+      @updateTitle()
+      @updateTooltip()
+
+    @item.on? 'modified-status-changed', =>
+      @updateModifiedStatus()
+
     @updateTitle()
     @updateModifiedStatus()
-    @initializeTooltip()
+    @updateTooltip()

-  initializeTooltip: ->
-    return unless @item.getPath
-    @setTooltip
-      title: @item.getPath()
-      delay:
-        show: 2000
-        hide: 100
-      placement: 'bottom'
+  updateTooltip: ->
+    @destroyTooltip()
+
+    if itemPath = @item.getPath?()
+      @setTooltip
+        title: @item.getPath()
+        delay:
+          show: 2000
+          hide: 100
+        placement: 'bottom'

   beforeRemove: ->
     @destroyTooltip()


Fix #5

Fix #5

--- lib/terminal.coffee
+++ lib/terminal.coffee
@@ -252,6 +252,8 @@
   ## Writing typed messages to the terminal ------------------------------------

   writeTypedMessage: ({type,head,body,data}={}) ->
+    headElem = ''
+    bodyElem = ''
     if head or body
       startTag  = "<message type=\"#{type}\""
       startTag += " data-#{key}=\"#{value}\""  for key,value of data
@@ -281,6 +283,19 @@

   updateTypedMessageBufferOnDidCreateNewLine: ->
     if @typedMessageBuffer?
+      unless @typedMessageCurrentLineBuffer.match(/^<message\s+.*type=.*>$/)? \
+          or @typedMessageCurrentLineBuffer.match(/^<head>$/)? \
+          or @typedMessageCurrentLineBuffer.match(/^<\/head>$/)? \
+          or @typedMessageCurrentLineBuffer.match(/^<body>$/)? \
+          or @typedMessageCurrentLineBuffer.match(/^<\/body>$/)? \
+          or @typedMessageCurrentLineBuffer.match(/^<\/message>/)?
+        # escape special characters
+        @typedMessageCurrentLineBuffer = @typedMessageCurrentLineBuffer\
+          .replace(/&/g,'&')
+          .replace(/"/g,'"')
+          .replace(/'/g,''')
+          .replace(/</g,'<')
+          .replace(/>/g,'>')
       @typedMessageBuffer += "#{@typedMessageCurrentLineBuffer}\n"
       if @typedMessageCurrentLineBuffer.match(/^<\/message>/)?
         typedMessage = @readTypedMessage(@typedMessageBuffer)
@@ -298,6 +313,8 @@
         @emitter.emit('did-start-reading-typed-message')

   readTypedMessage: (buffer) ->
+    console.log buffer
+
     typedMessageXml = $($.parseXML(buffer)).find('message')
     typedMessage = {id: @constructor.getTypedMessageId()}


Trigger added and removed events on the element being added and removed

--- lib/assets/javascripts/nested_form_fields.js.coffee
+++ lib/assets/javascripts/nested_form_fields.js.coffee
@@ -24,7 +24,7 @@
       $child.replaceWith($("<script id='#{$child.attr('id')}' type='text/html' />").html($child.html()))

     $template.before( $parsed_template )
-    $.event.trigger("fields_added.nested_form_fields",{object_class: object_class, added_index: added_index, association_path: association_path});
+    $parsed_template.trigger("fields_added.nested_form_fields", {object_class: object_class, added_index: added_index, association_path: association_path});
     false

   $('body').off("click", '.remove_nested_fields_link')
@@ -38,7 +38,7 @@
     $nested_fields_container.before "<input type='hidden' name='#{delete_association_field_name}' value='1' />"
     $nested_fields_container.hide()
     $nested_fields_container.find('input[required]:hidden').removeAttr('required')
-    $.event.trigger("fields_removed.nested_form_fields",{object_class: object_class, delete_association_field_name: delete_association_field_name, removed_index: removed_index});
+    $nested_fields_container.trigger("fields_removed.nested_form_fields",{object_class: object_class, delete_association_field_name: delete_association_field_name, removed_index: removed_index});
     false

 $(document).on "page:change", ->


BindAll was not working since it could not find the bind method on pageObject. Changed it to call bind on 'this'

--- lib/calatrava/templates/kernel/app/calatrava.coffee
+++ lib/calatrava/templates/kernel/app/calatrava.coffee
@@ -89,7 +89,7 @@
     calatrava.bridge.runtime.attachProxyEventHandler(proxyId, event)

   bindAll: (options) ->
-    _.each options, (handler, event) => pageObject.bind event, handler
+    _.each options, (handler, event) => @bind event, handler

   dispatch: (event) ->
     args = _.toArray(arguments).slice(1)


moved jshint

moved jshint

--- app/templates/_gruntfile.coffee
+++ app/templates/_gruntfile.coffee
@@ -12,7 +12,7 @@

   # package options
   packageJson = grunt.file.readJSON('package.json')
-
+
   # configurable paths
   yeomanConfig =
     appName: "<%= appName %>"
@@ -304,7 +304,7 @@
     cssmin:
       options:
         report: 'gzip'
-        banner: '/*! Build <%%= pkg.name %> - v<%%= pkg.version %> - <%%= grunt.template.today("yyyy-mm-dd") %> */'
+        banner: '/*! Build <%%= pkg.name %> - v<%%= pkg.version %> - <%%= grunt.template.today("yyyy-mm-dd") %> */'

       dist:
         files:
@@ -400,6 +400,7 @@
   # $ grunt build
   grunt.registerTask "build", [
     "clean:dist"
+    "jshint"
     "coffeelint"
     "concurrent:dist"
     "uglify:dist"
@@ -414,7 +415,7 @@
   ]

   # $ grunt
-  grunt.registerTask "default", ["jshint", "build"]
+  grunt.registerTask "default", ["build"]

   # $ grunt view
   grunt.registerTask "view", ["open", "server"]


Update _test.coffee

Update _test.coffee


Rewrite weird construct

Rewrite weird construct

--- app/views/home-page-view.coffee
+++ app/views/home-page-view.coffee
@@ -29,9 +29,9 @@
     @mapInitialEntitySpawnManager.spawn()

     for entity in mediator.entities
-      continue if entity.tileSet.name isnt "Player"
-      @player = entity
-      break
+      if entity.tileSet.name is "Player"
+        @player = entity
+        break

     @soundManager = new SoundManager()
     @soundManager.update(@player.position)


Gradually reducing the initial size of the highlight arrow as the player levels up.

--- app/views/kinds/CocoView.coffee
+++ app/views/kinds/CocoView.coffee
@@ -341,10 +341,11 @@

     @pointerRadialDistance = -47
     @pointerRotation = options.rotation ? Math.atan2(@$el.outerWidth() * 0.5 - targetLeft, targetTop - @$el.outerHeight() * 0.5)
+    initialScale = Math.max 1, 20 - me.level()
     $pointer.css
       opacity: 1.0
       transition: 'none'
-      transform: "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px) scale(20)"
+      transform: "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px) scale(#{initialScale})"
       top: targetTop - 50
       left: targetLeft - 50
     _.defer =>


printing -- hide button except for sagews (for now)

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -1210,6 +1210,10 @@
             e.data('name', name).tooltip(delay:{ show: 500, hide: 100 }).click (event) ->
                 that.click_edit_button($(@).data('name'))
                 return false
+
+        # TODO: implement printing for other file types
+        if @filename.slice(@filename.length-7) != '.sagews'
+            @element.find("a[href=#print]").hide()

     click_edit_button: (name) =>
         cm = @codemirror_with_last_focus


Log save enable/disable

Log save enable/disable

--- areas/admin/client/admin.coffee
+++ areas/admin/client/admin.coffee
@@ -1,2 +1,7 @@
 Template.enableLogs.helpers
-  isEnabled: -> Logger.isEnabled+  isEnabled: -> Logger.isEnabled
+
+Template.enableLogs.events
+  "click #toggleLogs": ->
+     if Logger.isEnabled then Logger.disable()
+     else Logger.enable()


add new menu option to spec fixture

add new menu option to spec fixture

--- tutor/specs/components/navbar/spec-test-params.coffee
+++ tutor/specs/components/navbar/spec-test-params.coffee
@@ -27,6 +27,13 @@
     params: {courseId: '1'}
     label: 'Change Student ID'
   }
+  # FIXME: This should not be here, but it needs to be present
+  # so specs pass until BE gives us a way to tell if a user
+  # is a verififed instructor
+  {
+    name: 'createNewCourse'
+    label: 'Add New Course'
+  }
 ]

 TEACHER_MENU = [
@@ -51,6 +58,10 @@
   {
     name: 'courseSettings'
     label: 'Course Settings and Roster'
+  }
+  {
+    name: 'createNewCourse'
+    label: 'Add New Course'
   }
 ]


Match in lowercase

Match in lowercase

--- scripts/hot-topics.coffee
+++ scripts/hot-topics.coffee
@@ -27,6 +27,6 @@
   robot.hear /(.+)/i, (msg) ->
     text = msg.message.text
     unless text.match 'hot topic'
-      memories = for phrase, memory of robot.brain.data.hot_topics when text.match(phrase)
+      memories = for phrase, memory of robot.brain.data.hot_topics when text.match(phrase.toLowerCase())
         memory
       msg.send memories.join("\n")


added radix to parseInt() calls and make them fail fast (#806)

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -13,9 +13,13 @@
 			opt = JSON.parse(option)
 			return opt
 		catch err
-			console.error "problem parsing #{option}, invalid JSON"
-			return undefined
-
+			throw new Error("problem parsing #{option}, invalid JSON")
+
+parseIntOrFail = (value)->
+	parsedValue = parseInt(value, 10)
+	if isNaN(parsedValue)
+		throw new Error("'#{value}' is an invalid integer")
+	return parsedValue

 DATA_DIR = '/var/lib/sharelatex/data'
 TMP_DIR = '/var/lib/sharelatex/tmp'
@@ -213,7 +217,7 @@
 		collaborators: -1
 		dropbox: true
 		versioning: true
-		compileTimeout: parseInt(process.env["COMPILE_TIMEOUT"] or 180, 10)
+		compileTimeout: parseIntOrFail(process.env["COMPILE_TIMEOUT"] or 180)
 		compileGroup: "standard"
 		trackChanges: true
 		templates: true
@@ -379,7 +383,7 @@
 			timeout: (
 				if _ldap_timeout = process.env["SHARELATEX_LDAP_TIMEOUT"]
 					try
-						parseInt(_ldap_timeout)
+						parseIntOrFail(_ldap_timeout)
 					catch e
 						console.error "Cannot parse SHARELATEX_LDAP_TIMEOUT"
 				else
@@ -388,7 +392,7 @@
 			connectTimeout: (
 				if _ldap_connect_timeout = process.env["SHARELATEX_LDAP_CONNECT_TIMEOUT"]
 					try
-						parseInt(_ldap_connect_timeout)
+						parseIntOrFail(_ldap_connect_timeout)
 					catch e
 						console.error "Cannot parse SHARELATEX_LDAP_CONNECT_TIMEOUT"
 				else
@@ -446,7 +450,7 @@
 			acceptedClockSkewMs: (
 				if _saml_skew = process.env["SHARELATEX_SAML_ACCEPTED_CLOCK_SKEW_MS"]
 					try
-						parseInt(_saml_skew)
+						parseIntOrFail(_saml_skew)
 					catch e
 						console.error "Cannot parse SHARELATEX_SAML_ACCEPTED_CLOCK_SKEW_MS"
 				else
@@ -455,7 +459,7 @@
 			requestIdExpirationPeriodMs: (
 				if _saml_exiration = process.env["SHARELATEX_SAML_REQUEST_ID_EXPIRATION_PERIOD_MS"]
 					try
-						parseInt(_saml_expiration)
+						parseIntOrFail(_saml_expiration)
 					catch e
 						console.error "Cannot parse SHARELATEX_SAML_REQUEST_ID_EXPIRATION_PERIOD_MS"
 				else


Added location to shop model

Added location to shop model

--- server/models/shop.coffee
+++ server/models/shop.coffee
@@ -13,6 +13,13 @@
     type: String, default: '', trim: true
   password:
     type: String, default: '', trim: true
+  loc:
+    latitude:
+      type: Number, default: 0.0
+    longitude:
+      type: Number, default: 0.0
+
+ShopSchema.index {loc: "2d"}

 mongoose.model "Shop", ShopSchema


Rotation, made all times work in milliseconds

--- src/animation.coffee
+++ src/animation.coffee
@@ -30,7 +30,7 @@
 	AnimationProperties: [
 		"view", "properties", "curve", "time",
 		"origin", "tolerance", "precision", "modifiers"
-		"debug", "profile"
+		"debug", "profile", "callback"
 	]
 	AnimatableCSSProperties: {
 		opacity: "",
@@ -40,7 +40,7 @@
 	AnimatableMatrixProperties: [
 		"x", "y", "z",
 		"scaleX", "scaleY", "scaleZ", # "scale",
-		"rotateX", "rotateY", "rotateZ", # "rotate"
+		"rotationX", "rotationY", "rotationZ", # "rotation"
 	]

 	constructor: (args) ->
@@ -92,7 +92,7 @@
 		# Calculate the curve values

 		@curveValues = @_parseCurve @curve
-		@totalTime = @curveValues.length / @precision
+		@totalTime = (@curveValues.length / @precision) * 1000

 		########################################################
@@ -108,8 +108,8 @@
 			propertiesB.scaleX = propertiesB.scale
 			propertiesB.scaleY = propertiesB.scale

-		if propertiesB.rotate
-			propertiesB.rotateZ = propertiesB.rotate
+		if propertiesB.rotation
+			propertiesB.rotationZ = propertiesB.rotation

 		@propertiesA = {}
 		@propertiesB = {}
@@ -149,7 +149,7 @@
 			#{@keyFrameAnimationCSS}

 			.#{@animationName} {
-				-webkit-animation-duration: #{@totalTime}s;
+				-webkit-animation-duration: #{@totalTime / 1000}s;
 				-webkit-animation-name: #{@animationName};
 				-webkit-animation-timing-function: linear;
 				-webkit-animation-fill-mode: both;
@@ -164,7 +164,7 @@
 		if @debug
 			endTime = new Date().getTime() - startTime
 			console.log "Animation[#{@animationId}].setupTime = #{endTime}ms"
-			console.log "Animation[#{@animationId}].totalTime = #{utils.round @totalTime, 2}ms"
+			console.log "Animation[#{@animationId}].totalTime = #{utils.round @totalTime, 0}ms"

 		console.profileEnd @animationName if @profile

@@ -242,6 +242,7 @@
 		@view._matrix = endMatrix
 		@view.style = endStyles

+		@callback? @
 		@emit "end"

 	_css: ->


Removed console.log

Removed console.log

--- src/api/tasks.coffee
+++ src/api/tasks.coffee
@@ -68,8 +68,6 @@
     # exclude transactions object from tasks list
     projectionFiltersObject = { 'transactions': 0 }

-    console.log( filters )
-
     this.body = yield Task.find({}).exec()

     # execute the query


Clampable#clampToBounds

Clampable#clampToBounds

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -16,5 +16,6 @@
       x:
         min: bounds.x + I.width/2
         max: bounds.width - I.width/2
-
+      y:
+        min: bounds.y


simple open with dumb test

simple open with dumb test

--- src/cli/index.coffee
+++ src/cli/index.coffee
@@ -27,6 +27,7 @@
     installPow: require './commands/install_pow'
     uninstallPow: require './commands/uninstall_pow'
     status: require './commands/status'
+    open: require './commands/open'

   run: (argv) ->
     argv = optimist.parse argv
@@ -77,4 +78,7 @@
     if 'status' in argv._
       return @commands.status()

+    if 'open' in argv._
+      return @commands.open()
+
     return @commands.help()


modified lines that were causing parse errors

--- vendor/assets/javascripts/simple_datatables.js.coffee
+++ vendor/assets/javascripts/simple_datatables.js.coffee
@@ -68,11 +68,7 @@
   );

   if sSearch
-    op =
-    if bRegex
-      "_contains"
-    else
-      "_sw"
+    op = bRegex ? "_contains" : "_sw"
     data.push({name: "search["+searchcolumns.join("_or_")+op+"]", value: sSearch});

   $.ajax( { "dataType": 'json', "type": "GET", "url": sSource, "data": data, "success": fnCallback } );


added support for SVG 1.1 tags (see #107)

--- src/coffeekup.coffee
+++ src/coffeekup.coffee
@@ -1,10 +1,10 @@
 # **CoffeeKup** lets you to write HTML templates in 100% pure
 # [CoffeeScript](http://coffeescript.org).
-#
+#
 # You can run it on [node.js](http://nodejs.org) or the browser, or compile your
 # templates down to self-contained javascript functions, that will take in data
 # and options and return generated HTML on any JS runtime.
-#
+#
 # The concept is directly stolen from the amazing
 # [Markaby](http://markaby.rubyforge.org/) by Tim Fletcher and why the lucky
 # stiff.
@@ -65,6 +65,19 @@
  select small span strong style sub summary sup table tbody td textarea tfoot
  th thead time title tr u ul video'

+ # Support for SVG 1.1 tags
+  svg: 'a altGlyph altGlyphDef altGlyphItem animate animateColor animateMotion
+ animateTransform circle clipPath color-profile cursor defs desc ellipse
+ feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix
+ feDiffuseLighting feDisplacementMap feDistantLight feFlood feFuncA feFuncB
+ feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology
+ feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence
+ filter font font-face font-face-format font-face-name font-face-src
+ font-face-uri foreignObject g glyph glyphRef hkern image line linearGradient
+ marker mask metadata missing-glyph mpath path pattern polygon polyline
+ radialGradient rect script set stop style svg switch symbol text textPath
+ title tref tspan use view vkern'
+
   # Valid self-closing HTML 5 elements.
   void: 'area base br col command embed hr img input keygen link meta param
  source track wbr'
@@ -86,14 +99,15 @@
 # Public/customizable list of possible elements.
 # For each name in this list that is also present in the input template code,
 # a function with the same name will be added to the compiled template.
-coffeekup.tags = merge_elements 'regular', 'obsolete', 'void', 'obsolete_void'
+coffeekup.tags = merge_elements 'regular', 'obsolete', 'void', 'obsolete_void',
+  'svg'

 # Public/customizable list of elements that should be rendered self-closed.
 coffeekup.self_closing = merge_elements 'void', 'obsolete_void'

 # This is the basic material from which compiled templates will be formed.
 # It will be manipulated in its string form at the `coffeekup.compile` function
-# to generate the final template function.
+# to generate the final template function.
 skeleton = (data = {}) ->
   # Whether to generate formatted HTML with indentation and line breaks, or
   # just the natural "faux-minified" output.
@@ -106,7 +120,7 @@
   # Internal CoffeeKup stuff.
   __ck =
     buffer: []
-
+
     esc: (txt) ->
       if data.autoescape then h(txt) else String(txt)

@@ -124,15 +138,15 @@

     render_idclass: (str) ->
       classes = []
-
+
       for i in str.split '.'
         if '#' in i
           id = i.replace '#', ''
         else
           classes.push i unless i is ''
-
+
       text " id=\"#{id}\"" if id
-
+
       if classes.length > 0
         text " class=\""
         for c in classes
@@ -144,7 +158,7 @@
       for k, v of obj
         # `true` is rendered as `selected="selected"`.
         v = k if typeof v is 'boolean' and v
-
+
         # Functions are rendered in an executable form.
         v = "(#{v}).call(this);" if typeof v is 'function'

@@ -174,22 +188,22 @@

     render_tag: (name, idclass, attrs, contents) ->
       @indent()
-
+
       text "<#{name}"
       @render_idclass(idclass) if idclass
       @render_attrs(attrs) if attrs
-
+
       if name in @self_closing
         text ' />'
         text '\n' if data.format
       else
         text '>'
-
+
         @render_contents(contents)

         text "</#{name}>"
         text '\n' if data.format
-
+
       null

   tag = (name, args...) ->
@@ -225,11 +239,11 @@
       .replace(/</g, '<')
       .replace(/>/g, '>')
       .replace(/"/g, '"')
-
+
   doctype = (type = 'default') ->
     text __ck.doctypes[type]
     text '\n' if data.format
-
+
   text = (txt) ->
     __ck.buffer.push String(txt)
     null
@@ -237,7 +251,7 @@
   comment = (cmt) ->
     text "<!--#{cmt}-->"
     text '\n' if data.format
-
+
   coffeescript = (param) ->
     switch typeof param
       # `coffeescript -> alert 'hi'` becomes:
@@ -253,11 +267,11 @@
       when 'object'
         param.type = 'text/coffeescript'
         script param
-
+
   # Conditional IE comments.
   ie = (condition, contents) ->
     __ck.indent()
-
+
     text "<!--[if #{condition}]>"
     __ck.render_contents(contents)
     text "<![endif]-->"
@@ -284,9 +298,9 @@

   # If an object `hardcode` is provided, insert the stringified value
   # of each variable directly in the function body. This is a less flexible but
-  # faster alternative to the standard method of using `with` (see below).
+  # faster alternative to the standard method of using `with` (see below).
   hardcoded_locals = ''
-
+
   if options.hardcode
     for k, v of options.hardcode
       if typeof v is 'function'
@@ -298,11 +312,11 @@
   # all hundred-odd tags wasting space in the compiled function.
   tag_functions = ''
   tags_used = []
-
+
   for t in coffeekup.tags
     if template.indexOf(t) > -1 or hardcoded_locals.indexOf(t) > -1
       tags_used.push t
-
+
   tag_functions += "var #{tags_used.join ','};"
   for t in tags_used
     tag_functions += "#{t} = function(){return __ck.tag('#{t}', arguments);};"
@@ -320,16 +334,16 @@
   code += "(#{template}).call(data);"
   code += '}' if options.locals
   code += "return __ck.buffer.join('');"
-
+
   new Function('data', code)

 cache = {}

 # Template in, HTML out. Accepts functions or strings as does `coffeekup.compile`.
-#
+#
 # Accepts an option `cache`, by default `false`. If set to `false` templates will
 # be recompiled each time.
-#
+#
 # `options` is just a convenience parameter to pass options separately from the
 # data, but the two will be merged and passed down to the compiler (which uses
 # `locals` and `hardcode`), and the template (which understands `locals`, `format`
@@ -348,24 +362,24 @@
     # Legacy adapters for when CoffeeKup expected data in the `context` attribute.
     simple: coffeekup.render
     meryl: coffeekup.render
-
+
     express:
       TemplateError: class extends Error
         constructor: (@message) ->
           Error.call this, @message
           Error.captureStackTrace this, arguments.callee
         name: 'TemplateError'
-
-      compile: (template, data) ->
+
+      compile: (template, data) ->
         # Allows `partial 'foo'` instead of `text @partial 'foo'`.
         data.hardcode ?= {}
         data.hardcode.partial = ->
           text @partial.apply @, arguments
-
+
         TemplateError = @TemplateError
         try tpl = coffeekup.compile(template, data)
         catch e then throw new TemplateError "Error compiling #{data.filename}: #{e.message}"
-
+
         return ->
           try tpl arguments...
-          catch e then throw new TemplateError "Error rendering #{data.filename}: #{e.message}"+          catch e then throw new TemplateError "Error rendering #{data.filename}: #{e.message}"


collision

collision

--- src/collision.coffee
+++ src/collision.coffee
@@ -8,6 +8,24 @@
   ###*
   Takes two bounds objects and returns true if they collide (overlap), false otherwise.
   Bounds objects have x, y, width and height properties.
+
+

+     player = GameObject
+       x: 0
+       y: 0
+       width: 10
+       height: 10
+
+     enemy = GameObject
+       x: 5
+       y: 5
+       width: 10
+       height: 10
+
+     Collision.rectangular(player, enemy)
+  => true
+
+

   @name rectangular
   @methodOf Collision


Compute tally job.

Compute tally job.

--- job.coffee
+++ job.coffee
@@ -86,6 +86,9 @@
     @name

   @register: (jobClass) ->
+    # To allow calling @register() from inside a class body.
+    jobClass ?= @
+
     throw new Error "Job class '#{jobClass.name}' is not a subclass of Job class." unless jobClass.prototype instanceof Job
     throw new Error "Job class '#{jobClass.type()}' already exists" if jobClass.type() of @types


Fixed combineCanvases

Fixed combineCanvases

--- src/core/util.coffee
+++ src/core/util.coffee
@@ -22,7 +22,7 @@
     resize()

   combineCanvases: (a, b) ->
-    c = document.getElementsByTagName('canvas')[0]
+    c = document.createElement('canvas')
     c.width = Math.max(a.width, b.width)
     c.height = Math.max(a.height, b.height)
     ctx = c.getContext('2d')


bugfix : attachment requests didn't have authorization header

--- src/cozymodel.coffee
+++ src/cozymodel.coffee
@@ -129,9 +129,10 @@
     attach: (id, path, data, callback) ->
         [data, callback] = [null, data] if typeof(data) is "function"
         urlPath = "data/#{id}/attachments/"
-        client.sendFile urlPath, path, data, (error, response, body) ->
+        request = client.sendFile urlPath, path, data, (error, response, body) ->
             try body = JSON.parse(body)
             checkError error, response, body, 201, callback
+        request.setHeader 'Authorization', client.headers.authorization

     get: (id, filename, callback) ->
         urlPath = "data/#{id}/attachments/#{filename}"
@@ -149,9 +150,10 @@
     attach: (id, path, data, callback) ->
         [data, callback] = [null, data] if typeof(data) is "function"
         urlPath = "data/#{id}/binaries/"
-        client.sendFile urlPath, path, data, (error, response, body) ->
+        request = client.sendFile urlPath, path, data, (error, response, body) ->
             try body = JSON.parse(body)
             checkError error, response, body, 201, callback
+        request.setHeader 'Authorization', client.headers.authorization

     get: (id, filename, callback) ->
         urlPath = "data/#{id}/binaries/#{filename}"


Fix arXiv importing

Fix arXiv importing

--- server/manage.coffee
+++ server/manage.coffee
@@ -258,7 +258,7 @@
         #msc2010: record['msc-class']?[0].split(/\s*[,;]\s*|\s*\([^)]*\)\s*|\s+/).filter (x) -> x
         # Converts strings like "F.2.2; I.2.7" into ["F.2.2","I.2.7"]
         #acm1998: record['acm-class']?[0].split(/\s*[,;]\s*|\s+/).filter (x) -> x
-        foreignId: record._id[0]
+        foreignId: record.id[0]
         # TODO: Put foreign categories into tags?
         foreignCategories: record.categories[0].split /\s+/
         foreignJournalReference: record['journal-ref']?[0]


More accurate context binding

More accurate context binding

--- src/bodyguard.coffee
+++ src/bodyguard.coffee
@@ -15,7 +15,7 @@
         else
           unless bodyguard.silent
             console?.warn?("Method #{method} is not implemented.")
-          returnFunction::[method] = noop
+          returnFunction::[method] = -> bind obj[method], @

     returnFunction


fix error logging

fix error logging

--- server/router.coffee
+++ server/router.coffee
@@ -483,11 +483,15 @@
             console.log value
             console.log children
             console.log "---"
-            console.log locToMaxSubIntervals[value.id]
-            console.log subIntervalsToActiveCases(locToMaxSubIntervals[value.id], dailyDecayRate)
+            console.log locToMaxSubIntervals[value.id].length
+            console.log subIntervalsToActiveCases(
+              locToMaxSubIntervals[value.id], dailyDecayRate, dateWindow
+            ).slice(-5)
             console.log "---"
-            console.log locToMaxSubIntervals[children[0].location.id]
-            console.log subIntervalsToActiveCases(locToMaxSubIntervals[children[0].location.id], dailyDecayRate)
+            console.log locToMaxSubIntervals[children[0].location.id].length
+            console.log subIntervalsToActiveCases(
+              locToMaxSubIntervals[children[0].location.id], dailyDecayRate, dateWindow
+            ).slice(-5)
             throw new Error("Invalid Tree")
           return {
             location: value


register device fallback fix

register device fallback fix

--- src/bootstrap.coffee
+++ src/bootstrap.coffee
@@ -13,12 +13,13 @@
 PLATFORM_ENDPOINT = url.resolve(config.apiEndpoint, '/ewa/')
 resinAPI = new PlatformAPI(PLATFORM_ENDPOINT)

-registerDevice = (apiKey, userId, applicationId, deviceType) ->
+registerDevice = (apiKey, userId, applicationId, deviceType, uuid) ->
 	# I'd be nice if the UUID matched the output of a SHA-256 function, but although the length limit of the CN
 	# attribute in a X.509 certificate is 64 chars, a 32 byte UUID (64 chars in hex) doesn't pass the certificate
 	# validation in OpenVPN This either means that the RFC counts a final NULL byte as part of the CN or that the
 	# OpenVPN/OpenSSL implementation has a bug.
-	uuid = crypto.pseudoRandomBytes(31).toString('hex')
+	if not uuid?
+		uuid = crypto.pseudoRandomBytes(31).toString('hex')

 	resinAPI.post(
 		resource: 'device'
@@ -38,9 +39,9 @@
 	userConfig.deviceType ?= 'raspberry-pi'

 	Promise.try ->
-		if userConfig.uuid?
+		if userConfig.uuid? and userConfig.registered_at?
 			return userConfig.uuid
-		registerDevice(userConfig.apiKey, userConfig.userId, userConfig.applicationId, userConfig.deviceType)
+		registerDevice(userConfig.apiKey, userConfig.userId, userConfig.applicationId, userConfig.deviceType, userConfig.uuid)
 		.tap (uuid) ->
 			userConfig.uuid = uuid
 	.then (uuid) ->


Style fixes

Style fixes

--- src/bootstrap.coffee
+++ src/bootstrap.coffee
@@ -75,13 +75,13 @@
 			knex('config').truncate()
 			.then ->
 				userConfig
-				.then (userConfig) ->
-					knex('config').insert([
-						{key: 'uuid', value: uuid}
-						{key: 'apiKey', value: userConfig.apiKey}
-						{key: 'username', value: userConfig.username}
-						{key: 'userId', value: userConfig.userId}
-						{key: 'version', value: version}
-					])
+			.then (userConfig) ->
+				knex('config').insert([
+					{ key: 'uuid', value: uuid }
+					{ key: 'apiKey', value: userConfig.apiKey }
+					{ key: 'username', value: userConfig.username }
+					{ key: 'userId', value: userConfig.userId }
+					{ key: 'version', value: version }
+				])
 			knex('app').truncate()
 		])


feat(versions): add extra check

feat(versions): add extra check

check for hubot-[packagename] if the package is not found.

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -15,13 +15,19 @@
     robot.hear /\bodds (on|of|for)\b|\bwhat are the (chances|odds)\b|\bknow the (chances|odds)\b/i, (res) ->
         res.send res.random fiftyfifty

-    robot.respond /version (.*)/i, (res) ->
-        pkg = res.match[1]
+    robot.respond /version( (.*)|)/i, (res) ->
+        pkg = res.match[2]
         if pkg
             jsonfile.readFile 'node_modules/' + pkg + '/package.json', (err, obj) ->
                 if err
-                    res.send 'Could not find that package'
-                    console.log err
+                    jsonfile.readFile 'node_modules/hubot-' + pkg + '/package.json', (err, obj) ->
+                        if err
+                            res.send 'Could not find that package'
+                            console.log err
+                            return
+                        version = obj.version
+                        if version
+                            res.send pkg + ' is at version ' + version
                     return
                 version = obj.version
                 if version


scripts/meeting.coffee: don't mention to all in #general.

--- scripts/meeting.coffee
+++ scripts/meeting.coffee
@@ -8,7 +8,7 @@
 module.exports = (robot) ->
   new cron "0 10 10 * * 1-5", () ->
     if !(holiday.isHoliday(new Date()))
-      robot.send {room: "general"}, "<!channel> そろそろ朝会の時間ですよ"
+      robot.send {room: "general"}, "そろそろ朝会の時間ですよ"
     else
       date = new Date()
       d = holiday.between(date, date)[0]


Fix [].lastIndexOf and [].indexOf snippets. Sort all array snippets


Fix contacts drag'n'drop, refs #281

Fix contacts drag'n'drop, refs #281

--- client/app/components/mails_input.coffee
+++ client/app/components/mails_input.coffee
@@ -56,14 +56,16 @@
                     return a.address isnt address.address
                 @props.valueLink.requestChange known

-            onDragStart = (event) ->
+            onDragStart = (event) =>
                 event.stopPropagation()
-                data =
-                    name: address.name
-                    address: address.address
-                event.dataTransfer.setData 'text', JSON.stringify(data)
-                event.dataTransfer.effectAllowed = 'move'
-                event.dataTransfer.dropEffect = 'move'
+                if address?
+                    data =
+                        name: address.name
+                        address: address.address
+                    event.dataTransfer.setData 'address', JSON.stringify(data)
+                    event.dataTransfer.effectAllowed = 'all'
+                    # needed to prevent droping over drag source
+                    event.dataTransfer.setData @props.id, true

             onDragEnd = (event) ->
                 if event.dataTransfer.dropEffect is 'move'
@@ -91,8 +93,9 @@
         onChange = (event) =>
             value = event.target.value.split ','
             if value.length is 2
-                @state.known.push(MessageUtils.parseAddress value[0])
-                @props.valueLink.requestChange @state.known
+                known = _.clone @state.known
+                known.push(MessageUtils.parseAddress value[0])
+                @props.valueLink.requestChange known
                 @setState unknown: value[1].trim()
             else
                 @setState unknown: event.target.value
@@ -104,9 +107,18 @@
             open: @state.open and @state.contacts?.length > 0
         current    = 0

-        # inChrome, we need to cancel some events for drop to work
-        cancelDragEvent = (event) ->
+        # in Chrome, we need to cancel some events for drop to work
+        cancelDragEvent = (event) =>
             event.preventDefault()
+            # To prevent removing the contact when dropped where it has been dragged,
+            # we must set dropEffect to 'none'
+            # if Chrome, we can only access types of data, not data themselves
+            # In Chrome, types are an array; in Firefox, a DOMStringList
+            types = Array.prototype.slice.call(event.dataTransfer.types)
+            if types.indexOf(@props.id) is -1
+                event.dataTransfer.dropEffect = 'move'
+            else
+                event.dataTransfer.dropEffect = 'none'

         div
             className: className,
@@ -259,8 +271,9 @@
         address =
             name    : contact.get 'fn'
             address : contact.get 'address'
-        @state.known.push address
-        @props.valueLink.requestChange @state.known
+        known = _.clone @state.known
+        known.push address
+        @props.valueLink.requestChange known
         @setState unknown: '', contacts: null, open: false

         # try to put back the focus at the end of the field
@@ -276,10 +289,17 @@
     onDrop: (event) ->
         event.preventDefault()
         event.stopPropagation()
-        {name, address} = JSON.parse(event.dataTransfer.getData 'text')
-        address =
-            name    : name
-            address : address
-        @state.known.push address
-        @props.valueLink.requestChange @state.known
-        @setState unknown: '', contacts: null, open: false
+        {name, address} = JSON.parse(event.dataTransfer.getData 'address')
+        exists = @state.known.some (item) ->
+            return item.name is name and item.address is address
+        if address? and not exists
+            address =
+                name    : name
+                address : address
+            known = _.clone @state.known
+            known.push address
+            @props.valueLink.requestChange known
+            @setState unknown: '', contacts: null, open: false
+            event.dataTransfer.dropEffect = 'move'
+        else
+            event.dataTransfer.dropEffect = 'none'


Debounce calling of callback methods to not overwhelm e.g. remote server while zooming

Signed-off-by: Marko Locher <9efe9a5a1da5f41a4eb7599f2715dc24abf5bbc8@locher.at>

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -26,6 +26,16 @@

         # array to hold individual data points / data ranges
         @data = {}
+
+        # debounce function for rate limiting
+        @timeouts = []
+        debounce = (timeout, id, fn) =>
+            return unless timeout and id and fn
+            @timeouts[id] = -1 unless @timeouts[id]
+
+            return =>
+                window.clearTimeout(@timeouts[id]) if @timeouts[id] > -1
+                @timeouts[id] = window.setTimeout(fn, timeout)

         # create a custom formatter for labeling ticks
         customFormatter = (formats) =>
@@ -133,49 +143,52 @@
             @updateDataset(dataset.id)

         @updateDataset = (dataset) =>
-            @data[dataset].callback(@scales.x.domain()[0], @scales.x.domain()[1], (id, data) =>
-                el = @svg.select("g.datasets #dataset-#{id}")
-                d = @data[id]
-
-                d.ranges = []
-                d.points = []
-
-                for element in data
-                    if(element.length > 1)
-                        d.ranges.push(element)
-                    else
-                        d.points.push(element)
-
-                # ranges
-                el.selectAll('path').remove()
-                r = el.selectAll('path')
-                    .data(d.ranges)
-
-                r.enter().append('path')
-                    .attr('d',
-                        d3.svg.line()
-                            .x( (d) => @scales.x(d) )
-                            .y( -5 * d.index )
-                            .interpolate('linear')
-                        )
-                    .attr('stroke', d.color)
-
-                r.exit().remove()
-
-                # points
-                el.selectAll('circle').remove()
-                p = el.selectAll('circle')
-                    .data(d.points)
-                    .remove()
-
-                p.enter().append('circle')
-                        .attr('cx', (d) => @scales.x( new Date(d) ) )
-                        .attr('cy', "#{-5 * d.index}")
-                        .attr('fill', d.color)
-                        .attr('r', 2)
-
-                p.exit().remove()
+            callback = debounce(100, dataset, =>
+                @data[dataset].callback(@scales.x.domain()[0], @scales.x.domain()[1], (id, data) =>
+                    el = @svg.select("g.datasets #dataset-#{id}")
+                    d = @data[id]
+
+                    d.ranges = []
+                    d.points = []
+
+                    for element in data
+                        if(element.length > 1)
+                            d.ranges.push(element)
+                        else
+                            d.points.push(element)
+
+                    # ranges
+                    el.selectAll('path').remove()
+                    r = el.selectAll('path')
+                        .data(d.ranges)
+
+                    r.enter().append('path')
+                        .attr('d',
+                            d3.svg.line()
+                                .x( (d) => @scales.x(d) )
+                                .y( -5 * d.index )
+                                .interpolate('linear')
+                            )
+                        .attr('stroke', d.color)
+
+                    r.exit().remove()
+
+                    # points
+                    el.selectAll('circle').remove()
+                    p = el.selectAll('circle')
+                        .data(d.points)
+                        .remove()
+
+                    p.enter().append('circle')
+                            .attr('cx', (d) => @scales.x( new Date(d) ) )
+                            .attr('cy', "#{-5 * d.index}")
+                            .attr('fill', d.color)
+                            .attr('r', 2)
+
+                    p.exit().remove()
+                )
             )
+            callback()

         for dataset in @options.datasets
             do (dataset) => @drawDataset(dataset)


Implemented the distance option. You can now set distance so that moving the mouse wont immediately trigger drag, but wait the threshold distance in pixels

--- src/draggable.coffee
+++ src/draggable.coffee
@@ -109,6 +109,10 @@
       # * { top: …, right: …, bottom: …, left: … }: an object specifying a waypoint from a draggable's edge, the nearest of which should snap to the cursor when the draggable is dragged
       cursorAt: false

+      # Distance option:
+      # * The distance in pixels that the mouse needs to move before drag is initiated
+      distance: false
+
     #
     # Initialization
     #
@@ -190,6 +194,17 @@
         # Trigger the drag event
         @handleDrag(e)
       else
+        thresholdDistance = @getConfig().distance
+        if thresholdDistance?
+          # Find the actual distance travelled by the mouse
+          deltaX = e.clientX - @mousedownEvent.clientX
+          deltaY = e.clientY - @mousedownEvent.clientY
+
+          distanceMoved = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2))
+
+          # Prevent the drag start if the distance moved is less than the threshold distance
+          return true if distanceMoved < thresholdDistance
+
         # Trigger the start event
         @handleDragStart(e)


Draw flipper

Draw flipper

--- src/draw_grid.coffee
+++ src/draw_grid.coffee
@@ -39,14 +39,15 @@
     ]
   draw_arc = (width, x_sign, y_sign) ->
     "a#{width},#{width} 0 0,1 #{x_sign*width},#{y_sign*width} "
-
+  draw_flipper = (width, x_sign, y_sign) ->
+    draw_arc(width, x_sign, y_sign) + draw_arc(width, -1*x_sign, -1*y_sign)
+
   draw_turtle_path = (i, j) ->
     width = cell_size / 2
     x = i*cell_size
     y = j*cell_size
     path = "M#{x},#{y} "
-    path += draw_arc(width,  1, 1)
-    path += draw_arc(width, -1,-1)
+    path += draw_flipper(width,  1, 1)
     S.path {class: "path-turtle", d: path, stroke: "#00ff00", fill: "#88ff88"}

   grid_path = ""


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -8,9 +8,7 @@

   self.bind "update", ->
     if I.fadeOut
-      log I.age
-      log I.duration
-      log (I.age + 1 / I.duration)
+
       I.alpha = 1 - ((I.age + 1) / I.duration)

     if I.duration != -1 && I.age + 1 >= I.duration


add cruser to models

add cruser to models

--- components/backend/utilities/crud.coffee
+++ components/backend/utilities/crud.coffee
@@ -6,6 +6,7 @@
   # crud
   app.post '/'+config.urlRoot, auth, (req, res)->
     schema = new Schema
+    schema.cruser = app.user._id
     schema.user = app.user._id
     schema.crdate = new Date()
     schema.date = new Date()
@@ -25,6 +26,7 @@
     Schema.findById req.params.id, (e, schema)->
       app.emit config.moduleName+':before:put', req, res, schema
       schema.date = new Date()
+      schema.user = app.user._id
       schema.fields = req.body.fields
       schema.published = req.body.published
       schema.save ->


Support parentheses in street names.

Support parentheses in street names.

--- src/geocoding.coffee
+++ src/geocoding.coffee
@@ -112,7 +112,10 @@
                         deferred.resolve()

         addressSource: (query, callback) =>
-            re = new RegExp "^\\s*#{@street.translatedName}(\\s+\\d.*)?", 'i'
+            # escape parentheses for regexp
+            streetName = @street.translatedName
+                .replace /([()])/g, '\\$1'
+            re = new RegExp "^\\s*#{streetName}(\\s+\\d.*)?", 'i'
             matches = query.match re
             if matches?
                 [q, numberPart] = matches


Added a --classpath option to the runner. Hopefully I did this in an OK manner.

--- console/runner.coffee
+++ console/runner.coffee
@@ -6,12 +6,13 @@
 ClassFile = require '../src/class_file'
 methods = require '../src/methods'

+classpath = [ ".", "#{__dirname}/../third_party/classes" ]
+
 exports.read_binary_file = (filename) ->
   return null unless path.existsSync filename
   util.bytestr_to_array fs.readFileSync(filename, 'binary')

 exports.read_classfile = (cls) ->
-  classpath = [ ".", "#{__dirname}/../third_party/classes" ]
   for p in classpath
     data = exports.read_binary_file "#{p}/#{cls}.class"
     return new ClassFile data if data?
@@ -21,7 +22,7 @@
   {argv} = optimist

   optimist.usage '''
-  Usage: $0 /path/to/classfile
+  Usage: $0 [--classpath path1:...:pathn] /path/to/classfile
     --java=[args for JVM]
     [--log=[0-10]|debug|error]
     [--profile]
@@ -33,6 +34,10 @@
     util.log_level = util[argv.log.toUpperCase()]
   else if argv.log? # assume a number
     util.log_level = argv.log + 0
+
+  if argv.classpath?
+    classpath = argv.classpath.split(":")
+    classpath.push("#{__dirname}/../third_party/classes")

   cname = argv._[0]
   cname = cname[0...-6] if cname[-6..] is '.class'
@@ -46,7 +51,7 @@
       resume data

   rs = new runtime.RuntimeState(stdout, read_stdin, exports.read_classfile)
-
+
   if argv.profile
     timings = {}
     call_counts = {}


Add current directory to the classpath when running from console.

This allows us to obtain nested classes / classes from multi-class Java
files.

--- console/runner.coffee
+++ console/runner.coffee
@@ -1,13 +1,20 @@
 #!/usr/bin/env coffee
 fs = require 'fs'
+path = require 'path'
 jvm = require '../src/jvm'
 util = require '../src/util'
 ClassFile = require '../src/class_file'

 read_binary_file = (filename) ->
+  return null unless path.existsSync filename
   util.bytestr_to_array fs.readFileSync(filename, 'binary')

-read_classfile = (cls) -> read_binary_file "third_party/classes/#{cls}.class"
+read_classfile = (cls) ->
+  classpath = [ "third_party/classes" ]
+  classpath.push(path.dirname process.argv[2]) if process.argv[2]?
+  for p in classpath
+    data = read_binary_file "#{p}/#{cls}.class"
+    return data if data?

 # first two are 'coffee', 'scriptname.coffee'
 fname = if process.argv.length > 2 then process.argv[2] else '/dev/stdin'


Environment: DiaObject heights/spacing fixed

--- client/Environments/views/scene/environmentcontainer.coffee
+++ client/Environments/views/scene/environmentcontainer.coffee
@@ -30,6 +30,7 @@
       cssClass : 'add-button'
       callback : => @emit 'PlusButtonClicked'
     @addSubView @addButton
+    @addButton.setY 54

     {@appStorage} = @parent

@@ -42,7 +43,7 @@
     @loader.hide()

   addDia:(diaObj, pos)->
-    pos = x: 20, y: 68 + @diaCount() * (@itemHeight + 20)
+    pos = x: 20, y: 50 + @diaCount() * (@itemHeight + 14)
     super diaObj, pos

     diaObj.on "KDObjectWillBeDestroyed", @bound 'updatePositions'
@@ -54,7 +55,7 @@
     index = 0
     for _key, dia of @dias
       dia.setX 20
-      dia.setY 68 + index * (@itemHeight + 20)
+      dia.setY 50 + index * (@itemHeight + 14)
       index++

     @updateAddButton()
@@ -65,4 +66,4 @@
     @removeAllItems()

   updateAddButton:->
-    @addButton.setY 68 + @diaCount() * (@itemHeight + 20)
+    @addButton.setY 54 + @diaCount() * (@itemHeight + 14)


Stacks: Handle domain and rule dia connection.

--- client/Environments/views/scene/environmentsceneview.coffee
+++ client/Environments/views/scene/environmentsceneview.coffee
@@ -79,8 +79,14 @@
         createConnection()
     else if domain and rule
       createConnection()
+      @bindRuleToDomain domain, rule
     else if vm and extra
       createConnection()
+
+  bindRuleToDomain: (domain, rule) ->
+    {domain} = domain.dia.getData()
+    rule     = rule.dia.getData()
+    log domain, rule

   updateConnections:->
     @reset no


adds constant width custom binding

adds constant width custom binding

--- js/libs/custom-bindings.coffee
+++ js/libs/custom-bindings.coffee
@@ -24,6 +24,20 @@
       mixpanel.track valueAccessor(), {}, ->
         clearTimeout(backup_redirect)
         redirect()
+
+# Takes a css selector, finds all child elements matching that selector and
+# sets the width of all of the elements to the width of the max element
+# Example:
+# %div{data-bind: "foreach: items, equalizeWidth: '.item'"}
+#   .item{data-bind: "text: $data"}
+# Approach here: https://github.com/knockout/knockout/wiki/Bindings%3A-runafter
+ko.bindingHandlers.equalizeWidth =
+  update: (el, valueAccessor) =>
+    setTimeout () ->
+      selector = valueAccessor()
+      els = $(el).find(selector)
+      max = Math.max.apply(null, $.map(els, (e) -> $(e).width()))
+      $.each(els, (i, e) -> $(e).width(max))

 # Takes any kind of jQueryExtension, e.g. popover, tooltip, etc.
 jQueryExt = (type) =>


Updated gTLD list

Updated gTLD list

--- src/domain-me.coffee
+++ src/domain-me.coffee
@@ -15,93 +15,288 @@

 tlds = [
     "academy",
+    "accountants",
+    "active",
+    "actor",
     "agency",
+    "airforce",
+    "army",
+    "associates",
+    "attorney",
+    "auction",
+    "audio",
+    "autos",
+    "band",
     "bargains",
+    "beer",
     "berlin",
+    "best",
+    "bid",
     "bike",
+    "bio",
+    "biz",
+    "black",
+    "blackfriday",
     "blue",
+    "boo",
     "boutique",
     "build",
     "builders",
+    "business",
     "buzz",
     "cab",
     "camera",
     "camp",
+    "cancerresearch",
+    "capital",
+    "cards",
+    "care",
+    "career",
     "careers",
+    "cash",
+    "catering",
     "center",
     "ceo",
+    "channel",
     "cheap",
+    "christmas",
+    "church",
+    "city",
+    "claims",
+    "cleaning",
+    "click",
+    "clinic",
     "clothing",
     "club",
     "codes",
     "coffee",
+    "college",
+    "community",
     "company",
     "computer",
+    "condos",
     "construction",
+    "consulting",
     "contractors",
+    "cooking",
     "cool",
+    "country",
+    "credit",
+    "creditcard",
+    "cricket",
+    "cruises",
+    "dad",
+    "dance",
+    "dating",
+    "day",
+    "deals",
+    "degree",
+    "delivery",
+    "democrat",
+    "dental",
+    "dentist",
     "diamonds",
+    "diet",
+    "digital",
+    "direct",
     "directory",
+    "discount",
     "domains",
+    "eat",
     "education",
     "email",
+    "energy",
+    "engineer",
+    "engineering",
     "enterprises",
     "equipment",
     "estate",
+    "esq",
+    "events",
+    "exchange",
+    "expert",
+    "exposed",
+    "fail",
     "farm",
+    "feedback",
+    "finance",
+    "financial",
+    "fish",
+    "fishing",
+    "fitness",
+    "flights",
     "florist",
+    "fly",
+    "foo",
+    "forsale",
+    "foundation",
+    "fund",
+    "furniture",
+    "futbol",
     "gallery",
     "gift",
+    "gifts",
+    "gives",
     "glass",
+    "global",
+    "gop",
     "graphics",
+    "green",
+    "gripe",
+    "guide",
     "guitars",
     "guru",
+    "healthcare",
+    "help",
+    "here",
+    "hiphop",
+    "hiv",
     "holdings",
     "holiday",
+    "homes",
+    "horse",
+    "host",
+    "hosting",
     "house",
+    "how",
+    "info",
+    "ing",
+    "ink",
+    "insure",
     "institute",
     "international",
+    "investments",
+    "jobs",
     "kim",
     "kitchen",
     "kiwi",
     "land",
+    "laywer",
+    "lease",
+    "lgbt",
+    "life",
     "lighting",
+    "limited",
     "limo",
     "link",
+    "loans",
+    "lotto",
+    "luxe",
+    "luxury",
     "management",
+    "market",
     "marketing",
+    "media",
+    "meet",
+    "meme",
     "menu",
+    "mobi",
+    "moe",
+    "mortgage",
+    "motorcycles",
+    "mov",
+    "museum",
+    "name",
+    "navy",
+    "network",
+    "new",
+    "ngo",
+    "ninja",
+    "ong",
     "onl",
+    "ooo",
+    "organic",
+    "partners",
+    "parts",
+    "party",
+    "pharmacy",
     "photo",
     "photography",
     "photos",
+    "physio",
     "pics",
+    "pictures",
     "pink",
+    "pizza",
+    "place",
     "plumbing",
+    "poker",
+    "post",
+    "press",
+    "pro",
+    "productions",
+    "prof",
+    "properties",
+    "property",
     "qpon",
     "recipes",
     "red",
+    "rehab",
+    "ren",
+    "rentals",
     "repair",
+    "report",
+    "republican",
+    "reviews",
     "rich",
+    "rip",
+    "rocks",
+    "rodeo",
+    "rsvp",
     "ruhr",
+    "science",
+    "services",
     "sexy",
     "shiksha",
     "shoes",
     "singles",
+    "social",
+    "software",
     "solar",
     "solutions",
+    "space",
+    "supplies",
+    "supply",
     "support",
+    "surf",
+    "surgery",
     "systems",
     "tattoo",
     "technology",
+    "tel",
     "tips",
+    "tires",
     "today",
+    "tools",
+    "top",
+    "town",
+    "toys",
+    "trade",
     "training",
+    "travel",
+    "university",
     "uno",
+    "vacations",
     "ventures",
+    "vet",
     "viajes",
+    "villas",
+    "vision",
+    "vodka",
+    "vote",
+    "voting",
     "voyage",
+    "wang",
     "watch",
+    "webcam",
+    "website",
+    "wed",
+    "wiki",
+    "works",
+    "world",
+    "wtf",
+    "xxx",
+    "xyz",
     "zone"
 ]


in/out typo #118

in/out typo #118

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -133,7 +133,7 @@
       process: nodeKey
       port: portKey
       metadata: metadata
-    @emit 'addInport', publicPort, @outports[publicPort]
+    @emit 'addInport', publicPort, @inports[publicPort]
     @checkTransactionEnd()

   removeInport: (publicPort) ->


sane defaults

sane defaults

--- src/3d/Helper.coffee
+++ src/3d/Helper.coffee
@@ -38,7 +38,7 @@
     renderer.shadowMapType = THREE.PCFShadowMap
     renderer.shadowMapAutoUpdate = true

-  @skybox: (imgUrl, radius=1.5, segments=32)->
+  @skybox: (imgUrl, radius=90, segments=64)->
     geom = new (THREE.SphereGeometry)(radius, segments, segments)
     mat = new (THREE.MeshBasicMaterial)(
       map: THREE.ImageUtils.loadTexture(imgUrl)


Fix variable name.

Fix variable name.

--- src/frontend/network.coffee
+++ src/frontend/network.coffee
@@ -134,7 +134,7 @@
                     @createPeerConnection options
                 event = EventManager.getEvent "fillUsersList"
                 if event?
-                    event @peerConnections
+                    event @connections
             when 'peer.create'
                 trace "on peer create"

@@ -227,4 +227,4 @@
 if window?
     window.NetworkManager     = NM
 if exports?
-    exports.NetworkManager    = NM+    exports.NetworkManager    = NM


API: Stop-gap fix for a bizarre bug.

API: Stop-gap fix for a bizarre bug.

--- server/database.coffee
+++ server/database.coffee
@@ -1,3 +1,8 @@
+if process.env.POKEBATTLE_ENV == 'api'
+  # For some reason throwing an error will prevent an error from happening.
+  # If you console.log here, then you can see this module is included twice.
+  throw new Error("API should not connect to DB.")
+
 redis = switch process.env.NODE_ENV
   when 'test' then require 'redis-mock'
   else             require 'redis'


25 milliseconds is little to much to render at once

--- src/dt-jquery.coffee
+++ src/dt-jquery.coffee
@@ -27,7 +27,7 @@
 work_frame_queue = ->
     t1 = t2 = new Date().getTime()
     n = 0
-    while frame_queue.length && t2 - t1 < 25
+    while frame_queue.length && t2 - t1 < 5
         cb = frame_queue.shift()
         cb?()
         n++


Revert "Use collection to retrieve fact in opinionatersevidence"

This reverts commit a054b4d28c1250bba13bb292ff20fb2f0a86126a.

--- core/app/backbone/models/evidence.coffee
+++ core/app/backbone/models/evidence.coffee
@@ -4,8 +4,8 @@

   # TODO: eventually, fetching this model should populate
   #       the collection, not the other way around
-  initialize: ->
-    @_wheel = @collection.fact.getFactWheel()
+  initialize: (attributes, options) ->
+    @_wheel = options.fact.getFactWheel()
     @opinionaters().fetch()

     @_wheel.on 'change', =>


Use PopoverView as tooltip

Use PopoverView as tooltip

--- core/app/backbone/views/fact_view.coffee
+++ core/app/backbone/views/fact_view.coffee
@@ -47,7 +47,8 @@
     @bindTo @model, "change", @render, @

     if FactlinkApp.guided
-      @tooltipAdd '.js-close', new TextView(model: new Backbone.Model(text: "blablabla")), side: 'top', align: 'center'
+      tooltip_view = new PopoverView(model: new Backbone.Model(title: 'Title', text: 'Hi my name is popover.'))
+      @tooltipAdd '.js-close', tooltip_view, side: 'bottom', align: 'left'

   onRender: ->
     @factBaseView.show new FactBaseView(model: @model)


:memo: Doc public API

:memo: Doc public API

Closes #6

--- src/grammar-registry.coffee
+++ src/grammar-registry.coffee
@@ -5,6 +5,7 @@
 Grammar = require './grammar'
 NullGrammar = require './null-grammar'

+# Public: Registry containing one or more grammars.
 module.exports =
 class GrammarRegistry
   Emitter.includeInto(this)
@@ -17,8 +18,42 @@
     @nullGrammar = new NullGrammar(this)
     @addGrammar(@nullGrammar)

-  createToken: (value, scopes) -> {value, scopes}
+  # Public: Get all the grammars in this registry.
+  #
+  # Returns a non-empty {Array} of {Grammar} instances.
+  getGrammars: ->
+    _.clone(@grammars)

+  # Public: Get a grammar with the given scope name.
+  #
+  # * scopeName: A {String} such as `"source.js"`.
+  #
+  # Returns a {Grammar} or undefined.
+  grammarForScopeName: (scopeName) ->
+    @grammarsByScopeName[scopeName]
+
+  # Public: Remove a grammar from this registry.
+  #
+  # * grammar: The {Grammar} to remove.
+  removeGrammar: (grammar) ->
+    _.remove(@grammars, grammar)
+    delete @grammarsByScopeName[grammar.scopeName]
+    _.remove(@injectionGrammars, grammar)
+    @grammarUpdated(grammar.scopeName)
+
+  # Public: Remove the grammar with the given scope name.
+  #
+  # * scopeName: A {String} such as `"source.js"`.
+  removeGrammarForScopeName: (scopeName) ->
+    grammar = @grammarForScopeName(scopeName)
+    @removeGrammar(grammar) if grammar?
+
+  # Public: Add a grammar to this registry.
+  #
+  # A 'grammar-added' event is emitted after the grammar is added.
+  #
+  # * grammar: The {Grammar} to add. This should be a value previously returned
+  #            from {readGrammar} or {readGrammarSync}
   addGrammar: (grammar) ->
     @grammars.push(grammar)
     @grammarsByScopeName[grammar.scopeName] = grammar
@@ -26,31 +61,20 @@
     @grammarUpdated(grammar.scopeName)
     @emit 'grammar-added', grammar

-  removeGrammar: (grammar) ->
-    _.remove(@grammars, grammar)
-    delete @grammarsByScopeName[grammar.scopeName]
-    _.remove(@injectionGrammars, grammar)
-    @grammarUpdated(grammar.scopeName)
-
-  removeGrammarForScopeName: (scopeName) ->
-    grammar = @grammarForScopeName(scopeName)
-    @removeGrammar(grammar) if grammar?
-
-  grammarUpdated: (scopeName) ->
-    for grammar in @grammars when grammar.scopeName isnt scopeName
-      @emit 'grammar-updated', grammar if grammar.grammarUpdated(scopeName)
-
-  grammarForScopeName: (scopeName) ->
-    @grammarsByScopeName[scopeName]
-
-  createGrammar: (grammarPath, object) ->
-    grammar = new Grammar(this, object)
-    grammar.path = grammarPath
-    grammar
-
+  # Public: Read a grammar synchronously but don't add it to the registry.
+  #
+  # * grammarPath: A {String} absolute file path to a grammar file.
+  #
+  # Returns a {Grammar}.
   readGrammarSync: (grammarPath) ->
     @createGrammar(grammarPath, fs.readObjectSync(grammarPath))

+  # Public: Read a grammar asynchronously but don't add it to the registry.
+  #
+  # * grammarPath: A {String} absolute file path to a grammar file.
+  #
+  # * callback: A {Function} to call when loaded with `(error, grammar)`
+  #             arguments.
   readGrammar: (grammarPath, callback) ->
     fs.readObject grammarPath, (error, object) =>
       if error?
@@ -58,11 +82,22 @@
       else
         callback?(null, @createGrammar(grammarPath, object))

+  # Public: Read a grammar synchronously and add it to this registry.
+  #
+  # * grammarPath: A {String} absolute file path to a grammar file.
+  #
+  # Returns a {Grammar}.
   loadGrammarSync: (grammarPath) ->
     grammar = @readGrammarSync(grammarPath)
     @addGrammar(grammar)
     grammar

+  # Public: Read a grammar asynchronously and add it to the registry.
+  #
+  # * grammarPath: A {String} absolute file path to a grammar file.
+  #
+  # * callback: A {Function} to call when loaded with `(error, grammar)`
+  #             arguments.
   loadGrammar: (grammarPath, callback) ->
     fs.readObject grammarPath, (error, object) =>
       if error?
@@ -72,17 +107,54 @@
         @addGrammar(grammar)
         callback?(null, grammar)

-  grammarOverrideForPath: (path) ->
-    @grammarOverridesByPath[path]
+  # Public: Get the grammar override for the given file path.
+  #
+  # * filePath: A {String} file path.
+  #
+  # Returns a {Grammar} or undefined.
+  grammarOverrideForPath: (filePath) ->
+    @grammarOverridesByPath[filePath]

-  setGrammarOverrideForPath: (path, scopeName) ->
-    @grammarOverridesByPath[path] = scopeName
+  # Public: Set the grammar override for the given file path.
+  #
+  # * filePath: A {String} file path.
+  #
+  # * scopeName: A {String} such as `"source.js"`.
+  #
+  # Returns a {Grammar} or undefined.
+  setGrammarOverrideForPath: (filePath, scopeName) ->
+    @grammarOverridesByPath[filePath] = scopeName

-  clearGrammarOverrideForPath: (path) ->
-    delete @grammarOverridesByPath[path]
+  # Public: Remove the grammar override for the given file path.
+  #
+  # * filePath: A {String} file path.
+  clearGrammarOverrideForPath: (filePath) ->
+    delete @grammarOverridesByPath[filePath]

+  # Public: Remove all grammar overrides.
   clearGrammarOverrides: ->
     @grammarOverridesByPath = {}

+  # Public: Select a grammar for the given file path and file contents.
+  #
+  # This picks the best match by checking the file path and contents against
+  # each grammar.
+  #
+  # * filePath: A {String} file path.
+  #
+  # * fileContents: A {String} of text for the file path.
+  #
+  # Return a {Grammar}, never null.
   selectGrammar: (filePath, fileContents) ->
     _.max @grammars, (grammar) -> grammar.getScore(filePath, fileContents)
+
+  createToken: (value, scopes) -> {value, scopes}
+
+  grammarUpdated: (scopeName) ->
+    for grammar in @grammars when grammar.scopeName isnt scopeName
+      @emit 'grammar-updated', grammar if grammar.grammarUpdated(scopeName)
+
+  createGrammar: (grammarPath, object) ->
+    grammar = new Grammar(this, object)
+    grammar.path = grammarPath
+    grammar


Fixing an accidental array created/returned from a void method.

--- src/ClassData.coffee
+++ src/ClassData.coffee
@@ -219,6 +219,7 @@
     @static_fields = Object.create null
     for method in @methods
       method.initialize()
+    return

   get_interfaces: -> @interface_cdatas
   get_interface_types: -> @interfaces


disabled asset versioning

disabled asset versioning

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -259,6 +259,7 @@
   fs = require 'fs'
   assets = grunt.file.readJSON 'assets.json'
   grunt.registerMultiTask 'assetver', 'Version assets', () ->
+    ###
     rev = ''
     modified = false
     for file in @files
@@ -291,6 +292,7 @@
     if modified
       grunt.config 'assets', assets
       fs.writeFileSync('assets.json', JSON.stringify(assets, null, 4))
+    ###

   # React on single file
   grunt.event.on 'watch', (action, filepath, target) ->


Display tag id if name and description are missing

--- src/flux/exercise.coffee
+++ src/flux/exercise.coffee
@@ -10,7 +10,9 @@
   GENERIC: ['blooms', 'dok', 'length']

 getTagName = (tag) ->
-  [tag.name, tag.description].join(' ')
+  name = _.compact([tag.name, tag.description]).join(' ')
+  name = tag.id unless name
+  name

 getImportantTags = (tags) ->
   obj =


Run yum independently

Run yum independently

--- actions/yum.coffee
+++ actions/yum.coffee
@@ -33,6 +33,7 @@
     Update packages on the system
 ###
 module.exports.push module.exports.configure = (ctx) ->
+  require('./proxy').configure ctx
   ctx.config.yum ?= {}
   ctx.config.yum.clean ?= false
   ctx.config.yum.copy ?= null


fix syntax error

fix syntax error

--- server/controllers/konnectors.coffee
+++ server/controllers/konnectors.coffee
@@ -29,7 +29,7 @@

     import: (req, res, next) ->
         if req.konnector.isImporting
-            setTimeout, ->
+            setTimeout ->
                 data =
                     isImporting: false
                     lastImport: new Date()


Recompose gulp task dependencies

Recompose gulp task dependencies

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -21,7 +21,7 @@
 	.pipe libs.rename 'index.js'
 	.pipe gulp.dest './build'

-gulp.task 'task-dist', ->
+gulp.task 'dist', ['build'], ->
 	gulp.src 'build/index.js'
 	.pipe libs.header banner, pkg: pkg
 	.pipe libs.rename 'HTML5-niconicoplayer.js'
@@ -32,10 +32,8 @@
 	.pipe libs.sourcemaps.write './'
 	.pipe gulp.dest './dist'

-gulp.task 'dist', ['build', 'task-dist']
-
-gulp.task 'test', ->
+gulp.task 'test', ['build'], ->
 	gulp.src 'test/index.html'
 	.pipe libs.qunit()

-gulp.task 'default', ['build', 'test']
+gulp.task 'default', ['test']


Added meta and renamed source to sourceStream

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -1,12 +1,26 @@
 "use strict"

-browserify = require "browserify"
-gulp       = require "gulp"
-uglify     = require 'gulp-uglify'
-source     = require "vinyl-source-stream"
-buffer     = require "vinyl-buffer"
+path         = require "path"
+browserify   = require "browserify"
+gulp         = require "gulp"
+uglify       = require 'gulp-uglify'
+copy         = require 'gulp-copy'
+sourceStream = require "vinyl-source-stream"
+buffer       = require "vinyl-buffer"
+
+meta =
+  build  : 'build'
+  dist   : 'dist'
+  name   : 'micro'
+  source : 'source'
+  specs  : 'specs'
+
+source_ =
+  micro: meta.source + '/' + meta.name + '.coffee'

 gulp.task "browserify", ->
+  pa2 = path.normalize(__dirname + '/..') + '/bower-micro'
+
   bundler = browserify
     entries: [
       "./source/micro.coffee"
@@ -15,12 +29,16 @@
     debug: true

   bundler.bundle()
-    .pipe source "micro.js"
+    .pipe sourceStream meta.name + '.js'
     .pipe buffer()
-    .pipe gulp.dest "./build"
+    .pipe gulp.dest meta.build

   bundler.bundle()
-    .pipe source "micro.min.js"
+    .pipe sourceStream "micro.min.js"
     .pipe buffer()
     .pipe uglify()
-    .pipe gulp.dest "./build"+    .pipe gulp.dest pa2
+
+gulp.task "copy", ->
+  gulp.src ['./build/micro.js']
+    .pipe copy './dist'


Fix comparator for bad formatted datapoints without value ...

--- server/utils/compare_contacts.coffee
+++ server/utils/compare_contacts.coffee
@@ -8,9 +8,12 @@

 # Check if (cozy)contact fuzzily has the specified field
 hasField = (field, contact, checkType = false) ->
+    return false unless field.value?
+
     contact.datapoints.some (baseField) ->
         if field.name is baseField.name and (
-            not checkType or checkType and field.type is baseField.type)
+           not checkType or checkType and field.type is baseField.type) and
+           baseField.value?

             if field.name is 'tel'
                 return field.value.replace(/[-\s]/g, '') is baseField.value.replace(/[-\s]/g, '')


refactor TableFactory to move more logic into separate functions

--- source/javascripts/index/tableFactory.coffee
+++ source/javascripts/index/tableFactory.coffee
@@ -7,6 +7,8 @@
 //= require ./hebrewEvents
 //= require ./weekTable
 //= require ./holidayTableFactory
+
+endOfTable = (hebrewDate) -> hebrewDate.isShabbat() || hebrewDate.is2ndDayOfYomTob() || hebrewDate.isPurim() || hebrewDate.is10Tevet()

 title = (hebrewDate) -> switch
   when hebrewDate.is9Ab() then "תִּשְׁעָה בְּאָב"
@@ -31,18 +33,18 @@
     @gregorianWeek = (moment(gregorianDate).day(weekday) for weekday in moment.weekdays())
     @hebrewWeek = (new HebrewDate(date.toDate()) for date in @gregorianWeek)
     @zmanimWeek = (new Zmanim(day, window.coordinates) for day in @gregorianWeek)
-    @shaharitWeek = for i in [0...(@gregorianWeek.length)]
-      new Shaharit(@hebrewWeek[i], new Sunrise(@gregorianWeek[i]).time() ? @zmanimWeek[i].sunrise(), @zmanimWeek[i].sofZmanKeriatShema())
+    @shaharitWeek = (@shaharit(i) for i in [0...(@gregorianWeek.length)])
     @minchaWeek = (@mincha(i) for i in [0...(@gregorianWeek.length)])
   dateOfNextHadlakatNerot: (i) ->
     i++ until @hebrewWeek[i].hasHadlakatNerot()
     i
   nextHadlakatNerot: (iterator) -> @zmanimWeek[@dateOfNextHadlakatNerot(iterator)].hadlakatNerot()
   mincha: (iterator) -> new Mincha(@hebrewWeek[iterator], @zmanimWeek[iterator].plag(), @zmanimWeek[iterator].sunset()).time() ? @nextHadlakatNerot(iterator)
+  sunrise: (iterator) -> new Sunrise(@gregorianWeek[iterator]).time() ? @zmanimWeek[iterator].sunrise()
+  shaharit: (iterator) -> new Shaharit(@hebrewWeek[iterator], @sunrise(iterator), @zmanimWeek[iterator].sofZmanKeriatShema())
   generateWeekTable: ->
     weekTable = new WeekTable(@gregorianWeek, @hebrewWeek, @zmanimWeek, @shaharitWeek, @minchaWeek, window.HebrewEvents)
-    weekTableRows = for i in [0...(@gregorianWeek.length)]
-      weekTable.generateRow(i)
+    weekTableRows = (weekTable.generateRow(i) for i in [0...(@gregorianWeek.length)])
     weekTable.generateHeaderRow() + weekTableRows.join('')
   generateHolidayTables: ->
     holidayTableFactory = new HolidayTableFactory(@gregorianWeek, @hebrewWeek, @zmanimWeek, @shaharitWeek, @minchaWeek)
@@ -52,12 +54,11 @@
     for i in [0...(@gregorianWeek.length)]
       hebrewDate = @hebrewWeek[i]
       tableSection = holidayTableFactory.generateTableSection(i)
-      endOfTable = hebrewDate.isShabbat() || hebrewDate.is2ndDayOfYomTob() || hebrewDate.isPurim() || hebrewDate.is10Tevet()
       if tableSection?
         tableSections.push(tableSection)
         tableSectionTitle = title(hebrewDate)
         tableSectionTitles.push(tableSectionTitle) if tableSectionTitle? and (tableSectionTitle not in tableSectionTitles)
-      if (endOfTable || !tableSection?) && tableSections.length > 0
+      if (endOfTable(hebrewDate) || !tableSection?) && tableSections.length > 0
         table = """
           <table class='table table-striped table-condensed'>
             <tr>


Use the ip event instead of deprecated handle

--- src/RemoteSubGraph.coffee
+++ src/RemoteSubGraph.coffee
@@ -118,13 +118,21 @@

   prepareInport: (definition) ->
     name = definition.id
+    return if @inPorts.ports[name]
     # Send data across to remote graph
-    @inPorts.add name, @normalizePort(definition), (event, packet) =>
-      packet = null if event in ['connect', 'disconnect']
+    @inPorts.add name, @normalizePort definition
+    @inPorts.ports[name].on 'ip', (ip) ->
+      switch ip.type
+        when 'data'
+          event = data
+        when 'openBracket'
+          event = 'begingroup'
+        when 'closeBracket'
+          event = 'endgroup'
       @runtime.sendRuntime 'packet',
         port: name
         event: event
-        payload: packet
+        payload: ip.data
         graph: @graphName

   prepareOutport: (definition) ->


Remove comment from minified output.

Remove comment from minified output.

--- Jakefile.coffee
+++ Jakefile.coffee
@@ -74,7 +74,7 @@
   complete()

 commands =
-  minify: './node_modules/.bin/uglifyjs --lift-vars --output ./build/types.min.js ./build/types.js'
+  minify: './node_modules/.bin/uglifyjs --no-copyright --lift-vars --output ./build/types.min.js ./build/types.js'
   test: './node_modules/.bin/mocha --compilers coffee:coffee-script --ui tdd --reporter spec --colors --slow 50 ./test/types.coffee'
   jslint: './node_modules/.bin/jshint ./build/types.js --config config/jshint.json'
   cslint: './node_modules/.bin/coffeelint -f config/coffeelint.json ./src/types.coffee'


Add test task to Jakefile

Add test task to Jakefile

--- Jakefile.coffee
+++ Jakefile.coffee
@@ -1,3 +1,9 @@
+
+desc 'Run test watcher'
+task 'test', ->
+    jake.exec ['mocha test/'], { stdout: true}, () ->
+        complete
+

 desc 'Deploy to production'
 task 'deploy', ->


Default initialization for DataStore constructor paramsa

--- lib/datastore/datastore.coffee
+++ lib/datastore/datastore.coffee
@@ -15,7 +15,7 @@
   return typeof(s) == 'number' || s instanceof Number

 module.exports = class DataStore
-  constructor: (@balancesheet, @supermarket) ->
+  constructor: (@balancesheet=(new BalanceSheet()), @supermarket=(new SuperMarket())) ->

   deposit: (args) =>
     #if not isString(args.account)


Updated script

Updated script

--- src/instagram.coffee
+++ src/instagram.coffee
@@ -47,7 +47,7 @@
     msg.send "Please set the HUBOT_INSTAGRAM_CLIENT_KEY environment variable."
     return
   unless config.client_secret
-    msg.send "Please set the HUBOT_TWITTER_ACCESS_TOKEN environment variable."
+    msg.send "Please set the HUBOT_INSTAGRAM_ACCESS_TOKEN environment variable."
     return
   Instagram.set('client_id', config.client_key)
   Instagram.set('client_secret', config.client_secret)


update config.json on device registration

--- src/bootstrap.coffee
+++ src/bootstrap.coffee
@@ -6,6 +6,7 @@
 crypto = require 'crypto'
 config = require './config'
 PlatformAPI = require 'pinejs-client-js/request'
+fs = Promise.promisifyAll(require('fs'))

 PLATFORM_ENDPOINT = url.resolve(config.apiEndpoint, '/ewa/')
 resinAPI = new PlatformAPI(PLATFORM_ENDPOINT)
@@ -27,8 +28,8 @@
 			device_type: deviceType
 		customOptions:
 			apikey: apiKey
-	).then ->
-		return uuid
+	).then (data) ->
+		_.pick(data, 'id', 'uuid')

 module.exports = ->
 	# Load config file
@@ -39,9 +40,11 @@
 		if userConfig.uuid? and userConfig.registered_at?
 			return userConfig
 		registerDevice(userConfig.apiKey, userConfig.userId, userConfig.applicationId, userConfig.deviceType, userConfig.uuid)
-		.then (uuid) ->
-			userConfig.uuid = uuid
-			return userConfig
+		.then (device) ->
+			userConfig.uuid = device.uuid
+			userConfig.deviceId = device.id
+			fs.writeFileAsync('/boot/config.json', JSON.stringify(userConfig))
+		.return(userConfig)
 	.then ->
 		console.log('Finishing bootstrapping')
 		Promise.all([


Fix the default device type.

Fix the default device type.

--- src/bootstrap.coffee
+++ src/bootstrap.coffee
@@ -35,7 +35,7 @@
 module.exports = ->
 	# Load config file
 	userConfig = require('/boot/config.json')
-	userConfig.deviceType ?= 'Raspberry Pi'
+	userConfig.deviceType ?= 'raspberry-pi'

 	Promise.try ->
 		if userConfig.uuid?


enable to run with node v0.10 (path.isAbsolute)

--- src/base-domainify.coffee
+++ src/base-domainify.coffee
@@ -4,6 +4,8 @@
 path    = require 'path'
 coffee  = require 'coffee-script'
 require('coffee-script/register')
+
+path.isAbsolute ?= (str) -> str.charAt(0) is '/'

 MasterDataResource = require './master-data-resource'