test: test vimeoShell thoroughly

test: test vimeoShell thoroughly

For now, tests for PlayerView are commented out as they only run in a
regular server. They need to be adjusted so that phantomJS supports
them.

--- coffee/test/shells/vimeo.shell.spec.coffee
+++ coffee/test/shells/vimeo.shell.spec.coffee
@@ -1,6 +1,7 @@
 goog.provide 'acorn.specs.shells.VimeoShell'

 goog.require 'acorn.shells.VimeoShell'
+goog.require 'acorn.shells.LinkShell'
 goog.require 'acorn.util.test'

 describe 'acorn.shells.VimeoShell', ->
@@ -11,7 +12,8 @@
   PlayerView = VimeoShell.PlayerView
   RemixView = VimeoShell.RemixView

-  modelOptions = -> link: 'http://vimeo.com/8201078'
+  vimeoId = '8201078'
+  modelOptions = -> link: "http://www.vimeo.com/#{vimeoId}"
   viewOptions = ->
     model: new Model modelOptions()
     eventhub: _.extend {}, Backbone.Events
@@ -20,3 +22,378 @@
     expect(VimeoShell).toBeDefined()

   acorn.util.test.describeShellModule VimeoShell, modelOptions(), ->
+
+    validLinks = VimeoShell.validLinkPatterns
+    expectLinkMatches = (link) ->
+      expect(acorn.shells.LinkShell.linkMatches link, validLinks).toBe true
+
+
+    describe 'VimeoShell', ->
+
+      it 'should recognize player.vimeo.com/video/:id', ->
+        expectLinkMatches "www.player.vimeo.com/video/#{vimeoId}"
+
+      it 'should recognize player.vimeo.com/:id', ->
+        expectLinkMatches "http://player.vimeo.com/#{vimeoId}"
+
+      it 'should recognize vimeo.com/video/:id', ->
+        expectLinkMatches "vimeo.com/video/#{vimeoId}"
+
+      it 'should recognize vimeo.com/:id', ->
+        expectLinkMatches "https://www.vimeo.com/#{vimeoId}"
+
+
+    describe 'VimeoShell.Model', ->
+
+      it 'should retrieve vimeo id from url', ->
+        urls = [
+          "www.player.vimeo.com/video/#{vimeoId}"
+          "http://player.vimeo.com/#{vimeoId}"
+          "vimeo.com/video/#{vimeoId}"
+          "https://www.vimeo.com/#{vimeoId}"
+        ]
+
+        for url in urls
+          model = new Model link: url
+          expect(model.vimeoId()).toBe vimeoId
+
+      it 'should have a valid metaDataUrl', ->
+        model = new Model modelOptions()
+        expect(model.metaDataUrl()).toBe "http://vimeo.com/api/v2/video/" +
+            "#{vimeoId}.json?callback=?"
+
+      it 'should fetch metadata', ->
+        model = new Model modelOptions()
+        retrieved = false
+
+        runs ->
+          model.metaData().sync success: => retrieved = true
+
+        waitsFor (-> retrieved), 'retrieving metaData', 10000
+
+        runs ->
+          cache = model.metaData()
+          expect(cache.synced()).toBe true
+          expect(athena.lib.util.isStrictObject cache.data()[0]).toBe true
+
+      it 'should have a title method that uses metaData or link as title', ->
+        model = new Model modelOptions()
+
+        expect(model.title()).toBe modelOptions().link
+
+        retrieved = false
+
+        runs ->
+          model.metaData().sync success: => retrieved = true
+
+        waitsFor (-> retrieved), 'retrieving metaData', 10000
+
+        runs ->
+          title = model.metaData().data()[0].title
+          expect(model.title()).toBe title
+
+      it 'should have a description method that describes the shell', ->
+        _modelOptions = _.extend modelOptions(),
+          timeStart: 33
+          timeEnd: 145
+
+        model = new Model _modelOptions
+
+        expect(model.description()).toBe "Vimeo video #{_modelOptions.link}" +
+            " from 00:33 to 02:25."
+
+        retrieved = false
+
+        runs ->
+          model.metaData().sync success: => retrieved = true
+
+        waitsFor (-> retrieved), 'retrieving metaData', 10000
+
+        runs ->
+          title = model.metaData().data()[0].title
+          expect(model.description()).toBe "Vimeo video #{title} from 00:33" +
+              " to 02:25."
+
+      it 'should have a timeTotal method that returns metaData.duration or
+          Infinity', ->
+        model = new Model modelOptions()
+
+        expect(model.timeTotal()).toBe Infinity
+
+        retrieved = false
+
+        runs ->
+          model.metaData().sync success: => retrieved = true
+
+        waitsFor (-> retrieved), 'retrieving metaData', 10000
+
+        runs ->
+          timeTotal = model.metaData().data()[0].duration
+          expect(model.timeTotal()).toBe timeTotal
+
+
+    describe 'VimeoShell.PlayerView', ->
+      it '------ TODO ------ make PlayerView tests work with PhantomJS', ->
+
+    # TODO: most of these tests rely on in-DOM interaction with the vimeo API
+    # of a nature that phantomJS does not support. Although all of the following
+    # tests pass when run with `grunt server`, they fail when run with
+    # `grunt test`
+    xdescribe 'VimeoShell.PlayerView', ->
+
+      it 'should load and ready a vimeo player on render', ->
+        pv = new PlayerView viewOptions()
+        setPlayerListener = false
+        playerReady = false
+        pvReady = false
+        pv.on 'PlayerView:Ready', -> pvReady = true
+
+        # setup DOM
+        acorn.util.appendCss()
+        $hiddenPlayer = $('<div>').addClass('acorn-player hidden').width(600)
+            .height(400).appendTo('body')
+
+        # must be appended to DOM in order to load properly
+        runs -> $hiddenPlayer.append pv.render().el
+
+        waitsFor (->
+          # if 'PlayerView:Ready' event fired before listener was set, settle
+          # for inability to test low-level readiness and return true
+          if pvReady and not setPlayerListener
+            return true
+
+          # if possible, add listener to froogaloop 'ready' event
+          if pv.player? and not setPlayerListener
+            setPlayerListener = true
+            pv.player.addEvent 'ready', ->
+              playerReady = true
+
+              # call through (froogaloop overwrites old listener to ready)
+              pv.onVimeoPlayerReady()
+
+          # ideally, directly test both froogaloop 'ready' and playerView
+          # 'PlayerView:Ready' events
+          playerReady and pvReady
+        ), 'player ready', 10000
+
+      it 'should load a vimeo player on render that loads video when played', ->
+        pv = new PlayerView viewOptions()
+
+        loading = false
+        # set 'loadProgress' listener and start playing on pv ready
+        pv.on 'PlayerView:Ready', ->
+          pv.player.addEvent 'loadProgress', (params) ->
+            if parseFloat(params.percent) > 0
+              loading = true
+
+          pv.player.api 'play'
+
+        # setup DOM
+        acorn.util.appendCss()
+        $hiddenPlayer = $('<div>').addClass('acorn-player hidden').width(600)
+            .height(400).appendTo('body')
+
+        # must be appended to DOM in order to load properly
+        runs -> $hiddenPlayer.append pv.render().el
+
+        # wait for loading
+        waitsFor (-> loading), 'video loading', 10000
+
+      it 'should announce state changes', ->
+        pv = new PlayerView viewOptions()
+
+        # start playing on pv ready
+        pv.on 'PlayerView:Ready', -> pv.player.api 'play'
+
+        stateChanged = false
+        pv.on 'PlayerView:StateChange', -> stateChanged = true
+
+        # load player view
+        acorn.util.appendCss()
+        $hiddenPlayer = $('<div>').addClass('acorn-player hidden').width(600)
+            .height(400).appendTo('body')
+        runs -> $hiddenPlayer.append pv.render().el
+
+        waitsFor (-> stateChanged), 'state change event', 10000
+
+
+      describe 'video player view api', ->
+        pv = undefined
+        $hiddenPlayer = undefined
+
+        # froogaloop 'paused' query does not work, so track paused status
+        # manually. for details on bug, see:
+        #   https://github.com/vimeo/player-api/issues/31
+        #   http://stackoverflow.com/questions/14119494/
+        paused = true
+
+        beforeEach ->
+          pv = new PlayerView viewOptions()
+
+          loading = false
+          # set listeners and start playing on pv ready
+          pv.on 'PlayerView:Ready', ->
+            pv.player.addEvent 'play', -> paused = false
+            pv.player.addEvent 'pause', -> paused = true
+
+            pv.player.api 'play'
+
+          # setup DOM
+          acorn.util.appendCss()
+          $hiddenPlayer = $('<div>').addClass('acorn-player hidden').width(600)
+              .height(400).appendTo('body')
+
+          # must be appended to DOM in order to load properly
+          runs -> $hiddenPlayer.append pv.render().el
+
+          # wait for loading
+          waitsFor (-> not paused), 'video to play', 10000
+
+        afterEach ->
+          pv.destroy()
+          $hiddenPlayer.remove()
+          paused = true
+
+        it 'should play', ->
+          # pause video with vimeo API, don't expect PLAYING state
+          runs -> pv.player.api 'pause'
+          waitsFor (-> paused), 'video to pause with API', 10000
+
+          # play video, expect PLAYING state
+          runs -> pv.play()
+          waitsFor (-> not paused), 'video to play after pausing', 10000
+
+        it 'should pause', ->
+          expect(not paused).toBe true
+
+          # pause video, expect PAUSED state
+          runs -> pv.pause()
+          waitsFor (-> paused), 'video to pause after playing', 10000
+
+        it 'should seek, both when playing and when paused', ->
+          seekOffset = undefined
+          pv.player.addEvent 'seek', (params) ->
+            seekOffset = parseInt params.seconds
+
+          runs -> pv.seek 30
+          waitsFor (->
+            seekOffset == 30
+          ), 'video to seek to 30 while playing', 10000
+
+          runs -> pv.seek 40
+          waitsFor (->
+            seekOffset == 40
+          ), 'video to seek to 40 while playing', 10000
+
+          runs -> pv.seek 10
+          waitsFor (->
+            seekOffset == 10
+          ), 'video to seek to 10 while playing', 10000
+
+          runs -> pv.seek 20
+          waitsFor (->
+            seekOffset == 20
+          ), 'video to seek to 20 while playing', 10000
+
+          # achieve paused state
+          runs -> pv.player.api 'pause'
+          waitsFor (-> paused), 'video to pause with API', 10000
+
+          runs -> pv.seek 30
+          waitsFor (->
+            seekOffset == 30
+          ), 'video to seek to 30 while paused', 10000
+
+          runs -> pv.seek 40
+          waitsFor (->
+            seekOffset == 40
+          ), 'video to seek to 40 while paused', 10000
+
+          runs -> pv.seek 10
+          waitsFor (->
+            seekOffset == 10
+          ), 'video to seek to 10 while paused', 10000
+
+          runs -> pv.seek 20
+          waitsFor (->
+            seekOffset == 20
+          ), 'video to seek to 20 while paused', 10000
+
+        it 'should report whether or not it is playing', ->
+          # remove listener to PlayerView:Ready and set up native vimeo shell
+          # froogaloop event listeners again. this is necessary because the
+          # addEvent function overwrites the old listener to an event - the
+          # beforeEach block clobbered the shell's listener to the pause event
+          pv.off 'PlayerView:Ready'
+          pv.onVimeoPlayerReady()
+
+          # expect PLAYING state
+          runs -> expect(pv.isPlaying()).toBe true
+
+          # pause video, expect PAUSED state
+          runs -> pv.player.api 'pause'
+          waitsFor (->
+            not pv.isPlaying()
+          ), 'playerView to register paused state', 10000
+
+        it 'should report seek offset', ->
+          runs -> pv.player.api 'seekTo', 30
+          waitsFor (->
+            pv.seekOffset() == 30
+          ), 'playerView to register seekOffset to 30 while playing', 10000
+
+          runs -> pv.player.api 'seekTo', 40
+          waitsFor (->
+            pv.seekOffset() == 40
+          ), 'playerView to register seekOffset to 40 while playing', 10000
+
+          runs -> pv.player.api 'seekTo', 10
+          waitsFor (->
+            pv.seekOffset() == 10
+          ), 'playerView to register seekOffset to 10 while playing', 10000
+
+          runs -> pv.player.api 'seekTo', 20
+          waitsFor (->
+            pv.seekOffset() == 20
+          ), 'playerView to register seekOffset to 20 while playing', 10000
+
+          # achieve paused state
+          runs -> pv.player.api 'pause'
+          waitsFor (-> paused), 'video to pause with API', 10000
+
+          runs -> pv.player.api 'seekTo', 30
+          waitsFor (->
+            pv.seekOffset() == 30
+          ), 'playerView to register seekOffset to 30 while paused', 10000
+
+          runs -> pv.player.api 'seekTo', 40
+          waitsFor (->
+            pv.seekOffset() == 40
+          ), 'playerView to register seekOffset to 40 while paused', 10000
+
+          runs -> pv.player.api 'seekTo', 10
+          waitsFor (->
+            pv.seekOffset() == 10
+          ), 'playerView to register seekOffset to 10 while paused', 10000
+
+          runs -> pv.player.api 'seekTo', 20
+          waitsFor (->
+            pv.seekOffset() == 20
+          ), 'playerView to register seekOffset to 20 while paused', 10000
+
+
+      it 'should look good', ->
+        # setup DOM
+        acorn.util.appendCss()
+        $player = $('<div>').addClass('acorn-player').width(600).height(400)
+            .appendTo('body')
+
+        pv = new PlayerView viewOptions()
+        pvReady = false
+        pv.on 'PlayerView:Ready', -> pvReady = true
+
+        # load player view
+        runs ->
+          $player.append pv.render().el
+          pv.$el.find('iframe').width(600).height(371)
+        waitsFor (-> pvReady), 'player view ready', 10000


Use temp directory in isPathNew/Modified specs

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -41,17 +41,15 @@
       expect(repo.isPathIgnored('b.txt')).toBeFalsy()

   describe ".isPathModified(path)", ->
-    [repo, filePath, newPath, originalPathText] = []
-
-    beforeEach ->
-      repo = new Git(path.join(__dirname, 'fixtures', 'git', 'working-dir'))
-      filePath = require.resolve('./fixtures/git/working-dir/file.txt')
-      newPath = path.join(__dirname, 'fixtures', 'git', 'working-dir', 'new-path.txt')
-      originalPathText = fs.readFileSync(filePath, 'utf8')
-
-    afterEach ->
-      fs.writeFileSync(filePath, originalPathText)
-      fs.removeSync(newPath) if fs.existsSync(newPath)
+    [repo, filePath, newPath] = []
+
+    beforeEach ->
+      workingDirPath = temp.mkdirSync('atom-working-dir')
+      fs.copySync(path.join(__dirname, 'fixtures', 'git', 'working-dir'), workingDirPath)
+      fs.renameSync(path.join(workingDirPath, 'git.git'), path.join(workingDirPath, '.git'))
+      repo = new Git(workingDirPath)
+      filePath = path.join(workingDirPath, 'a.txt')
+      newPath = path.join(workingDirPath, 'new-path.txt')

     describe "when the path is unstaged", ->
       it "returns false if the path has not been modified", ->
@@ -72,13 +70,13 @@
     [filePath, newPath] = []

     beforeEach ->
-      repo = new Git(path.join(__dirname, 'fixtures', 'git', 'working-dir'))
-      filePath = require.resolve('./fixtures/git/working-dir/file.txt')
-      newPath = path.join(__dirname, 'fixtures', 'git', 'working-dir', 'new-path.txt')
+      workingDirPath = temp.mkdirSync('atom-working-dir')
+      fs.copySync(path.join(__dirname, 'fixtures', 'git', 'working-dir'), workingDirPath)
+      fs.renameSync(path.join(workingDirPath, 'git.git'), path.join(workingDirPath, '.git'))
+      repo = new Git(workingDirPath)
+      filePath = path.join(workingDirPath, 'a.txt')
+      newPath = path.join(workingDirPath, 'new-path.txt')
       fs.writeFileSync(newPath, "i'm new here")
-
-    afterEach ->
-      fs.removeSync(newPath) if fs.existsSync(newPath)

     describe "when the path is unstaged", ->
       it "returns true if the path is new", ->


Spec: sets components

Spec: sets components

--- spec/ioc_spec.coffee
+++ spec/ioc_spec.coffee
@@ -18,6 +18,18 @@
       components = new IOC.Dictionary
       resolutions = new IOC.Dictionary
       container = new IOC.Container components: components, resolutions: resolutions
+
+    describe "setting components", ->
+      [componentName, component] = []
+
+      beforeEach ->
+        componentName = uuid()
+        component = mocks.component()
+        spyOn(components, "set").and.callThrough()
+
+      it "stores the component using the name as a key", ->
+        container.setComponent componentName, component
+        expect(components.set).toHaveBeenCalledWith componentName, component

     describe "resolving a component", ->
       [componentName, component, resolution] = []


lodpanelframe: accessors for xlabels and ylabels

--- src/lodpanelframe.coffee
+++ src/lodpanelframe.coffee
@@ -27,6 +27,8 @@
     # chartOpts end
     xscale = null
     yscale = null
+    xlabels = null
+    ylabels = null
     chrSelect = null
     box = null
     svg = null
@@ -200,6 +202,8 @@
     # functions to grab stuff
     chart.xscale = () -> xscale
     chart.yscale = () -> yscale
+    chart.xlabels = () -> xlabels
+    chart.ylabels = () -> ylabels
     chart.chrSelect = () -> chrSelect
     chart.plot_width = () -> plot_width
     chart.plot_height = () -> plot_height


local_hub: make the filesystem sync lock expire (otherwise get problems on exception when using sshfs)

--- salvus/local_hub.coffee
+++ salvus/local_hub.coffee
@@ -60,6 +60,8 @@
 json = (out) -> misc.trunc(misc.to_json(out),512)

 {ensure_containing_directory_exists, abspath} = misc_node
+
+expire_time = (ttl) -> if ttl then new Date((new Date() - 0) + ttl*1000)

 # We make it an error for a client to try to edit a file larger than MAX_FILE_SIZE.
 # I decided on this, because attempts to open a much larger file leads
@@ -1556,9 +1558,12 @@
             return

         if @_filesystem_sync_lock
-            winston.debug("client_diffsync hit a filesystem_sync_lock -- send retry message back")
-            write_mesg('error', {error:"retry"})
-            return
+            if @_filesystem_sync_lock < new Date()
+                @_filesystem_sync_lock = false
+            else
+                winston.debug("client_diffsync hit a filesystem_sync_lock -- send retry message back")
+                write_mesg('error', {error:"retry"})
+                return

         @_client_sync_lock = true
         before = @content
@@ -1591,9 +1596,12 @@
             cb?("cannot sync with filesystem while syncing with clients")
             return
         if @_filesystem_sync_lock
-            winston.debug("sync_filesystem -- hit filesystem sync lock")
-            cb?("cannot sync with filesystem; already syncing")
-            return
+            if @_filesystem_sync_lock < new Date()
+                @_filesystem_sync_lock = false
+            else
+                winston.debug("sync_filesystem -- hit filesystem sync lock")
+                cb?("cannot sync with filesystem; already syncing")
+                return

         before = @content
@@ -1601,7 +1609,7 @@
             cb?("filesystem sync object (@diffsync_fileclient) no longer defined")
             return

-        @_filesystem_sync_lock = true
+        @_filesystem_sync_lock = expire_time(10)  # lock expires in 10 seconds no matter what -- uncaught exception could require this
         @diffsync_fileclient.sync (err) =>
             if err
                 # Example error: 'reset -- checksum mismatch (29089 != 28959)'


Add "build:once" task without webserver and watcher

--- gulp/development/index.coffee
+++ gulp/development/index.coffee
@@ -26,4 +26,18 @@
       'build:watch'
     )

+
+  gulp.task 'build:once', ->
+    sequence(
+      'build:clean'
+      [
+        'build:scss'
+        'build:jade'
+        'build:coffee'
+        'build:bower'
+        'build:copy:scripts'
+      ]
+    )
+
+
   gulp.task 'default', -> sequence 'build'


update config application view after start/stop

That way the start/stop button has a consistent behavior. It proposes to start
when the app is stopped and vice versa.

--- client/app/views/config_application.coffee
+++ client/app/views/config_application.coffee
@@ -175,6 +175,7 @@
                 success: =>
                     @startStopBtn.spin false
                     @stateLabel.html t 'stopped'
+                    @render()
                     Backbone.Mediator.pub 'app-state:changed',
                         status: 'stopped'
                         updated: false
@@ -187,6 +188,7 @@
                 success: =>
                     @startStopBtn.spin false
                     @stateLabel.html t 'started'
+                    @render()
                     Backbone.Mediator.pub 'app-state:changed',
                         status: 'started'
                         updated: false


remove unused props

remove unused props

--- client/skr/components/LocationChooser.cjsx
+++ client/skr/components/LocationChooser.cjsx
@@ -1,7 +1,6 @@
 class Skr.Components.LocationChooser extends Lanes.React.Component

     propTypes:
-        onModelSet: React.PropTypes.func
         name:       React.PropTypes.string
         hideSingle: React.PropTypes.bool

@@ -26,7 +25,8 @@
         if @props.hideSingle and Skr.Models.Location.all.length < 2
             return LC.SelectField.renderEmptyColumn(@props)

-        props = _.clone(@props)
+        props = _.omit(@props, 'hideSingle')
+
         if props.displayFinder
             <LC.RecordFinder ref="finder" sm=3 autoFocus
                 commands={@state.commands}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/controllable.coffee
+++ src/controllable.coffee
@@ -1,5 +1,5 @@
 ###*
-The Controllable module adds simple
+The Controllable module adds simple movement

   # create a player and include ClampBounds


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/controllable.coffee
+++ src/controllable.coffee
@@ -1,4 +1,4 @@
-Controllable = (I={}) ->
+Controllable = (I={},) ->
   Object.reverseMerge I,
     speed: 1


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/controllable.coffee
+++ src/controllable.coffee
@@ -11,7 +11,7 @@
     x: 15
     y: 30

-  # hold one of the move
+  # hold one of the movement key

   # update the player so ClampBounds can set
   # his position back inside the viewport.


Social: fix linting errors

Social: fix linting errors

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1410,9 +1410,9 @@
           return callback err if err?

           # announcement channel will only be created for koding channel
-          if @slug is "koding"
-
-            @createAnnouncementChannel client, options, (err, announcementChannelId) =>
+          if @slug is 'koding'
+
+            @createAnnouncementChannel client, options, (err, announcementChannelId) ->
               return callback err if err?

               return callback null, {
@@ -1422,7 +1422,7 @@
                 socialApiAnnouncementChannelId : announcementChannelId
               }
           else
-            @createDefaultChannel client, options, (err, defaultChannelId) =>
+            @createDefaultChannel client, options, (err, defaultChannelId) ->
               return callback err if err?

               return callback null, {
@@ -1433,9 +1433,9 @@

   createGroupChannel:(client, options, callback)->
-    options.name = "public"
-    options.varName = "socialApiChannelId"
-    options.typeConstant = "group"
+    options.name = 'public'
+    options.varName = 'socialApiChannelId'
+    options.typeConstant = 'group'

     return @createSocialAPIChannel client, options, callback

@@ -1472,7 +1472,7 @@

       op = { $set: {}, $push: {} }
       op.$set[varName] = channel.channel.id
-      op.$push["defaultChannels"] = channel.channel.id
+      op.$push['defaultChannels'] = channel.channel.id

       @update op, (err) ->
         return callback err if err


SocialWorker: JGroup: pass callback directly

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -657,9 +657,7 @@

         ]

-        async.series queue, (err) ->
-          return callback err  if err
-          callback null
+        async.series queue, callback

   notifyAccountOnRoleChange = (client, id, roles, callback) ->
     JAccount.one { _id: id }, (err, account) ->


removed console.log

removed console.log

--- src/slider.coffee
+++ src/slider.coffee
@@ -314,7 +314,6 @@
 			@position @position(),
 				changeEvent: false
 				transitionEvent: false
-			console.log 'refreshed'

 		if @transitioning
 			_.listenOnce @element, 'transition', refresh


Update sprite.coffee

Update sprite.coffee

hopefully make setting and changing Sprite._pos.a work with the Sprite._dis.highlight functionality

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -248,8 +248,15 @@

     #setter
     set: (what, to, _emit = true) ->
-        if what.match /(^x$|^y$|^a$)/
+        if what.match /(^x$|^y$)/
             @_pos[what] = to
+        else if what.match /^a$/
+            if @_pos.a?
+                diff = to - @_pos.a
+            @_pos.a = to
+            if diff
+                for pt in @_bnd
+                    pt.change 'a', diff
         else if what.match /(^dx$|^dy$|^da$)/
             @_mot[what] = to
         else if what.match /(^ddx$|^ddy$|^dda$)/
@@ -356,6 +363,9 @@
     change: (what, step, _emit = true) ->
         if what.match /(^x$|^y$|^a$)/
             @_pos[what] += step / env.FRAME_RATE
+            if what is 'a'
+                for pt in @_bnd
+                    pt.change 'a', step / env.FRAME_RATE
         else if what.match /(^dx$|^dy$|^da$)/
             @_mot[what] += step / env.FRAME_RATE
         else if what.match /(^ddx$|^ddy$|^dda$)/
@@ -493,19 +503,21 @@
                 @_dis.width, #width
                 @_dis.height) #height

+            #restore context
+            @_dis.context.restore()
+
             #highlight boundaries
             if @_dis.highlight
+                @_dis.context.save()
                 @_dis.context.lineWidth = 3
                 @_dis.context.strokeStyle = 'white'
                 @_dis.context.beginPath()
-                @_dis.context.moveTo(@_bnd[0]._x, -@_bnd[0]._y)
+                @_dis.context.moveTo @_bnd[0].get('x'), -@_bnd[0].get('y')
                 for pt, i in @_bnd when i isnt 0
-                    @_dis.context.lineTo(pt._x, -pt._y)
+                    @_dis.context.lineTo pt.get('x'), -pt.get('y')
                 @_dis.context.closePath()
                 @_dis.context.stroke()
-
-            #restore context
-            @_dis.context.restore()
+                @_dis.context.restore()

             #fire draw:after event
             @emit 'draw:after'


Cleaned up sprite and remove references to window and older fill-ins

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -56,11 +56,7 @@
     height: height

   Sprite.loadSheet = (name, tileWidth, tileHeight) ->
-    directory = App?.directories?.images || "images"
-
-    url = "#{BASE_URL}/#{directory}/#{name}.png?#{MTIME}"
-
-    console.log url
+    url = ResourceLoader.urlFor("images", name)

     sprites = []
     image = new Image()
@@ -91,22 +87,20 @@

     return proxy

-  pixieSpriteImagePath = "http://pixieengine.com/s3/sprites/"
+  ###*
+  Loads a sprite with the given pixie id.

-  fromPixieId = (id, callback) ->
-    Sprite.load(pixieSpriteImagePath + id + "/original.png", callback)
+  @name fromPixieId
+  @methodOf Sprite

-  window.Sprite = (name, callback) ->
-    if App.Sprites
-      id = App.Sprites[name]
-      if id
-        fromPixieId(id, callback)
-      else
-        warn("Could not find sprite named: '" + name + "' in App.")
+  @param id
+  @param [callback]

-    else
-      # Treat name as URL
-      return window.Sprite.fromURL(name, callback)
+  @type Sprite
+  ###
+  Sprite.fromPixieId = (id, callback) ->
+    Sprite.load("http://pixieengine.com/s3/sprites/#{id}/original.png", callback)
+

   ###*
   A sprite that draws nothing.
@@ -124,20 +118,7 @@
   @constant
   @type Sprite
   ###
-  window.Sprite.EMPTY = window.Sprite.NONE = LoaderProxy()
-
-  ###*
-  Loads a sprite with the given pixie id.
-
-  @name fromPixieId
-  @methodOf Sprite
-
-  @param id
-  @param [callback]
-
-  @type Sprite
-  ###
-  window.Sprite.fromPixieId = fromPixieId
+  Sprite.EMPTY = Sprite.NONE = LoaderProxy()

   ###*
   Loads a sprite from a given url.
@@ -150,7 +131,7 @@

   @type Sprite
   ###
-  window.Sprite.fromURL = Sprite.load
+  Sprite.fromURL = Sprite.load

   ###*
   Loads a sprite with the given name.
@@ -163,12 +144,9 @@

   @type Sprite
   ###
-  window.Sprite.loadByName = (name, callback) ->
-    directory = App?.directories?.images || "images"
-    url = "#{BASE_URL}/#{directory}/#{name}.png?#{MTIME}"
-    Sprite.load(url, callback)
+  Sprite.loadByName = (name, callback) ->
+    Sprite.load(ResourceLoader.urlFor("images", name), callback)

-  window.Sprite.create = Sprite
-
+  (exports ? this)["Sprite"] = Sprite
 )()


Add missing support for out-of-office resetStatusLed option

--- src/status.coffee
+++ src/status.coffee
@@ -349,7 +349,7 @@
   updateState: (state) ->
     state = "pending" if !state?

-    if @isOutOfOffice()
+    if @isOutOfOffice() && @outOfOffice["resetStatusLed"]
       setElDataAttr @elements.led, "state", "pending"
     else
       setElDataAttr @elements.led, "state", state


cosmetic update

cosmetic update

--- src/syslog.coffee
+++ src/syslog.coffee
@@ -22,11 +22,6 @@
 protocol_get = (type,str) ->
   return str.split("://")[0]            if type is "protocol"
   return str.split("://")[1].split(":") if type is "server"
-
-_ =
-  setname = (name,envelope) ->
-    envelope.user.name = filtername
-    envelope.message.user.name = filtername

 module.exports = (robot) ->

@@ -53,8 +48,11 @@

   logserver.send_to_channels = (app, data, filtername, filter) ->
     for output in filter.output
+      setname = (name,envelope) ->
+        envelope.user.name = filtername
+        envelope.message.user.name = filtername
       # override username otherwise hubot wants to message the user too
-      _.setname filtername, output if output.user?.name? and output.room?
+      setname filtername, output if output.user?.name? and output.room?
       logserver.robot.reply output, String(data.message)

   logserver.forward = (app, data, filtername, filter ) ->


Added Search functionality and get member cards

--- src/trello.coffee
+++ src/trello.coffee
@@ -16,6 +16,8 @@
 #   hubot trello add member <shortlink> "<first name of member>"
 #   hubot trello add comment <shortlink> "<comment>"
 #   hubot trello description <shortlink> "<discription>"
+#   hubot trello search "<criteria>"
+#   hubot trello get <name>'s cards
 #
 #
 # Author:
@@ -82,13 +84,22 @@
      id = members[member_name.toLowerCase()].username
   msg.send "Unable to find person named: #{user_name}" unless id?
   if id?
-    trello.get "/1/search", {query: "@"+id, idBoards: board.id, modelTypes:cards, card_fields: name,shortLink,url}, (err, data) ->
+    trello.get "/1/search", {query: "@"+id, idBoards: board.id, modelTypes: "cards", card_fields: "name,shortLink,url"}, (err, data) ->
       msg.reply "So sorry, I got an error and cannot give you that information" if err
       msg.reply "I got the following cards for you" unless err
       msg.send board.id
       msg.send id
       for cards in data.cards
         msg.send " * " + cards.name+ " | " + cards.url
+
+search = (msg, search) ->
+  if (search?)
+    msg.reply "I'm searching for results now"
+    trello.get "/1/search", {query: search, idBoards: board.id, modelTypes:"cards", card_fields: "name,shortLink,url"}, (err, data) ->
+      msg.reply "Sorry, I was unable to search at this time. Please try again later." if err
+      msg.reply "The following cards match your criteria" unless err
+      for cards in data.cards
+         msg.send " * " + cards.name + " | " + cards.url

 moveCard = (msg, card_id, list_name) ->
@@ -155,6 +166,9 @@

   robot.respond /trello get (\w+)['’]s cards/i, (msg) ->
     getMemberCards msg, msg.match[1]
+
+  robot.respond /trello search ["“'‘]((.+|\n)+)["”'’]/i, (msg) ->
+     search msg, msg.match[1]

   robot.respond /trello list lists/i, (msg) ->
     msg.reply "Here are all the lists on your board."
@@ -181,6 +195,8 @@
     msg.send " *  trello move <card.shortlink> \"<ListName>\""
     msg.send " *  trello list lists"
     msg.send " *  trello list member id <name>"
+    msg.send " *  trello get <first name>'s cards"
     msg.send " *  trello add member <card.shortLink> <Name>"
     msg.send " *  trello comment <card.shortLink> <Comment>"
     msg.send " *  trello description <card.shortLink> <Description>"
+    msg.send " *  trello search <criteria>"


Fix lmo url service specs

Fix lmo url service specs

--- lineman/spec/services/lmo_url_service_spec.coffee
+++ lineman/spec/services/lmo_url_service_spec.coffee
@@ -15,16 +15,16 @@
           host: 'localhost'

       it "handles a group without subdomain on localhost", ->
-        expect(@subject.group(@group)).toBe 'http://localhost:3000/g/key/name'
+        expect(@subject.group(@group)).toBe "http://localhost:3000/g/#{@group.key}/name"

       it "handles a group with subdomain on localhost", ->
         @group.updateFromJSON subdomain: 'subdomain'
-        expect(@subject.group(@group)).toBe 'http://subdomain.localhost:3000/'
+        expect(@subject.group(@group)).toBe "http://subdomain.localhost:3000/"

       it "handles a subgroup with subdomain on localhost", ->
         @group.updateFromJSON subdomain: 'subdomain'
         subgroup = @factory.create 'groups', parent_id: @group.id, key: 'subkey', name: 'subname'
-        expect(@subject.group(subgroup)).toBe 'http://subdomain.localhost:3000/g/subkey/name-subname'
+        expect(@subject.group(subgroup)).toBe "http://subdomain.localhost:3000/g/#{subgroup.key}/name-subname"

     describe 'on loom.io', ->
       beforeEach ->
@@ -34,7 +34,7 @@
           ssl: true

       it 'handles a group without subdomain on loom.io', ->
-        expect(@subject.group(@group)).toBe 'https://loom.io/g/key/name'
+        expect(@subject.group(@group)).toBe "https://loom.io/g/#{@group.key}/name"

       it 'handles a group with subdomain on loom.io', ->
         @group.updateFromJSON subdomain: 'subdomain'
@@ -43,7 +43,7 @@
       it 'handles a subgroup with subdomain on loom.io', ->
         @group.updateFromJSON subdomain: 'subdomain'
         subgroup = @factory.create 'groups', parent_id: @group.id, key: 'subkey', name: 'subname'
-        expect(@subject.group(subgroup)).toBe 'https://subdomain.loom.io/g/subkey/name-subname'
+        expect(@subject.group(subgroup)).toBe "https://subdomain.loom.io/g/#{subgroup.key}/name-subname"

     describe 'on loomio.org', ->
       beforeEach ->
@@ -52,16 +52,16 @@
           default_subdomain: 'www'

       it 'handles a group without subdomain on loomio.org', ->
-        expect(@subject.group(@group)).toBe 'http://www.loomio.org/g/key/name'
+        expect(@subject.group(@group)).toBe "http://www.loomio.org/g/#{@group.key}/name"

       it 'handles a group with subdomain on loomio.org', ->
         @group.updateFromJSON subdomain: 'subdomain'
-        expect(@subject.group(@group)).toBe 'http://subdomain.loomio.org/'
+        expect(@subject.group(@group)).toBe "http://subdomain.loomio.org/"

       it 'handles a subgroup with subdomain on loomio.org', ->
         @group.updateFromJSON subdomain: 'subdomain'
         subgroup = @factory.create 'groups', parent_id: @group.id, key: 'subkey', name: 'subname'
-        expect(@subject.group(subgroup)).toBe 'http://subdomain.loomio.org/g/subkey/name-subname'
+        expect(@subject.group(subgroup)).toBe "http://subdomain.loomio.org/g/#{subgroup.key}/name-subname"

     describe 'on loomio.anotherdomain.com', ->
       beforeEach ->
@@ -70,13 +70,13 @@
           default_subdomain: null

       it 'handles a group without subdomain on loomio.anotherdomain.com', ->
-        expect(@subject.group(@group)).toBe 'http://loomio.anotherdomain.com/g/key/name'
+        expect(@subject.group(@group)).toBe "http://loomio.anotherdomain.com/g/#{@group.key}/name"

       it 'handles a group with subdomain on loomio.anotherdomain.com', ->
         @group.updateFromJSON subdomain: 'subdomain'
-        expect(@subject.group(@group)).toBe 'http://subdomain.loomio.anotherdomain.com/'
+        expect(@subject.group(@group)).toBe "http://subdomain.loomio.anotherdomain.com/"

       it 'handles a subgroup with subdomain on loomio.anotherdomain.com', ->
         @group.updateFromJSON subdomain: 'subdomain'
         subgroup = @factory.create 'groups', parent_id: @group.id, key: 'subkey', name: 'subname'
-        expect(@subject.group(subgroup)).toBe 'http://subdomain.loomio.anotherdomain.com/g/subkey/name-subname'
+        expect(@subject.group(subgroup)).toBe "http://subdomain.loomio.anotherdomain.com/g/#{subgroup.key}/name-subname"


Sync EmberCart.cartsController.content with all carts in the store

--- app/assets/javascripts/ember_cart/ember/controllers/carts_controller.js.coffee
+++ app/assets/javascripts/ember_cart/ember/controllers/carts_controller.js.coffee
@@ -1,2 +1,4 @@
 EmberCart.cartsController = Ember.ArrayController.create
-  content: []
+  content: Ember.computed( ->
+    EmberCart.store.findAll(EmberCart.Cart)
+  ).property()


fixed problem with the process object directly put into the cheerio context. added only process.nextTick and problem was gone...

--- modules/gui/platforms/node/lib/HttpContext.coffee
+++ modules/gui/platforms/node/lib/HttpContext.coffee
@@ -134,123 +134,6 @@

-        ## ------------ JSDOM -------------- ##
-
-
-
-        ##
-        ##
-        ##
-        _processContent_jsdom: (req, res, content, model, fn)->
-
-            ##
-            #start = +new Date()
-            @_createWindow req, content, (err, window)=>
-                return fn(err) if err
-
-                #console.log 'create:', (+new Date()) - start
-
-                ##
-                window.addEventListener 'unload', next = ()=>
-                    #console.log 'run:', (+new Date()) - start
-
-                    window.removeEventListener 'unload', next
-
-                    ## send the response
-                    _dt = window.document._doctype?._fullDT || '<!DOCTYPE html>'
-                    fn null, _dt+window.document.innerHTML
-
-                    ## cleanup async!
-                    process.nextTick ()=>
-
-                        ## release the jsdom-window into the pool
-                        @_releaseWindow window
-
-                ##
-                window.addEventListener 'error', onerr = (e)=>
-                    window.removeEventListener 'unload', onerr
-
-                    fn e.data
-
-                    process.nextTick ()=>
-                        @_releaseWindow window
-
-
-                ## fire!
-                window.run "(#{__boot_jsdom__})(#{model})"
-
-
-
-
-        ##
-        ##
-        ##
-        _createWindow: (req, template, fn)->
-
-            ## TODO https
-            prefix = 'http://'+req.headers.host
-
-            ##
-            require('jsdom').env
-
-                html:template
-
-                src: [@__SCRIPT]
-
-                document:
-                    referer: req.referer
-                    cookie: 'FSID='+req.session.SID+'; path=/; httponly'
-
-                done: (err, window)=>
-                    return fn(err) if err
-
-                    process.nextTick ()=>
-
-                        window.$('html > script').remove()
-
-                        _loc = req.url
-                        if req.vhostpath
-                            _loc = _loc.substr req.vhostpath.length
-
-                        window.location = require('url').parse prefix+_loc
-
-                        ## fake the protocol while forwarded from https proxy
-                        if window.location.protocol is 'http:' && (proto = req.headers['x-forwarded-proto']) is 'https'
-                            window.location.protocol = 'https:'
-                            window.location.href = window.location.href.replace /^http\:/, 'https:'
-
-                        window.console = console
-
-                        window.floyd = window.require 'floyd'
-
-                        process.nextTick ()=>
-
-                            window.floyd.system.platform = 'jsdom'
-
-                            window.floyd.system.libdir = floyd.system.libdir
-                            window.floyd.system.appdir = floyd.system.appdir
-
-                            window.floyd.tools.files = floyd.tools.files
-
-                            window.floyd.__parent =
-                                lookup: (name, identity, fn)=>
-                                    @lookup name, identity, (err, ctx)=>
-                                        fn err, ctx
-
-                            fn null, window
-
-
-
-        ##
-        ##
-        ##
-        _releaseWindow: (window)->
-
-            window.require = null
-            window.floyd = null
-
-            window.close()
-

@@ -271,7 +154,7 @@
                 @_initContext req, res, ctx, (err)=>

                     ##
-                    ctx.$ = ctx.window.$ = require('cheerio').load content
+                    ctx.$ = require('cheerio').load content

                     ctx.__initTime__ = +new Date()

@@ -289,7 +172,6 @@

                     ##
-                    #require('vm').runInContext "(#{__boot_cheerio__})(#{model})", ctx
                     ctx.run "(#{__boot_cheerio__})(#{model})"

@@ -298,32 +180,26 @@
         ##
         ##
         _createContext: (fn)->
-            vm = require('vm')
-
-            ##
-            #ctx = vm.createContext
+
             ctx =
                 window: {}
-                process: process
-                console: console
-                setTimeout: setTimeout
-
-            require('contextify') ctx
-
-            ##
-            #vm.runInContext @__SCRIPT+"var floyd = require('floyd');", ctx
-            ctx.run @__SCRIPT+"var floyd = require('floyd');", ctx
-
-            ctx.floyd.__parent =
-                lookup: (name, identity, fn)=>
-                    @lookup name, identity, (err, ctx)=>
-                        fn err, ctx
+                console: console
+                process:
+                    nextTick: process.nextTick
+
+            require('contextify') ctx
+
+            ctx.run @__SCRIPT+"var floyd = require('floyd');"

             ctx.floyd.system.platform = 'cheerio'
             ctx.floyd.system.os = floyd.system.os
             ctx.floyd.system.libdir = floyd.system.libdir
             ctx.floyd.system.appdir = floyd.system.appdir
-            ctx.floyd.tools.files = floyd.tools.files
+            ctx.floyd.tools.files = floyd.tools.files
+
+            ctx.floyd.__parent =
+                lookup: (name, identity, fn)=>
+                    @lookup name, identity, fn

             ##
             fn null, ctx
@@ -344,19 +220,17 @@
         ##
         ##
         _releaseContext: (ctx)->
-            persistentKeys = ['run', 'getGlobal', 'dispose', 'setTimeout', 'floyd', 'require', '_modules', 'process', 'console']
+            persistentKeys = ['run', 'getGlobal', 'dispose', 'process', 'console', 'setTimeout', 'floyd', 'require', '_modules']

             if @__POOL__.length < @data.poolsize
                 for key, value of ctx
                     if persistentKeys.indexOf(key) is -1
                         delete ctx[key]

-                ## check again... just for the case
-                if @__POOL__.length < @data.poolsize
-                    @__POOL__.push ctx
-
-                else
-                    ctx.dispose()
+                @__POOL__.push ctx
+
+            else
+                ctx.dispose()

         ##
@@ -379,14 +253,132 @@
                 cookie: 'FSID='+req.session.SID+'; path=/; httponly'

             ##
-            ctx.window =
-                document:   ctx.document
-                location:   ctx.location
-                floyd:      ctx.floyd
+            ctx.window = ctx.getGlobal()

             ##
             done()

+
+
+
+
+        ## ------------ JSDOM -------------- ##
+
+
+
+        ##
+        ##
+        ##
+        _processContent_jsdom: (req, res, content, model, fn)->
+
+            ##
+            #start = +new Date()
+            @_createWindow req, content, (err, window)=>
+                return fn(err) if err
+
+                #console.log 'create:', (+new Date()) - start
+
+                ##
+                window.addEventListener 'unload', next = ()=>
+                    #console.log 'run:', (+new Date()) - start
+
+                    window.removeEventListener 'unload', next
+
+                    ## send the response
+                    _dt = window.document._doctype?._fullDT || '<!DOCTYPE html>'
+                    fn null, _dt+window.document.innerHTML
+
+                    ## cleanup async!
+                    process.nextTick ()=>
+
+                        ## release the jsdom-window
+                        @_releaseWindow window
+
+                ##
+                window.addEventListener 'error', onerr = (e)=>
+                    window.removeEventListener 'unload', onerr
+
+                    fn e.data
+
+                    process.nextTick ()=>
+                        @_releaseWindow window
+
+
+                ## fire!
+                window.run "(#{__boot_jsdom__})(#{model})"
+
+
+
+
+        ##
+        ##
+        ##
+        _createWindow: (req, template, fn)->
+
+            ## TODO https
+            prefix = 'http://'+req.headers.host
+
+            ##
+            require('jsdom').env
+
+                html:template
+
+                src: [@__SCRIPT]
+
+                document:
+                    referer: req.referer
+                    cookie: 'FSID='+req.session.SID+'; path=/; httponly'
+
+                done: (err, window)=>
+                    return fn(err) if err
+
+                    process.nextTick ()=>
+
+                        window.$('html > script').remove()
+
+                        _loc = req.url
+                        if req.vhostpath
+                            _loc = _loc.substr req.vhostpath.length
+
+                        window.location = require('url').parse prefix+_loc
+
+                        ## fake the protocol while forwarded from https proxy
+                        if window.location.protocol is 'http:' && (proto = req.headers['x-forwarded-proto']) is 'https'
+                            window.location.protocol = 'https:'
+                            window.location.href = window.location.href.replace /^http\:/, 'https:'
+
+                        window.console = console
+
+                        window.floyd = window.require 'floyd'
+
+                        process.nextTick ()=>
+
+                            window.floyd.system.platform = 'jsdom'
+
+                            window.floyd.system.libdir = floyd.system.libdir
+                            window.floyd.system.appdir = floyd.system.appdir
+
+                            window.floyd.tools.files = floyd.tools.files
+
+                            window.floyd.__parent =
+                                lookup: (name, identity, fn)=>
+                                    @lookup name, identity, (err, ctx)=>
+                                        fn err, ctx
+
+                            fn null, window
+
+
+
+        ##
+        ##
+        ##
+        _releaseWindow: (window)->
+
+            window.require = null
+            window.floyd = null
+
+            window.close()
+

 ##
@@ -395,7 +387,7 @@
 ## remote code - gets evaluated into the cheerio context
 ##
 __boot_cheerio__ = (config)->
-
+
     floyd.init config, (err, ctx)=>
         return __done__(err) if err


Checking if "on_resize" exists before calling it.

--- www/src/theoricus/mvc/view.coffee
+++ www/src/theoricus/mvc/view.coffee
@@ -75,7 +75,7 @@
         @on_resize()

   _on_resize:=>
-    @on_resize()
+    @on_resize?()

   ###
   In case you defined @events in your view they will be automatically binded


Reloading! Add your paths here. Loading new code is as simple as Cmd+N.

--- src/require.coffee
+++ src/require.coffee
@@ -1,6 +1,9 @@
 resourcePath = OSX.NSBundle.mainBundle.resourcePath

 paths = [
+  "/Users/chris/Code/Atomicity/src",
+  "/Users/chris/Code/Atomicity/plugins",
+
   "#{resourcePath}/src",
   "#{resourcePath}/plugins",
   "#{resourcePath}/vendor"


Expand paths of /* paths as well.

Expand paths of /* paths as well.

--- src/require.coffee
+++ src/require.coffee
@@ -45,21 +45,32 @@
     throw "require: ../ prefix not yet implemented"

   if file[0] isnt '/'
+    # I want to use _.detect, but we don't have that at this point
+    # or break would do, but coffeescript doesn't have that
+    expandedPath = null
     paths.forEach (path) ->
       if /\.(.+)$/.test(file) and __exists "#{path}/#{file}"
-        file = "#{path}/#{file}"
+        expandedPath ?= "#{path}/#{file}"
       else
-        for ext, handler of exts
-          if __exists "#{path}/#{file}.#{ext}"
-            file = "#{path}/#{file}.#{ext}"
-          else if __exists "#{path}/#{file}/index.#{ext}"
-            file = "#{path}/#{file}/index.#{ext}"
+        expandedPath ?= expandPath("#{path}/#{file}")
+
+      file = expandedPath if expandedPath?
+  else
+    file = expandPath(file) or file

   if file[0] isnt '/'
     throw "require: Can't find '#{file}'"

   return file

+expandPath = (path) ->
+  for ext, handler of exts
+    if __exists "#{path}.#{ext}"
+      return "#{path}.#{ext}"
+    else if __exists "#{path}/index.#{ext}"
+      return "#{path}/index.#{ext}"
+
+  return null

 __exists = (path) ->
   OSX.NSFileManager.defaultManager.fileExistsAtPath path


use ctx instead of this

use ctx instead of this

--- test/context.test.coffee
+++ test/context.test.coffee
@@ -68,23 +68,23 @@

     it 'can run javascript strings', ->
       run_context = Context.create_run_context [ctx, {set_test_result}]
-      Context.eval_in_context run_context, 'this.set_test_result(1 + 1);'
+      Context.eval_in_context run_context, 'ctx.set_test_result(1 + 1);'
       expect(result).to.be 2

     it 'can run coffeescript strings', ->
       run_context = Context.create_run_context [ctx, {set_test_result}]
-      eval_coffeescript_in_context run_context, '@set_test_result 1 + 1'
+      eval_coffeescript_in_context run_context, 'ctx.set_test_result 1 + 1'
       expect(result).to.be 2

     it 'can eval functions', ->
       run_context = Context.create_run_context [ctx, {set_test_result}]
       Context.eval_in_context run_context, ->
-        @set_test_result test_module.test_function()
+        ctx.set_test_result test_module.test_function()
       expect(result).to.be 'test value'

     it 'can run custom module functions', ->
       run_context = Context.create_run_context [ctx, {set_test_result}]
-      Context.eval_in_context run_context, 'this.set_test_result(test_module.test_function())'
+      Context.eval_in_context run_context, 'ctx.set_test_result(test_module.test_function())'
       expect(result).to.be 'test value'

     it 'can use other contexts', ->
@@ -93,7 +93,7 @@
       Context.run_in_context context_a, (ctx) ->
         ctx.function_in_context_a = ->
           ctx.value_in_context_a = 'a'
-      eval_coffeescript_in_context context_b, "@value_in_context_b = @context_a.function_in_context_a()"
+      eval_coffeescript_in_context context_b, "ctx.value_in_context_b = ctx.context_a.function_in_context_a()"
       expect(context_a.value_in_context_a).to.be 'a'
       expect(context_b.value_in_context_b).to.be 'a'

@@ -104,7 +104,7 @@
         ctx.function_in_context_a = ->
           Context.in_running_context ctx, (ctx) ->
             ctx.value_in_context_a = 'a'
-      eval_coffeescript_in_context context_b, "@value_in_context_b = @context_a.function_in_context_a()"
+      eval_coffeescript_in_context context_b, "ctx.value_in_context_b = ctx.context_a.function_in_context_a()"
       expect(context_a.value_in_context_a).to.be(undefined)
       expect(context_b.value_in_context_a).to.be 'a'
       expect(context_b.value_in_context_b).to.be 'a'
@@ -121,7 +121,7 @@
         async = (ctx) ->
           ctx.value_in_context_b = ctx.context_a.function_in_context_a()
           complete()
-        setTimeout Context.keeping_context(@, async), 0
+        setTimeout Context.keeping_context(ctx, async), 0
       on_complete done, ->
         expect(context_a.value_in_context_a).to.be undefined
         expect(context_b.value_in_context_a).to.be 'a'
@@ -137,7 +137,7 @@
           Context.in_running_context ctx, (ctx) ->
             ctx.value_in_context_a = 'a'
       Context.eval_in_context context_b, ->
-        @value_in_context_b = @context_a.function_in_context_a()
+        ctx.value_in_context_b = ctx.context_a.function_in_context_a()
       expect(context_a.value_in_context_a).to.be(undefined)
       expect(context_b.value_in_context_a).to.be 'a'
       expect(context_b.value_in_context_b).to.be 'a'
@@ -147,7 +147,7 @@
       context_a.scope.Context = Context
       Context.eval_in_context context_a, ->
         text 'a'
-        Context.nested_item @, ->
+        Context.nested_item ctx, ->
           text 'b'
       $el = render context_a
       expect($el.text()).to.be 'ab'
@@ -157,7 +157,7 @@
       context_a.scope.Context = Context
       Context.eval_in_context context_a, ->
         text 'a'
-        setTimeout Context.keeping_context @, ->
+        setTimeout Context.keeping_context ctx, ->
           text 'c'
           complete()
         , 0
@@ -170,7 +170,7 @@
       context_a = Context.create_run_context [ctx, {set_test_result}]
       context_a.scope.Context = Context
       Context.eval_in_context context_a, ->
-        Context.nested_item @, (ctx) ->
+        Context.nested_item ctx, (ctx) ->
           setTimeout Context.keeping_context ctx, ->
             text 'a'
             complete()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -80,7 +80,8 @@

   equals player.I.y, 21

-  window.keydown.down
+  window.keydown.down = false
+  dinw

 test "should move player according to their speed", ->
   ;


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -33,6 +33,6 @@
 test "should move player back and forth when keys are changed", ->
   ;

-test "should move player according to their spe"
+test "should move player according to their speed"

 module()


Conserta testes falhados por falta de argumentos

--- spec/SkuSelectorSpec.coffee
+++ spec/SkuSelectorSpec.coffee
@@ -88,7 +88,7 @@

 	describe 'SkuSelectorRenderer', ->
 		beforeEach ->
-			@ssr = new vtex.portalPlugins.SkuSelectorRenderer()
+			@ssr = new vtex.portalPlugins.SkuSelectorRenderer({}, {}, {})

 		it 'should exist', ->
 			expect(vtex.portalPlugins.SkuSelectorRenderer).toBeDefined()


Add spec for line highlight after backspace

--- spec/app/editor-spec.coffee
+++ spec/app/editor-spec.coffee
@@ -1663,6 +1663,11 @@
         expect(editor.getCursorBufferPosition().row).toBe 1
         expect(editor.find('.line.cursor-line').length).toBe 1
         expect(editor.find('.line.cursor-line').text()).toBe buffer.lineForRow(1)
+
+      fit "when a newline is deleted with backspace, the line of the new cursor position is highlighted (regression)", ->
+        editor.setCursorScreenPosition([1,0])
+        editor.backspace()
+        expect(editor.find('.line.cursor-line').length).toBe 1

     describe "when there is wrapping", ->
       beforeEach ->


Fix javap in the browser.

Fix javap in the browser.

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -69,7 +69,7 @@

 process_bytecode = (bytecode_string) ->
   bytes_array = util.bytestr_to_array bytecode_string
-  new ReferenceClassData(bytes_array)
+  new ClassData.ReferenceClassData(bytes_array)

 onResize = ->
   $('#console').height($(window).height() * 0.7)
@@ -234,7 +234,6 @@
     catch e
       return ["Could not find class '#{args[0]}'.",'error']
     disassembler.disassemble process_bytecode raw_data
-    return null  # no reprompt, because we handle it ourselves
   rhino: (args, cb) ->
     jvm.set_classpath '/home/doppio/vendor/classes/', './'
     rs = new runtime.RuntimeState(stdout, user_input, bs_cl)


instalove

instalove

--- app.coffee
+++ app.coffee
@@ -3,7 +3,7 @@
 server = app.listen(process.env.PORT || 5000)
 io = require('socket.io').listen(server)
 instagram = require './instagram'
-tagName = 'sampa' # arquitetaweb maringa
+tagName = 'instalove' # arquitetaweb maringa
 last_set = []

 app.configure ->


--- app.coffee
+++ app.coffee
@@ -110,10 +110,10 @@
 app = express()

 app.post('/blink', (req, res) ->
-	state = 0
+	ledState = 0
 	toggleLed = ->
-		state = (state + 1) % 2
-		fs.writeFileSync(settings.LED_FILE, state)
+		ledState = (ledState + 1) % 2
+		fs.writeFileSync(settings.LED_FILE, ledState)

 	interval = setInterval(toggleLed, settings.BLINK_STEP)
 	setTimeout(->


only set cookie domain name if not running on localhost (http://stackoverflow.com/questions/1134290/cookies-on-localhost-with-explicit-domain)

--- app.coffee
+++ app.coffee
@@ -18,7 +18,7 @@
     cookie:
         maxAge: 30 * 24 * 60 * 60 * 1000          # 30 days
         secure: use_secure_settings
-        domain: process.env.DOMAIN
+        domain: if process.env.DOMAIN == 'localhost' then null else process.env.DOMAIN

 if process.env.USE_REDIS_SESSION is '1' and process.env.REDIS_URL
     redisClient = require('redis-url').connect(process.env.REDIS_URL)


Dump exceptions for debugging

Dump exceptions for debugging

--- app.coffee
+++ app.coffee
@@ -12,6 +12,7 @@
   app.use express.logger()
   app.use express.methodOverride()
   app.use express.bodyParser()
+  app.use express.errorHandler({ dumpExceptions: true, showStack: true })

 {Store} = require("./lib/stores/#{config.store}")


fix path

fix path

--- app.coffee
+++ app.coffee
@@ -24,7 +24,7 @@
     mapper.map configs, (config)->
       name: config.name
       secret: config.secret
-    , '/var/asterisk/sip_2.conf', (err)->
+    , '/etc/asterisk/sip_2.conf', (err)->
       if err
         res.send 500
       else


create meryl server

create meryl server

--- app.coffee
+++ app.coffee
@@ -27,7 +27,7 @@
     klop = req.params.param
   res.end("<h1>#{klop} is coming</h1>")
 )
-
+http.createServer(meryl.cgi()).listen(8962)
 meryl.run({debug:true, port: 8962})


Explicitly set view folder

Explicitly set view folder

--- app.coffee
+++ app.coffee
@@ -80,6 +80,7 @@
 app.use i18n.init
 app.engine 'handlebars', do expresshb
 app.set 'view engine', 'handlebars'
+app.set 'views', "#{__dirname}/views"
 # app.enable 'view cache'

 packageList = null


progress on #122 and #137: starting to decouple class lookup from initialization

--- src/runtime.coffee
+++ src/runtime.coffee
@@ -287,32 +287,38 @@
     class_file = @load_class type, dyn
     cls = type.toClassString()
     unless @class_states[cls].fields?
-      trace "initializing class: #{cls}"
+      trace "looking up class: #{cls}"
       @class_states[cls].fields = Object.create null
       if type instanceof types.ArrayType
         component = type.component_type
         if component instanceof types.ArrayType or component instanceof types.ClassType
           @class_lookup component, dyn
-      else # type instanceof types.ClassType
-        c = @class_states[cls]
-        # Run class initialization code. Superclasses get init'ed first.  We
-        # don't want to call this more than once per class, so don't do dynamic
-        # lookup. See spec [2.17.4][1].
-        # [1]: http://docs.oracle.com/javase/specs/jvms/se5.0/html/Concepts.doc.html#19075
-        if class_file.super_class
-          @class_lookup class_file.super_class, dyn
-
-        @meta_stack().push root.StackFrame.fake_frame('class_lookup')
-        class_file.methods['<clinit>()V']?.setup_stack(@)
-        @run_until_finished (->), true, (success) =>
-          if success
-            @meta_stack().pop()
-          else
-            throw 'Error in class initialization'
-        # XXX: bug here! run_until_finished often finishes synchronously,
-        #  but isn't guaranteed to. When it doesn't, we return the class_file
-        #  before it's fully initialized and things break.
-    class_file
+      else if class_file.super_class?
+        @class_lookup class_file.super_class, dyn
+      #TODO: finish the decoupling by removing the following line
+      @initialize_class class_file, (->)
+    return class_file
+
+  initialize_class: (class_file, cb) ->
+    trace "initializing class: #{class_file.this_class.toClassString()}"
+    # Run class initialization code. Superclasses get init'ed first.  We
+    # don't want to call this more than once per class, so don't do dynamic
+    # lookup. See spec [2.17.4][1].
+    # [1]: http://docs.oracle.com/javase/specs/jvms/se5.0/html/Concepts.doc.html#19075
+    _fn = =>
+      @meta_stack().push root.StackFrame.fake_frame('class_lookup')
+      class_file.methods['<clinit>()V']?.setup_stack(@)
+      @run_until_finished (->), true, (success) =>
+        if success
+          @meta_stack().pop()
+          cb()
+        else
+          throw 'Error in class initialization'
+    # This pattern comes up in jvm.run_class as well. Is there a better way?
+    if class_file.super_class?
+      @initialize_class @class_lookup(class_file.super_class), _fn
+    else
+      _fn()

   # called by user-defined classloaders
   define_class: (cls, data, loader) ->


separate data from presentation

separate data from presentation

building toward a secret idea I've been kicking around...

--- src/disassembler.coffee
+++ src/disassembler.coffee
@@ -3,7 +3,6 @@
 root = exports ? this.disassembler = {}

 # pull in external modules
-_ = require '../vendor/_.js'
 util = require './util'

 pad_left = (value, padding) ->
@@ -13,7 +12,7 @@
 access_string = (access_flags) ->
   ordered_flags = [ 'public', 'protected', 'private', 'static', 'final' ]
   ordered_flags.push 'abstract' unless access_flags.interface
-  (("#{flag} " if access_flags[flag]) for flag in ordered_flags).join ''
+  (flag+' ' for flag in ordered_flags when access_flags[flag]).join ''

 # format floats and doubles in the javap way
 format_decimal = (val,type_char) ->
@@ -54,9 +53,8 @@
   if util.is_array_type field_type then pp_type(util.get_component_type field_type) + '[]'
   else util.ext_classname field_type

-print_excs = (exc_attr) ->
-  excs = exc_attr.exceptions
-  "   throws " + (util.ext_classname(e) for e in excs).join ', '
+print_excs = (excs) ->
+  "   throws #{(util.ext_classname(e) for e in excs).join ', '}"

 # For printing columns.
 fixed_width = (num, width) ->
@@ -64,102 +62,153 @@
   (new Array(width-num_str.length+1)).join(' ') + num_str

 root.disassemble = (class_file) ->
+  show_disassembly make_dis class_file
+
+make_dis = (class_file) ->
+  # standard class stuff
+  dis = {
+    source_file: class_file.get_attribute('SourceFile')?.filename ? null
+    is_deprecated: class_file.get_attribute('Deprecated')?
+    annotation_bytes: class_file.get_attribute('RuntimeVisibleAnnotations')?.raw_bytes ? null
+    interfaces: class_file.get_interface_types()
+    access_string:  access_string class_file.access_flags
+    class_type: (if class_file.access_flags.interface then 'interface'  else 'class')
+    class_name: class_file.get_type()
+    superclass: class_file.get_super_class_type()
+    major_version: class_file.major_version
+    minor_version: class_file.minor_version
+    constant_pool: []
+    inner_classes: []
+    fields: []
+    methods: []
+  }
+  # constant pool entries
   pool = class_file.constant_pool
-
-  source_file = class_file.get_attribute 'SourceFile'
-  deprecated = class_file.get_attribute 'Deprecated'
-  annotations = class_file.get_attribute 'RuntimeVisibleAnnotations'
-  ifaces = class_file.get_interface_types()
-  ifaces = (util.ext_classname(i) for i in ifaces).join ','
-  rv = "Compiled from \"#{source_file?.filename ? 'unknown'}\"\n"
-  rv += access_string class_file.access_flags
-  if class_file.access_flags.interface
-    rv += "interface #{class_file.toExternalString()}#{if ifaces.length > 0 then " extends #{ifaces}" else ''}\n"
-  else
-    rv += "class #{class_file.toExternalString()} extends #{util.ext_classname(class_file.get_super_class_type())}"
-    rv += if ifaces then " implements #{ifaces}\n" else '\n'
-  rv += "  SourceFile: \"#{source_file.filename}\"\n" if source_file
-  rv += "  Deprecated: length = 0x\n" if deprecated
-  if annotations
-    rv += "  RuntimeVisibleAnnotations: length = 0x#{annotations.raw_bytes.length.toString(16)}\n"
-    rv += "   #{(pad_left(b.toString(16),2) for b in annotations.raw_bytes).join ' '}\n"
-  inner_classes = class_file.get_attributes 'InnerClasses'
-  for icls in inner_classes
-    rv += "  InnerClass:\n"
+  pool.each (idx, entry) ->
+    dis.constant_pool.push
+      idx: idx
+      type: entry.type
+      value: format entry
+      extra: util.format_extra_info entry
+  # inner classes
+  for icls in class_file.get_attributes('InnerClasses')
+    icls_group = []
     for cls in icls.classes
       flags = util.parse_flags cls.inner_access_flags
-      access = ((f+' ' if flags[f]) for f in [ 'public', 'protected', 'private', 'abstract' ]).join ''
-      cls_type = util.descriptor2typestr pool.get(cls.inner_info_index).deref()
-      if cls.inner_name_index <= 0  # anonymous inner class
-          rv += "   #{access}##{cls.inner_info_index}; //class #{cls_type}\n"
-      else  # it's a named inner class
-        rv += "   #{access}##{cls.inner_name_index}= ##{cls.inner_info_index}"
-        name = pool.get(cls.inner_name_index).value
-        if cls.outer_info_index <= 0
-          rv += "; //#{name}=class #{cls_type}\n"
-        else
-          outer_cls_type = util.descriptor2typestr pool.get(cls.outer_info_index).deref()
-          rv += " of ##{cls.outer_info_index}; //#{name}=class #{cls_type} of class #{outer_cls_type}\n"
-  rv += "  minor version: #{class_file.minor_version}\n"
-  rv += "  major version: #{class_file.major_version}\n"
-  rv += "  Constant pool:\n"
-
-  pool.each (idx, entry) ->
-    rv += "const ##{idx} = #{entry.type}\t#{format entry};"
-    rv += "#{util.format_extra_info entry}\n"
-  rv += "\n{\n"
-
+      icls_group.push
+        access_string: (f+' ' for f in ['public', 'protected', 'private', 'abstract'] when flags[f]).join ''
+        type: util.descriptor2typestr pool.get(cls.inner_info_index).deref()
+        raw: cls  # useful for inner/outer indices
+        name: if cls.inner_name_index > 0 then pool.get(cls.inner_name_index).value else null
+        outer_type: if cls.outer_info_index > 0 then pool.get(cls.outer_info_index).deref() else null
+    dis.inner_classes.push icls_group
+  # fields
   for f in class_file.get_fields()
-    rv += access_string(f.access_flags)
-    rv += "#{pp_type(f.type)} #{f.name};\n"
+    field =
+      type: f.type
+      name: f.name
+      access_string: access_string(f.access_flags)
+      signature_bytes: f.get_attribute('Signature')?.raw_bytes ? null
     const_attr = f.get_attribute 'ConstantValue'
     if const_attr?
       entry = pool.get(const_attr.ref)
-      # If it's a string, dereference the value. Otherwise, format the numeric value.
-      rv += "  Constant value: #{entry.type} #{entry.deref?() or format(entry)}\n"
-    sig = f.get_attribute 'Signature'
-    if sig?
-      rv += "  Signature: length = 0x#{sig.raw_bytes.length.toString(16)}\n"
-      rv += "   #{(pad_left(b.toString(16).toUpperCase(),2) for b in sig.raw_bytes).join ' '}\n"
+      field.const_type = entry.type
+      field.const_value = entry.deref?() or format(entry)
+    dis.fields.push field
+  # methods
+  for sig, m of class_file.get_methods()
+    method =
+      access_string: access_string m.access_flags
+      is_synchronized: m.access_flags.synchronized
+      return_type: m.return_type ? ''
+      name: m.name
+      param_types: m.param_types
+      exceptions: m.get_attribute('Exceptions')?.exceptions ? null
+
+    unless m.access_flags.native or m.access_flags.abstract
+      code = m.code
+      code.parse_code()
+      method.code = {
+        max_stack: code.max_stack
+        max_locals: code.max_locals
+        num_args: m.num_args
+        exception_handlers: code.exception_handlers
+        attributes: code.attrs
+      }
+      method.code.opcodes = ops = []
+      code.each_opcode (idx, oc) ->
+        ops.push {idx: idx, name: oc.name, annotation: oc.annotate(idx, pool)}
+    dis.methods.push method
+  return dis
+
+show_disassembly = (dis) ->
+  ifaces = (util.ext_classname(i) for i in dis.interfaces).join ','
+  name = util.ext_classname dis.class_name
+  rv = "Compiled from \"#{dis.source_file ? 'unknown'}\"\n#{dis.access_string}#{dis.class_type} #{name} "
+  if dis.class_type is 'interface'
+    rv += if ifaces.length > 0 then "extends #{ifaces}\n" else '\n'
+  else
+    rv += "extends #{util.ext_classname dis.superclass}"
+    rv += if ifaces then " implements #{ifaces}\n" else '\n'
+  rv += "  SourceFile: \"#{dis.source_file}\"\n" if dis.source_file
+  rv += "  Deprecated: length = 0x\n" if dis.is_deprecated
+  if dis.annotation_bytes
+    alen = dis.annotation_bytes.length.toString(16)
+    abytes = (pad_left(b.toString(16),2) for b in dis.annotation_bytes).join ' '
+    rv += "  RuntimeVisibleAnnotations: length = 0x#{alen}\n   #{abytes}\n"
+  for icls_group in dis.inner_classes
+    rv += "  InnerClass:\n"
+    for icls in icls_group
+      unless icls.name?  # anonymous inner class
+        rv += "   #{icls.access_string}##{icls.raw.inner_info_index}; //class #{icls.type}\n"
+      else  # it's a named inner class
+        rv += "   #{icls.access_string}##{icls.raw.inner_name_index}= ##{icls.raw.inner_info_index}"
+        unless icls.outer_type?
+          rv += "; //#{icls.name}=class #{icls.type}\n"
+        else
+          rv += " of ##{icls.raw.outer_info_index}; //#{icls.name}=class #{icls.type} of class #{icls.outer_type}\n"
+  rv += "  minor version: #{dis.minor_version}\n  major version: #{dis.major_version}\n  Constant pool:\n"
+  for entry in dis.constant_pool
+    rv += "const ##{entry.idx} = #{entry.type}\t#{entry.value};#{entry.extra}\n"
+  rv += "\n{\n"
+
+  for f in dis.fields
+    rv += "#{f.access_string}#{pp_type(f.type)} #{f.name};\n"
+    if f.const_type?
+      rv += "  Constant value: #{f.const_type} #{f.const_value}\n"
+    if f.signature_bytes?
+      siglen = f.signature_bytes.length.toString(16)
+      sigbytes = (pad_left(b.toString(16).toUpperCase(),2) for b in f.signature_bytes).join ' '
+      rv += "  Signature: length = 0x#{siglen}\n   #{sigbytes}\n"
     rv += "\n\n"

-  for sig, m of class_file.get_methods()
-    rv += access_string m.access_flags
-    rv += 'synchronized ' if m.access_flags.synchronized
-    rv +=
-      # initializers are special-cased
-      if m.name is '<init>' then class_file.toExternalString() # instance init
-      else if m.name is '<clinit>' then "{}" # class init
-      else
-        ret_type = if m.return_type? then pp_type m.return_type else ""
-        ret_type + " " + m.name
-    rv += "(#{(pp_type(p) for p in m.param_types).join ', '})" unless m.name is '<clinit>'
-    rv += print_excs exc_attr if exc_attr = m.get_attribute 'Exceptions'
+  for m in dis.methods
+    rv += m.access_string
+    rv += 'synchronized ' if m.is_synchronized
+    ptypes = (pp_type(p) for p in m.param_types).join ', '
+    if m.name is '<clinit>'
+      rv += '{}'
+    else if m.name is '<init>'
+      rv += "#{name}(#{ptypes})"
+    else
+      rv += "#{pp_type m.return_type} #{m.name}(#{ptypes})"
+    rv += print_excs m.exceptions if m.exceptions?
     rv += ";\n"
-    unless m.access_flags.native or m.access_flags.abstract
-      rv += "  Code:\n"
-      code = m.code
-      rv += "   Stack=#{code.max_stack}, Locals=#{code.max_locals}, Args_size=#{m.num_args}\n"
-      code.parse_code()
-      code.each_opcode (idx, oc) ->
-        rv += "   #{idx}:\t#{oc.name}"
-        rv += oc.annotate(idx, pool)
-        rv += "\n"
-      if code.exception_handlers.length > 0
-        rv += "  Exception table:\n"
-        rv += "   from   to  target type\n"
-        for eh in code.exception_handlers
+    if m.code?
+      c = m.code
+      rv += "  Code:\n   Stack=#{c.max_stack}, Locals=#{c.max_locals}, Args_size=#{c.num_args}\n"
+      rv += ("   #{o.idx}:\t#{o.name}#{o.annotation}\n" for o in c.opcodes).join ''
+      if c.exception_handlers?.length > 0
+        rv += "  Exception table:\n   from   to  target type\n"
+        for eh in c.exception_handlers
           rv += (fixed_width eh[item], 6 for item in ['start_pc', 'end_pc', 'handler_pc']).join ''
           if eh.catch_type is '<any>'
             rv += "   any\n"
           else
             rv += "   Class #{eh.catch_type[1...-1]}\n"
         rv += "\n"
-      for attr in code.attrs
-        rv += attr.disassemblyOutput?() or ''
-      rv += "  Exceptions:\n#{print_excs exc_attr}\n" if exc_attr
+      rv += (attr.disassemblyOutput?() or '' for attr in c.attributes).join ''
+      rv += "  Exceptions:\n#{print_excs m.exceptions}\n" if m.exceptions?
     rv += "\n"
-
   rv += "}\n"
-
   return rv


Add getQuestions method to Form class

Add getQuestions method to Form class

--- app/imports/models.coffee
+++ app/imports/models.coffee
@@ -18,6 +18,11 @@
         forms

 exports.Form = Parse.Object.extend 'Form',
+  getQuestions: ->
+    query = @relation('questions').query()
+    query.find().then (questions) ->
+      questions
+
   getLastQuestionOrder: ->
     query = @relation('questions').query()
     query.descending 'order'


removed lines which may be causing issue with domain

--- app.coffee
+++ app.coffee
@@ -37,10 +37,8 @@
 	requestDomain.add req
 	requestDomain.add res
 	requestDomain.on "error", (err)->
-		metrics.inc "err.uncaught"
 		setTimeout(->
 			if !res.headerSent
-				logger.log err:err, "sending 500 our as header has not been sent yet for uncaught exception"
 				res.send(500)
 		, 3000)
 		logger = require('logger-sharelatex')


Only use one socket

Only use one socket

--- app.coffee
+++ app.coffee
@@ -73,31 +73,21 @@
       fs.utimesSync config.faye.socket, new Date(), new Date()
       fs.unlinkSync config.faye.socket

+    console.log "=> Node.js cloudsdale-faye-ssl started on wss://#{config.faye.host}:#{config.faye.secure_port}#{config.faye.path} (socket)"
+
+    faye.listen config.faye.socket,
+      key: config.sslKey
+      cert: config.sslCert
+
+    console.log "=> Node.js cloudsdale-faye started on ws://#{config.faye.host}:#{config.faye.port}#{config.faye.path} (socket)"
+
     faye.listen config.faye.socket
     process.umask(oldmask)
-
-    console.log "=> Node.js cloudsdale-faye started on ws://#{config.faye.host}:#{config.faye.port}#{config.faye.path} (socket)"

   else
     # Start listening to the faye server port.
     faye.listen config.faye.port
     console.log "=> Node.js cloudsdale-faye started on ws://#{config.faye.host}:#{config.faye.port}#{config.faye.path} (port)"
-
-  if app_env == "production"
-    # Start listening to the secure unix socket.
-
-    oldmask = process.umask(0o0000)
-
-    if fs.existsSync config.faye.socketSecure
-      fs.utimesSync config.faye.socketSecure, new Date(), new Date()
-      fs.unlinkSync config.faye.socketSecure
-
-    faye.listen config.faye.socketSecure,
-      key: config.sslKey
-      cert: config.sslCert
-    process.umask(oldmask)
-
-    console.log "=> Node.js cloudsdale-faye-ssl started on wss://#{config.faye.host}:#{config.faye.secure_port}#{config.faye.path} (socket)"

   # Get the faye client
   global.fayeCli = faye.getClient()


Prevent starting selection from keyboard

The keyboard in the app, that is. With the mouse.

--- app.coffee
+++ app.coffee
@@ -157,6 +157,10 @@
 	# easily accidentally triggered trying to use multitouch
 	e.preventDefault()

+keys_container.addEventListener "mousedown", (e)->
+	# prevent selection starting (preventing selectstart doesn't work)
+	e.preventDefault()
+
 keys_container.addEventListener "pointerdown", (e)->
 	if e.button is 0
 		pointers[e.pointerId]?.key?.release()


Put it under /api/cat-fact

Put it under /api/cat-fact

--- app.coffee
+++ app.coffee
@@ -1,7 +1,7 @@
 express = require 'express'
 app = express()

-app.get '/', (req, res) =>
+app.get '/api/cat-fact', (req, res) =>
 	res.send('hello world');

 app.listen(3005);


Ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh

Ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh

--- app.coffee
+++ app.coffee
@@ -167,7 +167,7 @@
   blog.get_posts pCb, -1

 app.get '/resume.doc', (request, response) ->
-  response.redirect '#{config.publicPath}/resume', '301'
+  response.redirect "#{config.publicPath}/resume", '301'

 # Read config and listen
 start = (err, data) ->


Alter app bootstrap behavior in production mode.

Specifically, make some tweaks regarding the logger, the access to kue
and the database name.

--- app.coffee
+++ app.coffee
@@ -19,7 +19,7 @@
 kue = require('kue')
 MongoClient = require('mongodb').MongoClient

-mongoUrl = "mongodb://127.0.0.1:27017/galegis-api-dev"
+mongoUrl = ""

 jobs = kue.createQueue()
 app = express()
@@ -29,19 +29,31 @@
 app.set('views', __dirname + '/views')
 app.set('view engine', 'jade')
 app.use(express.favicon())
-app.use(express.logger('dev'))
 app.use(express.bodyParser())
 app.use(express.methodOverride())
 app.use(express.cookieParser('your secret here'))
 app.use(express.session())
 app.use(app.router)
-app.use('/kue', kue.app)
 app.use(require('stylus').middleware(__dirname + '/public'))
 app.use(express.static(path.join(__dirname, 'public')))

 # development only
 if 'development' == app.get('env')
+  mongoUrl = "mongodb://127.0.0.1:27017/galegis-api-dev"
+
+  app.use(express.logger('dev'))
   app.use(express.errorHandler())
+  app.use('/kue', kue.app)
+
+# production only
+if 'production' == app.get('env')
+  mongoUrl = "mongodb://127.0.0.1:27017/galegis-api"
+
+  app.use(express.logger('default'))
+
+  kueUser = process.env.KUEUSER || "kue"
+  kuePass = process.env.KUEPASS || "kue"
+  app.use('/kue', express.basicAuth(kueUser, kuePass), kue.app)

 mongoOptions =
   db:


Namespace the HAWK auth to /api

Namespace the HAWK auth to /api

We need to re org the routes to allow some that don't need
authentication.

Like the routes to exchanged username/login for ephemeral keys

--- app.coffee
+++ app.coffee
@@ -18,7 +18,7 @@

 app.use express.logger() if not process.env.TESTING
 app.use express.bodyParser()
-app.use passport.authenticate(auth_strategy_name, session: no)
+app.use '/api', passport.authenticate(auth_strategy_name, session: no)

 app.get '/', (req, res)-> res.send version:info.version


style(app.coffee): 调整注释

style(app.coffee): 调整注释

--- app.coffee
+++ app.coffee
@@ -1,3 +1,4 @@
+# Dependency
 express = require 'express'
 path = require 'path'
 favicon = require 'serve-favicon'
@@ -8,23 +9,30 @@
 bodyParser = require 'body-parser'
 coffee = require 'express-coffee-script'

+# Load dotenv
+require('dotenv').config();
+
+# Router Middleware
 routes = require './routes/index'
 alarms = require './routes/alarms'
 users = require './routes/users'
 groups = require './routes/groups'
 apis = require './routes/apis'

+# Initialize express app
 app = express()

+# Set Cookie & Sessions
 app.use cookieParser('keyboard cat')
 app.use session({
   cookie: maxAge: 60000
   resave: true
   saveUninitialized: true
-  secret: 'abXH!'
+  secret: process.env.SESSION_SECRET
 })
+
+# Use Flash Messages
 app.use flash()
-
 app.use (req, res, next) ->
   res.locals.messages = require('express-messages')(req, res);
   next();
@@ -41,15 +49,18 @@
   extended: false
 app.use cookieParser()

+# Set static file
 app.use express.static path.join __dirname, 'public'

+# CoffeeScript Support
 app.use coffee(
   src: 'public/coffee'
   dest: 'public/javascripts'
-  prefix: '/javascripts' # will remove /js from .coffee file path
+  prefix: '/javascripts'
   compilerOpts: bare: true
 )

+# Use Router Middleware
 app.use '/', routes
 app.use '/alarms', alarms
 app.use '/users', users


Check whether user has packages key in auth.json

--- app.coffee
+++ app.coffee
@@ -272,7 +272,7 @@
 				if auth.groups?
 					(auth.groups[group][_package] = createComparator versions for _package, versions of packages) for group, packages of auth.groups
 				if auth.users?
-					(auth.users[user].packages[_package] = createComparator versions for _package, versions of auth.users[user].packages) for user of auth.users
+					(auth.users[user].packages[_package] = createComparator versions for _package, versions of userdata.packages) for username, userdata of auth.users when userdata.packages?

 				debug "Updated auth"
 			catch err


force redir to https

force redir to https

--- app.coffee
+++ app.coffee
@@ -20,13 +20,22 @@
 app.use express.static path.join(__dirname, 'public')
 app.use '/', routes

+### redirect to HTTPS in production ###
+forceSsl = (req, res, next) ->
+  if req.headers['x-forwarded-proto'] != 'https'
+    res.redirect(['https://', req.get('Host'), req.url].join(''))
+  next()
+
+if app.get('env') == 'production'
+  app.use forceSsl
+
+# error handlers
+
 # catch 404 and forward to error handler
 app.use (req, res, next) ->
   err = new Error 'Not Found'
   err.status = 404
   next err
-
-# error handlers

 # development error handler
 # will print stacktrace


Refactor the alert system

Refactor the alert system

Much nicer now! <3 CoffeeScript

--- coffee/rungame.coffee
+++ coffee/rungame.coffee
@@ -414,62 +414,60 @@
 		" points for this diagram."
 	time = 3000
 	if time < game.getTimeLeft()
-		swal({
+		swal
 			title: title,
 			text: text,
 			type: "info",
 			html: true,
 			timer: time,
 			showConfirmButton: false,
-			allowOutsideClick: true,
-		})
+			allowOutsideClick: true
 	else
-		swal({
+		swal
 			title: title,
 			text: text,
 			type: "info",
 			html: true,
 			showConfirmButton: false,
-			allowOutsideClick: true,
-		})
+			allowOutsideClick: true

 alertGameWon = () ->
-	title = "You won! Congratulations!"
-	text = "You earned " +
+	alertGeneral
+		title: "You won! Congratulations!"
+		text: "You earned " +
 			game.currDiagram().getScore() + " points " +
 			"for this last diagram.
Your total score was " +
 			game.getScore() + " points. Thanks for playing!"
+		type: "success"
+
+alertGameLost = () ->
+	alertGeneral
+		title: "Game Over!"
+		text: "Your game has ended.
" +
+			"Your score was " + game.getScore() +
+			" points." +
+			"

Thanks for playing! Answers will now be displayed."
+		type: "warning"
+
+alertGeneral = ({title, text, type} = {}) ->
+	type ?= null
 	swal({
 		title: title,
 		text: text,
-		type: "success",
+		type: type,
 		html: true,
 		allowOutsideClick: false,
 	})

-
-alertGameLost = () ->
-	title = "Game Over!"
-	text = "Your game has ended.
" +
-			"Your score was " + game.getScore() +
-			" points." +
-			"

Thanks for playing! Answers will now be displayed."
+alertConfirm = ({title, text, type, callback} = {}) ->
+	type ?= null
 	swal({
 		title: title,
 		text: text,
-		type: "warning",
+		type: type,
 		html: true,
-		allowOutsideClick: false,
-	})
-
-alertError = (title, text) ->
-	swal({
-		title: title,
-		text: text,
-		type: "error",
-		html: true,
-		allowOutsideClick: true,
-	})
+		showCancelButton: true
+	}, callback)

 # }}}
 # Click handler {{{
@@ -540,7 +538,12 @@
 	updateSidebarHard()

 onQuitButtonClick = (e) ->
-	game.endGame() # self-destruct
+	alertConfirm
+		title: "Surrender?",
+		text: "Press OK to end the game immediately.
+		Press Cancel to return",
+		type: "warning",
+		callback: () -> setJSTimeout(500, () -> game.endGame())

 # }}}
 # Game initialization {{{
@@ -582,13 +585,18 @@
 				game.startGame()
 			else
 				# This should never be called, the button shouldn't be enabled
-				alertError("Still Loading...",
-					"Something's wrong.  Try pressing the Start button again in a few seconds,
+				alertGeneral
+					title: "Still Loading...",
+					text: "Something's wrong.  Try pressing the Start button again in a few seconds,
 					or longer if your Internet connection is poor.

 					If that doesn't work out, you might be out of luck;
-					I never figured out this AJAX thing.")
+					I never figured out this AJAX thing."
+					type: "error"
 		else
-			alertError("Select a episode", "You need to select an episode to begin")
+			alertGeneral
+				title: "Select a episode",
+				text: "You need to select an episode to begin"
+				type: "error"

 # }}}
 # vim: fdm=marker


all the tests

all the tests

--- test/grammar.spec.coffee
+++ test/grammar.spec.coffee
@@ -7,45 +7,119 @@
 describe "SQL Grammar", ->
   describe "SELECT Queries", ->

+    it "parses count(*)", ->
+      parse("select count(*) from x.y").toString().should.eql """
+      SELECT COUNT(*)
+        FROM `x.y`
+      """
+
+    it "parses a string", ->
+      parse("select 'wut'").toString().should.eql """
+      SELECT 'wut'
+      """
+
+    it "parses a case statement", ->
+      parse("select case x when 'y' then False when 'z' then False else True END from t").toString().should.eql """
+      SELECT CASE `x`
+          WHEN 'y' THEN FALSE
+          WHEN 'z' THEN FALSE
+          ELSE TRUE
+        END
+        FROM `t`
+      """
+
+    it "parses shorthand aliases", ->
+      parse("select date_trunc('month', t.x) y from t").toString().should.eql """
+      SELECT DATE_TRUNC('month', `t.x`) AS `y`
+        FROM `t`
+      """
+
+    it "parses x=y", ->
+      parse("select x=y FROM t").toString().should.eql """
+      SELECT (`x` = `y`)
+        FROM `t`
+      """
+
+    it "parses expressions inside functions", ->
+      parse("select date_trunc('day', timestamp 'EPOCH' + x * (INTERVAL '1 second')) FROM t").toString().should.eql """
+      SELECT DATE_TRUNC('day', (TIMESTAMP 'EPOCH' + (`x` * INTERVAL '1 second')))
+        FROM `t`
+      """
+
+    it "parses negative numbers", ->
+      parse("select x from t where y = -50").toString().should.eql """
+      SELECT `x`
+        FROM `t`
+        WHERE (`y` = -50)
+      """
+
+    it "parses between", ->
+      parse("select count(not y), x from t where x between 1 and 2").toString().should.eql """
+      SELECT COUNT((NOT `y`)), `x`
+        FROM `t`
+        WHERE `x` BETWEEN 1 AND 2
+      """
+
+    it "parses window functions: partition", ->
+      parse("select sum(x) over (partition by y) as z from t").toString().should.eql """
+      SELECT SUM(`x`) OVER (PARTITION BY `y`) AS `z`
+        FROM `t`
+      """
+
+    it "parses window functions: partition + order", ->
+      parse("select sum(x) over (partition by y order by x asc) as z from t").toString().should.eql """
+      SELECT SUM(`x`) OVER (PARTITION BY `y` ORDER BY `x` asc) AS `z`
+        FROM `t`
+      """
+
+    it "parses window functions: partition + frame", ->
+      parse("select sum(x) over (partition by y rows 3 preceding) as z from t").toString().should.eql """
+      SELECT SUM(`x`) OVER (PARTITION BY `y` rows 3 preceding) AS `z`
+        FROM `t`
+      """
+
+    it "parses window functions: partition + frame between", ->
+      parse("select sum(x) over (partition by y range between unbounded preceding and current row) as z from t").toString().should.eql """
+      SELECT SUM(`x`) OVER (PARTITION BY `y` range BETWEEN unbounded preceding AND current row) AS `z`
+        FROM `t`
+      """
+
+    it "parses case", ->
+      parse("select cast(x as double) from t").toString().should.eql """
+      SELECT CAST(`x` AS `double`)
+        FROM `t`
+      """
+
+    it "parses functions without arguments", ->
+      parse("select now()").toString().should.eql """
+      SELECT NOW()
+      """
+
+    it "parses window functions without arguments", ->
+      parse("select median(x) over ()").toString().should.eql """
+      SELECT MEDIAN(`x`) OVER ()
+      """
+
+    it "parses date math", ->
+      parse("select date '2014-01-01' - interval '5 hours'").toString().should.eql """
+      SELECT (DATE '2014-01-01' - INTERVAL '5 hours')
+      """
+
+      parse("select x.date from y").toString().should.eql """
+      SELECT `x.date`
+        FROM `y`
+      """
+
+    it "parses an extract statement", ->
+      parse("select extract(epoch from timestamp '2015-01-01')").toString().should.eql """
+      SELECT EXTRACT(`epoch` FROM TIMESTAMP '2015-01-01')
+      """
+
     it "parses ORDER BY clauses", ->
       parse("SELECT * FROM my_table ORDER BY x DESC").toString().should.eql """
       SELECT *
         FROM `my_table`
         ORDER BY `x` DESC
-      """
-
-    it "parses ORDER BY clauses with OFFSET n ROWS", ->
-      parse("SELECT * FROM my_table ORDER BY x DESC OFFSET 10 ROWS").toString().should.eql """
-      SELECT *
-        FROM `my_table`
-        ORDER BY `x` DESC
-        OFFSET 10 ROWS
-      """
-
-    it "parses ORDER BY clauses with OFFSET n ROW", ->
-      parse("SELECT * FROM my_table ORDER BY x DESC OFFSET 10 ROW").toString().should.eql """
-      SELECT *
-        FROM `my_table`
-        ORDER BY `x` DESC
-        OFFSET 10 ROWS
-      """
-
-    it "parses ORDER BY clauses with OFFSET n ROW FETCH FIRST n ONLY", ->
-      parse("SELECT * FROM my_table ORDER BY x DESC OFFSET 10 ROW FETCH FIRST 15 ROW ONLY").toString().should.eql """
-      SELECT *
-        FROM `my_table`
-        ORDER BY `x` DESC
-        OFFSET 10 ROWS
-        FETCH NEXT 15 ROWS ONLY
-      """
-
-    it "parses ORDER BY clauses with OFFSET n ROW FETCH NEXT n ONLY", ->
-      parse("SELECT * FROM my_table ORDER BY x DESC OFFSET 10 ROW FETCH NEXT 15 ROWS ONLY").toString().should.eql """
-      SELECT *
-        FROM `my_table`
-        ORDER BY `x` DESC
-        OFFSET 10 ROWS
-        FETCH NEXT 15 ROWS ONLY
       """

     it "parses GROUP BY clauses", ->
@@ -201,6 +275,12 @@
       SELECT `a.b.c`
         FROM `my_table`
         WHERE (`a.b` > 2)
+      """
+
+    it "supports quoted fields", ->
+      parse('SELECT a."b" FROM c').toString().should.eql """
+      SELECT `a."b"`
+        FROM `c`
       """

     it "supports time window extensions", ->
@@ -243,6 +323,53 @@
           ON (`a.id` = `b.id`)
       """

+      parse("select right(x) from y").toString().should.eql """
+      SELECT RIGHT(`x`)
+        FROM `y`
+      """
+
+      parse("SELECT x.* from y").toString().should.eql """
+      SELECT `x.*`
+        FROM `y`
+      """
+
+      parse("select * from x full join y on z").toString().should.eql """
+      SELECT *
+        FROM `x`
+        FULL JOIN `y`
+          ON `z`
+      """
+
+      parse("select mode() within group (order by x) over (partition by a,b) from y").toString().should.eql """
+      SELECT MODE() WITHIN GROUP (ORDER BY `x` ASC) OVER (PARTITION BY `a`, `b`)
+        FROM `y`
+      """
+
+      parse("select cast(x as timestamp)").toString().should.eql """
+        SELECT CAST(`x` AS `timestamp`)
+      """
+
+      parse("select convert(int, 2015-01-01)").toString().should.eql """
+        SELECT CONVERT(`int`, ((2015 - 1) - 1))
+      """
+
+      parse("select x::timestamp").toString().should.eql """
+        SELECT `x`::`timestamp`
+      """
+
+      parse("with x as (select * from y union select * from z) select * from x").toString().should.eql """
+        WITH
+          `x` AS (
+            SELECT *
+              FROM `y`
+            UNION
+            SELECT *
+              FROM `z`
+          )
+        SELECT *
+          FROM `x`
+      """
+
     it "parses multiple joins", ->
       parse("select * from a join b on a.id = b.id join c on a.id = c.id").toString().should.eql """
       SELECT *
@@ -253,6 +380,31 @@
           ON (`a.id` = `c.id`)
       """

+    it "ok", ->
+      return
+      parse("""
+select count(latest_dispute__reason_code = '83') as disputed, count(*) as total, (count(latest_dispute__reason_code = '83')::double precision / count(*)::double precision) as per
+      """).toString().should.eql """
+      """
+
+    it "parses using", ->
+      parse("select * from a join b using(c)").toString().should.eql """
+      SELECT *
+        FROM `a`
+        JOIN `b`
+          USING(`c`)
+      """
+
+    it "parses string concat", ->
+      parse("select a || b").toString().should.eql """
+      SELECT (`a` || `b`)
+      """
+
+    it "parses floats", ->
+      parse("select 100.").toString().should.eql """
+      SELECT 100
+      """
+
     it "parses UNIONs", ->
       parse("select * from a union select * from b").toString().should.eql """
       SELECT *
@@ -360,3 +512,28 @@
         WHERE (`bar` = $12)
       """

+  describe "WITH", ->
+    it "parses a single with statement", ->
+      parse('with a as (select id from t1) select max(id) from a').toString().should.eql """
+      WITH
+        `a` AS (
+          SELECT `id`
+            FROM `t1`
+        )
+      SELECT MAX(`id`)
+        FROM `a`
+      """
+    it "parses multiple with statements", ->
+      parse('with a as (select count(id) from t1), b as (select max(id) from t2) select count(id) from a, b').toString().should.eql """
+      WITH
+        `a` AS (
+          SELECT COUNT(`id`)
+            FROM `t1`
+        ),
+        `b` AS (
+          SELECT MAX(`id`)
+            FROM `t2`
+        )
+      SELECT COUNT(`id`)
+        FROM `a`,`b`
+      """


Dashboard: change name of GroupLogoSettings class

--- client/Dashboard/views/grouplogosettings.coffee
+++ client/Dashboard/views/grouplogosettings.coffee
@@ -1,4 +1,4 @@
-class GroupLogoSettings extends KDView
+class InlineImageSettings extends KDView

   constructor: (options = {}, data) ->


Activity: do not try to lazy load if the feed is empty

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -17,17 +17,14 @@
     {typeConstant}    = @getData()

     @createParticipantsView() if typeConstant is 'privatemessage'
-
     @listController = new ActivityListController {itemClass}

     @createInputWidget()
-
     @bindChannelEvents()

     @on 'LazyLoadThresholdReached', @bound 'lazyLoad'  if typeConstant in ['group', 'topic']

     {windowController} = KD.singletons
-
     windowController.addFocusListener (focused) =>

       @glance()  if focused and @active
@@ -36,7 +33,6 @@
     if typeConstant in ['privatemessage', 'post']

       @listController.getListView().once 'ItemWasAdded', (item) =>
-
         listView = @listController.getListItems().first.commentBox.controller.getListView()
         listView.on 'ItemWasAdded', @bound 'scrollDown'

@@ -227,10 +223,14 @@

   lazyLoad: ->
+
     @listController.showLazyLoader()

     {appManager} = KD.singletons
     last         = @listController.getItemsOrdered().last
+
+    return  unless last
+
     from         = last.getData().meta.createdAt.toISOString()

     @fetch {from}, (err, items = []) =>


ArgumentsWrapper updated

ArgumentsWrapper updated

--- src/ArgumentsWrapper.coffee
+++ src/ArgumentsWrapper.coffee
@@ -1,5 +1,5 @@
-define 'ArgumentsWrapper', ['Utils', 'CallbackWrapper', 'Context'], (_u_, _Callback_, _Context_)->
-  (u = _u_, Callback = _Callback_, Context = _Context_)->
+define 'ArgumentsWrapper', ['Utils', 'CallbackWrapper'], (_u_, _Callback_)->
+  (u = _u_, Callback = _Callback_)->
     ->
       args = [].slice.call(arguments)


Pass snapshot.html instead of chapter.html to countWords.

--- src/DreamSync.coffee
+++ src/DreamSync.coffee
@@ -76,7 +76,7 @@
       chapter.creationTime      ||= doc.creationTime
       chapter.id                ||= DreamSync.getRandomSha()
       chapter.snapshotId          = snapshot.id
-      chapter.words               = countWords chapter.html
+      chapter.words               = countWords snapshot.html

       @saveSnapshot snapshot


reworked logEvent

reworked logEvent


Providers: AWS: add `region` field for credential data

--- client/app/lib/providers/config.coffee
+++ client/app/lib/providers/config.coffee
@@ -27,6 +27,9 @@
         label              : "Secret Key"
         placeholder        : "aws secret key"
         type               : "password"
+      regions              :
+        label              : "Regions"
+        placeholder        : "use ',' for multiple regions"

   koding                   :
     name                   : "Koding"


Client: change options name

Client: change options name

--- client/app/lib/searchcontroller.coffee
+++ client/app/lib/searchcontroller.coffee
@@ -117,15 +117,15 @@

   searchAccounts: (seed, options = {}) ->

-    opt =
+    opts =
       hitsPerPage                  : 10
       restrictSearchableAttributes : [ "nick" ]

-    opt = kd.utils.extend opt, options
+    opts = kd.utils.extend opt, options

     seed = seed.replace /[^-\w]/g, ''

-    @search 'accounts', seed, opt
+    @search 'accounts', seed, opts
       .then (data) ->
         throw new Error "No data!" if data.length is 0
         return data


Check if group exists before getting its constructorName

--- client/landing/app/CommonViews/LinkViews.coffee
+++ client/landing/app/CommonViews/LinkViews.coffee
@@ -117,7 +117,7 @@
       @createParticipantSubviews()
       @render()

-    if group[0].constructorName
+    if group[0]?.constructorName
       lastFour = group.slice -4
       bongo.cacheable lastFour, (err, bucketContents)=>
         callback bucketContents


Make the tests bullet-proof against timing differences.

--- test/logging_test.coffee
+++ test/logging_test.coffee
@@ -23,13 +23,21 @@
         assert.equal(messages[0].verb, 'create')
         assert.equal(messages[0].message, 'tmp/js/logging.js')

+    assertBundle = (index) ->
+        assert.equal(messages[index].verb, 'bundle')
+        assert.equal(messages[index].message, 'public/js/logging.bundle.js')
+
     it 'Logs the main source bundling', ->
-        assert.equal(messages[1].verb, 'bundle')
-        assert.equal(messages[1].message, 'public/js/logging.bundle.js')
+        # Order may flip around
+        assertBundle(if messages[1].verb == 'bundle' then 1 else 2)
+
+    assertMinify = (index) ->
+        assert.equal(messages[index].verb, 'minify')
+        assert.equal(messages[index].message, 'tmp/js/someLibrary.min.js')

     it 'Logs the library minification', ->
-        assert.equal(messages[2].verb, 'minify')
-        assert.equal(messages[2].message, 'tmp/js/someLibrary.min.js')
+        # Order may flip around
+        assertMinify(if messages[1].verb == 'bundle' then 2 else 1)

     it 'Logs the CoffeeScript minification', ->
         assert.equal(messages[3].verb, 'minify')