add propert point in poly test

add propert point in poly test

--- bokehjs/src/coffee/renderer/glyph/patches.coffee
+++ bokehjs/src/coffee/renderer/glyph/patches.coffee
@@ -5,6 +5,24 @@
   "renderer/properties",
   "./glyph",
 ], (_, rbush, Properties, Glyph) ->
+
+
+  point_in_poly = (x, y, px, py) ->
+    inside = false
+
+    x1 = px[px.length-1]
+    y1 = py[py.length-1]
+
+    for i in [0...px.length]
+        x2 = px[i]
+        y2 = py[i]
+        if ( y1 < y ) != ( y2 < y )
+            if x1 + ( y - y1 ) / ( y2 - y1 ) * ( x2 - x1 ) < x
+                inside = not inside
+        x1 = x2
+        y1 = y2
+
+    return inside

   class PatchesView extends Glyph.View

@@ -96,9 +114,11 @@

       candidates = (x[4].i for x in @index.search([x, y, x, y]))

-      # TODO (bev) need to do point in poly test here
-      hits = candidates
-
+      hits = []
+      for i in [0...candidates.length]
+        idx = candidates[i]
+        if point_in_poly(sx, sy, @sxs[idx], @sys[idx])
+          hits.push(idx)
       return hits

     draw_legend: (ctx, x0, x1, y0, y1) ->


* add the aDir argument to testFilter

* add the aDir argument to testFilter

--- test/filter.coffee
+++ test/filter.coffee
@@ -9,22 +9,23 @@
 fs              = require 'fs'
 path            = fs.path

-module.exports = (Folder, filterFn, expected)->
+module.exports = (Folder, aDir, filterFn, expected)->
   ->
+    aDir ?= path.join(__dirname, 'fixtures', 'folder')
     filterFn ?= (file)->path.extname(file.path) is '.md'
     expected ?= ['fixtures/folder/index.md']
     it 'should filter files sync default', ->
-      dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname, filter: filterFn
+      dir = Folder aDir, base: __dirname, filter: filterFn
       result = dir.loadSync read:true
       result = result.map (file)->file.relative
       expect(result).be.deep.equal expected
     it 'should filter files sync buffer', ->
-      dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname
+      dir = Folder aDir, base: __dirname
       result = dir.loadSync read:true, filter: filterFn
       result = result.map (file)->file.relative
       expect(result).be.deep.equal expected
     it 'should filter files sync stream', (done)->
-      dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname
+      dir = Folder aDir, base: __dirname
       result = []
       dir.loadSync read:true, buffer:false, filter: filterFn
       .on 'data', (file)->
@@ -35,21 +36,21 @@
         done()

     it 'should filter files async default', (done)->
-      dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname, filter: filterFn
+      dir = Folder aDir, base: __dirname, filter: filterFn
       dir.load read:true, (err, result)->
         unless err
           result = result.map (file)->file.relative
           expect(result).be.deep.equal expected
         done(err)
     it 'should filter files async buffer', (done)->
-      dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname
+      dir = Folder aDir, base: __dirname
       dir.load read:true, filter: filterFn, (err, result)->
         unless err
           result = result.map (file)->file.relative
           expect(result).be.deep.equal expected
         done(err)
     it 'should filter files async stream', (done)->
-      dir = Folder path.join(__dirname, 'fixtures', 'folder'), base: __dirname
+      dir = Folder aDir, base: __dirname
       result = []
       dir.load read:true, buffer:false, filter: filterFn, (err, stream)->
         return done(err) if err


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -28,6 +28,6 @@
   fastEnemy.include(Follow)
   fastEnemy.follow(player)

-  ok fastEnemy.I.velocity.equal(Point(0, -5))
+  ok fastEnemy.I.velocity.equal(Point(0, -5)),

 module()


Check that updates.$push is defined

Check that updates.$push is defined

Prevent the HIM from crashing when updating a transaction with some operation other than a push to the routes array.

--- src/api/transactions.coffee
+++ src/api/transactions.coffee
@@ -307,7 +307,7 @@
     # Update transaction metrics
     ###
     transactions.Transaction.findById transactionId, (err, doc) ->
-      if updates['$push'].routes?
+      if updates['$push']?.routes?
         for k, route of updates['$push']
           do (route) ->
             if route.metrics?


test for exception in factory

test for exception in factory

--- test/hinoki.coffee
+++ test/hinoki.coffee
@@ -52,6 +52,18 @@

             hinoki.inject container, (a) ->
                 test.equals a, 5
+                test.done()
+
+        'exception in factory': (test) ->
+            container =
+                factories:
+                    a: ->
+                        throw new Error 'b'
+
+            try
+                hinoki.inject container, (a) ->
+            catch error
+                test.equals error.message, "exception in factory 'a': Error: b"
                 test.done()

         'one seed': (test) ->


Make dashboard use all available height

Make dashboard use all available height

--- app/assets/javascripts/main.js.coffee
+++ app/assets/javascripts/main.js.coffee
@@ -105,6 +105,14 @@
       $('#post_view').empty()
       $('#groups_list .selected').removeClass('selected')
       $('#groups_list [data-name="' + new_location + '"]').addClass('selected')
+      if new_location == 'home'
+        $('#group_view').css('bottom', '0')
+        $('#group_view').css('border-bottom', '0')
+        $('#post_view').css('top', '100%')
+      else
+        $('#group_view').css('bottom', '')
+        $('#group_view').css('border-bottom', '')
+        $('#post_view').css('top', '')

 $(document).ready ->
   chunks.spinner = $('#loader .spinner').clone()


Fix markdown with turbolinks

Fix markdown with turbolinks

--- app/assets/javascripts/ui_bibz.coffee
+++ app/assets/javascripts/ui_bibz.coffee
@@ -39,11 +39,14 @@
     @table     = new window.UiBibzTable()
     @form      = new window.UiBibzForm()
     @interface = new window.UiBibzInterface()
+    @formula   = new window.UiBibzFormula()
     #delete window.UiBibzTable
     #delete window.UiBibzForm
     #delete window.UiBibzInterface

 ready = ->
   new UiBibz
+  # Fix for turbolinks
+  $( "textarea[data-provide*='markdown']" ).markdown() # fix markdown js if turbolinks exists

-$(document).on('turbolinks:load page:change', ready)
+$(document).on('ready turbolinks:load page:change', ready) # catch event for turbolinks and fix in ready() function


statusLEDs and sticky notification port #5 - admin modal: added option for broadcast

--- client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -196,6 +196,41 @@
                     partial   : '...'
                     cssClass  : 'information-line'

+          "Broadcast Restart" :
+            buttons           :
+              "Broadcast Restart"  :
+                title         : "Broadcast"
+                style         : "modal-clean-gray"
+                callback      : (event)=>
+                  {inputs, buttons} = @modalTabs.forms["Broadcast Restart"]
+
+                  KD.remote.api.JSystemStatus.scheduleRestart
+                    restartScheduled : Date.now()+inputs.Duration.getValue()*1000
+                    restartTitle : inputs.Title.getValue()
+                    restartContent : inputs.Description.getValue()
+                  ,(stuff)=>
+
+              "Cancel Restart" :
+                title         : "Cancel Restart"
+                style         : "modal-clean-gray"
+                callback      : (event)=>
+                  KD.remote.api.JSystemStatus.cancelRestart ->
+
+            fields            :
+              Title           :
+                label         : "Message Title"
+                type          : "text"
+                placeholder   : "Shutdown in"
+              Description           :
+                label         : "Message Details"
+                type          : "text"
+                placeholder   : "We are upgrading the platform. Please save your work."
+              Duration           :
+                label         : "Timer duration (in seconds)"
+                type          : "text"
+                defaultValue  : 5*60
+                placeholder   : "Please enter a reasonable timeout."
+
     super options, data

     @hideConnectedFields()


Now assigned to change is persisted on kanban.

--- app/coffee/modules/kanban/main.coffee
+++ app/coffee/modules/kanban/main.coffee
@@ -54,16 +54,39 @@
         promise.then null, =>
             console.log "FAIL"

-        # @scope.$on("usform:bulk:success", @.loadUserstories)
-        # @scope.$on("sprintform:create:success", @.loadSprints)
-        # @scope.$on("sprintform:create:success", @.loadProjectStats)
-        # @scope.$on("sprintform:remove:success", @.loadSprints)
-        # @scope.$on("sprintform:remove:success", @.loadProjectStats)
         @scope.$on("usform:new:success", @.onNewUserstory)
         @scope.$on("usform:edit:success", @.onUserstoryEdited)
+        @scope.$on("assigned-to:added", @.onAssignedToChanged)
         @scope.$on("kanban:us:move", @.moveUs)
-        # @scope.$on("sprint:us:moved", @.loadSprints)
-        # @scope.$on("sprint:us:moved", @.loadProjectStats)
+
+    # Template actions
+
+    editUserStory: (us) ->
+        @rootscope.$broadcast("usform:edit", us)
+
+    addNewUs: (type, statusId) ->
+        switch type
+            when "standard" then @rootscope.$broadcast("usform:new", statusId)
+            when "bulk" then @rootscope.$broadcast("usform:bulk", statusId)
+
+    changeUsAssignedTo: (us) ->
+        @rootscope.$broadcast("assigned-to:add", us)
+
+    # Scope Events Handlers
+
+    onNewUserstory: (ctx, us) ->
+        @scope.usByStatus[us.status].splice(0, 0, us)
+
+    onAssignedToChanged: (ctx, userid, us) ->
+        us.assigned_to = userid
+        promise = @repo.save(us)
+        promise.then null, ->
+            console.log "FAIL" # TODO
+
+    onUserstoryEdited: (ctx, us) ->
+        @.loadUserstories()
+
+    # Load data methods

     loadProjectStats: ->
         return @rs.projects.stats(@scope.projectId).then (stats) =>
@@ -155,25 +178,6 @@
             console.log "FAIL"

         return promise
-
-    ## Template actions
-    editUserStory: (us) ->
-        @rootscope.$broadcast("usform:edit", us)
-
-    addNewUs: (type, statusId) ->
-        switch type
-            when "standard" then @rootscope.$broadcast("usform:new", statusId)
-            when "bulk" then @rootscope.$broadcast("usform:bulk", statusId)
-
-    changeUsAssignedTo: (us) ->
-        @rootscope.$broadcast("assigned-to:add", us)
-
-    # Scope Events Handlers
-    onNewUserstory: (ctx, us) ->
-        @scope.usByStatus[us.status].splice(0, 0, us)
-
-    onUserstoryEdited: (ctx, us) ->
-        @.loadUserstories()

 module.controller("KanbanController", KanbanController)


Cleaned up UsbService

Cleaned up UsbService

--- app/coffee/modules/usb/service.coffee
+++ app/coffee/modules/usb/service.coffee
@@ -1,19 +1,3 @@
-# webusb.getDevices = function() {
-#   return navigator.usb.getDevices().then(devices => {
-#     return devices.map(device => findOrCreateDevice(device));
-#   });
-# };
-
-# webusb.requestDevice = function() {
-#   var filters = [
-#     { vendorId: 0x1209, productId: 0xa800 }
-#   ];
-#   return navigator.usb.requestDevice({filters: filters}).then(device => {
-#     return findOrCreateDevice(device);
-#   });
-# };
-
-

 # UsbService class definition
 # Responsible for managing USB devices
@@ -25,7 +9,6 @@

   getDevices: ->
     return navigator.usb.getDevices().then (devices) =>
-      console.log('GOT DEVICES')
       console.log(devices)

 # # # # #


fixes calendar x icon hiding on small screens

fixes the calendar modal being cut off when the browser
window is really small.

fixes CNVS-21755

test plan:
- From within the calendar create a new calendar event via the "+" icon
- Under the edit event field select the "more options" button
- Give the event a title and under the content editor add 20 lines (can be single character, just hit 'enter' after each one)
- Select "Update Event"
- Click the newly created event.
- Note that the top of popover (and the 'X' to close it) is positioned onscreen
- Enlarge and shrink the window notice x and title stay on screen

Change-Id: I37700ac74742ecd3ad27692a6e5393c21f7bf83a
Reviewed-on: https://gerrit.instructure.com/60328
Reviewed-by: Jennifer Stern <7e594e5cba47371ac9ee694a84b9d94f9f6ec815@instructure.com>
Tested-by: Jenkins
QA-Review: Steven Shepherd <e794458448443812fb2685675aab3bbd1a3891bc@instructure.com>
Product-Review: Matthew Wheeler <ce71fbafda91d6c8f12582e1837687d5fe592dd9@instructure.com>

--- app/coffeescripts/util/Popover.coffee
+++ app/coffeescripts/util/Popover.coffee
@@ -8,6 +8,7 @@
   # among other things, tells you where it positioned it relative to the target. we use it to add some
   # css classes that handle putting the pointer triangle (aka: caret) back to the trigger.
   using = ( position, feedback ) ->
+    position.top = 0 if position.top < 0
     $( this )
       .css( position )
       .toggleClass('carat-bottom', feedback.vertical == 'bottom')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/matrix.coffee
+++ test/matrix.coffee
@@ -84,9 +84,9 @@
     matrix = Matrix()

     matrix.skew(0.125.turns, 0)
-
-    p = Point()
-
+
+    p = Point(10, 10)
+
     equals(matrix.transformPoint(p))

   test ".rotation", ->


dummy commit

dummy commit

--- app/components/submission-list.coffee
+++ app/components/submission-list.coffee
@@ -4,7 +4,6 @@
 SubmissionListComponent = Ember.Component.extend

   classNames: ["container"]
-

   submissionCount: Ember.computed.alias 'submissions.length'
   hasSubmissions: Ember.computed.gt 'submissionCount', 0


Register default target for keymaps in ctor

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -186,6 +186,7 @@
     @registerDefaultOpeners()
     @registerDefaultDeserializers()
     @registerDefaultViewProviders()
+    @registerDefaultTargetForKeymaps()

     @installUncaughtErrorHandler()
     @installWindowEventHandler()
@@ -234,6 +235,9 @@
         when 'atom://.atom/init-script'
           @workspace.open(@getUserInitScriptPath())

+  registerDefaultTargetForKeymaps: ->
+    @keymaps.defaultTarget = @views.getView(@workspace)
+
   reset: (params) ->
     @deserializers.clear()
     @registerDefaultDeserializers()
@@ -593,9 +597,7 @@

     @packages.loadPackages()

-    workspaceElement = @views.getView(@workspace)
-    @keymaps.defaultTarget = workspaceElement
-    @document.body.appendChild(workspaceElement)
+    @document.body.appendChild(@views.getView(@workspace))

     @watchProjectPath()


clean testing vars after each test

clean testing vars after each test

--- test/parser.coffee
+++ test/parser.coffee
@@ -42,9 +42,11 @@
         describe '#For the 1st ad', ->
             ad1 = null

-            before (done) =>
+            before () =>
                 ad1 = _response.ads[0]
-                done()
+
+            after () =>
+                ad1 = null

             it 'should have retrieved Ad attributes', =>
                 ad1.id.should.eql "ad_id_0001"
@@ -92,9 +94,11 @@
             describe '1st creative (Linear)', ->
                 linear = null

-                before (done) =>
+                before () =>
                     linear = _response.ads[0].creatives[0]
-                    done()
+
+                after () =>
+                    linear = null

                 it 'should have linear type', =>
                     linear.type.should.equal "linear"
@@ -156,9 +160,11 @@
             describe '2nd creative (Companions)', ->
                 companions = null

-                before (done) =>
+                before () =>
                     companions = _response.ads[0].creatives[1]
-                    done()
+
+                after () =>
+                    companions = null

                 it 'should have companion type', =>
                     companions.type.should.equal "companion"
@@ -171,9 +177,11 @@
                     companion = null

                     describe 'as image/jpeg', ->
-                        before (done) =>
+                        before () =>
                             companion = companions.variations[0]
-                            done()
+
+                        after () =>
+                            companion = null

                         it 'should have parsed size and type attributes', =>
                             companion.width.should.equal '300'
@@ -197,9 +205,11 @@
                             companion.companionClickTrackingURLTemplates[1].should.equal  'http://example.com/companion1-clicktracking-second'

                     describe 'as IFrameResource', ->
-                      before (done) =>
+                      before () =>
                           companion = companions.variations[1]
-                          done()
+
+                      after () =>
+                          companion = null

                       it 'should have parsed size and type attributes', =>
                           companion.width.should.equal '300'
@@ -213,9 +223,11 @@
                         companion.iframeResource.should.equal 'http://www.example.com/companion2-example.php'

                     describe 'as text/html', ->
-                        before (done) =>
+                        before () =>
                             companion = companions.variations[2]
-                            done()
+
+                        after () =>
+                          companion = null

                         it 'should have parsed size and type attributes', =>
                             companion.width.should.equal '300'
@@ -235,9 +247,11 @@
         describe '#For the 2nd ad', ->
             ad2 = null

-            before (done) =>
+            before () =>
                 ad2 = _response.ads[1]
-                done()
+
+            after () =>
+                ad2 = null

             it 'should have retrieved Ad attributes', =>
                 _response.ads[1].id.should.eql "ad_id_0002"
@@ -268,9 +282,11 @@
             describe '1st creative (Linear)', ->
                 linear = null

-                before (done) =>
+                before () =>
                     linear = ad2.creatives[0]
-                    done()
+
+                after () =>
+                    linear = null

                 it 'should have linear type', =>
                     linear.type.should.equal "linear"


Added gulp.src to client-scripts

Added gulp.src to client-scripts

--- gulp/client-scripts.coffee
+++ gulp/client-scripts.coffee
@@ -28,4 +28,7 @@
     uglify = require 'gulp-uglify'
     plumber = require 'gulp-plumber'
     sourcemaps = require 'gulp-sourcemaps'
-    ngAnnotate = require 'gulp-ng-annotate'+    ngAnnotate = require 'gulp-ng-annotate'
+
+    # gather all coffeescript source files in /app/...
+    gulp.src ['app/**/*.coffee'], base: './app'


Prevent scrolling underlying page

Prevent scrolling underlying page

--- core/app/backbone/layouts/discussion_modal_container.coffee
+++ core/app/backbone/layouts/discussion_modal_container.coffee
@@ -17,3 +17,5 @@
   onClickButton: ->
     mp_track "Modal: Close button"
     FactlinkApp.vent.trigger 'close_discussion_modal'
+
+  onRender: -> @$el.preventScrollPropagation()


Don't auto-update code-peek on Atom, since I need to manually add "MagicPython" as a language.

--- atom/dot-atom/config.cson
+++ atom/dot-atom/config.cson
@@ -49,6 +49,10 @@
       "atom-ide-diagnostics-ui": false
   "atom-package-sync":
     syncSettings: false
+  "auto-update-plus":
+    excludedPackages: [
+      "code-peek"
+    ]
   "autocomplete-python":
     useKite: false
   build:


Fixes an issue where inputs should be blank and not auto filling to current day

--- autoform-pickadate.coffee
+++ autoform-pickadate.coffee
@@ -19,7 +19,7 @@
       else
         val = @val()
       return if val instanceof Date then val else @val()
-    return
+    return ""

   valueConverters:
     'string': (val) ->
@@ -59,13 +59,20 @@
   $picker.on 'close', ()->
     $(document.activeElement).blur()

+  $picker.on 'set', (set)=>
+    # fixes an issue where label is blank after clearing
+    if set.clear == null
+      if $($input).attr('aria-label')
+        $($input).attr('placeholder', $($input).attr('aria-label'))
+
   @autorun ->
     data = Template.currentData()
     # set field value
     if data.value instanceof Date
       $picker.set 'select', data.value
     else if typeof data.value == 'string'
-      $picker.set 'select', data.value
+      if data.value != ""
+        $picker.set 'select', data.value
     # set start date if there's a min in the schema
     if data.min instanceof Date
       # datepicker plugin expects local Date object,


experiments with fading

experiments with fading

--- src/Arena.coffee
+++ src/Arena.coffee
@@ -44,7 +44,7 @@
     @_addPhoton(64, 400, 225, 0, -100, 0x00ffff)

   _split:=>
-    return if @photons.countLiving() > 500
+    return if @photons.countLiving() > 512
     spawn = []
     @photons.forEach (photon)=>
       data =
@@ -60,7 +60,7 @@
       @_addPhoton(data.r * 0.5, data.x, data.y, -data.vy, -data.vx, data.colour)

   _addPhoton:(r, x, y, vx, vy, colour)->
-    r = 4 if r < 4
+    r = 16 if r < 16
     photon = @game.add.sprite(x, y, 'circle')
     @game.physics.enable(photon, Phaser.Physics.ARCADE)
     photon.width = r
@@ -71,10 +71,13 @@
     photon.body.velocity.y = vy
     photon.tint = colour
     photon.blendMode = Phaser.blendModes.SCREEN
+    fade = @game.add.tween(photon)
+    fade.to({ alpha: 0 }, 5000, Phaser.Easing.Linear.None, true)
+    fade.onComplete.add (obj)=>
+      obj.destroy()
     photon.checkWorldBounds = true
     photon.events.onOutOfBounds.add (obj)=>
       @photons.remove(obj)
-      obj.kill()

     @photons.add(photon)


Better calculation of pages in pagination

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -234,8 +234,8 @@
             total = ""
         else
             maxPage  = Math.ceil(max / @data.size) or 1
-            pageFrom = _.max [1, @data.page - 2]
-            pageTo   = _.min [maxPage, @data.page + 2]
+            pageFrom = _.max [1, @data.page - 2 - _.max [0, 2 + @data.page - maxPage]]
+            pageTo   = _.min [maxPage, @data.page + 2 + _.max [0, 3 - @data.page]]
             total = " of " + max + " entries"
         pages = ({number: i, active: (i == @data.page and "active") or ""} for i in _.range pageFrom, pageTo + 1)
         $("#pagination-main", @$el).html @paginationTemplate


Fix comment

Fix comment

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -138,8 +138,8 @@
             @data.size = parseInt(@data.size) or @size or 10
         return @

-    # Return a parsed querystring with the "?" (eg. query = "/users?hi=1&bye=hello")
-    # returns {hi: "1", bye: "hello"}
+    # Return a parsed querystring with the "?" (eg. query = "/users?hi=1&bye=hello"
+    # would return {hi: "1", bye: "hello"} )
     parseQueryString: (uri) ->
         ret = {}
         if (i = uri.indexOf("?")) >= 0


Remove unused __handledByCommandRegistry property

--- src/command-registry.coffee
+++ src/command-registry.coffee
@@ -186,8 +186,6 @@
       @selectorBasedListenersByCommandName[commandName] = listeners.slice()

   handleCommandEvent: (originalEvent) =>
-    originalEvent.__handledByCommandRegistry = true
-
     propagationStopped = false
     immediatePropagationStopped = false
     matched = false


simplify map implementation to be same for EventStreams and Properties

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -333,10 +333,14 @@
           @push end()
           Bacon.noMore

-  map: (f, args...) ->
-    convertArgsToFunction this, f, args, (f) ->
-      @withHandler (event) ->
-        @push event.fmap(f)
+  map: (p, args...) ->
+    if (p instanceof Property)
+      p.sampledBy(this, former)
+    else
+      convertArgsToFunction this, p, args, (f) ->
+        @withHandler (event) ->
+          @push event.fmap(f)
+
   mapError : (f, args...) ->
     f = makeFunction(f, args)
     @withHandler (event) ->
@@ -520,11 +524,6 @@
     @subscribe = dispatcher.subscribe
     @subscribeInternal = @subscribe
     @hasSubscribers = dispatcher.hasSubscribers
-  map: (p, args...) ->
-    if (p instanceof Property)
-      p.sampledBy(this, former)
-    else
-      super(p, args...)
   delay: (delay) ->
     @flatMap (value) ->
       Bacon.later delay, value


Use subscriber return values that won't cause collisions

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -27,9 +27,9 @@
       nop
   )

-Bacon.noMore = "veggies"
-
-Bacon.more = "moar bacon!"
+Bacon.noMore = ["<no-more>"]
+
+Bacon.more = ["<more>"]

 Bacon.later = (delay, value) ->
   Bacon.sequentially(delay, [value])


Switched console to Logger.

Switched console to Logger.

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -41,7 +41,7 @@
         deferCallback(result, callback)

       onError: (operation, error, template) ->
-        console.error('Error submitting form', operation, error, template)
+        Logger.error('Error submitting form', operation, error, template)
         onError = formArgs.onError ? formArgs.hooks?.onError
         onError?.apply(@, args)
         throw new Error(error)


Support asking for roles

Support asking for roles

--- src/alias.coffee
+++ src/alias.coffee
@@ -1,15 +1,15 @@
 # Description:
-#   Define user aliases to @ mention groups of people at once.
+#   Define user aliases to @mention groups of people at once.
 #
 # Dependencies:
-#   underscorejs
+#   hubot-auth - for dynamically configured grouos
 #
 # Configuration:
 #   HUBOT_GROUP_ALIAS: group1=user1,user2;group2=user1,user2,user3 [...]
 #   HUBOT_GROUP_ALIAS_NAME_PROP: A property on the user object for @metions
 #
 # Commands:
-#   None
+#   hubot list group aliases - give the names of the defined groups.
 #
 # Notes:
 #   Use @group_name in your message and your robot will pass it along to
@@ -39,6 +39,7 @@
   groupCache = _.object(staticGroups)
   return groupCache

+# Return Groups Mentioned in a message if there is a dynamic group.
 getGroups = (text) ->
   if useDynamicGroups
     groupMap = {}
@@ -58,6 +59,17 @@
     return groupMap
   else
     return buildGroupObject()
+
+getGroupsList = ->
+  if useDynamicGroups
+    groups = []
+    users = robot.brain.data.users
+    for own id, user of users
+      roles = robot.auth.userRoles(user)
+      groups = groups.concat(roles)
+    return _.uniq(groups)
+  else
+    return Object.keys(buildGroupObject())

 userFromName = (name) ->
   allUsers = robot.brain.data.users
@@ -84,17 +96,15 @@
     message = message.replace(reg, members)
   return message

+# Note this matches (alias) :alias: and @alias
 buildRegExp = ->
   if useDynamicGroups
     aliases = '\\w+'
-  else
-    # Compile RegEx to match only the aliases
-    # Note this matches (alias) :alias: and @alias
+  else # Compile RegEx to match only defined aliases
     aliases = _.keys(buildGroupObject()).join('|')
   # The last group is a set of stop conditions (word boundaries or end of line)
   atRE = '(?:@(' + aliases + ')(?:\\b[^.]|$))'
   emojiRE = '(?:[(:])(' + aliases + ')(?:[:)])'
-
   return new RegExp(atRE + '|' + emojiRE, 'i')

 module.exports = (robot) ->
@@ -106,6 +116,9 @@
     robot.logger.warning "Using dynamic groups requires hubot-auth to be loaded"
     return

+  robot.respond /list groups? alias(es)?/i, (resp) ->
+    resp.send "The currently setup groups are: #{listToMentions(getGroupsList())}"
+
   regex = buildRegExp()
   robot.hear regex, (resp) ->
     groups = getGroups(resp.message.text)


:lipstick: EditorComponent method order

:lipstick: EditorComponent method order

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -118,13 +118,6 @@
         measuringScrollbars: @measuringScrollbars
         height: horizontalScrollbarHeight
         width: verticalScrollbarWidth
-
-  getRenderedRowRange: ->
-    {editor, lineOverdrawMargin} = @props
-    [visibleStartRow, visibleEndRow] = editor.getVisibleRowRange()
-    renderedStartRow = Math.max(0, visibleStartRow - lineOverdrawMargin)
-    renderedEndRow = Math.min(editor.getScreenLineCount(), visibleEndRow + lineOverdrawMargin)
-    [renderedStartRow, renderedEndRow]

   getInitialState: ->
     visible: true
@@ -307,47 +300,6 @@
     @subscribe atom.config.observe 'editor.invisibles', @setInvisibles
     @subscribe atom.config.observe 'editor.showInvisibles', @setShowInvisibles

-  measureScrollbars: ->
-    @measuringScrollbars = false
-
-    {editor} = @props
-    scrollbarCornerNode = @refs.scrollbarCorner.getDOMNode()
-    width = (scrollbarCornerNode.offsetWidth - scrollbarCornerNode.clientWidth) or 15
-    height = (scrollbarCornerNode.offsetHeight - scrollbarCornerNode.clientHeight) or 15
-    editor.setVerticalScrollbarWidth(width)
-    editor.setHorizontalScrollbarHeight(height)
-
-  setFontSize: (fontSize) ->
-    @setState({fontSize})
-
-  setLineHeight: (lineHeight) ->
-    @setState({lineHeight})
-
-  setFontFamily: (fontFamily) ->
-    @setState({fontFamily})
-
-  setShowIndentGuide: (showIndentGuide) ->
-    @setState({showIndentGuide})
-
-  # Public: Defines which characters are invisible.
-  #
-  # invisibles - An {Object} defining the invisible characters:
-  #   :eol   - The end of line invisible {String} (default: `\u00ac`).
-  #   :space - The space invisible {String} (default: `\u00b7`).
-  #   :tab   - The tab invisible {String} (default: `\u00bb`).
-  #   :cr    - The carriage return invisible {String} (default: `\u00a4`).
-  setInvisibles: (invisibles={}) ->
-    defaults invisibles,
-      eol: '\u00ac'
-      space: '\u00b7'
-      tab: '\u00bb'
-      cr: '\u00a4'
-
-    @setState({invisibles})
-
-  setShowInvisibles: (showInvisibles) ->
-    @setState({showInvisibles})
-
   onFocus: ->
     @refs.input.focus()

@@ -405,13 +357,6 @@
         @pendingVerticalScrollDelta = 0
         @pendingHorizontalScrollDelta = 0

-  clearMouseWheelScreenRow: ->
-    if @mouseWheelScreenRow?
-      @mouseWheelScreenRow = null
-      @requestUpdate()
-
-  clearMouseWheelScreenRowAfterDelay: null # created lazily
-
   onScrollViewOverflowChanged: ->
     if @overflowChangedEventsPaused
       @overflowChangedWhilePaused = true
@@ -454,6 +399,56 @@

     @selectToMousePositionUntilMouseUp(event)

+  onStylesheetsChanged: (stylesheet) ->
+    @refreshScrollbars() if @containsScrollbarSelector(stylesheet)
+
+  onBatchedUpdatesStarted: ->
+    @batchingUpdates = true
+
+  onBatchedUpdatesEnded: ->
+    updateRequested = @updateRequested
+    @updateRequested = false
+    @batchingUpdates = false
+    if updateRequested
+      @requestUpdate()
+
+  onScreenLinesChanged: (change) ->
+    {editor} = @props
+    @pendingChanges.push(change)
+    @requestUpdate() if editor.intersectsVisibleRowRange(change.start, change.end + 1) # TODO: Use closed-open intervals for change events
+
+  onSelectionChanged: (selection) ->
+    {editor} = @props
+    if editor.selectionIntersectsVisibleRowRange(selection)
+      @selectionChanged = true
+      @requestUpdate()
+
+  onSelectionAdded: (selection) ->
+    {editor} = @props
+    if editor.selectionIntersectsVisibleRowRange(selection)
+      @selectionChanged = true
+      @selectionAdded = true
+      @requestUpdate()
+
+  onScrollTopChanged: ->
+    @scrollingVertically = true
+    @requestUpdate()
+    @onStoppedScrollingAfterDelay ?= debounce(@onStoppedScrolling, 100)
+    @onStoppedScrollingAfterDelay()
+
+  onStoppedScrolling: ->
+    @scrollingVertically = false
+    @mouseWheelScreenRow = null
+    @requestUpdate()
+
+  onStoppedScrollingAfterDelay: null # created lazily
+
+  onCursorsMoved: ->
+    @cursorsMoved = true
+
+  onGutterWidthChanged: (@gutterWidth) ->
+    @requestUpdate()
+
   selectToMousePositionUntilMouseUp: (event) ->
     {editor} = @props
     dragging = false
@@ -489,31 +484,13 @@
   selectToMousePosition: (event) ->
     @props.editor.selectToScreenPosition(@screenPositionForMouseEvent(event))

-  screenPositionForMouseEvent: (event) ->
-    pixelPosition = @pixelPositionForMouseEvent(event)
-    @props.editor.screenPositionForPixelPosition(pixelPosition)
-
-  pixelPositionForMouseEvent: (event) ->
-    {editor} = @props
-    {clientX, clientY} = event
-
-    scrollViewClientRect = @refs.scrollView.getDOMNode().getBoundingClientRect()
-    top = clientY - scrollViewClientRect.top + editor.getScrollTop()
-    left = clientX - scrollViewClientRect.left + editor.getScrollLeft()
-    {top, left}
-
-  getHiddenInputPosition: ->
-    {editor} = @props
-    {focused} = @state
-    return {top: 0, left: 0} unless @isMounted() and focused and editor.getCursor()?
-
-    {top, left, height, width} = editor.getCursor().getPixelRect()
-    width = 2 if width is 0 # Prevent autoscroll at the end of longest line
-    top -= editor.getScrollTop()
-    left -= editor.getScrollLeft()
-    top = Math.max(0, Math.min(editor.getHeight() - height, top))
-    left = Math.max(0, Math.min(editor.getWidth() - width, left))
-    {top, left}
+  requestScrollViewMeasurement: ->
+    return if @measurementPending
+
+    @scrollViewMeasurementRequested = true
+    requestAnimationFrame =>
+      @scrollViewMeasurementRequested = false
+      @measureScrollView()

   # Measure explicitly-styled height and width and relay them to the model. If
   # these values aren't explicitly styled, we assume the editor is unconstrained
@@ -536,121 +513,80 @@
       clientWidth = scrollViewNode.clientWidth
       editor.setWidth(clientWidth) if clientWidth > 0

+  measureScrollbars: ->
+    @measuringScrollbars = false
+
+    {editor} = @props
+    scrollbarCornerNode = @refs.scrollbarCorner.getDOMNode()
+    width = (scrollbarCornerNode.offsetWidth - scrollbarCornerNode.clientWidth) or 15
+    height = (scrollbarCornerNode.offsetHeight - scrollbarCornerNode.clientHeight) or 15
+    editor.setVerticalScrollbarWidth(width)
+    editor.setHorizontalScrollbarHeight(height)
+
+  containsScrollbarSelector: (stylesheet) ->
+    for rule in stylesheet.cssRules
+      if rule.selectorText?.indexOf('scrollbar') > -1
+        return true
+    false
+
+  refreshScrollbars: ->
+    # Believe it or not, proper handling of changes to scrollbar styles requires
+    # three DOM updates.
+
+    # Scrollbar style changes won't apply to scrollbars that are already
+    # visible, so first we need to hide scrollbars so we can redisplay them and
+    # force Chromium to apply updates.
+    @refreshingScrollbars = true
+    @requestUpdate()
+
+    # Next, we display only the scrollbar corner so we can measure the new
+    # scrollbar dimensions. The ::measuringScrollbars property will be set back
+    # to false after the scrollbars are measured.
+    @measuringScrollbars = true
+    @requestUpdate()
+
+    # Finally, we restore the scrollbars based on the newly-measured dimensions
+    # if the editor's content and dimensions require them to be visible.
+    @requestUpdate()
+
+  requestUpdate: ->
+    if @batchingUpdates
+      @updateRequested = true
+    else
+      @forceUpdate()
+
+  pauseOverflowChangedEvents: ->
+    @overflowChangedEventsPaused = true
+    @resumeOverflowChangedEventsAfterDelay ?= debounce(@resumeOverflowChangedEvents, 500)
+    @resumeOverflowChangedEventsAfterDelay()
+
+  resumeOverflowChangedEvents: ->
+    if @overflowChangedWhilePaused
+      @overflowChangedWhilePaused = false
+      @requestScrollViewMeasurement()
+
+  resumeOverflowChangedEventsAfterDelay: null
+
+  clearMouseWheelScreenRow: ->
+    if @mouseWheelScreenRow?
+      @mouseWheelScreenRow = null
+      @requestUpdate()
+
+  clearMouseWheelScreenRowAfterDelay: null # created lazily
+
+  consolidateSelections: (e) ->
+    e.abortKeyBinding() unless @props.editor.consolidateSelections()
+
+  lineNodeForScreenRow: (screenRow) -> @refs.lines.lineNodeForScreenRow(screenRow)
+
+  lineNumberNodeForScreenRow: (screenRow) -> @refs.gutter.lineNumberNodeForScreenRow(screenRow)
+
   screenRowForNode: (node) ->
     while node isnt document
       if screenRow = node.dataset.screenRow
         return parseInt(screenRow)
       node = node.parentNode
     null
-
-  onStylesheetsChanged: (stylesheet) ->
-    @refreshScrollbars() if @containsScrollbarSelector(stylesheet)
-
-  containsScrollbarSelector: (stylesheet) ->
-    for rule in stylesheet.cssRules
-      if rule.selectorText?.indexOf('scrollbar') > -1
-        return true
-    false
-
-  refreshScrollbars: ->
-    # Believe it or not, proper handling of changes to scrollbar styles requires
-    # three DOM updates.
-
-    # Scrollbar style changes won't apply to scrollbars that are already
-    # visible, so first we need to hide scrollbars so we can redisplay them and
-    # force Chromium to apply updates.
-    @refreshingScrollbars = true
-    @requestUpdate()
-
-    # Next, we display only the scrollbar corner so we can measure the new
-    # scrollbar dimensions. The ::measuringScrollbars property will be set back
-    # to false after the scrollbars are measured.
-    @measuringScrollbars = true
-    @requestUpdate()
-
-    # Finally, we restore the scrollbars based on the newly-measured dimensions
-    # if the editor's content and dimensions require them to be visible.
-    @requestUpdate()
-
-  onBatchedUpdatesStarted: ->
-    @batchingUpdates = true
-
-  onBatchedUpdatesEnded: ->
-    updateRequested = @updateRequested
-    @updateRequested = false
-    @batchingUpdates = false
-    if updateRequested
-      @requestUpdate()
-
-  onScreenLinesChanged: (change) ->
-    {editor} = @props
-    @pendingChanges.push(change)
-    @requestUpdate() if editor.intersectsVisibleRowRange(change.start, change.end + 1) # TODO: Use closed-open intervals for change events
-
-  onSelectionChanged: (selection) ->
-    {editor} = @props
-    if editor.selectionIntersectsVisibleRowRange(selection)
-      @selectionChanged = true
-      @requestUpdate()
-
-  onSelectionAdded: (selection) ->
-    {editor} = @props
-    if editor.selectionIntersectsVisibleRowRange(selection)
-      @selectionChanged = true
-      @selectionAdded = true
-      @requestUpdate()
-
-  onScrollTopChanged: ->
-    @scrollingVertically = true
-    @requestUpdate()
-    @stopScrollingAfterDelay ?= debounce(@onStoppedScrolling, 100)
-    @stopScrollingAfterDelay()
-
-  onStoppedScrolling: ->
-    @scrollingVertically = false
-    @mouseWheelScreenRow = null
-    @requestUpdate()
-
-  stopScrollingAfterDelay: null # created lazily
-
-  onCursorsMoved: ->
-    @cursorsMoved = true
-
-  onGutterWidthChanged: (@gutterWidth) ->
-    @requestUpdate()
-
-  requestUpdate: ->
-    if @batchingUpdates
-      @updateRequested = true
-    else
-      @forceUpdate()
-
-  requestScrollViewMeasurement: ->
-    return if @measurementPending
-
-    @scrollViewMeasurementRequested = true
-    requestAnimationFrame =>
-      @scrollViewMeasurementRequested = false
-      @measureScrollView()
-
-  pauseOverflowChangedEvents: ->
-    @overflowChangedEventsPaused = true
-    @resumeOverflowChangedEventsAfterDelay ?= debounce(@resumeOverflowChangedEvents, 500)
-    @resumeOverflowChangedEventsAfterDelay()
-
-  resumeOverflowChangedEvents: ->
-    if @overflowChangedWhilePaused
-      @overflowChangedWhilePaused = false
-      @requestScrollViewMeasurement()
-
-  resumeOverflowChangedEventsAfterDelay: null
-
-  consolidateSelections: (e) ->
-    e.abortKeyBinding() unless @props.editor.consolidateSelections()
-
-  lineNodeForScreenRow: (screenRow) -> @refs.lines.lineNodeForScreenRow(screenRow)
-
-  lineNumberNodeForScreenRow: (screenRow) -> @refs.gutter.lineNumberNodeForScreenRow(screenRow)

   hide: ->
     @setState(visible: false)
@@ -692,3 +628,67 @@
                   ReactPerf.printExclusive()
                   console.log "Wasted"
                   ReactPerf.printWasted()
+
+  setFontSize: (fontSize) ->
+    @setState({fontSize})
+
+  setLineHeight: (lineHeight) ->
+    @setState({lineHeight})
+
+  setFontFamily: (fontFamily) ->
+    @setState({fontFamily})
+
+  setShowIndentGuide: (showIndentGuide) ->
+    @setState({showIndentGuide})
+
+  # Public: Defines which characters are invisible.
+  #
+  # invisibles - An {Object} defining the invisible characters:
+  #   :eol   - The end of line invisible {String} (default: `\u00ac`).
+  #   :space - The space invisible {String} (default: `\u00b7`).
+  #   :tab   - The tab invisible {String} (default: `\u00bb`).
+  #   :cr    - The carriage return invisible {String} (default: `\u00a4`).
+  setInvisibles: (invisibles={}) ->
+    defaults invisibles,
+      eol: '\u00ac'
+      space: '\u00b7'
+      tab: '\u00bb'
+      cr: '\u00a4'
+
+    @setState({invisibles})
+
+  setShowInvisibles: (showInvisibles) ->
+    @setState({showInvisibles})
+
+  screenPositionForMouseEvent: (event) ->
+    pixelPosition = @pixelPositionForMouseEvent(event)
+    @props.editor.screenPositionForPixelPosition(pixelPosition)
+
+  pixelPositionForMouseEvent: (event) ->
+    {editor} = @props
+    {clientX, clientY} = event
+
+    scrollViewClientRect = @refs.scrollView.getDOMNode().getBoundingClientRect()
+    top = clientY - scrollViewClientRect.top + editor.getScrollTop()
+    left = clientX - scrollViewClientRect.left + editor.getScrollLeft()
+    {top, left}
+
+  getHiddenInputPosition: ->
+    {editor} = @props
+    {focused} = @state
+    return {top: 0, left: 0} unless @isMounted() and focused and editor.getCursor()?
+
+    {top, left, height, width} = editor.getCursor().getPixelRect()
+    width = 2 if width is 0 # Prevent autoscroll at the end of longest line
+    top -= editor.getScrollTop()
+    left -= editor.getScrollLeft()
+    top = Math.max(0, Math.min(editor.getHeight() - height, top))
+    left = Math.max(0, Math.min(editor.getWidth() - width, left))
+    {top, left}
+
+  getRenderedRowRange: ->
+    {editor, lineOverdrawMargin} = @props
+    [visibleStartRow, visibleEndRow] = editor.getVisibleRowRange()
+    renderedStartRow = Math.max(0, visibleStartRow - lineOverdrawMargin)
+    renderedEndRow = Math.min(editor.getScreenLineCount(), visibleEndRow + lineOverdrawMargin)
+    [renderedStartRow, renderedEndRow]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -1,3 +1,5 @@
+
+
 ###
   check for named colors in a hash table
   check for /\#?[0-9a-f]+/i give to hex parser (hex parser chex for length 3, 4, 6, 8. 4 and 8 are rgba in hex form)


Add dot before method name

Add dot before method name

--- src/grammar-registry.coffee
+++ src/grammar-registry.coffee
@@ -53,7 +53,7 @@
   # A 'grammar-added' event is emitted after the grammar is added.
   #
   # grammar - The {Grammar} to add. This should be a value previously returned
-  #           from {readGrammar} or {readGrammarSync}.
+  #           from {.readGrammar} or {.readGrammarSync}.
   addGrammar: (grammar) ->
     @grammars.push(grammar)
     @grammarsByScopeName[grammar.scopeName] = grammar


- Added notion of fixations.

- Added notion of fixations.

--- src/eyejs.coffee
+++ src/eyejs.coffee
@@ -23,6 +23,15 @@

 FRAME_TIMEOUT = 400

+
+##
+# The amount of time until we trigger a fixation on an element.
+#
+# @const FIXATION_TIME
+
+FIXATION_TIME = 180
+
+
 ##
 # The EyeJS class.
 #
@@ -38,12 +47,17 @@

     @blinks = new BlinkController

+    @blinkEl = null
+
     @blinks.on 'open', (timeClosed) =>
       @triggerEvents 'eyesopen'
-      if timeClosed > 400 and timeClosed < 1000
+      if timeClosed > 400 and timeClosed < 2000
+        @emit 'blink', @fixatedEl
         @triggerEvents 'blink click'
+      @_preventElChange = false

     @blinks.on 'close', =>
+      @_preventElChange = true
       @triggerEvents 'eyesclose'

     # The last frame received.
@@ -52,11 +66,19 @@
     # Element currently gazing-at.
     @gazeEl = null

+    # Element currently fixated-on.
+    @fixatedEl = null
+
+    # Timer to track fixations.
+    @fixationTimer = null
+
     @windowActive = true

     @indicator = new Indicator size: 60, visible: true

     @frozen = false
+
+    @_preventElChange = false

     @enabled = true

@@ -75,6 +97,17 @@
         @blinks.pushClose()
     , 100

+
+  triggerGazeEvents: (event) ->
+    @_triggerEvents(event, @gazeEl)
+
+
+  triggerEvents: (event) ->
+    @_triggerEvents(event, @fixatedEl)
+
+
+
+
   ##
   # This method triggers space-separated event names on the current gaze
   # element.
@@ -82,15 +115,28 @@
   # @method triggerEvents
   # @public

-  triggerEvents: (event) ->
+  _triggerEvents: (event, el) ->
     event = event.split /\s+/
     if event.length == 1
       event = event[0]
       evt = new CustomEvent event, bubbles: true, clientX: 0, clientY: 0
-      if @gazeEl isnt null then @gazeEl.dispatchEvent(evt)
+      if el isnt null then el.dispatchEvent(evt)
     else
       for e in event
         @triggerEvents e
+
+
+
+  _resetFixationTimer: ->
+    clearTimeout @fixationTimer
+    @fixationTimer = setTimeout =>
+      @fixatedEl = @gazeEl
+      if @fixatedEl is null then return
+      @fixatedEl.setAttribute 'data-eyejs-fixated', ''
+      @triggerEvents 'fixation'
+      @emit 'fixation', @fixatedEl
+    , FIXATION_TIME
+

   ##
   # Handles gaze event. If the current gaze element is unchanged, then
@@ -103,6 +149,7 @@
   # closed or otherwise off screen.

   handleGaze: ->
+    if @_preventElChange then return

     # Get the element currently under the gaze indicator.
     el = @indicator.getGazeElement()
@@ -110,12 +157,24 @@
     # If there is an element... (there may not be if the user isn't looking
     # inside the browser window).
     if el
-      el.setAttribute 'eyejs-gaze', ''
+      el.setAttribute 'data-eyejs-gaze', ''
+
       if @gazeEl and @gazeEl isnt el
-        @gazeEl.removeAttribute 'eyejs-gaze'
-        @triggerEvents 'gazeleave mouseleave mouseout'
+        @gazeEl.removeAttribute 'data-eyejs-gaze'
+        @triggerGazeEvents 'gazeleave mouseleave mouseout'
+
+      if @fixatedEl and @fixatedEl isnt el
+        @fixatedEl.removeAttribute 'data-eyejs-fixated'
+        @triggerEvents 'fixationend'
+        @emit 'fixationend', @fixatedEl
+        @fixatedEl = null
+
+      if @gazeEl isnt el and @fixatedEl is null
+        @_resetFixationTimer()
+
       @gazeEl = el
-      @triggerEvents 'gaze mousemove mouseenter mouseover'
+      @triggerGazeEvents 'gaze mousemove mouseenter mouseover'
+
     else
       @gazeEl = null

@@ -212,6 +271,15 @@
       # Smooth the positions.
       @smoothedX.push frame.avg.x
       @smoothedY.push frame.avg.y
+
+      # Emit the raw frame, if anyone cares.
+      @emit 'raw', frame
+
+      # Move the indicator to the new position.
+      @indicator.move @smoothedX.top(), @smoothedY.top()
+
+      # Handle the actual gaze.
+      @handleGaze()

       # Build and emit the gaze event.
       e =
@@ -219,13 +287,5 @@
         rawy: frame.avg.y
         x:    @smoothedX.top()
         y:    @smoothedY.top()
+        el:   @gazeEl
       @emit 'gaze', e
-
-      # Emit the raw frame, if anyone cares.
-      @emit 'raw', frame
-
-      # Move the indicator to the new position.
-      @indicator.move @smoothedX.top(), @smoothedY.top()
-
-      # Handle the actual gaze.
-      @handleGaze()


Assets lang path declaration

Assets lang path declaration

--- src/index.coffee
+++ src/index.coffee
@@ -10,12 +10,16 @@
 app = express() # Add Connect Assets
 app.use assets() # Set the public folder as static assets
 app.use express.static(process.cwd() + '/public') # Set View Engine
-app.set 'view engine', 'jade' # Configrue i18n
+app.set 'view engine', 'jade' # Configrue template
+
+# Set default lang
+lang = 'pl'

 i18n.init
   resGetPath: 'locales/__lng__.json' # Shorter path for locales
   #lng: "pl-PL" # Uncomment to set defualt language
-  fallbackLng: "pl-PL"
+  fallbackLng: lang
+  #load: 'unspecific'

 # Jade i18n proccessing
 i18n.addPostProcessor "jade", (val, key, opts) ->
@@ -23,6 +27,12 @@

 i18n.registerAppHelper app # Register tempalte helper
 app.use i18n.handle # Run i18n
+
+# set Asset path for i18n
+# You hate to prepare settings.langPath for using locale assets
+app.locals
+  langPath: () ->
+    i18n.t('settings.langPath', {defaultValue: lang })

 #
 # APP ROUTES


fixed missing shop object initialization

--- src/index.coffee
+++ src/index.coffee
@@ -19,6 +19,7 @@
 import {renderUICurrencyFromJSON} from './utils/currency'
 import {renderDate} from './utils/dates'

+Shop = {}
 Shop.Controls = Controls
 Shop.Events = Events
 Shop.Forms = Forms


opt error

opt error

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

     if urlStr
         if urlStr.indexOf('.') < 0
-            return cb new Error "Unvalid url string #{opts}."
+            err = new TypeError "Unvalid url string #{opts}."
+            err.code = 'UNVALID URL'
+            return cb
         if urlStr.indexOf('http') < 0
             urlStr = 'http://' + urlStr

@@ -50,7 +52,9 @@
             when 'https:' then _require('https').request

     if not request
-        cb new Error "Protocol #{opts.protocol} is not supported."
+        err = new TypeError "Protocol #{opts.protocol} is not supported."
+        err.code = 'UNVALID PROTOCOL'
+        cb err

     req = request opts, (res) ->
         statusCode = res.statusCode
@@ -96,7 +100,11 @@
                 decode null, buf
         .on 'error', cb

-    req.setTimeout timeout, -> req.emit 'error', new Error 'timeout'
+    req.setTimeout timeout, ->
+        err = new Error "Request #{url.format opts} timeout."
+        err.code = "TIMEOUT"
+        req.emit 'error', err
+
     req.end()
     req


more testing

more testing

--- src/index.coffee
+++ src/index.coffee
@@ -46,8 +46,7 @@
 io.of('/teacher')
   .on 'connection', (socket) ->
     socket.emit 'render', onlineData()
-    socket.on 'update', (data) ->
-        socket.emit 'update', data
+

 io.of('/student')
   .on 'connection', (socket) ->
@@ -56,6 +55,7 @@
     socket.emit 'online', online_data
     socket.broadcast.emit 'online', online_data
     io.of('/teacher').emit 'render', online_data
+    io.of('/teacher').emit 'update', {text:"test"}

     socket.set 'id', studentsOnline().length+1


Add support for the sendPrivate function.

--- src/index.coffee
+++ src/index.coffee
@@ -2,9 +2,15 @@
 Path  = require('path')
 Hubot = require('hubot')

+class MockResponse extends Hubot.Response
+  sendPrivate: (strings...) ->
+    @robot.adapter.sendPrivate @envelope, strings...
+
 class MockRobot extends Hubot.Robot
   constructor: (httpd=true) ->
     super null, null, httpd, 'hubot'
+
+    @Response = MockResponse

   loadAdapter: ->
     @adapter = new Room(@)
@@ -12,6 +18,8 @@
 class Room extends Hubot.Adapter
   constructor: (@robot) ->
     @messages = []
+
+    @privateMessages = {}

     @user =
       say: (userName, message) =>
@@ -32,6 +40,11 @@
   send: (envelope, strings...) ->
     @messages.push ['hubot', str] for str in strings

+  sendPrivate: (envelope, strings...) ->
+    if envelope.user.name not of @privateMessages
+      @privateMessages[envelope.user.name] = []
+    @privateMessages[envelope.user.name].push ['hubot', str] for str in strings
+
 class Helper
   constructor: (scriptsPath) ->
     @scriptsPath = Path.resolve(Path.dirname(module.parent.filename), scriptsPath)


fix local fallbacks

fix local fallbacks

--- src/index.coffee
+++ src/index.coffee
@@ -94,9 +94,11 @@
     else
       new Promise (resolve) =>
         if @sw
-          resolve @sw.call method, params
-          .catch (err) ->
-            return localMethod method, params
+          resolve(
+            @sw.call(method, params)
+            .catch (err) ->
+              return localMethod method, params
+          )
         else
           resolve localMethod(method, params)


Add awsImageAcl to all processed image versions

--- src/index.coffee
+++ src/index.coffee
@@ -149,11 +149,12 @@
 # Upload image version to S3
 ##
 Image.prototype._upload = (dest, version, cb) ->
+  version.awsImageAcl ?= @upload.opts.aws.acl
   format = extname(version.path).substr(1).toLowerCase()

   options =
     Key: "#{dest}#{version.suffix or ''}.#{format}"
-    ACL: version.awsImageAcl or @upload.opts.aws.acl
+    ACL: version.awsImageAcl
     Body: fs.createReadStream version.path
     ContentType: "image/#{if format is 'jpg' then 'jpeg' else format}"


Wercker retry

Wercker retry

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -38,7 +38,7 @@
 # 	DeviceScaleDidChange: "change:deviceScale"
 # 	DeviceContentScaleDidChange: "change:contentScale"
 # 	DeviceFullScreenDidChange: ""
-
+
 class exports.DeviceComponent extends BaseClass

 	@define "context", get: -> @_context


use png instead of jp2 for hands

use png instead of jp2 for hands

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -628,8 +628,8 @@
 			resourceUrl = window.FramerStudioInfo.deviceImagesUrl

 		# We'd like to use jp2 if possible, or check if we don't for this specific device
-		if Utils.isJP2Supported() and @_device.deviceImageJP2 is true
-			return "#{resourceUrl}/#{hand}.jp2"
+		# if Utils.isJP2Supported() and @_device.deviceImageJP2 is true
+		# 	return "#{resourceUrl}/#{hand}.jp2"

 		return "#{resourceUrl}/#{hand}.png"


+ convert tab to space

+ convert tab to space

--- src/hubot-order-menu.coffee
+++ src/hubot-order-menu.coffee
@@ -5,19 +5,19 @@
 #   coffee-script
 #
 # Configuration:
-#	HUBOT_ORDER_MENU_STORE_INFO=[name1,phone1,Http:\\link1;name2,phone2,Http:\\link2]
+# HUBOT_ORDER_MENU_STORE_INFO=[name1,phone1,Http:\\link1;name2,phone2,Http:\\link2]
 #
 # Commands:
-#	hubot order stores - show all store info.
+# hubot order stores - show all store info.
 #   hubot order <category> (<note> )$<money> - make an order.
 #   hubot order <category> (<note> )$<money> for @someone - make an order for someone.
 #   hubot order my - show your order.
 #   hubot order category <category> - show category's orders and calculate total money needed.
 #   hubot order all - show all orders and calculate total money needed.
-#	hubot order reset all - reset all orders.
-#	hubot order reset @someone - reset someone orders.
-#	hubot order reset my - reset your orders.
-#	hubot order reset category <category> - clear category.
+# hubot order reset all - reset all orders.
+# hubot order reset @someone - reset someone orders.
+# hubot order reset my - reset your orders.
+# hubot order reset category <category> - clear category.
 #
 # Author:
 #   Ghost.Yang
@@ -26,176 +26,176 @@
 Util = require 'util'

 module.exports = (robot) ->
-	class Order
-		constructor: (options) ->
-			{@name, @userId, @date, @category, @food, @note, @money} = options
-
-		toString: () ->
-			GetReplyMsg @
-
-	class Store
-		constructor: (options) ->
-			{@name, @phone, @link} = options
-
-		toString: () ->
-			"#{@name} #{@phone} - #{@link}"
-
-	class CommandStore
-		constructor: (options) ->
-			@bot = options
-			@commands = ['\n']
-
-		Add: (message) ->
-			@commands.push message
-
-		Send: () ->
-			message = @commands.join('\n')
-			@bot.reply message
-
-
-	unless process.env.HUBOT_ORDER_MENU_STORE_INFO?
-		robot.logger.warning 'The HUBOT_ORDER_MENU_STORE_INFO environment variable not set'
-
-	Stores = []
-	if process.env.HUBOT_ORDER_MENU_STORE_INFO?
-		rawStoreInfos = process.env.HUBOT_ORDER_MENU_STORE_INFO.split ';'
-		for rawStoreInfo in rawStoreInfos
-			robot.logger.debug rawStoreInfo
-			storeInfos = rawStoreInfo.split ','
-			robot.logger.debug storeInfos
-			Stores.push (new Store name:storeInfos[0], phone:storeInfos[1], link:storeInfos[2])
-
-	robot.brain.data['order'] = {}
-
-	robot.respond /order stores/i, (msg) ->
-		msg.reply 'The HUBOT_ORDER_MENU_STORE_INFO environment variable not set' unless Stores.length
-		commands = new CommandStore msg
-		for store in Stores
-			commands.Add store
-		commands.Send()
-
-	robot.respond /order\s+(\S*)\s+(\S*)\s+((?:\S*\s+)+)?\$(\S*)(?:\s+for\s+@?(\S*))?/i, (msg) ->
-		#robot.logger.debug Util.inspect msg
-		commands = new CommandStore msg
-		date_current = new Date()
-		category = msg.match[1]
-		food = msg.match[2]
-		note = msg.match[3]
-		money = msg.match[4]
-		user = ""
-		userId = ""
-		if (msg.match[5]?)
-			user = msg.match[5]
-			userId = parseTarget msg.message.rawText
-		else
-			user = msg.message.user.name
-			userId = msg.message.user.id
-
-		order = new Order name:user, userId:userId, date:date_current, category:category, food:food, note:note, money:money
-
-		if(robot.brain.data.order[category]? != true)
-			robot.brain.data.order[category]={}
-
-		robot.brain.data.order[category][user] = order
-
-		commands.Add GetReplyMsg order
-		commands.Send()
-
-	robot.respond /order my/i, (msg) ->
-		commands = new CommandStore msg
-		flag = false
-		user = msg.message.user.name
-		for categoryName, category of robot.brain.data.order
-			for userName, order of category when userName is user
-				if order?
-					commands.Add order
-					flag = true
-
-		if (flag isnt true)
-			commands.Add "you have no order."
-		commands.Send()
-
-	robot.respond /order cate(?:g?o?r?y?) (\S*)/i, (msg) ->
-		commands = new CommandStore msg
-		_categoryName = msg.match[1]
-		totalMoney = 0
-		flag = false
-		user = msg.message.user.name
-		for categoryName, category of robot.brain.data.order when categoryName is _categoryName
-			for userName, order of category when userName is user
-				if order?
-					commands.Add order
-					totalMoney += parseInt( order.money, 10 )
-					flag = true
-			if totalMoney isnt 0
-				commands.Add "Total money : #{totalMoney}"
-				totalMoney = 0
-				flag = true
-
-		if (flag isnt true)
-			commands.Add "you have no order."
-		commands.Send()
-
-	robot.respond /order all/i, (msg) ->
-		commands = new CommandStore msg
-		totalMoney = 0
-		flag = false
-		for categoryName, category of robot.brain.data.order
-			for userName, order of category
-				if order?
-					commands.Add order
-					totalMoney += parseInt( order.money, 10 )
-			if totalMoney isnt 0
-				commands.Add "Total money : #{totalMoney}"
-				totalMoney = 0
-				flag = true
-
-		if(flag != true)
-			commands.Add "There has no orders."
-		commands.Send()
-
-	robot.respond /order reset all/i, (msg) ->
-		robot.brain.data['order'] = {}
-		msg.reply "you reset all orders !"
-
-	robot.respond /order reset my/i, (msg) ->
-		user = msg.message.user.name
-		for categoryName, category of robot.brain.data.order
-			flag = false
-			for userName, order of category when userName is user
-				flag = true
-			delete category[user] if flag
-
-		if flag
-			msg.reply "you reset your orders !"
-
-	robot.respond /order reset @(\S*)/i, (msg) ->
-		user = msg.match[1]
-		userId = parseTarget msg.message.rawText
-		for categoryName, category of robot.brain.data.order
-			flag = false
-			for userName, order of category when userName is user
-				flag = true
-			delete category[user] if flag
-
-		if flag
-			msg.reply "you reset <@#{userId}|#{user}> orders !"
-
-	robot.respond /order reset cate(?:g?o?r?y?) (\S*)/i, (msg) ->
-		_categoryName = msg.match[1]
-		flag = false
-		for categoryName, category of robot.brain.data.order when categoryName is _categoryName
-			msg.reply "you reset category #{_categoryName} !"
-			delete robot.brain.data.order[_categoryName]
-
-	parseTarget = (text) ->
-		robot.logger.warning text
-		regexPattern = /order (.* for |reset )\<@?(\S*)\>/i
-		matches = text.match regexPattern
-		matches[2]
-
-	GetReplyMsg = (order) ->
-		if(order.note?)
-			"Order #{order.category} #{order.food} #{order.note} $#{order.money} for <@#{order.userId}|#{order.user}>"
-		else
-			"Order #{order.category} #{order.food} $#{order.money} for <@#{order.userId}|#{order.user}>"+  class Order
+    constructor: (options) ->
+      {@name, @userId, @date, @category, @food, @note, @money} = options
+
+    toString: () ->
+      GetReplyMsg @
+
+  class Store
+    constructor: (options) ->
+      {@name, @phone, @link} = options
+
+    toString: () ->
+      "#{@name} #{@phone} - #{@link}"
+
+  class CommandStore
+    constructor: (options) ->
+      @bot = options
+      @commands = ['\n']
+
+    Add: (message) ->
+      @commands.push message
+
+    Send: () ->
+      message = @commands.join('\n')
+      @bot.reply message
+
+
+  unless process.env.HUBOT_ORDER_MENU_STORE_INFO?
+    robot.logger.warning 'The HUBOT_ORDER_MENU_STORE_INFO environment variable not set'
+
+  Stores = []
+  if process.env.HUBOT_ORDER_MENU_STORE_INFO?
+    rawStoreInfos = process.env.HUBOT_ORDER_MENU_STORE_INFO.split ';'
+    for rawStoreInfo in rawStoreInfos
+      robot.logger.debug rawStoreInfo
+      storeInfos = rawStoreInfo.split ','
+      robot.logger.debug storeInfos
+      Stores.push (new Store name:storeInfos[0], phone:storeInfos[1], link:storeInfos[2])
+
+  robot.brain.data['order'] = {}
+
+  robot.respond /order stores/i, (msg) ->
+    msg.reply 'The HUBOT_ORDER_MENU_STORE_INFO environment variable not set' unless Stores.length
+    commands = new CommandStore msg
+    for store in Stores
+      commands.Add store
+    commands.Send()
+
+  robot.respond /order\s+(\S*)\s+(\S*)\s+((?:\S*\s+)+)?\$(\S*)(?:\s+for\s+@?(\S*))?/i, (msg) ->
+    #robot.logger.debug Util.inspect msg
+    commands = new CommandStore msg
+    date_current = new Date()
+    category = msg.match[1]
+    food = msg.match[2]
+    note = msg.match[3]
+    money = msg.match[4]
+    user = ""
+    userId = ""
+    if (msg.match[5]?)
+      user = msg.match[5]
+      userId = parseTarget msg.message.rawText
+    else
+      user = msg.message.user.name
+      userId = msg.message.user.id
+
+    order = new Order name:user, userId:userId, date:date_current, category:category, food:food, note:note, money:money
+
+    if(robot.brain.data.order[category]? != true)
+      robot.brain.data.order[category]={}
+
+    robot.brain.data.order[category][user] = order
+
+    commands.Add GetReplyMsg order
+    commands.Send()
+
+  robot.respond /order my/i, (msg) ->
+    commands = new CommandStore msg
+    flag = false
+    user = msg.message.user.name
+    for categoryName, category of robot.brain.data.order
+      for userName, order of category when userName is user
+        if order?
+          commands.Add order
+          flag = true
+
+    if (flag isnt true)
+      commands.Add "you have no order."
+    commands.Send()
+
+  robot.respond /order cate(?:g?o?r?y?) (\S*)/i, (msg) ->
+    commands = new CommandStore msg
+    _categoryName = msg.match[1]
+    totalMoney = 0
+    flag = false
+    user = msg.message.user.name
+    for categoryName, category of robot.brain.data.order when categoryName is _categoryName
+      for userName, order of category when userName is user
+        if order?
+          commands.Add order
+          totalMoney += parseInt( order.money, 10 )
+          flag = true
+      if totalMoney isnt 0
+        commands.Add "Total money : #{totalMoney}"
+        totalMoney = 0
+        flag = true
+
+    if (flag isnt true)
+      commands.Add "you have no order."
+    commands.Send()
+
+  robot.respond /order all/i, (msg) ->
+    commands = new CommandStore msg
+    totalMoney = 0
+    flag = false
+    for categoryName, category of robot.brain.data.order
+      for userName, order of category
+        if order?
+          commands.Add order
+          totalMoney += parseInt( order.money, 10 )
+      if totalMoney isnt 0
+        commands.Add "Total money : #{totalMoney}"
+        totalMoney = 0
+        flag = true
+
+    if(flag != true)
+      commands.Add "There has no orders."
+    commands.Send()
+
+  robot.respond /order reset all/i, (msg) ->
+    robot.brain.data['order'] = {}
+    msg.reply "you reset all orders !"
+
+  robot.respond /order reset my/i, (msg) ->
+    user = msg.message.user.name
+    for categoryName, category of robot.brain.data.order
+      flag = false
+      for userName, order of category when userName is user
+        flag = true
+      delete category[user] if flag
+
+    if flag
+      msg.reply "you reset your orders !"
+
+  robot.respond /order reset @(\S*)/i, (msg) ->
+    user = msg.match[1]
+    userId = parseTarget msg.message.rawText
+    for categoryName, category of robot.brain.data.order
+      flag = false
+      for userName, order of category when userName is user
+        flag = true
+      delete category[user] if flag
+
+    if flag
+      msg.reply "you reset <@#{userId}|#{user}> orders !"
+
+  robot.respond /order reset cate(?:g?o?r?y?) (\S*)/i, (msg) ->
+    _categoryName = msg.match[1]
+    flag = false
+    for categoryName, category of robot.brain.data.order when categoryName is _categoryName
+      msg.reply "you reset category #{_categoryName} !"
+      delete robot.brain.data.order[_categoryName]
+
+  parseTarget = (text) ->
+    robot.logger.warning text
+    regexPattern = /order (.* for |reset )\<@?(\S*)\>/i
+    matches = text.match regexPattern
+    matches[2]
+
+  GetReplyMsg = (order) ->
+    if(order.note?)
+      "Order #{order.category} #{order.food} #{order.note} $#{order.money} for <@#{order.userId}|#{order.user}>"
+    else
+      "Order #{order.category} #{order.food} $#{order.money} for <@#{order.userId}|#{order.user}>"


closes #161

closes #161

--- app/lib/slide-tutorial-content.coffee
+++ app/lib/slide-tutorial-content.coffee
@@ -21,6 +21,6 @@

   {image: 'assets/tutorial-content/slide5.jpg'
   title: '''Join the community and learn more!''',
-  content: '''That’s it! You’re ready to start exploring Gorongosa! Make sure to sign in with your Zooniverse account (in the top right corner) so you can track your contributions, collect images, and discuss the project on the Talk pages. If you’d like to learn more about the project, you can visit the About page or the WildCam Gorongosa blog. And if you have any more questions, or just want to chat about the project or share a cool image you found, please join us on WildCam Gorongosa Talk. Thank you for your help!''',
+  content: '''That’s it! You’re ready to start exploring Gorongosa! Make sure to sign in with your Zooniverse account (top right corner) so you can track your progress and collect images. Learn more on our About page or the WildCam Gorongosa blog. If you have any more questions or want to chat about the project, please join us on WildCam Gorongosa Talk. Thank you for your help!''',
   button: 'Finish'}
 ]


Move Vimeo CS1 videos to Cloudflare (#6504)

--- app/core/utils.coco.coffee
+++ app/core/utils.coco.coffee
@@ -718,7 +718,7 @@
   # gems in the deep
   "54173c90844506ae0195a0b4": {
     i18name: 'basic_syntax',
-    url: "https://player.vimeo.com/video/310626758",
+    url: "https://watch.videodelivery.net/d9a73d2f2d3d8de2e5e86203af47e20c",
     cn_url: "https://assets.koudashijie.com/videos/%E5%AF%BC%E8%AF%BE01-%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95-Codecombat%20Instruction%20for%20Teachers.mp4",
     title: "Basic Syntax",
     original: "54173c90844506ae0195a0b4",
@@ -728,7 +728,7 @@
   # fire dancing
   "55ca293b9bc1892c835b0136": {
     i18name: 'while_loops',
-    url: "https://player.vimeo.com/video/310626741",
+    url: "https://watch.videodelivery.net/1cec5da9a56cd42ade2906cd03c0b82b",
     cn_url: "https://assets.koudashijie.com/videos/%E5%AF%BC%E8%AF%BE03-CodeCombat%E6%95%99%E5%AD%A6%E5%AF%BC%E8%AF%BE-CS1-%E5%BE%AA%E7%8E%AFlogo.mp4",
     title: "While Loops",
     original: "55ca293b9bc1892c835b0136"
@@ -738,7 +738,7 @@
   # known enemy
   "5452adea57e83800009730ee": {
     i18name: 'variables',
-    url: "https://player.vimeo.com/video/310626807",
+    url: "https://watch.videodelivery.net/239838623c19b13437705ebe69929031",
     cn_url: "https://assets.koudashijie.com/videos/%E5%AF%BC%E8%AF%BE02-%E5%8F%98%E9%87%8F-CodeCombat-CS1-%E5%8F%98%E9%87%8Flogo.mp4",
     title: "Variables",
     original: "5452adea57e83800009730ee"


Scroll to top and property change

Scroll to top and property change

--- app/mixins/paginate.coffee
+++ app/mixins/paginate.coffee
@@ -23,6 +23,7 @@
         Ember.run.once @, 'versionIncrementer'

   objects: ( ->
+    window.scrollTo 0, 0
     that = @
     query =
       limit: @get "limit"


[Test] Geo Bypass

[Test] Geo Bypass

--- modules/player/util.coffee
+++ modules/player/util.coffee
@@ -87,7 +87,7 @@

   # Uses youtube-dl to get information of an URL or search term
   getInfo: (query)=>
-    flags = ['--default-search', 'ytsearch', '-i4f', 'bestaudio/best']
+    flags = ['--geo-bypass', '--default-search', 'ytsearch', '-i4f', 'bestaudio/best']
     try
       # get information
       info = await ytdl(query, flags, { maxBuffer: Infinity })


remove wrong sorting param

remove wrong sorting param

--- app/routes/branches.coffee
+++ app/routes/branches.coffee
@@ -14,7 +14,7 @@
     if @get('auth.signedIn')
       options.headers = { Authorization: "token #{@auth.token()}" }

-    $.ajax("#{apiEndpoint}/v3/repo/#{repoId}/branches?include=build.commit&limit=100&sort_by=exists_on_github", options).then (response) ->
+    $.ajax("#{apiEndpoint}/v3/repo/#{repoId}/branches?include=build.commit&limit=100", options).then (response) ->
       allTheBranches = response.branches

       defaultBranch = allTheBranches.filter (item, index) ->
@@ -22,7 +22,7 @@

       allTheBranches
       unless defaultBranch.length == 1
-        $.ajax("#{apiEndpoint}/v3/repo/#{repoId}/branches?include=build.commit&limit=100&offset=1&sort_by=exists_on_github", options).then (response) ->
+        $.ajax("#{apiEndpoint}/v3/repo/#{repoId}/branches?include=build.commit&limit=100&offset=1", options).then (response) ->
           allTheBranches = allTheBranches.concat(response.branches)

           allTheBranches


Scope bind calls to the monad to expose @unit

--- src/monad.coffee
+++ src/monad.coffee
@@ -6,7 +6,7 @@
 # Monads are awesome!
 TMonad = (value) -> Trait
   unit : Trait.required               # Constructable!
-  bind : (fn) -> fn value             # Bindable!
+  bind : (fn) -> fn.call this, value  # Bindable!

   # These could break out into top level traits but now this is useful.
   inspect : () -> util.inspect value  # Presentable!


Adding Google Analytics handling for Angular route changes.

--- dit4c-highcommand/app/assets/javascripts/app.coffee
+++ dit4c-highcommand/app/assets/javascripts/app.coffee
@@ -23,5 +23,11 @@
     $locationProvider.html5Mode(true).hashPrefix('!')
   ])

+  # Adapted from http://stackoverflow.com/a/19417858/701439
+  app.run(['$rootScope', '$location', '$window', ($rootScope, $location, $window) ->
+    $rootScope.$on '$routeChangeSuccess', () ->
+      $window.ga('send', 'pageview', $location.path()) if ($window.ga)
+  ])
+
   app
 )


remove ipprint from demo

remove ipprint from demo

--- start.coffee
+++ start.coffee
@@ -19,7 +19,6 @@
 # Event handling
 proxy.on 'request', (req) -> log.debug "Proxying request to #{req.headers.target}"
 proxy.on 'error', (err, req) -> log.error err
-proxy.on 'ready', (host, port, range) ->
-  log.info "Proxy started on #{host}:#{port} with #{range.length} IPs"
+proxy.on 'ready', (host, port) -> log.info "Proxy started on #{host}:#{port}"

 proxy.launch()


Clean up. Removed flag requirement for Dota history command; and fixed getCommunityID precision

--- steam.coffee
+++ steam.coffee
@@ -17,6 +17,7 @@
 #   MrSaints

 moment = require 'moment'
+require 'ref'

 personaStates = [
     "Offline"
@@ -38,7 +39,7 @@
     4: "Co-op with bots"
     5: "Team match"
     6: "Solo queue"
-    7: "N/A"
+    7: "Ranked match"
 towers = [
     "Ancient top"
     "Ancient bottom"
@@ -56,9 +57,8 @@
 module.exports = (robot) ->
     robot.respond /steam id( me)? (.*)/i, (msg) ->
         getSteamID msg, msg.match[2], (id) ->
-            if id
-                msg.match[2] += if msg.match[2].slice(-1) is "s" then "'" else "'s"
-                msg.reply "#{msg.match[2]} Steam ID is: #{id}"
+            msg.match[2] += if msg.match[2].slice(-1) is "s" then "'" else "'s"
+            msg.reply "#{msg.match[2]} Steam ID is: #{id}"

     robot.respond /steam status (\d+)/i, (msg) ->
         steam_request msg, "/ISteamUser/GetPlayerSummaries", steamids: msg.match[1], (object) ->
@@ -68,20 +68,21 @@
             msg.reply "#{msg.match[1]} belongs to #{player.personaname} who is currently #{status} and was last online #{lastOnline}."
         , 2

-    robot.respond /dota history (-u )?(.*)/i, (msg) ->
-        [command, flag, value] = msg.match
+    robot.respond /dota history (.*)/i, (msg) ->
+        [command, value] = msg.match

         params =
+            original: value
             account_id: value
             matches_requested: 5

         history = (params, type = 'Steam ID') ->
             steam_request msg, "/IDOTA2Match_570/GetMatchHistory", params, (object) ->
                 if object.result.status is 15
-                    msg.reply "The #{type} you have entered (\"#{msg.match[1]}\") does not exist or it does not have match history enabled."
+                    msg.reply "The #{type} you have entered (\"#{params.original}\") does not exist or it does not have match history enabled."
                     return
                 else if object.result.num_results is 0
-                    msg.reply "No game matches were found for the #{type}: #{msg.match[1]}."
+                    msg.reply "No game matches were found for the #{type}: #{params.original}."
                     return

                 communityID = getCommunityID params.account_id
@@ -97,16 +98,12 @@

                     msg.send "Match ID: #{match.match_id} | Lobby: #{lobbies[match.lobby_type]} | Hero: #{hero} | #{start}"

-        if flag is '-u '
+        if value.match /\d{17}/
+            history params
+        else
             getSteamID msg, value, (id) ->
-                return false if not id
                 params.account_id = id
                 history params, 'profile URL'
-        else
-            if typeof value isnt 'number'
-                msg.reply "Invalid Steam ID entered. Please ensure you have included the -u flag if you are trying to search by profile URL."
-                return
-            history params

     robot.respond /dota match (\d+)( \d*)?/i, (msg) ->
         steam_request msg, "/IDOTA2Match_570/GetMatchDetails", match_id: msg.match[1], (object) ->
@@ -134,14 +131,16 @@
 getSteamID = (msg, customURL, handler) ->
     steam_request msg, "/ISteamUser/ResolveVanityURL", vanityurl: customURL, (object) ->
         if object.response.success is 42
-            msg.reply "The custom URL you have entered (\"#{msg.match[2]}\") does not exist."
+            msg.reply "The custom URL you have entered (\"#{customURL}\") does not exist."
             return

         handler object.response.steamid

 getCommunityID = (steamID) ->
     # 64 -> 32
-    (steamID - 76561197960265728)
+    buffer = new Buffer 8
+    buffer.writeUInt64LE steamID, 0
+    buffer.readUInt32LE 0

 getHero = (heroID) ->
     return hero for hero in heroes when hero.id is heroID


Fixes and enhancements

Fixes and enhancements

- fix controls timer so that it actually stops and restarts the main switcher timeout
- change IDs of widget elements
- add support for templating the dashboard name display

--- lib/dashing-contrib/assets/widgets/switcher/switcher.coffee
+++ lib/dashing-contrib/assets/widgets/switcher/switcher.coffee
@@ -16,7 +16,6 @@

   start: (interval=60000) ->
     interval = parseInt(interval, 10)
-    self = @
     @maxPos = @dashboardNames.length - 1

     # Skip switching if no names defined
@@ -34,10 +33,13 @@
       @curName = @dashboardNames[@curPos]

     # instantiate switcher controls for countdown and manual switching
-    @switcherControls = new SwitcherControls(interval, @dashboardNames[@curPos + 1] || @dashboardNames[0])
+    @switcherControls = new DashboardSwitcherControls(interval, @)
     @switcherControls.start() if @switcherControls.present()

-    # Start loop
+    @startLoop(interval)
+
+  startLoop: (interval) ->
+    self = @
     @handle = setTimeout(() ->
       # Increase the position or reset back to zero
       self.curPos += 1
@@ -49,6 +51,18 @@
       window.location.pathname = "/#{self.curName}"

     , interval)
+
+  stopLoop: () ->
+    clearTimeout @handle
+
+  currentName: () ->
+    @curName
+
+  nextName: () ->
+    @dashboardNames[@curPos + 1] || @dashboardNames[0]
+
+  previousName: () ->
+    @dashboardNames[@curPos - 1] || @dashboardNames[@dashboardNames.length - 1]

 # Switches (hides and shows) elements within on list item
@@ -91,18 +105,20 @@
 # TODO:
 #   - show the name of the next dashboard
 #   - add controls for manually cycling through dashboards
-class SwitcherControls
+class DashboardSwitcherControls
   arrowContent = "￫"
   stopTimerContent = "stop timer"
-
-  constructor: (interval=60000, nextDashboardName) ->
+  startTimerContent = "start timer"
+
+  constructor: (interval=60000, dashboardSwitcher) ->
     @currentTime = parseInt(interval, 10)
     @interval = parseInt(interval, 10)
     @$elements = $('#dc-switcher-controls')
-    @nextDashboardName = nextDashboardName
+    @dashboardSwitcher = dashboardSwitcher
     @incrementTime = 1000 # refresh every 1000 milliseconds
-    @arrowContent = @$elements.data('arrow-content') || SwitcherControls.arrowContent
-    @stopTimerContent = @$elements.data('stop-timer-content') || SwitcherControls.stopTimerContent
+    @arrowContent = @$elements.data('next-dashboard-content') || DashboardSwitcherControls.arrowContent
+    @stopTimerContent = @$elements.data('stop-timer-content') || DashboardSwitcherControls.stopTimerContent
+    @startTimerContent = @$elements.data('start-timer-content') || DashboardSwitcherControls.startTimerContent
     @

   present: () ->
@@ -111,16 +127,20 @@
   start: () ->
     @addElements()
     @$timer = $.timer(@updateTimer, @incrementTime, true)
-    @paused = false

   addElements: () ->
+    template = @$elements.find('template')
+    if template.length
+      @$nextDashboardNameTemplate = template
+    else
+      @$nextDashboardNameTemplate = $("<template>Next dashboard: $nextName in </template>")
     @$nextDashboardNameContainer = $("")
     @$countdown = $("")
-    @$manualSwitcher = $("").
+    @$manualSwitcher = $("").
       html(@arrowContent).
       click () =>
-        location.href = "/#{@nextDashboardName}"
-    @$switcherStopper = $("").
+        location.href = "/#{@dashboardSwitcher.nextName()}"
+    @$switcherStopper = $("").
       html(@stopTimerContent).
       click(@pause)
     @$elements.
@@ -143,10 +163,15 @@

   pause: () =>
     @$timer.toggle()
-    if @$switcherStopper.hasClass('fa-pause')
-      @$switcherStopper.removeClass('fa-pause').addClass('fa-play')
+    if @isRunning()
+      @dashboardSwitcher.stopLoop()
+      @$switcherStopper.removeClass('fa-pause').addClass('fa-play').html(@startTimerContent)
     else
-      @$switcherStopper.removeClass('fa-play').addClass('fa-pause')
+      @dashboardSwitcher.startLoop @currentTime
+      @$switcherStopper.removeClass('fa-play').addClass('fa-pause').html(@stopTimerContent)
+
+  isRunning: () =>
+    @$switcherStopper.hasClass('fa-pause')

   resetCountdown: () ->
     # Get time from form
@@ -159,7 +184,9 @@

   updateTimer: () =>
     # Update dashboard name
-    @$nextDashboardNameContainer.text("Next dashboard: #{@nextDashboardName} in ")
+    @$nextDashboardNameContainer.html(
+      @$nextDashboardNameTemplate.html().replace('$nextName', @dashboardSwitcher.nextName())
+    )
     # Output timer position
     timeString = @formatTime(@currentTime)
     @$countdown.html(timeString)
@@ -177,7 +204,6 @@

 # Dashboard loaded and ready
 Dashing.on 'ready', ->
-  window.SwitcherControls = SwitcherControls
   # If multiple widgets per list item, switch them periodically
   $('.gridster li').each (index, listItem) ->
     $listItem = $(listItem)


copy dashboard switcher logic for widget switcher

--- lib/dashing-contrib/assets/widgets/switcher/switcher.coffee
+++ lib/dashing-contrib/assets/widgets/switcher/switcher.coffee
@@ -77,6 +77,7 @@
   constructor: (@elements) ->
     @$elements = $(@elements)

+
   start: (interval=5000) ->
     self = @
     @maxPos = @$elements.length - 1;
@@ -85,22 +86,155 @@
     # Show only first at start
     self.$elements.slice(1).hide()

-    # Start loop
+    # instantiate switcher controls for countdown and manual switching
+    @switcherControls = new WidgetSwitcherControls(interval, @)
+    @switcherControls.start() if @switcherControls.present()
+
+    @startLoop(interval)
+
+
+  startLoop: (interval) ->
+    self = @
     @handle = setInterval(()->
-      # Hide all at first - then show the current and ensure it uses table-cell display type
-      self.$elements.hide()
-      $(self.$elements[self.curPos]).show().css('display', 'table-cell')
-
-      # Increase the position or reset back to zero
-      self.curPos += 1
-      if self.curPos > self.maxPos
-        self.curPos = 0
+        self.next()

     , parseInt(interval, 10))

-  stop: () ->
+
+  next: () ->
+    @switcherControls.resetCountdown();
+    # Hide all at first - then show the current and ensure it uses table-cell display type
+    @$elements.hide()
+    $(@$elements[@curPos]).show().css('display', 'table-cell')
+
+    # Increase the position or reset back to zero
+    @curPos += 1
+    if @curPos > @maxPos
+      @curPos = 0
+
+
+  prev: () ->
+    self.curPos -= 1
+    if self.curPos < 0
+      self.curPos = self.maxPos
+
+    self.$elements.hide()
+    $(self.$elements[self.curPos]).show().css('display', 'table-cell')
+
+  stopLoop: () ->
     clearInterval(@handle)

+#  currentName: () ->
+#    @curName
+#
+  nextName: () ->
+    "to do"
+    #@dashboardNames[@curPos + 1] || @dashboardNames[0]
+#
+#  previousName: () ->
+#    @dashboardNames[@curPos - 1] || @dashboardNames[@dashboardNames.length - 1]
+
+
+class WidgetSwitcherControls
+  arrowContent = "￫"
+  stopTimerContent = "stop timer"
+  startTimerContent = "start timer"
+
+  constructor: (interval=60000, widgetSwitcher) ->
+    @currentTime = parseInt(interval, 10)
+    @interval = parseInt(interval, 10)
+    @$elements = $('#dc-wid-switcher-controls')
+    @widgetSwitcher = widgetSwitcher
+    @incrementTime = 1000 # refresh every 1000 milliseconds
+    @arrowContent = @$elements.data('next-widget-content') || WidgetSwitcherControls.arrowContent
+    @stopTimerContent = @$elements.data('stop-widget-timer-content') || WidgetSwitcherControls.stopTimerContent
+    @startTimerContent = @$elements.data('start-widget-timer-content') || WidgetSwitcherControls.startTimerContent
+    @
+
+  present: () ->
+    @$elements.length
+
+  start: () ->
+    @addElements()
+    @$timer = $.timer(@updateTimer, @incrementTime, true)
+
+  addElements: () ->
+    template = @$elements.find('widget-name-template')
+    if template.length
+      @$nextWidgetNameTemplate = template
+      @$nextWidgetNameTemplate.remove()
+    else
+      @$nextWidgetNameTemplate = $("<widget-name-template>Next widget: $nextName in </widget-name-template>")
+    @$nextWidgetNameContainer = $("")
+    @$countdown = $("")
+    @$manualSwitcher = $("").
+      html(@arrowContent).
+      click () =>
+        @widgetSwitcher.stopLoop()
+        @widgetSwitcher.next()
+        @widgetSwitcher.startLoop(@interval)
+    @$switcherStopper = $("").
+      html(@stopTimerContent).
+      click(@pause)
+    @$elements.
+      append(@$nextWidgetNameContainer).
+      append(@$countdown).
+      append(@$manualSwitcher).
+      append(@$switcherStopper)
+
+  formatTime: (time) ->
+    time = time / 10;
+    min = parseInt(time / 6000, 10)
+    sec = parseInt(time / 100, 10) - (min * 60)
+    "#{(if min > 0 then @pad(min, 2) else "00")}:#{@pad(sec, 2)}"
+
+  pad: (number, length) =>
+    str = "#{number}"
+    while str.length < length
+      str = "0#{str}"
+    str
+
+  pause: () =>
+    @$timer.toggle()
+    if @isRunning()
+      @widgetSwitcher.stopLoop()
+      @$switcherStopper.removeClass('fa-pause').addClass('fa-play').html(@startTimerContent)
+    else
+      @widgetSwitcher.startLoop @currentTime
+      @$switcherStopper.removeClass('fa-play').addClass('fa-pause').html(@stopTimerContent)
+
+  isRunning: () =>
+    @$switcherStopper.hasClass('fa-pause')
+
+  resetCountdown: () ->
+    # Get time from form
+    newTime = @interval
+    if newTime > 0
+      @currentTime = newTime
+
+    # Stop and reset timer
+    @$timer.stop().play(true)
+
+  updateTimer: () =>
+    # Update widget name
+    @$nextWidgetNameContainer.html(
+      @$nextWidgetNameTemplate.html().replace('$nextName', @widgetSwitcher.nextName())
+    )
+    # Output timer position
+    timeString = @formatTime(@currentTime)
+    @$countdown.html(timeString)
+
+    # If timer is complete, trigger alert
+    if @currentTime is 0
+      #@pause()
+      #@resetCountdown()
+      return
+
+    # Increment timer position
+    @currentTime -= @incrementTime
+    if @currentTime < 0
+      @currentTime = 0
+
 # Adds a countdown timer to show when next dashboard will appear
 # TODO:
 #   - show the name of the next dashboard


MainController: OnboardingController must be under AppStorageController.

--- client/Main/maincontroller/maincontroller.coffee
+++ client/Main/maincontroller/maincontroller.coffee
@@ -50,10 +50,10 @@
     @ready =>
       router.listen()
       KD.registerSingleton "widgetController",        new WidgetController
-      KD.registerSingleton "onboardingController",    new OnboardingController
       KD.registerSingleton "activityController",      new ActivityController
       KD.registerSingleton "appStorageController",    new AppStorageController
       KD.registerSingleton "kodingAppsController",    new KodingAppsController
+      KD.registerSingleton "onboardingController",    new OnboardingController
       # KD.registerSingleton "kontrol",                 new Kontrol

       # @showInstructionsBook()


activity: get content options by route path

--- client/Social/Activity/views/activitypane.coffee
+++ client/Social/Activity/views/activitypane.coffee
@@ -42,6 +42,17 @@
     @fakeMessageMap = {}

+  getActiveContentOptions: ->
+
+    panes =
+      '/Activity/Public/Liked'  : { name: 'mostLiked',  pane: @mostLiked }
+      '/Activity/Public/Recent' : { name: 'mostRecent', pane: @mostRecent }
+
+    path = KD.singletons.router.getCurrentPath()
+
+    return panes[path]
+
+
   bindLazyLoader: ->

     @scrollView.wrapper.on 'LazyLoadThresholdReached', =>


make the navigation mess a little better

--- client/views/common/navigation/navigation.coffee
+++ client/views/common/navigation/navigation.coffee
@@ -20,10 +20,8 @@
 					FlowRouter.path parent,
 						projectId: projectId
 						language: TAPi18n.getLanguage()
-				else if parent != 'home'
+				else
 					FlowRouter.path parent, language: TAPi18n.getLanguage()
-				else
-					FlowRouter.path 'home', language: TAPi18n.getLanguage()

 Template.navigation.onDestroyed ->


made the DB-hostname configurable

made the DB-hostname configurable

--- modules/stores/platforms/node/lib/engines/MongoStore.coffee
+++ modules/stores/platforms/node/lib/engines/MongoStore.coffee
@@ -11,7 +11,7 @@
         init: (options, fn)->
             mongoq = require 'mongoq'

-            @_db = mongoq 'mongodb://localhost/'+options.name
+            @_db = mongoq 'mongodb://'+(options.host || 'localhost')+'/'+options.name
             @_client = @_db.collection options.collection

             if !floyd.tools.objects.isArray(list = options.index)


Handle list of data objects

Handle list of data objects

--- jquery.transparency.coffee
+++ jquery.transparency.coffee
@@ -22,7 +22,3 @@
     context.before(tmp)

   return context.remove()
-
-'

            John            That rules

            Arnold            Great post!

      '
-
-'

            John            That rules

            Arnold            Great post!

      '


use conditional assignment

use conditional assignment

--- jquery.transparency.coffee
+++ jquery.transparency.coffee
@@ -24,10 +24,10 @@
   result

 jQuery.fn.render = (data, directives) ->
-  directives ?= {}
-  context     = if jQuery.isArray(data) then this.children().first() else this
-  template    = context.clone()
-  data        = [data] unless jQuery.isArray(data)
+  directives ||= {}
+  context      = if jQuery.isArray(data) then this.children().first() else this
+  template     = context.clone()
+  data         = [data] unless jQuery.isArray(data)

   for object in data
     local_values     = select(object,     (key, value)     -> typeof value     == 'string'  )


Fix scale for RAM in machine view

Fix scale for RAM in machine view

--- admin/static/coffee/servers/machine.coffee
+++ admin/static/coffee/servers/machine.coffee
@@ -244,14 +244,13 @@
                         for key of @model.get('stats')[namespace.get('id')].serializers
                             if @model.get('stats')[namespace.get('id')].serializers[key].cache?.blocks_in_memory?
                                 ram_rdb += parseInt(@model.get('stats')[namespace.get('id')].serializers[key].cache.blocks_in_memory) * parseInt(@model.get('stats')[namespace.get('id')].serializers[key].cache.block_size)
-
                 data =
-                    ram_used: human_readable_units @model.get('stats').proc.global_mem_used, units_space
-                    ram_free: human_readable_units @model.get('stats').proc.global_mem_total-@model.get('stats').proc.global_mem_used, units_space
-                    ram_total: human_readable_units @model.get('stats').proc.global_mem_total, units_space
+                    ram_used: human_readable_units @model.get('stats').proc.global_mem_used*1024, units_space
+                    ram_free: human_readable_units (@model.get('stats').proc.global_mem_total-@model.get('stats').proc.global_mem_used)*1024, units_space
+                    ram_total: human_readable_units @model.get('stats').proc.global_mem_total*1024, units_space
                     ram_used_percent: Math.ceil(100*@model.get('stats').proc.global_mem_used/@model.get('stats').proc.global_mem_total)+'%'
                     ram_rdb: human_readable_units ram_rdb, units_space
-                    ram_rdb_used_percent: Math.ceil(100*ram_rdb/@model.get('stats').proc.global_mem_used)+'%'
+                    ram_rdb_used_percent: Math.ceil(100*ram_rdb/(@model.get('stats').proc.global_mem_used*1024))+'%'

                 need_update = false
                 for key of data


Move audio to entity

Move audio to entity

--- app/scripts/scenery/CityAssets.coffee
+++ app/scripts/scenery/CityAssets.coffee
@@ -93,7 +93,8 @@
         wingLoaded: [5,4]
       paddingX: 1
       paddingY: 0
-
+  audio:
+    laugh: ['laugh.ogg']
 )

 generator.defineAssets(
@@ -110,6 +111,5 @@
     explosion: ['explosion.ogg']
     shoot: ['laser-shot.ogg']
     hit: ['laser-hit.ogg']
-    laugh: ['laugh.ogg']
     powerup: ['powerup.ogg']
 )


Add kill command for Windows

Add kill command for Windows

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -47,14 +47,17 @@
     contentsDir = shellAppDir
     appDir = path.join(shellAppDir, 'resources', 'app')
     installDir ?= path.join(process.env.ProgramFiles, appName)
+    killCommand = 'taskkill /IM atom.exe'
   else if process.platform is 'darwin'
     contentsDir = path.join(shellAppDir, 'Contents')
     appDir = path.join(contentsDir, 'Resources', 'app')
     installDir ?= path.join('/Applications', appName)
+    killCommand = 'pkill -9 Atom'
   else
     contentsDir = shellAppDir
     appDir = path.join(shellAppDir, 'resources', 'app')
     installDir ?= process.env.INSTALL_PREFIX ? '/usr/local'
+    killCommand ='pkill -9 Atom'

   coffeeConfig =
     glob_to_multiple:
@@ -213,7 +216,7 @@

     shell:
       'kill-atom':
-        command: 'pkill -9 Atom'
+        command: killCommand
         options:
           stdout: false
           stderr: false


Use TOC to pick first document. Remove gh-book dependency, wait until all content is loaded before opening editor.

--- scripts/controllers/routing.coffee
+++ scripts/controllers/routing.coffee
@@ -123,13 +123,18 @@
        @navigate("edit/#{encodeURIComponent(model.id or model.cid)}#{contextPath}")

     goDefault: () ->
-      require ['underscore', 'cs!gh-book/opf-file', 'cs!gh-book/xhtml-file'], (_, OpfFile, XhtmlFile) =>
-        # Find the first opf file
-        opf = allContent.findWhere({mediaType: OpfFile.prototype.mediaType})
-        if opf
-          # Find the first xhtml file which is not the navmodel
-          files = opf.manifest.where {mediaType: XhtmlFile.prototype.mediaType}
-          files = _.reject files, (o) -> o == opf.navModel
-          @goEdit _.first(files)
-        else
-          @goWorkspace()
+      require [ 'cs!views/layouts/workspace/menu' ], (menuLayout) =>
+        @_ensureLayout(menuLayout)
+        allContent.load()
+        .fail(() => alert 'Problem loading workspace. Please refresh and try again')
+        .done () =>
+          # Find the first opf file. This is gh-book specific, I suspect we
+          # want a more agnostic method of finding the first item and
+          # editing it?
+          opf = allContent.findWhere({mediaType: 'application/oebps-package+xml'})
+          if opf
+            # The first item in the toc is always the opf file, followed by the
+            # TOC nodes.
+            @_goEdit opf.tocNodes.at(1)
+          else
+            @goWorkspace()


Allow contextModel to be null.

Allow contextModel to be null.

--- scripts/controllers/routing.coffee
+++ scripts/controllers/routing.coffee
@@ -102,7 +102,7 @@
             # these are needed on the next render as a pointers to things
             # Always use the dereferenced node because content can be in more than one book
             @_currentModel = model.dereferencePointer?() or model
-            @_currentContext = contextModel.dereferencePointer?() or contextModel
+            @_currentContext = contextModel?.dereferencePointer?() or contextModel

             # this is needed right now to render the workspace
             @_currentContext?.set('_selected', true)


added commentary

added commentary

--- packages/rocketchat-custom-oauth/custom_oauth_server.coffee
+++ packages/rocketchat-custom-oauth/custom_oauth_server.coffee
@@ -128,6 +128,7 @@
 				identity.id = identity.user.userid
 				identity.email = identity.user.email

+			# Fix general 'phid' instead of 'id' from phabricator
 			if identity?.phid and not identity.id
 				identity.id = identity.phid


fix promise typos

fix promise typos

--- app/assets/javascripts/lib/geocoder.coffee
+++ app/assets/javascripts/lib/geocoder.coffee
@@ -23,7 +23,7 @@
       else
         promise.reject(status)
     fail = (err) ->
-      promise.rejext(err)
+      promise.reject(err)

     _googleGeocoder.geocode(requestBody, success, fail)

@@ -38,7 +38,7 @@
       if status == @statusOK
         promise.resolve(results)
     fail = (err) ->
-      promise.fail(err)
+      promise.reject(err)

     @googleGeocoder.geocode(requestBody, success, fail)


OX-721 undo data hotfix

OX-721 undo data hotfix

--- app/assets/javascripts/oxalis/model.coffee
+++ app/assets/javascripts/oxalis/model.coffee
@@ -46,7 +46,7 @@
             @user = new User(user)
             @scaleInfo = new ScaleInfo(tracing.content.dataSet.scale)
             @binary = new Binary(@user, tracing.content.dataSet, TEXTURE_SIZE_P, "color", 8)
-            @binaryVolume = new Binary(@user, tracing.content.dataSet, TEXTURE_SIZE_P, "color", 24)
+            @binaryVolume = new Binary(@user, tracing.content.dataSet, TEXTURE_SIZE_P, "volume", 24)
             @flycam = new Flycam2d(VIEWPORT_SIZE, @scaleInfo, @binary.cube.ZOOM_STEP_COUNT - 1, @user)
             @flycam3d = new Flycam3d(DISTANCE_3D, tracing.content.dataSet.scale)
             @flycam3d.on