Added hint adjusted scores

Added hint adjusted scores

--- src/trivia-game.coffee
+++ src/trivia-game.coffee
@@ -14,14 +14,15 @@
 #   None
 #
 # Commands:
-#   !trivia - ask a question
+#   !trivia or !t - ask a question
 #   !skip - skip the current question
 #   !answer <answer> or !a <answer> - provide an answer
 #   !score <player> - check the score of the player
-#   !hint - hints for trivia question
+#   !hint or !h - hints for trivia question
+#   !t top or !t bottom - trivia scoreboard
 #
 # Author:
-#   yincrash
+#   yincrash, ravikiranj

 Fs = require 'fs'
 Path = require 'path'
@@ -115,15 +116,27 @@
       checkAnswer = checkAnswer.replace /^(a(n?)|the)/g, ""

       if checkGuess.indexOf(checkAnswer) >= 0
-        resp.reply "YOU ARE CORRECT!!! The answer is #{@currentQ.answer}"
         name = resp.envelope.user.name.toLowerCase().trim()
         value = @currentQ.value.replace /[^0-9.-]+/g, ""
+        value = parseInt value
+
+        # Compute score as ((answerLength - hintLength)/answerLength) * value
+        answerLength = @currentQ.validAnswer.length
+        if @hintLength? and @hintLength > 1
+            rawAdjustedValue = Math.floor(((answerLength - @hintLength) / answerLength) * value)
+            # Round to nearest 100
+            adjustedValue = Math.floor(rawAdjustedValue/100)*100
+        else
+            adjustedValue = value
+
+        resp.reply "YOU ARE CORRECT!!! The answer is #{@currentQ.answer}, you scored #{adjustedValue} points."
         @robot.logger.debug "#{name} answered correctly."
-
-        value = parseInt value
+        if adjustedValue != value
+            resp.send "Hints Used = #{@hintLength}, original points = #{value}, adjusted points = #{adjustedValue}"
+
         user = resp.envelope.user.mention_name.toLowerCase().trim()
-        newScore = @scoreKeeper.add(user, value)
-        if newScore? then resp.send "#{user} has #{newScore} points."
+        newScore = @scoreKeeper.add(user, adjustedValue)
+        if newScore? then resp.send "#{user} has a total of #{newScore} points."

         @currentQ = null
         @hintLength = null
@@ -156,8 +169,9 @@
     score = @scoreKeeper.scoreForUser(name)
     resp.send "#{name} has #{score} points."

-  leaderBoard: (resp, topOrBottom, amount) ->
+  leaderBoard: (resp, topOrBottom) ->
     op = []
+    amount = 10
     tops = @scoreKeeper[topOrBottom](amount)

     for i in [0..tops.length-1]
@@ -169,7 +183,7 @@
   scoreKeeper = new ScoreKeeper(robot)
   game = new Game(robot, scoreKeeper)

-  robot.hear /^!trivia/, (resp) ->
+  robot.hear /^!t(rivia)?$/, (resp) ->
     game.askQuestion(resp)

   robot.hear /^!skip/, (resp) ->
@@ -181,9 +195,8 @@
   robot.hear /^!score (.*)/i, (resp) ->
     game.checkScore(resp, resp.match[1])

-  robot.hear /!t (top|bottom)( \d+)?/i, (resp) ->
-    amount = parseInt(resp.match[2])
-    game.leaderBoard(resp, resp.match[1], amount)
-
-  robot.hear /^!hint/i, (resp) ->
+  robot.hear /^!t (top|bottom)/i, (resp) ->
+    game.leaderBoard(resp, resp.match[1])
+
+  robot.hear /^!h(int)?/i, (resp) ->
     game.hint(resp)


Completed paginating tool closes issue #7

--- src/ubret/table.coffee
+++ src/ubret/table.coffee
@@ -13,6 +13,7 @@
     @createTable()
     @createHeader()
     @createRows()
+    @createPages()

   createTable: =>
     table = d3.select(@selector)
@@ -52,6 +53,16 @@
     if @selectedElements and @selectedElements.length isnt 0
       @highlightRows()

+  createPages: =>
+    @p.remove() if @p
+
+    pageNumber = if @currentPage < @pages then @currentPage else @currentPage % @pages
+
+    @p = d3.select(@selector)
+      .append('p')
+      .attr('class', 'pages')
+      .text("Page: #{pageNumber + 1} of #{@pages}")
+
   paginate: =>
     @numRows = Math.floor((@el.height() - 47 )/ 28) # Assumes thead height of 47px and tbody height of 28px
     @pages = Math.ceil(@dimensions.uid.group().size() / @numRows)
@@ -65,9 +76,13 @@
       else
         return @dimensions[@selectedKey].filter([top[@selectedKey], bottom[@selectedKey]])[@sortOrder](Infinity)
     else if number < @pages
-      top = @dimensions[@selectedKey][@sortOrder](number * @numRows)[(number * @numRows) - 1]
-      bottom = @dimensions[@selectedKey][@sortOrder](number * @numRows)[((number + 1) * @numRows) -1]
-      return @imdensions[@selectedKey].filterAll().filter([bottom[@selectedKey], top[@selectedKey]])[@sortOrder](Infinity)
+      console.log @currentPage, number
+      top = @dimensions[@selectedKey].filterAll()[@sortOrder](number * @numRows)[(number * @numRows) - 1]
+      bottom = @dimensions[@selectedKey][@sortOrder]((number + 1) * @numRows)[((number + 1) * @numRows) - 1]
+      console.log top, bottom
+      return @dimensions[@selectedKey].filter([bottom[@selectedKey], top[@selectedKey]])[@sortOrder](Infinity)
+    else if number >= @pages
+      @page number % @pages

   toArray: (data) =>
     ret = new Array


Refactor compiler chain mapping

Refactor compiler chain mapping

--- src/fs_utils/pipeline.coffee
+++ src/fs_utils/pipeline.coffee
@@ -31,38 +31,40 @@
   else
     callback null, []

-compile = (initialData, sourcePath, compilers, callback) ->
-  ext = sysPath.extname(sourcePath).slice(1)
-  compile.chain[ext] ?= compilers.map (compiler) =>
-    (params, next) =>
-      return next() unless params
-      {dependencies, compiled, source, sourceMap, path} = params
-      debug "Compiling '#{path}' with '#{compiler.constructor.name}'"
+mapCompilerChain = (compiler) ->
+  (params, next) ->
+    return next() unless params
+    {dependencies, compiled, source, sourceMap, path} = params
+    debug "Compiling '#{path}' with '#{compiler.constructor.name}'"

-      compilerData = compiled or source
-      compilerArgs = if compiler.compile.length is 2
-        # New API: compile({data, path}, callback)
-        [{data: compilerData, path, map: sourceMap}]
+    compilerData = compiled or source
+    compilerArgs = if compiler.compile.length is 2
+      # New API: compile({data, path}, callback)
+      [{data: compilerData, path, map: sourceMap}]
+    else
+      # Old API: compile(data, path, callback)
+      [compilerData, path]
+
+    compilerArgs.push (error, result) ->
+      return callback throwError 'Compiling', error if error?
+      return next() unless result?
+      if toString.call(result) is '[object Object]'
+        sourceMap = result.map if result.map?
+        compiled = result.data
       else
-        # Old API: compile(data, path, callback)
-        [compilerData, path]
+        compiled = result
+      unless compiled?
+        throw new Error "Brunch SourceFile: file #{path} data is invalid"
+      getDependencies source, path, compiler, (error, dependencies) =>
+        return callback throwError 'Dependency parsing', error if error?
+        next null, {dependencies, compiled, source, sourceMap, path}

-      compilerArgs.push (error, result) ->
-        return callback throwError 'Compiling', error if error?
-        return next() unless result?
-        if toString.call(result) is '[object Object]'
-          sourceMap = result.map if result.map?
-          compiled = result.data
-        else
-          compiled = result
-        unless compiled?
-          throw new Error "Brunch SourceFile: file #{path} data is invalid"
-        getDependencies source, path, compiler, (error, dependencies) =>
-          return callback throwError 'Dependency parsing', error if error?
-          next null, {dependencies, compiled, source, sourceMap, path}
+    compiler.compile.apply compiler, compilerArgs

-      compiler.compile.apply compiler, compilerArgs
-  first = (next) -> next null, {source: initialData, path: sourcePath}
+compile = (source, path, compilers, callback) ->
+  ext = sysPath.extname(path).slice(1)
+  compile.chain[ext] ?= compilers.map mapCompilerChain
+  first = (next) -> next null, {source, path}
   waterfall [first].concat(compile.chain[ext]), callback

 compile.chain = {}


Maybe fixing a weird webkit problem

Maybe fixing a weird webkit problem

--- app/models/subject.coffee
+++ app/models/subject.coffee
@@ -34,8 +34,8 @@

   @fetch: ->
     count = Config.subjectCache - @count()
-    _(super).tap =>
-      _(count - 1).times => super(1)
+    super.onSuccess =>
+      @fetch() if count > 1

   @show: (id) ->
     Api.get "/projects/galaxy_zoo/subjects/#{ id }"


Add exitrepeat AppleScript snippet

Add exitrepeat AppleScript snippet

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -457,6 +457,10 @@
     'prefix': 'repeattimes'
     'body': 'repeat ${1:n} times\n\t$2\nend repeat'

+  'exit repeat':
+    'prefix': 'exitrepeat'
+    'body': 'exit repeat'
+
   'tell':
     'prefix': 'tell'
     'body': 'tell ${1:object}\n\t$2\nend tell'


No need to abort transition: rejecting will abort automatically

--- assets/scripts/app/routes.coffee
+++ assets/scripts/app/routes.coffee
@@ -59,7 +59,6 @@

     if !@signedIn() && @get('needsAuth')
       Travis.auth.set('afterSignInTransition', transition)
-      transition.abort()
       Ember.RSVP.reject("needs-auth")
     else
       @_super.apply(this, arguments)


Fixed pagenation

Fixed pagenation

--- web/site/search.coffee
+++ web/site/search.coffee
@@ -74,7 +74,7 @@
         $scope.parseResults(res.data)

     $scope.maxResults = ->
-      return Math.max([$scope.volumeCount, $scope.partyCount])
+      return Math.max($scope.volumeCount, $scope.partyCount)

     #################################
     # Parse results, the main workhorse function
@@ -87,8 +87,8 @@
       $scope.partyCount = $scope.getTypeCounts("party", res)
       $scope.volumeCount = $scope.getTypeCounts("volume", res)

-      $scope.typeDisplay = [$scope.partyDisplayStr + " (" + $scope.partyCount + ")",
-        $scope.volumeDisplayStr + " (" + $scope.volumeCount + ")"]
+      $scope.typeDisplay = [$scope.partyDisplayStr + " (" + ($scope.partyCount || 0) + ")",
+        $scope.volumeDisplayStr + " (" + ($scope.volumeCount || 0) + ")"]

       console.log("PARTY RESULTS", $scope.partyResults)

@@ -100,7 +100,7 @@
       $scope.totalCount = $scope.partyCount + $scope.volumeCount

       $scope.minPage = 1
-      $scope.maxPage = 1 + ($scope.maxResults / ($scope.limit + 1))
+      $scope.maxPage = 1 + ($scope.maxResults() / ($scope.limit + 1))
       pageRange = []
       for i in [$scope.minPage .. $scope.maxPage] by 1
         pageRange.push(i)
@@ -111,7 +111,10 @@
         $scope.offset = $scope.limit * (page-1)
         $scope.search()

-      if parseInt($scope.maxResults) > ($scope.offset + $scope.limit)
+
+      console.log("MAX RESULTS", $scope.maxResults())
+
+      if parseInt($scope.maxResults()) > ($scope.offset + $scope.limit)
         $scope.next = ->
           $scope.offset = $scope.offset + $scope.limit
           $scope.search()
@@ -164,7 +167,7 @@

     $scope.updateFilterBoxOptions = ->
       console.log("SELTYPE", $scope.selectedType)
-      if $scope.selectedType
+      if $scope.selectedType and $scope.filterDisplay.length == 0
         if $scope.selectedType.join(" ").includes($scope.volumeDisplayStr)
           $scope.filterDisplay = (s for s in $scope.getVolumeFeatureBoxOpts())
         if $scope.selectedType.join(" ").includes($scope.partyDisplayStr)
@@ -173,7 +176,7 @@

     $scope.countWithArg = (group, argument, value) ->
       console.log(group)
-      result = (d for d in group.docs when d[argument] == value).length
+      result = (d for d in group.docs when d[argument] == value).length || 0
       return result

     # Perform a new search for people or volumes only... NOTE: does not do anything yet


Don't specify index routes

Don't specify index routes

They're generated by default

--- assets/scripts/app/routes.coffee
+++ assets/scripts/app/routes.coffee
@@ -72,9 +72,7 @@
   @route 'auth', path: '/auth'

   @resource 'profile', path: '/profile', ->
-    @route 'index', path: '/'
     @resource 'account', path: '/:login', ->
-      @route 'index', path: '/'
       @route 'profile', path: '/profile'

   @route 'notFound', path: "/*path"


Refactor _state setting in tip plugin.

Refactor _state setting in tip plugin.

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -143,12 +143,12 @@
       # Infer `doStem` and `doFollow` flags from respective `classNames` entries.
       @doStem = @classNames.stem isnt ''
       @doFollow = @classNames.follow isnt ''
-      # `_state` toggles between: `awake`, `asleep`, `waking`, `sleeping`. The
-      # use case behind these states is the tip will remain visible and `awake`
-      # as long as there is a high enough frequency of relevant mouse activity.
-      # This is achieved with a simple base implementation around timers
-      # `_sleepCountdown` and `_wakeCountdown`.
-      @_state = 'asleep'
+      # Updated with `_setState`, `_state` toggles between: `awake`, `asleep`,
+      # `waking`, `sleeping`. The use case behind these states is the tip will
+      # remain visible and `awake` as long as there is a high enough frequency
+      # of relevant mouse activity. This is achieved with a simple base
+      # implementation around timers `_sleepCountdown` and `_wakeCountdown`.
+      @_setState 'asleep'
       @_wakeCountdown = null
       @_sleepCountdown = null
       # `_$currentTrigger` helps manage trigger-related state.
@@ -333,6 +333,11 @@
         left:   parseInt @$context.css('padding-left'), 10
         bottom: $context.innerHeight()
         right:  $context.innerWidth()
+
+    _setState: (state) ->
+      return no if state is @_state
+      @_state = state
+      @debugLog @_state

     # `sizeForTrigger` does a stealth render to find tip size by temporarily un-
     # hiding and making invisible. It will return saved data if possible before
@@ -405,7 +410,7 @@
             onWake() if onWake?
           if @safeToggle is on then @$tip.siblings(@classNames.tip).fadeOut()
           @afterShow triggerChanged, event
-          @_state = 'awake'
+          @_setState 'awake'
       # Wake up depending on current state.
       # If we are in the middle of sleeping, stop sleeping by updating
       # `_sleepCountdown` and wake up sooner.
@@ -417,7 +422,7 @@
       # Start the normal wakeup and update `_wakeCountdown`.
       else if event? and event.type is 'truemouseenter'
         triggerChanged = yes
-        @_state = 'waking'
+        @_setState 'waking'
         @_wakeCountdown = setTimeout wake, delay
       yes

@@ -425,14 +430,14 @@
     # `wakeByTrigger`. It also updates `_state` and returns a bool for success.
     # As long as the tip isn't truly visible, sleep is unneeded.
     sleepByTrigger: ($trigger) ->
-      @_state = 'sleeping'
       # Don't toggle if asleep or sleeping.
       return no if @_state in ['asleep', 'sleeping']
+      @_setState 'sleeping'
       clearTimeout @_wakeCountdown
       @_sleepCountdown = setTimeout =>
         @onHide()
         @$tip.stop().fadeOut @ms.duration.out, =>
-          @_state = 'asleep'
+          @_setState 'asleep'
           @afterHide()
       , @ms.delay.out
       yes


More verbose trip pull

More verbose trip pull

--- src/packages/carpool-service/client/CarpoolServiceClient.coffee
+++ src/packages/carpool-service/client/CarpoolServiceClient.coffee
@@ -170,9 +170,9 @@
       owner: $ne: Meteor.userId()
       time: $gte: fromTime
     ).value();
-    d "Active trips", query
-    trips = Trips.find(query, sort: time: -1)
-    trips.fetch()
+    trips = Trips.find(query, sort: time: -1).fetch()
+    d "Active trips", query, trips
+    trips

   ###
   New version of getOwnTrips - reactive method to subscribe and find own Trips


Do not load Raven if it's already loaded

--- src/utils/raven.coffee
+++ src/utils/raven.coffee
@@ -5,7 +5,7 @@

 module.exports =
   init: (dsn, context)->
-    if dsn
+    if dsn && !window.Raven
       script(src: 'https://cdn.ravenjs.com/2.1.1/raven.min.js').then ->
         window.Raven.config(dsn).install()
         window.Raven.setExtraContext(context)


Start UI code - add a basic player frame to display name and health

--- webserver/static/js/renderer.coffee
+++ webserver/static/js/renderer.coffee
@@ -1,5 +1,5 @@
 @WIDTH = 700
-@HEIGHT = 300
+@HEIGHT = 400
 CENTER_X = @WIDTH / 2
 CENTER_Y = @HEIGHT / 2
 stage = new PIXI.Stage 0xEEFFFF
@@ -54,7 +54,6 @@
     for row, i in terrain
         for tile, j in row
             drawTile i, j
-    requestAnimFrame animate

 @stagePlayer :: (Num, Num) -> Any
 @stagePlayer = (x, y) ->
@@ -82,14 +81,63 @@
     for p in _.values(@world.players)
         updateSprite p, CENTER_X - camera.getX(), CENTER_Y - camera.getY()

+# UI CONSTANTS
+UI_FONT = {
+    font: "12pt Arial"
+}
+UI_FONT_HEALTH = {
+    font: "12pt Arial"
+    fill: "red"
+}
+UI_PLAYER_PANEL_X = 0
+UI_PLAYER_PANEL_Y = 300
+UI_PLAYER_PANEL_WIDTH = 200
+UI_PLAYER_PANEL_HEIGHT = 100
+UI_PLAYER_PANEL_BG = 0xABABAB
+UI_PLAYER_PANEL_BORDER_COLOR = 0x000000
+UI_PLAYER_PANEL_BORDER_SIZE = 1
+UI_PLAYER_PANEL_INDENT_X = 10
+UI_PLAYER_PANEL_INDENT_Y = 10
+UI_PLAYER_PANEL_SEP_Y = 16
+
+@UI = {
+    drawPlayerPanel: (player) ->
+        graphics.beginFill UI_PLAYER_PANEL_BG
+        graphics.lineStyle UI_PLAYER_PANEL_BORDER_SIZE, UI_PLAYER_PANEL_BORDER_COLOR, 1
+        graphics.drawRect UI_PLAYER_PANEL_X + 1, UI_PLAYER_PANEL_Y - 1, UI_PLAYER_PANEL_WIDTH, UI_PLAYER_PANEL_HEIGHT
+        graphics.endFill()
+
+    addText: (initText, font, x, y, parent) ->
+        text = new PIXI.Text initText, font
+        graphics.addChild text
+        text.position.x = x
+        text.position.y = y
+        return text
+
+    setupTextElements: (world) ->
+        @.playerName = @.addText "", UI_FONT, UI_PLAYER_PANEL_X + UI_PLAYER_PANEL_INDENT_X, UI_PLAYER_PANEL_Y + UI_PLAYER_PANEL_INDENT_Y
+        @.healthText = @.addText "", UI_FONT_HEALTH, @.playerName.position.x, @.playerName.position.y + UI_PLAYER_PANEL_SEP_Y
+}
+
+setupUI = (world) ->
+    @UI.setupTextElements world
+
+drawUI = (world) ->
+    @UI.playerName.setText world.player.name
+    @UI.healthText.setText (world.player.curHealth + " / " + world.player.maxHealth)
+    @UI.drawPlayerPanel (world.player)
+
 drawWorld = () ->
     updateSprites()
     xOff = CENTER_X - @world.camera.getX()
     yOff = CENTER_Y - @world.camera.getY()
     drawMap @world.grid, xOff, yOff
+    drawUI @world
+    requestAnimFrame animate

 @startRenderer = () ->
     document.getElementById('game').appendChild renderer.view
     stage.addChild graphics
     @world.player.sprite = @stagePlayer CENTER_X, CENTER_Y
+    setupUI @world
     setInterval drawWorld, 20


Edited src/docco.coffee via GitHub

Edited src/docco.coffee via GitHub

--- src/docco.coffee
+++ src/docco.coffee
@@ -9,10 +9,8 @@
 #
 #     docco src/*.coffee
 #
-# ...will generate linked HTML documentation for the named source files, saving
-# it into a `docs` folder. One HTML file per input source file will be generated.
-# For each call to docco, if there are multiple files, a menu will be generated to
-# navigate between files.  This will contain only the files for the current invocation.
+# ...will generate an HTML documentation page for each of the named source files,
+# with a menu linking to the other pages, saving it into a `docs` folder.
 #
 # The [source for Docco](http://github.com/jashkenas/docco) is available on GitHub,
 # and released under the MIT license.


change database name

change database name

--- database.coffee
+++ database.coffee
@@ -1,5 +1,5 @@
 Datastore  = require('nedb')
-db = new Datastore {filename: 'foobar.db', nodeWebkitAppName: 'pocket-dashboard', autoload: true }
+db = new Datastore {filename: 'database.db', nodeWebkitAppName: 'pocket-dashboard', autoload: true }

 exports.insert = (doc,callback) ->
     return db.insert doc, callback


Added protection to group queries’s after callback so it’s called immediately if all queries have already ran

--- src/group.coffee
+++ src/group.coffee
@@ -1,5 +1,6 @@
 module.exports = (knex, callback) ->
 	count = 0
+	total = 0
 	after = null

 	next = ->
@@ -9,6 +10,7 @@
 	callback
 		_inject: (obj) ->
 			count++
+			total++

 			t = obj.then
 			obj.then = (callback) ->
@@ -51,4 +53,8 @@
 		# Called after all queries have executed
 		after: (callback) ->
 			after = callback
+
+			# Protect against queries having already all executed
+			callback() if total > 0 and count is 0
+
 			return


Defining the instance property

Defining the instance property

--- src/index.coffee
+++ src/index.coffee
@@ -4,6 +4,7 @@

 class KeywordProcessor
+  @instance: null
   brunchPlugin: yes
   globalRE: null
   globalMap: null


* minor comments added

* minor comments added

--- src/index.coffee
+++ src/index.coffee
@@ -176,6 +176,7 @@
           @convertVirtualFolder(result.contents)
           aContents = result.contents
         if aOptions.recursive
+          # TODO: it must be loaded first if the file treat as virtual folder.
           aContents = aContents.filter (f)->f.isDirectory()
           for vFile in aContents
             vFile.loadSync aOptions if vFile instanceof Resource


Add `on` and `emit` methods

Add `on` and `emit` methods

Useful events include 'request', 'response', and 'close'.
Other events can be found at: https://nodejs.org/api/http.html#http_class_http_server

--- src/index.coffee
+++ src/index.coffee
@@ -76,9 +76,13 @@
       return onFinish res

   .then ->
+
     # Prevent DoS attacks using large POST bodies.
     req.destroy() if req.reading
-    measure req, res
+
+    if res.statusCode isnt 408
+      app.emit "response", res
+      measure req, res

   # Uncaught errors end up here.
   .fail (error) ->
@@ -87,6 +91,7 @@
     log.moat 1
     res.status 500
     res.send {error: "Something went wrong on our end. Sorry!"}
+    app.emit "response", res
     measure req, res

 type.defineMethods
@@ -122,6 +127,12 @@
   drain: (fn) ->
     @_layer.drain fn
     return this
+
+  on: (eventId, handler) ->
+    @_server.on eventId, handler
+
+  emit: (eventId, data) ->
+    @_server.emit eventId, data

   ready: (callback) ->
     if @_server.listening
@@ -166,6 +177,7 @@
 onTimeout = (req, res) ->
   res.status 408
   res.send {error: "Request timed out"}
+  @emit "response", req
   measure req, res

 measure = (req, res) ->


start

start

--- src/index.coffee
+++ src/index.coffee
@@ -1,4 +1,187 @@
 class MiniEventEmitter
+
+	# --------------------------------------------------
+	# Public functionality
+	# --------------------------------------------------
+	constructor: (obj) ->
+
+		# Store and define settings
+		@settings =
+			error  : obj?.error
+			trace  : obj?.trace
+			worker : obj?.worker
+
+		# Store all events
+		@events = {}
+
+		# Store all groups
+		@groups = {}
+
+		# Create a webworker if required
+		return if not @settings.worker
+
+		# Create a webworker instance
+		@worker = webworkify @settings.worker
+
+		# Listen for responses comming from the webworker
+		@worker.addEventListener 'message', ({data}) =>
+
+			# Parse response back into the function array
+			args = []; args[i] = arg for i, arg of JSON.parse data
+
+			# Retrieve event name
+			eventName = args.shift()
+
+			# Check if the event exists
+			return console.log "Event: '#{eventName}' not found" if not actions = @events[eventName]
+
+			# Run function with all arguments except for the eventName
+			action.apply action, args for action in actions
+
+
+	on: (event, group, fn) ->
+
+		# Make group optional
+		[group, fn] = check group, fn
+
+		# Event name must be a string
+		return error this, 'on', 1 if not isString event
+
+		# Group must be a string
+		return error this, 'on', 6 if not isString group
+
+		# Fn must be a function
+		return error this, 'on', 5, event if not isFunction fn
+
+		# Check if the provided group exists
+		if @groups[group]
+
+			# Check if the provided event exists within this group and either push to or create an array with functions
+			if @groups[group][event] then @groups[group][event].push fn else @groups[group][event] = [fn]
+
+		else
+
+			# Create the provided group
+			@groups[group] = {}
+
+			# Create an array with function for this group and event
+			@groups[group][event] = [fn]
+
+		# Either add a new event or push it to a list with others
+		if @events[event] then @events[event].push fn else @events[event] = [fn]
+
+		# Return this to allow chaining
+		this
+
+
+	off: (event, group, fn) ->
+
+		# Define the actual remove function variables are already know due to the scope the function is in
+		removeFn = =>
+
+			# Loop over all found function in the group
+			for action in actions
+
+				# Get the index of the stored function
+				index = @events[event].indexOf action
+
+				# Remove function from events list
+				@events[event].splice index, 1
+
+			# If no functions are left within the group remove it
+			delete @events[event] if @events[event].length is 0
+
+		# Make group optional
+		[group, fn] = check group, fn
+
+		# Event name must be a string
+		return error this, 'off', 1 if event and not isString event
+
+		# Group must be a string
+		return error this, 'off', 6 if not isString group
+
+		# Fn must be a function
+		return error this, 'off', 5, event if fn and not isFunction fn
+
+		# Provided group must exist
+		return error this, 'off', 8, event, group if not @groups[group]
+
+		if not event
+
+			# Remove all events of the provided group
+			removeFn() for event, actions of @groups[group]
+
+			# Remove the group
+			delete @groups[group]
+
+			# Return this to allow chaining
+			return this
+
+		# Event name doesn't exist for this group
+		return error this, 'off', 4, event, group if not actions = @groups[group][event]
+
+		# Check if a function to be removed is defined
+		if not fn
+
+			# Remove the eventListeners of the specified group+event
+			removeFn()
+
+			# Remove all events related to the group
+			delete @groups[group][event]
+
+			# If no events are left within the group remove it
+			delete @groups[group] if 0 is objLength @groups[group]
+
+			# Return this to allow chaining
+			return this
+
+		return error this, 'off', 2, event, group if -1 is index1 = actions.indexOf fn
+
+		# Remove function from groups list
+		actions.splice index1, 1
+
+		# If no functions are left within the event of a group remove it
+		delete @groups[group][event] if actions.length is 0
+
+		# If no events are left within the group remove it
+		delete @groups[group] if 0 is objLength @groups[group]
+
+		# Get the index of the stored function
+		index2 = @events[event].indexOf fn
+
+		# Remove function from events list
+		@events[event].splice index2, 1
+
+		# If no functions are left within the group remove it
+		delete @events[event] if @events[event].length is 0
+
+		# Return this to allow chaining
+		this
+
+
+	emit: ->
+
+		# Turn arguments into an array
+		args = Array.from arguments
+
+		# Retrieve event name from the provided arguments
+		event = args.shift()
+
+		# Pass the emit along to the actual emit function
+		return _emit
+			self     : this
+			args     : args
+			event    : event
+			internal : false
+
+
+	trigger: ->
+
+		# Send request along to emit
+		@emit.apply this, arguments
+
+		# Return this to allow chaining
+		this

 	# --------------------------------------------------
 	# Private functionality
@@ -93,196 +276,15 @@
 		this

-
-	# --------------------------------------------------
-	# Public functionality
-	# --------------------------------------------------
-	constructor: (obj) ->
-
-		@settings =
-			error  : obj?.error
-			trace  : obj?.trace
-			worker : obj?.worker
-
-		# Store all events
-		@events = {}
-		@groups = {}
-
-		# Create a webworker if required by setings
-		return if not @settings.worker
-
-		# Create a webworker instance
-		@worker = webworkify @settings.worker
-
-		# Listen for responses comming from the webworker
-		@worker.addEventListener 'message', ({data}) =>
-
-			# Parse response back into the function array
-			args = []; args[i] = arg for i, arg of JSON.parse data
-
-			# Retrieve event name
-			eventName = args.shift()
-
-			# Check if the event exists
-			return console.log "Event: '#{eventName}' not found" if not actions = @events[eventName]
-
-			# Run function with all arguments except for the eventName
-			action.apply action, args for action in actions
-
-
-	on: (event, group, fn) ->
-
-		# Make group optional
-		[group, fn] = check group, fn
-
-		# Event name must be a string
-		return error this, 'on', 1 if not isString event
-
-		# Group must be a string
-		return error this, 'on', 6 if not isString group
-
-		# Fn must be a function
-		return error this, 'on', 5, event if not isFunction fn
-
-		# Check if the provided group exists
-		if @groups[group]
-
-			# Check if the provided event exists within this group and either push to or create an array with functions
-			if @groups[group][event] then @groups[group][event].push fn else @groups[group][event] = [fn]
-
-		else
-
-			# Create the provided group
-			@groups[group] = {}
-
-			# Create an array with function for this group and event
-			@groups[group][event] = [fn]
-
-		# Either add a new event or push it to a list with others
-		if @events[event] then @events[event].push fn else @events[event] = [fn]
-
-		# Return this to allow chaining
-		this
-
-
-	off: (event, group, fn) ->
-
-		# Define the actual remove function variables are already know due to the scope the function is in
-		removeFn = =>
-
-			# Loop over all found function in the group
-			for action in actions
-
-				# Get the index of the stored function
-				index = @events[event].indexOf action
-
-				# Remove function from events list
-				@events[event].splice index, 1
-
-			# If no functions are left within the group remove it
-			delete @events[event] if @events[event].length is 0
-
-		# Make group optional
-		[group, fn] = check group, fn
-
-		# Event name must be a string
-		return error this, 'off', 1 if event and not isString event
-
-		# Group must be a string
-		return error this, 'on', 6 if not isString group
-
-		# Fn must be a function
-		return error this, 'off', 5, event if fn and not isFunction fn
-
-		# Provided group must exist
-		return error this, 'off', 8, event, group if not @groups[group]
-
-		if not event
-
-			# Remove all events of the provided group
-			removeFn() for event, actions of @groups[group]
-
-			# Remove the group
-			delete @groups[group]
-
-			# Return this to allow chaining
-			return this
-
-		# Event name doesn't exist for this group
-		return error this, 'off', 4, event, group if not actions = @groups[group][event]
-
-		# Check if a function to be removed is defined
-		if not fn
-
-			# Remove the eventListeners of the specified group+event
-			removeFn()
-
-			# Remove all events related to the group
-			delete @groups[group][event]
-
-			# If no events are left within the group remove it
-			delete @groups[group] if 0 is objLength @groups[group]
-
-			# Return this to allow chaining
-			return this
-
-		return error this, 'off', 2, event, group if -1 is index1 = actions.indexOf fn
-
-		# Remove function from groups list
-		actions.splice index1, 1
-
-		# If no functions are left within the event of a group remove it
-		delete @groups[group][event] if actions.length is 0
-
-		# If no events are left within the group remove it
-		delete @groups[group] if 0 is objLength @groups[group]
-
-		# Get the index of the stored function
-		index2 = @events[event].indexOf fn
-
-		# Remove function from events list
-		@events[event].splice index2, 1
-
-		# If no functions are left within the group remove it
-		delete @events[event] if @events[event].length is 0
-
-		# Return this to allow chaining
-		this
-
-
-	emit: ->
-
-		# Turn arguments into an array
-		args = Array.from arguments
-
-		# Retrieve event name from the provided arguments
-		event = args.shift()
-
-		# Pass the emit along to the actual emit function
-		return _emit
-			self     : this
-			args     : args
-			event    : event
-			internal : false
-
-
-	trigger: ->
-
-		# Send request along to emit
-		@emit.apply this, arguments
-
-		# Return this to allow chaining
-		this
-
-
-
-# Export for browserify or simple browser
-(->
+# --------------------------------------------------
+# Exposing | Browserify or Simple Browser
+# --------------------------------------------------
+
+do ->
 	if module? && module.exports
 		module.exports = MiniEventEmitter
 	else if window
 		window.MiniEventEmitter = MiniEventEmitter
 	else
 		msg = "Cannot expose MiniEventEmitter"
-		if console.warn then console.warn msg else console.log msg
-)()+		if console.warn then console.warn msg else console.log msg


changed url

changed url

--- src/index.coffee
+++ src/index.coffee
@@ -286,7 +286,7 @@
 init = () ->
   if(window.self isnt window.top)
     $('div.container').attr('class','container-fluid')
-    pageUrl = "http://kalandozok.hu/meregkevero"
+    pageUrl = "http://kalandozok.hu/meregkevero/"
   renderModifiers()
   renderNegativeModifiers()
   renderAlchemyModifiers()


Emit brain loaded event when initializing

--- src/index.coffee
+++ src/index.coffee
@@ -42,6 +42,8 @@
     else
       robot.loadFile Path.dirname(@scriptsPath), Path.basename(@scriptsPath)

+    robot.brain.emit 'loaded'
+
     robot.adapter

 module.exports = Helper


Touch the cache item if it's confirmed up-to-date

--- src/index.coffee
+++ src/index.coffee
@@ -36,6 +36,11 @@
           if err
             callback err
           else if response.statusCode == 304
+            # Touch the cache item for another 30 minutes
+            setImmediate ->
+              cacheItem.save (err) ->
+                if err
+                  console.error err
             callback null, cacheItem.body
           else
             setImmediate ->


testing

testing

--- src/index.coffee
+++ src/index.coffee
@@ -9,7 +9,7 @@

 class Connector extends EventEmitter
   constructor: ->
-    @validUntil = moment().utc()
+    @validUtil = moment().add(10, 'minutes').utc()
     @board = new five.Board {io: new Raspi(),repl: false,debug: false}
     @board.on 'ready', @handleReady

@@ -29,6 +29,7 @@
   checkMeeting:(event) =>
     currentMeeting = _.get event, 'genisys.currentMeeting'
     if currentMeeting?
+      console.log "====================================="
       console.log "curentMeeting found : ", currentMeeting
       if moment(@validUntil).isBefore(moment().utc())
         meetingId = _.get currentMeeting, 'meetingId'


make the default country/state work in all cases

--- src/index.coffee
+++ src/index.coffee
@@ -243,6 +243,9 @@
   state = store.get 'default-state' ? ''
   country = store.get 'default-country' ? ''

+  countriesReady = false
+  dontPrefill = false
+
   if !state || !country
     # get country/state
     # requires google maps to be in the namespace
@@ -255,7 +258,7 @@
           callback: (results, status)=>
             # abort if manually selected
             if data.get('order.shippingAddress.country') || data.get('order.shippingAddress.state')
-              return
+              dontPrefill = true

             if status == 'OK'
               state = ''
@@ -271,9 +274,14 @@
               store.set 'default-state', state
               store.set 'default-country', country

+              if countriesReady
+                data.set 'order.shippingAddress.country', country
+                data.set 'order.shippingAddress.state', state
+
   # use default geoloc
   data.on 'set', (k, v)->
-    if k == 'countries'
+    if k == 'countries' && !dontPrefill
+      countriesReady = true
       data.set 'order.shippingAddress.country', country
       data.set 'order.shippingAddress.state', state


SocialWorker: watch memory leakage for social worker

--- workers/social/lib/social/main.coffee
+++ workers/social/lib/social/main.coffee
@@ -2,12 +2,12 @@

 log = -> console.log arguments...

-{ argv } = require 'optimist'
-
+{ argv }           = require 'optimist'
 { exec }           = require 'child_process'
 { extend }         = require 'underscore'
 { join: joinPath } = require 'path'
 { NodejsProfiler } = require 'koding-datadog'
+memoryLeakWatcher  = require 'memoryleakwatcher'

 usertracker = require '../../../usertracker'
 datadog     = require '../../../datadog'
@@ -147,6 +147,8 @@
   nodejsProfiler = new NodejsProfiler 'socialWorker'
   nodejsProfiler.startMonitoring()

+  memoryLeakWatcher.setup()
+
   compression = require 'compression'
   bodyParser = require 'body-parser'


add upstream model with server default values

--- www/js/controller.coffee
+++ www/js/controller.coffee
@@ -11,9 +11,7 @@
     _.extend $scope,
        collection: collection
        create: ->
-         upstream = new resource.Upstream
-           prefix: '/prefix'
-           target: 'http://echo:1338'
+         upstream = new resource.Upstream()
          upstream
            .$save()
            .then ->


Refactors ObservableBase

Refactors ObservableBase

--- src/observable/observable_base.coffee
+++ src/observable/observable_base.coffee
@@ -74,58 +74,43 @@

     val

-  _intOrKey: (prop) ->
-    int = parseInt prop
+  _get: (prop, tracker = @, keypath) ->
+    return @ unless prop?

-    if !prop || prop == '' || isNaN int
-      prop
-    else
-      int
+    tracker._createTrack 'getter', keypath ? prop if tracker._tracking.getter

-  _get: (val, prop, keypath, parent) ->
-    prop = @_intOrKey prop
-
-    @_createTrack 'getter', prop if @_tracking.getter
+    val = @_observed[prop]

     if _.isFunction val
-      parent._getComputed prop
+      @_getComputed prop
     else
       val

   get: (keypath) ->
-    return @ unless keypath?
+    { obj, prop } = @getProperty keypath
+    obj._get prop, @, keypath
+
+  getProperty: (keypath) ->
+    return { obj: @ } unless keypath?

     keypath += ''
     path = keypath.split '.'
     len = path.length

     if len == 0
-      return @
+      { obj: @ }
     else if len == 1
-      return @_observed[keypath]
-
-    prop = path[path.length - 1]
-    ref = @_observed
-    parent = @
-
-    while ref && (p = path.shift())
-      p = @_intOrKey p
-      parent = ref if ref._observed
-      ref = ref.get?(p) ? ref[p]
-
-    @_get ref, prop, keypath, parent
-
-  getProperty: (keypath) ->
-    return { obj: @ } unless keypath
-
-    keypath += ''
-
-    if !~keypath.indexOf '.'
       { obj: @, prop: keypath }
     else
-      path = keypath.split '.'
       prop = path.pop()
-      obj = @get path.join '.'
+      obj = @
+      ref = @_observed
+
+      while ref && (p = path.shift())
+        p = @_intOrKey p
+        obj = ref if ref._observed
+        ref = ref.get?(p) ? ref[p]
+
       { obj, prop }

   _set: (prop, val) ->
@@ -199,5 +184,3 @@
     { obj, prop } = @getProperty keypath
     obj._unobserve prop, callback

-
-


only check for duplicates when dragging to a different panel

--- src/packages/tabs/lib/tab-view.coffee
+++ src/packages/tabs/lib/tab-view.coffee
@@ -82,7 +82,9 @@
     droppedNearTab = @getSortableElement(event)
     draggedTab     = fromPane.find(".#{TabView.viewClass()} .sortable:eq(#{previousDraggedTabIndex})")

-    return if draggedTab[0] is droppedNearTab[0] or @containsTab(draggedTab)
+    return if draggedTab.is(droppedNearTab)
+    if fromPaneIndex != toPaneIndex
+      return if toPane.find('.sortable-list').view().containsTab(draggedTab)

     draggedTab.remove()
     draggedTab.insertAfter(droppedNearTab)


Fix broken sharing page when not the owner of a project

--- services/web/public/coffee/project-members/ProjectMembersManager.coffee
+++ services/web/public/coffee/project-members/ProjectMembersManager.coffee
@@ -22,7 +22,7 @@
 					content : $(@templates.userPanel)
 					lock: true
 					onShown: () =>
-						@publishProjectView.refreshPublishStatus()
+						@publishProjectView?.refreshPublishStatus()

 			setupPublish = _.once =>
 				@publishProjectView = new PublishProjectView


run content() if content is a function

run content() if content is a function

--- vendor/assets/javascripts/fustrate/components/modal.coffee
+++ vendor/assets/javascripts/fustrate/components/modal.coffee
@@ -61,6 +61,8 @@
       $('.modal-title span', @modal).html title

   setContent: (content, reload = true) =>
+    content = content() if typeof content is 'function'
+
     $('.modal-content', @modal).html content

     @settings._cachedHeight = undefined


Bugfix for apt import

Bugfix for apt import

--- src/scripts/services/converter.coffee
+++ src/scripts/services/converter.coffee
@@ -224,6 +224,7 @@

 						for edge in postset
 							if edge isnt ""
+								existingEdge = false
 								if @isPartOfString("*", edge)
 									weight = edge.split("*")[0]
 									place = net.getNodeByText(edge.split("*")[1])


tasks: avoid double clicks

tasks: avoid double clicks

--- src/smc-webapp/tasks/show-toggle.cjsx
+++ src/smc-webapp/tasks/show-toggle.cjsx
@@ -28,6 +28,12 @@
         return <Icon name={name} />

     toggle_state: ->
+        # avoid accidental double clicks...
+        now = new Date()
+        if now - (@_last_call ? 0) <= 300
+            return
+        @_last_call = now
+
         if @props.show
             @props.actions["stop_showing_#{@props.type}"]()
         else
@@ -44,6 +50,7 @@
             style.color = '#666'
         else
             style.color = '#999'
+        # Debounce is to avoid accidental double clicks.

                 {toggle}


fix tool icon bug

fix tool icon bug

--- client/code/model/tool.coffee
+++ client/code/model/tool.coffee
@@ -12,7 +12,7 @@

   # :TODO: Horrible kludge to avoid tool manifest changes right now
   initialize: ->
-    @on 'add', =>
+    @on 'add change', =>
       manifest = @get('manifest')
       n = manifest.displayName.toLowerCase()
       if n.indexOf('in your browser') > -1


fix mass threshold

fix mass threshold


Add fake processing

Add fake processing


corrected misindented return null

corrected misindented return null

--- src/draft4/objects.coffee
+++ src/draft4/objects.coffee
@@ -88,6 +88,6 @@
       if @test_type "object", data
         for test in tests
           test data, runtime
-      null
+        null


Rework update strategy, and generalize filters

Instead of having to recreate the data map for each collection update, now we
store the updated version and make changes there. Also filters were separated in
a different class to make it easier to extend and create new ones.

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -1,18 +1,8 @@
 class Backbone.TableView extends Backbone.View
     tagName: "div"
     titleTemplate: _.template "
<%= model %>
"
-    filtersTemplate: _.template """
-        <% _.each(model, function (filter) { %>
-            <%= filter %>
-        <% }) %>
-    """
     searchTemplate: _.template """
         <input type="text" class="search-query pull-right" placeholder="<%= model.detail || model %>"></input>
-    """
-    inputTemplate: _.template """
-

-            <%= name %><input id="<%= id %>" type="text" class="filter <%= className %>" value="<%= init %>"></input>
-

     """
     paginationTemplate: _.template """

@@ -47,8 +37,7 @@
                 <%= title %>

-

-                <%= filters %>
+

@@ -76,96 +65,89 @@
         <%= pagination %>
     """
     events:
-        "keypress .search-query": "updateOnEnter"
-        "change   .filter":       "update"
+        "keypress .search-query": "updateSearchOnEnter"
         "click    .pager-prev":   "prevPage"
         "click    .pager-next":   "nextPage"
         "click    th":            "toggleSort"

     initialize: ->
         @collection.on "reset", @renderData
-        @page = @page or 1
-        @size = @size or 10
+        @data = @options.initialData or @initialData or {}
+        @data.page = @options.page or @page or 1
+        @data.size = @options.size or @size or 10
         return @

-    printFilter: (name, filter) =>
+    setData: (id, val) =>
+        @data[id] = val
+        @update()
+
+    createFilter: (name, filter) =>
         switch filter.type
             when "input"
-                return @inputTemplate
-                    name: @capitalize(name)
-                    id: "filter" + name
+                return new InputFilter
+                    id: name
                     init: filter.init or ""
-                    className: filter.className or ""
-        return ""
+                    className: "input-prepend inline"
+                    filterClass: filter.className or ""
+                    get: filter.get or _.identity
+                    setData: @setData
+        # For custom filters, we just provide the setData function
+        filter.setData = @setData
+        return filter

-    updateOnEnter: (e) =>
-        if e.keyCode == 13 then @update()
+    updateSearchOnEnter: (e) =>
+        if e.keyCode == 13
+            val = e.currentTarget.value
+            if val
+                @data[@search.query or "q"] = val
+            else
+                delete @data[@search.query or "q"]
+            @update()
         return @

     update: =>
-        data = {}
-        for key, val of @initialData
-            data[key] = val
-        for filter, options of @filters
-            field = $("#filter" + filter, @$el)
-            if field
-                val = field.val()
-                data[filter] = (options.get? and options.get(val)) or val
-        if @search
-            data[@search.query or "q"] = $(".search-query", @$el).val()
-        if @sortDir and @sortCol
-            data.sort_dir = @sortDir
-            data.sort_col = @sortCol
-        data.page = @page
-        data.size = @size
-        @collection.fetch data: data
+        @collection.fetch data: @data
         return @

-    capitalize: (str) ->
-        str.charAt(0).toUpperCase() + str.substring(1).toLowerCase()
-
     renderData: =>
-        $elData = $("tbody", @$el)
-        if $elData
-            $elData.html @dataTemplate
-                collection: @collection
-                columns:    @columns
-                empty:      @empty or ""
-        $(".page", @$el).html @page
+        $("tbody", @$el).html @dataTemplate
+            collection: @collection
+            columns:    @columns
+            empty:      @empty or "No records to show"
         return @

     prevPage: =>
-        @page = @page - 1
-        if @page < 1
-            @page = 1
-        else
+        if @data.page > 1
+            @data.page = @data.page - 1
+            $(".page", @$el).html @data.page
             @update()

     nextPage: =>
         # Since we don't have a collection count, for now we use the size of
         # the last GET as an heuristic to limit the use of nextPage
-        if @collection.length == @size
-            @page = @page + 1
+        if @collection.length == @data.size
+            @data.page = @data.page + 1
+            $(".page", @$el).html @data.page
             @update()

     toggleSort: (e) =>
         el = e.currentTarget
         cl = el.className
         if cl.indexOf("sorting_desc") >= 0
-            @sortDir = "asc"
+            @data.sort_dir = "asc"
             cl = "sorting_asc"
         else if cl.indexOf("sorting") >= 0 or cl.indexOf("sorting_asc") >= 0
-            @sortDir = "desc"
+            @data.sort_dir = "desc"
             cl = "sorting_desc"
         else
             return @
         $("th.sorting_desc, th.sorting_asc", @$el).removeClass("sorting_desc sorting_asc")
         $(el, @$el).addClass(cl)
-        @sortCol = el.abbr
+        @data.sort_col = el.abbr
         @update()

     applyTemplate: (template, model) ->
-        (model? and model and template model: model) or ""
+        (model and template model: model) or ""

     render: =>
         @$el.html @template
@@ -173,6 +155,37 @@
             empty:      @empty or ""
             title:      @applyTemplate @titleTemplate,      @title
             search:     @applyTemplate @searchTemplate,     @search
-            filters:    @applyTemplate @filtersTemplate,    _.map(@filters, (filter, name) => @printFilter name, filter)
             pagination: @applyTemplate @paginationTemplate, @pagination
+
+        @filters = _.map(@filters, (filter, name) => @createFilter(name, filter))
+        filtersDiv = $(".filters", @$el)
+        _.each @filters, (filter) ->
+            filtersDiv.append filter.render().el
+            filtersDiv.append " "
         @update()
+
+class Filter extends Backbone.View
+    tagName: "div"
+    className: "inline"
+
+    initialize: ->
+        @id = @options.id
+        @setData = @options.setData
+
+    prettyName: (str) ->
+        str.charAt(0).toUpperCase() + str.substring(1).replace(/_(\w)/g, (match, p1) -> " " + p1.toUpperCase())
+
+    render: =>
+        @options.name = @prettyName(@id)
+        @$el.html @template @options
+        return @
+
+class InputFilter extends Filter
+    template: _.template """
+        <%= name %><input type="text" class="filter <%= filterClass %>" value="<%= init %>"></input>
+    """
+    events:
+        "change .filter": "update"
+
+    update: (e) =>
+        @setData @id, @options.get e.currentTarget.value


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/point.coffee
+++ src/point.coffee
@@ -46,7 +46,7 @@
     ###*
     @name clamp
     @methodOf Point#
-    @pa
+    @param n
     @returns {Point} A new point whose magnitude has been clamped to the given value.
     ###
     clamp: (n) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/point.coffee
+++ src/point.coffee
@@ -44,6 +44,8 @@

   Point:: =
     ###*
+
+
     @name clamp
     @methodOf Point#
     @param {Number} n Maximum value for magnitude.


More touchups and todo items

More touchups and todo items

--- src/sound.coffee
+++ src/sound.coffee
@@ -26,15 +26,15 @@
     volume: 80

   types:
-    'mp3': 'audio/mpeg',
-    'ogg': 'audio/ogg',
-    'wav': 'audio/wav',
-    'aac': 'audio/aac',
+    'mp3': 'audio/mpeg'
+    'ogg': 'audio/ogg'
+    'wav': 'audio/wav'
+    'aac': 'audio/aac'
     'm4a': 'audio/x-m4a'

   sounds: []

-  el: document.createElement( 'audio' )
+  el: document.createElement('audio')

   all: ->
     return new @group(@sounds)
@@ -139,6 +139,8 @@
   # ----------------------------
   #   SOUND INSTANCES
   # ----------------------------
+
+  # TODO: Figure out how to make this into a class

   sound: (src, options={}) ->
     pid        = 0
@@ -217,7 +219,7 @@

     @stop = ->
       return @ if !supported
-      @setTime( @getDuration() )
+      @setTime(@getDuration())
       @sound.pause()
       return @

@@ -413,7 +415,7 @@
         for event, i in events
           namespace = event.idx.split('.')
           if event.idx == idx || (namespace[1] && namespace[1] == idx.replace('.', '') )
-            @sound.removeEventListener( type, event.func, true );
+            @sound.removeEventListener(type, event.func, true)
             events.splice(i, 1) # remove event

       return @
@@ -439,7 +441,7 @@

         for event in events
           eventType = event.idx.split('.')
-          if event.idx == idx || ( eventType[0] && eventType[0] == idx.replace('.', '') )
+          if event.idx == idx || (eventType[0] && eventType[0] == idx.replace('.', '') )
                 evt = document.createEvent('HTMLEvents')
                 evt.initEvent(eventType[0], false, true)
                 @sound.dispatchEvent(evt)
@@ -457,7 +459,7 @@
         duration = duration || Kona.Sound.defaults.duration

       from = @volume
-      delay = duration / Math.abs( from - to )
+      delay = duration / Math.abs(from - to)
       self = @
       @play()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/state.coffee
+++ src/state.coffee
@@ -5,13 +5,11 @@
     currentState: null
     initialState: null

-  self = GameObject(I).extend
-    currentState = (val) ->
-      if val != undefined
-        I.currentState = val
-
-        return self
-      else
-        return I.currentState
-
-  self
+
+  currentState: (val) ->
+    if val != undefined
+      I.currentState = val
+
+      return self
+    else
+      return I.currentState


Add removeEventHandler method

Add removeEventHandler method

--- src/state.coffee
+++ src/state.coffee
@@ -37,4 +37,11 @@
       @eventHandlers[evtType] = []
     if callback not in @eventHandlers[evtType]
       @eventHandlers[evtType].push callback
+    return
+  removeEventHandler: (evtType, callback) ->
+    if evtType of @eventHandlers
+      for cb, i in @eventHandlers[evtType]
+        if cb is callback
+          @eventHandlers[evtType].splice(i, 1)
+          break
     return


Use module variable naming conventions

Use module variable naming conventions

--- src/token.coffee
+++ src/token.coffee
@@ -10,7 +10,7 @@
 StartDotRegex = /^\.?/
 WhitespaceRegex = /\S/

-maxTokenLength = 20000
+MaxTokenLength = 20000

 # Private: Represents a single unit of text as selected by a grammar.
 module.exports =
@@ -167,10 +167,10 @@

       fragments = [leadingHtml]

-      if @value.length > maxTokenLength
+      if @value.length > MaxTokenLength
         while startIndex < endIndex
-          fragments.push "" + @escapeString(@value, startIndex, startIndex + maxTokenLength) + ""
-          startIndex += maxTokenLength
+          fragments.push "" + @escapeString(@value, startIndex, startIndex + MaxTokenLength) + ""
+          startIndex += MaxTokenLength
       else
         fragments.push @escapeString(@value, startIndex, endIndex)


generalize combineLines

generalize combineLines

--- src/utils.coffee
+++ src/utils.coffee
@@ -36,12 +36,12 @@
         else return 'SPAN'

   Node:
-    combineLines: (line1, line2) ->
-      children = _.clone(line2.childNodes)
+    combineNodes: (node1, node2) ->
+      children = _.clone(node2.childNodes)
       _.each(children, (child) ->
-        line1.appendChild(child)
+        node1.appendChild(child)
       )
-      line2.parentNode.removeChild(line2)
+      node2.parentNode.removeChild(node2)

     extract: (editor, startIndex, endIndex) ->
       [startLine, startOffset] = Tandem.Utils.Node.getChildAtOffset(editor.iframeDoc.body, startIndex)
@@ -58,7 +58,7 @@
         fragment.appendChild(rightStart)
         rightStart = next

-      Tandem.Utils.Node.combineLines(leftStart, rightEnd) if leftStart? && rightEnd?
+      Tandem.Utils.Node.combineNodes(leftStart, rightEnd) if leftStart? && rightEnd?

       return fragment


Fix path bug in menu generation

Fix path bug in menu generation

--- src/utils.coffee
+++ src/utils.coffee
@@ -31,12 +31,12 @@

 # Make `link` objects for the menu.
-exports.makeMenu = (files) ->
+exports.makeMenu = (files, inPath) ->
   menu = {}
   for file in files
     link =
       name: path.basename(file, path.extname file)
-      href: 'html/' + makeDestination file
+      href: 'html/' + makeDestination file, inPath
     parts = file.split('/').splice(1)
     key = if parts.length > 1 then parts[0] else './'
     if menu[key]?


added more exprs to filter desc.

added more exprs to filter desc.

--- src/data/ops/filter.coffee
+++ src/data/ops/filter.coffee
@@ -3,7 +3,7 @@
 #
 # {
 #   col: colname
-#   op: "<", ">", "<=", ">=", "=", "!="
+#   op: "<", ">", "<=", ">=", "=", "!=", 'in', 'of'
 #   val: VALUE
 # }
 #
@@ -81,11 +81,28 @@
       desc.op ?= '='

     if desc.op? and desc.col?
-      desc.op = switch desc.op
-        when '=' then '=='
-        else desc.op
-      cmd = "(row.get('#{desc.col}') #{desc.op} #{JSON.stringify desc.val})"
-      desc.f = Function("row", "return #{cmd}")
+      desc.f = switch desc.op
+        when '='
+          cmd = "(row.get('#{desc.col}') == #{JSON.stringify desc.val})"
+          Function("row", "return #{cmd}")
+        when '<>'
+          cmd = "(row.get('#{desc.col}') != #{JSON.stringify desc.val})"
+          Function("row", "return #{cmd}")
+        when 'in'
+          ((col, val) ->
+            (row) -> row.get(col) in val
+          )(desc.col, desc.val)
+        when 'of'
+          ((col, val) ->
+            (row) -> row.get(col) of val
+          )(desc.col, desc.val)
+        when 'between', 'btwn'
+          lastIdx = desc.val.length-1
+          cmd = "(row.get('#{desc.col}') >= #{desc.val[0]} && row.get('#{desc.col}') <= #{desc.val[1]})"
+          Function("row", "return #{cmd}")
+        else
+          cmd = "(row.get('#{desc.col}') #{desc.op} #{JSON.stringify desc.val})"
+          Function("row", "return #{cmd}")
     else if desc.f?
       if desc.col? and desc.col != '*'
         desc.f = ((f, col) ->


Improve code style.

Improve code style.

--- src/watch.coffee
+++ src/watch.coffee
@@ -80,9 +80,11 @@
 #
 # Returns nothing.
 initWatcher = (config, callback) ->
-  watched = config.paths.watched.concat config._normalized.paths.allConfigFiles
-  watched = watched.concat.apply watched, config._normalized.packageInfo['bower'].components.map (_) -> _.files
-  watched = watched.concat.apply watched, config._normalized.packageInfo['component'].components.map (_) -> _.files
+  {paths} = config._normalized
+  {bower, component} = config._normalized.packageInfo
+  getFiles = (pkgs) -> pkgs.components.map (_) -> _.files
+  watched = paths.watched.concat
+    paths.allConfigFiles, getFiles(bower), getFiles(component)

   exists = (path, callback) ->
     fs_utils.exists path, (value) ->


Store the range, not the version, in the resulting attribute names

--- bin/npm2nix.coffee
+++ bin/npm2nix.coffee
@@ -5,8 +5,8 @@

 generated = {}

-generateExpr = (name, version) ->
-   generated[name + "-" + version] = true
+generateExpr = (name, range) ->
+   generated[name + "-" + range] = true
    http.get "http://registry.npmjs.org/#{name}", (res) ->
     res.setEncoding()
     val = ""
@@ -15,7 +15,7 @@
     res.on 'end', ->
       pkginfo = JSON.parse val
       versions = (key for own key, value of pkginfo.versions)
-      version = semver.maxSatisfying versions, version
+      version = semver.maxSatisfying versions, range
       deps = pkginfo.versions[version].dependencies ? {}
       (generateExpr(nm, ver) unless generated[nm + "-" + ver]?) for nm, ver of deps
       http.get "http://registry.npmjs.org/#{name}/-/#{name}-#{version}.tgz", (res) ->
@@ -24,9 +24,9 @@
           hash.update chunk
         res.on 'end', ->
           console.log """
-            #{}  "#{name}" = self."#{name}-#{version}";
+            #{}  "#{name}" = self."#{name}-#{range}";

-              "#{name}-#{version}" = self.buildNodePackage rec {
+              "#{name}-#{range}" = self.buildNodePackage rec {
                 name = "#{name}-#{version}";
                 src = fetchurl {
                   url = "http://registry.npmjs.org/#{name}/-/${name}.tgz";


test reset and clean a little

test reset and clean a little

--- src/test/services/registration.coffee
+++ src/test/services/registration.coffee
@@ -63,20 +63,10 @@
     }
   )

-  formSend = false
-  angular.module('CoolFormServices').factory('networkService', ->
-    return ->
-      sendForm = (params, data) ->
-        formSend = true
-      return {
-        sendForm: sendForm
-      }
-    )
-
-  it 'should deal wtih a value', inject((registrationService) ->
+  it 'should test validation', inject((registrationService) ->

     service = registrationService(form)
-    changeValue = service.registerField('field1', null)
+    changeEmail = service.registerField('field1', null)
     fieldValue = false
     fieldError = false
     handlers =
@@ -92,38 +82,80 @@
     service.watchField('field1', handlers)

     expect(fieldValue).toEqual false
-    changeValue(true)
+    changeEmail(true)
     expect(fieldValue).toEqual true
-    changeValue(42)
+    changeEmail(42)
     expect(fieldValue).toEqual 42

     expect(fieldError).toEqual false
     service.validateField('field1')
     expect(fieldError).toEqual true
-    changeValue('fred@mail.com')
+    changeEmail('fred@mail.com')
     expect(fieldError).toEqual false
     service.validateField('field1')
     expect(fieldError).toEqual false

-    changeValue('wrong')
+    changeEmail('wrong')
     expect(fieldError).toEqual false
     service.validateAll()
     expect(fieldError).toEqual true
-    changeValue('fred@mail.com')
+    changeEmail('fred@mail.com')

     # dependencie validation
-    changeValue = service.registerField('field2', null)
+    changeTest = service.registerField('field2', null)
     expect(service.validateField('field2')).toEqual false
-    changeValue('test')
+    changeTest('test')
     expect(service.validateField('field2')).toEqual true
-    changeValue(false)
-
-    # submit
+    changeTest(false)
+  )
+
+  formSend = false
+  angular.module('CoolFormServices').factory('networkService', ->
+    return ->
+      sendForm = (params, data) ->
+        formSend = true
+      return {
+        sendForm: sendForm
+      }
+    )
+
+  it 'should test submit', inject((registrationService) ->
+    service = registrationService(form)
+    changeEmail = service.registerField('field1', null)
+    changeTest = service.registerField('field2', null)
+
+    changeEmail("wrong")
+    changeTest("test")
     expect(formSend).toEqual false
     service.submit()
     expect(formSend).toEqual false
-    changeValue('test')
+    changeEmail("fred.delbos@gmail.com")
     service.submit()
     expect(formSend).toEqual true
+  )

+  it 'should test reset', inject((registrationService) ->
+    value1 = null
+    handler1 =
+      change: (v) -> value1 = v
+    value2 = null
+    handler2 =
+      change: (v) -> value2 = v
+    service = registrationService(form)
+    changeEmail = service.registerField('field1', null)
+    changeTest = service.registerField('field2', null)
+
+    expect(service.validateAll()).toEqual false
+    service.watchField('field1', handler1)
+    service.watchField('field2', handler2)
+    changeEmail("fred@gmail.com")
+    changeTest("test")
+    expect(service.validateAll()).toEqual true
+    service.reset()
+    expect(service.validateAll()).toEqual false
+    expect(value1).toEqual null
+    expect(value2).toEqual null
+    changeEmail("fred@gmail.com")
+    changeTest("test")
+    expect(service.validateAll()).toEqual true
   )


Fix class attribute

Fix class attribute

--- src/trix/views/attachment_view.coffee
+++ src/trix/views/attachment_view.coffee
@@ -35,7 +35,7 @@
       @progressElement = makeElement
         tagName: "progress"
         attributes:
-          className: "attachment__progress"
+          class: "attachment__progress"
           value: @attachment.getUploadProgress()
           max: 100
         data:


fix a bug found during ES6 conversion.  spec_id -> spec.id


remove comment

remove comment


[PUBDEV-6206] Fix Combine predictions with frame. (#124)

Wrong import name.

--- src/ext/modules/h2o.coffee
+++ src/ext/modules/h2o.coffee
@@ -6,7 +6,7 @@
   CloudOutput: require('../components/cloud-output')
   ColumnSummaryOutput: require('../components/column-summary-output')
   CreateFrameInput: require('../components/create-frame-input')
-  DataFrameOutput: require('../components/dataframe-output.pug')
+  DataFrameOutput: require('../components/dataframe-output')
   DataFramesOutput: require('../components/dataframes-ouput')
   DeleteObjectsOutput: require('../components/delete-objects-output')
   ExportFrameInput: require('../components/export-frame-input')
@@ -53,7 +53,7 @@
   RDDsOutput: require('../components/rdds-output')
   ScalaCodeOutput: require('../components/scala-code-output')
   ScalaIntpOutput: require('../components/scala-intp-output')
-  BindFramesOutput: require('../components/bind-frames-output.pug')
+  BindFramesOutput: require('../components/bind-frames-output')
   SplitFrameInput: require('../components/split-frame-input')
   SplitFrameOutput: require('../components/split-frame-output')
   StackTraceOutput: require('../components/stacktrace-output')


selection highlighting for tutorial list

--- client/landing/app/Applications/Activity.kdapplication/ListItems/SelectableActivityListItemTutorial.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ListItems/SelectableActivityListItemTutorial.coffee
@@ -107,15 +107,8 @@

   click:(event)->

-    log @parent, @parent.$("div")
-    @parent.$("div.tutorial.selectable.selected").each (i,e)->
-      log i;e.removeClass "selected"
-    # $(event.target).parent("div.kdlistview").each (i, element)->
-      # element.removeClass "selected"
+    @parent.parent.$("div.tutorial.selected").removeClass "selected"
     @setClass "selected"
-  #   if $(event.target).closest("[data-paths~=title],[data-paths~=body],[data-paths~=preview]")
-  #     if not $(event.target).is("a.action-link, a.count, .like-view")
-  #       appManager.tell "Activity", "createContentDisplay", @getData()

   applyTextExpansions:(str = "")->
     str = @utils.expandUsernames str


fixed coffeelint errors on workers/social/lib/social/models/computeproviders/google.coffee

--- workers/social/lib/social/models/computeproviders/google.coffee
+++ workers/social/lib/social/models/computeproviders/google.coffee
@@ -2,88 +2,90 @@

 module.exports = class Google extends ProviderInterface

-  @ping = (client, callback)->
+  @ping = (client, callback) ->
     callback null, "Google. #{ client.connection.delegate.profile.nickname }!"

-  @create = (client, options, callback)->
+  @create = (client, options, callback) ->

     { credential, name } = options

-    @fetchCredentialData credential, (err, credential)->
+    @fetchCredentialData credential, (err, credential) ->

       return callback err  if err?

       meta = {
-        "type": "googlecompute",
-        "bucket_name": "my-project-packer-images",
-        "client_secrets_file": credential.clientSecretsContent,
-        "private_key_file": credential.privateKeyContent,
-        "project_id": credential.projectId,
-        "source_image": "debian-7-wheezy-v20131014",
-        "zone": "us-central1-a"
+        'type': 'googlecompute',
+        'bucket_name': 'my-project-packer-images',
+        'client_secrets_file': credential.clientSecretsContent,
+        'private_key_file': credential.privateKeyContent,
+        'project_id': credential.projectId,
+        'source_image': 'debian-7-wheezy-v20131014',
+        'zone': 'us-central1-a'
       }

       callback null, { meta }

-  @fetchAvailable = (client, options, callback)->
+  @fetchAvailable = (client, options, callback) ->

     callback null, [
       {
-        name  : "n1-standard-1"
-        title : "N1 Standard 1"
+        name  : 'n1-standard-1'
+        title : 'N1 Standard 1'
         spec  : {
-          cpu : 1, ram: 3.75, storage: "n/a"
+          cpu : 1, ram: 3.75, storage: 'n/a'
         }
-        price : "$0.070 per Hour"
+        price : '$0.070 per Hour'
       }
       {
-        name  : "n1-standard-2"
-        title : "N1 Standard 2"
+        name  : 'n1-standard-2'
+        title : 'N1 Standard 2'
         spec  : {
-          cpu : 2, ram: 7.5, storage: "n/a"
+          cpu : 2, ram: 7.5, storage: 'n/a'
         }
-        price : "$0.140 per Hour"
+        price : '$0.140 per Hour'
       }
       {
-        name  : "n1-standard-4"
-        title : "N1 Standard 4"
+        name  : 'n1-standard-4'
+        title : 'N1 Standard 4'
         spec  : {
-          cpu : 4, ram: 15, storage: "n/a"
+          cpu : 4, ram: 15, storage: 'n/a'
         }
-        price : "$0.280 per Hour"
+        price : '$0.280 per Hour'
       }
       {
-        name  : "n1-standard-8"
-        title : "N1 Standard 8"
+        name  : 'n1-standard-8'
+        title : 'N1 Standard 8'
         spec  : {
-          cpu : 8, ram: 30, storage: "n/a"
+          cpu : 8, ram: 30, storage: 'n/a'
         }
-        price : "$0.560 per Hour"
+        price : '$0.560 per Hour'
       }
       {
-        name  : "n1-highmem-2"
-        title : "N1 Highmem 2"
+        name  : 'n1-highmem-2'
+        title : 'N1 Highmem 2'
         spec  : {
-          cpu : 2, ram: 13, storage: "n/a"
+          cpu : 2, ram: 13, storage: 'n/a'
         }
-        price : "$0.164 per Hour"
+        price : '$0.164 per Hour'
       }
       {
-        name  : "n1-highmem-4"
-        title : "N1 Highmem 4"
+        name  : 'n1-highmem-4'
+        title : 'N1 Highmem 4'
         spec  : {
-          cpu : 4, ram: 26, storage: "n/a"
+          cpu : 4, ram: 26, storage: 'n/a'
         }
-        price : "$0.328 per Hour"
+        price : '$0.328 per Hour'
       }
       {
-        name  : "n1-highmem-8"
-        title : "N1 Highmem 8"
+        name  : 'n1-highmem-8'
+        title : 'N1 Highmem 8'
         spec  : {
-          cpu : 8, ram: 52, storage: "n/a"
+          cpu : 8, ram: 52, storage: 'n/a'
         }
-        price : "$0.656 per Hour"
+        price : '$0.656 per Hour'
       }

     ]
+
+


minor upgrades to the static assets connector


Fixed chat template on a tournament page

--- static/script/pages/tournament.coffee
+++ static/script/pages/tournament.coffee
@@ -36,19 +36,15 @@
 '

 window.settlers.templates.chat_post = doT.template '
-

+

-        Unread
-        {{? it.user.is_online}}
-
-        {{?}}
-        {{= it.user.name}}
-        {{? it.user.is_online}}
-
-        {{?}} - {{= it.time}}
+        {{= window.hlib._g("Unread")}}
+        {{= window.settlers.fmt_player(it) }} - {{= it.time}}
+
+
+

-

{{= it.message}}


truncate

truncate

--- src/components/Base.coffee
+++ src/components/Base.coffee
@@ -19,7 +19,6 @@
   handleTabletop: (data, tabletop)->
     if not data then return

-    # normalize data
     map_columns =
       andyouremailaddress: "email"
       anyotherremarks: "remarks"


Context: Correctly return rejected promises when calling a command on a destroyed context

--- src/context/context.coffee
+++ src/context/context.coffee
@@ -145,10 +145,9 @@

   command: (commandName, params) ->
     if @_isDestroyed
-      Promise.reject new Error """
+      return Promise.reject new Error """
         Context #{@name} was destroyed, cannot execute command #{commandName} with arguments #{JSON.stringify(params)}
       """
-      return

     executingCommand = new Promise (resolve, reject) =>
       @_verifyContextIsInitialized commandName


Fix brain_test.coffee

Fix brain_test.coffee

We need to pass a Robot (well, a mock Robot) to the Brain constructor.
And we need to wait for the save interval to elapse before testing
whether the brain was saved/closed, so do it in a timeout rather than
whenever the process exits.

--- test/brain_test.coffee
+++ test/brain_test.coffee
@@ -6,7 +6,10 @@
 closing = false
 closed  = false

-brain = new Brain
+fakeRobot =
+  on: ->
+
+brain = new Brain fakeRobot

 brain.on 'save', (data) ->
   is_closing = closing
@@ -19,7 +22,7 @@

 brain.data.abc = 1
 brain.resetSaveInterval 0.1
-
-process.on 'exit', ->
+setTimeout ->
   assert.ok saved
   assert.ok closed
+, 200


docs(macMenu): Updated comments on how to use custom templating

--- src/directives/menu.coffee
+++ src/directives/menu.coffee
@@ -5,7 +5,9 @@
 @description
 A directive for creating a menu with multiple items

-Since macMenu is using ngRepeat, some ngRepeat properities along with `item` are exposed on the local scope of each template instance, including:
+Menu allows for custom html templating for each item.
+
+Since macMenu is using ngRepeat, some ngRepeat properties along with `item` are exposed on the local scope of each template instance, including:

 | Variable  | Type    | Details                                                                     |
 |-----------|---------|-----------------------------------------------------------------------------|
@@ -16,6 +18,13 @@
 | `$even`   | Boolean | true if the iterator position `$index` is even (otherwise false).           |
 | `$odd`    | Boolean | true if the iterator position `$index` is odd (otherwise false).            |
 | `item`    | Object  | item object                                                                 |
+
+To use custom templating
+```
+<mac-menu>
+  <span> { {item.label}} </span>
+</mac-menu>
+```

 Template default to `item.label` if not defined


added selectLayer to Context

added selectLayer to Context

--- framer/Context.coffee
+++ framer/Context.coffee
@@ -131,6 +131,9 @@
 		return layer if layer
 		return @layerForElement(element.parentNode)

+	selectLayers: (selector) ->
+		return _.filter @_layers, (layer) -> Utils.layerMatchesSelector(layer, selector)
+
 	# Animations
 	@define "animations", get: -> _.clone(@_animations)


Not converting the Path point through the root context

Fixes https://github.com/motif/company/issues/6589

--- framer/SVGPath.coffee
+++ framer/SVGPath.coffee
@@ -136,13 +136,13 @@

 	start: (relativeToLayer = null) =>
 		point = @pointAtFraction(0)
-		point = @convertPointToLayer(point, relativeToLayer?.parent)
+		point = @convertPointToLayer(point, relativeToLayer?.parent, false)
 		point.rotation = @rotationAtFraction(0)
 		return point

 	end: (relativeToLayer = null) =>
 		point = @pointAtFraction(0)
-		point = @convertPointToLayer(point, relativeToLayer?.parent)
+		point = @convertPointToLayer(point, relativeToLayer?.parent, false)
 		point.rotation = @rotationAtFraction(1)
 		return point


minor cleanup

minor cleanup


Minor improvement in KiCad generator

Minor improvement in KiCad generator

--- src/kicad-generator.coffee
+++ src/kicad-generator.coffee
@@ -146,8 +146,8 @@
     fs.writeSync fd, "F0 \"#{element.refDes}\" #{refObj.x} #{refObj.y} #{refObj.fontSize} #{refObj.orientation} #{refObj.visible} #{refObj.halign} #{refObj.valign}NN\n"
     fs.writeSync fd, "F1 \"#{element.name}\" #{nameObj.x} #{nameObj.y} #{nameObj.fontSize} #{nameObj.orientation} #{nameObj.visible} #{nameObj.halign} #{nameObj.valign}NN\n"
     fs.writeSync fd, "F2 \"#{patternName}\" 0 0 0 H I C CNN\n"
-    element.datasheet ?= ""
-    fs.writeSync fd, "F3 \"#{element.datasheet}\" 0 0 0 H I C CNN\n"
+    if element.datasheet?
+      fs.writeSync fd, "F3 \"#{element.datasheet}\" 0 0 0 H I C CNN\n"
     i = 0
     for shape in symbol.shapes
       if (shape.kind is 'attribute') and (shape.name isnt 'refDes') and (shape.name isnt 'name')


Do not use @ when solely referring to this

--- src/lib/byte-buffer.coffee
+++ src/lib/byte-buffer.coffee
@@ -1,17 +1,17 @@
 attr = require('attr-accessor')

 class ByteBuffer
-  module.exports = @
+  module.exports = this

   # Byte order constants
   @LITTLE_ENDIAN = true
   @BIG_ENDIAN    = false

   # Reference to ByteBuffer
-  self = @
+  self = this

   # Shielded utility methods for creating getters/setters on the prototype
-  [get, set] = attr.accessors(@)
+  [get, set] = attr.accessors(this)

   # Creates a new ByteBuffer
   # - from given source (assumed to be number of bytes when numeric)
@@ -140,18 +140,18 @@
   # Sets index to front of the buffer
   front: ->
     @_index = 0
-    return @
+    return this

   # Sets index to end of the buffer
   end: ->
     @_index = @length
-    return @
+    return this

   # Seeks given number of bytes
   # Note: Backwards seeking is supported
   seek: (bytes=1) ->
     @index += bytes
-    return @
+    return this

   # Retrieves number of available bytes
   get available: ->
@@ -179,7 +179,7 @@

       @_view[method](@_index, value, order)
       @_index += bytes
-      return @
+      return this

   # Readers for bytes, shorts, integers, floats and doubles
   readByte: reader('getInt8', 1)
@@ -239,7 +239,7 @@

     @_raw.set(view, @_index)
     @_index += view.byteLength
-    return @
+    return this

   # Reads UTF-8 encoded string of given number of bytes (defaults to number of bytes available)
   #
@@ -438,7 +438,7 @@
     view.set(@_raw, bytes)
     @_index += bytes
     @buffer = view.buffer
-    return @
+    return this

   # Appends given number of bytes
   append: (bytes) ->
@@ -448,7 +448,7 @@
     view = new Uint8Array(@length + bytes)
     view.set(@_raw, 0)
     @buffer = view.buffer
-    return @
+    return this

   # Clips this buffer
   clip: (begin = @_index, end = @length) ->
@@ -457,7 +457,7 @@
     buffer = @_buffer.slice(begin, end)
     @_index -= begin
     @buffer = buffer
-    return @
+    return this

   # Slices this buffer
   slice: (begin = 0, end = @length) ->
@@ -474,7 +474,7 @@
   reverse: ->
     Array::reverse.call(@_raw)
     @_index = 0
-    return @
+    return this

   # Array of bytes in this buffer
   toArray: ->


spritesheet layout - try to put stuff near the top

--- src/lib/spritesheet.coffee
+++ src/lib/spritesheet.coffee
@@ -1,4 +1,18 @@
 {Vec2d} = require('./vec2d')
+
+byReverseHeightThenFilename = (a, b) ->
+  diff = b.size.y - a.size.y
+  if diff is 0
+    if b.filename > a.filename then 1 else -1
+  else
+    diff
+
+byXThenY = (a, b) ->
+  diff = a.x - b.x
+  if diff is 0
+    a.y - b.y
+  else
+    diff

 exports.Spritesheet = class Spritesheet
   # a list of objects that have a .size and a .filename
@@ -7,20 +21,13 @@
   # figure out where to place each image in the sprite sheet
   # sets the .pos such that they are in a spritesheet
   calculatePositions: ->
-    # reverse sort by height, filename
-    @image_list.sort (a, b) ->
-      diff = b.size.y - a.size.y
-      if diff is 0
-        if b.filename > a.filename then 1 else -1
-      else
-        diff
+    @image_list.sort byReverseHeightThenFilename

     @size = new Vec2d()
     positions = []

     for image, image_i in @image_list
-      # keep positions sorted by x
-      positions.sort (a, b) -> a.x - b.x
+      positions.sort byXThenY

       image.pos = do =>
         for pos, pos_i in positions


AppStorage: getValue: return cloned data

--- client/app/lib/appstorage.coffee
+++ client/app/lib/appstorage.coffee
@@ -77,9 +77,12 @@
   getValue: (key, group = AppStorage.DEFAULT_GROUP_NAME) ->

     appId = @_applicationID
-    return unless @_storage
-    return if @_storageData[group]?[appId]?.data?[key]? then @_storageData[group][appId].data[key]
-    return if @_storage[group]?[appId]?.data?[key]?     then @_storage[group][appId].data[key]
+    data  = do =>
+      return unless @_storage
+      return if @_storageData[group]?[appId]?.data?[key]? then @_storageData[group][appId].data[key]
+      return if @_storage[group]?[appId]?.data?[key]?     then @_storage[group][appId].data[key]
+
+    _.clone data

   setValue: (key, value, callback, group = AppStorage.DEFAULT_GROUP_NAME, notify = no) ->


Use npm pack to generate plugin offline tarballs

--- build_detail.coffee
+++ build_detail.coffee
@@ -37,6 +37,8 @@
 global.EXROOT = global.APPDATA_PATH
 config = require './lib/config'

+npm_exec_path = path.join __dirname, 'node_modules', 'npm', 'bin', 'npm-cli.js'
+
 plugin_json_path = path.join ROOT, 'assets', 'data', 'plugin.json'
 mirror_json_path = path.join ROOT, 'assets', 'data', 'mirror.json'

@@ -294,15 +296,44 @@
     Promise.resolve
       log: "Unsupported platform #{platform}."

-installPluginsTo = async (plugin_names, install_root, server) ->
-  fs.removeAsync install_root
+installPluginsTo = async (plugin_names, install_root, tarball_root, server) ->
+  console.log "tarball_root"+tarball_root
+  fs.removeSync install_root
   fs.ensureDirSync install_root
+  fs.removeSync tarball_root
+  fs.ensureDirSync tarball_root
+
+  # Install plugins
   npmConfig =
     prefix: install_root
     registry: server
   yield promisify(npm.load) npmConfig
   yield promisify(npm.commands.install) plugin_names

+  plugins_dir = (for name in plugin_names
+    console.log name
+    plugin_dir = path.join install_root, 'node_modules', name
+
+    # Modify package.json
+    plugin_package_json = path.join(plugin_dir, 'package.json')
+    contents = require plugin_package_json
+    # Delete this key, otherwise npm install won't succeed
+    delete contents._requiredBy
+    contents.bundledDependencies = (k for k of contents.dependencies)
+    fs.writeFileSync plugin_package_json, JSON.stringify(contents)
+    plugin_dir)
+
+  yield Promise.join (for plugin_dir in plugins_dir
+    new Promise (resolve) ->
+      require('child_process').fork npm_exec_path, ['install', '--no-bin-links'],
+        cwd: plugin_dir
+      .on 'exit', -> resolve())
+
+  yield new Promise (resolve) ->
+      require('child_process').fork npm_exec_path, ['pack'].concat(plugins_dir),
+        cwd: tarball_root
+      .on 'exit', -> resolve()
+
 module.exports.installPluginsAsync = async (poi_version) ->
   build_root = path.join __dirname, build_dir_name
   building_root = path.join build_root, "building_#{poi_version}"
@@ -312,21 +343,14 @@
   mirror = fs.readJsonSync mirror_json_path
   # Don't want to mess with detecting system language here without window.navigator
   language = config.get 'poi.language', 'zh-CN'
-  primaryServer = if language == "zh-CN" then "tsinghua" else "npm"
+  primaryServer = if language == 'zh-CN' then 'taobao' else 'npm'
   server = mirror[config.get "packageManager.mirrorName", primaryServer].server

   plugin_names = (n for n of packages)

   install_root = path.join building_root, 'poiplugins_install'
-  fs.removeSync install_root
-  fs.ensureDirSync install_root
-  yield installPluginsTo plugin_names, install_root, server
-
   gzip_root = path.join building_root, 'poiplugins'
-  fs.removeSync gzip_root
-  fs.ensureDirSync gzip_root
-  for name in plugin_names
-    yield compressGzipAsync path.join(install_root, "node_modules", name), path.join(gzip_root, name+'.tar.gz')
+  yield installPluginsTo plugin_names, install_root, gzip_root, server

   d = new Date()
   str_date = "#{d.getUTCFullYear()}-#{d.getUTCMonth()+1}-#{d.getUTCDate()}"


CADS-1076 : Employment hoursPerWeek was missing space before (optional) ... was rendered ok in view but then getting replaced in javascript and dropping the space. Because javascript updated for past / present tense.


Removed commented-out method

Removed commented-out method

--- src/io/web/renderer.coffee
+++ src/io/web/renderer.coffee
@@ -100,22 +100,6 @@
 	hasMoreLogs: -> no

 	showMoreLogs: ->
-
-	# showList: (@menu) ->
-	# 	if @menu?
-	# 		$('#menu').show().html "
-	#

-	# 				#{@menu.header}
-	#

-	#

-	# 				#{("
#{i}
" for i in @menu.items).join ''}
-	#

-	# 		"
-
-	# 	else
-	# 		$('#menu').hide().html ''
-
-	# 	@invalidate()

 	hideMenu: ->
 		$('#menu').off()


kontrol: properly unwrap responses to watcher

--- src/kontrol/kontrol.coffee
+++ src/kontrol/kontrol.coffee
@@ -39,12 +39,16 @@
         key       : token

   fetchKites: (query = {}, callback) ->
-    @kite.tell 'getKites', [query], (err, { kites: kiteDescriptors }) =>
+    @kite.tell 'getKites', [query], (err, result) =>
       if err?
         callback err
         return

-      callback null, @kiteify kiteDescriptors
+      unless result?
+        callback new Error "No kite found!"
+        return
+
+      callback null, @kiteify result.kites
       return
     return

@@ -78,13 +82,16 @@

   cancelWatcher: (id, callback) ->

-  createUpdateHandler: (changes) -> (err, { action, kite: kiteDescriptor, token }) =>
-    debugger  # this is not reliable AFAICT
+  createUpdateHandler: (changes) -> (response) =>
+    { err, result } = @kite.unwrapMessage response
+
     if err
       changes.emit 'error', err
       return

-    kite = @createKite kiteDescriptor
+    { action, kite, token } = result
+
+    kite = @createKite { kite, token }

     eventName = @constructor.actions[action]
     changes.emit eventName, kite


Prepare AsComponent for decaffeination

Prepare AsComponent for decaffeination

--- src/lib/AsComponent.coffee
+++ src/lib/AsComponent.coffee
@@ -88,7 +88,9 @@
     if hasCallback
       # Handle Node.js style async functions
       values.push (err, res) ->
-        return output.done err if err
+        if err
+          output.done err
+          return
         output.sendDone res
       res = func.apply null, values
       return


Router: Added getHistoryManager and getRoutes methods.

--- src/lib/core/Router.coffee
+++ src/lib/core/Router.coffee
@@ -42,6 +42,66 @@

   ###*
+    Sets HistoryManager's token to update URL. When history token is changed
+    HistoryManager will emit Navigated event. It's handled in here and it will
+    call the correct callback for that route.
+
+    @export
+    @param {string} path Path to change the active route.
+  ###
+  route: (path) ->
+    @historyManager.setToken path
+
+
+  ###*
+    Handle route for current page path. This is useful for handling the initial
+    route of the page when page reload. This method should be called by user
+    because Router doesn't actually know when all routes are added.
+
+    TODO: Rename this method.
+    @export
+  ###
+  init: ->
+    uri = new goog.Uri document.location.href
+    @handleRoute_ uri.getPath()
+
+
+  ###*
+    Returns the currently added routes.
+
+    @export
+    @return {Object} Routes object.
+  ###
+  getRoutes: ->
+    return @routes
+
+
+  ###*
+    Returns spark.core.HistoryManager instance.
+
+    @export
+    @return {spark.core.HistoryManager} HistoryManager instance of the Router.
+  ###
+  getHistoryManager: ->
+    return @historyManager
+
+
+  ###*
+    Execute callback function for particular route path.
+
+    @private
+  ###
+  handleRoute_: (path) ->
+    cb = @routes[path]
+    return cb() if cb
+
+    for route, regex of @routeRegexes when regex.exec path
+      cb = @routes[route]
+
+    cb() if cb # TODO: Pass route params.
+
+
+  ###*
     Creates a regex for route to match actual page link with our routes.
     This method create a regex like `\/activity\/post\/(\w+)\/comment\/(\w+)$`
     to match this route `/activity/post/1/comment/2`. Here is the regex101
@@ -61,42 +121,3 @@
                    .replace captureParams, '(\\w+)'

     @routeRegexes[route] = new RegExp "#{routeRegex}$"
-
-  ###*
-    Sets HistoryManager's token to update URL. When history token is changed
-    HistoryManager will emit Navigated event. It's handled in here and it will
-    call the correct callback for that route.
-
-    @export
-    @param {string} path Path to change the active route.
-  ###
-  route: (path) ->
-    @historyManager.history.setToken path
-
-
-  ###*
-    Execute callback function for particular route path.
-
-    @private
-  ###
-  handleRoute_: (path) ->
-    cb = @routes[path]
-    return cb() if cb
-
-    for route, regex of @routeRegexes when regex.exec path
-      cb = @routes[route]
-
-    cb() if cb # TODO: Pass route params.
-
-
-  ###*
-    Handle route for current page path. This is useful for handling the initial
-    route of the page when page reload. This method should be called by user
-    because Router doesn't actually know when all routes are added.
-
-    TODO: Rename this method.
-    @export
-  ###
-  init: ->
-    uri = new goog.Uri document.location.href
-    @handleRoute_ uri.getPath()


object_extensions

--- src/object_extensions.coffee
+++ src/object_extensions.coffee
@@ -82,10 +82,10 @@
 Helper method that tells you if something is an object.

-  object = {a: 1}
+object = {a: 1}

-  Object.isObject(object)
-  # => true
+Object.isObject(object)
+# => true

 @name isObject


code bin iframe now feeds from nickname.koding.com.. kind of

--- client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeBin.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeBin.coffee
@@ -130,16 +130,60 @@
     @codeView = new KDCustomHTMLView
       tagName  : "iframe"
       cssClass : "result-frame"
-      pistachio : '{{#(content)}}'
+      name : "result-frame"
       attributes:
-        srcdoc:""
+        src: "/unsupported.html"
+        # sandbox : "allow-scripts allow-same-origin"
+        # srcdoc:""
     , data

     @on "CodeBinResultShouldReset",->
-      @$(".result-frame").attr srcdoc : ""
+      # TODO reload the iframe

     @on "CodeBinSourceHasChanges",->
-      @$(".result-frame").attr  srcdoc : "<!DOCTYPE html>"+Encoder.htmlDecode(@getData().attachments[0].content)+""
+
+      if KD.isLoggedIn()
+
+        {nickname}    = KD.whoami().profile
+
+        @tempFileTimestamp = new Date().getTime()
+
+        @tempFilePath = "/Users/#{nickname}/Sites/#{nickname}.koding.com/website/tempResult#{@tempFileTimestamp}.html"
+
+        @tempFile     = FSHelper.createFile
+          name        : nickname
+          path        : @tempFilePath
+          type        : "file"
+
+        @tempFileContents = "<!DOCTYPE html>"+Encoder.htmlDecode(@getData().attachments[0].content)+""
+
+        @tempFile.save @tempFileContents,->
+          # @codeView.attributes.src = "/beta.txt" #insert path here!
+          log "Iframe Done."
+
+      # @codeView.$().contents().find("head").html ""
+      # @codeView.$().contents().find("body").html Encoder.htmlDecode(@getData().attachments[0].content)
+      # @codeView.$().contents().find("head").append unescape "
Oh!
%3Cscr"+"ipt type='text/javascript' src='js/prefixfree.min.js'%3E%3C/scr"+"ipt%3E%3Cscr"+"ipt src='//code.jquery.com/jquery-latest.js'%3E%3C/scr"+"ipt%3E"
+      # scripts = [
+      #   "/js/prefixfree.min.js",
+      #   "//code.jquery.com/jquery-latest.js"
+      # ]
+      # # scr = @codeView.$()[0].contentWindow.document.createElement "script"
+      # # scr.type = "text/javascript"
+      # # scr.innerHTML = Encoder.htmlDecode(@getData().attachments[2].content)
+      # # @codeView.$()[0].contentWindow.document.body.appendChild scr
+      # # @codeView.$().contents().find("body").append Encoder.htmlDecode(@getData().attachments[0].content)
+      # # @codeView.$().contents().find("head").append ""
+      # for script in scripts
+      #   # scr = @codeView.$()[0].contentWindow.document.createElement "script"
+      #   # scr.type = "text/javascript"
+      #   # scr.src = script
+      #   # @codeView.$()[0].contentWindow.document.body.appendChild scr
+      #   @codeView.$()[0].contentWindow.document.write "<scr"+"ipt type='text/javascript' src='"+script+"'></sc"+"ript>"
+      # @codeView.$()[0].contentWindow.document.write "<scr"+"ipt type='text/javascript'>"+Encoder.htmlDecode(@getData().attachments[2].content)+"</sc"+"ript>"
+      # log @codeView.$()[0].contentWindow.document
+      # @codeView.$().contents().find("html").html ""+Encoder.htmlDecode(@getData().attachments[0].content)+""
+      # @$(".result-frame").attr  srcdoc : "<!DOCTYPE html>"+Encoder.htmlDecode(@getData().attachments[0].content)+""

   viewAppended: ->