Use references for all section children

Use references for all section children

--- server/models/section.coffee
+++ server/models/section.coffee
@@ -1,9 +1,13 @@
 Narrative = require("./narrative.coffee").schema
+Visualisation = require("./visualisation.coffee").schema
+Indicator = require("./indicator.coffee").schema
 mongoose = require('mongoose')

 sectionSchema = mongoose.Schema(
   title: String
-  narratives: [Narrative]
+  narrative: {type: mongoose.Schema.Types.ObjectId, ref: 'Narrative'}
+  indicator: {type: mongoose.Schema.Types.ObjectId, ref: 'Indicator'}
+  visualisation: {type: mongoose.Schema.Types.ObjectId, ref: 'Visualisation'}
 )

 Section = mongoose.model('Section', sectionSchema)


Add fallback Promise.promisify

Add fallback Promise.promisify

this method only have bluebird.
fallback function does not match of the bluebird's one, but this function can
pass project unittest chain.

--- src/songbird.coffee
+++ src/songbird.coffee
@@ -1,6 +1,18 @@
-Promise = require("bluebird")
+Promise = global.Promise || require("bluebird")

 module.exports = Promise
+
+if (!Promise.promisify)
+  Promise.promisify = (asyncFn, context) ->
+    (args...) ->
+      args = Array.prototype.slice.apply(args)
+      new Promise (resolve, reject) ->
+        args.push (err, rets...) ->
+          if err
+            reject(err)
+          else
+            resolve.apply(context, rets)
+        asyncFn.apply(context, args)

 synchronize = (asyncFn) ->
   (args...) ->


Removed logging.

Removed logging.

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -50,11 +50,6 @@
       unless bot.info.id == author.id
         self.receive new Robot.LeaveMessage(author)

-    bot.on "TimestampMessage", withAuthor (id, created, room, user, body, author) ->
-      console.log "Timestamp was sent to the room #{room}"
-      if author is null
-        console.log "Timestamp Message had a null author. Author: #{author}"
-
     bot.Me (err, data) ->
       bot.info = data.user
       bot.name = bot.info.name


small removal of extra object in comment

--- src/thebigdb.coffee
+++ src/thebigdb.coffee
@@ -116,7 +116,7 @@
   ## Engine Helpers
   ##############

-  # serializeQueryParams({{house: "bricks", animals: ["cat", "dog"], computers: {cool: true, drives: ["hard", "flash"]}})
+  # serializeQueryParams({house: "bricks", animals: ["cat", "dog"], computers: {cool: true, drives: ["hard", "flash"]}})
   # => house=bricks&animals%5B%5D=cat&animals%5B%5D=dog&computers%5Bcool%5D=true&computers%5Bdrives%5D%5B%5D=hard&computers%5Bdrives%5D%5B%5D=flash
   # which will be read by the server as:
   # => house=bricks&animals[]=cat&animals[]=dog&computers[cool]=true&computers[drives][]=hard&computers[drives][]=flash


Added support for multiple sort orders and for reverse sorts in Model/Scope#order.

--- lib/ST/Model/Scope.coffee
+++ lib/ST/Model/Scope.coffee
@@ -6,16 +6,14 @@
       @init()
       @_model = model
       @_conditions = []
-      @_order = null
+      @_orders = null
       @_populated = false

     @initializer 'withScope', (scope) ->
       @init()
       @_model = scope._model
-      @_conditions = []
-      for condition in scope._conditions
-        @_conditions.push condition
-      @_order = scope._order
+      @_conditions = scope._conditions && scope._conditions.slice(0)
+      @_orders = scope._orders && scope._orders.slice(0)
       @_populated = false

     @property 'model', 'read'
@@ -30,9 +28,14 @@
         for condition in conditions
           @_conditions.push condition

-    @method 'order', (order) ->
+    @method 'order', (orders...) ->
       @fork ->
-        @_order = order
+        @_orders ||= []
+        for order in orders
+          if found = order.match /^(\w+) (desc|asc)$/i
+            order = found[1]
+            order = {reverse: order} if found[2].toLowerCase() == 'desc'
+          @_orders.push order

     @method 'index', ->
       unless @_index
@@ -81,17 +84,19 @@
         @index().each (candidate) ->
           self.add candidate if candidate.matches self._conditions

-        if @_order
-          order = @_order
+        if @_orders
+          orders = @_orders
           @_array.sort (a, b) ->
-            a_value = a.get order
-            b_value = b.get order
-            if a_value > b_value
-              1
-            else if a_value < b_value
-              -1
-            else
-              0
+            for attribute in orders
+              if attribute.reverse
+                a_value = b.get attribute.reverse
+                b_value = a.get attribute.reverse
+              else
+                a_value = a.get attribute
+                b_value = b.get attribute
+              return 1  if a_value > b_value
+              return -1 if a_value < b_value
+            0

         @_populated = true


Changed Model/Scope to inherit from List, for more functionality and better compatability with TableView.

--- lib/ST/Model/Scope.coffee
+++ lib/ST/Model/Scope.coffee
@@ -1,14 +1,13 @@
 #require ST/Model
 #require ST/Enumerable

-ST.Model.class 'Scope', ->
-  @include 'Enumerable'
-
+ST.Model.class 'Scope', 'List', ->
   @initializer 'withModel', (model) ->
     @init()
     @_model = model
     @_conditions = []
     @_order = null
+    @_populated = false

   @initializer 'withScope', (scope) ->
     @init()
@@ -17,6 +16,7 @@
     for condition in scope._conditions
       @_conditions.push condition
     @_order = scope._order
+    @_populated = false

   @method 'fork', (block) ->
     scope = @_class.createWithScope this
@@ -52,53 +52,50 @@
     @_bindingsAdded = false

   @method 'bind', (trigger, receiver, selector) ->
+    @populate()
     @addBindings() unless @_bindingsAdded
     @super trigger, receiver, selector

   @method 'unbindAll', (receiver) ->
     @super receiver
     @removeBindings() if @_bindingsAdded && !@isBound()
+
+  @method 'each', (yield) ->
+    @populate()
+    @super yield
+
+  @method 'count', ->
+    @populate()
+    @super()

-  @method 'each', (yield) ->
-    self = this
-    yield = ST.toProc yield
-
-    candidates = null
-    for condition in @_conditions
-      if condition.index
-        return unless condition.index.length
-        candidates = condition.index.find condition.value
-
-    candidates ||= @_model._byUuid
-    matches = []
-
-    for uuid of candidates
-      if candidates.hasOwnProperty uuid
-        candidate = candidates[uuid]
-        if candidate.matches @_conditions
-          if @_order
-            matches.push candidate
+  @method 'populate', ->
+    unless @_populated
+      candidates = null
+      for condition in @_conditions
+        if condition.index
+          return unless condition.index.length
+          candidates = condition.index.find condition.value
+
+      candidates ||= @_model._byUuid
+
+      for uuid of candidates
+        if candidates.hasOwnProperty uuid
+          candidate = candidates[uuid]
+          if candidate.matches @_conditions
+            @_array.push candidate
+
+      if @_order
+        order = @_order
+        @_array.sort (a, b) ->
+          a_value = a.get order
+          b_value = b.get order
+          if a_value > b_value
+            1
+          else if a_value < b_value
+            -1
           else
-            yield candidate
-
-    if @_order
-      matches.sort (a, b) ->
-        a_value = a.get self._order
-        b_value = b.get self._order
-        if a_value > b_value
-          1
-        else if a_value < b_value
-          -1
-        else
-          0
-      for match in matches
-        yield match
-
-  @method 'count', ->
-    count = 0
-    @each (item) ->
-      count++
-    count
+            0
+      @_populated = true

   @method 'destroyAll', ->
     @each 'destroy'
@@ -112,10 +109,10 @@
     @_model.createWithData defaults

   @method 'itemAdded', (index, item) ->
-    @trigger 'itemAdded', item if item.matches @_conditions
+    @add item if item.matches @_conditions

   @method 'itemRemoved', (index, item) ->
-    @trigger 'itemRemoved', item if item.destroyed || !item.matches(@_conditions)
+    @remove item if item.destroyed || !item.matches(@_conditions)

   @method 'itemChanged', (index, item) ->
     @trigger 'itemChanged', item if item.matches @_conditions


Scrolling up on the mouse wheel should zoom in

This makes it consistent with Google Maps and Photoshop (and probably
most of the world).

Keep in mind that it is expected "up" zooms out if you enable "natural
scrolling" on a Mac.

--- app/components/frame-viewer.cjsx
+++ app/components/frame-viewer.cjsx
@@ -263,7 +263,7 @@
       when 1
         e.preventDefault()
         @setState zooming: true
-        if e.deltaY > 0 then @zoom(.9) else @zoom(1.1)
+        if e.deltaY > 0 then @zoom(1.1) else @zoom(0.9)

   panHorizontal:(direction) ->
     maximumX = (@state.frameDimensions.width - @state.viewBoxDimensions.width) + (@state.frameDimensions.width * 0.6)


Pass user context

Pass user context

--- app/components/pass-context.cjsx
+++ app/components/pass-context.cjsx
@@ -4,10 +4,12 @@
 module.exports = React.createClass
   contextTypes:
     router: routerShape
+    user: React.PropTypes.object
     geordi: React.PropTypes.object

   childContextTypes:
     router: routerShape
+    user: React.PropTypes.object
     geordi: React.PropTypes.object

   getChildContext: ->


setupPusher even if not logged in and also on IOS

--- app/controllers/user_edit.coffee
+++ app/controllers/user_edit.coffee
@@ -67,10 +67,10 @@
     @render()

   setupPusher: ->
-    return if @pusher || !@item
+    return if @pusher

     # Disable pusher on dev mode for now
-    return if Config.env!='production'
+    return if Config.env=='development'
     # Enable pusher logging - don't include this in production
     if Config.env!='production'
       Pusher.log = (message) ->


Remove CWW offline subjects

Remove CWW offline subjects

--- app/lib/get_empty_subject.coffee
+++ app/lib/get_empty_subject.coffee
@@ -2,26 +2,7 @@

 module.exports = ->

-  offline_subjects = [
-    'offline/subjects/Raccoon_coyote.JPG',
-    'offline/subjects/edison_park_deer.JPG',
-    'offline/subjects/empty_3.jpg',
-    'offline/subjects/gray_squirrel.JPG',
-    'offline/subjects/jefferson_park_red_fox_2.JPG',
-    'offline/subjects/rabbit.JPG',
-    'offline/subjects/coyote.JPG',
-    'offline/subjects/empty_1.jpg',
-    'offline/subjects/fox_squirrel_4.JPG',
-    'offline/subjects/human.JPG',
-    'offline/subjects/near_north_coyotes.JPG',
-    'offline/subjects/rat.JPG',
-    'offline/subjects/dog.JPG',
-    'offline/subjects/empty_2.jpg',
-    'offline/subjects/garfield_ridge_opossum.JPG',
-    'offline/subjects/hyde_park_raccoon.jpg',
-    'offline/subjects/opossum.JPG',
-    'offline/subjects/skunk.JPG'
-  ]
+  offline_subjects = []

   #create a subject with a random image from the list of sample offline subjects
   new Subject


Disable polling for EarnedAchievements, not currently used.

If we start using achievements in Ozaria, we can re-enable this.


Add expert role

Add expert role

--- app/pages/lab/collaborators.cjsx
+++ app/pages/lab/collaborators.cjsx
@@ -5,8 +5,9 @@
 POSSIBLE_ROLES = [
   'owner'
   'collaborator'
+  'expert'
+  'scientist'
   'moderator'
-  'scientist'
   'tester'
   'translator'
 ]


Updated compile specs

Updated compile specs

--- spec/tin-foil_spec.coffee
+++ spec/tin-foil_spec.coffee
@@ -311,87 +311,66 @@
       @Base.definition('extension').defaultValue.should.equal 'original-value'

   describe 'compilation', ->
-
-    beforeEach ->
-      @event =
-        property2: 'property2'
-        nest:
-          nested: 'nested'
-        mix: 'mixed'
-        collectionValue2: 'collection value 2'
-        nestedCollectionValue2: 'nested collection value 2'
-        mapValue2: 'map value 2'
-        nestedMapValue2: 'nested map value 2'
-
-      @Nest = TinFoil.extend()
-      @Nest.set 'nested', to: (event) -> event.nest.nested
-      @Nest.define 'map', as: TinFoilMap, with_alias: 'add_to_map'
-      @Nest.define 'collection', as: TinFoilCollection, with_alias: 'add_to_collection'
-
-      @Mix = TinFoil.extend()
-      @Mix.set 'mix', to: (event) -> event.mix
-
-      @Base.set 'property_1', 'property1'
-      @Base.set 'property_2', (event) -> event.property2
-      @Base.define 'nest', as: @Nest, with_prefix: 'nest_'
-      @Base.define 'empty_collection', as: TinFoilCollection
-      @Base.define 'collection', as: TinFoilCollection, with_alias: 'add_to_collection'
-      @Base.define 'empty_map', as: TinFoilMap
-      @Base.define 'map', as: TinFoilMap, with_alias: 'add_to_map'
-      @Base.mixin @Mix
-
-      @Base.add_to_collection 'collection value 1'
-      @Base.add_to_collection (event) -> event.collectionValue2
-      @Base.nest_add_to_collection 'nested collection value 1'
-      @Base.nest_add_to_collection (event) -> event.nestedCollectionValue2
-
-      @Base.add_to_map 'map_value_1', 'map value 1'
-      @Base.add_to_map 'map_value_2', (event) -> event.mapValue2
-      @Base.nest_add_to_map 'nested_map_value_1', 'nested map value 1'
-      @Base.nest_add_to_map 'nested_map_value_2', (event) -> event.nestedMapValue2
-
-      @compiled = @Base.compile @event
+    Activity = require '../lib/activity'
+
+    beforeEach ->
+      @data =
+        tenantId: '4321-8765-dcba'
+        scene:
+          id: '1234-5678-abcd'
+          title: 'When Zombies Attack'
+
+      class @Scene extends Activity
+        @identify_from (data) -> "http://skilitix.com/xapi/activities/scene/#{data.scene.id}"
+
+        @definition_named 'en-US': 'Scene'
+        @definition_description_from (data) -> 'en-US': data.scene.title
+        @definition_typed_as 'http://skilitix.com/xapi/activities/node'
+
+        @definition_with_extension 'urn:organisation-id', (data) -> data.tenantId
+        @definition_with_extension 'urn:nothing', 'nowhere'
+
+        @definition_interaction_type 'none'
+        @definition_add_choice 'Jump out the window'
+        @definition_add_choice 'Stay inside and play xBox'
+
+        @define 'emptyMap', as: TinFoilMap, with_alias: 'add_to_empty_map'
+
+      @Base.define 'scene', as: @Scene, with_prefix: 'scene_'
+
+      @compiled = @Base.compile @data
+
+    it 'should compile set properties', ->
+      @compiled.scene.objectType.should.equal 'Activity'

     it 'should compile static properties', ->
-      @compiled.property_1.should.equal 'property1'
+      @compiled.scene.definition.name['en-US'].should.equal 'Scene'

     it 'should compile dynamic properties', ->
-      @compiled.property_2.should.equal 'property2'
+      @compiled.scene.id.should.equal 'http://skilitix.com/xapi/activities/scene/1234-5678-abcd'
+      @compiled.scene.definition.description['en-US'].should.equal 'When Zombies Attack'

     it 'should compile nested properties', ->
-      @compiled.nest.nested.should.equal 'nested'
+      @compiled.scene.id.should.equal 'http://skilitix.com/xapi/activities/scene/1234-5678-abcd'

     it 'should compile mixed properties', ->
-      @compiled.mix.should.equal 'mixed'
+      @compiled.scene.definition.interactionType.should.equal 'none'

     it 'should not include properties with a value of "undefined"', ->
-      expect(@compiled.not_set).to.be.undefined
+      expect(@compiled.scene.moreinfo).to.be.undefined

     describe 'collections', ->
       it 'should not include empty collections', ->
-        expect(@compiled.empty_collection).to.be.undefined
+        expect(@compiled.scene.correctResponsePattern).to.be.undefined

       it 'should compile the collection', ->
-        @compiled.collection.should.be.an 'Array'
-        @compiled.collection[0].should.equal 'collection value 1'
-        @compiled.collection[1].should.equal 'collection value 2'
-
-      it 'should compile nested collections', ->
-        @compiled.nest.collection.should.be.an 'Array'
-        @compiled.nest.collection[0].should.equal 'nested collection value 1'
-        @compiled.nest.collection[1].should.equal 'nested collection value 2'
+        @compiled.scene.definition.choices[0].should.equal 'Jump out the window'
+        @compiled.scene.definition.choices[1].should.equal 'Stay inside and play xBox'

     describe 'maps', ->
       it 'should not indlue empty maps', ->
-        expect(@compiled.empty_map).to.be.undefined
+        expect(@compiled.scene.emptyMap).to.be.undefined

       it 'should compile the map', ->
-        @compiled.map.should.be.an 'Object'
-        @compiled.map['map_value_1'].should.equal 'map value 1'
-        @compiled.map['map_value_2'].should.equal 'map value 2'
-
-      it 'should compile nested maps', ->
-        @compiled.nest.map.should.be.an 'Object'
-        @compiled.nest.map['nested_map_value_1'].should.equal 'nested map value 1'
-        @compiled.nest.map['nested_map_value_2'].should.equal 'nested map value 2'
-
+        @compiled.scene.definition.extensions['urn:organisation-id'].should.equal '4321-8765-dcba'
+        @compiled.scene.definition.extensions['urn:nothing'].should.equal 'nowhere'


reuse overlay styles for fuzzy finder

reuse overlay styles for fuzzy finder

--- src/fuzzy-finder-view.coffee
+++ src/fuzzy-finder-view.coffee
@@ -12,7 +12,7 @@
     @instance = new FuzzyFinderView(rootView)

   @viewClass: ->
-    [super, 'fuzzy-finder'].join(' ')
+    [super, 'fuzzy-finder', 'overlay', 'from-top'].join(' ')

   allowActiveEditorChange: null
   maxItems: 10


fix fuzzy finder directory styles

fix fuzzy finder directory styles

--- src/fuzzy-finder-view.coffee
+++ src/fuzzy-finder-view.coffee
@@ -46,9 +46,9 @@
           typeClass = 'pdf-name'
         else
           typeClass = 'text-name'
-        @span fs.base(path), class: "file #{typeClass}"
+        @span fs.base(path), class: "file label #{typeClass}"
         if folder = fs.directory(path)
-          @span "- #{folder}/", class: 'directory'
+          @span "#{folder}/", class: 'directory'

   openPath: (path) ->
     @rootView.open(path, {@allowActiveEditorChange}) if path


Fixes bug when building posts.

Fixes bug when building posts.

--- apps/blog/lib/build-posts.coffee
+++ apps/blog/lib/build-posts.coffee
@@ -42,7 +42,8 @@
             tags[tag].push post.slug
     tags

-module.exports.build = ({config, callback}) ->
+module.exports.build = (opts={}) ->
+    {config, callback} = opts
     config ?= require '../config/redis.json'
     client = redis.createClient()


formated support/cson

formated support/cson

--- support/cson.coffee
+++ support/cson.coffee
@@ -1,8 +1,10 @@
-coffee = require 'coffee-script'
+fs      = require('fs')
+coffee  = require 'coffee-script'
+
 require.extensions['.cson'] = (module, filename) ->
-  content = require('fs').readFileSync filename, 'utf8'
+  content = fs.readFileSync filename, 'utf8'
   try
     module.exports = eval coffee.compile content, bare: true
   catch err
-    err.message = filename + ': ' + err.message
+    err.message = "#{filename}: #{err.message}"
     throw err


bugzilla/xmlrpc: fix undefined callback

bugzilla/xmlrpc: fix undefined callback

--- plugins/bugzilla/xmlrpc.coffee
+++ plugins/bugzilla/xmlrpc.coffee
@@ -20,24 +20,24 @@
     request.end (response) =>
       return callback(response.error) if response.error
       xmlResponseParser.parse response, (xmlError, xmljson) =>
-        @handleErrors(xmlError, xmljson.methodResponse.fault)
+        callback(xmlError) if xmlError
+        if fault = xmljson.methodResponse.fault
+          errorMsg = fault.value.struct.member[0].value.string
+          errorCode = fault.value.struct.member[1].value.int
+          callback(new Error("#{errorMsg} (code: #{errorCode})"))
+
         if tokenCallback
           tokenCallback(@extractToken(xmljson))
         else
-          id = xmljson.methodResponse.params.param.value.struct.member.value.int
+          id = @extractId(xmljson)
           callback null,
             id: id,
             url: "#{@host}/show_bug.cgi?id=#{id}"

-
-  handleErrors: (xmlError, fault) ->
-    callback(xmlError) if xmlError
-    if fault
-      errorMsg = fault.value.struct.member[0].value.string
-      errorCode = fault.value.struct.member[1].value.int
-      callback(new Error("#{errorMsg} (code: #{errorCode})"))
-
   extractToken: (xmljson) ->
     xmljson.methodResponse.params.param.value.struct.member[1].value.string

+  extractId: (xmljson) ->
+    xmljson.methodResponse.params.param.value.struct.member.value.int
+
 module.exports = XmlRpc;


found another capitalize reference to json-client-api

--- app/assets/javascripts/components/group-browser.cjsx
+++ app/assets/javascripts/components/group-browser.cjsx
@@ -1,7 +1,7 @@
 # @cjsx React.DOM

 React = require 'react'
-API   = require '../lib/API'
+API   = require '../lib/api'

 GroupBrowser = React.createClass
   displayName: 'GroupBrowser'


Prevent recalculating time remaining more than once per second to stop infinite digest errors.

--- app/assets/javascripts/directives/uploader.js.coffee
+++ app/assets/javascripts/directives/uploader.js.coffee
@@ -90,9 +90,14 @@
         return null unless @startTime
         loaded = @progress.loaded + (@current?.progress?.loaded || 0)
         return null if loaded == 0
-        elapsed = Date.now() - @startTime
+
+        now = Date.now()
+        return @lastTimeRemaining if now - (@lastTimeRemainingCalculatedAt || 0) < 1000
+        @lastTimeRemainingCalculatedAt = now
+
+        elapsed = now - @startTime
         expected = elapsed * @progress.total / loaded
-        Math.ceil((expected - elapsed) / 1000) * 1000
+        @lastTimeRemaining = Math.ceil((expected - elapsed) / 1000) * 1000

       uploadRate: ->
         if @rateWindow? && @rateWindow.length >= 2


OX-11 measuring speed on server

OX-11 measuring speed on server

--- app/assets/javascripts/model/binary/pullqueue.coffee
+++ app/assets/javascripts/model/binary/pullqueue.coffee
@@ -6,8 +6,8 @@
 class PullQueue

   # Constants
-  BATCH_LIMIT : 2
-  BATCH_SIZE : 100
+  BATCH_LIMIT : 20
+  BATCH_SIZE : 5
   ROUND_TRIP_TIME_SMOOTHER : .125
   BUCKET_TIME_SMOOTHER : .125


Increase time because of Safari

Increase time because of Safari

--- app/assets/javascripts/search_autocomplete.js.coffee
+++ app/assets/javascripts/search_autocomplete.js.coffee
@@ -191,7 +191,7 @@
       # If input is blank then restore state
       if @searchInput.val() is ''
         @restoreOriginalState()
-    , 100)
+    , 150)

   addLocationBadge: (item) ->
     category = if item.category? then "#{item.category}: " else ''


mp4: Rename getTree() to dump()

mp4: Rename getTree() to dump()

--- mp4.coffee
+++ mp4.coffee
@@ -18,7 +18,7 @@
     startTime = process.hrtime()
     while @bits.has_more_data()
       box = Box.parse @bits, null  # null == root box
-      process.stdout.write box.getTree 0, 1
+      process.stdout.write box.dump 0, 1
     diffTime = process.hrtime startTime
     console.log "took #{(diffTime[0] * 1e9 + diffTime[1]) / 1000000} ms to parse"
     console.log "EOF"
@@ -29,7 +29,7 @@
   @mp4TimeToDate: (time) ->
     return new Date(new Date('1904-01-01 00:00:00+0000').getTime() + time * 1000)

-  getTree: (depth=0, detailLevel=0) ->
+  dump: (depth=0, detailLevel=0) ->
     str = ''
     for i in [0...depth]
       str += '  '
@@ -41,7 +41,7 @@
     str += "\n"
     if @children?
       for child in @children
-        str += child.getTree depth+1, detailLevel
+        str += child.dump depth+1, detailLevel
     return str

   getDetails: (detailLevel) ->


Remove courses store from user store

Remove courses store from user store

--- src/flux/current-user.coffee
+++ src/flux/current-user.coffee
@@ -45,8 +45,6 @@

 CurrentUserActions = flux.createActions [
   'setToken'  # (token) ->
-  'loadAllCourses'
-  'loadedAllCourses'
   'loadName'
   'loadedName'
   'logout'    # () ->    # API Hooks onto this action and transitions
@@ -56,16 +54,12 @@
 CurrentUserStore = flux.createStore
   actions: [
     CurrentUserActions.setToken
-    CurrentUserActions.loadAllCourses
-    CurrentUserActions.loadedAllCourses
     CurrentUserActions.loadName
     CurrentUserActions.loadedName
     CurrentUserActions.reset
   ]

   _token: null
-  # DEPRECATE?  courseIds being stored on _courses?
-  _courseIds: null # Just store the id's. They will be looked up in the course store

   _getRouteByRole: (routeType, menuRole) ->
     ROUTES[routeType].roles[menuRole] or ROUTES[routeType].roles.default
@@ -93,35 +87,18 @@
     @_isAdmin = results.is_admin
     @emitChange()

-  loadAllCourses: -> # Used by API
-  loadedAllCourses: (results) ->
-    # {items} = results # TODO: This JSON format is inconsistent with the paged results
-    items = results
-    @_courses = _.map items, (course) ->
-      {id} = course
-      CourseActions.loaded(course, id)
-      id # Just store the ids in @_courses
-
-    @emitChange()
-
   reset: ->
     @_token = null
     @_name = 'Guest'
-    @_courses = null

   exports:
     getToken: -> @_token
     getCSRFToken: -> CSRF_Token
-    isCoursesLoaded: -> !!@_courses
     getName: -> @_name
     isAdmin: -> @_isAdmin

     getCourseRole: (courseId) ->
       @_getCourseRole(courseId)
-
-    getCourses: ->
-      _.map @_courses, (id) ->
-        CourseStore.get(id)

     getDashboardRoute: (courseId) ->
       menuRole = @_getCourseRole(courseId)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -74,9 +74,11 @@
   Math.round(this)

 ###*
+
+
 @name circularPoint
 @methodOf Number#
-@ret
+
 ###
 Number::circularPoints = (block) ->
   n = this


now compatible with 0.6.5

now compatible with 0.6.5

--- server/publications.coffee
+++ server/publications.coffee
@@ -80,7 +80,10 @@

   fn = Meteor.bindEnvironment save_collections, (e) ->
     console.log e
-  Meteor._RemoteCollectionDriver.mongo.db.collections fn
+
+  # MongoInternals is the 'right' solution as of 0.6.5
+  mongo_driver = MongoInternals?.defaultRemoteCollectionDriver() or Meteor._RemoteCollectionDriver
+  mongo_driver.mongo.db.collections fn

 publish_to_admin = (name, publish_func) ->
   Meteor.publish name, ->


On a user profile page - do not show proposals that were deleted

--- server/publications.coffee
+++ server/publications.coffee
@@ -49,7 +49,8 @@
   query = {} if not query
   users = User.find(_.extend(query, speakerPred), _.extend(options, {fields: userFields}))
   userIds = users.map((u)->u._id)
-  proposals = Proposal.find({user_id: {$in: userIds}}, {fields: proposalFields(@userId)})
+  proposals = Proposal.find(_.extend({user_id: {$in: userIds}}, notDeletedPred),
+                            {fields: proposalFields(@userId)})
   [users, proposals]

 Meteor.publish "moderators", ->


Added control structures to sqf snippets

--- settings/language-sqf.cson
+++ settings/language-sqf.cson
@@ -2033,13 +2033,17 @@
         'QGVAR',
         'EGVAR',
         'QEGVAR',
-
         'FUNC',
         'EFUNC',
         'DFUNC',
         'DEFUNC',
         'QFUNC',
         'QEFUNC',
+        'QUOTE',
+        'LSTRING',
+        'ELSTRING',
+        'CSTRING',
+        'ECSTRING',

         'PREP',
         'ADDON',
@@ -2048,17 +2052,13 @@
         'CALLSTACK',
         'CALLSTACK_NAMED',

-        'QUOTE',
         'GETVAR',
         'SETVAR',
         'GETMVAR',
         'GETUVAR',
         'SETMVAR',
         'SETUVAR',
-        'LSTRING',
-        'ELSTRING',
-        'CSTRING',
-        'ECSTRING',
+
         'HASHCREATE',
         'HASH_REM',
         'HASH_HASKEY',


Added descriptions to Macro snippets and updated bodies and URL

--- snippets/language-sqf.cson
+++ snippets/language-sqf.cson
@@ -190,117 +190,117 @@
     'body': """
       GVAR(${1:[variable]})
     """
-    'description': 'Expand to PREFIX_MODULE_variable'
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+    'description': 'Expands to: prefix_component_variable'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'QGVAR':
     'prefix': 'QGVAR'
     'body': """
       QGVAR(${1:[variable]})
     """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+    'description': 'Expands to: "prefix_component_variable"'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'EGVAR':
     'prefix': 'EGVAR'
     'body': """
       EGVAR(${1:[component]}),${2:[variable]})
     """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+    'description': 'Expands to: prefix_component_variable'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'QEGVAR':
     'prefix': 'QEGVAR'
     'body': """
       QEGVAR(${1:[component]}),${2:[variable]})
     """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+    'description': 'Expands to: "prefix_component_variable"'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'FUNC':
     'prefix': 'FUNC'
     'body': """
-      FUNC(${1:[function name]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      FUNC(${1:[functionName]})
+    """
+    'description': 'Expands to: prefix_component_fnc_functionName'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'EFUNC':
     'prefix': 'EFUNC'
     'body': """
-      EFUNC(${1:[component name]}),${2:[function name]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      EFUNC(${1:[component]}),${2:[functionName]})
+    """
+    'description': 'Expands to: prefix_component_fnc_functionName'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'DFUNC':
     'prefix': 'DFUNC'
     'body': """
-      DFUNC(${1:[function name]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      DFUNC(${1:[functionName]})
+    """
+    'description': 'Expands to: prefix_component_fnc_functionName'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'DEFUNC':
     'prefix': 'DEFUNC'
     'body': """
-      DEFUNC(${1:[component name]}),${2:[function name]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      DEFUNC(${1:[component]}),${2:[functionName]})
+    """
+    'description': 'Expands to: prefix_component_fnc_functionName'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'QFUNC':
     'prefix': 'QFUNC'
     'body': """
-      QFUNC(${1:[function name]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      QFUNC(${1:[functionName]})
+    """
+    'description': 'Expands to: "prefix_component_fnc_functionName"'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'QEFUNC':
     'prefix': 'QEFUNC'
     'body': """
-      QEFUNC(${1:[component name]}),${2:[function name]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      QEFUNC(${1:[component]}),${2:[functionName]})
+    """
+    'description': 'Expands to: "prefix_component_fnc_functionName"'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'QUOTE':
     'prefix': 'QUOTE'
     'body': """
-      QUOTE(${1:[Content]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      QUOTE(${1:[content]})
+    """
+    'description': 'Expands to: "content"'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'LSTRING':
     'prefix': 'LSTRING'
     'body': """
-      LSTRING(${1:[String table entry]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      LSTRING(${1:[string]})
+    """
+    'description': 'Expands to: "STR_prefix_component_fnc_string"'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'ELSTRING':
     'prefix': 'ELSTRING'
     'body': """
-      ELSTRING(${1:[Component]}),${2:[String table entry]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      ELSTRING(${1:[component]}),${2:[string]})
+    """
+    'description': 'Expands to: "STR_prefix_component_fnc_string"'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'CSTRING':
     'prefix': 'CSTRING'
     'body': """
-      CSTRING(${1:[String table entry]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      CSTRING(${1:[string]})
+    """
+    'description': 'Expands to: "$STR_prefix_component_fnc_string"'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'

   'ECSTRING':
     'prefix': 'ECSTRING'
     'body': """
-      ELSTRING(${1:[Component]}),${2:[String table entry]})
-    """
-    'description': ''
-    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-2-general-purpose-macros'
+      ELSTRING(${1:[component]}),${2:[string]})
+    """
+    'description': 'Expands to: "$STR_prefix_component_fnc_string"'
+    'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'


Made it easier and more realistic to add paramters to classes

--- lib/class-complete.coffee
+++ lib/class-complete.coffee
@@ -8,13 +8,15 @@
         text = cursor.getCurrentBufferLine().trim()
         splitText = text.split(":")
         className = splitText[0]
-        parameters = []
+        parameters = splitText[1].split(",")

         editor.selectToBeginningOfLine()
         editor.insertText(className + " = (function() {\n")
         editor.insertText("\tfunction " + className + "(")
-        if splitText.length > 1
-            editor.insertText(splitText[1])
+        `for(var i = 0; i < parameters.length; i++) {
+            if(i == parameters.length - 1) editor.insertText(parameters[i]);
+            else editor.insertText(parameters[i] + ", ");
+        }`
         editor.insertText(") {\n")
         editor.insertText("\t\n")
         editor.insertText("\t}\n")


Use default params

Use default params

--- spec/GetBorderlessBox.coffee
+++ spec/GetBorderlessBox.coffee
@@ -309,8 +309,5 @@
       inSrc = 'de.jpg'
       testutils.getCanvasWithImageNoShift inSrc, (c) ->
         canvas.beginGroup groupId
-        mean.send 0.5
-        max.send 10
-        avg.send 10
-        canvas.send c
-        canvas.endGroup()
+        canvas.send c
+        canvas.endGroup()


(test) updated Deploy spec for new .xml files

--- spec/core/deploy.spec.coffee
+++ spec/core/deploy.spec.coffee
@@ -37,10 +37,18 @@
   it "creates dist/config.ios.xml", =>
     iosConfigCreated = false
     waitsFor =>
-      iosConfigCreated = @session.stdout.match("Creating dist/config.ios.xml...")
+      iosConfigCreated = @session.stdout.match("Creating www/config.ios.xml")

     runs ->
       expect( iosConfigCreated ).toBeTruthy()
+
+  it "creates dist/config.android.xml", =>
+    androidConfigCreated = false
+    waitsFor =>
+      androidConfigCreated = @session.stdout.match("Creating www/config.android.xml")
+
+    runs ->
+      expect( androidConfigCreated ).toBeTruthy()

   it "packages", =>
     packaged = false


remove uncesary rename in coverage task

remove uncesary rename in coverage task

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -74,7 +74,6 @@
     -r test/register-coffee-coverage -r should
     -R mocha-lcov-reporter', { silent: true }
     .exec()
-    #.pipe rename 'lcov.info'
     .pipe streamify coveralls()

 gulp.task 'testwatch', ['test' ], ->


:lipstick: Indent select-all specs

:lipstick: Indent select-all specs

--- spec/select-next-spec.coffee
+++ spec/select-next-spec.coffee
@@ -145,29 +145,29 @@
           [[5, 6], [5, 9]]
         ]

-  describe "when a word is selected", ->
-    it "find and selects all occurrences", ->
-      editor.setText """
-        for
-        information
-        format
-        another for
-        fork
-        a 3rd for is here
-      """
+    describe "when a word is selected", ->
+      it "find and selects all occurrences", ->
+        editor.setText """
+          for
+          information
+          format
+          another for
+          fork
+          a 3rd for is here
+        """

-      editor.setSelectedBufferRange([[3, 8], [3, 11]])
+        editor.setSelectedBufferRange([[3, 8], [3, 11]])

-      editorView.trigger 'find-and-replace:select-all'
-      expect(editor.getSelectedBufferRanges()).toEqual [
-        [[0, 0], [0, 3]]
-        [[3, 8], [3, 11]]
-        [[5, 6], [5, 9]]
-      ]
+        editorView.trigger 'find-and-replace:select-all'
+        expect(editor.getSelectedBufferRanges()).toEqual [
+          [[0, 0], [0, 3]]
+          [[3, 8], [3, 11]]
+          [[5, 6], [5, 9]]
+        ]

-      editorView.trigger 'find-and-replace:select-all'
-      expect(editor.getSelectedBufferRanges()).toEqual [
-        [[0, 0], [0, 3]]
-        [[3, 8], [3, 11]]
-        [[5, 6], [5, 9]]
-      ]
+        editorView.trigger 'find-and-replace:select-all'
+        expect(editor.getSelectedBufferRanges()).toEqual [
+          [[0, 0], [0, 3]]
+          [[3, 8], [3, 11]]
+          [[5, 6], [5, 9]]
+        ]


Add spec for undo stack clearing

Add spec for undo stack clearing

--- spec/text-buffer-spec.coffee
+++ spec/text-buffer-spec.coffee
@@ -1893,3 +1893,22 @@
       encodingChangeHandler.reset()
       buffer.setEncoding('win1251')
       expect(encodingChangeHandler.callCount).toBe 0
+
+    it "does not push the encoding change onto the undo stack", ->
+      filePath = join(__dirname, 'fixtures', 'win1251.txt')
+      buffer = new TextBuffer({filePath, load: true})
+      reloadHandler = jasmine.createSpy('reloadHandler')
+
+      waitsFor ->
+        buffer.loaded
+
+      runs ->
+        buffer.setEncoding('win1251')
+        buffer.onDidReload(reloadHandler)
+
+      waitsFor ->
+        reloadHandler.callCount is 1
+
+      runs ->
+        buffer.undo()
+        expect(buffer.getText()).toBe 'тест 1234 абвгдеёжз'


Use already defined variable instead of msg.match[3]

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -106,7 +106,7 @@
       return unless triggerdByPagerDutyUserId

       # Figure out what we're trying to page
-      pagerDutyUserFromNameOrSchedule msg, msg.match[3], (userToPage) ->
+      pagerDutyUserFromNameOrSchedule msg, userOrSchedule, (userToPage) ->
         if !userToPage
           msg.reply "Couldn't find a user or unique schedule matching #{userOrSchedule} :/"
         else


editor: remove a mysterious mobile hack which was the reason for https://github.com/sagemathinc/smc/issues/288 (jupyter ipad broken)

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -3693,12 +3693,7 @@
         if not @element?
             return
         @element.show()
-        if not IS_MOBILE
-            @element.css(top:@editor.editor_top_position(), position:'fixed')
-        else
-            # TODO: this is a terrible HACK for position the top of the editor.
-            @element.closest(".salvus-editor-content").css(position:'relative', top:'0')
-            @element.css(position:'relative', top:'0')
+        @element.css(top:@editor.editor_top_position(), position:'fixed')
         @wrapped?.show?()

     hide: () =>


:lipstick:

:lipstick:

--- src/snippet-expansion.coffee
+++ src/snippet-expansion.coffee
@@ -37,7 +37,6 @@
         _.remove(@tabStopAnchorRanges, anchorRange)
       anchorRange
     @setTabStopIndex(0)
-

   indentSubsequentLines: (startRow, snippet) ->
     initialIndent = @editSession.lineForBufferRow(startRow).match(/^\s*/)[0]


:gb::arrow_up_small: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -1,4 +1,4 @@
-version: "0.4.0"
+version: "0.4.1"
 entryPoint: "main"
 remoteDependencies: [
   "https://code.jquery.com/jquery-1.11.0.min.js"
@@ -6,4 +6,4 @@
 ]
 dependencies:
   cson: "distri/cson:v0.1.0"
-  styl: "distri/styl:v0.2.8-pre.0"
+  styl: "distri/styl:v0.2.8"


:mans_shoe::u5408: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -8,7 +8,7 @@
 dependencies:
   "jquery-utils": "STRd6/jquery-utils:v0.1.2"
   runtime: "STRd6/runtime:v0.1.1"
-  "touch-canvas": "STRd6/touch-canvas:v0.1.1"
+  "touch-canvas": "STRd6/touch-canvas:v0.2.0-alpha.0"
   "commando": "STRd6/commando:v0.9.0"
 width: 480
 height: 320


Add spec for no package name in deprecation

--- spec/metrics-spec.coffee
+++ spec/metrics-spec.coffee
@@ -186,6 +186,17 @@
         expect(url).toContain "ea=metrics"
         expect(url).toMatch "el=[0-9]+\.[0-9]+\.[0-9]+"

+    it "does not report when deprecation has no package name specified", ->
+      Reporter.request.reset()
+      jasmine.snapshotDeprecations()
+      grim.deprecate('bad things are bad', packageName: null)
+      jasmine.restoreDeprecationsSnapshot()
+
+      waits 1
+
+      runs ->
+        expect(Reporter.request.callCount).toBe 0
+
   describe "when deactivated", ->
     it "stops reporting pane items", ->
       spyOn(Reporter, 'sendPaneItem')


:green_heart: Adjust minimap specs based on new text editor behaviour

--- spec/minimap-spec.coffee
+++ spec/minimap-spec.coffee
@@ -108,18 +108,14 @@
       editorElement.setScrollTop(editorElement.getScrollHeight())
       expect(minimap.getScrollTop()).toEqual(minimap.getMaxScrollTop())

-    describe 'when getScrollTop() and maxScrollTop both equal 0', ->
+    describe 'getTextEditorScrollRatio(), when getScrollTop() and maxScrollTop both equal 0', ->
       beforeEach ->
         editor.setText(smallSample)
         editorElement.setHeight(40)
         atom.config.set 'editor.scrollPastEnd', true

-      it 'getTextEditorScrollRatio() should return 0', ->
+      it 'returns 0', ->
         editorElement.setScrollTop(0)
-
-        maxScrollTop = (editorElement.getScrollHeight() - editorElement.getHeight()) - (editorElement.getHeight() - 3 * editor.getLineHeightInPixels())
-
-        expect(maxScrollTop).toEqual(10)
         expect(minimap.getTextEditorScrollRatio()).toEqual(0)

   describe 'when soft wrap is enabled', ->
@@ -170,10 +166,10 @@
       expect(minimap.getScrollTop()).toEqual(editorScrollRatio * minimap.getMaxScrollTop())

     it 'computes the first visible row in the minimap', ->
-      expect(minimap.getFirstVisibleScreenRow()).toEqual(66)
+      expect(minimap.getFirstVisibleScreenRow()).toEqual(58)

     it 'computes the last visible row in the minimap', ->
-      expect(minimap.getLastVisibleScreenRow()).toEqual(77)
+      expect(minimap.getLastVisibleScreenRow()).toEqual(69)

     describe 'down to the bottom', ->
       beforeEach ->


Fix faulty error handling code.

Fix faulty error handling code.

--- src/accessibility.coffee
+++ src/accessibility.coffee
@@ -24,9 +24,15 @@
                         tags:
                             type: 'smbackend_accessibility_rule'
                         extra:
+                            data: data
                             error_type: textStatus
                             error_thrown: errorThrown
-                     Raven.captureException exception, context
+                    if errorThrown
+                        Raven.captureException errorThrown, context
+                    else
+                        Raven.captureMessage(
+                            'Error fetching accessibility rules',
+                            context)
             Backbone.ajax settings
         _emitShortcoming: (rule, messages) ->
             """


Change server address

Change server address

--- src/app/home/home.coffee
+++ src/app/home/home.coffee
@@ -28,7 +28,7 @@
     sharejs.open(
       'hello'
       'text'
-      'http://localhost:8000/channel'
+      'http://multeam-server.herokuapp.com/channel'
       (error, doc) ->
         doc.attach_ace(editor)
     )


Base LineMap.linesForScreenRows/BufferRows on linesByDelta

--- src/atom/line-map.coffee
+++ src/atom/line-map.coffee
@@ -27,6 +27,15 @@

   lineForScreenRow: (row) ->
     @linesForScreenRows(row, row)[0]
+
+  linesForScreenRows: (startRow, endRow) ->
+    @linesByDelta('screenDelta', startRow, endRow)
+
+  lineForBufferRow: (row) ->
+    @linesForBufferRows(row, row)[0]
+
+  linesForBufferRows: (startRow, endRow) ->
+    @linesByDelta('bufferDelta', startRow, endRow)

   bufferLineCount: ->
     @bufferPositionForScreenPosition([Infinity, 0]).row
@@ -74,7 +83,7 @@

     @lineFragments[startIndex...stopIndex] = lineFragments

-  linesForScreenRows: (startRow, endRow) ->
+  linesByDelta: (deltaType, startRow, endRow) ->
     lines = []
     delta = new Point

@@ -85,26 +94,15 @@
           pendingFragment = pendingFragment.concat(lineFragment)
         else
           pendingFragment = _.clone(lineFragment)
-        if pendingFragment.screenDelta.row > 0
+        if pendingFragment[deltaType].row > 0
           pendingFragment.bufferDelta = new Point(1, 0)
           lines.push pendingFragment
           pendingFragment = null
-      delta = delta.add(lineFragment.screenDelta)
+      delta = delta.add(lineFragment[deltaType])

     lines

-  lineForBufferRow: (row) ->
-    line = null
-    delta = new Point
-    for lineFragment in @lineFragments
-      break if delta.row > row
-      if delta.row == row
-        if line
-          line = line.concat(lineFragment)
-        else
-          line = lineFragment
-      delta = delta.add(lineFragment.bufferDelta)
-    line
+

   translatePosition: (sourceDeltaType, targetDeltaType, sourcePosition) ->
     sourcePosition = Point.fromObject(sourcePosition)


The data passed to Widget::render is merged with the result of Widget::beforeRender in case it contains promises

--- src/client/widget.coffee
+++ src/client/widget.coffee
@@ -165,9 +165,9 @@
       ctx = @buildContext.call(@)
       ctx.fail (err)-> console.error("Error building context: ", err)
       ctx.then (ctx)=>
-        beforeCtx = @beforeRender.call(@, ctx)
+        beforeCtx = _.extend(@beforeRender.call(@, ctx), data)
         $.when(beforeCtx).done (dataAfterBefore)=>
-          data = _.extend(dataAfterBefore || ctx, data)
+          data = dataAfterBefore || ctx
           @doRender(tpl, data)
           _.defer(@afterRender.bind(@, data))
           _.defer((-> @sandbox.start(@$el)).bind(@))


More feedback.

More feedback.

--- src/coffee/import.coffee
+++ src/coffee/import.coffee
@@ -29,6 +29,7 @@
         if _.size(@validator.map.errors) isnt 0
           @returnResult false, @validator.map.errors, callback
           return
+        console.log "Mapping done. Downloading existing products ..."
         @productService.getAllExistingProducts(@rest).then (existingProducts) =>
           console.log "Comparing against #{_.size existingProducts} existing product(s) ..."
           @initMatcher existingProducts
@@ -108,6 +109,7 @@
     deferred = Q.defer()
     allSameValueAttributes = types.id2SameForAllAttributes[product.productType.id]
     diff = @sync.buildActions(product, existingProduct, allSameValueAttributes)
+    # console.log "ACTIONS %j", diff.get()
     diff.update (error, response, body) =>
       @tickProgress()
       if error
@@ -121,11 +123,9 @@
         else if response.statusCode is 304
           deferred.resolve "[row #{rowIndex}] Product update not necessary."
         else if response.statusCode is 400
-          humanReadable = JSON.stringify body, null, '  '
-          deferred.resolve "[row #{rowIndex}] Problem on updating product:\n" + humanReadable
+          deferred.resolve "[row #{rowIndex}] Problem on updating product:\n" + body
         else
-          humanReadable = JSON.stringify body, null, '  '
-          deferred.reject "[row #{rowIndex}] Error on updating product: " + humanReadable
+          deferred.reject "[row #{rowIndex}] Error on updating product: " + body

     deferred.promise

@@ -142,11 +142,9 @@
           .fail (msg) ->
             deferred.reject msg
         else if response.statusCode is 400
-          humanReadable = JSON.stringify body, null, '  '
-          deferred.reject "[row #{rowIndex}] Problem on creating new product:\n" + humanReadable
+          deferred.reject "[row #{rowIndex}] Problem on creating new product:\n" + body
         else
-          humanReadable = JSON.stringify body, null, '  '
-          deferred.reject "[row #{rowIndex}] Error on creating new product: " + humanReadable
+          deferred.reject "[row #{rowIndex}] Error on creating new product: " + body

     deferred.promise

@@ -172,11 +170,9 @@
           if ignore400
             deferred.resolve "[row #{rowIndex}] Product is already #{action}ed."
           else
-            humanReadable = JSON.stringify body, null, '  '
-            deferred.reject "[row #{rowIndex}] Problem on #{action}ing product:\n" + humanReadable
+            deferred.reject "[row #{rowIndex}] Problem on #{action}ing product:\n" + body
         else
-          humanReadable = JSON.stringify body, null, '  '
-          deferred.reject "[row #{rowIndex}] Problem on #{action}ing product (code #{response.statusCode}): " + humanReadable
+          deferred.reject "[row #{rowIndex}] Problem on #{action}ing product (code #{response.statusCode}): " + body

     deferred.promise


Do not show all infos from package.json

Do not show all infos from package.json

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

   # homepage
   app.get '/', (req, res, next) ->
-    res.status(200).json _.omit pkg, 'devDependencies'
+    res.status(200).json _.pick(pkg, 'name', 'description', 'version', 'homepage', 'author', 'repository', 'bugs')

   # retrieve a link to the generated pdf
   app.post '/api/pdf/url', (req, res, next) ->


Added advanced settings

Added advanced settings

--- config/advanced.coffee
+++ config/advanced.coffee
@@ -5,6 +5,37 @@
   Only change them if you know what you're doing.
 ###
 module.exports = (cfg) ->
+  # Un-comment this line if you want to disable Web Uploading.
+  #cfg.disable 'web upload'
+
+  # Un-comment this line if you want to disable the Indexer Worker.
+  # This is not recommended. Honestly, I don't know why I put it
+  # there. Just don't touch it.
+  #cfg.disable 'run indexer worker'
+
   # Max number of errors a Worker is allowed before stopping
-  # further attempts at reviving it
-  cfg.set 'max worker fails', 100+  # further attempts at reviving it.
+  cfg.set 'max worker fails', 100
+
+  # Whether to rename or delete conflicting Torrents during
+  # indexing. A conflicting Torrent is one which differs in
+  # uploader or categories from the filesystem and the Index.
+  cfg.set 'torrent conflict solution', 'delete'
+
+  # Extension used when above is 'rename'.
+  cfg.set 'torrent conflict extension', 'bak'
+
+  # This determines how BlackSam will process download of
+  # metadata and indexing. There's two options:
+  #   parallel - Run the processing of the whole batch at the
+  #              same time. Provides faster indexing, but may
+  #              starve bandwidth, ram or processor in small
+  #              servers.
+  #   series   - Run the processing of the batch one item at
+  #              a time. It could be slow, mostly for
+  #              just-setup install. Works best for dedicated
+  #              infrastructure.
+  cfg.set 'torrent index worker method', 'series'
+
+  # Items per processing batch
+  cfg.set 'torrent index worker batch', 10


ActivityTicker: do not include guest results

--- workers/social/lib/social/models/activityticker.coffee
+++ workers/social/lib/social/models/activityticker.coffee
@@ -131,9 +131,8 @@
       else
         do kallback

-  @fetch = secure (client, options = {}, callback) ->
-    {connection: {delegate}} = client
-
+  @_fetch = (requestOptions = {}, callback)->
+    {options, client} = requestOptions
     sources = constructorNames
     as      = relationshipNames
     targets = constructorNames
@@ -161,15 +160,27 @@
       limit      : 10 # Math.min options.limit ? 15, 15
       sort       : timestamp  : -1

-    Relationship.some selector, relOptions, (err, relationships) ->
-      buckets = []
-      return  callback err, buckets  if err
-      daisy queue = relationships.map (relationship) ->
-        ->
-          relationship.fetchTeaser ->
-            decorateEvents relationship, (err, decoratedEvent)=>
-              buckets.push decoratedEvent  if decoratedEvent
-              queue.next()
+    JGroup = require './group'
+    JGroup.one slug:'guests', (err, group)->
+      return callback err if err
+      return callback new Error "Group not found" if not group
+      # do not include guest group results to data set
+      selector.sourceId = { "$ne" : group.getId() }

-      queue.push ->
-        callback null, buckets
+      Relationship.some selector, relOptions, (err, relationships) ->
+        buckets = []
+        return  callback err, buckets  if err
+        queue = relationships.map (relationship) ->
+          ->
+            relationship.fetchTeaser ->
+              decorateEvents relationship, (err, decoratedEvent)=>
+                buckets.push decoratedEvent  if decoratedEvent
+                queue.next()
+
+        queue.push ->
+          callback null, buckets
+          queue.next()
+
+        daisy queue
+
+


[skip] Fixed toggling completed tickets in the Milestone views (5m)

--- app/assets/javascripts/houston/roadmap/views/_show_milestone_view.coffee
+++ app/assets/javascripts/houston/roadmap/views/_show_milestone_view.coffee
@@ -1,9 +1,6 @@
 class Roadmap.ShowMilestoneView extends @TicketsView
   supportsSorting: false
   template: HandlebarsTemplates['houston/roadmap/milestone/show']
-
-  events:
-    'click #show_completed_tickets': 'toggleShowCompleted'

   initialize: ->
     @milestone = @options.milestone
@@ -11,6 +8,8 @@
     @projectTicketTracker = @options.projectTicketTracker
     @usesFeedback = @options.usesFeedback
     super
+
+    @$el.on 'click', '#show_completed_tickets', _.bind(@toggleShowCompleted, @)

   render: ->
     tickets = for ticket in @tickets.models
@@ -179,7 +178,7 @@
       sum_xx += _x * _x
       sum_xy += _x * _y
     m = (n*sum_xy - sum_x*sum_y) / (n*sum_xx - sum_x*sum_x)
-    b = (sum_y - m*sum_x)/n
+    b = (sum_y - m*sum_x) / n

     # No progress is being made
     return null if m == 0


Add missing char on payments.js

Add missing char on payments.js

--- app/assets/javascripts/neighborly/balanced/creditcard/payments.js.coffee
+++ app/assets/javascripts/neighborly/balanced/creditcard/payments.js.coffee
@@ -51,7 +51,7 @@
                      } )
       flashWrapper = $('.flash') || $('body > header').next($('<div>', { 'class': 'flash', 'html' }))
       flashWrapper.append(alertBox)
-      $('.neighborly-balanced-creditcard-form [type="submit"').
+      $('.neighborly-balanced-creditcard-form [type="submit"]').
         removeAttr('disabled').
         removeAttr('data-disable-with').
         attr('value', 'Confirm payment')


Only listen for onchange on checkboxes

Only listen for onchange on checkboxes

--- source/runtime.coffee
+++ source/runtime.coffee
@@ -83,7 +83,7 @@
 specialBindings =
   INPUT:
     checked: (element, value, context) ->
-      element.oninput = element.onchange = ->
+      element.onchange = ->
         value? element.checked

       bindObservable element, value, context, (newValue) ->


Remove trailing spaces, trigger jenkins

Remove trailing spaces, trigger jenkins

--- app/assets/javascripts/oxalis/controller/abstract_tree_controller.coffee
+++ app/assets/javascripts/oxalis/controller/abstract_tree_controller.coffee
@@ -1,4 +1,4 @@
-### define
+### define
 ../view/skeletontracing/abstract_tree_view : AbstractTreeView
 ###

@@ -11,7 +11,7 @@

   constructor : (@model) ->
-
+
     container = $("#abstractTreeViewer")
     @view = new AbstractTreeView(container.width(), container.height())
     container.append(@view.canvas)
@@ -35,12 +35,12 @@

   drawTree : ->
-
+
     @view.drawTree(@model.skeletonTracing.getTree(), @model.skeletonTracing.getActiveNodeId())

   setActiveNode : (nodeId, centered, mergeTree) ->
-
+
     { model } = @

     model.skeletonTracing.setActiveNode(nodeId, mergeTree)


add extra test for Bus.push

add extra test for Bus.push

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -2807,10 +2807,12 @@
         s.push "pullMe"
         soon ->
           s.push "pushMe"
+          # test that it works regardless of "this"
+          s.push.call(null, "pushSomeMore")
           s.error()
           s.end()
         s
-      ["pushMe", error()])
+      ["pushMe", "pushSomeMore", error()])

   it "does not deliver pushed events after end() call", ->
     called = false


Add test on erroring when trying to send to addressable without index

--- spec/Component.coffee
+++ spec/Component.coffee
@@ -434,6 +434,31 @@
         done() unless expected.length
       sin1.post new noflo.IP 'data', 'first'
       sin1.post new noflo.IP 'data', 'second'
+
+    it 'trying to send to addressable port without providing index should fail', (done) ->
+      c = new noflo.Component
+        inPorts:
+          foo:
+            datatype: 'string'
+        outPorts:
+          baz:
+            datatype: 'boolean'
+            addressable: true
+        process: (input, output) ->
+          return unless input.hasData 'foo'
+          packet = input.get 'foo'
+          noIndex = new noflo.IP 'data', packet.data
+          chai.expect(-> output.sendDone noIndex).to.throw Error
+          done()
+
+      c.inPorts.foo.attach sin1
+      c.outPorts.baz.attach sout1, 1
+      c.outPorts.baz.attach sout2, 0
+
+      sout1.on 'ip', (ip) ->
+      sout2.on 'ip', (ip) ->
+
+      sin1.post new noflo.IP 'data', 'first'

     it 'should not be triggered by non-triggering ports', (done) ->
       triggered = []


Fix stream typo and change open&closeBracket data to null

--- spec/Component.coffee
+++ spec/Component.coffee
@@ -1593,11 +1593,11 @@
         c.forwardBrackets = {}
         c.inPorts.in.attach sin1

-        sin1.post new noflo.IP 'openBracket', ''
-        sin1.post new noflo.IP 'openBracket', ''
-        sin1.post new noflo.IP 'openBracket', ''
+        sin1.post new noflo.IP 'openBracket', null
+        sin1.post new noflo.IP 'openBracket', null
+        sin1.post new noflo.IP 'openBracket', null
         sin1.post new noflo.IP 'data', 'eh'
-        sin1.post new noflo.IP 'closeBracket', ''
+        sin1.post new noflo.IP 'closeBracket', null

       it 'should trigger when forwardingBrackets because then it is only data with no brackets and is a full stream', (done) ->
         c = new noflo.Component
@@ -1627,13 +1627,13 @@
           process: (input, output) ->
             return unless input.hasStream 'eh'
             originalBuf = input.buffer.get 'eh'
-            stream = input.getStream 'in'
+            stream = input.getStream 'eh'
             afterStreamBuf = input.buffer.get 'eh'
             chai.expect(stream).to.eql originalBuf
             chai.expect(afterStreamBuf).to.eql []
             done()

         c.inPorts.eh.attach sin1
-        sin1.post new noflo.IP 'openBracket', ''
+        sin1.post new noflo.IP 'openBracket', null
         sin1.post new noflo.IP 'data', 'moose'
-        sin1.post new noflo.IP 'closeBracket', ''
+        sin1.post new noflo.IP 'closeBracket', null


Update the rest of Component tests

Update the rest of Component tests

--- spec/Component.coffee
+++ spec/Component.coffee
@@ -792,7 +792,7 @@
       sin1.post new noflo.IP 'data', 'some-data'

     it 'should send out string other port if there is only one port aside from error', (done) ->
-      c = new component.Component
+      c = new noflo.Component
         inPorts:
           in:
             datatype: 'all'
@@ -814,10 +814,10 @@
       c.inPorts.in.attach sin1
       c.outPorts.out.attach sout1

-      sin1.post new IP 'data', 'first'
+      sin1.post new noflo.IP 'data', 'first'

     it 'should send object out other port if there is only one port aside from error', (done) ->
-      c = new component.Component
+      c = new noflo.Component
         inPorts:
           in:
             datatype: 'all'
@@ -839,11 +839,11 @@
       c.inPorts.in.attach sin1
       c.outPorts.out.attach sout1

-      sin1.post new IP 'data', 'first'
+      sin1.post new noflo.IP 'data', 'first'

     it 'should throw an error if sending without specifying a port and there are multiple ports', (done) ->
       try
-        c = new component.Component
+        c = new noflo.Component
           inPorts:
             in:
               datatype: 'string'
@@ -859,7 +859,7 @@
             output.sendDone 'test'

         c.inPorts.in.attach sin1
-        sin1.post new IP 'data', 'some-data'
+        sin1.post new noflo.IP 'data', 'some-data'
       catch e
         done()


Remove unnecessary member declarations.

Remove unnecessary member declarations.


recovering a shame :)

recovering a shame :)

--- client/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
+++ client/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
@@ -61,7 +61,7 @@

             #{Encoder.XSSEncode name} #{Encoder.XSSEncode version} by #{Encoder.XSSEncode author}

#{Encoder.XSSEncode description.slice(0,200)}#{if description.length > 199 then '...' else ''}

-            #{additionalinfo}
+            #{if additionalinfo then "#{Encoder.XSSEncode additionalinfo}" else ""}

           """
       click    : -> no
@@ -242,7 +242,7 @@
     if data.type is 'comingsoon'
       data.disabled = yes

-    data.additionalinfo = "This is a shortcut for an internal Koding Application"
+    data.additionalinfo = "This is a shortcut for an internal Koding Application"

     super options, data


Added link to informatics from problem page

--- client/components/Problem.coffee
+++ client/components/Problem.coffee
@@ -2,5 +2,9 @@
 import { Link } from 'react-router-dom'

 export default Problem = (props) ->
-

+    href = "http://informatics.mccme.ru/moodle/mod/statements/view3.php?chapterid=" + props.material._id.substring(1)
+

+

+

+        Задача на informatics


deal with nil file_data

deal with nil file_data

--- client/lanes/models/Asset.coffee
+++ client/lanes/models/Asset.coffee
@@ -53,7 +53,7 @@
         Lanes.config.api_path + '/asset'

     urlFor: (type) ->
-        data = @file_data[type]
+        data = @file_data?[type]
         if data then "#{@baseUrl()}/#{data.id}" else undefined

     onBlobChange: ->


rename

rename

--- spec/juno-spec.coffee
+++ spec/juno-spec.coffee
@@ -4,7 +4,7 @@
 juno.connection.process.jlpath = -> "julia"
 juno.connection.process.workingDir = -> process.env.HOME || process.env.USERPROFILE

-describe "package activation", ->
+describe "the package", ->
   it "activates without errors", ->
     waitsForPromise ->
       atom.packages.activatePackage 'ink'


Update spec

Update spec

--- spec/list-spec.coffee
+++ spec/list-spec.coffee
@@ -88,7 +88,19 @@
     listPackages [], ->
       expect(console.log.argsForCall[1][0]).toContain 'test-module@1.0.0 (disabled)'

-  it 'displays only enabled packages --enabled is called', ->
+  it 'displays only enabled packages when --enabled is called', ->
+    atomPackages =
+      'test-module':
+        metadata:
+          name: 'test-module'
+          version: '1.0.0'
+      'test2-module':
+        metadata:
+          name: 'test2-module'
+          version: '1.0.0'
+
+    fs.writeFileSync(path.join(resourcePath, 'package.json'), JSON.stringify(_atomPackages: atomPackages))
+
     packagesPath = path.join(atomHome, 'packages')
     fs.makeTreeSync(packagesPath)
     wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures', 'test-module'), path.join(packagesPath, 'test-module'))
@@ -97,7 +109,10 @@
       core: disabledPackages: ["test-module"]

     listPackages ['--enabled'], ->
-      expect(console.log.argsForCall[1][0]).toContain '(empty)'
+      expect(console.log.argsForCall[1][0]).toContain 'test2-module@1.0.0'
+      expect(console.log.argsForCall[3][0]).toContain 'dev-package@1.0.0'
+      expect(console.log.argsForCall[6][0]).toContain 'user-package@1.0.0'
+      expect(console.log.argsForCall[9][0]).toContain 'git-package@1.0.0'

   it 'lists packages in json format when --json is passed', ->
     listPackages ['--json'], ->


ride specified

ride specified

--- spec/ride_spec.coffee
+++ spec/ride_spec.coffee
@@ -5,10 +5,17 @@

   describe "'constructor'", ->

+    # we use lightweight objects
+    # we have:
+    #     orig and dest as strings of the primary place keys
+    #     arr and dep as number of the unix time stamp
+    # various convenience methods provide objects of Place and Date
+
     it "should accept Place key strings", ->
       r = new Ride(orig:"DE:RP:Mainz", dest:"DE:Berlin:Berlin")
       expect(r.origin().constructor).toBe(Place)
-      expect(r.origin().source()).toBe("Mainz")
+      expect(r.orig).toBe("DE:RP:Mainz")
+      expect(r.origin().city()).toBe("Mainz")

     it "should accept Place objects", ->
       r = new Ride(
@@ -16,9 +23,70 @@
         dest: new Place("DE:Berlin:Berlin"))
       expect(r.origin().constructor).toBe(Place)
       expect(r.origin().city()).toBe("Mainz")
-    it  "should work with time of arrival", ->
-      r
+
+    it  "should accept timestamps", ->
+      r = new Ride(
+        orig:"DE:RP:Mainz", dest: "DE:Berlin:Berlin"
+        dep: 959143320000, arr: 959157720000
+      expect(r.departure()).toEqual(new Date(959143320000))
+      expect(r.arrival()).toEqual(new Date(959157720000))

+
+    it  "should accept date objects", ->
+      r = new Ride(
+        orig: new Place("DE:RP:Mainz"), dest: new Place("DE:Berlin:Berlin")
+        dep: new Date(959143320000), arr: new Date(959157720000)
+      expect(r.dep).toEqual(new Date(959143320000))
+      expect(r.arr).toEqual(new Date(959157720000))
+
+    it "should tolerate various terminology", ->
+      r1 = new Ride(source:"DE:RP:Mainz", ziel:"DE:Berlin:Berlin")
+      r2 = new Ride(from:"DE:RP:Mainz", to:"DE:Berlin:Berlin")
+      r3 = new Ride(origin:"DE:RP:Mainz", target:"DE:Berlin:Berlin")
+      expect(r1).toEqual(r2)
+      expect(r2).toEqual(r3)
+
+  describe 'methods', ->
+
+    it "origin should have convienence accessors", ->
+      r = new Ride(orig:"DE:RP:Mainz", dest:"DE:Berlin:Berlin")
+      expect(r.source()).toEqual(r.origin())
+      expect(r.start()).toEqual(r.origin())
+      expect(r.from()).toEqual(r.origin())
+      expect(r.origin()).toEqual(new Place(r.orig))
+    it "destination should have convienence accessors", ->
+      r = new Ride(orig:"DE:RP:Mainz", dest:"DE:Berlin:Berlin")
+      expect(r.to()).toEqual(r.destination())
+      expect(r.target()).toEqual(r.destination())
+      expect(r.ziel()).toEqual(r.destination())
+      expect(r.destination()).toEqual(new Place(r.dest))
+
+  describe "json method", ->
+    it "should serialise to a proper json string", ->
+      r = new Ride(orig:"DE:RP:Mainz", dest:"DE:Berlin:Berlin")
+      json = r.toJson()
+      json_template = ""
+      expect(r.toJson()).toBe('{"orig":"DE:RP:Mainz","dest":"DE:Berlin:Berlin"}')
+
+  describe "details", ->
+      r = new Ride
+        orig: "DE:RP:Mainz"
+        dest: "DE:Berlin:Berlin"
+        provider: "deinbus.de"
+        mode: "bus"
+        id: "/checkout/cart/add/product/2100"
+        price: 14
+        currency: "€" # default is bitcoin
+      expect(r.link()).toBe("http://www.deinbus.de/checkout/cart/add/product/2100")
+      expect(r.image()).toBe("http://ride2go.com/images/provider/deinbus.de.png")
+      expect(r.displayPrice()).toBe("14,00 €")
+      expect(r.mode).toBe("bus")
+      expect(r.toGo).toBe("ready to go :-)")
+
+  xdescribe "expiration logic ... part of RDS?", ->
+
+    xit "TODO: should serialise to a dycapo json string"
+
   describe 'constructor switcher', ->
     fromStringConstructor = {}
     beforeEach ->


Minor fixes.

Minor fixes.

--- src/api/xapi/index.coffee
+++ src/api/xapi/index.coffee
@@ -9,7 +9,8 @@
     'task'
   ]
   $_.each types, (type) ->
-    exports["#{type}.destroy"] = (session, request) ->
+    exports[type] ?= {}
+    exports[type].destroy = (session, request) ->
       {id} = request.params
       @throw 'INVALID_PARAMS' unless id?

@@ -19,7 +20,7 @@
       @checkPermission session, 'admin'

       # Retrieves the object.
-      object = @xo.objects.get id
+      object = @xo.xobjs.get id
       @throw 'NO_SUCH_OBJECT' unless object?

       # Gets the corresponding connection.


Scope split-view bindings on body so fuzzy-finder can open in splits

Previously, they were scoped on .pane, but fuzzy-finder isn't inside
a pane and still needs to be able to respond to split events.

--- src/app/keymaps/atom.cson
+++ src/app/keymaps/atom.cson
@@ -30,15 +30,16 @@
   'ctrl-tab': 'window:focus-next-pane'
   'ctrl-meta-f': 'window:toggle-full-screen'

+  'ctrl-|': 'pane:split-right'
+  'ctrl-w v': 'pane:split-right'
+  'ctrl--': 'pane:split-down'
+  'ctrl-w s': 'pane:split-down'
+
 '.pane':
   'meta-{': 'pane:show-previous-item'
   'meta-}': 'pane:show-next-item'
   'alt-meta-left': 'pane:show-previous-item'
   'alt-meta-right': 'pane:show-next-item'
-  'ctrl-|': 'pane:split-right'
-  'ctrl-w v': 'pane:split-right'
-  'ctrl--': 'pane:split-down'
-  'ctrl-w s': 'pane:split-down'

 '.tool-panel':
   'meta-escape': 'tool-panel:unfocus'


Fix path for subscriptions

Fix path for subscriptions

--- command-subscription-list.coffee
+++ command-subscription-list.coffee
@@ -36,7 +36,7 @@
       protocol: 'http'
       hostname: hostname
       port: port
-      pathname: "/device/#{uuid}/subscriptions"
+      pathname: "/devices/#{uuid}/subscriptions"

     requestOption =
       url: uri


Separate unavailable port

Separate unavailable port

--- components/ConnectRuntime.coffee
+++ components/ConnectRuntime.coffee
@@ -18,6 +18,10 @@
       runtime:
         datatype: 'object'
         description: 'FBP Runtime instance'
+        required: true
+      unavailable:
+        datatype: 'object'
+        description: 'Unavailable FBP Runtime instance'
         required: true
       error:
         datatype: 'object'
@@ -52,6 +56,12 @@
     onError = (e) =>
       @outPorts.error.send e
       @outPorts.error.disconnect()
+
+      return unless rt
+      @outPorts.unavailable.beginGroup definition.id
+      @outPorts.unavailable.send rt
+      @outPorts.unavailable.endGroup()
+      @outPorts.unavailable.disconnect()
       return

     rt = new Runtime definition


Revert commented code.

Revert commented code.

--- src/coffee/mapping.coffee
+++ src/coffee/mapping.coffee
@@ -136,9 +136,9 @@
   mapNumber: (rawNumber, attribName, rowIndex) ->
     return if _.isString(rawNumber) and rawNumber.length is 0
     number = parseInt rawNumber
-#    if "#{number}" isnt rawNumber
-#      @errors.push "[row #{rowIndex}:#{attribName}] The number '#{rawNumber}' isn't valid!"
-#      return
+    if "#{number}" isnt rawNumber
+      @errors.push "[row #{rowIndex}:#{attribName}] The number '#{rawNumber}' isn't valid!"
+      return
     number

   # "a.en,a.de,a.it"


Retire unused method _throw_throws_must_be_after_expects_or_args.

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -279,9 +279,6 @@
 _throw_returns_and_throws_both_called = ->
   throw new Error("you called returns() and throws() on the same expectation; use one or the other but not both")

-_throw_throws_must_be_after_expects_or_args = ->
-  throw ".throws() must be called immediately after .expects() or .args()"
-
 _throw_unknown_expectation = (expectation) ->
   throw "#{expectation} does not match any expectations"


add new selector logic

add new selector logic

--- src/Stylesheet.coffee
+++ src/Stylesheet.coffee
@@ -82,7 +82,7 @@
         sheet.deleteRule(previous.length)
     else
       body = property + ':' + value
-      selectors = @getSelector(operation)
+      selectors = @getSelector(stylesheet, operation)
       index = sheet.insertRule(selectors + "{" + body + "}", previous.length)
     return true

@@ -193,10 +193,10 @@

     return sheet.join('')

-  getSelector: (operation) ->
-    return @getSelectors(operation).join(', ')
-
-  getSelectors: (operation) ->
+  getSelector: (stylesheet, operation) ->
+    return @getSelectors(stylesheet, operation).join(', ')
+
+  getSelectors: (stylesheet, operation) ->
     parent = operation
     results = wrapped = custom = undefined

@@ -211,35 +211,34 @@

       # Add rule selector to path
       else if parent.command.type == 'Iterator'
-        query = parent[1]
-
-        selectors = []
         # Prepend selectors with selectors of a parent rule
-        if results?.length
-          update = []
-
-          for result, index in results
-            if result.substring(0, 12) == ' [matches~="'
-              update.push ' ' + @getCustomSelector(query.command.path, result)
-            else
-              for selector in @getRuleSelectors(parent[1])
-                update.push selector + result
-          results = update
-        # Wrap custom selectors
-        else
-          results = @getRuleSelectors(parent[1], true)
-
-
-      parent = parent.parent
+        results = @combineSelectors(results, parent[1])
+
+      if !(parent = parent.parent) && stylesheet?.selectors
+        results = @combineSelectors(results,  stylesheet)

     return results

-  getRuleSelectors: (operation) ->
-    if operation[0] == ','
-      for index in [1 ... operation.length] by 1
-        @getRuleSelector(operation[index], operation.command)
-    else
-      return [@getRuleSelector(operation)]
+  empty: ['']
+
+  combineSelectors: (results = @empty, operation) ->
+    update = []
+    for result, index in results
+      if operation.selectors
+        if result.substring(0, 12) == ' [matches~="'
+          update.push ' ' + @getCustomSelector(result.selector, result)
+        else
+          for selector in operation.selectors
+            update.push selector + result
+      else if result.substring(0, 12) == ' [matches~="'
+        update.push ' ' + @getCustomSelector(operation.command.path, result)
+      else if operation[0] == ','
+        for index in [1 ... operation.length] by 1
+          update.push @getRuleSelector(operation[index], operation.command) + result
+      else
+        update.push @getRuleSelector(operation) + result
+    return update
+

   getRuleSelector: (operation, parent) ->
     command = operation.command


fix renderer to allow for HTMLElement

fix renderer to allow for HTMLElement

--- src/coffee/controller.coffee
+++ src/coffee/controller.coffee
@@ -36,12 +36,12 @@
   # Rendering

   _getBody: ->
-    $ @bodySelector
+    $(@bodySelector)

   _render: (contents) ->
     return false if @rendered
     @rendered = true
-    @_getBody().html SwanKiosk.Layout.build(contents)
+    @_getBody().get(0).appendChild SwanKiosk.Layout.build(contents)

   _renderPlain: (contents) ->
     return false if @rendered


Ensure filters are properly added to params

--- src/collections/teams.coffee
+++ src/collections/teams.coffee
@@ -67,8 +67,14 @@
     types.splice types.indexOf('availability'), 1

   params = teamId: teamId, types: types.map(@underscoreType).join(',')
-  if loadParams?.scopeTo?
-    params.scopeTo = @underscoreType(loadParams.scopeTo)
+  if loadParams?
+    if loadParams.scopeTo?
+      params.scopeTo = @underscoreType(loadParams.scopeTo)
+    # Check loadParams for filters
+    for key, value of loadParams
+      if key.indexOf('__') isnt -1
+        params[key] = value
+
   @collections.root.queryItems 'bulkLoad', params, callback


Make @probablycorey happier

Make @probablycorey happier

--- src/app/buffer.coffee
+++ src/app/buffer.coffee
@@ -9,6 +9,7 @@
 module.exports =
 class Buffer
   @idCounter = 1
+  undoManager: null
   modified: null
   lines: null
   file: null


Remove serialization methods from editor

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -55,11 +55,6 @@
   newSelections: null
   redrawOnReattach: false

-  @deserialize: (state) ->
-    editor = new Editor(mini: state.mini, editSession: deserialize(state.editSession))
-    editor.isFocused = state.isFocused
-    editor
-
   initialize: (editSessionOrOptions) ->
     if editSessionOrOptions instanceof EditSession
       editSession = editSessionOrOptions
@@ -90,15 +85,6 @@
       )
     else
       throw new Error("Must supply an EditSession or mini: true")
-
-  serialize: ->
-    @saveScrollPositionForActiveEditSession()
-    deserializer: "Editor"
-    editSession: @activeEditSession.serialize()
-    isFocused: @isFocused
-
-  copy: ->
-    Editor.deserialize(@serialize(), rootView)

   bindKeys: ->
     editorBindings =


Always store cursor row as a buffer row

Always store cursor row as a buffer row

Previously the buffer vs. screen row difference
was causing the highlight to not always be present

--- src/app/gutter.coffee
+++ src/app/gutter.coffee
@@ -9,7 +9,7 @@
     @div class: 'gutter', =>
       @div outlet: 'lineNumbers', class: 'line-numbers'

-  cursorRow: -1
+  cursorBufferRow: -1
   firstScreenRow: -1

   afterAttach: (onDom) ->
@@ -21,13 +21,13 @@
   renderLineNumbers: (startScreenRow, endScreenRow) ->
     @firstScreenRow = startScreenRow
     lastScreenRow = -1
-    currentCursorRow = @cursorRow
+    currentCursorBufferRow = @cursorBufferRow
     rows = @editor().bufferRowsForScreenRows(startScreenRow, endScreenRow)

     @lineNumbers[0].innerHTML = $$$ ->
       for row in rows
         rowClass = null
-        if row isnt currentCursorRow
+        if row isnt currentCursorBufferRow
           rowClass = 'line-number'
         else
           rowClass = 'line-number cursor-line-number'
@@ -47,8 +47,9 @@
   highlightCursorLine: ->
     return if @firstScreenRow < 0

-    newCursorRow = @editor().getCursorBufferPosition().row - @firstScreenRow
-    if newCursorRow isnt @cursorRow
-      @cursorRow = newCursorRow
+    newCursorBufferRow = @editor().getCursorBufferPosition().row
+    if newCursorBufferRow isnt @cursorBufferRow
+      @cursorBufferRow = newCursorBufferRow
+      screenRow = @cursorBufferRow - @firstScreenRow
       @find('.line-number.cursor-line-number').removeClass('cursor-line-number')
-      @find(".line-number:eq(#{newCursorRow})").addClass('cursor-line-number')
+      @find(".line-number:eq(#{screenRow})").addClass('cursor-line-number')


Load user's .atom config after root view is attached

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -21,8 +21,8 @@
     $(document).on 'keydown', @_handleKeyEvent

   startup: (path) ->
+    @attachRootView(path)
     @loadUserConfiguration()
-    @attachRootView(path)
     $(window).on 'close', => @close()
     $(window).on 'beforeunload', =>
       @shutdown()


memoize Buffer @contentOnDisk so we don't re-read the whole file on every buffer-change event

--- src/app/buffer.coffee
+++ src/app/buffer.coffee
@@ -14,6 +14,7 @@
   @idCounter = 1
   undoManager: null
   modified: null
+  contentOnDisk: null
   modifiedOnDisk: null
   lines: null
   file: null
@@ -30,7 +31,8 @@
     if path
       throw "Path '#{path}' does not exist" unless fs.exists(path)
       @setPath(path)
-      @setText(fs.read(@getPath()))
+      @contentOnDisk = fs.read(@getPath())
+      @setText(@contentOnDisk)
     else
       @setText('')

@@ -68,7 +70,8 @@
       @trigger "path-change", this

   reload: ->
-    @setText(fs.read(@file.getPath()))
+    @contentOnDisk = fs.read(@getPath())
+    @setText(@contentOnDisk)
     @modified = false
     @modifiedOnDisk = false

@@ -236,6 +239,7 @@
     @modified = false
     @modifiedOnDisk = false
     @setPath(path)
+    @contentOnDisk = @getText()
     @trigger 'after-save'
     @trigger 'buffer-change'

@@ -249,7 +253,7 @@
     @modified

   contentDifferentOnDisk: ->
-    fs.read(@file.getPath()) != @getText()
+    @contentOnDisk != @getText()

   getAnchors: -> new Array(@anchors...)


Restore window dimensions after atom.windowMode is set.

It's required to set atom.windowMode before getting window state.

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -36,12 +36,6 @@
   if nativeStylesheetPath = fsUtils.resolveOnLoadPath(process.platform, ['css', 'less'])
     requireStylesheet(nativeStylesheetPath)

-  dimensions = atom.getWindowState('dimensions')
-  dimensions = defaultWindowDimensions unless dimensions?.width and dimensions?.height
-  window.setDimensions(dimensions)
-  remote.getCurrentWindow().show()
-  $(window).on 'unload', -> atom.setWindowState('dimensions', window.getDimensions())
-
 # This method is only called when opening a real application window
 window.startEditorWindow = ->
   installAtomCommand()
@@ -49,6 +43,7 @@

   atom.windowMode = 'editor'
   windowEventHandler = new WindowEventHandler
+  restoreDimensions()
   config.load()
   keymap.loadBundledKeymaps()
   atom.loadThemes()
@@ -61,10 +56,12 @@
     atom.hide()
     unloadEditorWindow()
     false
-  $(window).focus()
+  remote.getCurrentWindow().show()
+  atom.focus()

 window.startConfigWindow = ->
   atom.windowMode = 'config'
+  restoreDimensions()
   windowEventHandler = new WindowEventHandler
   config.load()
   keymap.loadBundledKeymaps()
@@ -77,7 +74,8 @@
     atom.hide()
     unloadConfigWindow()
     false
-  $(window).focus()
+  remote.getCurrentWindow().show()
+  atom.focus()

 window.unloadEditorWindow = ->
   return if not project and not rootView
@@ -208,6 +206,12 @@
   browserWindow.setPosition(x, y)
   browserWindow.setSize(width, height)

+window.restoreDimensions = ->
+  dimensions = atom.getWindowState('dimensions')
+  dimensions = defaultWindowDimensions unless dimensions?.width and dimensions?.height
+  window.setDimensions(dimensions)
+  $(window).on 'unload', -> atom.setWindowState('dimensions', window.getDimensions())
+
 window.closeWithoutConfirm = ->
   ipc.sendChannel 'close-without-confirm'


Enable zooming out by clicking the ancestor nodes

--- src/d3-flame-graph.coffee
+++ src/d3-flame-graph.coffee
@@ -7,7 +7,7 @@
     tokens = fqdn.split(".")
     tokens.slice(tokens.length - 2).join(".")

-  # Return a vector (0.0->1.0) that is a hash of the input string.
+  # Return a vector (0.0 -> 1.0) that is a hash of the input string.
   # The hash is computed to favor early characters over later ones, so
   # that strings with similar starts have similar vectors. Only the first
   # 6 characters are considered.
@@ -61,7 +61,7 @@

     zoom: (node) ->
       if node in @_ancestors
-        @_ancestors.slice(0, @_ancestors.indexOf(node))
+        @_ancestors = @_ancestors.slice(0, @_ancestors.indexOf(node))
       else
         @_ancestors.push(@data()[0])
       @data(node).render(@_selector)
@@ -144,7 +144,7 @@
       console.timeEnd('render')

       console.log("Rendered #{@container.selectAll('.node')[0]?.length} elements")
-      @_enableNavigation()._renderBreadcrumbs() if @breadcrumbs()
+      @_renderBreadcrumbs()._enableNavigation() if @breadcrumbs()
       @_renderTooltip()                         if @tooltip()
       @

@@ -208,6 +208,12 @@
         .on 'click', (d, idx) =>
           d3.event.stopPropagation()
           @zoom(d) if idx > 0
+      @container
+        .selectAll('.ancestor')
+        .on 'click', (d, idx) =>
+          console.log(d)
+          d3.event.stopPropagation()
+          @zoom(@_ancestors[idx])
       @

     _generateAccessors: (accessors) ->


fix oauth 1

fix oauth 1

--- src/core/oauth/oauth1.coffee
+++ src/core/oauth/oauth1.coffee
@@ -102,8 +102,8 @@
 				options.oauth.verifier = req.params.oauth_verifier
 			else
 				options.oauth.verifier = ''
-			if ( ! options.callback)
-				delete options.callback
+			if ( ! options.oauth.callback)
+				delete options.oauth.callback
 			delete query.oauth_callback

 			# do request to access_token


al-ctrl, able to inherit

al-ctrl, able to inherit

--- src/directive/ctrl.coffee
+++ src/directive/ctrl.coffee
@@ -11,24 +11,30 @@
                 element: element
             return

-        $ns = scope.$ns
-        if $ns and $ns.ctrl
-            fn = $ns.ctrl[name]
-            if not fn and not $ns.inheritGlobal
-                error '', 'Controller not found in $ns: ' + name
+        self =
+            getController: (name) ->
+                $ns = scope.$ns
+                if $ns and $ns.ctrl
+                    fn = $ns.ctrl[name]
+                    if not fn and not $ns.inheritGlobal
+                        error '', 'Controller not found in $ns: ' + name
+                        return
+
+                if not fn
+                    fn = alight.ctrl[name]
+
+                    if not fn and alight.d.al.ctrl.global
+                        fn = window[name]
+
+                if not fn
+                    error '', 'Controller not found: ' + name
+                fn
+
+            start: ->
+                fn = self.getController name
+                if fn
+                    try
+                        fn scope, element, name, env
+                    catch e
+                        error e, 'Error in controller: ' + name
                 return
-
-        if not fn
-            fn = alight.ctrl[name]
-
-            if not fn and alight.d.al.ctrl.global
-                fn = window[name]
-
-        if fn
-            try
-                fn scope, element, name, env
-            catch e
-                error e, 'Error in controller: ' + name
-        else
-            error '', 'Controller not found: ' + name
-        return


Only show repo icon at root of tree

Only show repo icon at root of tree

--- src/directory-view.coffee
+++ src/directory-view.coffee
@@ -26,7 +26,7 @@
     if repo?
       path = @directory.getPath()
       @directoryName.addClass('ignored') if repo.isPathIgnored(path)
-      if path is repo.getWorkingDirectory()
+      if not parent and path is repo.getWorkingDirectory()
         @directoryName.addClass('repository-icon')
       else if repo.isSubmodule(path)
         @directoryName.addClass('submodule-icon')


Ensure isEqual is called with two objects with the same prototype

--- src/display-marker.coffee
+++ src/display-marker.coffee
@@ -191,6 +191,7 @@
   #
   # * `other` {DisplayMarker} other marker
   isEqual: (other) ->
+    return false unless other instanceof @constructor
     @bufferMarker.isEqual(other.bufferMarker)

   ###


Touch support

Touch support

--- src/controller.coffee
+++ src/controller.coffee
@@ -2329,6 +2329,33 @@
       @dragCanvas.style.left = '-9999px'

     @dragCanvas.style.zIndex = 0
+
+  # TOUCHSCREEN SUPPORT
+  # =================================
+
+  # We will attempt to emulate
+  # mouse events using touchstart/end
+  # data.
+  touchEvents =
+    'touchstart': 'mousedown'
+    'touchmove': 'mousemove'
+    'touchend': 'mouseup'
+
+  # We will bind the same way as mouse events do,
+  # wrapping to be compatible with a mouse event interface.
+  hook 'populate', 0, ->
+    for touchEvent, mouseEvent of touchEvents then do (touchEvent, mouseEvent) =>
+      @tracker.addEventListener touchEvent, (event) =>
+        # We will take the first touch (we do not support multitouch)
+        trackPoint = @getPointRelativeToTracker event.touches[0]
+
+        # We keep a state object so that handlers
+        # can know about each other.
+        state = {}
+
+        # Call all the handlers.
+        for handler in editorBindings[mouseEvent]
+          handler.call this, trackPoint, event, state

   # CLOSING FOUNDATIONAL STUFF
   # ================================


Rendering improvements.

Rendering improvements.

--- app/draw.coffee
+++ app/draw.coffee
@@ -173,6 +173,8 @@
 robot_label = (r) ->
   r.robot_id

+transitionDuration = 750
+
 drawField = (field_geometry, is_blue_left=true) ->

   # spacing between lines
@@ -222,36 +224,42 @@

   f.select(".field-line")
     .transition()
-    .duration(750)
+    .duration(transitionDuration)
     .attr("d", field_path)

   f.select(".left-goal")
     .classed("blue", is_blue_left)
     .classed("yellow", not is_blue_left)
     .transition()
-    .duration(750)
+    .duration(transitionDuration)
     .attr("d", left_goal_path)

   f.select(".right-goal")
     .classed("blue", not is_blue_left)
     .classed("yellow", is_blue_left)
     .transition()
-    .duration(750)
+    .duration(transitionDuration)
     .attr("d", right_goal_path)

   sp = 25

   f.select(".time-left")
-    .attr("x", 0)
+    .transition()
+    .duration(transitionDuration)
+    .attr("x", 2)
     .attr("y", (f) -> -f.field_width / 2 - sp)

   f.select(".left-name")
+    .transition()
+    .duration(transitionDuration)
     .attr("x", (f) -> -f.field_length / 2 + sp)
-    .attr("y", (f) -> -f.field_width / 2 + sp)
+    .attr("y", (f) -> -f.field_width / 2 + sp + vPxPerLetter)

   f.select(".right-name")
+    .transition()
+    .duration(transitionDuration)
     .attr("x", (f) -> f.field_length / 2 - sp)
-    .attr("y", (f) -> -f.field_width / 2 + sp)
+    .attr("y", (f) -> -f.field_width / 2 + sp + vPxPerLetter)

 # in miliseconds
 max_screen_time = 100
@@ -332,18 +340,25 @@
       not d.timestamp? or timestamp - d.timestamp > max_screen_time or d.timestamp > timestamp
     .remove()

+hPxPerLetter = 156
+vPxPerLetter = 250
+hPxPerLetterSm = 93
+
 drawReferee = (referee, is_blue_left) ->
   #d3.select("#time_left").datum(referee).html((d) -> ticks_to_time(d.stage_time_left))
   svg.select(".time-left").datum(referee.stage_time_left)
     .text(ticks_to_time)
+    .attr("textLength", (d) -> ticks_to_time(d).length * hPxPerLetterSm)

   [left, right] = if is_blue_left then [referee.blue, referee.yellow] else [referee.yellow, referee.blue]

   svg.select(".left-name").datum(left)
     .text((d) -> d.name)
+    .attr("textLength", (d) -> d.name.length * hPxPerLetter)

   svg.select(".right-name").datum(right)
     .text((d) -> d.name)
+    .attr("textLength", (d) -> d.name.length * hPxPerLetter)

   persistTip("#{stg2txt(referee.stage)}: #{cmd2txt(referee.command)}")
   window.referee = referee
@@ -445,7 +460,6 @@
 # Time voodoo: converting ticks to minutes:seconds
 #XXX: tried to use moment.js without success
 ticks_to_time = (ticks) ->
-  #TODO: time may be negative, ought to represent that
   if ticks?
     time = "#{pad(~~(Math.abs(ticks) / (60 * 1000000)), 2, " ")}:#{pad(Math.abs(~~(ticks / 1000000)) % 60, 2)}"
     if ticks > 0 then time else "-#{time}"
@@ -458,14 +472,15 @@
 svg.append("path").classed("left-goal", true)
 svg.append("path").classed("right-goal", true)
 svg.append("text").classed("time-left", true)
+  .attr("lengthAdjust", "spacingAndGlyphs")
 svg.append("text").classed("team-name", true)
   .classed("left-name", true)
   .attr("text-anchor", "start")
-  .attr("alignment-baseline", "hanging")
+  .attr("lengthAdjust", "spacingAndGlyphs")
 svg.append("text").classed("team-name", true)
   .classed("right-name", true)
   .attr("text-anchor", "end")
-  .attr("alignment-baseline", "hanging")
+  .attr("lengthAdjust", "spacingAndGlyphs")

 # draw default sized field
 drawField(default_geometry_field)


Changing variable avoid conflicts.

Changing variable avoid conflicts.

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -15,8 +15,8 @@
 describe "TreeView", ->
   [treeView, root, sampleJs, sampleTxt, workspaceElement] = []

-  selectEntry = (path) ->
-    treeView.selectEntryForPath atom.project.resolve path
+  selectEntry = (pathToSelect) ->
+    treeView.selectEntryForPath atom.project.resolve pathToSelect

   beforeEach ->
     fixturesPath = atom.project.getPaths()[0]


Configure api

Configure api

--- app/main.coffee
+++ app/main.coffee
@@ -24,10 +24,7 @@
 feedbackContent = require './views/feedback_page'

 app = {}
-api = new Api project: 'serengeti'
-
-#TODO do we need this
-$('.before-load').remove()
+api = new Api project: 'wisconsin'

 app.stack = new Stack
   className: "main #{Stack::className}"
@@ -53,7 +50,6 @@
 app.topBar = new TopBar
 app.footer = new Footer

-#TODO note Navigation does not extend from Controller c.f. Condors:app/controllers/SiteNavigation
 navigation = new Navigation
 app.navigation = navigation


hide context methods

hide context methods

--- modules/omarpc/platforms/node/lib/HttpContext.coffee
+++ modules/omarpc/platforms/node/lib/HttpContext.coffee
@@ -130,7 +130,7 @@
         ##
         ##
         _exec: (o, m, a, _ident, fn)->
-            if !(@_HIDDEN.indexOf(m) is -1)
+            if @_HIDDEN.indexOf(m) > -1
                 return fn('unknown method: '+o+'.'+m)

             ## login
@@ -148,7 +148,7 @@
                     try
                         a.push fn

-                        if obj[m]
+                        if obj[m] && (!obj._HIDDEN || obj._HIDDEN.indexOf(m) is -1)
                             if _ident && obj.forIdentity
                                 obj.forIdentity _ident.identity, (err, wrapper)=>
                                     return fn(err) if err
@@ -177,4 +177,7 @@
             else if typeof obj is 'function'
                 obj fn

-            else fn null, obj
+            else if obj
+                fn null, obj
+
+            else fn new Error 'unknown object: '+o


Update version to 2.0.0.

Update version to 2.0.0.

--- src/jquery.turbolinks.coffee
+++ src/jquery.turbolinks.coffee
@@ -4,7 +4,7 @@
 $document = $(document)

 $.turbo =
-  version: '2.0.0.rc'
+  version: '2.0.0'

   isReady: false


Fix Jakefile

Fix Jakefile

--- Jakefile.coffee
+++ Jakefile.coffee
@@ -1,12 +1,13 @@
+
 desc 'Deploy to production'
 task 'deploy', ->
-    console.log '-----> Deploying...' ;
+    console.log '-----> Deploying...'

     cmds = [
-        //'git ci -m "lol"',
-        //'git push',
-        'cd ../prod',
-        'pwd'
+        #'git add .',
+        #'git ci',
+        #'git push',
+        'cd ~/Sites/prod && git pull && npm install'
     ]

     jake.exec cmds, { printStdOut: true }, () ->
@@ -14,5 +15,4 @@
         complete

-task 'default', ->
-    jake.Task['deploy'].invoke
+task 'default', ['deploy']


Make sure count stays correct.

Make sure count stays correct.

--- app/assets/javascripts/appointments/appointment_sync.service.js.coffee
+++ app/assets/javascripts/appointments/appointment_sync.service.js.coffee
@@ -89,7 +89,8 @@
       type = angular.extend(type, {name: 'Slot Taken', colorClass: 'black', textColor: 'white'})
       angular.extend(existing, {title: type.name || ''})
     else
-      angular.extend(existing, {title: "#{appointment.user?.display || ''}#{if appointment.groupMembersAttributes.length then " (#{appointment.groupMembersAttributes.length})" else ""}"})
+      groupMembers = _.reject(appointment.groupMembersAttributes, (g) -> g.destroy)
+      angular.extend(existing, {title: "#{appointment.user?.display || ''}#{if groupMembers.length then " (#{groupMembers.length})" else ""}"})
     angular.extend(existing, {color: type.colorClass, textColor: type.textColor, allDay: false, appointment: appointment})
     if !appointment.disableEdit && @can_access(appointment)
       existing.editable = true


Added show, cancel and select events

Added show, cancel and select events

Added three events to the datepicker component:
— show: Triggered when the datepicker is shown
— cancel: Triggered when the datepicker is closed without selecting a
date
— select: Triggered when a new date has been selected

--- src/datepicker.coffee
+++ src/datepicker.coffee
@@ -16,9 +16,13 @@
   create: (model, dom) ->
     global.moment = moment
     dom.on "click", (e) =>
-      model.set "show", true if @parent.contains(e.target)
+      if @parent.contains(e.target)
+        model.set "show", true, =>
+          @emit "show"
     dom.on "mousedown", (e) =>
-      model.set "show", false unless @parent.contains(e.target)
+      unless @parent.contains(e.target)
+        model.set "show", false, =>
+          @emit "cancel"

   gotoMonthView: (date) ->
     @setCurrentDate date
@@ -82,9 +86,10 @@
     selectedMonth = date.month()
     currentDate = moment(@getCurrentDate())
     currentMonth = currentDate.month()
-    @gotoMonthView date  if selectedMonth isnt currentMonth
-    @model.set "active", selectedDate.fullDate
-    @model.set "show", false
+    @emitDelayable "select", =>
+      @gotoMonthView date  if selectedMonth isnt currentMonth
+      @model.set "active", selectedDate.fullDate
+      @model.set "show", false

   prevMonth: ->
     # get current month


Switch orders to use endpoint variable

Switch orders to use endpoint variable

--- src/features/order.coffee
+++ src/features/order.coffee
@@ -1,4 +1,6 @@
   class Order extends Abstract
+
+    endpoint: 'orders'

     constructor: (@m) ->

@@ -6,19 +8,19 @@
     `
     Items: (id, callback, error) ->

-      return @m.Request 'orders/'+id+'/items', 'GET', null, callback, error
+      return @m.Request @endpoint+'/'+id+'/items', 'GET', null, callback, error

     AddItem: (id, data, callback, error) ->

-      return @m.Request 'orders/'+id+'/items', 'POST', data, callback, error
+      return @m.Request @endpoint+'/'+id+'/items', 'POST', data, callback, error

     UpdateItem: (id, data, callback, error) ->

-      return @m.Request 'orders/'+id+'/items', 'PUT', data, callback, error
+      return @m.Request @endpoint+'/'+id+'/items', 'PUT', data, callback, error

     RemoveItem: (order, id, callback, error) ->

-      return @m.Request 'orders/'+order+'/items/'+id, 'DELETE', null, callback, error
+      return @m.Request @endpoint+'/'+order+'/items/'+id, 'DELETE', null, callback, error

     `// @endif
     `


server copy of task plan was getting updated by reference, esp the tasking_plans since that's the nested property, _.clone, which is shallow, would not cut it either.  this means that when updating a saved plan and changing the due date for example, the new due date would not post since tasking plan would appear not to be different from the server info, when in fact the server copy had accidentally been updated with the other stuffs

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -48,13 +48,13 @@
   _server_copy: {}

   _loaded: (obj, planId) ->
-    @_server_copy[planId] = obj
+    @_server_copy[planId] = JSON.stringify(obj)
     obj

   # Somewhere, the local copy gets taken apart and rebuilt.
   # Keep a copy of what was served.
   _getOriginal: (planId) ->
-    @_server_copy[planId]
+    JSON.parse(@_server_copy[planId])

   _getPlan: (planId) ->
     @_local[planId] ?= {}