Missed my first commit

Missed my first commit

--- fattable.coffee
+++ fattable.coffee
@@ -1,8 +1,3 @@
-
-
-
-generate_table = (m,n)->
-    (  ( i + "," + j  for j in [0...n] by 1) for i in [0...m] by 1)

 sum = (arr)->
@@ -21,16 +16,16 @@
     cs

 class TableData
-
-    constructor: (@data)->
+    # TODO make it asynchronous
+    constructor: (@_nb_rows, @_nb_cols)->
     get: (i,j)->
-        @data[i][j]
+        i + "," + j
     header: (i)->
         "col " + i
     nb_cols: ->
-        @data[0].length
+        @_nb_cols
     nb_rows: ->
-        @data.length
+        @_nb_rows

 scroll_options =
     mouseWheel: true
@@ -38,15 +33,15 @@
     scrollX: true
     probeType: 3
     interactiveScrollbars: true
-    momentum: false
+    deceleration: 0.01
     keyBindings:
-        pageUp: 33,
-        pageDown: 34,
-        end: 35,
-        home: 36,
-        left: 37,
-        up: 38,
-        right: 39,
+        pageUp: 33
+        pageDown: 34
+        end: 35
+        home: 36
+        left: 37
+        up: 38
+        right: 39
         down: 40

@@ -71,6 +66,16 @@
 distance = (a1, a2)->
     Math.abs(a2-a1)

+closest = (x, vals...)->
+    d = 99999
+    res = undefined
+    for x_ in vals
+        d_ = distance x,x_
+        if d_ < d
+            d = d_
+            res = x_
+    res
+
 class TableView

     constructor: (container, @data, @layout)->
@@ -80,10 +85,11 @@
             @container = container
         @nb_cols = @data.nb_cols()
         @nb_rows = @data.nb_rows()
-        @W = sum @layout.column_width
+        @W = sum @layout.column_widths
+        @row_height  = @layout.row_height
         @H = @layout.row_height * @nb_rows
-        @col_offset = cumsum @layout.column_width
-        @min_col = Math.min.apply null, @layout.column_width
+        @col_offset = cumsum @layout.column_widths
+        @min_col = Math.min.apply null, @layout.column_widths
         onDomReady = =>
             document.removeEventListener "DOMContentLoaded", arguments.callee, false
             @setup()
@@ -119,7 +125,7 @@
             @headerContainer.innerHtml = ""

         @w = @container.offsetWidth
-        @h = @container.offsetHeight - @layout.columnHeaderHeight
+        @h = @container.offsetHeight - @layout.header_height

         @last_i = 0
         @last_j = 0
@@ -130,21 +136,21 @@
         @headerContainer = document.createElement "div"
         @headerContainer.style.position =  "absolute"
         @headerContainer.style.top = "0px";
-        @headerContainer.style.height = @layout.columnHeaderHeight + "px";
+        @headerContainer.style.height = @layout.header_height + "px";
         @headerContainer.style.width = "100%";
         @headerContainer.className = "header-container"

         @headerViewport =  document.createElement "div"
         @headerViewport.className = "viewport"
         @headerViewport.style.width = @W + "px"
-        @headerViewport.style.height = @layout.columnHeaderHeight + "px"
+        @headerViewport.style.height = @layout.header_height + "px"
         @headerViewport.style.overflow = "hidden"
         @headerContainer.appendChild @headerViewport

         # body container
         @bodyContainer = document.createElement "div"
         @bodyContainer.style.position =  "absolute"
-        @bodyContainer.style.top = @layout.columnHeaderHeight + "px";
+        @bodyContainer.style.top = @layout.header_height + "px";
         @bodyContainer.style.bottom = "0px";
         @bodyContainer.style.width = "100%";

@@ -188,7 +194,16 @@
             me.headerScroll.scrollTo @x,0
             me.repaint -@x,-@y
         @scroll.on "scrollEnd", ->
-            me.repaint -@x,-@y
+            if @y > 0
+                @scrollTo @x, 0
+            if @x > 0
+                @scrollTo 0, @y
+            [i,j] = me.visible -@x,-@y
+            snapped_x = closest -@x, me.col_offset[j], me.col_offset[j+1]
+            snapped_y = closest -@y, i * me.row_height, (i+1)*me.row_height
+            @scrollTo -snapped_x, -snapped_y
+            me.headerScroll.scrollTo -snapped_x, 0
+            me.repaint snapped_x,snapped_y

     show_column_header: (j)->
         colEl = @headerPool.pop()
@@ -196,8 +211,8 @@
         colEl.innerText = data
         colEl.style.left = @col_offset[j] + "px"
         colEl.style.top = "0"
-        colEl.style.width = @layout.column_width[j] - 1 + "px"
-        colEl.style.height = @layout.columnHeaderHeight + "px"
+        colEl.style.width = @layout.column_widths[j] - 1 + "px"
+        colEl.style.height = @layout.header_height + "px"
         @headerViewport.appendChild colEl
         @columns[j] = colEl

@@ -213,7 +228,7 @@
         el.innerText = data
         el.style.left = @col_offset[j] + "px"
         el.style.top = @layout.row_height * i + "px"
-        el.style.width = @layout.column_width[j] - 1 + "px"
+        el.style.width = @layout.column_widths[j] - 1 + "px"
         el.style.height = @layout.row_height - 1 + "px"
         @viewport.appendChild el
         @cells[","+ (i + j * @nb_rows) ] = el
@@ -275,23 +290,6 @@
         @last_j = j

-layout =
-    columnHeaderHeight: 100
-    row_height:  20
-    column_width: ( 50 for i in [0...100] )
-
-table_data = new TableData (generate_table 1000,500)
-window.table_view = new TableView "#container", table_data, layout
-
-
-
-
-
-
-
-
-
-
-
-
-
+window.TableData = TableData
+window.TableView = TableView
+


Fix implementation of and add repetition to moveTabLeft and moveTabRight commands

This fixes #1056.

--- background_scripts/main.coffee
+++ background_scripts/main.coffee
@@ -219,7 +219,9 @@

 moveTab = (callback, direction) ->
   chrome.tabs.getSelected(null, (tab) ->
-    chrome.tabs.move(tab.id, {index: tab.index + direction }, callback))
+    # Use Math.max to prevent -1 as the new index, otherwise the tab of index n will wrap to the far RHS when
+    # moved left by exactly (n+1) places.
+    chrome.tabs.move(tab.id, {index: Math.max(0, tab.index + direction) }, callback))

 # Start action functions

@@ -238,8 +240,6 @@
   previousTab: (callback) -> selectTab(callback, "previous")
   firstTab: (callback) -> selectTab(callback, "first")
   lastTab: (callback) -> selectTab(callback, "last")
-  moveTabLeft: (callback) -> moveTab(callback, -1)
-  moveTabRight: (callback) -> moveTab(callback, 1)
   removeTab: ->
     chrome.tabs.getSelected(null, (tab) ->
       chrome.tabs.remove(tab.id))
@@ -275,6 +275,8 @@
     chrome.tabs.getSelected(null, (tab) ->
       chrome.tabs.sendMessage(tab.id,
         { name: "toggleHelpDialog", dialogHtml: helpDialogHtml(), frameId:frameId }))
+  moveTabLeft: (count) -> moveTab(null, -count)
+  moveTabRight: (count) -> moveTab(null, count)
   nextFrame: (count) ->
     chrome.tabs.getSelected(null, (tab) ->
       frames = framesForTab[tab.id].frames


remove old scheduling

remove old scheduling

--- src/db.appsscript.coffee
+++ src/db.appsscript.coffee
@@ -4,7 +4,7 @@
 	@TYPE_MAIL:		'mail'
 	@TYPE_USER:		'user'

-	@getMails: (user, version, time, processed = null) ->
+	@getMails: (user, version, time, processed = null, messageId = null) ->
 		q =
 			type:		@TYPE_MAIL

@@ -12,6 +12,7 @@
 		q.version = @DB.lessThanOrEqualTo Number version if version
 		q.when = @DB.lessThanOrEqualTo Number time if time
 		q.processed = !! processed if processed isnt null
+		q.messageId = messageId if messageId

 		Logger.log 'getting mails with query %s', q

@@ -27,6 +28,17 @@
 		result = result.sortBy 'user'
 		result

+	@cancelMails: (user, messageId) ->
+		scheduledMails = @getMails user, null, null, false, messageId
+		modified = while result.hasNext()
+			mail.next().processed = 'canceled'
+
+		if modified.length > 0
+			@DB.saveBatch modified, false
+			@DB.allOk modified
+		else
+			true
+
 	@storeMail: (user, version, props) ->
 		return null if not props or not user or not version
 		props.user = user
@@ -34,6 +46,7 @@
 		props.type = @TYPE_MAIL
 		props.processed = false

+		@cancelMails user, props.messageId
 		@DB.save props

 	@removeMail: (mail) ->


apparently Java says that '.' is no longer on the classpath if one is provided

--- console/runner.coffee
+++ console/runner.coffee
@@ -39,7 +39,8 @@
     util.log_level = argv.log + 0

   if argv.classpath?
-    classpath = argv.classpath.split(':').concat classpath
+    classpath = argv.classpath.split ':'
+    classpath.push "#{__dirname}/../third_party/classes"

   cname = argv._[0]
   cname = cname[0...-6] if cname[-6..] is '.class'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -57,7 +57,7 @@

   @name distance
   @methodOf Bounded#
-  @returns {Point} The position of this object
+  @returns {Number} The position of this object
   ###
   distance: (otherObj) ->
     Point.distance(self.position(), otherObj.position())


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -40,7 +40,8 @@
     height: 8
     collisionMargin: Point(0, 0)

-
+  distance: (otherObj) ->
+    Point.distance(self.position(), otherObj.position())
   ###*
   The position of this game object. By default it is the top left point.
   Redefining the center method will change the relative position.


Add "chosen" class to the target element

--- vendor/assets/javascripts/chosen/chosen.js.coffee
+++ vendor/assets/javascripts/chosen/chosen.js.coffee
@@ -29,7 +29,7 @@
       @disabled = true
       @enable()

-    @$target.hide().after(@$container)
+    @$target.addClass("chosen").hide().after(@$container)

   destroy: ->
     @unbind_events()
@@ -38,7 +38,7 @@

     @$container.remove()
     @$dropdown.remove()
-    @$target.removeData("chosen").show()
+    @$target.removeData("chosen").removeClass("chosen").show()

     delete @$container
     delete @$dropdown
@@ -327,6 +327,7 @@
     @move_selection_to(cursor)

     if @cursor_option and @cursor_option.selected and @parser.selectable_options.length
+      # TODO: optimize this
       return @move_selection(dir)

     return @


added shift enter for newlines

added shift enter for newlines

--- src/components/app/pages/thread/chat_box.coffee
+++ src/components/app/pages/thread/chat_box.coffee
@@ -8,7 +8,10 @@
     @refs.textField.clearValue()

   _onKeyDown: (e) ->
-    if e.which == 13
+    if e.which == 13 and e.shiftKey
+      # On shift enter, let it behave as normal.
+      return true
+    else if e.which == 13
       e.preventDefault()
       e.stopPropagation()
       @_onSend()


Fix typo

Fix typo

--- src/cmd/use.coffee
+++ src/cmd/use.coffee
@@ -13,7 +13,7 @@

 Only installed versions are used. Use `#{PACKAGE.mingzi} install` for new ones.

-Say `#{PACKAGE.mingzi} use none` to temprarily disable #{PACKAGE.mingzi}.
+Say `#{PACKAGE.mingzi} use none` to temporarily disable #{PACKAGE.mingzi}.
 """

 @cmd = ->


Fixed if function behaivour

Fixed if function behaivour

--- src/corelib.coffee
+++ src/corelib.coffee
@@ -64,10 +64,10 @@
   # Returns interpreted second arg if first arg evaluates to true.
   # Otherwise it returns the interpreted third argument.
   if: (input, context) ->
-    if input[0]
-      whip.interpret input[1], context
+    if whip.interpret input[1]
+      whip.interpret input[2], context
     else
-      whip.interpret input[2], context if input[2]?
+      whip.interpret input[3], context if input[2]?

 module.exports = fns
 # Lambda shortcut of `->`


Resolved committed merge errors

Resolved committed merge errors

--- client/list.coffee
+++ client/list.coffee
@@ -128,79 +128,6 @@
         element.addClass('hidden')
         Session.set("edit-#{single}", null)
         return false
-=======
-    _listRowEvents[templateEvent] = (event,template) ->
-      data = template.data
-      single = data.context.single
-      sessionVar = "selected-#{single}"
-      if Session.get(sessionVar) is data._id
-        Session.set(sessionVar, null)
-      else
-        Session.set(sessionVar, data._id)
-
-      return false
-
-    templateEvent = "click .enable-#{data.single}"
-    _listRowEvents[templateEvent] = (event,template) ->
-      data = template.data
-      single = data.context.single
-      Klass = data.context.klass
-      bootbox.itemId = data._id
-      confirmation = """
-      Are you sure you want to re-enable this #{single}?
-      """
-      bootbox.confirm confirmation, (confirmed) ->
-        if confirmed
-          Klass.update bootbox.itemId, {$set: {status: ''}}
-      return false
-
-    templateEvent = "click .disable-#{data.single}"
-    _listRowEvents[templateEvent] = (event,template) ->
-      data = template.data
-      single = data.context.single
-      Klass = data.context.klass
-      bootbox.itemId = data._id
-      if typeof data.context.disableMessageTemplate isnt 'undefined'
-        confirmation = data.context.disableMessageTemplate
-      else
-        confirmation = """
-        Are you sure you want to disable this #{single}?
-

-

-

-
Severe Warning

-          No users will be able to use this #{single}.  This could lead to a service interruption.
-

-        """
-
-      bootbox.confirm confirmation, (confirmed) ->
-        if confirmed
-          Klass.update bootbox.itemId, {$set: {status: 'disabled'}}
-      return false
-
-    templateEvent = "click .edit-#{data.single}"
-    _listRowEvents[templateEvent] = (event,template) ->
-      data = template.data
-      single = data.context.single
-      Session.set("edit-#{single}", data._id)
-      element = $("##{data._id}")
-      if element.hasClass('hidden')
-        $('.form-row').addClass('hidden')
-        element.removeClass('hidden')
-      else
-        element.addClass('hidden')
-        Session.set("edit-#{single}", null)
-      return false
-
-    templateEvent = "click .close-form-#{data.single}"
-    _listRowEvents[templateEvent] = (event,template) ->
-      data = template.data
-      single = data.context.single
-      element = $("##{data._id}")
-      element.addClass('hidden')
-      Session.set("edit-#{single}", null)
-      return false
->>>>>>> 2d6d00a2da830ee8d011084d970a22eed0949f7f

     # Add in or override user defined events.
     if data.list?.events?
@@ -218,7 +145,6 @@
     # Build up an array of the items to be in the list
     # adding in the context data
     items = []
-<<<<<<
     # console.log 'listNav:nav', this
-=======
-    if data.items? and typeof data.items is 'function'
-      for item in data.items()
-        data = Object.reject(data, 'items')
-        item.context = data
-        items.add item
-
-      if items.length > 0
-        return items
-
-Template.listNav.helpers
-  nav: () ->
->>>>>>> 2d6d00a2da830ee8d011084d970a22eed0949f7f
     data = this
     single = data.single
     plural = data.plural
@@ -259,7 +172,6 @@
     # data as nav.events.
     navEvents = {}
     templateEvent = "click .add-#{data.single}"
-<<<<<<
@@ -317,67 +229,12 @@
         $(".#{single}-search-query").attr('value','')
         Session.set("#{single}Find",{})
         return false
-=======
-    navEvents[templateEvent] = (event, template) ->
-      context = template.data
-      single = context.single
-      klass = context.klass
-      console.log "You have not defined a custom add handler for #{data.title}.  Using the default."
-      if typeof context.newItemCallback isnt 'undefined'
-        _id = context.newItemCallback()
-      else
-        _id = klass.insert()
-
-      # Show the edit form after a 100ms delay after the item is inserted .
-      showForm = () ->
-        $("##{_id}").removeClass('hidden')
-
-      setTimeout showForm, 100, _id
-
-      return false
-    # ---------------------------------------------------------------------------------------
-
-    templateEvent = "click .purge-disabled-#{data.plural}"
-    navEvents[templateEvent] = (event, template) ->
-      context = template.data
-      plural = context.plural
-      console.log "You have not defined a custom purge handler for #{data.title}.  Using the default."
-      confirm_dialog = """
-      Are you sure you want to purge the diabled #{plural}?
-

-

-
Severe Warning

-        All purged #{plural} cannot be recovered.  This will be your ONLY warning.
-

-      """
-
-      bootbox.confirm confirm_dialog, (confirmed) ->
-        if confirmed
-          Meteor.call "purge#{context.title}",
-          (error, result) ->
-            if error
-              Meteor.Errors.throw error
-            if result
-              Meteor.Notices.throw('Your disabled records have been purged.', 'success')
-      return false
-    # ---------------------------------------------------------------------------------------
-
-    templateEvent = "click .reset"
-    navEvents[templateEvent] = (event, template) ->
-      context = template.data
-      single = context.single
-      $(".#{single}-search").addClass('hide')
-      $(".#{single}-search-query").attr('value','')
-      Session.set("#{single}Find",{})
-      return false
->>>>>>> 2d6d00a2da830ee8d011084d970a22eed0949f7f
-
+
     # ---------------------------------------------------------------------------------------
     # Search
     # At the moment the query for search uses name as the key.  Need to revist this
     # to make it so that the package user can set what the default
     templateEvent = "keyup .#{data.single}-search-query"
-<<<<<<
         context = template.data
@@ -408,37 +265,6 @@
           $(".#{data.single}-search").addClass("hide")
           $(".#{data.single}-search-query").attr("value","")
         return false
-=======
-    navEvents[templateEvent] = (event, template) ->
-      context = template.data
-      single = context.single
-      value = event.target.value
-      $(".#{data.single}-search").removeClass("hide")
-      if value?
-        if value.has("=")
-          key = value.split("=")[0]
-          search_string = value.split("=")[1]
-          search = {}
-          search[key] =
-            $regex: ".*#{search_string}.*"
-            $options: "i"
-          Session.set("#{data.single}Find", search)
-        else if value.has(" ")
-          search_terms = value.split " "
-          regex_array =
-            for term in search_terms
-               ".*#{term}.*"
-          regex = regex_array.join "|"
-          Session.set("#{data.single}Find", {name: {$regex: regex, $options: "i"}})
-        else
-          regex = ".*#{value}.*"
-          Session.set("#{data.single}Find", {name: {$regex: regex, $options: "i"}})
-      else
-        Session.set("#{data.single}Find", {})
-        $(".#{data.single}-search").addClass("hide")
-        $(".#{data.single}-search-query").attr("value","")
-      return false
->>>>>>> 2d6d00a2da830ee8d011084d970a22eed0949f7f

     if data.nav?.events?
       _.extend(data.nav.events, navEvents)
@@ -448,7 +274,6 @@

     # Setup the add and purge buttons if the user
     # didn't pass in any.
-<<<<<<< HEAD
     if not data.addButton? and data.addButton isnt false
       data.addButton =
         title: "Add a #{singleTitle}"
@@ -466,22 +291,6 @@
         icon: "remove"

     return data
-=======
-    if not data.addButton?
-      data.addButton =
-        title: "Add a #{singleTitle}"
-        label: "Add a #{singleTitle}"
-        control_class: "add-#{single}"
-
-    if not data.purgeButton?
-      data.purgeButton =
-        title: "Purge Disabled #{pluralTitle}"
-        label: "Purge Disabled #{pluralTitle}"
-        control_class: "purge-disabled-#{plural}"
-
-    return 'List Navigation and Controls'
->>>>>>> 2d6d00a2da830ee8d011084d970a22eed0949f7f
-

 Template._listRow.helpers
   disabled: () ->
@@ -496,7 +305,6 @@

 # Helper for the column headers.  data should have the field
 # names in listFields.
-<<<<<<
   listFields = this.listFields
   if Array.isArray(listFields) and listFields.length > 0


Refactoring mail

Refactoring mail

--- mail.coffee
+++ mail.coffee
@@ -2,80 +2,99 @@
 #   Mail System
 #
 # Dependencies:
-#   "moment": "^2.6.0"
+#   "moment": "^2.8.3"
 #
 # Configuration:
 #   None
 #
 # Commands:
 #   hubot mail <recipient> <message> - Sends a <message> to <recipient> when found available
-#   hubot unmail [<recipient>] - Deletes all mail sent by you. Optionally, if <recipient> is specified, all mail sent to <recipient> by you will be deleted
+#   hubot unmail [<recipient>] - Deletes all mail sent by you. Optionally, if <recipient> is specified, only mail sent to <recipient> by you will be deleted
 #
 # Author:
 #   MrSaints

 moment = require 'moment'

+#
+# Config
+#
+MAIL_STORAGE_KEY = process.env.HUBOT_MAIL_KEY or "_mail"
+
 module.exports = (robot) ->
-    getMails = ->
-        robot.brain.data.mails or= {}
+    # Returns mail from the brain or an empty object if none is found
+    GetMail = ->
+        robot.brain.data[MAIL_STORAGE_KEY] or= {}

-    deliverMails = (context, recipient) ->
-        return unless robot.brain.data.mails?
+    # Delivers all mail belonging to a recipient in `ctx` via reply()
+    # Returns nothing
+    DeliverMail = (ctx) ->
+        recipient = ctx.message.user.name.toLowerCase()
+        if mails = GetMail()[recipient]
+            for mail in mails
+                ctx.reply "[From #{mail[0]}, #{moment.unix(mail[1]).fromNow()}] #{mail[2]}"
+            delete GetMail()[recipient]
+            ctx.robot.brain.save()

-        recipient = recipient.toLowerCase()
-
-        if mails = getMails()[recipient]
-            for mail in mails
-                context.send "#{robot.brain.userForName(recipient).name}: [From #{mail[0]}, #{moment.unix(mail[1]).fromNow()}] #{mail[2]}"
-            delete getMails()[recipient]
-
+    #
+    # Hubot commands
+    #
     robot.respond /unmail\s?(.*)/i, (msg) ->
         deleted = 0

-        deleteUsingRecipient = (recipient) ->
-            data = getMails()[recipient.toLowerCase()] or []
-            for mail, index in data by -1
-                if mail[0] is msg.message.user.name
-                    data.splice index, 1
-                    ++deleted
+        # TODO: Detect and delete empty nodes
+        DeleteByRecipient = (recipient) ->
+            if mails = GetMail()[recipient.toLowerCase()]
+                for mail, index in mails by -1
+                    if mail[0] is msg.message.user.name.toLowercase()
+                        mails.splice index, 1
+                        ++deleted

-        if msg.match[1]
-            deleteUsingRecipient msg.match[1]
+        # Delete using a specified recipient
+        if recipient = msg.match[1]
+            DeleteByRecipient recipient
             if deleted is 0
-                msg.reply "There are no outbound mail sent by you towards #{msg.match[1]}."
+                msg.reply "There are no outbound mail sent by you towards #{recipient}."
             else
-                msg.reply "#{deleted} of your mail(s) towards #{msg.match[1]} has been deleted."
+                msg.reply "#{deleted} of your mail(s) towards #{recipient} has been deleted."
+                robot.brain.save()
             return

-        for user, mails of getMails()
-            deleteUsingRecipient user
+        # Delete all from sender / command executor
+        for recipient, mails of GetMail()
+            DeleteByRecipient recipient

         if deleted is 0
             msg.reply "There are no outbound mail sent by you."
         else
             msg.reply "#{deleted} of your mail(s) has been deleted."
+            robot.brain.save()

     robot.respond /mail (\S+) (.+)/i, (msg) ->
-        [command, recipient, message] = msg.match
-        sender = msg.message.user.name
-
-        if recipient is robot.name
-            msg.reply "Thanks, but no thanks! I do not need any mail."
-            return
-
-        if recipient is sender
-            msg.reply "Are you sure you want to send a mail to yourself? Sad."
-            return
-
+        [_command, recipient, message] = msg.match
+        sender = msg.message.user.name.toLowerCase()
         recipient = recipient.toLowerCase()

-        getMails()[recipient] or= []
-        getMails()[recipient].push [sender, moment().unix(), message]
-        msg.reply "Your mail has been prepared for #{recipient}."
+        if sender is recipient
+            msg.reply "Are you sure you want to send a mail to yourself? Sad."
+            false
+        if recipient is robot.name.toLowerCase()
+            msg.reply "Thanks, but no thanks! I do not need any mail."
+            false

-    robot.enter (response) ->
-        deliverMails response, response.message.user.name
+        try
+            GetMail()[recipient] or= []
+            GetMail()[recipient].push [sender, moment().unix(), message]
+            robot.brain.save()
+            msg.reply "Your mail has been prepared for #{recipient}."
+        catch error
+            return robot.logger.error error
+
+    #
+    # Hubot events
+    #
+    robot.enter (msg) ->
+        DeliverMail msg

     robot.hear /./i, (msg) ->
-        deliverMails msg, msg.message.user.name+        DeliverMail msg


Added rank delta for leaderboard script.

--- tasks/shortStats.coffee
+++ tasks/shortStats.coffee
@@ -4,9 +4,10 @@
 Data = require('../server/api/data/data.model')
 Statistics = require('../server/api/statistics/statistics.model')
 mongoose.connect(config.mongo.uri, config.mongo.options)
+_ = require('lodash')

 # Should limit to 1800 elo and above on release.
-updateEloLeaderboards = ->
+updateEloLeaderboards = (previousTable)->
   eloArray = []
   processElo =
     do(eloArray=eloArray)->
@@ -25,10 +26,13 @@
     console.log 'error processing leaderboard'
   )
   stream.on('close',
-    do(eloArray=eloArray, Statistics=Statistics, startTime=startTime)->
+    do(eloArray=eloArray, Statistics=Statistics, startTime=startTime, previousTable=previousTable, _=_)->
       ->
         eloArray.sort((a, b)->
           return b.elo - a.elo
+        )
+        _.each(eloArray, (player, ind)->
+          player.delta = previousTable[player.playerName] - ind
         )
         doc = {
           name: 'eloleaderboard'
@@ -47,4 +51,12 @@
         )
   )

-updateEloLeaderboards()+
+Statistics.findOne({name: 'eloleaderboard'}, (err, doc)->
+  placementTable = {}
+  _.each(doc.data, (player, ind)->
+    placementTable[player.playerName] = ind
+  )
+  updateEloLeaderboards(placementTable)
+
+)


Accept connections IDENT'd as 'custard' as well as 'root'

--- code/server.coffee
+++ code/server.coffee
@@ -236,7 +236,7 @@
   if not re.test boxname
     return res.send 404,
       error: "Box name should match the regular expression #{String(re)}"
-  if req.ident != 'root'
+  if req.ident != 'root' && req.ident != 'custard'
     return res.send 403
       error: 'Only Custard running as Root can contact me'
   if not req.body.uid?


Put new Liverpool office IP address into list allowed to connect to dev server.

--- code/server.coffee
+++ code/server.coffee
@@ -146,6 +146,7 @@
     "88.211.55.91"
     "176.58.127.147"
     "23.23.37.109"
+    "151.237.236.138" # Liverpool office
     ]
 # Middleware that checks the IP address of the connecting
 # partner (which we expect to be custard).


Comment out the example tour

Comment out the example tour

--- shepherd.coffee
+++ shepherd.coffee
@@ -353,69 +353,69 @@

 window.Tour = Tour

-tour = new Tour
-  defaults:
-    classes: 'drop drop-open drop-theme-arrows'
-    scrollTo: true
-
-tour.addStep 'start',
-  title: "Welcome to KaPow!"
-  text: "KaPow is the ultimate comic book marketplace.  We supply over six
-  hundred tons of comic books to the waste management industry each year.  The
-  burning of these books supplies enough energy to power eight hundred American
-  homes."
-  tetherOptions:
-    attachment: 'middle center'
-    targetAttachment: 'middle center'
-
-tour.addStep
-  title: "Are you a seller, or a waste management professional?"
-  buttons: [
-    text: "Seller"
-    action: 'seller-start'
-  ,
-    text: "Waste Management Pro"
-    action: 'wmp-start'
-  ]
-
-tour.addStep 'seller-start',
-  text: [
-    "Selling is the ultimate way to unload your useless 'art'.",
-    "Begin by clicking the 'Start Selling' button."
-  ]
-  attachTo: 'a.small.button:first-of-type bottom'
-  advanceOn: 'click button.start-selling'
-  classes: 'tour-wide drop drop-open drop-theme-arrows'
-
-tour.addStep 'wmp-start',
-  text: [
-    "Thousands of pounds of priceless memories can be in your furnaces in
-    as little as three days!",
-    "Begin by entering your Waste Management Licence number below."
-  ]
-  attachTo:
-    element: 'input[placeholder="large-12.columns"]'
-    on: 'bottom'
-    classes: 'tour-highlight'
-    offset: '2px 0'
-  advanceOn: 'submit form.wmp-start'
-  when:
-    shown: ->
-    completed: ->
-    cancelled: ->
-    hidden: ->
-  buttons: [
-    text: "Back"
-    action: tour.back
-  ,
-    text: "Cancel"
-    action: tour.cancel
-  ,
-    text: "Skip"
-    action: tour.next
-  ]
-
-tour.start
-  hash: true
+# tour = new Tour
+#   defaults:
+#     classes: 'drop drop-open drop-theme-arrows'
+#     scrollTo: true
+
+# tour.addStep 'start',
+#   title: "Welcome to KaPow!"
+#   text: "KaPow is the ultimate comic book marketplace.  We supply over six
+#   hundred tons of comic books to the waste management industry each year.  The
+#   burning of these books supplies enough energy to power eight hundred American
+#   homes."
+#   tetherOptions:
+#     attachment: 'middle center'
+#     targetAttachment: 'middle center'
+
+# tour.addStep
+#   title: "Are you a seller, or a waste management professional?"
+#   buttons: [
+#     text: "Seller"
+#     action: 'seller-start'
+#   ,
+#     text: "Waste Management Pro"
+#     action: 'wmp-start'
+#   ]
+
+# tour.addStep 'seller-start',
+#   text: [
+#     "Selling is the ultimate way to unload your useless 'art'.",
+#     "Begin by clicking the 'Start Selling' button."
+#   ]
+#   attachTo: 'a.small.button:first-of-type bottom'
+#   advanceOn: 'click button.start-selling'
+#   classes: 'tour-wide drop drop-open drop-theme-arrows'
+
+# tour.addStep 'wmp-start',
+#   text: [
+#     "Thousands of pounds of priceless memories can be in your furnaces in
+#     as little as three days!",
+#     "Begin by entering your Waste Management Licence number below."
+#   ]
+#   attachTo:
+#     element: 'input[placeholder="large-12.columns"]'
+#     on: 'bottom'
+#     classes: 'tour-highlight'
+#     offset: '2px 0'
+#   advanceOn: 'submit form.wmp-start'
+#   when:
+#     shown: ->
+#     completed: ->
+#     cancelled: ->
+#     hidden: ->
+#   buttons: [
+#     text: "Back"
+#     action: tour.back
+#   ,
+#     text: "Cancel"
+#     action: tour.cancel
+#   ,
+#     text: "Skip"
+#     action: tour.next
+#   ]
+
+# tour.start
+#   hash: true

 #tour.completed 'seller-start'


Change room click area

Change room click area

--- frontend/app/scripts/view/list/RoomItem/RoomItem.coffee
+++ frontend/app/scripts/view/list/RoomItem/RoomItem.coffee
@@ -16,7 +16,7 @@
       '@ui.before': 'text: before'

     events:
-      'click @ui.name': 'onClickName'
+      'click': 'onClick'

     computeds:
       before:
@@ -25,7 +25,7 @@
           console.log is_mine
           if is_mine then '@' else '#'

-    onClickName: ->
+    onClick: ->
       @model.set {active: true}


When there is no section break, appended sub_fragment to wizard_view and then it append to response fields.

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -724,6 +724,7 @@
             if wizard_view && field_view && !field_view.is_section_break
               sub_frag.appendChild(field_view.render().el)
             if cnt == fieldViews.length && wizard_view
+              wizard_view.$el.append(sub_frag)
               @$responseFields.append wizard_view.$el
             cnt += 1


Added readonly attribute for fields whose state is readonly

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -342,7 +342,7 @@

           if !@is_section_break
-            @$el.addClass('readonly') if @model.get("field_options").state is "readonly"
+            $('.readonly').find('input, textarea, select').attr('readonly', 'readonly') if @model.get("field_options").state is "readonly"
             @$el.addClass('response-field-'+ @field_type + ' '+ @model.getCid())
               .data('cid', cid)
               .html(Formbuilder.templates["view/base#{base_templ_suff}"]({


Support Chrome for the ZoneSelection. Make the interface refresh a little bit more.

--- src/FileManager/ZoneSelectionDirective.coffee
+++ src/FileManager/ZoneSelectionDirective.coffee
@@ -42,7 +42,7 @@

       element.on('mousedown', ($event)->
-        if $event.buttons == 1 and not $event.target.className.match(/^file.*/)
+        if ($event.buttons == 1 or $event.button == 0) and not $event.target.className.match(/^file.*/)
           Selection.clear()
           body.appendChild(zone)
           scope.start = $event
@@ -50,7 +50,7 @@
       )
       $document.on('mousemove', ($event)->
         actual = new Date().getTime()
-        if scope.start? and scope.last+30<actual
+        if scope.start? and scope.last+20<actual
           scope.last = actual
           scope.end  = $event


atom-linter: update

atom-linter: update

Cherry-picked from laptop

--- .atom/config.cson
+++ .atom/config.cson
@@ -35,6 +35,7 @@
     useKeyboardLayout: "fr_FR"
   linter:
     lintOnFly: false
+    showErrorPanel: false
   "linter-flake8":
     executablePath: "flake8;/home/nathdwek/.local/bin/flake8"
   "linter-pep257": {}


test file name same as namespace

test file name same as namespace

--- test/tests/Generation/Generator.coffee
+++ test/tests/Generation/Generator.coffee
@@ -21,6 +21,15 @@
     .then ({generatedFiles, namespaces}) ->
       for file, contents of generatedFiles
         assert.match contents, "# file: MyApp", file
+
+  test "file name same as parent namespace", ->
+    generator = new Generator root = "root", pretend: true, quiet: true
+    generator.generateFromFiles [
+        "root/MyNamespace/my_namespace.coffee"
+      ]
+    .then ({generatedFiles, namespaces}) ->
+      log ((v for k, v of generatedFiles).join "\n\n")
+      assert.match generatedFiles["root/MyNamespace/index.coffee"], /includeInNamespace.*my_namespace/

   test "special file names", ->
     generator = new Generator "root", pretend: true, quiet: true


JLog::log: provide default callback

JLog::log: provide default callback

--- workers/social/lib/social/models/log/index.coffee
+++ workers/social/lib/social/models/log/index.coffee
@@ -41,7 +41,7 @@
         type        : String
         required    : no

-  @log = (data, callback)->
+  @log = (data, callback = ->)->
     log = new JLog data
     log.save (err) -> callback err


Add tests for exit codes when called with -v and --version

--- test/cli/version.coffee
+++ test/cli/version.coffee
@@ -9,7 +9,17 @@
 				stdout.should.include 'v' + packageVersion
 				done()

+		it 'should exit with an exit code of 0 when called with -v', (done) ->
+			exec ['-v'], (error, stdout, stderr) ->
+				should.not.exist error
+				done()
+
 		it 'should output brave-mouse’s version when called with --version', (done) ->
 			exec ['--version'], (error, stdout, stderr) ->
 				stdout.should.include 'v' + packageVersion
 				done()
+
+		it 'should exit with an exit code of 0 when called with --version', (done) ->
+			exec ['--version'], (error, stdout, stderr) ->
+				should.not.exist error
+				done()


fixing issue where opening feed would be halfway down page

--- app/assets/coffee/apps/episodes/show/show_view.coffee
+++ app/assets/coffee/apps/episodes/show/show_view.coffee
@@ -80,8 +80,8 @@
         "click td.js-view-detail": "showClicked"
         "click a.js-preview-audio": "previewAudio"

-      onBeforeRender: ->
-
+      onRender: ->
+        window.scrollTo(0,0)
       initialize: ->

       templateHelpers:


minor refactor of render method

minor refactor of render method

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -147,50 +147,43 @@
   render: ->
     return null if @state.subjects is null or @state.subjects.length is 0
     viewBox = [0, 0, @state.imageWidth, @state.imageHeight]
+
     if @state.loading
-

-

-

-            <LoadingIndicator/>
-

-
{@state.subjects.location}

-

-            <ActionButton loading={@state.loading} />
-

+      markingSurfaceContent = <LoadingIndicator />
+    else
+      markingSurfaceContent =
+        <svg
+          className = "subject-viewer-svg"
+          width = {@state.imageWidth}
+          height = {@state.imageHeight}
+          viewBox = {viewBox}
+          data-tool = {@props.selectedDrawingTool?.type} >
+          <rect
+            ref = "sizeRect"
+            width = {@state.imageWidth}
+            height = {@state.imageHeight} />
+          <Draggable
+            onStart = {@handleInitStart}
+            onDrag  = {@handleInitDrag}
+            onEnd   = {@handleInitRelease} >
+            <SVGImage
+              src = {@state.subject.location}
+              width = {@state.imageWidth}
+              height = {@state.imageHeight} />
+          </Draggable>
+        </svg>
+
+

+

+

+          {markingSurfaceContent}
+

+
{@state.subjects.location}

+

+          <ActionButton loading={@state.loading} />

-    else
-      action_button =  <ActionButton label={"NEXT PAGE"} onActionSubmit={@nextSubject} />
-

-

-

-            <svg
-              className = "subject-viewer-svg"
-              width = {@state.imageWidth}
-              height = {@state.imageHeight}
-              viewBox = {viewBox}
-              data-tool = {@props.selectedDrawingTool?.type} >
-              <rect
-                ref = "sizeRect"
-                width = {@state.imageWidth}
-                height = {@state.imageHeight} />
-              <Draggable
-                onStart = {@handleInitStart}
-                onDrag  = {@handleInitDrag}
-                onEnd   = {@handleInitRelease} >
-                <SVGImage
-                  src = {@state.subject.location}
-                  width = {@state.imageWidth}
-                  height = {@state.imageHeight} />
-              </Draggable>
-            </svg>
-

-
{@state.subject.location}

-

-            {action_button}
-

-

-

+

 module.exports = SubjectViewer
 window.React = React


Slideshow precache forward and backward.

--- app/assets/javascripts/controllers/slides_ctrl.coffee
+++ app/assets/javascripts/controllers/slides_ctrl.coffee
@@ -27,8 +27,9 @@
   updateCache: =>
     return unless @items?

-    i = @params.i
-    offset = @getOffset()
-    for j in _.range(i, Math.min(i + CACHE_AHEAD, LIMIT))
-      if (upload = @items[j])?
+    i = @params.i - @getOffset()
+    for j in _.range(0, CACHE_AHEAD)
+      if (upload = @items[i + j])?
         @imageCache.store(upload.large_url)
+      if (upload = @items[i - j])?
+        @imageCache.store(upload.large_url)


set host back to localhost (inadvertant check-in)

--- configs/webpack/base.coffee
+++ configs/webpack/base.coffee
@@ -167,7 +167,7 @@
   mergeWebpackConfigs(productionBase, {postLoaders})

 makeDevelopmentBase = (projectConfig) ->
-  host = process.env.OX_PROJECT_HOST or projectConfig.host or 'tutor.dev'
+  host = process.env.OX_PROJECT_HOST or projectConfig.host or 'localhost'
   servePath = "http://#{host}:#{projectConfig.devPort}"
   publicPath = "#{servePath}/dist/"
   outputPath = "#{projectConfig.basePath}/"


Use npmlog for output

Use npmlog for output

--- src/enfield.coffee
+++ src/enfield.coffee
@@ -64,7 +64,7 @@

         conf.get options, (err, config) ->
           if err
-            console.error "Could not get configuration: #{err.message}"
+            log.error "enfield", "Could not get configuration: #{err.message}"
             process.exit -1

           printConfiguration config
@@ -76,12 +76,12 @@
       when 'help'
         exports.help()
       else
-        console.error "Invalid command. Use --help for more information"
+        log.error "enfield", "Invalid command. Use --help for more information"
         process.exit -1

   new: (dir) ->
     unless dir
-      console.error "Must specify a path"
+      log.error "enfield", "Must specify a path"
       process.exit -1

     resolved = path.resolve dir
@@ -89,7 +89,7 @@
     # Throw error if exists and not empty
     if fs.existsSync(resolved)
       if fs.readdirSync(resolved).length
-        console.error "Confict: #{resolved} exists and is not empty"
+        log.error "enfield", "Confict: #{resolved} exists and is not empty"
         process.exit -1
       else
         # Remove it so we can bulk copy
@@ -98,9 +98,9 @@
     # TODO: Copy site_template over
     fs.copy path.join(__dirname, '../site_template'), resolved, (err) ->
       if err
-        console.error "Could not create new site: #{err.message}"
+        log.error "enfield", "Could not create new site: #{err.message}"
       else
-        console.log "New Enfield site installed in #{resolved}"
+        log.info "enfield", "New site installed in #{resolved}"

   build: (config, callback = ->) ->
     generate config, callback
@@ -109,15 +109,15 @@
     # Watching happens within the build command
     exports.build config, (err) ->
       if err
-        console.error "Could not generate site: #{err.message}"
+        log.error "enfield", Could not generate site: #{err.message}"
         process.exit -1

       fileServer = new(node_static.Server) config.destination
       server = require('http').createServer (request, response) ->
-        console.info "[#{timestamp()}] #{request.method} #{request.url}"
+        log.http "server", "[#{timestamp()}] #{request.method} #{request.url}"
         fileServer.serve request, response

-      console.log "Running server at http://#{config.host}:#{config.port}"
+      log.info "enfield", "Running server at http://#{config.host}:#{config.port}"
       server.listen config.port, config.host

   version: ->


Added shipmentState to order model

Added shipmentState to order model

--- app/assets/javascripts/sprangular/models/order.coffee
+++ app/assets/javascripts/sprangular/models/order.coffee
@@ -19,11 +19,13 @@
     @total = 0
     @errors = null
     @state = null
+    @shipmentState = null

   load: (data) ->
     @clear()
     @number = data.number
     @state = data.state
+    @shipmentState = data.shipment_state
     @itemTotal = Number(data.item_total)
     @taxTotal = Number(data.tax_total)
     @shipTotal = Number(data.ship_total)


fix 'da' locale file

fix 'da' locale file

--- app/assets/javascripts/uploadcare/locale/da.js.coffee
+++ app/assets/javascripts/uploadcare/locale/da.js.coffee
@@ -1,6 +1,6 @@
 # Note: English locale is the default and used as a fallback.
 uploadcare.namespace 'uploadcare.locale.translations', (ns) ->
-  ns.en =
+  ns.da =
     uploading: 'Uploader... Vent venligst.'
     loadingInfo: 'Henter information...'
     errors:
@@ -105,6 +105,6 @@
 # Pluralization rules taken from:
 # http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
 uploadcare.namespace 'uploadcare.locale.pluralize', (ns) ->
-  ns.en = (n) ->
+  ns.da = (n) ->
     return 'one' if n == 1
     'other'


Added menu items

Added menu items

--- menus/node-debugger.cson
+++ menus/node-debugger.cson
@@ -0,0 +1,34 @@
+'menu': [
+  {
+    'label': 'Packages'
+    'submenu': [
+      'label': 'Node Debugger'
+      'submenu': [
+        {
+          'label': 'Start/Resume'
+          'command': 'node-debugger:start-resume'
+        },
+        {
+          'label': 'Stop'
+          'command': 'node-debugger:stop'
+        },
+        {
+          'label': 'Step In'
+          'command': 'node-debugger:step-in'
+        },
+        {
+          'label': 'Stop Out'
+          'command': 'node-debugger:step-out'
+        },
+        {
+          'label': 'Step Next'
+          'command': 'node-debugger:step-next'
+        },
+        {
+          'label': 'Toggle Breakpoint'
+          'command': 'node-debugger:toggle-breakpoint'
+        }
+      ]
+    ]
+  }
+]


improved implementation of fading text for long proposal description text

--- @client/collapsed_proposal.coffee
+++ @client/collapsed_proposal.coffee
@@ -332,29 +332,38 @@
             parent.removeChild div

-
-            DIV
-              className: 'description_on_homepage'
+            DIV
               style:
-                maxHeight: max_desc_height
-
-              onClick: (e) =>
-                if e.target.tagName not in ["A", "BUTTON"]
-                  loadPage proposal_url(proposal, just_you && current_user.logged_in)
-                  window.scrollTo(0, 0)
-
-              dangerouslySetInnerHTML: __html: desc
+                position: 'relative'
+
+              DIV
+                className: 'description_on_homepage'
+                style:
+                  maxHeight: max_desc_height
+
+                onClick: (e) =>
+                  if e.target.tagName not in ["A", "BUTTON"]
+                    loadPage proposal_url(proposal, just_you && current_user.logged_in)
+                    window.scrollTo(0, 0)
+
+                dangerouslySetInnerHTML: __html: desc
+
+              if exceeds_height
+                DIV
+                  style:
+                    background: 'linear-gradient(0deg, rgba(255,255,255,1) 0%, rgba(255,255,255,1) 34%, rgba(255,255,255,0) 100%)'
+                    bottom: 0
+                    height: 18
+                    width: '100%'
+                    position: 'absolute'
+                    pointerEvents: 'none'
+

           DIV
             className: 'metadata monospaced'
-            style: if showing_description
-                    background: 'linear-gradient(0deg, rgba(255,255,255,1) 0%, rgba(255,255,255,1) 60%, rgba(255,255,255,0) 100%)'
-                    marginTop: if exceeds_height then -26 else -12
-                    paddingTop: if exceeds_height then 36 else 12
-                    position: 'relative'

             if customization('proposal_meta_data', null, subdomain)?


Prettify adopt messages

Prettify adopt messages

--- scripts/adopt.coffee
+++ scripts/adopt.coffee
@@ -12,6 +12,8 @@
 #   Zach Whaley (zachwhaley) <zachbwhaley@gmail.com>

 fs = require 'fs'
+capitalize = require 'capitalize'
+
 git = require './git'

 adopt = (repo, greyhound, callback) ->
@@ -27,7 +29,7 @@
     data = data.replace(
       ///^(#{greyhound}:[\s\S]+?)available:\s(yes|no)///m, (match, p1, p2) ->
         if p2 is 'no'
-          err = "#{greyhound} has already been adopted 😝"
+          err = "#{capitalize(greyhound)} has already been adopted 😝"
           return
         "#{p1}available: no"
     )
@@ -39,17 +41,18 @@
 module.exports = (robot) ->
   robot.respond /adopt (.*)/i, (res) ->
     greyhound = res.match[1]
-    message = "#{greyhound} Adopted!"
+    message = "#{capitalize(greyhound)} Adopted! 💗"
     branch = "adopt-#{greyhound}"
     user =
       name: res.message.user.real_name,
       email: res.message.user.profile.email

-    res.reply "Moving #{greyhound} to Happy Tails! 💗\nHang on a sec..."
+    res.reply "Moving #{capitalize(greyhound)} to Happy Tails! 💗\n" +
+              "Hang on a sec..."
     git.pull (repo) ->
-      adopt repo, greyhound, (err) ->
-        return res.reply err if err
-        git.branch repo, branch, (ref) ->
+      git.branch repo, branch, (ref) ->
+        adopt repo, greyhound, (err) ->
+          return res.reply err if err
           git.commit repo, user, message, (oid) ->
             git.push repo, ref, ->
               git.pullrequest message, branch, (pr) ->


arxiv: error testing on heroku

arxiv: error testing on heroku

--- scripts/arxiv.coffee
+++ scripts/arxiv.coffee
@@ -9,6 +9,7 @@
         url = 'http://export.arxiv.org/rss/astro-ph'
         parser url, (err,rss)->
             text = []
+            text.push err
             text.push "Today's astro-ph postings for: " + keyWords.join(', ')
             text.push ""
             gotNone = true


Adding more snippets

Adding more snippets

--- snippets/javascriptevents.cson
+++ snippets/javascriptevents.cson
@@ -1,73 +1,73 @@
 '.source.feature':
   'javascriptevents1':
-    'prefix': '"mouse<something>" on the hidden element found by "identifier"'
-    'body': '"mousedown" on the hidden element found by "$1"'
+    'prefix': 'mouse<something> on the hidden element found by "identifier"'
+    'body': '"$1" on the hidden element found by "$2"'
   'javascriptevents2':
-    'prefix': '"mouse<something>" on the hidden element found by "identifier" if it exists'
-    'body': '"mouse<something>" on the hidden element found by "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element found by "identifier" if it exists'
+    'body': '"$1" on the hidden element found by "$2" if it exists'
   'javascriptevents3':
-    'prefix': '"mouse<something>" on the hidden element found by alias "alias name"'
-    'body': '"mouse<something>" on the hidden element found by alias "$1"'
+    'prefix': 'mouse<something> on the hidden element found by alias "alias name"'
+    'body': '"$1" on the hidden element found by alias "$2"'
   'javascriptevents4':
-    'prefix': '"mouse<something>" on the hidden element found by alias "alias name" if it exists'
-    'body': '"mouse<something>" on the hidden element found by alias "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element found by alias "alias name" if it exists'
+    'body': '"$1" on the hidden element found by alias "$2" if it exists'
   'javascriptevents10':
-    'prefix': '"mouse<something>" on the hidden element with the ID of "identifier"'
-    'body': '"mouse<something>" on the hidden element with the ID of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the ID of "identifier"'
+    'body': '"$1" on the hidden element with the ID of "$2"'
   'javascriptevents11':
-    'prefix': '"mouse<something>" on the hidden element with the class of "identifier"'
-    'body': '"mouse<something>" on the hidden element with the class of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the class of "identifier"'
+    'body': '"$1" on the hidden element with the class of "$2"'
   'javascriptevents12':
-    'prefix': '"mouse<something>" on the hidden element with the xpath of "identifier"'
-    'body': '"mouse<something>" on the hidden element with the xpath of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the xpath of "identifier"'
+    'body': '"$1" on the hidden element with the xpath of "$2"'
   'javascriptevents13':
-    'prefix': '"mouse<something>" on the hidden element with the name of "identifier"'
-    'body': '"mouse<something>" on the hidden element with the name of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the name of "identifier"'
+    'body': '"$1" on the hidden element with the name of "$2"'
   'javascriptevents14':
-    'prefix': '"mouse<something>" on the hidden element with the css selector of "identifier"'
-    'body': '"mouse<something>" on the hidden element with the css selector of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the css selector of "identifier"'
+    'body': '"$1" on the hidden element with the css selector of "$2"'
   'javascriptevents20':
-    'prefix': '"mouse<something>" on the hidden element with the ID of "identifier" if it exists'
-    'body': '"mouse<something>" on the hidden element with the ID of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the ID of "identifier" if it exists'
+    'body': '"$1" on the hidden element with the ID of "$2" if it exists'
   'javascriptevents21':
-    'prefix': '"mouse<something>" on the hidden element with the class of "identifier" if it exists'
-    'body': '"mouse<something>" on the hidden element with the class of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the class of "identifier" if it exists'
+    'body': '"$1" on the hidden element with the class of "$2" if it exists'
   'javascriptevents22':
-    'prefix': '"mouse<something>" on the hidden element with the xpath of "identifier" if it exists'
-    'body': '"mouse<something>" on the hidden element with the xpath of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the xpath of "identifier" if it exists'
+    'body': '"$1" on the hidden element with the xpath of "$2" if it exists'
   'javascriptevents23':
-    'prefix': '"mouse<something>" on the hidden element with the name of "identifier" if it exists'
-    'body': '"mouse<something>" on the hidden element with the name of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the name of "identifier" if it exists'
+    'body': '"$1" on the hidden element with the name of "$2" if it exists'
   'javascriptevents24':
-    'prefix': '"mouse<something>" on the hidden element with the css selector of "identifier" if it exists'
-    'body': '"mouse<something>" on the hidden element with the css selector of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the css selector of "identifier" if it exists'
+    'body': '"$1" on the hidden element with the css selector of "$2" if it exists'
   'javascriptevents30':
-    'prefix': '"mouse<something>" on the hidden element with the ID alias of "identifier alias"'
-    'body': '"mouse<something>" on the hidden element with the ID alias of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the ID alias of "identifier alias"'
+    'body': '"$1" on the hidden element with the ID alias of "$2"'
   'javascriptevents31':
-    'prefix': '"mouse<something>" on the hidden element with the class alias of "identifier alias"'
-    'body': '"mouse<something>" on the hidden element with the class alias of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the class alias of "identifier alias"'
+    'body': '"$1" on the hidden element with the class alias of "$2"'
   'javascriptevents32':
-    'prefix': '"mouse<something>" on the hidden element with the xpath alias of "identifier alias"'
-    'body': '"mouse<something>" on the hidden element with the xpath alias of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the xpath alias of "identifier alias"'
+    'body': '"$1" on the hidden element with the xpath alias of "$2"'
   'javascriptevents33':
-    'prefix': '"mouse<something>" on the hidden element with the name alias of "identifier alias"'
-    'body': '"mouse<something>" on the hidden element with the name alias of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the name alias of "identifier alias"'
+    'body': '"$1" on the hidden element with the name alias of "$2"'
   'javascriptevents34':
-    'prefix': '"mouse<something>" on the hidden element with the css selector alias of "identifier alias"'
-    'body': '"mouse<something>" on the hidden element with the css selector alias of "$1"'
+    'prefix': 'mouse<something> on the hidden element with the css selector alias of "identifier alias"'
+    'body': '"$1" on the hidden element with the css selector alias of "$2"'
   'javascriptevents30':
-    'prefix': '"mouse<something>" on the hidden element with the ID alias of "identifier alias" if it exists'
-    'body': '"mouse<something>" on the hidden element with the ID alias of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the ID alias of "identifier alias" if it exists'
+    'body': '"$1" on the hidden element with the ID alias of "$2" if it exists'
   'javascriptevents31':
-    'prefix': '"mouse<something>" on the hidden element with the class alias of "identifier alias" if it exists'
-    'body': '"mouse<something>" on the hidden element with the class alias of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the class alias of "identifier alias" if it exists'
+    'body': '"$1" on the hidden element with the class alias of "$2" if it exists'
   'javascriptevents32':
-    'prefix': '"mouse<something>" on the hidden element with the xpath alias of "identifier alias" if it exists'
-    'body': '"mouse<something>" on the hidden element with the xpath alias of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the xpath alias of "identifier alias" if it exists'
+    'body': '"$1" on the hidden element with the xpath alias of "$2" if it exists'
   'javascriptevents33':
-    'prefix': '"mouse<something>" on the hidden element with the name alias of "identifier alias" if it exists'
-    'body': '"mouse<something>" on the hidden element with the name alias of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the name alias of "identifier alias" if it exists'
+    'body': '"$1" on the hidden element with the name alias of "$2" if it exists'
   'javascriptevents34':
-    'prefix': '"mouse<something>" on the hidden element with the css selector alias of "identifier alias" if it exists'
-    'body': '"mouse<something>" on the hidden element with the css selector alias of "$1" if it exists'
+    'prefix': 'mouse<something> on the hidden element with the css selector alias of "identifier alias" if it exists'
+    'body': '"$1" on the hidden element with the css selector alias of "$2" if it exists'


Cosmetic

Cosmetic

--- snippets/language-latex2e.cson
+++ snippets/language-latex2e.cson
@@ -21,9 +21,8 @@
     'description': 'Begin amsmath align env`ironment'
     'descriptionMoreURL': 'https://www.sharelatex.com/learn/Aligning_equations_with_amsmath#!#Aligning_several_equations'

-
+# These snippets are available in mathmode
 '.text.tex.latex .string.other.math':
-  # These snippets are available in mathmode
   'fraction':
     'prefix': '\\frac'
     'body': '\\\\frac{$1}{$2}$0'


minor update to learning

minor update to learning

--- scripts/learn.coffee
+++ scripts/learn.coffee
@@ -43,7 +43,7 @@
   getAmbiguousUserText = (users) ->
     "Be more specific, I know #{users.length} people named like that: #{(user.name for user in users).join(", ")}"

-  robot.respond /(who|what) (is|are) @?(\w+)$/i, (msg) ->
+  robot.respond /(who|what) (is|are) @?(\w+)\??$/i, (msg) ->
     action = msg.match[2].trim()
     word = msg.match[3].trim()
     key = inflection.singularize(word)
@@ -86,7 +86,7 @@
       else
         msg.send lookup.user

-  robot.respond /@?(\w+) (is|are) (.*)\??/i, (msg) ->
+  robot.respond /@?(\w+) (is|are) (.*)/i, (msg) ->
     word = msg.match[1].trim()
     key = inflection.singularize(word)
     action = msg.match[2].trim()
@@ -144,7 +144,7 @@
     else
       key = msg.random Object.keys memory.words
       lookup = msg.random memory.words[key]
-
+
       memory.lastRecall = lookup
       robot.brain.set('learned', memory)


Diminui escopo do sku-selector-product (chama funções Fire)

--- src/coffee/portal-sku-selector-product.coffee
+++ src/coffee/portal-sku-selector-product.coffee
@@ -1,13 +1,7 @@
-$(document).on "dimensionChanged", ->
-	$(".notifyme").hide()
-	$(".buy-button").hide()
+$(document).ready hideBuyInfo
+$(document).on "dimensionChanged", hideBuyInfo

 $(document).on "skuSelected", (evt, sku) ->
-	console.log "Foi selecionado um sku que está " + (if sku.available then "" else "un") + "available: ", sku
-	if sku.available
-		$(".buy-button").show()
-		$(".skuBestPrice").text "R$ " + $.formatCurrency(sku.bestPrice)
-		# etc...
-	else
-		$(".notifyme").show()
-		# etc...+	window.FireSkuChangeImage?(sku.sku)
+	#window.FireSkuDataReceived?(sku.sku)
+	window.FireSkuSelectionChanged?(sku.sku)


Dont show empty categories in filter view

--- app/assets/javascripts/categories.coffee
+++ app/assets/javascripts/categories.coffee
@@ -29,6 +29,7 @@
     input '#tag-all', name: "tags", type: "radio", hidden: true, checked: true, onchange: "tagSelected(this)"

     for tag in tags
+        continue if tag.bookCount == 0
         input '#tag-'+tag.name, name: "tags", type: "radio", hidden: true, onchange: "tagSelected(this)"
         label '.btn.btn-sm.badge', name: "label-tag", for: "tag-"+tag.name, 'data-badge': tag.bookCount, style: "margin-left: 1em;", ->
             text tag.name


Animate filling transition

Animate filling transition

For the circular progress bar I have modified the code in a way, that
the filling transition is now animated.

Signed-off-by: Konrad Reiche <4ff0213f56e2082fcd8c882d04c546881c579ce4@gmail.com>

--- app/assets/javascripts/metrics.js.coffee
+++ app/assets/javascripts/metrics.js.coffee
@@ -25,8 +25,8 @@
   foreground = meter.append("path")
     .attr("class", "foreground")

-  progress = 0.5
-
-  d3.transition().tween("progress", () ->
-    (t) ->
-      foreground.transition().duration(5000).attr("d", arc.endAngle(twoPi * progress)))
+  i = d3.interpolate(0.0, 0.5)
+  foreground.transition().tween("progress", () ->
+    return (t) ->
+      progress = i(t)
+      foreground.attr("d", arc.endAngle(twoPi * progress))).duration(1000)


Fix spelling issue.

Fix spelling issue.

--- app/assets/javascripts/prediction.coffee
+++ app/assets/javascripts/prediction.coffee
@@ -2,7 +2,7 @@
   constructor: (@$button) ->
     @$form = @$button.parent().parent()
     @$is_final = @$form.find('.hidden_is_final')
-    @register_listners()
+    @register_listeners()
     if @marked_final()
       @disable_form()
     else
@@ -10,11 +10,7 @@

     @disable_submit_button()

-  register_listners: ->
-    @$button.click (e) =>
-      if @question_answered
-        @record_answer()
-        @disable_form()
+  register_listeners: ->
     $(document).on "answer_for", (e,opts) =>
       if opts.source == @$form.attr('id')
         @enable_submit_button()


foreach

foreach

--- app/assets/javascripts/prototypes.coffee
+++ app/assets/javascripts/prototypes.coffee
@@ -57,4 +57,16 @@
   this.addEventListener(event, newCB)

 if (!Element::matches)
-  Element::matches = Element::msMatchesSelector+  Element::matches = Element::msMatchesSelector
+
+# Nodelist
+forEachMethod = (callback, thisArg) ->
+  thisArg = thisArg || window
+  for i in [0..this.length - 1]
+    callback.call thisArg, this[i], i, this
+
+if window.NodeList and !NodeList::forEach
+  NodeList::forEach = forEachMethod
+
+if window.HTMLCollection and !HTMLCollection::forEach
+  HTMLCollection::forEach = forEachMethod


fix tooltip in work summary for weekly rows

--- app/assets/javascripts/summary.js.coffee
+++ app/assets/javascripts/summary.js.coffee
@@ -1,5 +1,5 @@
 $ ->
-  $('.has-tip').hover (e) ->
+  $(document).on 'mouseover', '.content-summary .has-tip', ->
     $(this).popover
       trigger: 'hover'
       content: $(this).data('tooltip')


added comment to highchart stock js

added comment to highchart stock js

--- app/assets/javascripts/welcome.js.coffee
+++ app/assets/javascripts/welcome.js.coffee
@@ -82,6 +82,8 @@
 ])

+# HighChart Stocks
+
 $ ->
   $.getJSON "http://www.highcharts.com/samples/data/jsonp.php?filename=aapl-c.json&callback=?", (data) ->


fix history xss

fix history xss

--- app/coffee/modules/common/history.coffee
+++ app/coffee/modules/common/history.coffee
@@ -84,11 +84,11 @@

                  from

-                <%= point[0] %>
+                <%- point[0] %>

                  to

-                <%= point[1] %>
+                <%- point[1] %>

@@ -103,11 +103,11 @@

                  from

-                <%= from %>
+                <%- from %>

                  to

-                <%= to %>
+                <%- to %>

@@ -121,11 +121,11 @@
             <% _.each(diff, function(change) { %>

                     <%= change.name %> from

-                    <%= change.from %>
+                    <%- change.from %>

                     <%= change.name %> to

-                    <%= change.to %>
+                    <%- change.to %>

             <% }) %>


Refactor max/min buttons for older jQueries.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -205,11 +205,25 @@
         $("<button />")
           .addClass('minimize')
           .text("Minimize Anchor Tab")
+          .click(->
+              $("#anchor-tab").removeClass("visible").addClass("minimized")
+              setStateCookie 'minimized'
+
+              gaqInfo = "Domain: " + document.domain
+              _gaq.push ["at._trackEvent", "Tab Visibility", "Minimize Tab", gaqInfo]
+            )
       )
       .append(
         $("<button />")
           .addClass("maximize")
           .text("Maximize Anchor Tab")
+          .click(->
+              $("#anchor-tab").removeClass("minimized").addClass("visible")
+              setStateCookie 'visible'
+
+              gaqInfo = "Domain: " + document.domain
+              _gaq.push ["at._trackEvent", "Tab Visibility", "Maximize Tab", gaqInfo]
+            )
       )
       .append(
         $("<p />")
@@ -233,22 +247,6 @@
     else
       anchorTab.addClass "visible"
   , displayDelay
-
-  $("#anchor-tab")
-    .on('click', '.minimize', ->
-      $("#anchor-tab").removeClass("visible").addClass("minimized")
-      setStateCookie 'minimized'
-
-      gaqInfo = "Domain: " + document.domain
-      _gaq.push ["at._trackEvent", "Tab Visibility", "Minimize Tab", gaqInfo]
-    )
-    .on('click', '.maximize', ->
-      $("#anchor-tab").removeClass("minimized").addClass("visible")
-      setStateCookie 'visible'
-
-      gaqInfo = "Domain: " + document.domain
-      _gaq.push ["at._trackEvent", "Tab Visibility", "Maximize Tab", gaqInfo]
-    )

 loadAnchorTab = ->
   # Load the anchor tab. At this point we can assume that jQuery exists and that we're able to use it


Fixing a typo...

Fixing a typo...

--- app/views/home/home.coffee
+++ app/views/home/home.coffee
@@ -54,7 +54,7 @@
       .then (info) ->
         TranzitRecipient.find(info.name, yes)
       .then (recv) ->
-        recipient = _.map(Object.keys(recv), (i) -> recv[i])[0]s
+        recipient = _.map(Object.keys(recv), (i) -> recv[i])[0]
         pkg =
           tracking: $scope.scanner.value,
           recipient: recipient.id


Changed subscription frequency to prevent multiple score registrations

--- app/views/home_view.coffee
+++ app/views/home_view.coffee
@@ -147,13 +147,11 @@
           god.spells = @filterProgrammableComponents level.attributes.thangs, @generateSpellToSourceMap data.sessions
           god.createWorld()
           @god = god
-          Backbone.Mediator.subscribe 'god:new-world-created', @onWorldCreated, @
+          Backbone.Mediator.subscribeOnce 'god:new-world-created', @onWorldCreated, @

   onWorldCreated: (data) ->
     return if @alreadyPostedResults
     taskResults = @translateGoalStatesIntoTaskResults data.goalStates
-    console.log "Task Results"
-    console.log taskResults
     @god?.destroy()

     $.ajax


Make modals not hide when clicking on greyed out background

--- app/assets/javascripts/bootstrap-custom.js.coffee
+++ app/assets/javascripts/bootstrap-custom.js.coffee
@@ -2,3 +2,7 @@
   $(".topbar-wrapper").dropdown()
 $ ->
   $(".alert-message").alert()
+$ ->
+  $(".modal").modal
+    show: false
+    backdrop: "static"


removed unnecessary @fetchSubjectSets() call in mark/index

--- app/assets/javascripts/components/mark/index.cjsx
+++ app/assets/javascripts/components/mark/index.cjsx
@@ -116,7 +116,6 @@
     console.log "if new_subject_set_index..", @state.subjectSets.length
     if new_subject_set_index >= @state.subjectSets.length
       console.warn "NO MORE SUBJECT SETS"
-      @fetchSubjectSets @getActiveWorkflow().id, @getActiveWorkflow().subject_fetch_limit
       return

     console.log "Mark#index Advancing to subject_set_index #{new_subject_set_index} (of #{@state.subjectSets.length}), subject_index #{new_subject_index} (of #{@state.subjectSets[new_subject_set_index].subjects.length})"


Fix IssuableContext javascript

Fix IssuableContext javascript

We were calling `affix` before the events were bound so they never
triggered correctly.

Closes #2597

--- app/assets/javascripts/issuable_context.js.coffee
+++ app/assets/javascripts/issuable_context.js.coffee
@@ -11,12 +11,13 @@
       $(this).submit()

     $('.issuable-details').waitForImages ->
+      $('.issuable-affix').on 'affix.bs.affix', ->
+        $(@).width($(@).outerWidth())
+      .on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
+        $(@).width('')
+
       $('.issuable-affix').affix offset:
         top: ->
           @top = ($('.issuable-affix').offset().top - 70)
         bottom: ->
           @bottom = $('.footer').outerHeight(true)
-      $('.issuable-affix').on 'affix.bs.affix', ->
-        $(@).width($(@).outerWidth())
-      .on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
-        $(@).width('')


Fix global save state[#88907694]

Fix global save state[#88907694]

 - IframePhoneManager.getPhone needs iframe DOM object as param and not jquery object.

--- app/assets/javascripts/global-iframe-saver.coffee
+++ app/assets/javascripts/global-iframe-saver.coffee
@@ -17,7 +17,7 @@
     @_iframePhones = []

   addNewInteractive: (iframeEl) ->
-    phone = IframePhoneManager.getPhone iframeEl
+    phone = IframePhoneManager.getPhone $(iframeEl)[0]
     @_iframePhones.push phone
     @_setupPhoneListeners phone
     if @_globalState


Hide the hover menu when mouse leaves graph.

Closes #41

--- app/assets/javascripts/prosperity/graph.js.coffee
+++ app/assets/javascripts/prosperity/graph.js.coffee
@@ -20,6 +20,7 @@
       labels: []
       smooth: false
       data: @data
+      hideHover: true

     @chartOptions.postUnits = '%' if options.key == 'change'


rename previewBase to previewUrlProvider

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -37,7 +37,7 @@
     cdnBase: 'https://ucarecdn.com'
     urlBase: 'https://upload.uploadcare.com'
     socialBase: 'https://social.uploadcare.com'
-    previewBase: null
+    previewUrlProvider: null
     overridePreviewUrl: null
     # fine tuning
     imagePreviewMaxSize: 25 * 1024 * 1024
@@ -204,11 +204,11 @@
     if settings.validators
       settings.validators = settings.validators.slice()

-    if settings.previewBase and not settings.overridePreviewUrl
+    if settings.previewUrlProvider and not settings.overridePreviewUrl
       settings.overridePreviewUrl = (url, info) =>
-        useGetParam = /\?$/.test(settings.previewBase)
+        useGetParam = /\?$/.test(settings.previewUrlProvider)
         location = if useGetParam then "url=#{encodeURIComponent(url)}" else "/#{url}"
-        utils.normalizeUrl(settings.previewBase) + location
+        utils.normalizeUrl(settings.previewUrlProvider) + location

     if settings.overridePreviewUrl
       if typeof settings.overridePreviewUrl is 'string'


sidebar: remove more link for vms, put it to header

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -529,8 +529,7 @@
       tagName  : 'h3'
       cssClass : 'sidebar-title'
       partial  : 'VMs'
-      click    : KD.singletons.computeController
-        .bound 'handleNewMachineRequest'
+      click    : @bound 'handleMoreVMsClick'

     section.addSubView @machineTree.getView()

@@ -545,9 +544,9 @@
       @fetchMachines @bound 'listMachines'

     # section.addSubView more = new MoreVMsModal {}, null
-    section.addSubView more = new SidebarMoreLink
-      tagName  : 'a'
-      click    : @bound 'handleMoreVMsClick'
+    # section.addSubView more = new SidebarMoreLink
+    #   tagName  : 'a'
+    #   click    : @bound 'handleMoreVMsClick'

   handleMachineItemClick: (machineItem, event) ->


do not use postfix conditions

do not use postfix conditions

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -150,7 +150,8 @@
     size: size

   defaultPreviewUrlCallback = (url, info) ->
-    return url if not @previewProxy
+    if not @previewProxy
+      return url

     encodedUrl = encodeURIComponent(url)

@@ -159,8 +160,8 @@
     addQuestionSign = not /\?/.test(@previewProxy)

     queryPart = if justAppend then encodedUrl else "url=#{encodedUrl}"
-    queryPart = '&'.concat(queryPart) if useAmpersand
-    queryPart = '?'.concat(queryPart) if addQuestionSign
+    if useAmpersand then queryPart = '&' + queryPart
+    if addQuestionSign then queryPart = '?' + queryPart

     utils.normalizeUrl(@previewProxy) + queryPart


format numbers for patient results

format numbers for patient results

--- app/assets/javascripts/views/query_view.js.coffee
+++ app/assets/javascripts/views/query_view.js.coffee
@@ -11,8 +11,8 @@
     @popChart = PopHealth.viz.populationChart().width(125).height(25).maximumValue(PopHealth.patientCount)
   shouldDisplayPercentageVisual: -> !@model.isContinuous() and PopHealth.currentUser.shouldDisplayPercentageVisual()
   resultValue: -> if @model.isContinuous() then @model.observation() else @model.performanceRate()
-  fractionTop: -> if @model.isContinuous() then @model.measurePopulation() else @model.numerator()
-  fractionBottom: -> if @model.isContinuous() then @model.ipp() else @model.performanceDenominator()
+  fractionTop: -> if @model.isContinuous() then @model.measurePopulation() else PopHealth.Helpers.formatNumber(@model.numerator())
+  fractionBottom: -> if @model.isContinuous() then @model.ipp() else PopHealth.Helpers.formatNumber(@model.performanceDenominator())
   episodeOfCare: -> @model.parent.get('episode_of_care')
   unit: -> if @model.isContinuous() and @model.parent.get('cms_id') isnt 'CMS179v2' then 'min' else '%'

@@ -32,16 +32,16 @@
   initialize: ->
     @currentPopulation ?= 'IPP'
   ipp: -> @model.ipp()
-  numerator: -> @model.numerator()
-  denominator: -> @model.denominator()
+  numerator: -> PopHealth.Helpers.formatNumber(@model.numerator())
+  denominator: -> PopHealth.Helpers.formatNumber(@model.denominator())
   hasExceptions: -> @model.hasExceptions()
-  exceptions: -> @model.exceptions()
+  exceptions: -> PopHealth.Helpers.formatNumber(@model.exceptions())
   hasExclusions: -> @model.hasExclusions()
-  exclusions: -> @model.exclusions()
+  exclusions: -> PopHealth.Helpers.formatNumber(@model.exclusions())
   hasOutliers: -> @model.hasOutliers()
-  outliers: -> @model.outliers()
+  outliers: -> PopHealth.Helpers.formatNumber(@model.outliers())
   performanceRate: -> @model.performanceRate()
-  performanceDenominator: -> @model.performanceDenominator()
+  performanceDenominator: -> PopHealth.Helpers.formatNumber(@model.performanceDenominator())

   ippIsActive: -> @isActive and @currentPopulation is 'IPP'
   numeratorIsActive: -> @isActive and @currentPopulation is 'NUMER'


Send OAuth token as web socket query param

--- src/packages/collaboration/lib/ws-channel.coffee
+++ src/packages/collaboration/lib/ws-channel.coffee
@@ -1,5 +1,6 @@
 _ = require 'underscore'
 guid = require 'guid'
+keytar = require 'keytar'

 module.exports =
 class WsChannel
@@ -7,7 +8,8 @@

   constructor: (@name) ->
     @clientId = guid.create().toString()
-    @socket = new WebSocket('ws://localhost:8080')
+    token = keytar.getPassword('github.com', 'github')
+    @socket = new WebSocket("ws://localhost:8080?token=#{token}")
     @socket.onopen = =>
       @rawSend 'subscribe', @name, @clientId
       @trigger 'channel:opened'


webapp: adding missing import of ErrorDisplay -- issue #2517

--- src/smc-webapp/collaborators/add-to-project.cjsx
+++ src/smc-webapp/collaborators/add-to-project.cjsx
@@ -6,7 +6,7 @@

 {Alert, Button, ButtonToolbar, FormControl, FormGroup, Well, Checkbox} = require('react-bootstrap')

-{Icon, LabeledRow, Loading, MarkdownInput, SearchInput} = require('../r_misc')
+{Icon, LabeledRow, Loading, MarkdownInput, SearchInput, ErrorDisplay} = require('../r_misc')

 {webapp_client}      = require('../webapp_client')


UI Fix: Check the win-game state in capstone creative mode (#547)

--- ozaria/site/views/play/level/tome/CastButtonView.coffee
+++ ozaria/site/views/play/level/tome/CastButtonView.coffee
@@ -136,7 +136,11 @@
     maxStage = Math.max((@options.level.get('additionalGoals') || []).map((g) -> g.stage)...)
     if @options.level.get('ozariaType') == 'capstone' and @options?.capstoneStage == maxStage and @options.level.get('creativeMode') == true
       # In final stage of creativeMode capstone we ignore all goals to allow unconstrained freedom.
-      @winnable = true
+      # If there is a win-goal state set to that one single state.
+      if typeof e.goalStates?['win-game']?.status == 'string'
+        @winnable = e.goalStates?['win-game']?.status is 'success'
+      else
+        @winnable = true

     if @winnable
       @$el.find('#next').removeClass('inactive')


No longer need to fetch account for account step

--- components/inquiry_questionnaire/views/account.coffee
+++ components/inquiry_questionnaire/views/account.coffee
@@ -8,7 +8,7 @@
   forgot: -> require('../templates/account/forgot.jade') arguments...

 module.exports = class Account extends StepView
-  className: 'iq-account iq-loadable is-loading'
+  className: 'iq-account'

   template: ->
     templates[@mode()] arguments...
@@ -21,15 +21,12 @@
   initialize: ({ @user, @inquiry, @artwork, @state }) ->
     @active = new Backbone.Model mode: 'auth'

-    @listenTo @user.related().account, 'sync error', @render
-    @listenToOnce @user.related().account, 'sync error', @done
     @listenTo @active, 'change:mode', @render
     @listenTo @active, 'change:mode', @forgot

     super

   setup: ->
-    @user.related().account.fetch()
     @sendResetOnce = _.once _.bind(@user.forgot, @user)

   mode: ->
@@ -61,6 +58,3 @@
   change: (e) ->
     e.preventDefault()
     @active.set 'mode', $(e.currentTarget).data 'mode'
-
-  done: ->
-    @$el.removeClass 'is-loading'


Removed unneeded helpers

Removed unneeded helpers

--- core/app/backbone/views/facts/fact_bottom_view.coffee
+++ core/app/backbone/views/facts/fact_bottom_view.coffee
@@ -6,15 +6,6 @@
   events:
     "click .js-open-proxy-link" : "openProxyLink"

-  templateHelpers: ->
-    formatted_time: ->
-      if @friendly_time
-        # this is relevant in a channel, a fact is then 'posted'
-        # or reposted <time> ago
-        "Posted #{@friendly_time} ago"
-      else
-        @created_by_ago
-
   openProxyLink: (e) ->
     mp_track "Factlink: Open proxy link",
       site_url: @model.get("fact_url")


<server> Initializing drivers lists when starting.

--- server/config.coffee
+++ server/config.coffee
@@ -16,13 +16,13 @@

             app.server = server

-            # @todo Load Drivers
-            # actuatorsDrivers = ...
-            # sensorsDrivers = ...
+            # Load Drivers & Pass their reference to models:
+            actuatorsDrivers = {}
+            sensorsDrivers = {}

-            # @todo Pass their reference to controllers:
-            # require('./controllers/actuator').setDrivers actuatorsDrivers
-            # require('./controllers/sensor').setDrivers sensorsDrivers
+            require('./models/actuator').setDrivers actuatorsDrivers
+            require('./models/sensor').setDrivers sensorsDrivers
+            require('./models/driver').setDrivers sensorsDrivers, actuatorsDrivers

             # Initialize Realtime
             realtime = RealtimeAdapter server, ['nest.*']


where test

where test

--- test/integration.coffee
+++ test/integration.coffee
@@ -77,6 +77,21 @@
         body.length.should.equal 1
         done()

+    it 'should return all users with mike as a friend', (done) ->
+      opt =
+        method: "GET"
+        json: true
+        uri: "http://localhost:#{PORT}/users?bestFriend=#{MikeModel._id}"
+
+      request opt, (err, res, body) ->
+        should.not.exist err
+        res.statusCode.should.equal 200
+        should.exist body
+        Array.isArray(body).should.equal true
+        body.length.should.equal 1
+        body[0].name.should.equal TomModel.name
+        done()
+
     it 'should return all users with skip', (done) ->
       opt =
         method: "GET"


NEW: align can be updated on TextInputElements

--- source/Art/Engine/Forms/TextInputElement.coffee
+++ source/Art/Engine/Forms/TextInputElement.coffee
@@ -27,6 +27,14 @@
     placeholder:  postSetter: (v) -> @domElement?.placeholder = v ? ""
     maxLength:    postSetter: (v) -> @domElement?.maxLength   = v ? null
     fontFamily:   "sans-serif", postSetter: (v) -> @domElement?.fontFamily  = v ? "sans-serif"
+    align:
+      validate: (v) ->
+        switch v
+          when "left", "center", "right", null, undefined then true
+          else false
+      preprocess: (v) -> v ? "left"
+      postSetter: (v) -> @domElement?.style.textAlign = v
+
     fontSize:
       validate: (v) -> v > 0
       default: defaultFontSize


added raphael for SVG

added raphael for SVG

--- source/assets/javascripts/application.js.coffee
+++ source/assets/javascripts/application.js.coffee
@@ -1,4 +1,5 @@
 //= require 'lib/jquery-2.1.3.min'
 //= require 'lib/underscore'
+//= require 'lib/raphael'
 //= require 'cell'
-//= require 'grid'
+//= require 'grid'


[Hotfix] Wrong keyword.

[Hotfix] Wrong keyword.

--- src/grafana.coffee
+++ src/grafana.coffee
@@ -111,7 +111,7 @@
         template_map = []
         for template in data.templating.list
           robot.logger.debug template
-          next unless template.current
+          continue unless template.current
           template_map['$' + template.name] = template.current.text

       # Return dashboard rows


return arguments in case others are chained

--- test/components/helpers/component-testing.coffee
+++ test/components/helpers/component-testing.coffee
@@ -41,14 +41,16 @@
       props = _.clone(options.props)
       props._wrapped_component = component
       wrapper = React.render( React.createElement(Wrapper, props), div )
-      resolve(
+      resolve({
         wrapper,
         element: wrapper.refs.element,
         dom: React.findDOMNode(wrapper.refs.element)
-      )
+      })
     )
     # defer adding the then callback so it'll be called after whatever is attached after the return
-    _.defer -> promise.then -> React.unmountComponentAtNode(div)
+    _.defer -> promise.then ->
+      React.unmountComponentAtNode(div)
+      return arguments
     promise

   actions: commonActions


sending all events to particles

sending all events to particles

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -10,6 +10,8 @@
 TimedEvents = (I={}) ->
   Object.reverseMerge I,
     everyEvents: []
+
+  self.bind ""

   ###*
   Execute fn every n frames.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -5,6 +5,8 @@
 player = GameObject()

 player.include TimedEvents
+
+player.every 4,
 </pre></code>

 @see GameObject


Fixed arXiv date parsing. Fixes #18

Fixed arXiv date parsing. Fixes #18

--- server/manage.coffee
+++ server/manage.coffee
@@ -174,8 +174,8 @@
           continue

         # TODO: Really process versions
-        created = moment.utc(record.version[0].date).toDate()
-        updated = moment.utc(record.version[record.version.length - 1].date).toDate()
+        created = moment.utc(record.version[0].date[0]).toDate()
+        updated = moment.utc(record.version[record.version.length - 1].date[0]).toDate()

         authors = record.authors[0]


Simplify arguments handling

Simplify arguments handling

--- src/lib/Component.coffee
+++ src/lib/Component.coffee
@@ -192,8 +192,8 @@
   # Returns true if a port (or ports joined by logical AND) has a new IP
   # Passing a validation callback as a last argument allows more selective
   # checking of packets.
-  has: (port = 'in') ->
-    args = if arguments.length is 0 then [port] else Array.prototype.slice.call(arguments)
+  has: (args...) ->
+    args = ['in'] unless args.length
     if typeof args[args.length - 1] is 'function'
       validate = args.pop()
       for port in ports
@@ -204,8 +204,8 @@
     res

   # Fetches IP object(s) for port(s)
-  get: (port = 'in') ->
-    args = if arguments.length is 0 then [port] else arguments
+  get: (args...) ->
+    args = ['in'] unless args.length
     if (@nodeInstance.ordered or @nodeInstance.autoOrdering) and
     @nodeInstance.activateOnInput and
     not ('__resolved' of @result)
@@ -214,8 +214,8 @@
     if args.length is 1 then res[0] else res

   # Fetches `data` property of IP object(s) for given port(s)
-  getData: (port = 'in') ->
-    args = if arguments.length is 0 then [port] else arguments
+  getData: (args...) ->
+    args = ['in'] unless args.length
     ips = @get.apply this, args
     if args.length is 1
       return ips?.data ? undefined


remove debugging

remove debugging

--- server/models.coffee
+++ server/models.coffee
@@ -37,7 +37,6 @@

     # Verify the signature matches
     if (@isModified 'content') or (@isModified 'sig')
-      console.log 'verify', pub: @pubkey, content: @content, sig: @sig
       key = new Key 'pub', (Array.apply null, @pubkey)
       try unless key.verify_sig @content, (Array.apply null, @sig)
         return next new Error 'Invalid signature provided'


add comment for BaseList#itemFactory

add comment for BaseList#itemFactory

--- src/lib/base-list.coffee
+++ src/lib/base-list.coffee
@@ -62,6 +62,13 @@

     @property loaded
     @type Boolean
+    ###
+
+    ###*
+    itemFactory: instance of factory which creates item models
+
+    @property itemFactory
+    @type BaseFactory
     ###

     ###*


Fix and add test in LevelComponent on the server side.

--- test/server/handlers/level_component.spec.coffee
+++ test/server/handlers/level_component.spec.coffee
@@ -7,30 +7,120 @@
     description:'Makes the unit uncontrollably bash anything bashable, using the bash system.'
     code: 'bash();'
     language: 'javascript'
-    official: true
     permissions:simplePermissions

   components = {}

   url = getURL('/db/level.component')

-  it 'clears things first', (done) ->
+  it 'preparing test : clears things first.', (done) ->
     clearModels [Level, LevelComponent], (err) ->
       expect(err).toBeNull()
       done()

-  it 'can make a LevelComponent, without setting official.', (done) ->
+  it 'can\'t be created by ordinary users.', (done) ->
     loginJoe (joe) ->
       request.post {uri:url, json:component}, (err, res, body) ->
+        expect(res.statusCode).toBe(403)
+        done()
+
+  it 'can be created by an admin.', (done) ->
+    loginAdmin (joe) ->
+      request.post {uri:url, json:component}, (err, res, body) ->
         expect(res.statusCode).toBe(200)
-        expect(body.official).toBeUndefined()
+        expect(body._id).toBeDefined()
+        expect(body.name).toBe(component.name)
+        expect(body.description).toBe(component.description)
+        expect(body.code).toBe(component.code)
+        expect(body.language).toBe(component.language)
+        expect(body.__v).toBe(0)
+        expect(body.creator).toBeDefined()
+        expect(body.original).toBeDefined()
+        expect(body.created).toBeDefined()
+        expect(body.version).toBeDefined()
+        expect(body.permissions).toBeDefined()
         components[0] = body
         done()

-  it 'can allows admins to edit the official property.', (done) ->
+  it 'have a unique name.', (done) ->
+    loginAdmin (joe) ->
+      request.post {uri:url, json:component}, (err, res, body) ->
+        expect(res.statusCode).toBe(422)
+        done()
+
+  it 'can read by an admin.', (done) ->
+    loginAdmin (joe) ->
+      request.get {uri:url+'/'+components[0]._id}, (err, res, body) ->
+        expect(res.statusCode).toBe(200)
+        body = JSON.parse(body)
+        expect(body._id).toBe(components[0]._id)
+        done()
+
+  it 'can be read by ordinary users.', (done) ->
+    loginJoe (joe) ->
+      request.get {uri:url+'/'+components[0]._id}, (err, res, body) ->
+        expect(res.statusCode).toBe(200)
+        body = JSON.parse(body)
+        expect(body._id).toBe(components[0]._id)
+        expect(body.name).toBe(components[0].name)
+        expect(body.slug).toBeDefined()
+        expect(body.description).toBe(components[0].description)
+        expect(body.code).toBe(components[0].code)
+        expect(body.language).toBe(components[0].language)
+        expect(body.__v).toBe(0)
+        expect(body.official).toBeDefined()
+        expect(body.creator).toBeDefined()
+        expect(body.original).toBeDefined()
+        expect(body.created).toBeDefined()
+        expect(body.configSchema).toBeDefined()
+        expect(body.dependencies).toBeDefined()
+        expect(body.propertyDocumentation).toBeDefined()
+        expect(body.version.isLatestMajor).toBe(true)
+        expect(body.version.isLatestMinor).toBe(true)
+        expect(body.permissions).toBeDefined()
+        done()
+
+  it 'is unofficial by default', (done) ->
+    loginJoe (joe) ->
+      request.get {uri:url+'/'+components[0]._id}, (err, res, body) ->
+        expect(res.statusCode).toBe(200)
+        body = JSON.parse(body)
+        expect(body._id).toBe(components[0]._id)
+        expect(body.official).toBe(false)
+        done()
+
+  it 'has system ai by default', (done) ->
+    loginJoe (joe) ->
+      request.get {uri:url+'/'+components[0]._id}, (err, res, body) ->
+        expect(res.statusCode).toBe(200)
+        body = JSON.parse(body)
+        expect(body._id).toBe(components[0]._id)
+        expect(body.system).toBe("ai")
+        done()
+
+  it 'official property isn\'t editable by an ordinary user.', (done) ->
+    components[0].official = true
+    loginJoe (joe) ->
+      request.post {uri:url, json:components[0]}, (err, res, body) ->
+        expect(res.statusCode).toBe(403)
+        done()
+
+  it 'official property is editable by an admin.', (done) ->
     components[0].official = true
     loginAdmin (joe) ->
       request.post {uri:url, json:components[0]}, (err, res, body) ->
+        expect(res.statusCode).toBe(200)
         expect(body.official).toBe(true)
-        expect(res.statusCode).toBe(200)
-        done()
+        expect(body.original).toBe(components[0].original)
+        expect(body.version.isLatestMinor).toBe(true)
+        expect(body.version.isLatestMajor).toBe(true)
+        components[1] = body
+
+        request.get {uri:url+'/'+components[0]._id}, (err, res, body) ->
+          expect(res.statusCode).toBe(200)
+          body = JSON.parse(body)
+          expect(body._id).toBe(components[0]._id)
+          expect(body.official).toBe(false)
+          expect(body.version.isLatestMinor).toBe(false)
+          expect(body.version.isLatestMajor).toBe(false)
+          done()


Make comment/explanation match implementation.

--- content_scripts/mode.coffee
+++ content_scripts/mode.coffee
@@ -144,10 +144,10 @@
         keyup: (event) =>
           if KeyboardUtils.isPrintable event then @stopBubblingAndFalse else @stopBubblingAndTrue

-    # if @options.suppressTrailingKeyEvents is set, then we suppress all key events until a subsquent
-    # (non-repeat) keydown or keypress.  In particular, the intention is to catch keyup events for keys which
-    # we have handled, but which otherwise might trigger page actions (if the page is listening for keyup
-    # events).
+    # if @options.suppressTrailingKeyEvents is set, then  -- on exit -- we suppress all key events until a
+    # subsquent (non-repeat) keydown or keypress.  In particular, the intention is to catch keyup events for
+    # keys which we have handled, but which otherwise might trigger page actions (if the page is listening for
+    # keyup events).
     if @options.suppressTrailingKeyEvents
       @onExit ->
         handler = (event) ->


Un-F the specs

Un-F the specs

--- spec/atom/vim-mode-spec.coffee
+++ spec/atom/vim-mode-spec.coffee
@@ -1,7 +1,7 @@
 Editor = require 'editor'
 VimMode = require 'vim-mode'

-fdescribe "VimMode", ->
+describe "VimMode", ->
   editor = null

   beforeEach ->


Ensure failing seeds are not failing anymore

--- spec/display-layer-spec.coffee
+++ spec/display-layer-spec.coffee
@@ -528,8 +528,6 @@
   it "updates the displayed text correctly when the underlying buffer changes", ->
     for i in [0...10] by 1
       seed = Date.now()
-      # seed = 1455064492569
-      # seed = 1455064532720
       seedFailureMessage = "Seed: #{seed}"
       random = new Random(seed)
       buffer = new TextBuffer(text: buildRandomLines(random, 10))


Add invisibles to randomized DisplayLayer test

--- spec/display-layer-spec.coffee
+++ spec/display-layer-spec.coffee
@@ -320,7 +320,9 @@
       seedFailureMessage = "Seed: #{seed}"
       random = new Random(seed)
       buffer = new TextBuffer(text: buildRandomLines(random, 10))
-      displayLayer = buffer.addDisplayLayer(tabLength: 4, patchSeed: seed)
+      invisibles = {}
+      invisibles.space = '•' if random(2) > 0
+      displayLayer = buffer.addDisplayLayer({tabLength: 4, patchSeed: seed, invisibles})
       textDecorationLayer = new TestDecorationLayer([], buffer, random)
       displayLayer.setTextDecorationLayer(textDecorationLayer)

@@ -338,7 +340,7 @@
         return if currentSpecFailed()

         # incrementally-updated text matches freshly computed text
-        expectedDisplayLayer = buffer.addDisplayLayer(foldsMarkerLayer: displayLayer.foldsMarkerLayer.copy(), tabLength: 4)
+        expectedDisplayLayer = buffer.addDisplayLayer({foldsMarkerLayer: displayLayer.foldsMarkerLayer.copy(), tabLength: 4, invisibles})
         expect(JSON.stringify(displayLayer.getText())).toBe(JSON.stringify(expectedDisplayLayer.getText()), seedFailureMessage)
         return if currentSpecFailed()


Remove test focus

Remove test focus

--- spec/display-table-spec.coffee
+++ spec/display-table-spec.coffee
@@ -1,7 +1,7 @@
 DisplayTable = require '../lib/display-table'
 Table = require '../lib/table'

-fdescribe 'DisplayTable', ->
+describe 'DisplayTable', ->
   [table, displayTable] = []

   beforeEach ->


Capitalize Linux

Capitalize Linux

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -124,7 +124,7 @@
   charCode = parseInt(hexCharCode, 16)

   # Chromium includes incorrect keyIdentifier values on keypress events for
-  # certain symbols keys on linux.
+  # certain symbols keys on Linux.
   #
   # See https://code.google.com/p/chromium/issues/detail?id=51024
   # See https://bugs.webkit.org/show_bug.cgi?id=19906


:art: Remove fat-arrow Coffeescript linter warning

--- spec/random-editor-spec.coffee
+++ spec/random-editor-spec.coffee
@@ -85,7 +85,7 @@
     referenceEditor.setText(editor.getText())
     referenceEditor.setSoftWrapped(editor.isSoftWrapped())

-    screenLines = [0..referenceEditor.getLastScreenRow()].map (row) => referenceEditor.lineTextForScreenRow(row)
+    screenLines = [0..referenceEditor.getLastScreenRow()].map (row) -> referenceEditor.lineTextForScreenRow(row)
     bufferRows = referenceEditor.bufferRowsForScreenRows(0, referenceEditor.getLastScreenRow())

     {screenLines, bufferRows}


fix alerts routes

fix alerts routes

--- src/services/routes/routes.svc.coffee
+++ src/services/routes/routes.svc.coffee
@@ -159,10 +159,10 @@

         alerts:
           create: (kpi_id) ->
-            "#{defaults.mnoHub}#{defaults.impacPrefix}/kpis/#{kpi_id}/alerts"
+            "#{defaults.mnoHub}/impac/kpis/#{kpi_id}/alerts"

           delete: (alert_id) ->
-            "#{defaults.mnoHub}#{defaults.impacPrefix}/alerts/#{alert_id}"
+            "#{defaults.mnoHub}/impac/alerts/#{alert_id}"

       service.organizations =
         appInstancesSync: (uid) ->


one more fix

one more fix

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -18,7 +18,7 @@
                 notExists = true
                 for j in self._locations
                     if i.id == j.id
-                        i.notSeen = false
+                        j.notSeen = false
                         notExists = false

                 if notExists


add debounce for AJAX data requests

add debounce for AJAX data requests

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -1,5 +1,13 @@
 class MainController extends SimpleMVC.Controller
+    _navigateDebounce: () =>
+        if this._navTimeoutId?
+            clearTimeout this._navTimeoutId
+
+        this._navTimeoutId = setTimeout this._navigateToNewBounds, 100
+
     _navigateToNewBounds: () =>
+        this._navTimeoutId = undefined
+
         # Set not seen flag so we know which ones to remove later
         if not this._locations?
             this._locations = []
@@ -94,7 +102,7 @@
         placeLoc = this._place.geometry.location
         window.app.navigate "/loc/" + placeLoc.lat() + "/" + placeLoc.lng(), true, false
         this._handlePossibleAdd = true
-        google.maps.event.addListener this._map, "bounds_changed", this._navigateToNewBounds
+        google.maps.event.addListener this._map, "bounds_changed", this._navigateDebounce
         this._map.setZoom 15

     reportError: (id) ->
@@ -156,7 +164,7 @@
             this._map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions)

             # handler so we can reload the list
-            google.maps.event.addListener this._map, "bounds_changed", this._navigateToNewBounds
+            google.maps.event.addListener this._map, "bounds_changed", this._navigateDebounce

             # initialize autocomplete widget
             this._autocomplete = new google.maps.places.Autocomplete(document.getElementById("address"))


Fix font-size when resizing a postit

Fix font-size when resizing a postit

--- assets/views/postit.coffee
+++ assets/views/postit.coffee
@@ -34,7 +34,7 @@
   render: ->
     @$el.html JST.postit @model.toJSON()
     @buildSelector()
-    @update()
+    @p.textContent = @model.get 'title'
     @colorize()
     @move()
     @resize()
@@ -88,6 +88,7 @@
     offset = 2 + Math.floor size.h * zoom / 40
     radius = Math.floor size.h * zoom / 15
     @shadow.style.boxShadow = "-#{offset}px 0 #{radius}px #999"
+    @adjustFontSize()
     @

   rotate: =>


textwrap checks style before attr for text anchor

--- src/textwrap/helpers/parseText.coffee
+++ src/textwrap/helpers/parseText.coffee
@@ -21,5 +21,5 @@

   # Detects text-align if not specified with .align( )
   unless vars.align.value
-    vars.container.align = vars.container.value.attr("text-anchor") or
-                           vars.container.value.style("text-anchor")
+    vars.container.align = vars.container.value.style("text-anchor") or
+                           vars.container.value.attr("text-anchor")