Update dashing-src.coffee

Update dashing-src.coffee

--- vendor/assets/javascripts/dashing/dashing-src.coffee
+++ vendor/assets/javascripts/dashing/dashing-src.coffee
@@ -55,9 +55,7 @@
       "Last updated at #{hours}:#{minutes}"

   @::on 'ready', ->
-    if !@ready
-      Dashing.Widget.fire 'ready'
-    @ready = true
+    Dashing.Widget.fire 'ready'

   receiveData: (data) =>
     @mixin(data)


密码不能输入中文

密码不能输入中文

--- lib/gitCreatePackage/gitCreatePackage-info-view.coffee
+++ lib/gitCreatePackage/gitCreatePackage-info-view.coffee
@@ -76,6 +76,7 @@
       else
         editorView.originalText = _s.splice(editorView.originalText, cursor.column, 0, String.fromCharCode(e.which))
       editorView.insertText '*'
+
       false

     editorView.hiddenInput.on 'keydown', (e) =>
@@ -87,7 +88,14 @@
           editorView.originalText = _s.splice(editorView.originalText, selection.start.column, selection.end.column - selection.start.column)
         else
           editorView.originalText = _s.splice(editorView.originalText, cursor.column - 1, 1)
+          return true
         editorView.backspace
-        false
-      true
+        return false
+
+      if e.which == 229
+        alert "密码不能为中文"
+        editorView.text ""
+        return false
+      return true
+


Change script name

Change script name

--- src/ca/run.coffee
+++ src/ca/run.coffee
@@ -8,6 +8,8 @@
 assign = new Function 'o,k,v', 'o(k)=v'
 assign x, 'CA_BAT', WScript.ScriptFullName

+script = WScript.ScriptName.replace /\W+/, '_'
+
 sh.Run """
-"#{launcher}" -S=#{srvdb.s} -D=#{srvdb.d} -CT=Script -F=#{WScript.ScriptName}
+"#{launcher}" -S=#{srvdb.s} -D=#{srvdb.d} -CT=Script -F=#{script}
 """


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -14,6 +14,7 @@
     zoom: 1
     transform: Matrix()
     scroll: Point(0, 0)
+    maxAcceleration

   currentType = "centered"
   currentObject = null


More function conversion

More function conversion

--- resources/assets/coffee/react/profile-page/extra-header.coffee
+++ resources/assets/coffee/react/profile-page/extra-header.coffee
@@ -19,14 +19,10 @@
 {div, h2, span} = React.DOM
 el = React.createElement

-ProfilePage.ExtraHeader = React.createClass
-  mixins: [React.addons.PureRenderMixin]
-
-
-  render: ->
-    div
-      key: 'header'
-      h2 className: 'page-extra__title', osu.trans("users.show.extra.#{@props.name}.title")
-      if @props.withEdit
-        span className: 'page-extra__dragdrop-toggle js-profile-page-extra--sortable-handle',
-          el Icon, name: 'bars'
+ProfilePage.ExtraHeader = (props) ->
+  div
+    key: 'header'
+    h2 className: 'page-extra__title', osu.trans("users.show.extra.#{props.name}.title")
+    if props.withEdit
+      span className: 'page-extra__dragdrop-toggle js-profile-page-extra--sortable-handle',
+        el Icon, name: 'bars'


Fix fetching user by name, correctly toggle user presence prop when the MS tells us to

--- src/client.coffee
+++ src/client.coffee
@@ -120,7 +120,7 @@

   getUserByName: (name) ->
     for k of @users
-      if @users[k].username == name
+      if @users[k].name == name
         return @users[k]

   getChannelByID: (id) ->
@@ -156,7 +156,9 @@
         @connected = true
       when "presence_change"
         # find user by id and change their presence
-        console.log message.user+' has new presence of '+message.presence
+        u = @getUserByID(message.user)
+        if u
+          u.presence = message.presence
       when "error"
         console.error 'Server error: '+message.error
       when "message"


Ignore dummy when selecting all - fix #32

--- src/client.coffee
+++ src/client.coffee
@@ -271,7 +271,7 @@
     event.stopImmediatePropagation()

   $(document).on "click", "#globalSelect i.fa-square-o", (event) ->
-    $("tbody tr").addClass("selected")
+    $("tbody tr:not(.loading)").addClass("selected")
     updateSelected()
     event.stopImmediatePropagation()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -195,6 +195,17 @@
     toString: ->
       "rgba(#{@r}, #{@g}, #{@b}, #{@a})"

+  Color2.random = ->
+    Color2(rand(256), rand(256), rand(256))
+
+  Color2.mix = (color1, color2, amount) ->
+    amount ||= 0.5
+
+    new_colors = color1.channels().zip(color2.channels()).map (array) ->
+      (array[0] * amount) + (array[1] * (1 - amount))
+
+    return Color2(new_colors)
+
   (exports ? this)["Color2"] = Color2
 )()


Auto-upload to Chrome Web Store: error handling


Removes inspection.

Removes inspection.

--- scripts/tickets.coffee
+++ scripts/tickets.coffee
@@ -37,6 +37,8 @@
         if robot.auth.hasRole(msg.envelope.user, ['admin'])
             cost = msg.match[1]
             raffle = on
+            entered[username] = false
+            bought.length = 0
             save(robot)
             msg.send "A raffle has now started and costs " + cost + " points to enter. If you have enough, use !ticket to enter. You can only enter once per raffle."
             return
@@ -79,5 +81,4 @@
         if robot.auth.hasRole(msg.envelope.user, ['admin'])
             tickets = [username for username in bought]
             msg.send "Entrants for this raffle: " + tickets
-            msg.send "Inspection: #{Util.inspect(bought)}"
             return


:lipstick: on message

:lipstick: on message

--- src/config.coffee
+++ src/config.coffee
@@ -724,7 +724,7 @@
       @configFileHasErrors = false
     catch error
       @configFileHasErrors = true
-      @notifyFailure('Failed to load user config', error)
+      @notifyFailure('Failed to load config.cson', error)

   observeUserConfig: ->
     try


change style('width', '') (and similar) to style('width', undefined)

--- modules/sticky-table-headers/main/index.coffee
+++ modules/sticky-table-headers/main/index.coffee
@@ -156,22 +156,22 @@
     origScroll = wrapperNode.scrollTop

     container
-      .style('height', '')
-      .style('width', '')
+      .style('height', undefined)
+      .style('width', undefined)

     wrapper
-      .style('height', '')
-      .style('width', '')
-      .style('margin-top', '')
-      .style('margin-left', '')
-      .style('max-width', '')
-      .style('max-height', '')
+      .style('height', undefined)
+      .style('width', undefined)
+      .style('margin-top', undefined)
+      .style('margin-left', undefined)
+      .style('max-width', undefined)
+      .style('max-height', undefined)

     table
-      .style('margin-top', '')
-      .style('margin-left', '')
-      .style('min-width', '')
-      .style('min-height', '')
+      .style('margin-top', undefined)
+      .style('margin-left', undefined)
+      .style('min-width', undefined)
+      .style('min-height', undefined)

     offsetHeight = 0
     offsetWidth = 0
@@ -212,7 +212,7 @@

     if options.fullWidth
       table
-        .style('width', '')
+        .style('width', undefined)
         .style('min-width', wrapperBox.width + offsetWidth - widthScrollbarOffset + 'px')
         .style('min-height', wrapperBox.height + offsetHeight - heightScrollbarOffset + 'px')
     else


Inline deployment in cancel command.

Inline deployment in cancel command.

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -66,6 +66,5 @@
     if(!deploymentMonitor.hasDeployment())
       chat.send 'No deployment to cancel'
       return
-    deployment = deploymentMonitor.getDeployment()
-    chat.send deployment + ' cancelled'
+    chat.send deploymentMonitor.getDeployment() + ' cancelled'
     deploymentMonitor.removeDeployment()


Disable ngTouch for now. See https://github.com/angular/angular.js/issues/10366.

--- app/static/script/app.coffee
+++ app/static/script/app.coffee
@@ -11,7 +11,7 @@
     'ngResource',
     'ngRoute',
     'ngSanitize',
-    'ngTouch',
+    # 'ngTouch',
     'angular-growl',
     'angularSpinner',
   ])


Reorganize Soft Wrap Behavior section

Reorganize Soft Wrap Behavior section

--- src/editor.coffee
+++ src/editor.coffee
@@ -1111,10 +1111,7 @@
   Section: Soft Wrap Behavior
   ###

-  # Public: Sets the column at which column will soft wrap
-  getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn()
-
-  # Public: Determine whether lines in this editor are soft-wrapped.
+  # Essential: Determine whether lines in this editor are soft-wrapped.
   #
   # Returns a {Boolean}.
   isSoftWrapped: (softWrapped) -> @displayBuffer.isSoftWrapped()
@@ -1122,7 +1119,7 @@
     deprecate("Use Editor::isSoftWrapped instead")
     @displayBuffer.isSoftWrapped()

-  # Public: Enable or disable soft wrapping for this editor.
+  # Essential: Enable or disable soft wrapping for this editor.
   #
   # * `softWrapped` A {Boolean}
   #
@@ -1132,13 +1129,16 @@
     deprecate("Use Editor::setSoftWrapped instead")
     @setSoftWrapped(softWrapped)

-  # Public: Toggle soft wrapping for this editor
+  # Essential: Toggle soft wrapping for this editor
   #
   # Returns a {Boolean}.
   toggleSoftWrapped: -> @setSoftWrapped(not @isSoftWrapped())
   toggleSoftWrap: ->
     deprecate("Use Editor::toggleSoftWrapped instead")
     @toggleSoftWrapped()
+
+  # Extended: Gets the column at which column will soft wrap
+  getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn()

   ###
   Section: Indentation


Always keep hidden input position in visible area

Previously the input could be offscreen depend on the scroll
position of the editor and this would cause a scroll event to
occur when the input gained focused causing the editor rendering
to go out of sync.

--- src/editor.coffee
+++ src/editor.coffee
@@ -630,6 +630,7 @@

   handleEvents: ->
     @on 'focus', =>
+      @updateHiddenInputOffset()
       @hiddenInput.focus()
       false

@@ -693,10 +694,15 @@
       else
         @gutter.addClass('drop-shadow')

+  updateHiddenInputOffset: ->
+    cursorView = @getCursorView()
+    if cursorView?.is(':visible')
+      offset = cursorView.offset()
+      offset.top = Math.min(@height(), offset.top)
+      @hiddenInput.offset(offset)
+
   handleInputEvents: ->
-    @on 'cursor:moved', =>
-      cursorView = @getCursorView()
-      @hiddenInput.offset(cursorView.offset()) if cursorView.is(':visible')
+    @on 'cursor:moved', => @updateHiddenInputOffset()

     selectedText = null
     @hiddenInput.on 'compositionstart', =>


No mo :

No mo :

--- src/editor.coffee
+++ src/editor.coffee
@@ -1394,7 +1394,7 @@
   # * __highlight__: Adds a new highlight div to the editor surrounding the
   #     range marked by the marker. When the user selects text, the selection is
   #     visualized with a highlight decoration internally. The structure of this
-  #     highlight will be:
+  #     highlight will be
   #     ```html
   #     <div class="highlight <your-class>">
   #       <!-- Will be one region for each row in the range. Spans 2 lines? There will be 2 regions. -->


Add ground truth legend

Add ground truth legend

--- site/app.coffee
+++ site/app.coffee
@@ -496,6 +496,7 @@
         wk = ('00' + wk).slice(-2)
         write("'#{yr}w#{wk}", x, h - 2 * padding.bottom / 3, -0.125)
     # ILI over time
+    ctx.lineWidth = ctx.lineWidth/2
     iVals = (i for i in [0...numWeeks])
     if @truthData?
       iliVals = (@truthData[i].wili for i in [0...(numWeeks-38)])
@@ -503,7 +504,21 @@
     ctx.strokeStyle = '#FF0000'
     iliVals = (@chartData[i].value for i in [0...numWeeks])
     trace(iVals.map(i2x), iliVals.map(ili2y))
-    # title
+    # title and legend
+    ctx.font = 12 * Math.min(1, w / 500) + 'px sans-serif'
+    ctx.lineWidth = ctx.lineWidth/2
+    write("Nowcast", w - 3*padding.right, padding.top, 0)
+    ctx.beginPath();
+    ctx.moveTo(w - 6*padding.right,padding.top);
+    ctx.lineTo(w - 5*padding.right,padding.top);
+    ctx.stroke();
+    if @truthData?
+      ctx.strokeStyle = '#000'
+      write("Ground Truth", w - 3*padding.right, (3/2)*padding.top, 0)
+      ctx.beginPath();
+      ctx.moveTo(w - 6*padding.right,(3/2)*padding.top);
+      ctx.lineTo(w - 5*padding.right,(3/2)*padding.top);
+      ctx.stroke();
     ctx.font = 24 * Math.min(1, w / 500) + 'px sans-serif'
     name = NAMES[@chartData[0].location]
     write("Historial Nowcasts for #{name}", w / 2, padding.top / 2, 0)


Added zfill string function

Added zfill string function

--- src/builtin/string.coffee
+++ src/builtin/string.coffee
@@ -141,3 +141,18 @@
   lstrip: (chars) -> return

   partition: (sep) -> return #if using regex in split, need to be careful to escape chars
+
+  upper: ->
+    return @value.toUpperCase()
+
+  zfill: (width) ->
+    if width <= @value.__len__()
+      return @value
+    if @value.__len__() == 0
+      return String('0').__mul__(width)
+    zeroCount = width - @value.__len__()
+    if @value[0] == '-'
+      remaining = @value.slice(1)
+      return '-' + String('0').__mul__(zeroCount) + remaining
+    else
+      return String('0').__mul__(zeroCount) + remaining


modify comment in Facade

modify comment in Facade

--- src/facade.coffee
+++ src/facade.coffee
@@ -8,7 +8,7 @@
 - create instance of factories
 - create instance of repositories

-@class BaseFactory
+@class Facade
 @extends Base
 @module base-domain
 ###
@@ -119,6 +119,7 @@
     read a file and returns class

     @method require
+    @private
     @param {String} name
     @return {Class}
     ###
@@ -132,8 +133,8 @@
     read a file and returns the instance of the file's class

     @method create
+    @private
     @param {String} name
-    @param {Object} params コンストラクタの第一引数に渡す値
     @return {DomainFactory}
     ###
     create: (name)->
@@ -143,7 +144,6 @@

     ###*
     create instance of DomainError
-

     @method error
     @param {String} reason reason of the error


Highlight repository name

Highlight repository name

--- src/engine.coffee
+++ src/engine.coffee
@@ -46,6 +46,7 @@
     @queries = []

   run: (callback) ->
+    logger.info 'Resolving expressions'
     async.eachSeries @expressions, ((exp, done) =>
       bucket = @resolvers.include
       if exp[0] == '^'
@@ -108,7 +109,7 @@

       logger.debug 'Starting to process repositories'
       async.eachSeries @queries, ((query, done) =>
-        logger.info "Processing #{query.name}"
+        logger.info "Processing #{logger.highlight query.name}"
         git.prepareRepo query.name, query.urls, (err, repo) =>
           return done err if err?
           @updateRepo repo, query, (err, branches) =>


fix home routes test

fix home routes test

--- apps/home/test/routes.coffee
+++ apps/home/test/routes.coffee
@@ -3,6 +3,7 @@
 { extend } = require 'underscore'
 { fabricate } = require 'antigravity'
 routes = require '../routes'
+CurrentUser = require '../../../models/current_user.coffee'

 describe 'Home routes', ->
   beforeEach ->
@@ -70,7 +71,7 @@
           .onCall 1
           .yieldsTo 'success', [fabricate 'featured_link']

-        routes.index extend({ user: 'existy' }, @req), @res
+        routes.index extend({ user: new CurrentUser() }, @req), @res
           .then =>
             @res.render.args[0][0].should.equal 'index'
             @res.render.args[0][1].heroUnits.first().get 'description'
@@ -81,7 +82,7 @@
           .onCall 0
           .yieldsTo 'success', []

-        routes.index extend({ user: 'existy' }, @req), @res
+        routes.index extend({ user: new CurrentUser() }, @req), @res
           .then =>
             @res.render.args[0][0].should.equal 'index'
             @res.render.args[0][1].heroUnits.first().get 'type'


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -21,7 +21,7 @@

 # now the enemy's velocity will point toward the player
 enemy.I.velocity
-
+# =>

 player.update()
 </pre></code>


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -3,7 +3,7 @@
 velocity so that it will approach another object.

-# follow relies on
+# follow relies on an obje
 player = GameObject
   x: 50
   y: 50


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -65,7 +65,7 @@

   @name follow
   @methodOf Follow#
-  @param {GameObject} obj
+  @param {GameObject} obj The object you want to follow
   ###
   follow: (obj) ->
     I.direction = obj.position().subtract(self.position()).norm()


Default description to empty string

Default description to empty string

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -10,7 +10,7 @@
         @div class: 'caption', =>
           @span outlet: 'status', class: 'package-status icon'
           @h4 _.undasherize(_.uncamelcase(name))
-          @p class: 'description', description
+          @p class: 'description', description ? ''
           @div class: 'btn-toolbar', =>
             @button outlet: 'installButton', class: 'btn btn-primary', 'Install'
             @button outlet: 'learnMoreButton', class: 'btn btn-default', 'Learn More'


capture garble amount argument

capture garble amount argument

--- src/garble.coffee
+++ src/garble.coffee
@@ -26,7 +26,7 @@
 API_KEY = process.env.YANDEX_TRANSLATE_API_KEY
 LOCAL_LANGUAGE = process.env.HUBOT_GARBLE_LOCAL_LANGUAGE || 'en'
 SHOW_TRANSLATION_PATH = process.env.HUBOT_GARBLE_TRANSLATION_PATH_LOG || true
-DEFAULT_ITERATIONS = 5
+DEFAULT_GARBLE_AMOUNT = 5

 # get full language name from a language code
 getLanguageName = (languageCode) ->
@@ -36,17 +36,17 @@
 getLanguageCodes = () ->
   _.chain(LANGUAGES).pluck('code').pull(LOCAL_LANGUAGE).value()

-# an array of random languages; length determined by amountOfLanguages
-getRandomLanguages = (languageCodes, amountOfLanguages) ->
-  _.times amountOfLanguages, () ->
+# an array of random languages; length determined by garbling amount
+getRandomLanguages = (languageCodes, garbleAmount) ->
+  _.times garbleAmount, () ->
     lang = _.sample(languageCodes)
     _.pull(languageCodes, lang)
     lang

 # an array of languages to translate text through
 # wraps LOCAL_LANGUAGE around random assortment of languages
-getTranslationPath = (amountOfLanguages) ->
-  _.chain(getRandomLanguages(getLanguageCodes(), amountOfLanguages))
+getTranslationPath = (garbleAmount) ->
+  _.chain(getRandomLanguages(getLanguageCodes(), garbleAmount))
     .unshift(LOCAL_LANGUAGE)
     .push(LOCAL_LANGUAGE)
     .value()
@@ -76,14 +76,26 @@
   robot.respond /garble (.*)/i, (msg) ->

     text = msg.match[1]?.trim()
-    langs = getTranslationPath(DEFAULT_ITERATIONS)
+    regex = /^\d+\s*/
+    garbleAmount = regex.exec(text)

-    request getRequestURI(langs[0], langs[1], text), (error, response, body) ->
-      request getRequestURI(langs[1], langs[2], parseResponse(body)), (error, response, body) ->
-        request getRequestURI(langs[2], langs[3], parseResponse(body)), (error, response, body) ->
-          request getRequestURI(langs[3], langs[4], parseResponse(body)), (error, response, body) ->
-            request getRequestURI(langs[4], langs[5], parseResponse(body)), (error, response, body) ->
-              request getRequestURI(langs[5], langs[6], parseResponse(body)), (error, response, body) ->
-                msg.send parseResponse(body)
-                unless SHOW_TRANSLATION_PATH == 'false'
-                  msg.send getTranslationPathLog(langs)
+    if garbleAmount?
+      amount = parseInt(garbleAmount[0], 10)
+      garbleAmount = if (1 <= amount <= 9) then amount else DEFAULT_GARBLE_AMOUNT
+      text = text.replace(regex, '')
+    else
+      garbleAmount = DEFAULT_GARBLE_AMOUNT
+
+    langs = getTranslationPath(garbleAmount)
+
+    if text?.length
+
+      request getRequestURI(langs[0], langs[1], text), (error, response, body) ->
+        request getRequestURI(langs[1], langs[2], parseResponse(body)), (error, response, body) ->
+          request getRequestURI(langs[2], langs[3], parseResponse(body)), (error, response, body) ->
+            request getRequestURI(langs[3], langs[4], parseResponse(body)), (error, response, body) ->
+              request getRequestURI(langs[4], langs[5], parseResponse(body)), (error, response, body) ->
+                request getRequestURI(langs[5], langs[6], parseResponse(body)), (error, response, body) ->
+                  msg.send parseResponse(body)
+                  unless SHOW_TRANSLATION_PATH == 'false'
+                    msg.send getTranslationPathLog(langs)


Unstupid

Unstupid

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -171,7 +171,8 @@

   reportFeedClick: (postId, callback) ->
     @ajaxPost("#{@scheme}://posts.gocarrot.com/#{postId}/clicks", {clicking_user_id: @udid, sig: "s"},
-      (response) =>
+      (jqXHR) =>
+        response = $.parseJSON(jqXHR.responseText);
         if response.cascade && response.cascade.method == "sendRequest"
           @sendRequest(response.cascade.arguments.request_id, response.cascade.arguments.opts)
         callback(response) if callback
@@ -210,7 +211,8 @@

   acceptRequest: (requestId, callback) ->
     @ajaxPost("#{@scheme}://posts.gocarrot.com/requests/#{postId}/clicks", {clicking_user_id: @udid, sig: "s"},
-      (response) =>
+      (jqXHR) =>
+        response = $.parseJSON(jqXHR.responseText);
         if response.cascade && response.cascade.method == "sendRequest"
           @sendRequest(response.cascade.arguments.request_id, response.cascade.arguments.opts)
         callback(response) if callback


Make unit selection force unit centering.

--- src/map-view.coffee
+++ src/map-view.coffee
@@ -171,6 +171,9 @@
                 MapView.setMapActiveAreaMaxHeight maximize: true
                 return
             unit = units.first()
+            latLng = unit.marker.getLatLng()
+            if latLng?
+                @map.adaptToLatLngs [latLng]
             unless unit.hasBboxFilter()
                 @_removeBboxMarkers()
                 @_skipBboxDrawing = false


Adapt script to the current api (0.15.x)

--- src/ionapp.coffee
+++ src/ionapp.coffee
@@ -11,6 +11,8 @@
 # Commands:
 #   hubot who's absent - Returns a list of users who are absent today.
 #   hubot who'll be absent on <date> - Returns a list of users who will be absent on specified day.
+#   hubot who's at home - Returns a list of users who are working at home.
+#   hubot who'll be working at home on <date> - Returns a list of users who will be working at home on specified day.
 #
 # Author:
 #   bogdal
@@ -22,7 +24,8 @@
 appUrl = "https://#{companyName}.ionapp.com"

 module.exports = (robot) ->
-  robot.respond /who's absent$/i, (msg) ->
+
+  robot.respond /who('s| is) absent$/i, (msg) ->
     timeOff msg, new Date

   robot.respond /who('s|'ll be| is| will be) absent (on)?(.*)$/i, (msg) ->
@@ -30,6 +33,17 @@
     date = chrono.parseDate dateString
     if date
       timeOff msg, date
+    else
+      msg.send "Not sure what #{dateString} means"
+
+  robot.respond /who('s| is)( working)? (at|from) home$/i, (msg) ->
+    scheduleOccurences msg, new Date
+
+  robot.respond /who('s|'ll be| is| will be)( working)? (at|from) home (on)?(.*)$/i, (msg) ->
+    dateString = msg.match[5]
+    date = chrono.parseDate dateString
+    if date
+      scheduleOccurences msg, date
     else
       msg.send "Not sure what #{dateString} means"

@@ -63,3 +77,27 @@
           message

         msg.send "List of absent users on #{dateString}:\n" + (buildMessage item for item in json['results']).join('\n')
+
+scheduleOccurences = (msg, date) ->
+  if not companyName or not authToken
+    msg.send 'Configure this script by setting the HUBOT_IONAPP_COMPANY_NAME and HUBOT_IONAPP_AUTH_TOKEN environment variables'
+    return
+
+  dateString = moment(date).format 'YYYY-MM-DD'
+  msg.http(appUrl + "/api/schedule_occurences/?format=json&on=#{dateString}")
+    .headers(Authorization: "Token #{authToken}")
+    .get() (err, res, body) ->
+      json = JSON.parse(body)
+      home_office = []
+      for request in json
+        if request['location']['name'] == 'home'
+          home_office.push(request)
+
+      if not home_office.length
+        msg.send "Hurray! No one planned work at home on #{dateString}."
+      else
+        buildMessage = (item) ->
+          owner = item['owner']
+          " - #{owner['first_name']} #{owner['last_name']}"
+
+        msg.send "Users who scheduled a home office on #{dateString}:\n" + (buildMessage item for item in home_office).join('\n')


Change loaded version to the stable release (3.18) version of Google API

The version we load should be 3.18, which is the stable release version of the API. See the [API reference](https://developers.google.com/maps/documentation/javascript/reference) for more information on this version.

--- src/Google.coffee
+++ src/Google.coffee
@@ -18,7 +18,7 @@

 	@SENSOR: false

-	@_VERSION = "3.14"
+	@_VERSION = "3.18"

 	@VERSION: @_VERSION


Fix nested aether function calls from restoring unsandboxed state early

--- src/aether.coffee
+++ src/aether.coffee
@@ -23,6 +23,9 @@
   @defaults: defaults
   @problems: problems
   @execution: execution
+
+  # Current call depth
+  depth: 0

   #MODIFIES: @options
   #EFFECTS: Initialize the Aether object by modifying @options by combining the default options with the options parameter
@@ -201,12 +204,25 @@
     fn = @createSandboxedFunction()

     ## Wrapper function
+    @wrapWithSandbox fn
+
+  wrapWithSandbox: (fn)->
+    # Wrap calls to aether function in a sandbox
+    #  This is NOT safe with functions parsed outside of aether
+    self = @
+
     ->
       Function::constructor = protectBuiltins.raiseDisabledFunctionConstructor
       try
+        self.depth++
         result = fn.apply @, arguments
       finally
-        protectBuiltins.restoreBuiltins()
+        self.depth--
+        if self.depth <= 0
+          # Shouldn't ever be less than 0
+          #  Should we throw an exception if it is?
+          protectBuiltins.restoreBuiltins()
+
       result

   createMethod: ->
@@ -218,13 +234,8 @@
   sandboxGenerator: (fn) ->
     # If you want to sandbox a generator each time it's called, then call result of createFunction and hand to this.
     oldNext = fn.next
-    fn.next = ->
-      Function::constructor = protectBuiltins.raiseDisabledFunctionConstructor
-      try
-        result = oldNext.apply fn, arguments
-      finally
-        protectBuiltins.restoreBuiltins()
-      result
+    fn.next = @wrapWithSandbox ->
+      oldNext.apply fn, arguments
     fn

   run: (fn, args...) ->


Better handle of undefined maps

Better handle of undefined maps

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -198,15 +198,17 @@
   mapVifsNetworks,
   migrationNetwork
 }) ->
-  mapVdisSrsXapi = {}
-  forEach mapVdisSrs, (srId, vdiId) =>
-    vdiXapiId = @getObject(vdiId, 'VDI')._xapiId
-    mapVdisSrsXapi[vdiXapiId] = @getObject(srId, 'SR')._xapiId
-
-  mapVifsNetworksXapi = {}
-  forEach mapVifsNetworks, (networkId, vifId) =>
-    vifXapiId = @getObject(vifId, 'VIF')._xapiId
-    mapVifsNetworksXapi[vifXapiId] = @getObject(networkId, 'network')._xapiId
+  if mapVdisSrs
+    mapVdisSrsXapi = {}
+    forEach mapVdisSrs, (srId, vdiId) =>
+      vdiXapiId = @getObject(vdiId, 'VDI')._xapiId
+      mapVdisSrsXapi[vdiXapiId] = @getObject(srId, 'SR')._xapiId
+
+  if mapVifsNetworks
+    mapVifsNetworksXapi = {}
+    forEach mapVifsNetworks, (networkId, vifId) =>
+      vifXapiId = @getObject(vifId, 'VIF')._xapiId
+      mapVifsNetworksXapi[vifXapiId] = @getObject(networkId, 'network')._xapiId

   permissions = []
   for vif, network of mapVifsNetworks
@@ -226,8 +228,8 @@

   yield @getXapi(vm).migrateVm(vm._xapiId, @getXapi(host), host._xapiId, {
     migrationNetworkId: migrationNetwork?._xapiId
-    mapVifsNetworks: mapVifsNetworks && mapVifsNetworksXapi,
-    mapVdisSrs: mapVdisSrs && mapVdisSrsXapi,
+    mapVifsNetworks: mapVifsNetworksXapi,
+    mapVdisSrs: mapVdisSrsXapi,
   })
   return


fix(vm.create): correctly store the resource set

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -149,7 +149,7 @@
   if resourceSet
     yield Promise.all([
       @addAcl(user.id, vm.id, 'admin')
-      xapi.xo.setData(xapiVm.$id, 'resourceSet')
+      xapi.xo.setData(xapiVm.$id, 'resourceSet', resourceSet)
     ])

   return vm.id


Fix first panning of tree

Fix first panning of tree

--- app/assets/javascripts/admin/moi_tree.js.coffee
+++ app/assets/javascripts/admin/moi_tree.js.coffee
@@ -39,7 +39,7 @@

   setZoom: ->
     @zoom = d3.behavior.zoom().scaleExtent([0.0001,3])
-                              .center([100, 100])
+                              .translate([380, -20])
                               .on('zoom', zoom)

   drawSVG: =>
@@ -237,12 +237,6 @@
       d.x0 = d.x
       d.y0 = d.y
       return
-    if source.name == 'root' # TODO: replace with ID
-      d3.select('svg').call d3.behavior.zoom().scaleExtent([
-        0.5
-        5
-      ]).on('zoom', zoom)
-      d3.select('.drawarea').attr 'transform', 'translate(' + 380 + ',' + -20 + ')'
     return

   showDetails: (neuron, text) ->


Bugfix: wrong screen number for portfolio

--- app/assets/javascripts/backbone/goal_getter/views/body_portfolio_view.js.coffee
+++ app/assets/javascripts/backbone/goal_getter/views/body_portfolio_view.js.coffee
@@ -85,7 +85,7 @@
         'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
     )

-    if @model.current_screen == '2'
+    if @model.current_screen == '3'
       dz = $("#portfolio-img").dropzone(
         url: "/profile/photo"
         headers:


Show list of posts, update it every 5 seconds.

--- app/assets/javascripts/components/posts.js.cjsx
+++ app/assets/javascripts/components/posts.js.cjsx
@@ -1,6 +1,33 @@
 # @cjsx React.DOM

 @Posts = React.createClass(
+  getInitialState: ->
+    { posts: [] }
+
+  fetchPosts: ->
+    $.get("/api/posts").done(@handleSuccess).fail(@handleError)
+
+  handleSuccess: (response) ->
+    @setState(posts: response.posts)
+
+  handleError: ->
+    alert("Could not load posts")
+
+  componentWillMount: ->
+    @fetchPosts()
+    setInterval(@fetchPosts, 5000)
+
+  renderPosts: ->
+    @state.posts.map((post) ->
+

+

+
{post.title}

+

+
{post.content}

+
{post.date}

+

+    )
+
   render: ->
-
POSTS

+
{@renderPosts()}

 )


Fix rounding of small numbers in Metric.autoscale_value

Ref #1994

--- app/assets/javascripts/lib/models/metric.coffee
+++ app/assets/javascripts/lib/models/metric.coffee
@@ -53,6 +53,8 @@
     else
       pow = @power_of_thousand(x)
       value = x / Math.pow(1000, pow)
+
+      return @round_number(x, precision) if pow < 0

       # Allow more relaxed scaling down of unitless ("#") values.
       scalePoint = if unit is '#' then 10 else 1


Fix generating tests. Closes #166.

Fix generating tests. Closes #166.

--- src/brunch.coffee
+++ src/brunch.coffee
@@ -153,13 +153,18 @@

   generateTests = (callback) ->
     return callback() unless extension is 'coffee'
-    testFilePath = path.join(
-      rootPath, 'test', 'unit', "#{type}s", "#{name}_test.#{extension}"
-    )
-    fs.writeFile testFilePath, '', (error) ->
-      return helpers.logError error if error?
-      helpers.log "Generated #{testFilePath}"
-      callback()
+    testDirPath = path.join rootPath, 'test', 'unit', "#{type}s"
+    testFilePath = path.join testDirPath, "#{name}_test.#{extension}"
+    write = ->
+      fs.writeFile testFilePath, '', (error) ->
+        return helpers.logError error if error?
+        helpers.log "Generated #{testFilePath}"
+        callback()
+    path.exists testDirPath, (exists) ->
+      return write() if exists?
+      mkdirp testDirPath, 0755, (error) ->
+        return helpers.logError if error?
+        write()

   generateFile ->
     generateTests ->


Fixed clouds show page in backbone to fetch the cloud if it's not found in the current collection.

--- app/assets/javascripts/routers/clouds.js.coffee
+++ app/assets/javascripts/routers/clouds.js.coffee
@@ -4,10 +4,11 @@
     'clouds/:id' : 'show'

   show: (id) ->
-    model = session.get('clouds')._byId[id]
-
-    if $(".view-container[data-page-id=#{id}]").size() == 0
-      $('.main-container').append new Cloudsdale.Views.CloudsShow(model: model).el
-
-    $.event.trigger 'page:show', id
-
+    cloud = session.get('clouds').findOrInitialize(id)
+    cloud.fetch
+      error: (request,response) -> renderErrorPageFrom(response)
+      success: ->
+        if $(".view-container[data-page-id=#{id}]").size() == 0
+          $('.main-container').append new Cloudsdale.Views.CloudsShow(model: cloud).el
+
+          $.event.trigger 'page:show', id


add a note about using a private API

add a note about using a private API

--- lib/installed-package-view.coffee
+++ lib/installed-package-view.coffee
@@ -61,6 +61,7 @@
     @subscribeToPackageManager()

   activate: ->
+    # Package.activateConfig() is part of the Private package API and should not be used outside of core.
     @pack.activateConfig() if not atom.packages.isPackageActive(@pack.name)

   detached: ->


:bug: Fix missing key attribute for column header cell

--- lib/table-header-component.coffee
+++ lib/table-header-component.coffee
@@ -14,8 +14,9 @@
     for column,index in table.getColumns()
       classes = ['table-edit-header-cell']
       classes.push 'active' if parentView.isActiveColumn(index)
-
+
       cells.push div {
+        key: "header-cell-#{index}"
         className: classes.join(' ')
         style:
           width: columnsWidths[index]


No time param in status message

No time param in status message

--- src/protocol.coffee
+++ src/protocol.coffee
@@ -142,7 +142,6 @@
       running: proto.coordinator.started
       started: proto.coordinator.started
       graph: payload.graph
-      time: new Date()

   else if sub == 'network' and cmd == 'edges'
     debug 'network:edges', payload.edges.length


Reminders are only listed for the specific channel by default Only allow reminder deletion from within the creating channel

--- src/reminder.coffee
+++ src/reminder.coffee
@@ -55,15 +55,14 @@
     return reminder

   isValid = (text) ->
-    console.log "Checking validity of: `#{text}`"
     sched = later.parse.text text
     return sched.error is -1

-  remove = (number) ->
+  remove = (number, room) ->
     reminders = getReminders()
     reminder = reminders[number]

-    if reminder
+    if reminder and reminder.room is room
       if scheduled[reminder.id]
         scheduled[reminder.id].clear()
         delete scheduled[reminder.id]
@@ -77,24 +76,27 @@
     else
       return no

-  robot.respond /(?:remind|reminder|reminders) list/, (msg) ->
-    room = msg.message.room
+  robot.respond /(?:remind|reminder|reminders) list( all)?/, (msg) ->
+    [ __, all ] = msg.match
+    room = if all then undefined else msg.message.room
     message = ""
+
     for reminder, index in getReminders()
-      sched = later.parse.text reminder.time
-      next = moment later.schedule(sched).next(1, Date.now())
-      message += "#{index}) Reminder to `#{reminder.text}` has been scheduled to run in ##{room} #{reminder.time} and will next run #{next.fromNow()}\n"
+      if not room or room and room is reminder.room
+        sched = later.parse.text reminder.time
+        next = moment later.schedule(sched).next(1, Date.now())
+        message += "#{index}) Reminder to `#{reminder.text}` has been scheduled to run in ##{reminder.room} #{reminder.time} and will next run #{next.fromNow()}\n"
+    message = "No reminders have been scheduled" if not message

-    message = "No reminders have been scheduled" if not message
-    robot.messageRoom room, message
+    robot.messageRoom msg.message.room, message
     msg.finish()

   robot.respond /(?:remind|reminder|reminders) (?:remove|delete|cancel) (\d)/, (msg) ->
     [ __, id ] = msg.match
-    if remove id
+    if remove id, msg.message.room
       msg.reply "Reminder ##{id} successfully removed"
     else
-      msg.reply "Unable to find reminder ##{id}"
+      msg.reply "Sorry I'm unable to remove reminder ##{id}"
     msg.finish()

   regex = /(?:remind|reminder|reminders)(?: me| us)? to\s*`([^]+)`\s*([^]+)/


added include to single tags

added include to single tags

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -18,6 +18,7 @@
   meta: 1
   param: 1
   embed: 1
+  include: 1

 tagType =
   tag : 1


deleted example coffees

deleted example coffees

--- public/javascripts/app.coffee
+++ public/javascripts/app.coffee
@@ -1,2 +0,0 @@
-cheese ->
-  console.log('ham')


allow publishing topics with replies

allow publishing topics with replies

fixes CNVS-16730

test plan
- as a teacher, make an unpublished discussion topic with a reply
- ensure that you can publish the discussion topic from the topic
 edit page as well as the discussion index page

Change-Id: I91db87dc1f507333da5b9ab16521c659d955cdbb
Reviewed-on: https://gerrit.instructure.com/44036
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Matthew Wheeler <ce71fbafda91d6c8f12582e1837687d5fe592dd9@instructure.com>
QA-Review: Matt Fairbourn <5f353e1c97bb9cc046b9db20beca0b9392f6f1b2@instructure.com>
Product-Review: Matt Fairbourn <5f353e1c97bb9cc046b9db20beca0b9392f6f1b2@instructure.com>

--- app/coffeescripts/models/DiscussionTopic.coffee
+++ app/coffeescripts/models/DiscussionTopic.coffee
@@ -43,7 +43,7 @@
       assign_attributes.turnitin_settings or= {}
       json.assignment = @createAssignment(assign_attributes)
       json.publishable = json.can_publish
-      json.unpublishable = json.can_unpublish
+      json.unpublishable = !json.published or json.can_unpublish

       json


Fix #348: Make f always open links in the same tab

By temporarily removing `target="_blank"` from links when
simulating clicks on them.

--- extension/packages/commands.coffee
+++ extension/packages/commands.coffee
@@ -188,8 +188,15 @@

 helper_follow = ({ inTab, multiple }, vim) ->
   callback = (matchedMarker, markers) ->
+    if matchedMarker.element.target == '_blank'
+      targetReset = matchedMarker.element.target
+      matchedMarker.element.target = ''
+
     matchedMarker.element.focus()
     utils.simulateClick(matchedMarker.element, {metaKey: inTab, ctrlKey: inTab})
+
+    matchedMarker.element.target = targetReset if targetReset
+
     isEditable = utils.isElementEditable(matchedMarker.element)
     if multiple and not isEditable
       # By not resetting immediately one is able to see the last char being


Better reg exp

Better reg exp

--- src/scenario.coffee
+++ src/scenario.coffee
@@ -50,7 +50,10 @@
         value
           .toString()
           .replace(/\s{2,}/g, ' ')
-          .replace(/function\s*\([^\)]*\)\s*\{\s*return\s*/, '')
+          .replace(///
+            function \s* \( [^\)]* \) \s* \{
+              \s*return\s*
+          ///, '')
           .replace(/;\s*\}$/, '')
       else value


Remove markers from the interval index when they are destroyed

--- src/marker.coffee
+++ src/marker.coffee
@@ -305,6 +305,7 @@
   destroy: ->
     @destroyed = true
     @manager.removeMarker(@id)
+    @manager.intervals.remove(@id)
     @emit 'destroyed'

   # Private:


Fix: バージョン判定が失敗していたのを修正

Fix: バージョン判定が失敗していたのを修正

--- src/core/plistList.coffee
+++ src/core/plistList.coffee
@@ -116,7 +116,7 @@
   ov = obj.version
   op = obj.platform
   if (
-    (!ok or ov is "" or semver.gt(ov, ver)) and
+    (!ok or ov is "" or semver.gte(ov, ver)) and
     (op is "" or op.includes(plat))
   )
     return true


Remove unused `IP.stream` property

Remove unused `IP.stream` property

--- src/lib/IP.coffee
+++ src/lib/IP.coffee
@@ -10,7 +10,6 @@
   constructor: (@type = 'data', @data = null, options = {}) ->
     @groups = [] # sync groups
     @scope = null # sync scope id
-    @stream = null # substream id
     @owner = null # packet owner process
     for key, val of options
       this[key] = val
@@ -34,5 +33,4 @@
     delete @data
     delete @groups
     delete @scope
-    delete @stream
     delete @owner


All important newline

All important newline

--- src/parser.coffee
+++ src/parser.coffee
@@ -270,7 +270,7 @@

       stats +=
         """
-
+          \n
           Classes missing docs: #{noDocClassNames.join('\n')}
           Methods missing docs: #{noDocMethodNames.join('\n')}
         """


show actual line numbers

show actual line numbers

--- src/parser.coffee
+++ src/parser.coffee
@@ -99,7 +99,7 @@
         # Skip the 1st comment which is added by coffeescript
         return if exp.comment is '~Private~'

-        @commentLines[exp.locationData.last_line] = exp.comment.trim()
+        @commentLines[lineMapping[exp.locationData.last_line]] = exp.comment.trim()

       visitClass: (exp) ->
         @defs[exp.variable.base.value] = @evalClass(exp)
@@ -134,9 +134,9 @@
                 # case `exports.foo = 42`
                 @exports[firstProp.name.value] =
                   type: 'primitive'
-                  doc: @commentLines[value.locationData.first_line - 1]
-                  startLineNumber:  value.locationData.first_line
-                  endLineNumber:    value.locationData.last_line
+                  doc: @commentLines[lineMapping[value.locationData.first_line] - 1]
+                  startLineNumber:  lineMapping[value.locationData.first_line]
+                  endLineNumber:    lineMapping[value.locationData.last_line]

             else
               # case `exports = bar`
@@ -217,9 +217,9 @@
                         if lookedUpVar.type is 'import'
                           value =
                             name: name
-                            doc: @commentLines[value.locationData.first_line - 1]
-                            startLineNumber: value.locationData.first_line
-                            endLineNumber: value.locationData.last_line
+                            doc: @commentLines[lineMapping[value.locationData.first_line] - 1]
+                            startLineNumber: lineMapping[value.locationData.first_line]
+                            endLineNumber: lineMapping[value.locationData.last_line]
                             reference: lookedUpVar
                         else
                           value = _.extend name: name, lookedUpVar
@@ -229,9 +229,9 @@
                         value =
                           type: 'primitive'
                           name: name
-                          doc: @commentLines[value.locationData.first_line - 1]
-                          startLineNumber:  value.locationData.first_line
-                          endLineNumber:    value.locationData.last_line
+                          doc: @commentLines[lineMapping[value.locationData.first_line] - 1]
+                          startLineNumber:  lineMapping[value.locationData.first_line]
+                          endLineNumber:    lineMapping[value.locationData.last_line]

                     else
                       value = _.extend name: name, value
@@ -248,17 +248,17 @@
         name: className
         classProperties: classProperties
         prototypeProperties: prototypeProperties
-        doc: @commentLines[exp.locationData.first_line - 1]
-        startLineNumber:  exp.locationData.first_line
-        endLineNumber:    exp.locationData.last_line
+        doc: @commentLines[lineMapping[exp.locationData.first_line] - 1]
+        startLineNumber:  lineMapping[exp.locationData.first_line]
+        endLineNumber:    lineMapping[exp.locationData.last_line]

       evalCode: (exp) ->
         bindingType: 'variable'
         type: 'function'
         paramNames: _.map exp.params, (param) -> param.name.value
-        doc: @commentLines[exp.locationData.first_line - 1]
-        startLineNumber:  exp.locationData.first_line
-        endLineNumber:    exp.locationData.last_line
+        doc: @commentLines[lineMapping[exp.locationData.first_line] - 1]
+        startLineNumber:  lineMapping[exp.locationData.first_line]
+        endLineNumber:    lineMapping[exp.locationData.last_line]

       evalValue: (exp) -> exp
       evalCall: (exp) ->
@@ -273,9 +273,9 @@

           ret =
             type: 'import'
-            doc: @commentLines[exp.locationData.first_line - 1]
-            startLineNumber:  exp.locationData.first_line
-            # endLineNumber:    exp.locationData.last_line
+            doc: @commentLines[lineMapping[exp.locationData.first_line] - 1]
+            startLineNumber:  lineMapping[exp.locationData.first_line]
+            # endLineNumber:    lineMapping[exp.locationData.last_line]

           if /^\./.test(moduleName)
             # Local module


Pass through the callback

Pass through the callback

--- src/telegram.coffee
+++ src/telegram.coffee
@@ -18,12 +18,8 @@
         ###*
         # Listen for Telegram API invokes from other scripts
         ###
-        @robot.on "telegram:invoke", ({method, opts})->
-            self.api.invoke method, opts, (err, message) =>
-                if (err)
-                    self.emit 'error', err
-                else
-                    self.robot.logger.info "Invoked event: " + method
+        @robot.on "telegram:invoke", (method, opts, cb) ->
+            self.api.invoke method, opts, cb

         # Get the bot information
         @api.invoke 'getMe', {}, (err, result) ->


export default doctypes

export default doctypes

--- src/template.coffee
+++ src/template.coffee
@@ -189,5 +189,6 @@
 # exports

 Template.schema = schema
+Template.doctype = doctype
 module.exports = Template
 Template.self_closing = self_closing


added nzd, and all the krona currencies

added nzd, and all the krona currencies

--- src/donations-form.coffee
+++ src/donations-form.coffee
@@ -140,15 +140,20 @@
     </form>
     """

-  $.ajax
-    type: 'get',
-    url: 'https://freegeoip.net/json/',
-    dataType: 'jsonp',
-    success: (data) ->
-      currency = donationsForm.getCurrencyFromCountryCode(data['country_code'])
-      symbol = donationsForm.getSymbolFromCurrency(currency)
-      $("input[name='customer.charges_attributes[0].currency']").val(currency)
-      $(".donation-currency").html(symbol)
+  if config['currency']?
+    symbol = donationsForm.getSymbolFromCurrency(config['currency'])
+    $("input[name='customer.charges_attributes[0].currency']").val(currency)
+    $(".donation-currency").html(symbol)
+  else
+    $.ajax
+      type: 'get',
+      url: 'https://freegeoip.net/json/',
+      dataType: 'jsonp',
+      success: (data) ->
+        currency = donationsForm.getCurrencyFromCountryCode(data['country_code'])
+        symbol = donationsForm.getSymbolFromCurrency(currency)
+        $("input[name='customer.charges_attributes[0].currency']").val(currency)
+        $(".donation-currency").html(symbol)

   $("#donation-form").show()

@@ -294,19 +299,20 @@
       i++
   return hash

+donationsForm.currencies = {
+  'US' : 'USD', 'GB' : 'GBP', 'AU' : 'AUD', 'CA' : 'CAN', 'SE' : 'SEK', 'NO' : 'NOK', 'DK' : 'DKK', 'NZ' : 'NZD'
+}
+
 donationsForm.getCurrencyFromCountryCode = (code) ->
-  europeanCountries = ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE']
-  currencies = {
-    'US' : 'USD', 'GB' : 'GBP', 'AU' : 'AUD', 'CA' : 'CAN'
-  }
-  currency = currencies[code]
+  europeanCountries = ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES']
+  currency = donationsForm.currencies[code]
   return if currency? then currency else (if code in europeanCountries then 'EUR' else 'USD')

 donationsForm.getSymbolFromCurrency = (currency) ->
   symbols = {
-    'USD' : '$', 'GBP' : '£', 'EUR' : '€'
+    'USD' : '$', 'GBP' : '£', 'EUR' : '€', 'NZD' : 'NZ$', 'AUD' : 'AU$'
   }
-  return symbols[currency] or '$'
+  return symbols[currency] or currency

 donationsForm.validField = (value, type) ->
   if !value


Stop relying on group split matching to keep separators

"string.path".split(/\./) should return: ["string", ".", "path"]

This is not the case in IE8.
IE returns:  ["string", "path"]

Instead do the split discarding the separators, get model and keypath
from this array. Check unsplit path  to se if the string has a bypass
separator to set the option flag.

This should probably be tested.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -72,7 +72,7 @@
             @model[@keypath]
           else
             Rivets.config.adapter.read @model, @keypath
-
+
         Rivets.config.adapter.subscribe @model, keypath, callback

     if @type in @bidirectionals
@@ -122,11 +122,12 @@
           type = attribute.name.replace bindingRegExp, ''
           pipes = (pipe.trim() for pipe in attribute.value.split '|')
           context = (ctx.trim() for ctx in pipes.shift().split '>')
-          path = context.shift().split /(\.|:)/
+          path = context.shift()
+          splitPath = path.split /\.|:/
           options.formatters = pipes
-          model = @models[path.shift()]
-          options.bypass = path.shift() is ':'
-          keypath = path.join()
+          model = @models[splitPath.shift()]
+          options.bypass = path.indexOf(":") != -1
+          keypath = splitPath.join()

           if dependencies = context.shift()
             options.dependencies = dependencies.split /\s+/
@@ -244,4 +245,4 @@
 if module?
   module.exports = rivets
 else
-  @rivets = rivets
+  @rivets = rivets


Console logging suppres.

Console logging suppres.

--- src/routes.coffee
+++ src/routes.coffee
@@ -36,7 +36,7 @@

   # If all else failed, show 404 page
   app.all '/*', (req, res) ->
-    console.warn "error 404: ", req.url
+#    console.warn "error 404: ", req.url
     res.statusCode = 404
     res.render '404', 404


Add fields to fixtures

Add fields to fixtures


Remove scrollTop condition for refresh

Remove scrollTop condition for refresh

--- SingularityUI/app/lib/router.coffee
+++ SingularityUI/app/lib/router.coffee
@@ -25,8 +25,7 @@
 globalRefresh = =>
     clearTimeout(window.globalRefreshTimeout) if window.globalRefreshTimeout
     window.globalRefreshTimeout = setInterval ->
-        # TODO - improve the app such that these conditions are not necessary
-        if not $('body > .vex').length and $(window).scrollTop() is 0
+        if not $('body > .vex').length
             app.views.current?.refresh?()
     , 10 * 1000


Fix log message for setLogToDisplay

Fix log message for setLogToDisplay

--- src/device.coffee
+++ src/device.coffee
@@ -84,7 +84,7 @@
 				return false
 			else
 				if body.Data == true
-					logMessage("#{enable ? "Enabled" : "Disabled"} logs to display")
+					logMessage("#{if enable then "Enabled" else "Disabled"} logs to display")
 				return body.Data
 		.catch (err) ->
 			logMessage("Error setting log to display: #{err}", {error: err}, "Set log to display error")


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine.coffee
+++ src/engine.coffee
@@ -287,6 +287,10 @@
     return self

   Engine.defaultModules = [
-    "Keyboard", "Mouse", "Background", "Delay", "GameState", "Selector", "Collision"]
+    "Keyboard"
+    "Mouse"
+    "Background"
+    "Delay"
+    "GameState", "Selector", "Collision"]
   (exports ? this)["Engine"] = Engine
 )()


Fix variable name _i to i

Fix variable name _i to i

--- src/filter.coffee
+++ src/filter.coffee
@@ -362,7 +362,7 @@
 					toprocess = []

 					content = ""
-					content += "" + groupable_array[_i] + "
"
+					content += "" + groupable_array[i] + "
"

 					if groupable_array_content.length > 0
 						for j in [pointer_left..groupable_array_content.length-1]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,5 +1,5 @@
 ###*
-The Follow module provides a simple method to set an object's velocity to one pointing tow
+The Follow module provides a simple method to set an object's velocity to o

 # Health regeneration


added dynamic routes to metrics endpoints

--- src/koaApi.coffee
+++ src/koaApi.coffee
@@ -81,7 +81,7 @@

   app.use route.get '/metrics', if statsd_instance.enabled then statsd.retrieveTransactionCountPerHour else metrics.getGlobalLoadTimeMetrics
   app.use route.get '/metrics/status', if statsd_instance.enabled then statsd.fetcGlobalStatusMetrics else metrics.getGlobalStatusMetrics
-  app.use route.get '/metrics/:type/:channelId', if statsd_instance.enabled then statsd.retrieveChannelMetrics metrics.getChannelMetrics
+  app.use route.get '/metrics/:type/:channelId', if statsd_instance.enabled then statsd.retrieveChannelMetrics else metrics.getChannelMetrics
   app.use route.get '/metrics/load-time', if statsd_instance.enabled then statsd.retrieveAverageLoadTimePerHour else metrics.getGlobalLoadTimeMetrics

   app.use route.get '/statsd/status', statsd.fetcGlobalStatusMetrics


this is pure virtual, make it stand out as such.

--- src/listener.iced
+++ src/listener.iced
@@ -75,6 +75,11 @@

   ##-----------------------------------------

+  got_new_connection : (x) ->
+    throw new Error "@got_new_connection() is abstract; please implement!"
+
+  ##-----------------------------------------
+
   _make_server : () ->
     @_tcp_server = net.createServer (c) => @_got_new_connection c


feat: alternative signature syntax

feat: alternative signature syntax

--- src/loader.coffee
+++ src/loader.coffee
@@ -39,13 +39,13 @@
 Module::_compile = (answer, filename) ->
   dir = path.dirname(filename)
   basename = path.basename(filename, '.coffee')
-  module = {
+  module =
     key: "#{dir}/#{basename}"
     filename: filename
     basename: basename
     dir: dir
     exports: {}
-  }
+
   module.code = module_code(module, answer)
   plv8_exports[currentModule] = module
   res = oldcompile.apply(this, arguments)
@@ -53,12 +53,23 @@
   for k,v of @exports when v.plv8_signature?
       sig = v.plv8_signature
       params = parse_params(v.toString())[1..-1]
-      module.exports[k] ={
+
+      if Array.isArray(sig)
+        returns = sig[(sig.length - 1)]
+        args = zip(params, sig)
+      else if sig.returns
+        returns = sig.returns
+        args = zip(params, sig.arguments)
+        immutable = sig.immutable
+      else
+        throw new Error('plv8_signature should be array or object')
+
+      module.exports[k] =
         name: k
-        returns: sig[(sig.length - 1)]
-        params: zip(params, sig)
+        returns: returns
+        params: args
         filename: filename
-      }
+        immutable: immutable
   res

 _isAbsolute = (pth)->
@@ -69,6 +80,7 @@
   params = (x.join(" ") for x in info.params).join(', ')
   pass_params = (x for [x,_] in info.params).join(', ')
   declaration = "#{mod.schema}.#{name}(#{params})"
+  immutable = (info.immutable && 'IMMUTABLE') || ''

   """
   ---
@@ -78,7 +90,7 @@
   RETURNS #{info.returns} AS $JAVASCRIPT$
     var mod = require("#{mod.filename}")
     return mod.#{name}(plv8, #{pass_params})
-  $JAVASCRIPT$ LANGUAGE plv8;
+  $JAVASCRIPT$ LANGUAGE plv8 #{immutable};
   ---
   """


add user_id to device

add user_id to device

--- services/engine.coffee
+++ services/engine.coffee
@@ -7,6 +7,7 @@
 validateNewDevice = (user, new_device, cb) ->
     DataService 'getUser', user, (err, user) ->
         if user?
+            new_device.user_id = user._id
             DataService 'createDevice', new_device, cb
         else
             cb "User invalid"


Refactor: rename method to set_fields_to_edit_mode

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -149,7 +149,7 @@
     return 'Data you have entered may not be saved.' if is_dirty()
     return undefined                                      # 'undefined' suppresses 'leave page?' prompt

-set_fields_to_view_mode = ->
+set_fields_to_edit_mode = ->
   set_edit_mode_class(user_name(), edit_mode_checkbox().checked)  # TODO: smells; why do we need to know about edit_mode_checkbox?
   set_edit_mode_class(user_title(), edit_mode_checkbox().checked)
   set_edit_mode_class(user_email(), false)              # always 'false' because email is not editable
@@ -157,7 +157,7 @@
 ready = ->
   if is_resume_page()
     install_handlers()
-    set_fields_to_view_mode()
+    set_fields_to_edit_mode()
     set_dirty(false)
     display_data()


remove links of ical subscriptions

remove links of ical subscriptions

--- app/assets/javascripts/suggest.coffee
+++ app/assets/javascripts/suggest.coffee
@@ -149,21 +149,7 @@

#{event.title}

#{event.location}

-

-

-

-

-

-

-

-

-

-

-

-

-

+

" )
@@ -171,4 +157,4 @@
       previewElement.append( "

-                         " )+                         " )


Fix bug #1016: better tags rendering

Fix bug #1016: better tags rendering

--- app/coffee/modules/common/tags.coffee
+++ app/coffee/modules/common/tags.coffee
@@ -138,7 +138,8 @@
                 $model.$setViewValue(normalizeTags(tags))

         $scope.$watch $attrs.ngModel, (val) ->
-            renderTags($el, val, editable, $scope.project.tags_colors) if val?
+            tags_colors = if $scope.project?.tags_colors? then $scope.project.tags_colors else []
+            renderTags($el, val, editable, tags_colors)

         bindOnce $scope, "projectId", (projectId) ->
             # If not editable, no tags preloading is needed.


Ensure we are in a digest loop when updating validation errors

--- services/web/public/coffee/main/new-subscription.coffee
+++ services/web/public/coffee/main/new-subscription.coffee
@@ -81,7 +81,9 @@
 		completeSubscription = (err, recurly_token_id) ->
 			$scope.validation.errorFields = {}
 			if err?
-				$scope.$apply () ->
+				# We may or may not be in a digest loop here depending on
+				# whether recurly could do validation locally, so do it async
+				$scope.$evalAsync () ->
 					$scope.processing = false
 					$scope.genericError = err.message
 					_.each err.fields, (field)-> $scope.validation.errorFields[field] = true


Add missing Grim prefix to deprecate calls

Closes #3706

--- src/marker.coffee
+++ src/marker.coffee
@@ -160,7 +160,7 @@
   getProperties: ->
     @bufferMarker.getProperties()
   getAttributes: ->
-    deprecate 'Use Marker::getProperties instead'
+    Grim.deprecate 'Use Marker::getProperties instead'
     @getProperties()

   # Essential: Merges an {Object} containing new properties into the marker's
@@ -170,14 +170,14 @@
   setProperties: (properties) ->
     @bufferMarker.setProperties(properties)
   setAttributes: (properties) ->
-    deprecate 'Use Marker::getProperties instead'
+    Grim.deprecate 'Use Marker::getProperties instead'
     @setProperties(properties)

   matchesProperties: (attributes) ->
     attributes = @displayBuffer.translateToBufferMarkerParams(attributes)
     @bufferMarker.matchesParams(attributes)
   matchesAttributes: (attributes) ->
-    deprecate 'Use Marker::matchesProperties instead'
+    Grim.deprecate 'Use Marker::matchesProperties instead'
     @matchesProperties(attributes)

   ###


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/matrix.coffee
+++ src/matrix.coffee
@@ -269,7 +269,7 @@
     return scaleMatrix

   ###*
-  Returns a matrix that corresponds to a skew of tx, ty.
+  Returns a matrix that corresponds to a skew of skew.
   @see Matrix#translate
   @param {Number} tx The amount to translate in the x direction.
   @param {Number} ty The amount to translate in the y direction.


Implement SearchList for autocompletion and full-text search

--- src/models.coffee
+++ src/models.coffee
@@ -83,7 +83,7 @@

     class ServiceList extends SMCollection
         model: Service
-        initialize: () ->
+        initialize: ->
             @chosen_service = null
         expand: (id) ->
             if not id
@@ -99,6 +99,39 @@
                             data:
                                 parent: id

+    class SearchList extends SMCollection
+        initialize: ->
+            @model = (attrs, options) ->
+                type_to_model =
+                    service: Service
+                    unit: Unit
+
+                type = attrs.object_type
+                if type of type_to_model
+                    return new type_to_model[type](attrs, options)
+                else
+                    console.log "Unknown search result type '#{type}'"
+                    return new Backbone.Model(attrs, options)
+
+        autocomplete: (input, options) ->
+            opts = _.extend {}, options
+            opts.data =
+                input: input
+                language: p18n.get_language()
+            opts.reset = true
+            @fetch opts
+
+        search: (query, options) ->
+            opts = _.extend {}, options
+            opts.data =
+                q: query
+                language: p18n.get_language()
+            opts.reset = true
+            @fetch opts
+
+        url: ->
+            return "#{backend_base}/search/"
+
     exports =
         Unit: Unit
         UnitList: UnitList
@@ -111,6 +144,7 @@
         AdministrativeDivisionList: AdministrativeDivisionList
         AdministrativeDivisionType: AdministrativeDivisionType
         AdministrativeDivisionTypeList: AdministrativeDivisionTypeList
+        SearchList: SearchList

     # Expose models to browser console to aid in debugging
     window.models = exports


Don't do crazy meta-programming for caching hashKeys

--- src/object.coffee
+++ src/object.coffee
@@ -116,8 +116,7 @@

   hashKey: ->
     return if typeof @isEqual is 'function'
-    @hashKey = -> key
-    key = "<Batman.Object #{@_batmanID()}>"
+    @_batman.hashKey ||= "<Batman.Object #{@_batmanID()}>"

   toJSON: ->
     obj = {}


Fixed unvalid trackingEvents property for Wrapped Companions

--- src/parser.coffee
+++ src/parser.coffee
@@ -102,9 +102,10 @@

                                 if ad.trackingEvents?
                                     for creative in wrappedAd.creatives
-                                        for eventName in Object.keys ad.trackingEvents
-                                            creative.trackingEvents[eventName] or= []
-                                            creative.trackingEvents[eventName] = creative.trackingEvents[eventName].concat ad.trackingEvents[eventName]
+                                        if creative.type is 'linear'
+                                            for eventName in Object.keys ad.trackingEvents
+                                                creative.trackingEvents[eventName] or= []
+                                                creative.trackingEvents[eventName] = creative.trackingEvents[eventName].concat ad.trackingEvents[eventName]

                                 response.ads.splice index, 0, wrappedAd


fix options

fix options

--- src/parser.coffee
+++ src/parser.coffee
@@ -30,7 +30,7 @@
         l.trim()[0] is '#'

     parseOption: (l)->
-        kv = l.replace(/\s|;/g, '')[1...].split '='
+        kv = l.replace(/\s|;|'|"/g, '')[1...].split '='
         if kv and kv[1]
             if /^true|yes$/i.test kv[1] then kv[1] = yes
             if /^false|no$/i.test kv[1] then kv[1] = no


support code in atom/atom

support code in atom/atom

--- src/parser.coffee
+++ src/parser.coffee
@@ -92,8 +92,12 @@
       constructor: (root) ->
         root.traverseChildren no, (exp) => @visit(exp) # `no` means Stop at scope boundaries

-      visit: (exp) ->  @["visit#{exp.constructor.name}"](exp)
-      eval:  (exp) ->  @["eval#{exp.constructor.name}"](exp)
+      visit: (exp) ->
+        # throw new Error "Could not parse line #{lineMapping[exp.locationData.first_line]} because of missing visit#{exp.constructor.name}()" unless @["visit#{exp.constructor.name}"]
+        @["visit#{exp.constructor.name}"](exp)
+      eval:  (exp) ->
+        # throw new Error "Could not parse line #{lineMapping[exp.locationData.first_line]} because of missing eval#{exp.constructor.name}()" unless @["eval#{exp.constructor.name}"]
+        @["eval#{exp.constructor.name}"](exp)

       visitComment: (exp) ->
         # Skip the 1st comment which is added by coffeescript
@@ -109,15 +113,17 @@
         variable = @eval(exp.variable)
         value = @eval(exp.value)

-        baseName = variable.base.value
+        baseName = exp.variable.base.value
         switch baseName
           when 'module'
-            unless variable.properties?[0]?.name?.value is 'exports'
+            return if exp.variable.properties.length is 0 # Ignore `module = ...` (atom/src/browser/main.coffee)
+            unless exp.variable.properties?[0]?.name?.value is 'exports'
+              # console.log _.map variable.properties, (item) -> item.name.value
               throw new Error 'BUG: Does not support module.somthingOtherThanExports'
             baseName = 'exports'
-            firstProp = variable.properties[1]
+            firstProp = exp.variable.properties[1]
           when 'exports'
-            firstProp = variable.properties[0]
+            firstProp = exp.variable.properties[0]

         switch baseName
           when 'exports'
@@ -144,17 +150,20 @@

           # case left-hand-side is anything other than `exports...`
           else
-            # Handle 3 common cases:
+            # Handle 4 common cases:
             #
             # X     = ...
             # {X}   = ...
             # {X:Y} = ...
-            switch variable.base.constructor.name
+            # X.y   = ...
+            switch exp.variable.base.constructor.name
               when 'Literal'
-                # case X = ...
-                @defs[variable.base.value] = _.extend name: variable.base.value, value
+                # case X.y = ...  (just ignore it)
+                unless exp.variable.properties?.length > 0
+                  # case X = ...
+                  @defs[exp.variable.base.value] = _.extend name: exp.variable.base.value, value
               when 'Obj'
-                for key in variable.base.objects
+                for key in exp.variable.base.objects
                   switch key.constructor.name
                     when 'Value'
                       # case {X} = ...
@@ -260,7 +269,16 @@
         startLineNumber:  lineMapping[exp.locationData.first_line]
         endLineNumber:    lineMapping[exp.locationData.last_line]

-      evalValue: (exp) -> exp
+      evalValue: (exp) ->
+        if exp.base
+          type: 'primitive'
+          name: exp.base?.value
+          doc: @commentLines[lineMapping[exp.locationData.first_line] - 1]
+          startLineNumber:  lineMapping[exp.locationData.first_line]
+          endLineNumber:    lineMapping[exp.locationData.last_line]
+        else
+          throw new Error 'BUG? Not sure how to evaluate this value if it does not have .base'
+
       evalCall: (exp) ->
         # The only interesting call is `require('foo')`
         if exp.variable.base.value is 'require'
@@ -288,13 +306,32 @@
           ret

         else
-          null
-
-      evalAssign: (exp) -> throw new Error 'BUG: Not implemented yet'
-      evalLiteral: (exp) -> throw new Error 'BUG: Not implemented yet'
-      evalObj: (exp) -> throw new Error 'BUG: Not implemented yet'
-      evalAccess: (exp) -> throw new Error 'BUG: Not implemented yet'
-
+          type: 'function'
+          doc: @commentLines[lineMapping[exp.locationData.first_line] - 1]
+          startLineNumber:  lineMapping[exp.locationData.first_line]
+          endLineNumber:    lineMapping[exp.locationData.last_line]
+
+
+      evalError: (str, exp) ->
+        throw new Error "BUG: Not implemented yet: #{str}. Line #{exp.locationData.first_line}"
+      evalAssign: (exp) -> @eval(exp.value) # Support x = y = z
+      evalLiteral: (exp) -> @evalError 'evalLiteral', exp
+      evalObj: (exp) -> @evalError 'evalObj', exp
+      evalAccess: (exp) -> @evalError 'evalAccess', exp
+
+      evalUnknown: (exp) -> exp
+      evalIf: -> @evalUnknown(arguments)
+      visitIf: ->
+      visitOp: ->
+      visitArr: ->
+      visitNull: ->
+      visitBool: ->
+      visitIndex: ->
+      visitParens: ->
+
+      evalOp: (exp) -> exp
+
+    # console.log 'Visiting ' + file

     {defs:unindexedObjects, exports:exports} = new Visitor(root)
     objects = {}


rename parse tree node factories

rename parse tree node factories

--- src/parser.coffee
+++ src/parser.coffee
@@ -4,13 +4,13 @@

 $ = require './symbols'

-# parse tree node builders
-parseTreeLeafNode = (type, value = null) -> {type, value}
-parseTreeBranchNode = (type, value = null, children = []) -> {type, value, children}
+# parse tree node factories
+ParseTreeLeafNode = (type, value = null) -> {type, value}
+ParseTreeBranchNode = (type, value = null, children = []) -> {type, value, children}

 module.exports = class Parser
   parse: (code, @opts = {}) ->
-    @parseTree = parseTreeBranchNode @opts.root or $.ROOT # concrete syntax tree (initialised with root node)
+    @parseTree = ParseTreeBranchNode @opts.root or $.ROOT # concrete syntax tree (initialised with root node)
     @activeStates = [@parseTree] # stack tracking current parse tree position (starting with root)
     @chunkLine = 0 # The start line for the current @chunk.
     @chunkColumn =  0 # The start column of the current @chunk.
@@ -70,10 +70,10 @@
           prefix = pragmaMatch[1]
         else
           prefix = 'React.DOM'
-        @addLeafNodeToActiveBranch parseTreeLeafNode $.CJSX_PRAGMA, prefix
+        @addLeafNodeToActiveBranch ParseTreeLeafNode $.CJSX_PRAGMA, prefix
         return comment.length

-    @addLeafNodeToActiveBranch parseTreeLeafNode $.CS_COMMENT, comment
+    @addLeafNodeToActiveBranch ParseTreeLeafNode $.CS_COMMENT, comment
     comment.length

   # Matches heredocs
@@ -81,7 +81,7 @@
     return 0 unless match = HEREDOC.exec @chunk
     heredoc = match[0]

-    @addLeafNodeToActiveBranch parseTreeLeafNode $.CS_HEREDOC, heredoc
+    @addLeafNodeToActiveBranch ParseTreeLeafNode $.CS_HEREDOC, heredoc

     heredoc.length

@@ -93,7 +93,7 @@
       when '"' then string = @balancedString @chunk, '"'
     return 0 unless string

-    @addLeafNodeToActiveBranch parseTreeLeafNode $.CS_STRING, string
+    @addLeafNodeToActiveBranch ParseTreeLeafNode $.CS_STRING, string

     string.length

@@ -112,7 +112,7 @@
     return 0 if regex.indexOf("\n") > -1 # no newlines in a normal regex
     # Avoid conflicts with floor division operator.
     return 0 if regex is '//'
-    @addLeafNodeToActiveBranch parseTreeLeafNode $.CS_REGEX, match
+    @addLeafNodeToActiveBranch ParseTreeLeafNode $.CS_REGEX, match
     match.length

   # Matches multiline extended regular expressions.
@@ -120,7 +120,7 @@
     return 0 unless match = HEREGEX.exec @chunk
     [heregex, body, flags] = match

-    @addLeafNodeToActiveBranch parseTreeLeafNode $.CS_HEREGEX, heregex
+    @addLeafNodeToActiveBranch ParseTreeLeafNode $.CS_HEREGEX, heregex

     heregex.length

@@ -129,7 +129,7 @@
     return 0 unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
     script = match[0]

-    @addLeafNodeToActiveBranch parseTreeLeafNode $.JS_ESC, script
+    @addLeafNodeToActiveBranch ParseTreeLeafNode $.JS_ESC, script

     script.length

@@ -139,8 +139,8 @@

     return 0 unless selfClosing or @chunk.indexOf("</#{tagName}>", input.length) > -1

-    @pushActiveBranchNode parseTreeBranchNode $.CJSX_EL, tagName
-    @pushActiveBranchNode parseTreeBranchNode $.CJSX_ATTRIBUTES
+    @pushActiveBranchNode ParseTreeBranchNode $.CJSX_EL, tagName
+    @pushActiveBranchNode ParseTreeBranchNode $.CJSX_ATTRIBUTES

     1+tagName.length

@@ -168,41 +168,41 @@

     if attrName
       if doubleQuotedVal? # "value"
-        @addLeafNodeToActiveBranch parseTreeBranchNode($.CJSX_ATTR_PAIR, null, [
-          parseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
-          parseTreeLeafNode($.CJSX_ATTR_VAL, "\"#{doubleQuotedVal}\"")
+        @addLeafNodeToActiveBranch ParseTreeBranchNode($.CJSX_ATTR_PAIR, null, [
+          ParseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
+          ParseTreeLeafNode($.CJSX_ATTR_VAL, "\"#{doubleQuotedVal}\"")
         ])
         return input.length
       else if singleQuotedVal? # 'value'
-        @addLeafNodeToActiveBranch parseTreeBranchNode($.CJSX_ATTR_PAIR, null, [
-          parseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
-          parseTreeLeafNode($.CJSX_ATTR_VAL, "'#{singleQuotedVal}'")
+        @addLeafNodeToActiveBranch ParseTreeBranchNode($.CJSX_ATTR_PAIR, null, [
+          ParseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
+          ParseTreeLeafNode($.CJSX_ATTR_VAL, "'#{singleQuotedVal}'")
         ])
         return input.length
       else if cjsxEscVal # {value}
-        @pushActiveBranchNode parseTreeBranchNode $.CJSX_ATTR_PAIR
-        @addLeafNodeToActiveBranch parseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
+        @pushActiveBranchNode ParseTreeBranchNode $.CJSX_ATTR_PAIR
+        @addLeafNodeToActiveBranch ParseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
         # on next iteration of parse loop, '{' will trigger CJSX_ESC state
         return input.indexOf('{') # consume up to start of cjsx escape
       else if bareVal # value
-        @addLeafNodeToActiveBranch parseTreeBranchNode($.CJSX_ATTR_PAIR, null, [
-          parseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
-          parseTreeLeafNode($.CJSX_ATTR_VAL, bareVal)
+        @addLeafNodeToActiveBranch ParseTreeBranchNode($.CJSX_ATTR_PAIR, null, [
+          ParseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
+          ParseTreeLeafNode($.CJSX_ATTR_VAL, bareVal)
         ])
         return input.length
       else # valueless attr
-        @addLeafNodeToActiveBranch parseTreeBranchNode($.CJSX_ATTR_PAIR, null, [
-          parseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
-          parseTreeLeafNode($.CJSX_ATTR_VAL, 'true')
+        @addLeafNodeToActiveBranch ParseTreeBranchNode($.CJSX_ATTR_PAIR, null, [
+          ParseTreeLeafNode($.CJSX_ATTR_KEY, "\"#{attrName}\"")
+          ParseTreeLeafNode($.CJSX_ATTR_VAL, 'true')
         ])
         return input.length
     else if spreadAttr # {... x, y}
-      @pushActiveBranchNode parseTreeBranchNode $.CJSX_ATTR_SPREAD
+      @pushActiveBranchNode ParseTreeBranchNode $.CJSX_ATTR_SPREAD
       # on next iteration of parse loop, '{' will trigger CJSX_ESC state and be
       # parsed as CJSX_ESC (which has similar properties), to be transformed later
       return input.indexOf('{')
     else if whitespace?
-      @addLeafNodeToActiveBranch parseTreeLeafNode($.CJSX_WHITESPACE, whitespace)
+      @addLeafNodeToActiveBranch ParseTreeLeafNode($.CJSX_WHITESPACE, whitespace)
       return input.length
     else
       throwSyntaxError \
@@ -213,7 +213,7 @@
     return 0 unless @chunk.charAt(0) is '{' and
     @currentState() in [$.CJSX_EL, $.CJSX_ATTR_PAIR, $.CJSX_ATTR_SPREAD]

-    @pushActiveBranchNode parseTreeBranchNode $.CJSX_ESC
+    @pushActiveBranchNode ParseTreeBranchNode $.CJSX_ESC
     @activeBranchNode().stack = 1 # keep track of opening and closing braces
     return 1

@@ -246,7 +246,7 @@
     return 0 unless @currentState() is $.CJSX_EL

     unless @newestNode().type is $.CJSX_TEXT
-      @addLeafNodeToActiveBranch parseTreeLeafNode $.CJSX_TEXT, '' # init value as string
+      @addLeafNodeToActiveBranch ParseTreeLeafNode $.CJSX_TEXT, '' # init value as string

     # newestNode is (now) $.CJSX_TEXT
     @newestNode().value += @chunk.charAt 0
@@ -266,7 +266,7 @@
           return 0

     unless @newestNode().type is $.CS
-      @addLeafNodeToActiveBranch parseTreeLeafNode $.CS, '' # init value as string
+      @addLeafNodeToActiveBranch ParseTreeLeafNode $.CS, '' # init value as string

     # newestNode is (now) $.CS
     @newestNode().value += @chunk.charAt 0


Refactored keys for moving into object

Refactored keys for moving into object

--- src/player.coffee
+++ src/player.coffee
@@ -3,6 +3,21 @@

 direction = require './direction'
 {whilst} = require './util'
+
+keys = {
+	'1': 'down-left'
+	'2': 'down'
+	'3': 'down-right'
+	'4': 'left'
+	'5': 'idle'
+	'6': 'right'
+	'7': 'up-left'
+	'8': 'up'
+	'9': 'up-right'
+
+	'up', 'down', 'left', 'right'
+	'.': 'idle'
+}

 module.exports = class Player
 	constructor: (@creature) ->
@@ -26,29 +41,33 @@
 			d = Q.defer()

 			game.events.once 'key.*', (ch, key) =>
-				moveOffset = direction.directions[key.name] ? [0, 0]
+				moveDir = keys[key.full]
+				if moveDir?
+					return d.resolve 12 if moveDir is 'idle'

-				if @creature.move moveOffset...
-					d.resolve 12
+					moveOffset = direction.parse moveDir

-				else
-					switch key.full
-						when 's' then game.save 'test-save.json'
-						when 'S-s' then game.load 'test-save.json'
+					return d.resolve (
+						if (@creature.move moveOffset...) then 12 else 0
+					)

-						when 'p'
-							entities = @creature.map.entities
-							entities.push entities.shift()
-							@creature = entities[0]
-							(require './game').camera.update()
+				switch key.full
+					when 's' then game.save 'test-save.json'
+					when 'S-s' then game.load 'test-save.json'

-						when 'd'
-							winston = require 'winston'
+					when 'p'
+						entities = @creature.map.entities
+						entities.push entities.shift()
+						@creature = entities[0]
+						(require './game').camera.update()

-							for e in @creature.map.entities
-								winston.info e.toJSON()
+					when 'd'
+						winston = require 'winston'

-					d.resolve 0
+						for e in @creature.map.entities
+							winston.info e.toJSON()
+
+				d.resolve 0

 			d.promise


Email handles subscription and unsubscription

--- src/server.coffee
+++ src/server.coffee
@@ -51,10 +51,10 @@
         labels = labelActions.map (la) -> la.label

         # Look for subscribe request
-        subscribeLabels = la.label for la in labelActions when 'subscribe' in la.actions
+        subscribeLabels = (la.label for la in labelActions when 'subscribe' in la.actions)

         # Look for unsubscribe request
-        unsubscribeLabels = la.label for la in labelActions when 'unsubscribe' in la.actions
+        unsubscribeLabels = (la.label for la in labelActions when 'unsubscribe' in la.actions)

         # Inject a MessageID if does not exist. We need this to track Threads
         #
@@ -67,27 +67,59 @@
             body: message.body[0].content

         console.log msg
-
-        if id = message.header['in-reply-to']?.value
-            Thread.findOne { 'messages.id': id }, (err, thread) =>
+
+        # Subscription/Unsubscription handling
+        #
+        senderEmail = utils.extractEmails [ msg.sender ]
+
+        if subscribeLabels?
+            User.findOne { 'email': senderEmail }, (err, user) =>
+                if err?
+                    @logger.error "Cannot subscribe #{subscribeLabels} for #{senderEmail}: #{err}"
+                else
+                    # Create user if not existing
+                    user = new User({ name: senderEmail, email: senderEmail, subscriptions: [] }) unless user?
+                    user.subscriptions.push label for label in subscribeLabels
+                    console.log subscribeLabels
+                    console.log user.subscriptions
+                    user.save()
+
+        if unsubscribeLabels?
+            User.findOne { 'email': senderEmail }, (err, user) =>
+                if err?
+                    @logger.error "Cannot unsubscribe #{unsubscribeLabels} for #{senderEmail}: #{err}"
+                else
+                    # Create user if not existing
+                    user = new User({ name: senderEmail, email: senderEmail, subscriptions: [] }) unless user?
+                    newSubscriptions = (s for s in user.subscriptions when s not in unsubscribeLabels)
+                    user.subscriptions = newSubscriptions
+                    user.save()
+
+        # If this user is subscribing or unsubscribing
+        # We also check if he is actually posting something
+        #
+        if (subscribeLabels? or unsubscribeLabels?) and msg.body.match /^\s*$/
+            console.log 'Empty body. Ignoring'
+        else
+            if id = message.header['in-reply-to']?.value
+                Thread.findOne { 'messages.id': id }, (err, thread) =>
+                    thread.messages.push msg
+                    thread.save (err) =>
+                        @logger.error "Problem saving the thread: #{err}" if err?
+
+                        User.find { pins: thread.id }, (err, users) =>
+                            console.log "send to user #{users}"
+                            @send(user, message) for user in users
+            else
+                thread = new Thread labels: labels
                 thread.messages.push msg
+
                 thread.save (err) =>
-                    @logger.error "Problem saving the thread: #{err}" if err?
+                    @logger.error "Cannot create new thread: #{err}" if err?

-                    User.find { pins: thread.id }, (err, users) =>
-                        console.log "send to user #{users}"
+                    User.find { 'subscriptions': { $in: labels } }, (err, users) =>
                         @send(user, message) for user in users
-        else
-            thread = new Thread labels: labels
-            thread.messages.push msg
-
-            thread.save (err) =>
-                @logger.error "Cannot create new thread: #{err}" if err?
-
-                User.find { 'subscriptions': { $in: labels } }, (err, users) =>
-                    @send(user, message) for user in users
-
-        console.log "sent"
+            console.log "sent"

         #Thread.find { 'labels': { $in: labels } }, (err, docs) ->
         #    console.log docs