:lipstick:

:lipstick:

--- spec/app/config-spec.coffee
+++ spec/app/config-spec.coffee
@@ -110,7 +110,7 @@
       config.set("foo.bar.baz", "i'm back")
       expect(observeHandler).toHaveBeenCalledWith("i'm back")

-  describe "initializeConfigDirectory()", ->
+  describe ".initializeConfigDirectory()", ->
     beforeEach ->
       config.configDirPath = '/tmp/dot-atom-dir'
       expect(fsUtils.exists(config.configDirPath)).toBeFalsy()
@@ -148,20 +148,21 @@
           expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-dark-syntax.css'))).toBeTruthy()
           expect(fsUtils.isFile(fsUtils.join(config.configDirPath, 'themes/atom-light-syntax.css'))).toBeTruthy()

-  describe "when the config file is not parseable", ->
-    beforeEach ->
-     config.configDirPath = '/tmp/dot-atom-dir'
-     config.configFilePath = fsUtils.join(config.configDirPath, "config.cson")
-     expect(fsUtils.exists(config.configDirPath)).toBeFalsy()
+  describe ".loadUserConfig()", ->
+    describe "when the config file cannot be parsed", ->
+      beforeEach ->
+       config.configDirPath = '/tmp/dot-atom-dir'
+       config.configFilePath = fsUtils.join(config.configDirPath, "config.cson")
+       expect(fsUtils.exists(config.configDirPath)).toBeFalsy()

-    afterEach ->
-      fsUtils.remove('/tmp/dot-atom-dir') if fsUtils.exists('/tmp/dot-atom-dir')
+      afterEach ->
+        fsUtils.remove('/tmp/dot-atom-dir') if fsUtils.exists('/tmp/dot-atom-dir')

-    it "logs an error to the console and does not overwrite the config file", ->
-      config.save.reset()
-      spyOn(console, 'error')
-      fsUtils.write(config.configFilePath, "{{{{{")
-      config.loadUserConfig()
-      config.set("hair", "blonde") # trigger a save
-      expect(console.error).toHaveBeenCalled()
-      expect(config.save).not.toHaveBeenCalled()
+      it "logs an error to the console and does not overwrite the config file", ->
+        config.save.reset()
+        spyOn(console, 'error')
+        fsUtils.write(config.configFilePath, "{{{{{")
+        config.loadUserConfig()
+        config.set("hair", "blonde") # trigger a save
+        expect(console.error).toHaveBeenCalled()
+        expect(config.save).not.toHaveBeenCalled()


add app.coffee comments

add app.coffee comments

--- client/assets/coffee/app.coffee
+++ client/assets/coffee/app.coffee
@@ -1,3 +1,4 @@
+# Initialize all namespaces
 SwanKiosk =
   Interpreter:  {}
   Interpreters: {}


Updated somewhere with the new API

Updated somewhere with the new API

--- lib/jsonserver.coffee
+++ lib/jsonserver.coffee
@@ -12,7 +12,7 @@
   bodyParser = require 'body-parser'
   multipart  = require 'connect-multiparty'
   logger     = require 'morgan'
-  db         = require 'somewhere'
+  DB         = require 'somewhere'
   app        = do express

   app.use do bodyParser
@@ -27,7 +27,7 @@
     res.setHeader 'Access-Control-Allow-Origin', '*'
     do next

-  db.connect(options.path)
+  db = new DB options.path

   app.get '/:resource', (req, res) ->
     res.jsonp db.find req.params.resource


Fix tests for multiple editors

Fix tests for multiple editors

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -57,6 +57,7 @@
       filePath = "/tmp/atom-whitespace.txt"
       fs.writeSync(filePath, "")
       rootView.open(filePath)
+      editor = rootView.getActiveView()
       expect(statusBar.bufferModified.text()).toBe ''
       editor.insertText("\n")
       advanceClock(buffer.stoppedChangingDelay)
@@ -86,6 +87,7 @@
     it "updates the buffer modified indicator for the new buffer", ->
       expect(statusBar.bufferModified.text()).toBe ''
       rootView.open('sample.txt')
+      editor = rootView.getActiveView()
       editor.insertText("\n")
       advanceClock(buffer.stoppedChangingDelay)
       expect(statusBar.bufferModified.text()).toBe '*'


Don’t attach to DOM twice

Don’t attach to DOM twice

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -160,7 +160,6 @@
       editorView.height(100)
       editorView.component.pollDOM()

-      atom.workspaceView.attachToDom()
       editor.setSelectedBufferRange([[0, 0], [0, 2]])
       expect(statusBar.selectionCount.textContent).toBe '(2)'


Changed to use token: Spree.api_key

Changed to use token: Spree.api_key

authenticity_token: AUTH_TOKEN by token: Spree.api_key

--- backend/app/assets/javascripts/spree/backend/images/index.js.coffee
+++ backend/app/assets/javascripts/spree/backend/images/index.js.coffee
@@ -9,7 +9,7 @@
       type: 'GET'
       url: @href
       data: (
-        authenticity_token: AUTH_TOKEN
+        token: Spree.api_key
       )
       success: (r) ->
         ($ '#images').html r


:no_mouth:

--- scripts/remember.coffee
+++ scripts/remember.coffee
@@ -48,13 +48,13 @@
           searchPattern = match[2]
           matchingKeys = findSimilarMemories(searchPattern)
           if matchingKeys.length > 0
-            value = "I remember:\n#{matchingKeys.join('\n')}"
+            value = "I remember:\n#{matchingKeys.join(', ')}"
           else
             value = "I don't remember anything matching `#{searchPattern}`"
         else
           matchingKeys = findSimilarMemories(key)
           if matchingKeys.length > 0
-            keys = matchingKeys.join('\n')
+            keys = matchingKeys.join(', ')
             value = "I don't remember `#{key}`. Did you mean:\n#{keys}"
           else
             value = "I don't remember anything matching `#{key}`"
@@ -72,7 +72,7 @@
     msg.finish()
     keys = []
     keys.push key for key of memories()
-    msg.send "I remember:\n#{keys.join('\n')}"
+    msg.send "I remember:\n#{keys.join(', ')}"

   robot.respond /what are your favorite memories/i, (msg) ->
     msg.finish()
@@ -80,7 +80,7 @@
       memoriesByRecollection()[key]
     sortedMemories.reverse()

-    msg.send "My favorite memories are:\n#{sortedMemories[0..20].join('\n')}"
+    msg.send "My favorite memories are:\n#{sortedMemories[0..20].join(', ')}"

   robot.respond /(me|random memory|memories)\s+?(.*)?$/i, (msg) ->
     msg.finish()


fix typo

fix typo

--- lib/misc/paths.coffee
+++ lib/misc/paths.coffee
@@ -62,7 +62,7 @@
       proc = child_process.exec "\"#{path}\" --version", (err, stdout, stderr) =>
         return reject(stderr) if err?
         res = stdout.match /(\d+)\.(\d+)\.(\d+)/
-        return reject("Coudln't resolve version.") unless res?
+        return reject("Couldn't resolve version.") unless res?
         [_, major, minor, patch] = res
         resolve {major, minor, patch}


allow object for select

allow object for select

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -70,6 +70,7 @@
         @el = @$(data)

     select: (from, to) ->
+        {from, to} = from if typeof from is 'object'
         # selector
         el = @el.find(from)
         # dont touch origin


removing snapshot logic

removing snapshot logic

--- src/document-adapter.coffee
+++ src/document-adapter.coffee
@@ -288,7 +288,7 @@
     })

   createRecord: (store, type, record) ->
-    json = store.serializerFor(type.typeKey).serialize(record._createSnapshot())
+    json = store.serializerFor(type.typeKey).serialize(record())
     @_push(store, type, record, json)

   updateRecord: (store, type, record) ->


Clear dataset elements for pooled objects

--- src/dom-element-pool.coffee
+++ src/dom-element-pool.coffee
@@ -9,12 +9,14 @@
     for tagName, freeElements of @freeElementsByTagName
       freeElements.length = 0

-  build: (tagName, className, textContent) ->
+  build: (tagName, className, textContent = "") ->
     element = @freeElementsByTagName[tagName]?.pop()
     element ?= document.createElement(tagName)
-    element.className = className
+    delete element.dataset[dataId] for dataId of element.dataset
+    element.removeAttribute("class")
+    element.removeAttribute("style")
+    element.className = className if className?
     element.textContent = textContent
-    element.removeAttribute("style")

     @freedElements.delete(element)


tweek

tweek

--- src/driver/sqlDriver.coffee
+++ src/driver/sqlDriver.coffee
@@ -79,7 +79,8 @@
         throw new Error("unknown filter type '#{filter.type}'")

   addFilter: (filter) ->
-    @filterPart = 'WHERE ' + @filterToSQL(filter)
+    return unless filter
+    @filterPart = "WHERE #{@filterToSQL(filter)}"
     return this

   timeBucketing: {
@@ -233,8 +234,7 @@

   filter = andFilters(filter, condensedQuery.filter)
   try
-    if filter
-      sqlQuery.addFilter(filter)
+    sqlQuery.addFilter(filter)

     # split
     split = condensedQuery.split


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -6,8 +6,8 @@
     player = GameObject
       health: 50

-    # health will approach 100 by 1
-    # each
+    # health will approach 100
+    # by 1 each update
     player.cooldown "health",
       target: 100


Allow syncFollows method of FollowItems override fetch options.

--- collections/follow_items.coffee
+++ collections/follow_items.coffee
@@ -23,13 +23,13 @@
   findByItemId: (itemId) ->
     @find (m) -> m.getItem().id is itemId

-  syncFollows: (itemIds) ->
+  syncFollows: (itemIds, options) ->
     return unless CurrentUser.orNull()
-    @fetch
+    @fetch _.extend
       data: @getSyncFollowsData(itemIds)
       cache: false
       remove: false
-      merge: true
+      merge: true, options

   follow: (itemId, options) ->
     followItem = @findByItemId itemId


Removed auth love option from authorize.user()

--- server/authorize.coffee
+++ server/authorize.coffee
@@ -71,9 +71,8 @@
         # Throw unhandled errors
         .done()

-
   # User authorization check
-  self.user = (level = 'user', type = 'default') ->
+  self.user = (type = 'default') ->
     return (req, res, next) ->
       # Use a Conveyor to handle the stuff
       conveyor = new Conveyor req, res, token: req.authToken


Prevent email from Chrome know bug

Prevent email from Chrome know bug

--- error_logger_server.coffee
+++ error_logger_server.coffee
@@ -1,5 +1,6 @@
 REPETITIVE_ERRORS =
   "Connection timeout. No sockjs heartbeat received."
+  "TypeError: Cannot read property 'invalidate' of undefined"
 LOG_ROUTE = '/errorlog'
 ERROR_FIELDS = ['errorType', 'ipAddress', 'location', 'browser', 'details']
 ERROR_TYPE_TO_SUBJECT_GETTER =


Fix #393, #380: Don’t suppress Esc in browser chrome

--- extension/packages/modes.coffee
+++ extension/packages/modes.coffee
@@ -5,6 +5,10 @@
 , isEscCommandKey
 , isReturnCommandKey
 , findStorage }         = require 'commands'
+
+{ interfaces: Ci } = Components
+
+XULDocument = Ci.nsIDOMXULDocument

 modes = {}

@@ -44,7 +48,12 @@
       #   custom dialog of a website is open, we should be able to cancel hint
       #   markers on it without closing it. Secondly, otherwise cancelling hint
       #   markers on Google causes its search bar to be focused.
-      if keyStr == 'Esc' and not autoInsertMode
+      # - It may only be suppressed in web pages, not in browser chrome. That
+      #   allows for reseting the location bar when blurring it, and closing
+      #   dialogs such as the “bookmark this page” dialog (<c-d>).
+      document = event.originalTarget.ownerDocument
+      inBrowserChrome = (document instanceof XULDocument)
+      if keyStr == 'Esc' and (not autoInsertMode or inBrowserChrome)
         return false

       return true


Closes #117. Hotfix for custom hint chars

--- extension/packages/utils.coffee
+++ extension/packages/utils.coffee
@@ -264,6 +264,8 @@
     if hintChars.length < 3
       hintChars = getDefaultPref('hint_chars')

+    return hintChars
+
 exports.Bucket                    = Bucket
 exports.getCurrentTabWindow       = getCurrentTabWindow
 exports.getEventWindow            = getEventWindow


Update subscriber.coffee

Update subscriber.coffee

Patch that make suscriber works with this config:
Redis version 2.8.17
NodeJS version 0.10.36
NPM version 1.4.28
CoffeeScript version 1.9.0

--- lib/subscriber.coffee
+++ lib/subscriber.coffee
@@ -129,6 +129,12 @@
                         num = parseInt(value)
                         @info[key] = if num + '' is value then num else value
                     cb(@info)
+                else if @info?.updated? # subscriber exists
+                    # transform numeric value to number type
+                    for own key, value of @info
+                        num = parseInt(value)
+                        @info[key] = if num + '' is value then num else value
+                    cb(@info)
                 else
                     cb(@info = null) # null if subscriber doesn't exist + flush cache


Use multi property set in example

Use multi property set in example

--- examples/collection.coffee
+++ examples/collection.coffee
@@ -46,8 +46,10 @@

 window.aPost = new Post()

-aPost.comments = [{body: 'test'}, {body: 'another'}]
+aPost.set
+  title: 'Monkey.js released!'
+  body: 'New contender in the JS framework wars!'
+  comments: [{body: 'This is cool'}, {body: 'I hate it'}]

 window.onload = ->
-  view = Monkey.render('post', aPost)
-  sandbox = document.body.appendChild(view)
+  document.body.appendChild Monkey.render('post', aPost)


Pet CoffeeScript's more strict parser.

Pet CoffeeScript's more strict parser.

--- shared/learnsets.coffee
+++ shared/learnsets.coffee
@@ -132,31 +132,34 @@

   # Check against event Pokemon
   # TODO: Event Pokemon require more stringent checks, e.g. gender/ability etc.
-  return true  if looper (learnset, pokemonName, formeName) ->
+  checksOut = looper (learnset, pokemonName, formeName) ->
     events = EventPokemon[pokemonName] || []
     events = events.filter((event) -> event.forme == formeName)
     for event in events
       lsetLeftovers = leftoverMoves.filter (move) ->
         move in event.moves && pokemonLevel >= event.level
       return true  if lsetLeftovers.length == leftoverMoves.length
+  return true  if checksOut

   # These learnset groups are non-standard but can be used. If a non-standard
   # group completely overlaps the leftover moves, the moveset is valid.
   nonstandardGroups = [ "light-ball-egg", "stadium-surfing-pikachu" ]
-  return true  if looper (learnset) ->
+  checksOut = looper (learnset) ->
     for group in nonstandardGroups
       continue  if !learnset[group]
       total = (m  for m in leftoverMoves when m of learnset[group]).length
       return true  if total == leftoverMoves.length
+  return true  if checksOut

   # If the remaining moves are all dream world moves, it's a valid moveset.
-  return true  if looper (learnset) ->
+  checksOut = looper (learnset) ->
     return  if !learnset['dreamWorld']
     dreamWorldMoves = []
     for moveName of learnset['dreamWorld']
       continue  if moveName in dreamWorldMoves || moveName not in leftoverMoves
       dreamWorldMoves.push(moveName)
     return true  if leftoverMoves.length == dreamWorldMoves.length
+  return true  if checksOut

   # This makes it so if the remaining moves are all egg moves, the moveset is
   # valid. That's false, but it's permissive. Later, factor in chain-breeding.
@@ -182,7 +185,7 @@
   {level} = pokemon
   level ||= 100

-  return true  if looper (learnset) ->
+  checksOut = looper (learnset) ->
     # Check level-up, TM/HM, and tutors.
     return true  if learnset["level-up"]?[move] <= level ||
                     learnset["machine"]?[move]  <= level ||
@@ -193,6 +196,7 @@
     # so you must take care of them at a higher level.
     return true  if learnset["level-up"]?["Sketch"] <= level &&
                     move not in [ "Chatter", "Struggle" ]
+  return true  if checksOut

   # TODO: Skip unavailable Pokemon (due to being a generation later).
   # TODO: level-up moves can be bred.


disable fastclick for now

disable fastclick for now

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -14,7 +14,7 @@
   setupPusherAndCurrentUser()
   setupViews()
   setupAjaxHeaders()
-  setupFastClick()
+  # setupFastClick()
   initShortCuts()

 setupViews = ->


Remove console

Remove console

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -175,8 +175,6 @@
     current_user.get('private_connections_count') < 10 or
     current_user.get('is_exceeding_private_connections_limit')

-  console.log 'shouldReturn', shouldReturn
-
   return if shouldReturn

   model = new Backbone.Model


Import and export dependencies

Import and export dependencies

--- lib/y-richtext.coffee
+++ lib/y-richtext.coffee
@@ -1,4 +1,8 @@
 BaseClass = (require "./misc.coffee").BaseClass
+Y = require '../../yjs/lib/y.coffee'
+Y.List = require '../../y-list/lib/y-list.coffee'
+Y.Selections = require '../../y-selections/lib/y-selections.coffee'
+

 # All dependencies (like Y.Selections) to other types (that have its own
 # repository) should  be included by the user (in order to reduce the amount of
@@ -198,4 +202,6 @@
   # @param character [Y.Object] the character to look for
   #TODO: check that it works
   indexOf = (character) ->
-    (@_get "characters").val().indexOf(character)+    (@_get "characters").val().indexOf(character)
+if module?
+  module.exports = RichText


table rendererOptions for hiding totals

table rendererOptions for hiding totals

--- src/pivot.coffee
+++ src/pivot.coffee
@@ -444,7 +444,10 @@
     pivotTableRenderer = (pivotData, opts) ->

         defaults =
-            table: clickCallback: null
+            table:
+                clickCallback: null
+                rowTotals: true
+                colTotals: true
             localeStrings: totals: "Totals"

         opts = $.extend(true, {}, defaults, opts)
@@ -506,7 +509,7 @@
                     if parseInt(j) == colAttrs.length-1 and rowAttrs.length != 0
                         th.setAttribute("rowspan", 2)
                     tr.appendChild th
-            if parseInt(j) == 0
+            if parseInt(j) == 0 && opts.table.rowTotals
                 th = document.createElement("th")
                 th.className = "pvtTotalLabel pvtRowTotalLabel"
                 th.innerHTML = opts.localeStrings.totals
@@ -555,46 +558,50 @@
                     td.onclick = getClickHandler(val, rowKey, colKey)
                 tr.appendChild td

-            totalAggregator = pivotData.getAggregator(rowKey, [])
-            val = totalAggregator.value()
-            td = document.createElement("td")
-            td.className = "pvtTotal rowTotal"
-            td.textContent = totalAggregator.format(val)
-            td.setAttribute("data-value", val)
-            if getClickHandler?
-                td.onclick = getClickHandler(val, rowKey, [])
-            td.setAttribute("data-for", "row"+i)
-            tr.appendChild td
+            if opts.table.rowTotals || colAttrs.length == 0
+                totalAggregator = pivotData.getAggregator(rowKey, [])
+                val = totalAggregator.value()
+                td = document.createElement("td")
+                td.className = "pvtTotal rowTotal"
+                td.textContent = totalAggregator.format(val)
+                td.setAttribute("data-value", val)
+                if getClickHandler?
+                    td.onclick = getClickHandler(val, rowKey, [])
+                td.setAttribute("data-for", "row"+i)
+                tr.appendChild td
             tbody.appendChild tr

         #finally, the row for col totals, and a grand total
-        tr = document.createElement("tr")
-        th = document.createElement("th")
-        th.className = "pvtTotalLabel pvtColTotalLabel"
-        th.innerHTML = opts.localeStrings.totals
-        th.setAttribute("colspan", rowAttrs.length + (if colAttrs.length == 0 then 0 else 1))
-        tr.appendChild th
-        for own j, colKey of colKeys
-            totalAggregator = pivotData.getAggregator([], colKey)
-            val = totalAggregator.value()
-            td = document.createElement("td")
-            td.className = "pvtTotal colTotal"
-            td.textContent = totalAggregator.format(val)
-            td.setAttribute("data-value", val)
-            if getClickHandler?
-                td.onclick = getClickHandler(val, [], colKey)
-            td.setAttribute("data-for", "col"+j)
-            tr.appendChild td
-        totalAggregator = pivotData.getAggregator([], [])
-        val = totalAggregator.value()
-        td = document.createElement("td")
-        td.className = "pvtGrandTotal"
-        td.textContent = totalAggregator.format(val)
-        td.setAttribute("data-value", val)
-        if getClickHandler?
-            td.onclick = getClickHandler(val, [], [])
-        tr.appendChild td
-        tbody.appendChild tr
+        if opts.table.colTotals || rowAttrs.length == 0
+            tr = document.createElement("tr")
+            if opts.table.colTotals || rowAttrs.length == 0
+                th = document.createElement("th")
+                th.className = "pvtTotalLabel pvtColTotalLabel"
+                th.innerHTML = opts.localeStrings.totals
+                th.setAttribute("colspan", rowAttrs.length + (if colAttrs.length == 0 then 0 else 1))
+                tr.appendChild th
+            for own j, colKey of colKeys
+                totalAggregator = pivotData.getAggregator([], colKey)
+                val = totalAggregator.value()
+                td = document.createElement("td")
+                td.className = "pvtTotal colTotal"
+                td.textContent = totalAggregator.format(val)
+                td.setAttribute("data-value", val)
+                if getClickHandler?
+                    td.onclick = getClickHandler(val, [], colKey)
+                td.setAttribute("data-for", "col"+j)
+                tr.appendChild td
+            if opts.table.rowTotals || colAttrs.length == 0
+                totalAggregator = pivotData.getAggregator([], [])
+                val = totalAggregator.value()
+                td = document.createElement("td")
+                td.className = "pvtGrandTotal"
+                td.textContent = totalAggregator.format(val)
+                td.setAttribute("data-value", val)
+                if getClickHandler?
+                    td.onclick = getClickHandler(val, [], [])
+                tr.appendChild td
+            tbody.appendChild tr
         result.appendChild tbody

         #squirrel this away for later


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/point.coffee
+++ src/point.coffee
@@ -601,10 +601,42 @@
   @fieldOf Point
   @returns {Point} The point (0, 0)
   ###
-  Point.ZERO = Point()
+  Point.ZERO = Point(0, 0)
+
+  ###*
+  @name LEFT
+  @fieldOf Point
+  @returns {Point} The point (-1, 0)
+  ###
+  Point.LEFT = Point(-1, 0)
+
+  ###*
+  @name RIGHT
+  @fieldOf Point
+  @returns {Point} The point (1, 0)
+  ###
+  Point.RIGHT = Point(1, 0)
+
+  ###*
+  @name UP
+  @fieldOf Point
+  @returns {Point} The point (0, -1)
+  ###
+  Point.UP = Point(0, -1)
+
+  ###*
+  @name DOWN
+  @fieldOf Point
+  @returns {Point} The point (0, 1)
+  ###
+  Point.DOWN = Point(0, 1)

   if Object.freeze
     Object.freeze Point.ZERO
+    Object.freeze Point.LEFT
+    Object.freeze Point.RIGHT
+    Object.freeze Point.UP
+    Object.freeze Point.DOWN

   (exports ? this)["Point"] = Point
 )()


point

point

--- src/point.coffee
+++ src/point.coffee
@@ -5,7 +5,7 @@
   @name Point
   @param {Number} [x]
   @param {Number} [y]
-  @constructor ASFsdfASD
+  @constructor
   ###
   Point = (x, y) ->
     __proto__: Point::


Fix popup displaying.

Fix popup displaying.

--- src/popup.coffee
+++ src/popup.coffee
@@ -2,17 +2,11 @@
 .module('angular-w')
 .directive('wPopup', ['$rootScope', "$compile", 'wPopupManager', ($rootScope, $compile, popupManager)->
   restrict: 'E'
-  scope: {}
   compile: (tElement, tAttrs)->
-    content = "
#{tElement.html().trim()}
"
-    tElement.empty()
-    popupLinkFn = $compile(angular.element(content))
-    popupManager.add tAttrs.name, popupLinkFn
-    $rootScope.popup = popupManager unless $rootScope.popup?
-
-    return (scope, element, attrs)->
-      element.remove()
-]).factory('wPopupManager', ['$document', '$rootScope', ($document, $rootScope)->
+    content = "#{tElement.html().trim()}"
+    tElement.remove()
+    popupManager.add tAttrs.name, content
+]).factory('wPopupManager', ['$document', '$compile', '$rootScope', ($document, $compile, $rootScope)->
   attachTo = undefined
   currentPopup = undefined
   documentClickBind = (event)->
@@ -20,24 +14,27 @@
       $rootScope.$apply ->
         $rootScope.popup.hide()

-  popups: {}
-  add: (name, popup)->
-    @popups[name] = popup
-  show: (name, target)->
-    popupLinkFn = @popups[name]
-    return unless popupLinkFn
-    attachTo = target
-    attachToElement = angular.element(attachTo)
-    attachToScope = attachToElement.scope()
-    currentPopup = popupLinkFn(attachToScope)
-    currentPopup.bind 'click', (event)->
-      event.preventDefault()
-      event.stopPropagation()
-    attachToElement.after(currentPopup)
-    $document.bind('click', documentClickBind)
-    return
-  hide: ->
-    $document.unbind('click', documentClickBind)
-    currentPopup?.remove()
-    return
+  popupManager =
+    popups: {}
+    add: (name, popup)->
+      @popups[name] = popup
+    show: (name, target)->
+      popupContent = @popups[name]
+      return unless popupContent?
+      attachTo = target
+      attachToElement = angular.element(attachTo)
+      attachToScope = attachToElement.scope()
+      popupElement = angular.element("
#{popupContent}
")
+      currentPopup = $compile(popupElement)(attachToScope)
+      currentPopup.bind 'click', (event)->
+        event.preventDefault()
+        event.stopPropagation()
+      attachToElement.after(currentPopup)
+      $document.bind('click', documentClickBind)
+      return
+    hide: ->
+      $document.unbind('click', documentClickBind)
+      currentPopup?.remove()
+      return
+  $rootScope.popup = popupManager
 ])


Fix the Hipchat adapter -- inheriting from Robot doesn't work

--- src/adapters/hipchat.coffee
+++ src/adapters/hipchat.coffee
@@ -2,7 +2,7 @@
 HTTPS        = require "https"
 Wobot        = require("wobot").Bot

-class HipChat extends Robot
+class HipChat extends Robot.Adapter
   send: (user, strings...) ->
     for str in strings
       @bot.message user.reply_to, str


Regex futzing.

Regex futzing.

--- src/quote.coffee
+++ src/quote.coffee
@@ -92,7 +92,7 @@
       else
         msg.send "#{quotes.length} quotes loaded successfully."

-  robot.respond /create quote: (.*)/i, (msg) ->
+  robot.respond /create quote: ([\s\S]*)/i, (msg) ->
     unless robot.auth.hasRole(msg.message.user, CREATOR_ROLE)
       msg.reply [
         "You can't do that! You're not a *#{CREATOR_ROLE}*."


cleaning up array constructions

cleaning up array constructions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -385,3 +385,4 @@
 Array::sum = ->
   this.inject 0, (sum, n) ->
     sum + n
+


setup: Add missing error handling from discoverParticipantQueues

--- src/setup.coffee
+++ src/setup.coffee
@@ -254,6 +254,7 @@
     # wait for FBP discovery messsages, use queues from there
     getBindings = (broker, graph, cb) ->
       discoverParticipantQueues options.broker, graph, (err, defs) ->
+        return cb err if err
         #console.log 'got defs', definitions
         bindings = bindingsFromDefinitions graph, defs
         return cb null, bindings


:lipstick:

:lipstick:

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -55,15 +55,15 @@

   setupNodePath: ->
     resourcePaths = [
-      'src/stdlib',
-      'src/app',
-      'src/packages',
-      'src',
-      'vendor',
-      'static',
-      'node_modules',
-      'spec',
-      '',
+      'src/stdlib'
+      'src/app'
+      'src/packages'
+      'src'
+      'vendor'
+      'static'
+      'node_modules'
+      'spec'
+      ''
     ]

     resourcePaths.push path.join(@getHomeDir(), '.atom', 'packages')


:lipstick: Inline parsed url

:lipstick: Inline parsed url

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -263,8 +263,6 @@
   #    + devMode:
   #      Boolean to control the opened window's dev mode.
   openUrl: ({urlToOpen, devMode}) ->
-    parsedUrl = url.parse(urlToOpen)
-    packageName = parsedUrl.host
     unless @packages?
       PackageManager = require './package-manager'
       fsUtils = require './fs-utils'
@@ -274,6 +272,7 @@
         resourcePath: @resourcePath
         urlToOpen: urlToOpen

+    packageName = url.parse(urlToOpen).host
     pack = _.find @packages.getAvailablePackageMetadata(), ({name}) -> name is packageName
     if pack?
       if pack.urlMain


updated deprecated functions

updated deprecated functions

--- src/skype.coffee
+++ src/skype.coffee
@@ -24,10 +24,10 @@
     @skype = require('child_process').spawn(py, [pyScriptPath])
     @skype.stdout.on 'data', (data) =>
         decoded = JSON.parse(data.toString())
-        user = self.userForName decoded.user
+        user = @robot.brain.userForName decoded.user
         unless user?
             id = (new Date().getTime() / 1000).toString().replace('.','')
-            user = self.userForId id
+            user = @robot.brain.userForId id
             user.name = decoded.user
         user.room = decoded.room
         return unless decoded.message


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/state.coffee
+++ src/state.coffee
@@ -9,6 +9,12 @@
     defaultState: I.initialState

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

   self


Removes useless .call(this)

Removes useless .call(this)

--- src/client/component.coffee
+++ src/client/component.coffee
@@ -163,7 +163,7 @@
       # afterRender
       # Start nested components...
       render: (tpl, data)=>
-        ctxPromise = @buildContext.call(@)
+        ctxPromise = @buildContext()
         ctxPromise.fail (err)->
           console.error("Error fetching Datasources ", err.message, err)
         ctxPromise.then (ctx)=>


createFromObject(obj, baseModel) set obj over baseModel

--- src/lib/base-factory.coffee
+++ src/lib/base-factory.coffee
@@ -32,7 +32,6 @@
     constructor

     @constructor
-    @return
     ###
     constructor: ->

@@ -70,9 +69,10 @@
     @method createFromObject
     @public
     @param {Object} obj
+    @param {BaseModel} baseModel fallback properties
     @return {BaseModel} model
     ###
-    createFromObject: (obj) ->
+    createFromObject: (obj, baseModel) ->

         obj = @beforeCreateFromObject obj

@@ -82,7 +82,6 @@

         ModelClass = @getModelClass()
         model = new ModelClass()
-        model[prop] = undefined for prop of ModelClass.properties

         for own prop, value of obj

@@ -92,6 +91,12 @@
             else
                 model[prop] = @modifyValueByPropName(prop, value)

+        if baseModel
+            for prop of ModelClass.properties
+                if not model[prop]?
+                    model[prop] ?= baseModel[prop]
+        else
+            model[prop] ?= undefined for prop of ModelClass.properties

         model = @afterCreateModel model


Closes #32. Added *mail.google.com* to the default black list

--- extension/packages/prefs.coffee
+++ extension/packages/prefs.coffee
@@ -11,7 +11,7 @@
   disabled:     false
   scroll_step:  60
   scroll_time:  100
-  black_list:   ''
+  black_list:   '*mail.google.com*'

 # Get Firefox preference value of type specified in `PREFS`
 getFFPref = do ->


Toggle -> cycle

Toggle -> cycle

--- src/utils.coffee
+++ src/utils.coffee
@@ -1,4 +1,5 @@
 _ = require "underscore"
+{config} = require "./config"

 Function::define = (prop, desc) ->
 	Object.defineProperty @prototype, prop, desc
@@ -101,13 +102,13 @@
 #
 # Examples
 #
-#		opacityToggle = exports.toggle(0, 1)
+#		opacityToggle = exports.cycle(0, 1)
 #		dropdown.on 'click', -> dropdown.opacity = opacityToggle()
 #
 # Returns a Function
 #

-exports.toggle = ->
+exports.cycle = ->

 	if _.isArray arguments[0]
 		args = arguments[0]
@@ -120,6 +121,8 @@
 		curr = 0 if curr >= args.length
 		return args[curr]

+# Backwards compatibility
+exports.toggle = exports.cycle

 # Public: Returns a String containing a color in rgba(...) format.
 #
@@ -143,7 +146,7 @@
 # Returns a Timer
 #
 exports.delay = (time, f) ->
-	timer = setTimeout f, time
+	timer = setTimeout f, time * config.timeSpeedFactor
 	# window._delayTimers ?= []
 	# window._delayTimers.push timer
 	return timer
@@ -158,7 +161,7 @@
 # Returns a Timer
 #
 exports.interval = (time, f) ->
-	timer = setInterval f, time
+	timer = setInterval f, time * config.timeSpeedFactor
 	window._delayIntervals ?= []
 	window._delayIntervals.push timer
 	return timer
@@ -295,7 +298,6 @@
 	d = Math.pow 10, decimals
 	Math.round(value * d) / d

-
 # Public: Returns a Boolean indicating wether the current browser uses the
 # Webkit engine.
 #
@@ -318,3 +320,35 @@
 		navigator.userAgent.toLowerCase()

+
+
+
+# __domComplete = []
+#
+# document.onreadystatechange = (event) =>
+# 	console.log "onreadystatechange", document.readyState
+#
+# 	utils.delay 100, ->
+#
+# 		if document.readyState is "complete"
+# 			while __domComplete.length
+# 				__domComplete.shift()()
+#
+# 		# __domComplete.map (f) ->
+# 		# 	console.log f
+# 		# 	f()
+# 		# __domComplete = []
+#
+# exports.domComplete = (f) ->
+# 	if document.readyState is "complete"
+# 		f()
+# 	else
+# 		__domComplete.push f
+#
+# exports.domCompleteCancel = (f) ->
+# 	__domComplete = _.whithout __domComplete, f
+
+
+
+
+


Added dummies for (set|get)ThumbnailsSettings

--- src/utils.coffee
+++ src/utils.coffee
@@ -1,4 +1,4 @@
-dummy = pushState = () ->
+setThumbnailsSettings = getThumbnailsSettings = pushState = dummy = () ->

 merge = (o1, o2) ->
   for own key, value of o2


operations: fix CreateNode config

operations: fix CreateNode config

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -385,6 +385,7 @@
                 # Already exists
                 cb2 new errors.Conflict("Node #{@req.node} already exists")
         , (cb2) =>
+            config = @req.config
             # Pick config defaults
             if isTopic
                 defaults = defaultConfiguration nodeUser
@@ -392,17 +393,17 @@
                 defaults = defaultTopicConfiguration nodeUser
             defaults = defaults[getNodeType @req.nodenode]
             if defaults
-                # Mix into @config
+                # Mix into config
                 for own key, value of defaults
-                    unless @config
-                        @config = {}
+                    unless config
+                        config = {}
                     # Don't overwrite existing
-                    unless @config.hasOwnProperty key
-                        @config[key] = value
-
-            if @config?
+                    unless config.hasOwnProperty key
+                        config[key] = value
+
+            if config?
                 # Set
-                t.setConfig @req.node, @config, cb2
+                t.setConfig @req.node, config, cb2
             else
                 cb2 null
         , (cb2) =>


Make subgraphs also call normal component start

--- src/components/Graph.coffee
+++ src/components/Graph.coffee
@@ -62,6 +62,7 @@
     , true

   start: (callback) ->
+    return if @started
     unless callback
       callback = ->
     unless @isReady()
@@ -72,7 +73,7 @@
     return callback null unless @network
     @network.start (err) =>
       return callback err if err
-      @started = true
+      super()

   checkComponent: (name, process) ->
     unless process.component.isReady()


Fix loading from compiled version

Fix loading from compiled version

--- src/components/Graph.coffee
+++ src/components/Graph.coffee
@@ -1,7 +1,4 @@
-if typeof process isnt 'undefined' and process.execPath and process.execPath.indexOf('node') isnt -1
-  noflo = require "../../lib/NoFlo"
-else
-  noflo = require '../lib/NoFlo'
+noflo = require "../lib/NoFlo"

 class Graph extends noflo.Component
   constructor: (@metadata) ->


Triggering through api can set metaKey on non-macs

--- src/modules/keyboard.coffee
+++ src/modules/keyboard.coffee
@@ -68,8 +68,8 @@
     dom(@quill.root).on('keydown', (event) =>
       prevent = false
       _.each(@hotkeys[event.which], (hotkey) =>
-        return if dom.isMac() and !!hotkey.metaKey != !!event.metaKey
-        return if !dom.isMac() and !!hotkey.metaKey != !!event.ctrlKey
+        metaKey = if dom.isMac() then event.metaKey else event.metaKey or event.ctrlKey
+        return if !!hotkey.metaKey != !!metaKey
         return if !!hotkey.shiftKey != !!event.shiftKey
         return if !!hotkey.altKey != !!event.altKey
         prevent = hotkey.callback(@quill.getSelection()) == false or prevent


Get rid of command buffer

Get rid of command buffer

--- src/config/inspector.coffee
+++ src/config/inspector.coffee
@@ -81,7 +81,6 @@

     program = programs.get('program')
     my.assert program, "no current program"
-    world.replace_child display_program(EXECUTING, program)

     strategy = world.find_child(STRATEGY)
     strategy.authority = world.make_world ROW_AUTHORITY
@@ -122,7 +121,6 @@
       ]
     }
     COMMANDS
-    EXECUTING
     {_LABEL: STRATEGY}
   ]


remove API() in def of available_upgrades

--- src/smc-util/message.coffee
+++ src/smc-util/message.coffee
@@ -2484,19 +2484,9 @@

 # client <-- hub

-API message
-    event : 'available_upgrades'
-    fields :
-        id:
-           init  : undefined
-           desc  : 'A unique UUID for the query'
-        total :
-            init : required
-            desc : 'info about the total upgrades that the user has purchased'
-        excess :
-            init : required
-            desc : 'info about upgrades where the total allocated exceeds what user has purchased'
-        available :
-            init : required
-            desc : 'how much of each purchased upgrade is available'
-    desc : 'See https://github.com/sagemathinc/cocalc/blob/master/src/smc-util/upgrade-spec.coffee for units'
+message
+    event      : 'available_upgrades'
+    id         : undefined
+    total      : required  # total upgrades the user has purchased
+    excess     : required  # upgrades where the total allocated exceeds what user has purchased
+    available  : required  # how much of each purchased upgrade is available


fix al-select

fix al-select

--- src/directive/select.coffee
+++ src/directive/select.coffee
@@ -84,22 +84,31 @@
         cd = env.changeDetector.new()  # child CD
         env.stopBinding = true

-        cd.$select = mapper = new Mapper
+        cd.$select =
+            mapper: mapper = new Mapper

-        alight.bind cd, element,
-            skip_attr: env.skippedAttr()
+        # child-options were changed
+        lastValue = null
+        cd.$select.change = ->
+            setValueOfElement lastValue

-        watch = cd.watch key, (value) ->
+        setValueOfElement = (value) ->
             id = mapper.getId value
             if id
                 element.value = id
             else
                 element.selectedIndex = -1
-        cd.scan()
+
+        watch = cd.watch key, (value) ->
+            lastValue = value
+            setValueOfElement value
+
+        alight.bind cd, element,
+            skip_attr: env.skippedAttr()

         onChangeDOM = (event) ->
-            item = mapper.getItem event.target.value
-            cd.setValue key, item
+            lastValue = mapper.getItem event.target.value
+            cd.setValue key, lastValue
             cd.scan
                 skipWatch: watch

@@ -112,17 +121,20 @@
     alight.d.al.option = (scope, element, key, env) ->
         cd = step = env.changeDetector
         for i in [0..4]
-            mapper = step.$select
-            if mapper
+            select = step.$select
+            if select
                 break
             step = step.parent or {}
-        if not mapper
+        if not select
             alight.exceptionHandler '', 'Error in al-option - al-select is not found',
                 cd: cd
                 scope: cd.scope
                 element: element
                 value: key
             return
+
+        mapper = select.mapper
+        select.change()

         id = null
         cd.watch key, (item) ->
@@ -140,4 +152,5 @@

         cd.watch '$destroy', ->
             mapper.release id
+            select.change()
         return


Don't use guard clauses in short measurement methods

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -854,20 +854,16 @@
   measureLineHeightAndDefaultCharWidth: ->
     if @isVisible()
       @measureLineHeightAndDefaultCharWidthWhenShown = false
+      @refs.lines.measureLineHeightAndDefaultCharWidth()
     else
       @measureLineHeightAndDefaultCharWidthWhenShown = true
-      return
-
-    @refs.lines.measureLineHeightAndDefaultCharWidth()

   remeasureCharacterWidths: ->
     if @isVisible()
       @remeasureCharacterWidthsWhenShown = false
+      @refs.lines.remeasureCharacterWidths()
     else
       @remeasureCharacterWidthsWhenShown = true
-      return
-
-    @refs.lines.remeasureCharacterWidths()

   measureScrollbars: ->
     @measureScrollbarsWhenShown = false


Eliminate overlayer by preserving lines during mousewheel events

Previously, the overlayer served as a permanent target for mousewheel
events that would never be removed by scrolling. This is because the
velocity scrolling effect on a trackpad is implemented by repeating
events on the original mousewheel event target. If this target is
removed, the events stop repeating and the velocity effect is ruined.
Now we refrain from removing any lines until mousewheel events stop
flowing.

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -1,5 +1,6 @@
 {React, div, span} = require 'reactionary'
 {$$} = require 'space-pen'
+{debounce} = require 'underscore-plus'

 InputComponent = require './input-component'
 SelectionComponent = require './selection-component'
@@ -37,20 +38,13 @@
     WebkitTransform = "translateY(#{-editor.getScrollTop()}px)"

     div className: 'scrollable-content', style: {height, WebkitTransform},
-      @renderOverlayer()
+      @renderCursors()
       @renderVisibleLines()
       @renderUnderlayer()

-  renderOverlayer: ->
-    {editor} = @props
-
-    div className: 'overlayer',
-      for selection in editor.getSelections() when editor.selectionIntersectsVisibleRowRange(selection)
-        CursorComponent(cursor: selection.cursor)
-
   renderVisibleLines: ->
     {editor} = @props
-    [startRow, endRow] = editor.getVisibleRowRange()
+    [startRow, endRow] = @getVisibleRowRange()
     lineHeightInPixels = editor.getLineHeight()
     precedingHeight = startRow * lineHeightInPixels
     followingHeight = (editor.getScreenLineCount() - endRow) * lineHeightInPixels
@@ -62,12 +56,25 @@
       div className: 'spacer', key: 'bottom-spacer', style: {height: followingHeight}
     ]

+  renderCursors: ->
+    {editor} = @props
+
+    for selection in editor.getSelections() when editor.selectionIntersectsVisibleRowRange(selection)
+      CursorComponent(cursor: selection.cursor)
+
   renderUnderlayer: ->
     {editor} = @props

     div className: 'underlayer',
       for selection in editor.getSelections() when editor.selectionIntersectsVisibleRowRange(selection)
         SelectionComponent({selection})
+
+  getVisibleRowRange: ->
+    visibleRowRange = @props.editor.getVisibleRowRange()
+    if @visibleRowOverrides?
+      visibleRowRange[0] = Math.min(visibleRowRange[0], @visibleRowOverrides[0])
+      visibleRowRange[1] = Math.max(visibleRowRange[1], @visibleRowOverrides[1])
+    visibleRowRange

   getInitialState: -> {}

@@ -246,8 +253,21 @@
         @pendingScrollTop = null

   onMousewheel: (event) ->
+    # To preserve velocity scrolling, delay removal of the event's target until
+    # after mousewheel events stop being fired. Removing the target before then
+    # will cause scrolling to stop suddenly.
+    @visibleRowOverrides = @getVisibleRowRange()
+    @clearVisibleRowOverridesAfterDelay ?= debounce(@clearVisibleRowOverrides, 40)
+    @clearVisibleRowOverridesAfterDelay()
+
     @refs.verticalScrollbar.getDOMNode().scrollTop -= event.wheelDeltaY
     event.preventDefault()
+
+  clearVisibleRowOverrides: ->
+    @visibleRowOverrides = null
+    @forceUpdate()
+
+  clearVisibleRowOverridesAfterDelay: null

   onOverflowChanged: ->
     @props.editor.setHeight(@refs.scrollView.getDOMNode().clientHeight)
@@ -300,7 +320,7 @@
     {lineHeightInPixels, charWidth}

   measureNewLines: ->
-    [visibleStartRow, visibleEndRow] = @props.editor.getVisibleRowRange()
+    [visibleStartRow, visibleEndRow] = @getVisibleRowRange()
     linesNode = @refs.lines.getDOMNode()

     for tokenizedLine, i in @props.editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)


Use setImmediate to batch all editor updates, even in animation frames

When I was using nextTick to batch updates, there were issues with
flicker. So I was attempting to always update synchronously in animation
frames, which was complicated. setImmediate doesn't cause the flicker
however, so I'm able to remove the special logic for sync updates in
animation frames and just use it across the board.

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -25,8 +25,6 @@
   pendingScrollTop: null
   pendingScrollLeft: null
   selectOnMouseMove: false
-  updatesPaused: false
-  updateRequestedWhilePaused: false
   updateRequested: false
   cursorsMoved: false
   selectionChanged: false
@@ -205,23 +203,13 @@
     @remeasureCharacterWidthsIfNeeded(prevState)

   requestUpdate: ->
-    if @updatesPaused
-      @updateRequestedWhilePaused = true
-    else
-      if @performSyncUpdates ? EditorComponent.performSyncUpdates
-        @forceUpdate()
-      else unless @updateRequested
-        @updateRequested = true
-        process.nextTick =>
-          @updateRequested = false
-          @forceUpdate() if @isMounted()
-
-  requestAnimationFrame: (fn) ->
-    requestAnimationFrame =>
-      @updatesPaused = true
-      fn()
-      @updatesPaused = false
-      @forceUpdate() if @updateRequestedWhilePaused
+    if @performSyncUpdates ? EditorComponent.performSyncUpdates
+      @forceUpdate()
+    else unless @updateRequested
+      @updateRequested = true
+      setImmediate =>
+        @updateRequested = false
+        @forceUpdate() if @isMounted()

   getRenderedRowRange: ->
     {editor, lineOverdrawMargin} = @props
@@ -522,7 +510,7 @@
     animationFramePending = @pendingScrollTop?
     @pendingScrollTop = scrollTop
     unless animationFramePending
-      @requestAnimationFrame =>
+      requestAnimationFrame =>
         @props.editor.setScrollTop(@pendingScrollTop)

   onHorizontalScroll: (scrollLeft) ->
@@ -533,7 +521,7 @@
     animationFramePending = @pendingScrollLeft?
     @pendingScrollLeft = scrollLeft
     unless animationFramePending
-      @requestAnimationFrame =>
+      requestAnimationFrame =>
         @props.editor.setScrollLeft(@pendingScrollLeft)
         @pendingScrollLeft = null

@@ -554,7 +542,7 @@
       @clearMouseWheelScreenRowAfterDelay()

     unless animationFramePending
-      @requestAnimationFrame =>
+      requestAnimationFrame =>
         {editor} = @props
         editor.setScrollTop(editor.getScrollTop() + @pendingVerticalScrollDelta)
         editor.setScrollLeft(editor.getScrollLeft() + @pendingHorizontalScrollDelta)


Adjusting developer tools message based on app width and height

--- src/engine_developer.coffee
+++ src/engine_developer.coffee
@@ -10,6 +10,15 @@
   @param {Object} self Reference to the engine
   ###
   Engine.Developer = (I, self) ->
+    screenWidth = App?.width or 480
+    screenHeight = App?.height or 320
+    margin = 10
+    boxWidth = 240
+    boxHeight = 60
+    textStart = screenWidth - boxWidth + margin
+    font = "bold 9pt arial"
+    lineHeight = 16
+
     self.bind "draw", (canvas) ->
       if I.paused
         #TODO: Move this into the debug draw method of the objects themselves
@@ -18,12 +27,14 @@
             canvas.fillColor 'rgba(255, 0, 0, 0.5)'
             canvas.fillRect(object.bounds().x, object.bounds().y, object.bounds().width, object.bounds().height)

+        #TODO Displaying text shouldn't be such a damn ordeal
+        canvas.font(font)
         canvas.fillColor 'rgba(0, 0, 0, 0.5)'
-        canvas.fillRect(430, 10, 200, 60)
+        canvas.fillRect(screenWidth - boxWidth, 0, boxWidth, boxHeight)
         canvas.fillColor '#fff'
-        canvas.fillText("Developer Mode. Press Esc to resume", 440, 25)
-        canvas.fillText("Shift+Left click to add boxes", 440, 43)
-        canvas.fillText("Right click red boxes to edit properties", 440, 60)
+        canvas.fillText("Developer Mode. Press Esc to resume", textStart, margin + 5)
+        canvas.fillText("Shift+Left click to add boxes", textStart, margin + 5 + lineHeight)
+        canvas.fillText("Right click red boxes to edit properties", textStart, margin + 5 + 2 * lineHeight)

     self.bind "init", ->
       window.updateObjectProperties = (newProperties) ->


:memo: Add examples to `Path` documentation

--- src/geom/mixins/path.coffee
+++ src/geom/mixins/path.coffee
@@ -43,6 +43,8 @@
   # Returns the coordinates on the path at the given
   # {Number} position.
   #
+  # <script>drawGeometry('geometry', {paths: [0.1, 0.45, 0.73]})</script>
+  #
   # pos - The {Number} between `0` and `1` at which get the path coordinates.
   # pathBasedOnLength - A {Boolean} of whether the position on the path
   #                     consider the length of the path segments or not.
@@ -69,6 +71,8 @@
   # Returns the orientation on the path at the given
   # {Number} position.
   #
+  # <script>drawGeometry('geometry', {paths: [0.1, 0.45, 0.73]})</script>
+  #
   # pos - The {Number} between `0` and `1` at which get the path coordinates.
   # pathBasedOnLength - A {Boolean} of whether the position on the path
   #                     consider the length of the path segments or not.
@@ -87,6 +91,8 @@
     return d.angle()

   # Returns the tangent vector at the given {Number} position.
+  #
+  # <script>drawGeometry('geometry', {paths: [0.1, 0.45, 0.73]})</script>
   #
   # pos - The {Number} between `0` and `1` at which get the path coordinates.
   # accuracy - A {Number} giving the distance, relatively to the path length,


size frame whenever content is set.

size frame whenever content is set.

--- js-library/app/js/jail_iframe/classes/control_iframe.coffee
+++ js-library/app/js/jail_iframe/classes/control_iframe.coffee
@@ -19,6 +19,8 @@
     while bodyEl.firstChild
       bodyEl.removeChild(bodyEl.firstChild)
     bodyEl.appendChild(contentNode)
+    @resizeFrame()
+
   resizeFrame: ->
     @el.style.width = @doc.body.clientWidth  + 'px'
     @el.style.height = @doc.body.clientHeight + 'px'


Ton of scrollview fixes

Ton of scrollview fixes

--- src/views/scrollview.coffee
+++ src/views/scrollview.coffee
@@ -14,7 +14,7 @@
 		# startY = startTopScroll = deltaY = undefined
 		#
 		# @on "touchstart", =>
-		# 	el = @layer.element
+		# 	el = @_element
 		# 	startY = event.touches[0].pageY
 		# 	startTopScroll = el.scrollTop
 		# 	if startTopScroll <= 0
@@ -36,29 +36,29 @@
 			@style["overflow-x"] = if value then "scroll" else "hidden"

 	scrollToTop: ->
-		@layer.element.scrollTop = 0
+		@_element.scrollTop = 0

 	scrollToBottom: ->
 		setTimeout =>
-			@scrollPoint = @layer.element.scrollHeight - @frame.height
+			@scrollPoint = @_element.scrollHeight - @frame.height
 		, 0

 	@define "scrollPoint"
 		get: ->
-			@layer.element.scrollTop
+			@_element.scrollTop
 		set: (value) ->
-			@layer.element.scrollTop = value
+			@_element.scrollTop = value

 	@define "scrollFrame"
 		get: ->
 			return new Frame {
-				x: @layer.element.scrollLeft
-				y: @layer.element.scrollTop
+				x: @_element.scrollLeft
+				y: @_element.scrollTop
 				width: @width
 				height: @height
 			}
 		set: (frame) ->
-			@layer.element.scrollLeft = frame.x
-			@layer.element.scrollTop = frame.y
-			# @layer.element.innerWidth = frame.width
-			# @layer.element.innerHeight = frame.height+			@_element.scrollLeft = frame.x
+			@_element.scrollTop = frame.y
+			# @_element.innerWidth = frame.width
+			# @_element.innerHeight = frame.height


Add achievement placeholder methods

Add achievement placeholder methods

--- src/achievements.coffee
+++ src/achievements.coffee
@@ -1,4 +1,11 @@
 class Game.Achievements extends Game.TwoWay
   constructor: (@state) ->
     super
-    @achievements = {}+    @achievements = {}
+    @listen(@state, @onEvent)
+  onEvent: (e) ->
+    console.log e.type, e.data, new Date().getTime()
+  hasAchieved: (name) ->
+    false
+  addAchievement: (name, requirements) ->
+    return


swtiched order that announcements are displayed for hachrazat rosh chodesh, to reflect the order they are announced

--- js/mincha.coffee
+++ js/mincha.coffee
@@ -106,7 +106,7 @@
     @rabbenu_tam_schedule()
     if @hebrew_date.isShabbatMevarechim()
       nextMonth = new HachrazatRoshChodesh(@hebrew_date)
-      $(".#{@chag()}.molad").removeClass('hidden').find('td').html("#{nextMonth.sephardicAnnouncement()}
#{nextMonth.moladAnnouncement()}")
+      $(".#{@chag()}.molad").removeClass('hidden').find('td').html("#{nextMonth.moladAnnouncement()}
#{nextMonth.sephardicAnnouncement()}")
     $(".#{@chag()}.molad").removeClass('hidden').find('td').html("#{(new HachrazatTaanit(@hebrew_date)).announcement()}") if @hebrew_date.isHachrazatTaanit()
   yom_kippur_schedule: ->
     @set_date()


Fixed bug where addObject task doesn't return response

--- tasks.coffee
+++ tasks.coffee
@@ -221,47 +221,50 @@
       callback(null, STATUS_STARTED, {methodRequest : {type : 'AddObject', objectName : "#{task.objectName}."}})
       return

-  if task.subtask.name is 'getParameterNames'
-    this.getParameterNames(task.subtask, methodResponse, (err, status, cwmpResponse, deviceUpdates) =>
-      common.extend(allDeviceUpdates, deviceUpdates)
-      if deviceUpdates and deviceUpdates.parameterNames
-        for p in deviceUpdates.parameterNames
-          task.parameterNames.push(p[0]) if not common.endsWith(p[0], '.')
-
-      if status is STATUS_FINISHED
-        task.subtask = {name : 'getParameterValues', parameterNames : task.parameterNames}
-      else if status is STATUS_STARTED
-        callback(err, STATUS_STARTED, cwmpResponse, allDeviceUpdates)
-      else
-        throw Error('Unexpected subtask status')
-    )
-
-  if task.subtask.name is 'getParameterValues'
-    this.getParameterValues(task.subtask, methodResponse, (err, status, cwmpResponse, deviceUpdates) =>
-      common.extend(allDeviceUpdates, deviceUpdates)
-      if deviceUpdates and deviceUpdates.parameterValues
-        for p1 in deviceUpdates.parameterValues
-          for p2 in task.parameterValues
-            if common.endsWith(p1[0], ".#{p2[0]}")
-              t = if p2[2] then p2[2] else p1[2]
-              v = common.matchType(p1[1], p2[1])
-              # TODO only include if writable
-              task.appliedParameterValues.push([p1[0], v, t])
-
-      if methodResponse.faultcode?
-        # Ignore GetParameterValues errors. A workaround for the crappy Seewon devices.
-        methodResponse = {parameterList : {}}
-      if status is STATUS_FINISHED and task.appliedParameterValues.length > 0
-        task.subtask = {name : 'setParameterValues', parameterValues : task.appliedParameterValues}
-      else
-        callback(err, status, cwmpResponse, allDeviceUpdates)
-    )
-
-  if task.subtask.name is 'setParameterValues'
-    this.setParameterValues(task.subtask, methodResponse, (err, status, cwmpResponse, deviceUpdates) =>
-      common.extend(allDeviceUpdates, deviceUpdates)
-      callback(err, status, cwmpResponse, allDeviceUpdates)
-    )
+  subtask = () =>
+    switch task.subtask.name
+      when 'getParameterNames'
+        this.getParameterNames(task.subtask, methodResponse, (err, status, cwmpResponse, deviceUpdates) =>
+          common.extend(allDeviceUpdates, deviceUpdates)
+          if deviceUpdates and deviceUpdates.parameterNames
+            for p in deviceUpdates.parameterNames
+              task.parameterNames.push(p[0]) if not common.endsWith(p[0], '.')
+
+          if status is STATUS_FINISHED
+            task.subtask = {name : 'getParameterValues', parameterNames : task.parameterNames}
+            subtask()
+          else if status is STATUS_STARTED
+            callback(err, STATUS_STARTED, cwmpResponse, allDeviceUpdates)
+          else
+            throw Error('Unexpected subtask status')
+        )
+      when 'getParameterValues'
+        this.getParameterValues(task.subtask, methodResponse, (err, status, cwmpResponse, deviceUpdates) =>
+          common.extend(allDeviceUpdates, deviceUpdates)
+          if deviceUpdates and deviceUpdates.parameterValues
+            for p1 in deviceUpdates.parameterValues
+              for p2 in task.parameterValues
+                if common.endsWith(p1[0], ".#{p2[0]}")
+                  t = if p2[2] then p2[2] else p1[2]
+                  v = common.matchType(p1[1], p2[1])
+                  # TODO only include if writable
+                  task.appliedParameterValues.push([p1[0], v, t])
+
+          if methodResponse.faultcode?
+            # Ignore GetParameterValues errors. A workaround for the crappy Seewon devices.
+            methodResponse = {parameterList : {}}
+          if status is STATUS_FINISHED and task.appliedParameterValues.length > 0
+            task.subtask = {name : 'setParameterValues', parameterValues : task.appliedParameterValues}
+            subtask()
+          else
+            callback(err, status, cwmpResponse, allDeviceUpdates)
+        )
+      when 'setParameterValues'
+        this.setParameterValues(task.subtask, methodResponse, (err, status, cwmpResponse, deviceUpdates) =>
+          common.extend(allDeviceUpdates, deviceUpdates)
+          callback(err, status, cwmpResponse, allDeviceUpdates)
+        )
+  subtask()

 this.deleteObject = (task, methodResponse, callback) ->


use empty object for ranges if it doesn't exist

--- services/document-updater/app/coffee/DocumentManager.coffee
+++ services/document-updater/app/coffee/DocumentManager.coffee
@@ -30,7 +30,7 @@
 						return callback(error) if error?
 						RedisManager.setHistoryType doc_id, projectHistoryType, (error) ->
 							return callback(error) if error?
-							callback null, lines, version, ranges, pathname, projectHistoryId, null, false
+							callback null, lines, version, ranges || {}, pathname, projectHistoryId, null, false
 			else
 				callback null, lines, version, ranges, pathname, projectHistoryId, unflushedTime, true


remove unused code and fix flushing

remove unused code and fix flushing

--- services/web/app/coffee/Features/Compile/ClsiManager.coffee
+++ services/web/app/coffee/Features/Compile/ClsiManager.coffee
@@ -133,8 +133,13 @@
 					logger.log project_id: project_id, projectStateHash: projectStateHash, docs: docUpdaterDocs?, "checked project state"
 					# see if we can send an incremental update to the CLSI
 					if docUpdaterDocs? and options.syncType isnt "full"
-						Metrics.inc "compile-from-redis"
-						ClsiManager._buildRequestFromDocupdater project_id, options, project, projectStateHash, docUpdaterDocs, callback
+						# Workaround: for now, always flush project to mongo on compile
+						# until we have automatic periodic flushing on the docupdater
+						# side, to prevent documents staying in redis too long.
+						DocumentUpdaterHandler.flushProjectToMongo project_id, (error) ->
+							return callback(error) if error?
+							Metrics.inc "compile-from-redis"
+							ClsiManager._buildRequestFromDocupdater project_id, options, project, projectStateHash, docUpdaterDocs, callback
 					else
 						Metrics.inc "compile-from-mongo"
 						ClsiManager._buildRequestFromMongo project_id, options, project, projectStateHash, callback
@@ -172,16 +177,6 @@
 			options.syncType = "full"
 			options.syncState = projectStateHash
 			ClsiManager._finaliseRequest project_id, options, project, docs, files, callback
-
-	_getContentFromDocUpdater: (project_id, callback = (error, docs) ->) ->
-		# Workaround: for now, always flush project to mongo on compile
-		# until we have automatic periodic flushing on the docupdater
-		# side, to prevent documents staying in redis too long.
-		DocumentUpdaterHandler.flushProjectToMongo project_id, (error) ->
-			return callback(error) if error?
-			DocumentUpdaterHandler.getProjectDocs project_id, (error, docs) ->
-				return callback(error) if error?
-				callback(null, docs)

 	_getContentFromMongo: (project_id, callback = (error, docs, files) ->) ->
 		DocumentUpdaterHandler.flushProjectToMongo project_id, (error) ->


- added better log handling (minor fix to enable log filtering)

--- src/assetManager.coffee
+++ src/assetManager.coffee
@@ -9,11 +9,11 @@
   logger = Minilog('asset-manager')

 if detectEnv.isBrowser
-  Minilog.pipe(Minilog.backends.console.formatClean).pipe(Minilog.backends.console)
+  Minilog.pipe(Minilog.suggest).pipe(Minilog.backends.console.formatClean).pipe(Minilog.backends.console)
   logger = Minilog('asset-manager')

 if detectEnv.isNode
-  Minilog.pipe(Minilog.backends.console.formatColor).pipe(Minilog.backends.console)
+  Minilog.pipe(Minilog.suggest).pipe(Minilog.backends.console.formatColor).pipe(Minilog.backends.console)

 #TODO: add loading from git repos , with optional tag, commit, hash, branch etc (similar to npm dependencies)
 #TODO: perhaps we should seperate store TYPE (local , xhr, dropbox) from store NAME (the root uri ?)


Return empty list for currentLinePos if no points exist

--- player.coffee
+++ player.coffee
@@ -43,6 +43,7 @@
       @current_line.points.slice() # Copy array

     currentLinePos: ->
+      return [] unless @current_line.points.length > 0
       @current_line.points[@current_line.points.length - 1]

     addToLine: (pos) ->


Log to a file on windows, for now

Log to a file on windows, for now

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -12,7 +12,12 @@
 dialog = require 'dialog'

 console.log = (args...) ->
-  nslog(args.map((arg) -> JSON.stringify(arg)).join(" "))
+  # TODO: Make NSLog work as expected
+  output = args.map((arg) -> JSON.stringify(arg)).join(" ")
+  if process.platform == 'darwin'
+    nslog(output)
+  else
+    fs.writeFileSync('debug.log', output, flag: 'a')

 process.on 'uncaughtException', (error={}) ->
   nslog(error.message) if error.message?


Standardization

Standardization

--- memory_sync.coffee
+++ memory_sync.coffee
@@ -19,8 +19,6 @@
   initialize: ->
     return if @is_initialized; @is_initialized = true
     @model_type._schema.initialize()
-
-  sync: -> return @

   read: (model, options) ->
     options.success(if model.models then (json for id, json of @store) else @store[model.attributes.id])
@@ -63,6 +61,7 @@
   sync_fn = (method, model, options={}) ->
     sync['initialize']()
     return module.exports.apply(null, Array::slice.call(arguments, 1)) if method is 'createSync' # create a new sync
+    return sync if method is 'sync'
     sync[method].apply(sync, Array::slice.call(arguments, 1))

   require('./lib/model_extensions')(model_type, sync_fn) # mixin extensions


updated servers command to show folder/job paths and changed build command regex to accept /

--- src/jenkins-enhanced.coffee
+++ src/jenkins-enhanced.coffee
@@ -226,11 +226,19 @@

   servers: =>
     for server in @_servers
-      jobs = server.getJobs()
-      message = "- #{server.url}"
-      for job in jobs
-        message += "\n-- #{job.name}"
+      message = "#{server.url}\n"
+      message += @_serversSubFoldersAndJobs(server, server.getFolder())
       @send message
+
+  _serversSubFoldersAndJobs: (server, folder) =>
+    response = ""
+    # add the current folder's jobs first
+    for job in folder.getJobs(false)
+      response += "-".repeat(folder.depth+1)+" #{job.name}\n"
+    # add the sub folder's jobs and folders
+    for subFolder in folder.getFolders(false)
+      response += "-".repeat(folder.depth+1)+" #{subFolder.name}\n"+@_serversSubFoldersAndJobs(server, subFolder)
+    response

   _loadConfiguration: =>
     @_addServer process.env.HUBOT_JENKINS_URL, process.env.HUBOT_JENKINS_AUTH
@@ -406,7 +414,7 @@
     # make the default/root level folder
     rootFolder = server.getFolder()
     if rootFolder == null
-      rootFolder = new JenkinsFolder("Root", "", -1)
+      rootFolder = new JenkinsFolder("Root", "", 0)
       server.setFolder(rootFolder)

     @_addJobsToFoldersList(items, server, rootFolder)
@@ -722,7 +730,7 @@
   robot.respond /j(?:enkins)? aliases/i, id: 'jenkins.aliases', (msg) ->
     pluginFactory(msg).listAliases()

-  robot.respond /j(?:enkins)? build ([\w\.\-_ ]+)(, (.+))?/i, id: 'jenkins.build', (msg) ->
+  robot.respond /j(?:enkins)? build (.*)(, (.+))?/i, id: 'jenkins.build', (msg) ->
     pluginFactory(msg).build false

   robot.respond /j(?:enkins)? b (\d+)(, (.+))?/i, id: 'jenkins.b', (msg) ->


Added trace facility, stack overflow.

Added trace facility, stack overflow.

 * Secretly pass a trace method to see what's going on.
 * Track depth of descent, not terribly accurately, but enough to know when
   we're headed for trouble.
 * Call `nextTick` when depth is greater than 1024 to keep from blowing stack.

--- src/lib/async-object.coffee
+++ src/lib/async-object.coffee
@@ -1,44 +1,64 @@
-loadObject = (object, callback) ->
+descend = (trace, error, object, depth, userCallback, callback) ->
+  trace "descend", depth, error, object is null, userCallback is callback
+  if isNaN(depth)
+    process.exit 1
+  if userCallback is callback
+    callback(error, object)
+  else if depth > 1024
+    process.nextTick ->
+      descend(trace, error, object, 0, userCallback, callback)
+  else
+    callback(error, object, depth)
+
+loadObject = (trace, object, depth, userCallback, callback) ->
+  trace "loadObject", depth, object is null, userCallback is callback
   switch typeof object
     when "object"
       if not object?
-        callback(null, object)
+        descend(trace, null, object, depth + 1, userCallback, callback)
       else if Array.isArray(object)
-        next = (object, index) ->
+        next = (object, index, depth) ->
+          trace "arrayNext", depth, object is null, index, userCallback is callback
           if index is object.length
-            callback(null, object)
+            descend(trace, null, object, depth + 1, userCallback, callback)
           else
-            loadObject object[index], (error, data) ->
+            loadObject trace, object[index], depth + 1, userCallback, (error, data, depth) ->
               if error
-                callback(error)
+                descend(trace, error, null, depth + 1, userCallback, callback)
               else
-                loadObject data, (error, data) ->
+                loadObject trace, data, depth + 1, userCallback, (error, data, depth) ->
                   if error
-                    callback(error)
+                    descend(trace, error, null, depth + 1, userCallback, callback)
                   else
                     object[index] = data
-                    next(object, index + 1)
-        next(object, 0)
+                    next(object, index + 1, depth + 1)
+        next(object, 0, depth + 1)
       else
         keys = Object.keys(object)
-        next = (object, keys, index) ->
+        next = (object, keys, index, depth) ->
+          trace "objectNext", depth, object is null, keys[index], userCallback is callback
           if index is keys.length
-            callback(null, object)
+            descend(trace, null, object, depth + 1, userCallback, callback)
           else
-            loadObject object[keys[index]], (error, data) ->
+            loadObject trace, object[keys[index]], depth + 1, userCallback, (error, data, depth) ->
               if error
-                callback(error)
+                descend(trace, error, null, depth + 1, userCallback, callback)
               else
-                loadObject data, (error, data) ->
+                loadObject trace, data, depth + 1, userCallback, (error, data, depth) ->
                   if error
-                    callback(error)
+                    descend(trace, error, null, depth + 1, userCallback, callback)
                   else
                     object[keys[index]] = data
-                    next(object, keys, index + 1)
-        next(object, keys, 0)
+                    next(object, keys, index + 1, depth + 1)
+        next(object, keys, 0, depth + 1)
     when "function"
-      object(callback)
+      object (error, data) ->
+        trace "CALLBACK", depth, error, data is null
+        descend(trace, error, data, depth + 1, userCallback, callback)
     else
-      callback(null, object)
+      descend(trace, null, object, depth + 1, userCallback, callback)

-module.exports.loadObject = loadObject
+noop = ->
+
+module.exports.loadObject = (object, callback, trace) ->
+  loadObject(trace or noop, object, 0, callback, callback)


try something

try something

--- models/team.coffee
+++ models/team.coffee
@@ -113,9 +113,11 @@

 # class methods
 TeamSchema.static 'findBySlug', (slug, rest...) ->
+  slug = slug.trim() if slug
   Team.findOne { slug: slug }, rest...

 TeamSchema.static 'findByCode', (code, rest...) ->
+  code = code.trim() if code
   Team.findOne { code: code }, rest...

 TeamSchema.static 'canRegister', (regCode, next) ->


comments, whitespace

comments, whitespace

--- src/coffee/tools.coffee
+++ src/coffee/tools.coffee
@@ -39,13 +39,12 @@
         e.preventDefault()
         e.stopPropagation())

-
     $(document).bind('keydown', (e) =>
       if e[@options.keyName]
         @_start_drag()
+      #disable the tool when ESC is pressed
       if e.keyCode == 27
         eventSink.trigger("clear_active_tool"))
-

     $(document).bind('keyup', (e) =>
       if not e[@options.keyName]
@@ -54,13 +53,13 @@
     @plotview.main_can_wrapper.bind('mousedown', (e) =>
       if @button_activated
         @_start_drag()
-        return false
-    )
+        return false)
+
     @plotview.main_can_wrapper.bind('mouseup', (e) =>
       if @button_activated
         @_stop_drag()
-        return false
-    )
+        return false)
+
     @$tool_button = $("<button class='btn btn-small'> #{@options.buttonText} </button>")
     @plotview.$el.find('.button_bar').append(@$tool_button)

@@ -80,7 +79,6 @@
       @tool_active=true;
       @$tool_button.addClass('active'))
     return eventSink
-

   _start_drag : ->
     @eventSink.trigger("active_tool", @toolName)
@@ -125,6 +123,7 @@
         e.stopPropagation())

     $(document).bind('keydown', (e) =>
+      #disable the tool when ESC is pressed
       if e.keyCode == 27
         eventSink.trigger("clear_active_tool"))

@@ -212,8 +211,7 @@
       mapper = new Bokeh.LinearMapper({},
         data_range : datarange
         viewstate : @plot_view.viewstate
-        screendim : dim
-      )
+        screendim : dim)
       @mappers.push(mapper)
     return @mappers

@@ -247,8 +245,7 @@
         mapper.map_data(screenhigh)]
       mapper.data_range.set(
         start : start
-        end : end
-      )
+        end : end)
     return null

 class SelectionToolView extends ToolView
@@ -277,7 +274,6 @@
     SetBasepoint : "_start_selecting",
     deactivated : "_stop_selecting"}

-
   mouse_coords : (e, x, y) ->
     [x, y] = [@plot_view.viewstate.rxpos(x), @plot_view.viewstate.rypos(y)]
     return [x, y]
@@ -287,8 +283,8 @@
       start_x : null
       start_y : null
       current_x : null
-      current_y : null
-    )
+      current_y : null)
+
     @plotview.$el.removeClass("shading")
     for renderer in @mget_obj('renderers')
       renderer.get_obj('data_source').set('selecting', false)
@@ -423,8 +419,7 @@
       [start, end] = [mapper.map_data(start), mapper.map_data(end)]
       mapper.data_range.set(
         start : start
-        end : end
-      )
+        end : end)
     return null

 Bokeh.SelectionToolView = SelectionToolView


fix race condition when quickly scrolling

--- src/main/coffee/main.coffee
+++ src/main/coffee/main.coffee
@@ -278,31 +278,40 @@
         window.setTimeout this.importSingleFeed, 0

     fetchMorePosts: =>
+        errorWrapper = (type, desc) =>
+            this._enableFetch = true
+            this._apiError type, desc
+
+        successWrapper = (data) =>
+            if data.length > 0
+                this._enableFetch = true
+                this._postPage = this._postPage + 1
+                this._processFeedPosts data
+
         if this._enableFetch
+            this._enableFetch = false
             if this._fid > 0
-                NR.API.GetPostsForFeed this._fid, this._postPage, Date.parse(this.articleList.at(this.articleList.length - 1).article.pubDate), this.localSettings.showOnlyUnread, (data) =>
-                    if data.length == 0
-                        this._enableFetch = false
-                    else
-                        this._postPage = this._postPage + 1
-                        this._processFeedPosts data
-                , this._apiError
+                NR.API.GetPostsForFeed(
+                    this._fid,
+                    this._postPage,
+                    Date.parse(this.articleList.at(this.articleList.length - 1).article.pubDate),
+                    this.localSettings.showOnlyUnread,
+                    successWrapper,
+                    errorWrapper)
             else if this._savedPostsMode
-                NR.API.GetSavedPosts this._uid, this._postPage, Date.parse(this.articleList.at(this.articleList.length - 1).article.pubDate), (data) =>
-                    if data.length == 0
-                        this._enableFetch = false
-                    else
-                        this._postPage = this._postPage + 1
-                        this._processFeedPosts data
-                , this._apiError
+                NR.API.GetSavedPosts(
+                    this._uid,
+                    this._postPage,
+                    Date.parse(this.articleList.at(this.articleList.length - 1).article.pubDate),
+                    successWrapper,
+                    errorWrapper)
             else
-                NR.API.GetAllPosts this._postPage, Date.parse(this.articleList.at(this.articleList.length - 1).article.pubDate), this.localSettings.showOnlyUnread, (data) =>
-                    if data.length == 0
-                        this._enableFetch = false
-                    else
-                        this._postPage = this._postPage + 1
-                        this._processFeedPosts data
-                , this._apiError
+                NR.API.GetAllPosts(
+                    this._postPage,
+                    Date.parse(this.articleList.at(this.articleList.length - 1).article.pubDate),
+                    this.localSettings.showOnlyUnread,
+                    successWrapper,
+                    errorWrapper)

     togglePostAsRead: (article) =>
         if article.unread


update currentUser on user update

update currentUser on user update

--- src/remote/current-user.coffee
+++ src/remote/current-user.coffee
@@ -7,6 +7,9 @@

     loggedIn: -> @currentUser?
     is: (userId)-> @currentUser?.id == userId
+    isUserUpdate: (user)->
+      return unless user
+      @is(user.id) && @currentUser?.updated_at != user.updated_at
     updateDescription: ()->
       @updater.handle({ url: 'me', nocallback: true })
     updateSettings: ()->
@@ -15,7 +18,7 @@
     userManager = new UserManager(app.config.data.me, app.config.settings, app.core.handler)
     app.core.handler.after (h)->
       userId = h.headers['Hull-User-Id']
-      unless userManager.is(userId) # It changed
+      if !userManager.is(userId) # It changed
         delete app.core.handler.headers['Hull-Access-Token']
         p1 = userManager.updateDescription().then (h)->
           userManager.currentUser = h.response
@@ -31,6 +34,12 @@
           app.sandbox.emit 'remote.settings.update', {}
         )
         p2
+      else if userManager.isUserUpdate(h.response)
+        userManager.currentUser = h.response
+        app.sandbox.emit 'remote.user.update', userManager.currentUser
+
+
+

     app.core.currentUser = -> JSON.parse(JSON.stringify(userManager.userDesc)) #TODO Seriously use lodash
     app.core.settings = -> JSON.parse(JSON.stringify(userManager.currentSettings)) #TODO Seriously use lodash


Stop this silliness

Stop this silliness

--- lib/cache.coffee
+++ lib/cache.coffee
@@ -90,19 +90,11 @@
     item.next = this

   get: ->
-    @sub @value
-
-  sub: (obj) ->
-    if _.isObject(obj)
-      sub = {}
-      sub.__proto__ = obj
-      for k, v of obj
-        if _.isObject(v) || (_.isArray(v) && _.isObject(v[0]))
-          sub[k] = @sub v
-      sub
-    else if _.isArray(obj)
-      @sub item for item in obj
+    if !_.isObject(@value)
+      @value
     else
-      obj
+      clone = {}
+      clone.__proto__ = @value
+      clone

 module.exports = Cache


add emmet-comments option

add emmet-comments option

--- lib/emmet.coffee
+++ lib/emmet.coffee
@@ -77,8 +77,8 @@
         se.destroy()
       else
         return evt.abortKeyBinding()
-
-  if action is 'toggle_comment' and toggleCommentSyntaxes.indexOf(syntax) is -1
+
+  if action is 'toggle_comment' and (toggleCommentSyntaxes.indexOf(syntax) is -1 or not atom.config.get 'emmet.useEmmetComments')
     return evt.abortKeyBinding()

   if action is 'insert_formatted_line_break_only'
@@ -129,6 +129,10 @@
     formatLineBreaks:
       type: 'boolean'
       default: true
+    useEmmetComments:
+      type: 'boolean'
+      default: true
+      description: 'disable to use atom native commenting system'

   activate: (@state) ->
     @subscriptions = new CompositeDisposable
@@ -146,4 +150,4 @@
     @subscriptions.add atom.commands.add 'atom-text-editor', @actions

   deactivate: ->
-    atom.config.transact => @subscriptions.dispose()+    atom.config.transact => @subscriptions.dispose()


provide interface for easy context querying

--- src/ch.coffee
+++ src/ch.coffee
@@ -17,7 +17,17 @@
         new ChContextOperation(null, subject)

     fn.o = (objects) -> new ChObjectsOperation(objects)
-    fn.c = (objects) -> new ChContextOperation(objects)
+
+    fn.c = ->
+      if arguments.length == 2
+        [system, model] = arguments
+        endpoint = endpoints[system]
+        throw new Error("no endpoint url defined for #{system}") unless endpoint
+        new ChContextOperation(null, { '@context': "#{endpoint}/context/#{model}" })
+      else
+        contextUrl = arguments[0]
+        new ChContextOperation(null, contextUrl)
+
     fn
   ]


IT WORKS

IT WORKS

--- tentacle-transformer.coffee
+++ tentacle-transformer.coffee
@@ -6,7 +6,7 @@
   constructor: (opts={}) ->
     @path = opts.path || __dirname + '/tentacle-message.proto'
     @messageType = opts.message || 'TentacleMessage'
-
+    @buffer = ByteBuffer.wrap(new Buffer(0))
     builder = ProtoBuf.loadProtoFile @path
     @MicrobluProto = builder.build @messageType

@@ -18,15 +18,11 @@
   addData: (data) =>
     console.log "adding data"
     return unless data
-
-    if !@buffer
-      @buffer = ByteBuffer.wrap(data)
-    else
-      @buffer = ByteBuffer.concat [@buffer, ByteBuffer.wrap(data)]
+    @buffer = ByteBuffer.concat [@buffer, ByteBuffer.wrap(data)]

   toJSON: () =>
     return unless @buffer?.remaining() > 0
-
+
     try
       decoded = @MicrobluProto.decodeDelimited @buffer
       return null if !decoded


syntax cleanup, make collection optional

--- lib/index.coffee
+++ lib/index.coffee
@@ -28,7 +28,7 @@
       @roots.__records = []
       for key, obj of opts
         @roots.__records.push(exec.call(@, key, obj))
-      W.all @roots.__records
+      W.all(@roots.__records)

     ###*
      * Promises to retrieve data, then
@@ -43,7 +43,7 @@
       .then (res) =>
         if obj.template
           compile_single_views.call(@,
-            obj.collection(res),
+            (if obj.collection? then obj.collection(res) else res),
             obj.template,
             obj.out
           )
@@ -136,10 +136,14 @@
         compiler = _.find @roots.config.compilers, (c) ->
           _.contains(c.extensions, path.extname(template_path).substring(1))
         compiler_options = @roots.config[compiler.name] ? {}
+
         compiler.renderFile(
           template_path,
-          _.extend(@roots.config.locals, compiler_options,_path: _path)
-          ).then((res) => @util.write(compiled_file_path, res.result))
+          _.extend(
+            @roots.config.locals,
+            compiler_options,
+            _path: _path
+          )).then((res) => @util.write(compiled_file_path, res.result))

     __parse = (response, resolver) ->
       try


tweak a few lines

tweak a few lines

--- lib/index.coffee
+++ lib/index.coffee
@@ -2,7 +2,8 @@
 corepath   = require 'path'
 Path       = require 'fspath'

-class Dirator extends (require 'events').EventEmitter
+class Dirator extends require('events').EventEmitter
+
   constructor: (options, done) -> @_configure options, done

   _configure: (options = {}, done) ->
@@ -50,7 +51,7 @@
     super event, listener

   run: (options, done) ->
-    if options? then @_configure options, done
+    if arguments.length > 0 then @_configure options, done
     unless @target? then throw new Error 'dirator requires a `target` to run'
     if @async then @_iterate [new Path @target] else @_iterateSync()

@@ -173,7 +174,7 @@
 #  1. Dirator = require('dirator').Dirator
 #  2. dirator = require('dirator')
 #     dirator options
-#  3. require('dirator') (options)
+#  3. require('dirator')(options)
 module.exports = (args...) -> new Dirator(args...).run()
 module.exports.Dirator = Dirator
 for type in ['files', 'dirs', 'paths']