Fixup profile selection messaging

Fixup profile selection messaging

--- web/party/profile.coffee
+++ web/party/profile.coffee
@@ -21,15 +21,18 @@
       select: () ->
         s = @selected
         @constructor.selection = {}
+        messages.clear(Item)
         if s == true
           selected = false
           @constructor.foreign.selection = {}
-          messages.clear(this)
         else
           selected = true
           @constructor.selection[@id] = true
           @constructor.foreign.selection = @access
-          messages.clear()
+          messages.add
+            body: @selectionMessage()
+            type: 'dark'
+            owner: Item
           @selectionMessage()
         return

@@ -39,11 +42,9 @@
       constructor: (@party) ->
         @access = {}
         Party.all[@party.id] = @
-        @selectionMessage = ->
-          messages.add
-            body: constants.message('profile.state', 'volumes', @party.name)
-            type: 'dark'
-            owner: this
+
+      selectionMessage: ->
+        constants.message('profile.state', 'volumes', @party.name)

       @make = (p) ->
         Party.all[p.id] || new Party(p)
@@ -61,12 +62,8 @@
           p.access[@volume.id] = a
           @access[p.party.id] = a

-        @selectionMessage = ->
-          displayName = if @volume.alias then @volume.alias else @volume.name
-          messages.add
-            body: constants.message('profile.state', 'users', @volume.displayName)
-            type: 'dark'
-            owner: this
+      selectionMessage: ->
+        constants.message('profile.state', 'users', @volume.displayName)

       Object.defineProperty @prototype, 'id',
         get: -> @volume.id


Updated seed method

Updated seed method

--- server/startup/loadBookings.coffee
+++ server/startup/loadBookings.coffee
@@ -1,73 +1,56 @@
 Meteor.startup ->
-  if @Bookings.find().count() == 0
+
+  #users = Meteor.users.find()
+  #users.forEach (u)->
+    #Meteor.users.remove u._id
+
+  if Meteor.users.find().count() is 0
+    users = [
+      { username: 'a' }
+      { username: 'b' }
+      { username: 'c' }
+      { username: 'd' }
+      { username: 'e' }
+    ]
+    console.log "---------"
+    users.forEach (u)->
+      console.log "inserting user #{u.username}"
+      Accounts.createUser
+        username: u.username
+        color: randomColor()
+        email: "#{u.username}@#{u.username}.com",
+        password: ("#{u.username}" for i in [0..5]).join('')
+        confirmed: lodash.random(10) <= 5
+    console.log "---------"
+
+  if @Bookings.find().count() is 0
     console.log "No bookings, inserting...."
-    bookings = [
-      {
-        date: moment.utc([2015,11,12])
-        owner: 1
+    users = Meteor.users.find()
+    uids =   users.map (u)-> u._id
+    cache = {}
+    bookings = [1,2,3,4,5,6,7,8,9,12,12,12,12,13,14,15,16,18,18,19,20,21,21,23,25,26].map (d)->
+      id = null
+      unless "#{d}" of cache
+        id = lodash.sample uids
+        cache["#{d}"] = [id]
+      else
+        v = cache["#{d}"]
+        x = uids.reduce (ar,i)->
+          ar[+(if ~ar[1].indexOf(i) then 1 else 0)].push i
+          ar
+        , [[],v]
+        id = lodash.sample x[0]
+        cache["#{d}"].push id
+      return {
+        date: moment.utc([2015,11,d])
+        owner: id
+        color: lodash.find(users, (u)-> u._id is id)?.color ? randomColor()
       }
-      {
-        date: moment.utc([2015,11,12])
-        owner: 2
-      }
-      {
-        date: moment.utc([2015,11,12])
-        owner: 3
-      }
-      {
-        date: moment.utc([2015,11,12])
-        owner: 4
-      }
-      {
-        date: moment.utc([2015,11,12])
-        owner: 5
-      }
-      {
-        date: moment.utc([2015,11,12])
-        owner: 6
-      }
-      {
-        date: moment.utc([2015,11,13])
-        owner: 1
-      }
-      {
-        date: moment.utc([2015,11,14])
-        owner: 1
-      }
-      {
-        date: moment.utc([2015,11,15])
-        owner: 1
-      }
-      {
-        date: moment.utc([2015,11,16])
-        owner: 1
-      }
-      {
-        date: moment.utc([2015,11,17])
-        owner: 1
-      }
-      {
-        date: moment.utc([2015,11,18])
-        owner: 1
-      }
-      {
-        date: moment.utc([2015,11,22])
-        owner: 1
-      }
-      {
-        date: moment.utc([2015,11,23])
-        owner: 1
-      }
-      {
-        date: moment.utc([2015,11,24])
-        owner: 1
-      }
-    ].forEach (b)=>
-      console.log "--------"
-      console.log b.date
+    .forEach (b)=>
       @Bookings.insert
-        date: b.date.toISOString()
+        date: b.date.toDate()
         owner: b.owner
+        color: b.color

   return


no file in import : 400

no file in import : 400

--- server/controllers/ical.coffee
+++ server/controllers/ical.coffee
@@ -38,7 +38,7 @@
                             console.log "failed to cleanup file", file.path, err

         unless file = files['file']?[0]
-            res.send error: 'no file sent', 500
+            res.send error: 'no file sent', 400
             return cleanUp()

         parser = new ical.ICalParser()


use u(window) to bind resize event

use u(window) to bind resize event

--- js/umobi.page.coffee
+++ js/umobi.page.coffee
@@ -94,7 +94,7 @@
           # if touchScroll option is not enabled, we should just
           # adjust cotnent padding to keep space for header and footer.
           if not umobi.config.touchScroll
-            AdjustContentPadding = () ->
+            AdjustContentPadding = ->
               # console.log "pagereveal", h.height(), f.height()
               $contentContainer.css("paddingTop", h.height() + 'px') if h.get(0)
               $contentContainer.css("paddingBottom", f.height() + 'px') if f.get(0)
@@ -115,7 +115,7 @@
             upage.on "pagereveal", AdjustContentHeight

         resizeTimeout = null
-        $(window).on "resize",->
+        u(window).on "resize",->
           clearTimeout(resizeTimeout) if resizeTimeout
           resizeTimeout = setTimeout AdjustContentHeight, 100
         h.addClass("ui-fixed-header") if h.attr "data-fixed"


Prevent selection when right clicking on the gutter

--- lib/table-element.coffee
+++ lib/table-element.coffee
@@ -20,6 +20,8 @@
   e.preventDefault()
   f?(e)

+leftClick = (f) -> (e) -> f?(e) if e.which is 1
+
 module.exports =
 class TableElement extends HTMLElement
   PropertyAccessors.includeInto(this)
@@ -155,7 +157,8 @@
       'click': stopPropagationAndDefault()

     @subscriptions.add @subscribeTo @body, '.tablr-gutter',
-      'mousedown': stopPropagationAndDefault (e) => @startGutterDrag(e)
+      'mousedown': stopPropagationAndDefault leftClick (e) =>
+        @startGutterDrag(e)
       'click': stopPropagationAndDefault()

     @subscriptions.add @subscribeTo @body, '.tablr-gutter .row-resize-handle',


Handle error in ameli konnector

Handle error in ameli konnector

Returns the error if the first request to ameli fails

--- server/konnectors/ameli.coffee
+++ server/konnectors/ameli.coffee
@@ -47,7 +47,8 @@

     # First request to get the cookie
     request options, (err, res, body) ->
-
+        return next err if err?
+
         loginOptions =
             method: 'POST'
             form: form


added order by statements for queries

added order by statements for queries

--- server/lib/server/graph.coffee
+++ server/lib/server/graph.coffee
@@ -96,6 +96,7 @@
       'match koding-[r:user]->users'
       'where koding.name="JApp" and r.createdAt > "2012-11-14T23:56:48Z"'
       'return *'
+      'order by r.createdAt DESC'
       'limit 40'
     ].join('\n');

@@ -118,6 +119,7 @@
       'start koding=node:koding(\'id:*\')'
       'where koding.name="JAccount" and koding.`meta.createdAt` > "2013-02-14T23:56:48Z"'
       'return *'
+      'order by koding.`meta.createdAt` DESC'
       'limit 40'
     ].join('\n');


More cleanup. CS is great

More cleanup. CS is great

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -31,31 +31,24 @@
   # Services (order is important here)
   services =
     kite_webterm:
-      count: 0
       pattern: 'webterm'
     worker_auth:
-      count: 0
       pattern: 'auth'
     kite_sharedhosting:
-      count: 0
       pattern: 'kite-sharedHosting'
     kite_applications:
-      count: 0
       pattern: 'kite-application'
     kite_databases:
-      count: 0
       pattern: 'kite-application'
     webserver:
-      count: 0
       pattern: 'web'
     worker_social:
-      count: 0
       pattern: 'social'

   incService = (serviceKey, inc) ->
     for key, value of services
       if serviceKey.indexOf(value.pattern) > -1
-        value.count += inc
+        value.count = if value.count then value.count += inc else 1
         break

   # Presences


No longer scrolls to top when item selected

For New Files.  Whenever an item was selected,
the page would scroll to the top (for the Files
page).  This has to do with how we were redrawing
and repainting the DOM for a11y.

fixes CNVS-17238

Test plan:
* Enable "Better File Browsing" feature flag
* Go to any course's Files page
* Make sure your browser window is small enough
  where you are required to scroll vertically
  to view the whole page
* Select an item in the file pane
* The browser should not scroll to the top
* The folder tree should still be accessible
  on Safari with VO enabled (see below)
* Refresh the page
* Behavior described above should remain intact

How to test for folder tree accessibilty:
(original test plan notes from the commit
that introduced the scrolling bug; this behavior
should still be occurring)
- Load up Better File Browsing
- Use Safari and VoiceOver and go to new files.
- Navigate to the folder listing.
- You should be able to navigate it with VO
- Refresh the page.  The same behavior should occur.
- Navigate into a subfolder. The same behavior should occur.

Change-Id: I619bd4cdded2178bf00c2b3d9d5ec123683d81ab
Reviewed-on: https://gerrit.instructure.com/45694
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Dan Minkevitch <2591e5f46f28d303f9dc027d475a5c60d8dea17a@instructure.com>

--- app/coffeescripts/react_files/utils/forceScreenreaderToReparse.coffee
+++ app/coffeescripts/react_files/utils/forceScreenreaderToReparse.coffee
@@ -1,5 +1,3 @@
 define [], ->
   forceScreenreaderToReparse = (node) ->
-    node.style.display = 'none'
-    node.offsetHeight
-    node.style.display = ''+    node.offsetHeight


Don't use path var name

Don't use path var name

--- lib/git-diff-view.coffee
+++ lib/git-diff-view.coffee
@@ -16,8 +16,8 @@
     atom.project.getRepositories().forEach (repository) =>
       @subscriptions.add repository.onDidChangeStatuses =>
         @scheduleUpdate()
-      @subscriptions.add repository.onDidChangeStatus (path) =>
-        @scheduleUpdate() if path is @editor.getPath()
+      @subscriptions.add repository.onDidChangeStatus (changedPath) =>
+        @scheduleUpdate() if changedPath is @editor.getPath()

     @subscribeToBuffer()


FixeFix Error

FixeFix Error

--- tests/integration/components/file-details-test.coffee
+++ tests/integration/components/file-details-test.coffee
@@ -13,4 +13,4 @@

   @render hbs """{{file-details}}"""

-  assert.equal @$().text().trim(), 'File DetailsHigh: Medium: Low: Passed: Unknown: Static ScanDynamic ScanManual Scancreated onversion codeSelected Device TypeNo PreferenceSelected OS VersionReport Password  CopyPDF ReportVulnerability Details'
+  assert.equal @$().text().trim(), 'File DetailsHigh: Medium: Low: Passed: Unknown:   Static Scan  Dynamic Scan  Manual Scancreated onversion codeSelected Device TypeNo PreferenceSelected OS VersionReport Password  CopyPDF ReportVulnerability Details'


Add goback support

Add goback support

--- views/components/ship/ship.cjsx
+++ views/components/ship/ship.cjsx
@@ -6,6 +6,8 @@
 {PaneBody} = require './parts'

 inBattle = [false, false, false, false]
+goback = {}
+
 getStyle = (state) ->
   if state in [0..5]
     # 0: Cond >= 40, Supplied, Repaired, In port
@@ -77,8 +79,34 @@
         when '/kcsapi/api_port/port'
           names = body.api_deck_port.map (e) -> e.api_name
           inBattle = [false, false, false, false]
+          goback = {}
         when '/kcsapi/api_req_hensei/change', '/kcsapi/api_req_hokyu/charge', '/kcsapi/api_get_member/deck', '/kcsapi/api_get_member/ship_deck', '/kcsapi/api_get_member/ship2', '/kcsapi/api_get_member/ship3', '/kcsapi/api_req_kousyou/destroyship', '/kcsapi/api_req_kaisou/powerup', '/kcsapi/api_req_nyukyo/start', '/kcsapi/api_req_nyukyo/speedchange'
           true
+        # FCF
+        when '/kcsapi/api_req_combined_battle/goback_port'
+          {decks, _ships} = @state
+          damagedShip = -1
+          if damagedShip == -1
+            for shipId in decks[0].api_ship
+              continue if shipId == -1
+              ship = _ships[shipId]
+              if ship.api_nowhp / ship.api_maxhp < 0.250001 and !goback[shipId]
+                damagedShip = shipId
+          if damagedShip == -1
+            for shipId in decks[1].api_ship
+              continue if shipId == -1
+              ship = _ships[shipId]
+              if ship.api_nowhp / ship.api_maxhp < 0.250001 and !goback[shipId]
+                damagedShip = shipId
+          if damagedShip != -1
+            gobackShip = -1
+            for shipId in decks[1].api_ship
+              continue if shipId == -1
+              ship = _ships[shipId]
+              if ship.api_nowhp / ship.api_maxhp > 0.750001 and !goback[shipId]
+                gobackShip = shipId
+            if gobackShip != -1
+              goback[damagedShip] = goback[gobackShip] = true
         when '/kcsapi/api_req_map/start'
           deckId = parseInt(postBody.api_deck_id) - 1
           inBattle[deckId] = true
@@ -88,8 +116,8 @@
           for shipId in deck.api_ship
             continue if shipId == -1
             ship = _ships[shipId]
-            if ship.api_nowhp / ship.api_maxhp < 0.250001
-              toggleModal '出击注意！', "Lv. #{ship.api_lv} - #{ship.api_name} 大破，可能会被击沉！"
+            if ship.api_nowhp / ship.api_maxhp < 0.250001 and !goback[shipId]
+              toggleModal '出击注意！', "Lv. #{ship.api_lv} - #{ship.api_name} 大破，请到舰队面板确认是否有应急修理要员/女神携带！"
         when '/kcsapi/api_req_map/next'
           {decks, states} = @state
           {_ships} = window
@@ -98,8 +126,8 @@
             for shipId in deck.api_ship
               continue if shipId == -1
               ship = _ships[shipId]
-              if ship.api_nowhp / ship.api_maxhp < 0.250001
-                toggleModal '进击注意！', "Lv. #{ship.api_lv} - #{ship.api_name} 大破，可能会被击沉！"
+              if ship.api_nowhp / ship.api_maxhp < 0.250001 and !goback[shipId]
+                toggleModal '进击注意！', "Lv. #{ship.api_lv} - #{ship.api_name} 大破，可能会被击沉，请到舰队面板确认是否有应急修理要员/女神携带！"
         else
           flag = false
       return unless flag


debugging methods

debugging methods

--- evolution.coffee
+++ evolution.coffee
@@ -1,6 +1,7 @@
 # constants
-qt  = Math.PI / 2
-tau = Math.PI * 2
+pi  = Math.PI
+qt  = pi / 2
+tau = pi * 2

 # general handle on everything; keeps track of geometry of entities it contains
 class Universe
@@ -159,7 +160,7 @@
       instance.x       = x
       instance.y       = y
       instance.context = c.getContext '2d'
-      instance.angle   = Math.PI * 1.5 # heading up
+      instance.angle   = pi * 1.5 # heading up
       instance.draw()
       instance.x       = oldX
       instance.y       = oldY
@@ -220,6 +221,7 @@
       @topic.topicColor  = @outline
       @topic.inheritMark = false
       @draw()
+      @topic.cell.draw()
   # highlight an organism by coloring its belly
   highlight: ( x, y, color, inherit ) ->
     for t in @thingsAt( x, y ) when t instanceof Organism
@@ -658,9 +660,15 @@
   h = Math.sqrt( x*x + y*y )
   theta = Math.acos( a / h )
   if x < 0
-    if y < 0 then Math.PI + theta else Math.PI - theta
+    if y < 0 then pi + theta else pi - theta
   else
-    if y < 0 then 2 * Math.PI - theta else theta
+    if y < 0 then 2 * pi - theta else theta
+
+# calculates the euclidean distance between two things
+euclid = ( t1, t2 ) ->
+  x = t1.x - t2.x
+  y = t1.y - t2.y
+  Math.sqrt( x*x + y*y )

 grep = ( ar, f ) ->
   x for x in ar when f(x)
@@ -848,7 +856,7 @@
   margins: ->
     m = @marges ?= []
     return m if m.length
-    fi = Math.PI * @visualAngle() / 2
+    fi = pi * @visualAngle() / 2
     t1 = @angle - fi
     t1 = @universe.tp( Math.sin(t1), Math.cos(t1) )
     t2 = @angle + fi
@@ -1183,7 +1191,7 @@
   drawEar: (left) ->
     point = @angle + if left then -qt else qt
     rad = @earSize
-    [ start, end ] = if left then [ @angle, @angle + Math.PI ] else [ @angle - Math.PI, @angle ]
+    [ start, end ] = if left then [ @angle, @angle + pi ] else [ @angle - pi, @angle ]
     [ x, y ] = @edgePoint point, rad + @radius
     @drawArc x, y, rad, @bodyColor, start, end
   calcTailSize: ->
@@ -1202,7 +1210,7 @@
     [ x, y ] = @edgePoint a
     @drawCircle x, y, @eyeSize, 'black'
   drawTail: ->
-    a = @angle + Math.PI
+    a = @angle + pi
     [ x1, y1 ] = @edgePoint a
     [ x2, y2 ] = @edgePoint a, @radius + @tailSize
     c = @ctx()


Rewriting mongo queue

Rewriting mongo queue

--- server/queue.coffee
+++ server/queue.coffee
@@ -35,13 +35,7 @@
   constructor: ->
     @configure()
     @sqs = @generateSQSInstance()
-
-  configure: ->
-    aws.config.update
-      accessKeyId: config.queue.accessKeyId
-      secretAccessKey: config.queue.secretAccessKey
-      region: config.queue.region
-
+  ###Public API###
   registerQueue: (queueName, options, callback) ->
     #returns new queue in data argument of callback
     @sqs.createQueue {QueueName: queueName}, (err,data) =>
@@ -50,6 +44,12 @@
         throw new Error("Fatal SQS error, see Winston output")
       newQueue = new SQSQueue(queueName, data.QueueUrl, @sqs)
       callback err, newQueue
+  ###Public API###
+  configure: ->
+    aws.config.update
+      accessKeyId: config.queue.accessKeyId
+      secretAccessKey: config.queue.secretAccessKey
+      region: config.queue.region

   generateSQSInstance: ->
     new aws.SQS()
@@ -96,15 +96,18 @@
 class MongoQueueClient extends AbstractQueueClient
   constructor: ->
     @configure()
+    @createMongoConnection()
+    @messageModel = @generateMessageModel()

+  ###Public API###
   registerQueue: (queueName, options, callback) ->
-    channel = new MongoQueue queueName,options,this
-    callback(null, channel)
+    newQueue = new MongoQueue queueName,options,this
+    callback(null, newQueue)
+  ###Public API###

   configure: ->
     @databaseAddress = db.generateDatabaseAddress()
     @mongoDatabaseName = config.mongoQueue.queueDatabaseName;
-    @createMongoConnection()

   createMongoConnection: ->
     @mongooseConnection = mongoose.createConnection "mongodb://#{@databaseAddress}/#{@mongoDatabaseName}"
@@ -113,12 +116,20 @@
     @mongooseConnection.once 'open', ->
       winston.info "Successfully connected to MongoDB queue!"

+  generateMessageModel: ->
+    #do find something like: messages not processing, queue as current queue, visibility time before now, sort by insertion time, findOne
+    schema = new mongoose.Schema
+      messageBody : Object,
+      processing: false,
+      insertionTime: {type: Date, default: Date.now }
+      queue: String
+      scheduledVisibilityTime: Date
+    @mongooseConnection.model 'Message',schema

 class MongoQueue extends events.EventEmitter
-  constructor: (@queueName, options, mubSubClient) ->
-    @channel = mubSubClient.channel queueName, options
-    @subscribe 'message', receieveMessage
+  constructor: (@queueName, options, @Message) ->
+
   subscribe: (eventName, callback) ->
     @channel.subscribe eventName, callback

@@ -126,11 +137,34 @@
     subscriptionObject.unsubscribe()

   publish: (messageBody, delayInSeconds, callback) ->
-    #TODO: Mongo-based persistence of delayed messages
-    setTimeout @channel.publish.bind(this), delayInSeconds * 1000, @queueName, messageBody, callback
+    @sendMessage messageBody, delayInSeconds, callback

   receieveMessage: (callback) ->
-    throw new Error "MongoQueue does not support fetching one message, it continually listens"
+    conditions = {queue: @queueName, processing: false, scheduledVisibilityTime: {$lt:Date.now()}}
+    options = {sort: 'insertionTime'}
+    update = {$set:{processing: true}}
+    @Message.findOneAndUpdate conditions, update, options, callback
+
+  deleteMessage: (receiptHandle, callback) ->
+    #receiptHandle in this case is an ID
+    conditions = {queue: @queueName, processing: false, _id : receiptHandle}
+    @Message.findOneAndRemove conditions, {}, callback
+
+
+  sendMessage: (messageBody, delaySeconds, callback) ->
+    messageContents =
+      messageBody: messageBody
+      processing: false
+      queue: @queueName
+      scheduledVisibilityTime: Date.now() + (delaySeconds * 1000)
+
+    messageToSend = new @Message messageContents
+
+    messageToSend.save (err) =>
+      callback(err,messageContents)
+
+
+
   deleteMessage: (callback) ->
     throw new Error "MongoQueue "

@@ -141,3 +175,12 @@

+
+
+
+
+
+
+
+
+


Client: Activity: Updated onChange method to search user without `@` sign

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -198,8 +198,7 @@

     matchResult = value.match /^@(.*)/

-    query = value
-    query = matchResult[1]  if matchResult
+    if matchResult then query = matchResult[1] else query = value

     { channel, user  } = ActivityFlux.actions


Workspace: Fixed a Firebase bug on our implementation.

https://github.com/firebase/firepad/issues/30

--- client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
@@ -12,11 +12,10 @@
       @firepad    = Firepad.fromCodeMirror @ref, @codeMirrorEditor

       @firepad.on "ready", =>
+        @firepad.setText " " # fix for a firepad bug
+        @codeMirrorEditor.scrollTo 0, 0
         {file, content} = @getOptions()
         return @openFile file, content  if file
-        if @firepad.isHistoryEmpty()
-          @firepad.setText "" # fix for a firepad bug
-          @codeMirrorEditor.scrollTo 0, 0

       @ref.on "value", (snapshot) =>
         value = snapshot.val()


CUI.Layer

CUI.Layer

- Fixed a bug in "viewport-resize" event: don't try
  to position the layer if the governing element is no
  in DOM any more. this fixed "positioner not found"
  messages when a submenu has not disappeared before
  an non-submenu menu item was clicked and opened
  another Layer.

--- src/base/Layer/Layer.coffee
+++ src/base/Layer/Layer.coffee
@@ -1202,6 +1202,10 @@
 			instance: @
 			node: @__layer
 			call: (ev) =>
+				if @__element and not @elementIsInDOM()
+					# ignore the event
+					return
+
 				# CUI.debug "Layer caught event: \"#{ev.getType()}\", executing \"position\".", "destroyed:", @isDestroyed(), "in DOM:", DOM.isInDOM(@__layer_root.DOM[0])

 				@__placement = null # reset placement so it cant be used as previous placement.
@@ -1211,6 +1215,7 @@
 				@__placement = null # reset placement so it cant be used as previous placement.
 				@__resetFixedLayerDim()
 				@position()
+				return

 		@_onBeforeShow?(@, ev)
 		@__shown = true


Fix deprecated selectors in keymap

Fix deprecated selectors in keymap

--- keymaps/bookmarks.cson
+++ keymaps/bookmarks.cson
@@ -1,16 +1,16 @@
-'.editor':
+'atom-text-editor':
   'ctrl-f2': 'bookmarks:view-all'
   'f2': 'bookmarks:jump-to-next-bookmark'
   'shift-f2': 'bookmarks:jump-to-previous-bookmark'

-'.platform-darwin .editor':
+'.platform-darwin atom-text-editor':
   'cmd-f2': 'bookmarks:toggle-bookmark'
   'cmd-shift-f2': 'bookmarks:clear-bookmarks'

-'.platform-win32 .editor':
+'.platform-win32 atom-text-editor':
   'alt-ctrl-f2': 'bookmarks:toggle-bookmark'
   'ctrl-shift-f2': 'bookmarks:clear-bookmarks'

-'.platform-linux .editor':
+'.platform-linux atom-text-editor':
   'ctrl-shift-f2': 'bookmarks:toggle-bookmark'
   'alt-shift-f2': 'bookmarks:clear-bookmarks'


Fix log message

Fix log message

--- rtsp.coffee
+++ rtsp.coffee
@@ -419,7 +419,7 @@
         c.on 'close', =>
           logger.info "[#{TAG}] client #{id_str} is closed"

-          logger.debug "[client:#{@id}] teardown: session=#{@sessionID}"
+          logger.debug "[client:#{id_str}] teardown: session=#{sessionID}"
           try
             c.end()
           catch e


Call save rather than return it

Call save rather than return it

--- server_setup.coffee
+++ server_setup.coffee
@@ -97,7 +97,7 @@
     if req.user
       sendMain(req, res)
       req.user.set('lastIP', req.connection.remoteAddress)
-      req.user.save
+      req.user.save()
     else
       user = auth.makeNewUser(req)
       makeNext = (req, res) -> -> sendMain(req, res)


when mincha or arbit is null, overwrite existing value instead of making no changes to table cell

--- source/javascripts/schedule.coffee
+++ source/javascripts/schedule.coffee
@@ -47,8 +47,8 @@
         new Vatikin(day_iterator, hebrew_date).updateDOM()
     catching_errors 'Afternoon', ->
       afternoon = mincha_and_arbit(day_iterator)
-      $(".#{day} .mincha").html(afternoon.mincha)
-      $(".#{day} .arbit").html(afternoon.arbit)
+      $(".#{day} .mincha").html(afternoon.mincha || "")
+      $(".#{day} .arbit").html(afternoon.arbit || "")
     $(".#{day} .omer").removeClass('hidden').html("#{day_iterator.format('ddd')}. night: <b>#{hebrew_date.omer().tonight}</b>") if hebrew_date.omer()? and hebrew_date.omer().tonight
     day_iterator.subtract(1, 'days')
   $(".header .event")[show_if($('.one_day .event').not('.hidden').length > 0)]('hidden')


egg image relative path loading.

egg image relative path loading.

--- web/app/source/nvwrapper.coffee
+++ web/app/source/nvwrapper.coffee
@@ -143,12 +143,12 @@
         # eggcellent
         #
         switch
-          when d.value > 75 then '/img/egg1.png'
-          when d.value > 72 then '/img/egg2.png'
-          when d.value > 68 then '/img/egg3.png'
-          when d.value > 64 then '/img/egg4.png'
-          when d.value > 60 then '/img/egg5.png'
-          else '/img/egg6.png'
+          when d.value > 75 then './img/egg1.png'
+          when d.value > 72 then './img/egg2.png'
+          when d.value > 68 then './img/egg3.png'
+          when d.value > 64 then './img/egg4.png'
+          when d.value > 60 then './img/egg5.png'
+          else './img/egg6.png'

     text.enter().append('text')


Update area_results_view.coffee

Update area_results_view.coffee

Changed area from km2 to ha

--- app/assets/javascripts/analysis/templates/area_results_view.coffee
+++ app/assets/javascripts/analysis/templates/area_results_view.coffee
@@ -48,7 +48,7 @@

               Habitat
-              Area km2
+              Area ha
               Area % of Tot.
               C-Stock T


Hide sign out link on wide screens

Hide sign out link on wide screens

--- app/assets/javascripts/lib/mobile/core/authenticator_mobile.coffee
+++ app/assets/javascripts/lib/mobile/core/authenticator_mobile.coffee
@@ -40,7 +40,7 @@
       signOutLink = document.createElement('a')
       signOutLink.href = @options.signOutUrl
       signOutLink.textContent = "Sign out"
-      signOutLink.className = "icon--signout-inline--grey--before nav__item nav__item--primary"
+      signOutLink.className = "icon--signout-inline--grey--before nav__item nav__item--primary wv--hidden"
       primaryNav.appendChild(signOutLink)

     showLoginAndRegister: ()->


render app immediately when main script loads

--- web_modules/website/main.coffee
+++ web_modules/website/main.coffee
@@ -4,15 +4,12 @@
 require('file?name=CNAME!./CNAME')
 require('./style.css')

-init = ->
-    React = require('react')
-    App = require('./app')
+React = require('react')
+App = require('./app')

-    contentEl = document.getElementById('content')
-    if !contentEl
-        contentEl = document.createElement('div')
-        document.body.appendChild(contentEl)
+contentEl = document.getElementById('content')
+if !contentEl
+    contentEl = document.createElement('div')
+    document.body.appendChild(contentEl)

-    React.render(React.createElement(App), contentEl)
-
-init()
+React.render(React.createElement(App), contentEl)


Smooth scroll; add comment and remove unused variable.

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -124,7 +124,6 @@
     delta = roundOut(amount / duration)

     animatorId = null
-    lastTime = null
     start = null
     scrolledAmount = 0


Stub rate_limit check

Stub rate_limit check

--- js/hocr-reader.coffee
+++ js/hocr-reader.coffee
@@ -5,6 +5,18 @@
     token: ''
     auth: 'oauth'
   })
+
+check_rate_limit = () ->
+  $.ajax 'https://api.github.com/rate_limit',
+    type: 'GET'
+    dataType: 'json'
+    crossDomain: 'true'
+    error: (jqXHR, textStatus, errorThrown) ->
+      console.log "Image fetch error: #{textStatus}"
+    success: (data) ->
+      if data.rate.remaining == 0
+        $(document.body).empty()
+        $(document.body).append($('<p>').text("You've exceeded GitHub's rate limit for unauthenticated applications. Authenticate with GitHub, or wait #{data.rate.reset - Math.floor(Date.now() / 1000)} seconds"))

 hocr_handler = (req) ->
   console.log(req.params)


Let remade rdfa extractor use expanded JSON-LD

--- js/rdfa/parser.coffee
+++ js/rdfa/parser.coffee
@@ -1,7 +1,8 @@
 ((exports) ->

   RDF_IRI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-  RDF_XMLLiteral = RDF_IRI + 'XMLLiteral'
+  RDF_XML_LITERAL = RDF_IRI + 'XMLLiteral'
+  RDFA_USES_VOCAB = "http://www.w3.org/ns/rdfa#usesVocabulary"

   extract = (doc, base, profile='html') ->
@@ -39,33 +40,36 @@
       current = state.current
       nextObj = current

+      if desc.usesVocab
+        baseObj = getOrCreate(res, state.base)
+        addPropToObj(baseObj, RDFA_USES_VOCAB, desc.usesVocab)
+
       s = desc.getSubject()
       if s
-        current = res.all[s]
-        unless current
-          current = res.all[s] = {"@id": s}
+        current = getOrCreate(res, s)

       o = desc.getReference()
       if o
-        nextObj = res.all[o]
-        unless nextObj
-          nextObj = res.all[o] = {"@id": o}
-
+        nextObj = getOrCreate(res, o)
+
+      types = desc.types
+      if types
+        typed = if desc.about then current else nextObj
+        for type in types
+          addPropToObj(typed, "@type", type)
+
+      if o
         for link in desc.getLinks()
-          refs = current[link]
-          unless refs
-            refs = current[link] = []
-          refs.push({"@id": o})
-
-      value = desc.getValue()
+          addPropToObj(current, link, {"@id": o})
+
+      value = desc.getLiteral()
       if value
         for prop in desc.getValueProperties()
-          values = current[prop]
-          unless values
-            values = current[prop] = []
-          values.push(value)
-
-      subState = desc.getChildState()
+          addPropToObj(current, prop, value)
+
+      subState = desc.state
+      #nextListMap
+      #nextHangSubject, nextHangRel, nextHangRev
       subState.current = nextObj
       return subState

@@ -85,35 +89,93 @@
       #{'@context': state.result.context, '@graph': items}

+  getOrCreate = (res, id) ->
+      obj = res.all[id]
+      unless obj
+        obj = res.all[id] = {"@id": id}
+      obj
+
+  addPropToObj = (obj, prop, value) ->
+    values = obj[prop]
+    unless values
+      values = obj[prop] = []
+    values.push(value)
+
+
   class State
-    constructor: (base=null, @profile, @resolveURI) ->
-      @mappings = {base: base, lang: null, vocab: null, prefixes: {}}
+    constructor: (@base, @profile, resolveURI) ->
+      @mapper = new Mapper(null, contexts[@profile])
+      @lang = null
       @lists = {}
       @hanging = {rels: [], revs: [], lists: []}
       @result = null
       @current = null
-
-    newLocal: (base, lang, vocab, prefixes) ->
+      @resolveURI = resolveURI
+
+    createInherited: (base, lang, vocab, prefixes) ->
       # TODO: new State with updated members
-      ctor = () ->
-      ctor.prototype = this
-      return new ctor
-
-
-  class Mappings
+      subState = inherit(this)
+      subState.lang = lang or @lang
+      subState.mapper = @mapper.createInherited(vocab, prefixes)
+      return subState
+
+    expandTermOrCurieOrIRI: (expr) ->
+      @mapper.expandTermOrCurieOrIRI(expr)
+
+    expandCurieOrIRI: (expr) ->
+      @mapper.expandCurieOrIRI(expr)
+
+    expandAndResolve: (curieOrIri) ->
+      @resolveURI(@expandCurieOrIRI(curieOrIri))
+
+
+  class Mapper
+    constructor: (@vocab=null, @map={}) ->
+
+    createInherited: (vocab, prefixes) ->
+      vocab ?= @vocab
+      subMap = inherit(@map)
+      for pfx, iri of prefixes
+        subMap[pfx] = iri
+      return new Mapper(vocab, subMap)
+
+    expandTermOrCurieOrIRI: (expr) ->
+      iri = @map[expr]
+      if iri
+        return iri
+      else if expr.indexOf(":") is -1
+        if @vocab
+          return @vocab + expr
+        else
+          return null
+      else
+        return @expandCurieOrIRI(expr)
+
+    expandCurieOrIRI: (expr) ->
+      i = expr.indexOf(':')
+      if i is -1
+        return expr
+      pfx = expr.substring(0, i)
+      term = expr.substring(i + 1)
+      if term.slice(0, 2) is "//"
+        return expr
+      ns = @map[pfx]
+      if ns
+        return ns + term
+      return expr

   ##
   # A representation of the interpreted description formed by the logical
   # attributes of an element. Use this to produce triples.
   class Description
-    constructor: (el, parentState=null) ->
-      parentState = @computeParentState(el, parentState)
+    constructor: (el, parentState) ->
       #@data =
       data = new ElementData(el, parentState)
+      @usesVocab = data.getVocab()
       @state = data.state
       @tagName = data.tagName
-      #@mappings = data.mappings
+      #@mapper = data.mapper
       @about = data.getAbout()
       @resource = data.getResource()
       @types = data.getTypes()
@@ -121,29 +183,9 @@
       @rels = data.getRels()
       @revs = data.getRevs()
       if @properties
-        @datatype = data.getDatatype()
-        if @datatype is RDF_XMLLiteral
-          @xml = data.getXML()
-        else
-          @content = data.getContent()
+        @literal = data.getLiteral()
       if data.isInlist()
         @inlist = true
-
-    computeParentState: (el, parentState) ->
-      if typeof parentState is 'string'
-        base = parentState
-        parentState = null
-      if parentState
-        return parentState
-      if not el.parentNode or el.parentNode is el
-        throw "NotImplemented: computeParentState for non-root not supported"
-      return new State(base)
-
-    getChildState: ->
-      @state
-      #nextSubject
-      #nextListMap
-      #nextHangSubject, nextHangRel, nextHangRev

     getErrors: ->
       @data.errors
@@ -162,17 +204,10 @@
       else if @typeof
        return @newBNode()

-    getValue: ->
-      if @content
-        if @datatype
-          return {"@value": @content, "@datatype": @datatype}
-        else if @lang
-          # TODO: inherited (in compact: if diff. from top-level lang)
-          return {"@value": @content, "@language": @lang}
-        else
-          return @content
-      else# if @xml
-        return {"@value": @xml, "@datatype": @datatype}
+    getLiteral: ->
+      lit = @literal
+      return null unless lit
+      {"@value": lit.value, "@language": lit.lang, "@datatype": lit.datatype}

     getLinks: ->
       if @rels
@@ -207,7 +242,7 @@
       @attrs = @el.attributes
       @tagName = @el.nodeName.toLowerCase()
       @errors = []
-      @state = parentState.newLocal(
+      @state = parentState.createInherited(
         @getBase(), @getLang(), @getVocab(), @getPrefixes())

     getBase: ->
@@ -232,27 +267,54 @@

     getAbout: ->
       if @attrs.about? #and not parent
-        next = @state.resolveURI(@attrs.about.value)
+        next = @state.expandAndResolve(@attrs.about.value)

     getResource: ->
       if @attrs.resource?
-        next = @state.resolveURI(@attrs.resource.value)
+        next = @state.expandAndResolve(@attrs.resource.value)
       else if @attrs.href?
         next = @el.href
       else if @attrs.src?
         next = @state.resolveURI(@attrs.src.value)

     getTypes: ->
-      @attrs.typeof?.value.split(/\s+/)
+      @expandAll @attrs.typeof?.value.split(/\s+/)

     getProperties: ->
-      predicate = @attrs.property?.value.split(/\s+/)
+      @expandAll @attrs.property?.value.split(/\s+/)

     getRels: ->
-      @attrs.rel?.value.split(/\s+/)
+      @expandAll @attrs.rel?.value.split(/\s+/)

     getRevs: ->
-      @attrs.rev?.value.split(/\s+/)
+      @expandAll @attrs.rev?.value.split(/\s+/)
+
+    expandAll: (expressions) ->
+      return null unless expressions
+      result = []
+      for expr in expressions
+        iri = @state.expandTermOrCurieOrIRI(expr)
+        if iri
+          result.push(iri)
+      result
+
+    getLiteral: ->
+      datatype = @getDatatype()
+      lang = @state.lang
+      if datatype is RDF_XML_LITERAL
+        xml = @getXML()
+      else
+        content = @getContent()
+      if content
+        if datatype
+          return {value: content, datatype: datatype}
+        else if lang
+          # TODO: inherited (in compact: if diff. from top-level lang)
+          return {value: content, lang: lang}
+        else
+          return content
+      else# if xml
+        return {value: xml, datatype: datatype}

     getContent: ->
       if @attrs.content?
@@ -316,6 +378,12 @@
     }

+  inherit = (obj) ->
+    ctor = () ->
+    ctor.prototype = obj
+    return new ctor
+
+
   exports.extract = extract
   exports.Description = Description
   exports.State = State


social: sloppy whitespace

social: sloppy whitespace

--- workers/social/lib/social/models/account/index.coffee
+++ workers/social/lib/social/models/account/index.coffee
@@ -545,7 +545,7 @@
       targetId   : @getId()
       sourceName : 'JAccount'
     , (err, count)=>
-       @update ($set: 'counts.following': count), ->
+      @update ($set: 'counts.following': count), ->

     # Tag Following count
     Relationship.count


admin: added nelson and richard to admins

--- workers/social/lib/social/models/account/index.coffee
+++ workers/social/lib/social/models/account/index.coffee
@@ -415,7 +415,8 @@
     , (err, count)=>
       @update ($set: 'counts.topics': count), ->

-  dummyAdmins = ["sinan", "devrim", "aleksey-m", "gokmen", "chris", "arvidkahl", "testdude"]
+  dummyAdmins = [ "sinan", "devrim", "aleksey-m", "gokmen", "chris",
+                  "arvidkahl", "testdude", "blum", "neelance"]

   flagAccount: secure (client, flag, callback)->
     {delegate} = client.connection


Use toContain now that URI has query param

--- spec/image-editor-view-spec.coffee
+++ spec/image-editor-view-spec.coffee
@@ -27,7 +27,7 @@
     waitsFor -> view.loaded

   it "displays the image for a path", ->
-    expect(view.image.attr('src')).toBe filePath
+    expect(view.image.attr('src')).toContain filePath

   describe "image-view:zoom-in", ->
     it "increases the image size by 10%", ->


Detach image editor view before attaching workspace

--- spec/image-editor-view-spec.coffee
+++ spec/image-editor-view-spec.coffee
@@ -67,6 +67,7 @@
     [imageSizeStatus] = []

     beforeEach ->
+      view.detach()
       jasmine.attachToDOM(workspaceElement)

       waitsForPromise ->


Refactor git-commit-spec to not use ::start

--- spec/models/git-commit-spec.coffee
+++ spec/models/git-commit-spec.coffee
@@ -71,71 +71,68 @@

 describe "GitCommit", ->
   describe "a regular commit", ->
+    beforeEach ->
+      atom.config.set "git-plus.openInPane", false
+      setupMocks()
+      waitsForPromise ->
+        GitCommit(repo)
+
     it "gets the current pane", ->
-      setupMocks()
-      GitCommit repo
       expect(atom.workspace.getActivePane).toHaveBeenCalled()

-    describe "::start", ->
-      beforeEach ->
-        atom.config.set "git-plus.openInPane", false
-        setupMocks()
-        waitsForPromise ->
-          GitCommit(repo).start()
+    it "gets the commentchar from configs", ->
+      expect(git.cmd).toHaveBeenCalledWith ['config', '--get', 'core.commentchar']

-      it "gets the commentchar from configs", ->
-        expect(git.cmd).toHaveBeenCalledWith ['config', '--get', 'core.commentchar']
+    it "gets staged files", ->
+      expect(git.cmd).toHaveBeenCalledWith ['status'], cwd: repo.getWorkingDirectory()

-      it "gets staged files", ->
-        expect(git.cmd).toHaveBeenCalledWith ['status'], cwd: repo.getWorkingDirectory()
+    it "removes lines with '(...)' from status", ->
+      expect(status.replace).toHaveBeenCalled()

-      it "removes lines with '(...)' from status", ->
-        expect(status.replace).toHaveBeenCalled()
+    it "gets the commit template from git configs", ->
+      expect(git.cmd).toHaveBeenCalledWith ['config', '--get', 'commit.template']

-      it "gets the commit template from git configs", ->
-        expect(git.cmd).toHaveBeenCalledWith ['config', '--get', 'commit.template']
+    it "writes to a file", ->
+      argsTo_fsWriteFile = fs.writeFileSync.mostRecentCall.args
+      expect(argsTo_fsWriteFile[0]).toEqual commitFilePath

-      it "writes to a file", ->
+    it "shows the file", ->
+      expect(atom.workspace.open).toHaveBeenCalled()
+
+    it "calls git.cmd with ['commit'...] on textEditor save", ->
+      textEditor.save()
+      expect(git.cmd).toHaveBeenCalledWith ['commit', '--cleanup=strip', "--file=#{commitFilePath}"], cwd: repo.getWorkingDirectory()
+
+    it "closes the commit pane when commit is successful", ->
+      textEditor.save()
+      waitsFor -> commitPane.destroy.callCount > 0
+      runs -> expect(commitPane.destroy).toHaveBeenCalled()
+
+    it "cancels the commit on textEditor destroy", ->
+      textEditor.destroy()
+      expect(currentPane.activate).toHaveBeenCalled()
+      expect(fs.unlinkSync).toHaveBeenCalledWith commitFilePath
+
+  describe "when core.commentchar config is not set", ->
+    it "uses '#' in commit file", ->
+      setupMocks()
+      GitCommit(repo).then ->
         argsTo_fsWriteFile = fs.writeFileSync.mostRecentCall.args
-        expect(argsTo_fsWriteFile[0]).toEqual commitFilePath
+        expect(argsTo_fsWriteFile[1].trim().charAt(0)).toBe '#'

-      it "shows the file", ->
-        expect(atom.workspace.open).toHaveBeenCalled()
-
-      it "calls git.cmd with ['commit'...] on textEditor save", ->
-        textEditor.save()
-        expect(git.cmd).toHaveBeenCalledWith ['commit', '--cleanup=strip', "--file=#{commitFilePath}"], cwd: repo.getWorkingDirectory()
-
-      it "closes the commit pane when commit is successful", ->
-        textEditor.save()
-        waitsFor -> commitPane.destroy.callCount > 0
-        runs -> expect(commitPane.destroy).toHaveBeenCalled()
-
-      it "cancels the commit on textEditor destroy", ->
-        textEditor.destroy()
-        expect(currentPane.activate).toHaveBeenCalled()
-        expect(fs.unlinkSync).toHaveBeenCalledWith commitFilePath
-
-    describe "when core.commentchar config is not set", ->
-      it "uses '#' in commit file", ->
-        setupMocks()
-        GitCommit(repo).start().then ->
-          argsTo_fsWriteFile = fs.writeFileSync.mostRecentCall.args
-          expect(argsTo_fsWriteFile[1].trim().charAt(0)).toBe '#'
-
-    describe "when core.commentchar config is set to '$'", ->
-      it "uses '$' as the commentchar", ->
-        commentchar_config = '$'
-        setupMocks()
-        GitCommit(repo).start().then ->
-          argsTo_fsWriteFile = fs.writeFileSync.mostRecentCall.args
-          expect(argsTo_fsWriteFile[1].trim().charAt(0)).toBe commentchar_config
+  describe "when core.commentchar config is set to '$'", ->
+    it "uses '$' as the commentchar", ->
+      commentchar_config = '$'
+      setupMocks()
+      GitCommit(repo).then ->
+        argsTo_fsWriteFile = fs.writeFileSync.mostRecentCall.args
+        expect(argsTo_fsWriteFile[1].trim().charAt(0)).toBe commentchar_config

     describe "when commit.template config is not set", ->
       it "commit file starts with a blank line", ->
         setupMocks()
         waitsForPromise ->
-          GitCommit(repo).start().then ->
+          GitCommit(repo).then ->
             argsTo_fsWriteFile = fs.writeFileSync.mostRecentCall.args
             expect(argsTo_fsWriteFile[1].charAt(0)).toEqual "\n"

@@ -143,7 +140,7 @@
       it "commit file starts with content of that file", ->
         templateFile = 'template'
         setupMocks()
-        GitCommit(repo).start()
+        GitCommit(repo)
         waitsFor ->
           fs.writeFileSync.callCount > 0
         runs ->
@@ -153,12 +150,18 @@
   describe "when 'stageChanges' option is true", ->
     it "calls git.add with update option set to true", ->
       setupMocks()
-      GitCommit(repo, stageChanges: true).start().then ->
+      GitCommit(repo, stageChanges: true).then ->
         expect(git.add).toHaveBeenCalledWith repo, update: true

-  describe "when 'andPush' option is true", ->
-    it "calls git.cmd with ['remote'...] as args", ->
-      setupMocks()
-      GitCommit(repo, andPush: true).start().then -> textEditor.save()
-      waitsFor -> git.cmd.mostRecentCall.args?[0].args[0] is 'remote'
-      runs -> expect(true).toBe true
+  ## Tough as nails to test because GitPush is called outside of test
+  # describe "when 'andPush' option is true", ->
+  #   it "calls git.cmd with ['remote'...] as args", ->
+  #     setupMocks()
+  #     GitCommit(repo, andPush: true).then ->
+  #       runs ->
+  #         textEditor.save()
+  #       waitsFor((->
+  #         git.cmd.mostRecentCall.args[0][0] is 'remote'),
+  #         "some stuff", 10000
+  #       )
+  #       expect(git.cmd).toHaveBeenCalledWith ['remote']


:green_heart: Fix broken autocomplete provider test since 2.8.0

--- spec/pigments-provider-spec.coffee
+++ spec/pigments-provider-spec.coffee
@@ -64,11 +64,14 @@
       waitsFor ->
         autocompleteManager.displaySuggestions.calls.length is 1

+      waitsFor -> editorView.querySelector('.autocomplete-plus li')?
+
       runs ->
-        expect(editorView.querySelector('.autocomplete-plus')).toExist()
-        expect(editorView.querySelector('.autocomplete-plus span.word').textContent).toEqual('base-color')
+        popup = editorView.querySelector('.autocomplete-plus')
+        expect(popup).toExist()
+        expect(popup.querySelector('span.word').textContent).toEqual('base-color')

-        preview = editorView.querySelector('.autocomplete-plus span.completion-label .color-suggestion-preview')
+        preview = popup.querySelector('.color-suggestion-preview')
         expect(preview).toExist()
         expect(preview.style.background).toEqual('rgb(255, 255, 255)')

@@ -112,8 +115,11 @@
         waitsFor ->
           autocompleteManager.displaySuggestions.calls.length is 1

+        waitsFor -> editorView.querySelector('.autocomplete-plus li')?
+
         runs ->
-          expect(editorView.querySelector('.autocomplete-plus')).toExist()
-          expect(editorView.querySelector('.autocomplete-plus span.word').textContent).toEqual('button-padding')
+          popup = editorView.querySelector('.autocomplete-plus')
+          expect(popup).toExist()
+          expect(popup.querySelector('span.word').textContent).toEqual('button-padding')

-          expect(editorView.querySelector('.autocomplete-plus span.completion-label').textContent).toEqual('6px 8px')
+          expect(popup.querySelector('span.right-label').textContent).toEqual('6px 8px')


Fix jq-validator bug

Fix jq-validator bug

--- p_coffeescript/jq-validator.coffee
+++ p_coffeescript/jq-validator.coffee
@@ -23,9 +23,7 @@
     return @each ()->

       $this = $(this)
-      $formElements = $("input:not([type=\"radio\"]):not([type=\"button\"]), textarea, select", $this)
-      # $formCheckbox = $("input[type=\"checkbox\"]", $this)
-      # $formRadio = $("input[type=\"radio\"]", $this)
+      $formElements = $("input:not([type=\"radio\"]):not([type=\"hidden\"]):not([type=\"button\"]):not([disabled=\"disabled\"]), textarea, select", $this)
       $submit = $(settings.buttonClass, $this)

       # Regular expressions
@@ -259,14 +257,14 @@
           isDataLength = $theErrorField.hasClass "length-field-error"
           isDataCheckbox = $theErrorField.attr "checkbox-field-error"

-          # Get the "data-lenght" to use into the errorå
+          # Get the "data-lenght" to use into the error
           theDataLenght = $theErrorField.attr "data-length"

           # Print the right error depending on
           # what the error is displayed by its class

           # Data -length error
-          if isDataLengt
+          if isDataLength
             if theDataLenght > 1
               $theErrorField.val("").attr "placeholder", errorsArray[3].val.first + " #{theDataLenght} " + errorsArray[3].val.second
             else


Turns the many args in an options object

--- src/client/templates.coffee
+++ src/client/templates.coffee
@@ -1,6 +1,6 @@
 define ['underscore', 'lib/hullbase', 'handlebars'], (_, Hull, Handlebars) ->

-  strategies =
+  strategies =
     app: ['hullGlobal', 'meteor', 'sprockets', 'hullDefault']
     dom: ['inner', 'global']
     server: ['require']
@@ -45,10 +45,10 @@
         path = "text!#{path}.#{format}"
         dfd = module.deferred()
         module.require [path], (tpl)->
-          dfd.resolve setupTemplate(tpl, tplName)
+          dfd.resolve setupTemplate(tpl, tplName)
         , (err)->
-          console.error "Error loading template", tplName, err.message
-          dfd.reject err
+          console.error "Error loading template", tplName, err.message
+          dfd.reject err
         dfd

   _execute = (type, args...)->
@@ -68,16 +68,16 @@
     _execute('server', tplName, path, format)

-  lookupTemplate = (componentName, el, ref, format, name)->
-    path = "#{ref}/#{name}"
-    tplName = [componentName, name.replace(/^_/, '')].join("/")
+  lookupTemplate = (options, name)->
+    path = "#{options.ref}/#{name}"
+    tplName = [options.componentName, name.replace(/^_/, '')].join("/")

-    tpl = applyDomStrategies tplName, el if module.domFind
+    tpl = applyDomStrategies tplName, options.rootEl if module.domFind
     tpl = applyAppStrategies tplName unless tpl

     module.define path, tpl if tpl

-    tpl = applyServerStrategies tplName, path, format unless tpl
+    tpl = applyServerStrategies tplName, path, options.templateFormat unless tpl
     tpl

   module =
@@ -91,10 +91,15 @@
       module.domFind = app.core.dom.find
       module.deferred = app.core.data.deferred
       app.core.template.load = (names=[], ref, el, format="hbs") ->
+        dfd = app.core.data.deferred()
         names = [names] if _.isString(names)
-        componentName = ref.replace('__component__$', '').split('@')[0]
-        dfd = app.core.data.deferred()
-        tpls = _.map names, _.bind(lookupTemplate, undefined, componentName, el, ref, format)
+        componentProps =
+          componentName: ref.replace('__component__$', '').split('@')[0]
+          templateFormat: format
+          rootEl: el
+          ref: ref
+
+        tpls = _.map names, _.bind(lookupTemplate, undefined, componentProps)
         app.core.data.when(tpls...).then ->
           dfd.resolve _.object(names, [].slice.apply(arguments))
         , (err)->


Use editor settings for creating comparison string showing invisibles

--- spec/app/root-view-spec.coffee
+++ spec/app/root-view-spec.coffee
@@ -698,12 +698,14 @@
       expect(rightEditor.find(".line:first").text()).toBe "    "
       expect(leftEditor.find(".line:first").text()).toBe "    "

+      withInvisiblesShowing = "#{rightEditor.invisibles.space}#{rightEditor.invisibles.tab} #{rightEditor.invisibles.space}#{rightEditor.invisibles.eol}"
+
       rootView.trigger "window:toggle-invisibles"
-      expect(rightEditor.find(".line:first").text()).toBe "•▸ •¬"
-      expect(leftEditor.find(".line:first").text()).toBe "•▸ •¬"
+      expect(rightEditor.find(".line:first").text()).toBe withInvisiblesShowing
+      expect(leftEditor.find(".line:first").text()).toBe withInvisiblesShowing

       lowerLeftEditor = leftEditor.splitDown()
-      expect(lowerLeftEditor.find(".line:first").text()).toBe "•▸ •¬"
+      expect(lowerLeftEditor.find(".line:first").text()).toBe withInvisiblesShowing

       rootView.trigger "window:toggle-invisibles"
       expect(rightEditor.find(".line:first").text()).toBe "    "


Search controller was made as optional.

Search controller was made as optional.

--- client/activity/lib/views/privatemessage/privatemessagepane.coffee
+++ client/activity/lib/views/privatemessage/privatemessagepane.coffee
@@ -31,7 +31,8 @@
     options.itemClass         or= PrivateMessageListItemView
     options.channelType       or= 'privatemessage'

-    options.initialParticipantStatus = 'active'
+    options.initialParticipantStatus  = 'active'
+    options.autoCompleteController  or= ParticipantSearchController

     super options, data

@@ -400,6 +401,8 @@

   createAddParticipantForm: ->

+    { autoCompleteController } = @getOptions()
+
     @autoCompleteForm = new KDFormViewWithFields
       title              : 'START A CHAT WITH:'
       cssClass           : 'new-message-form inline'
@@ -408,7 +411,7 @@
           itemClass      : KDView
       submit             : (e) -> e.preventDefault()

-    @autoComplete = new ParticipantSearchController
+    @autoComplete = new autoCompleteController
       name                : 'userController'
       placeholder         : 'Type a username...'
       itemClass           : ActivityAutoCompleteUserItemView


Fix bug with leaving open ended response page and coming back

--- common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -124,6 +124,7 @@
   rubric_collapse_sel: '.rubric-collapse'
   next_rubric_sel: '.rubric-next-button'
   previous_rubric_sel: '.rubric-previous-button'
+  oe_alert_sel: '.open-ended-alert'

   constructor: (el) ->
     @el=el
@@ -137,6 +138,7 @@
     $(selector, @el)

   reinitialize: (element) ->
+    @has_been_reset = false
     @wrapper=@$(@wrapper_sel)
     @coe = @$(@coe_sel)

@@ -253,6 +255,30 @@
         if response.hide_reset
           @reset_button.hide()

+  get_last_response: () =>
+    @submit_button.hide()
+    @answer_area.attr("disabled", true)
+    data = {}
+    $.postWithPrefix "#{@ajax_url}/get_last_response", data, (response) =>
+      if response.success && response.response != ""
+        @answer_area.html(response.response)
+        if response.state!='initial'
+          @submit_button.hide()
+          @answer_area.attr("disabled", true)
+        if @has_been_reset
+          @submit_button.show()
+          @answer_area.attr("disabled", false)
+          @gentle_alert "Here is your previous answer to this qu
+                        estion."
+        else if @allow_reset=="True"
+          @reset_button.show()
+          @gentle_alert "You may reset and answer this question again."
+        else
+          @gentle_alert "You have answered this question."
+      else
+        @submit_button.show()
+        @answer_area.attr("disabled", false)
+        @$(@oe_alert_sel).animate(opacity: 0, 700)

   show_status_current: () =>
     data = {}
@@ -260,12 +286,6 @@
       if response.success
         @status_container.after(response.html).remove()
         @status_container= $(@status_container_sel)
-
-  get_last_response: () =>
-    data = {}
-    $.postWithPrefix "#{@ajax_url}/get_last_response", data, (response) =>
-      if response.success
-        console.log(response.response)

   message_post: (event)=>
     external_grader_message=$(event.target).parent().parent().parent()
@@ -301,6 +321,7 @@

   rebind: () =>
+    @get_last_response()
     # rebind to the appropriate function for the current state
     @submit_button.unbind('click')
     @submit_button.show()
@@ -308,6 +329,7 @@
     @hide_file_upload()
     @next_problem_button.hide()
     @hint_area.attr('disabled', false)
+
     if @task_number>1 or @child_state!='initial'
       @show_status_current()

@@ -376,6 +398,7 @@
   save_answer: (event) =>
     event.preventDefault()
     @submit_button.hide()
+    @answer_area.attr("disabled", true)
     max_filesize = 2*1000*1000 #2MB
     pre_can_upload_files = @can_upload_files
     if @child_state == 'initial'
@@ -414,7 +437,6 @@
             @gentle_alert response.error

       $.ajaxWithPrefix("#{@ajax_url}/save_answer",settings)
-
     else
       @errors_area.html(@out_of_sync_message)

@@ -492,6 +514,7 @@
           @coe.after(response.html).remove()
           @allow_reset="False"
           @reinitialize(@element)
+          @has_been_reset = true
           @rebind()
           @reset_button.hide()
         else
@@ -523,11 +546,11 @@
       @errors_area.html(@out_of_sync_message)

   gentle_alert: (msg) =>
-    if @$el.find('.open-ended-alert').length
-      @$el.find('.open-ended-alert').remove()
+    if @$el.find(@oe_alert_sel).length
+      @$el.find(@oe_alert_sel).remove()
     alert_elem = "
" + msg + "
"
     @$el.find('.open-ended-action').after(alert_elem)
-    @$el.find('.open-ended-alert').css(opacity: 0).animate(opacity: 1, 700)
+    @$el.find(@oe_alert_sel).css(opacity: 0).animate(opacity: 1, 700)

   queueing: =>
     if @child_state=="assessing" and @child_type=="openended"


Add more API methods

Add more API methods

--- routes/api.coffee
+++ routes/api.coffee
@@ -2,6 +2,7 @@
 router = express.Router()
 passport = require 'passport'
 _ = require 'lodash'
+users = require '../users'

 router.use (req, res, next) ->
   if !req.user
@@ -9,15 +10,23 @@
     res.redirect "/"
   else
     next()
+router.use (req, res, next) ->
+  res.respond = (data) ->
+    res.json data: data
+  next()

 router.get "/getUser", (req, res) ->
-  res.json _.omit req.user, 'password'
+  res.respond _.omit req.user, 'password'

-router.get '/name', (req, res) ->
-  res.json data: req.user.username
+router.get '/getName', (req, res) ->
+  res.respond req.user.username

-router.get '/files', (req, res) ->
-  res.json data: req.user.slides
+router.get '/getSlides', (req, res) ->
+  res.respond req.user.slides
+
+router.post '/setSlides', (req, res) ->
+  req.user.slides = req.body.slides
+  res.respond req.user.slides

 router.use (req, res, next) ->
   if !req.user.admin
@@ -26,4 +35,7 @@
   else
     next()

+router.get '/getUsers', (req, res) ->
+  res.respond users.getUsers()
+
 module.exports = router


Slight tweak to test Hubot in Heroku.

Slight tweak to test Hubot in Heroku.

--- scripts/rain.coffee
+++ scripts/rain.coffee
@@ -101,7 +101,7 @@
 				    			else
 				    				currentWeather = "Don't worry. I don't think it's going to rain in the next hour."
     		catch e
-    			currentWeather = "Sorry, I can't get a forecast at the moment."
+    			currentWeather = "Sorry, I can't get a forecast right now."
     		cb currentWeather

 module.exports = (robot) ->


hub -- make sync in hub totally locked until done.

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -1940,7 +1940,7 @@

         @connect      = misc.retry_until_success_wrapper(f:@_connect, logname:'connect')
         # min_interval: to avoid possibly DOS's a local hub -- not sure what best choice is here.
-        @sync = misc.retry_until_success_wrapper(f:@_sync, min_interval:200, logname:'localhub_sync')
+        @sync         = misc.retry_until_success_wrapper(f:@_sync, min_interval:200, logname:'localhub_sync')

         # The downstream (web browser) clients of this hub
         @diffsync_clients = {}
@@ -2071,13 +2071,14 @@
         @_sync_lock = true
         before = @diffsync_server.live
         ds_client.recv_edits    mesg.edit_stack, mesg.last_version_ack, (err) =>
-            @_sync_lock = false
             if err
+                @_sync_lock = false
                 client.error_to_client(id:mesg.id, error:"CodeMirrorSession -- unable to push diffsync changes from client (id=#{client.id}) -- #{err}")
                 return

             # Update master live document with result.
             @set_content(ds_client.live)
+            @_sync_lock = false

             # Send back our own edits to this client.
             ds_client.remote.current_mesg_id = mesg.id  # used to tag the return message
@@ -2113,9 +2114,12 @@
         @_sync_lock = true
         before = @diffsync_server.live
         @diffsync_server.push_edits (err) =>
-            @_sync_lock = false
             after = @diffsync_server.live
+            @set_content(after)
             if err
+                # We do *NOT* remove the sync_lock in this branch; only do that after a successful sync, since
+                # otherwise clients think they have successfully sync'd with the hub, but when the hub resets,
+                # the clients end up doing the wrong thing.
                 winston.debug("codemirror session local hub sync error -- #{err}")
                 if typeof(err) == 'string'
                     err = err.toLowerCase()
@@ -2124,12 +2128,17 @@
                         # This is normal -- it's because the diffsync algorithm only allows sync with
                         # one client (and upstream) at a time.
                         cb(err); return
-                    if err.indexOf("unknown") != -1
+                    else if err.indexOf("unknown") != -1
                         winston.debug("sync: reconnecting...")
-                        @_connect(cb); return
+                        @_connect () =>
+                            cb(err); return # still an error even if connect works.
+                    else if err.indexOf("timed out") != -1
+                        @local_hub.restart () =>
+                            cb(err); return
                 cb(err)
             else
                 winston.debug("codemirror session local hub sync -- pushed edits, thus completing cycle")
+                @_sync_lock = false
                 @_last_sync = after # what was the last successful sync with upstream.
                 if before != after
                     @_tell_clients_to_sync()


[Cartilage.Views.ListView] Added (disabled) support for scrolling selected items into view.

It is disabled because we are now tied into browser focus, which has its own scroll into view logic built-in. Will need to evaluate our options and whether or not we want to continue supporting native focus.

--- app/assets/javascripts/cartilage/views/list_view.js.coffee
+++ app/assets/javascripts/cartilage/views/list_view.js.coffee
@@ -260,3 +260,67 @@
     # Select the elements in the requested indexes.
     elements = ($ @el).find("li").slice indexes...
     _.each elements, @selectAnother
+
+  #
+  # Scrolls the list view to fully expose the selected item if it is not
+  # fully visible within the list view. Returns true or false depending on
+  # whether the scroll was performed.
+  #
+  # scrollToSelectedItem: =>
+  #   console.log "scrollToSelectedItem"
+  #
+  #   scrollElement = ($ @el).parent()[0]
+  #   console.log scrollElement
+  #
+  #   if ($ scrollElement).scrollHeight < ($ scrollElement).innerHeight() and ($ scrollElement).scrollWidth < ($ scrollElement).innerWidth()
+  #     console.log "returning"
+  #     return
+  #
+  #   selectedItemTop     = @selectedElement.offset().top - ($ scrollElement).offset().top
+  #   selectedItemBottom  = selectedItemTop + @selectedElement.innerHeight()
+  #   selectedItemLeft    = @selectedElement.offset().left - ($ scrollElement).offset().left
+  #   selectedItemRight   = selectedItemLeft + @selectedElement.innerWidth()
+  #   currentScrollTop    = ($ scrollElement).scrollTop()
+  #   currentScrollBottom = ($ scrollElement).scrollTop() + ($ scrollElement).innerHeight()
+  #   currentScrollLeft   = ($ scrollElement).scrollLeft()
+  #   currentScrollRight  = ($ scrollElement).scrollLeft() + ($ scrollElement).innerWidth()
+  #   itemTopMargin       = parseInt(@selectedElement.css("margin-top"), 10)
+  #   itemBottomMargin    = parseInt(@selectedElement.css("margin-bottom"), 10)
+  #   itemLeftMargin      = parseInt(@selectedElement.css("margin-left"), 10)
+  #   itemRightMargin     = parseInt(@selectedElement.css("margin-right"), 10)
+  #   scrollTopValue      = selectedItemTop - itemTopMargin
+  #   scrollLeftValue     = selectedItemLeft - itemLeftMargin
+  #   shouldScrollTop     = false
+  #   shouldScrollLeft    = false
+  #
+  #   # selectedItem is above current scroll position
+  #   if selectedItemTop < currentScrollTop
+  #     shouldScrollTop = true
+  #
+  #   # selectedItem is below current, viewable scroll position
+  #   if selectedItemTop >= currentScrollBottom
+  #     shouldScrollTop = true
+  #
+  #   # selectedItem is partially visible below the current, viewable scroll position
+  #   else if selectedItemBottom > currentScrollBottom
+  #     shouldScrollTop = true
+  #     scrollTopValue = currentScrollTop + (selectedItemBottom - currentScrollBottom) + itemBottomMargin
+  #
+  #   # selectedItem is left of current scroll position
+  #   if selectedItemLeft < currentScrollLeft
+  #     shouldScrollLeft = true
+  #
+  #   # selectedItem is right of current, viewable scroll position
+  #   if selectedItemLeft >= currentScrollRight
+  #     shouldScrollLeft = true
+  #
+  #   # selectedItem is partially visible below the current, viewable scroll position
+  #   else if selectedItemRight > currentScrollRight
+  #     shouldScrollLeft = true
+  #     scrollLeftValue = currentScrollLeft + (selectedItemRight - currentScrollRight) + itemRightMargin
+  #
+  #   if shouldScrollTop or shouldScrollLeft
+  #     ($ @el).scrollTop(scrollTopValue) if shouldScrollTop
+  #     ($ @el).scrollLeft(scrollLeftValue) if shouldScrollLeft
+  #
+  #   (shouldScrollTop or shouldScrollLeft)


rename fix and include

rename fix and include

--- src/app/common/task-service.coffee
+++ src/app/common/task-service.coffee
@@ -24,7 +24,7 @@
     'working_on_it':      'Working On It',
     'need_help':          'Need Help',
     'redo':               'Redo',
-    'fix_and_include':    'Fix and Include',
+    'fix_and_include':    "Don't resubmit",
     'fix_and_resubmit':   'Resubmit',
     'discuss':            'Discuss',
     'complete':           'Complete'


Moved back to FF31, current ESR version.

--- app/assets/javascripts/app/lib/app_post/browser.coffee
+++ app/assets/javascripts/app/lib/app_post/browser.coffee
@@ -30,7 +30,7 @@
     # define min. required browser version
     map =
       Chrome2: 37
-      Firefox: 32
+      Firefox: 31
       Explorer: 10
       Safari: 6
       Opera: 22


Fix adding inner elements to array

Fix adding inner elements to array

--- app/assets/javascripts/local_time/page_observer.coffee
+++ app/assets/javascripts/local_time/page_observer.coffee
@@ -22,7 +22,7 @@
     elements = []
     if element?.nodeType is Node.ELEMENT_NODE
       elements.push(element) if elementMatchesSelector(element, @selector)
-      elements.concat(element.querySelectorAll(@selector)...)
+      elements.push(element.querySelectorAll(@selector)...)
     elements

   processMutations: (mutations) =>


deprecate and simplify previev

deprecate and simplify previev

--- app/assets/javascripts/uploadcare/files/base.js.coffee
+++ app/assets/javascripts/uploadcare/files/base.js.coffee
@@ -98,51 +98,35 @@
     __cancel: =>
       @__uploadDf.reject('user', this)

-    __preview: (p, selector) =>
-      p.done (info) =>
-        return $(selector).empty() unless info.crop
-
-        opts = info.crop
-
+    __preview: (selector) =>
+      window.console?.warn? "'preview' method is deprecated. " +
+                            "Use fileInfo.cdnUrl as image source."
+      @apiPromise.done (info) =>
         img = new Image()
         img.onload = ->
-          if opts.sw || opts.sh # Resized?
-            sw = opts.sw || opts.sh * opts.width / opts.height
-            sh = opts.sh || opts.sw * opts.height / opts.width
-          else
-            sw = opts.width
-            sh = opts.height
+          $(selector).html(
+            $('<div>')
+              .css
+                position: 'relative'
+                overflow: 'hidden'
+                width: @width
+                height: @height
+              .append img
+          )
+        img.src = info.cdnUrl

-          sx = sw / opts.width
-          sy = sh / opts.height
-
-          el = $('<div>').css({
-            position: 'relative'
-            overflow: 'hidden'
-            width: sw
-            height: sh
-          }).append($(img).css({
-            position: 'absolute'
-            left: opts.x * -sx
-            top: opts.y * -sy
-            width: img.width * sx
-            height: img.height * sy
-          }))
-          $(selector).html(el)
-        img.src = info.originalUrl
-
-    __extendPromise: (p) =>
-      p.cancel = =>
+    __extendApi: (api) =>
+      api.cancel = =>
         @__cancel()

-      p.preview = (selector) =>
-        @__preview(p, selector)
+      api.preview = (selector) =>
+        @__preview(selector)

-      __then = p.then
-      p.pipe = p.then = =>  # 'pipe' is alias to 'then' from jQuery 1.8
-        @__extendPromise __then.apply(p, arguments)
+      __then = api.then
+      api.pipe = api.then = =>  # 'pipe' is alias to 'then' from jQuery 1.8
+        @__extendApi __then.apply(api, arguments)

-      p # extended promise
+      api # extended promise

     __notifyApi: =>
       @apiDeferred.notify @__progressInfo()
@@ -160,7 +144,7 @@

     __initApi: ->
       @apiDeferred = $.Deferred()
-      @apiPromise = @__extendPromise @apiDeferred.promise()
+      @apiPromise = @__extendApi @apiDeferred.promise()

       @__uploadDf.progress (progress) =>
         @__progress = progress


Break on first path match

Break on first path match

--- src/app/package-config-view.coffee
+++ src/app/package-config-view.coffee
@@ -139,6 +139,7 @@
       for packageDirPath in config.bundledPackageDirPaths
         if packagePath.indexOf("#{packageDirPath}/") is 0
           @bundled = true
+          break

       version = loadedPackage?.metadata.version
       unless version


Update tests

Update tests

--- spec/network.coffee
+++ spec/network.coffee
@@ -164,12 +164,16 @@
           node: 'Hello'
           port: 'in'
     describe 'on starting the network', ->
-      it 'should get started', (done) ->
+      it 'should get started and stopped', (done) ->
+        started = false
         listener = (message) ->
           chai.expect(message).to.be.an 'object'
           chai.expect(message.data.protocol).to.equal 'network'
-          if message.data.command is 'start'
+          if message.data.command is 'started'
             chai.expect(message.data.payload).to.be.a 'date'
+            started = true
+          if message.data.command is 'stopped'
+            chai.expect(started).to.equal true
             window.removeEventListener 'message', listener, false
             done()
         window.addEventListener 'message', listener, false
@@ -187,13 +191,16 @@
             chai.expect(message.data.payload.inPorts).to.eql [
               id: 'in'
               type: 'all'
+              array: true
             ,
               id: 'options'
               type: 'all'
+              array: false
             ]
             chai.expect(message.data.payload.outPorts).to.eql [
               id: 'out'
               type: 'all'
+              array: false
             ]
             window.removeEventListener 'message', listener, false
             done()


if we are generating a staging bitmap anyway, and cacheDraw is true, immediatly cache. This always is <= total overhead. Usually < unless the next frame would invalidte the cache. In this case it is mearly ==.

--- src/art/engine/core/element.coffee
+++ src/art/engine/core/element.coffee
@@ -970,9 +970,50 @@
       unless cacheAggressively
         Element._drawCachingEnabled = _drawCachingEnabled

+    ###
+    TODO:
+
+      Support for "pixel-exact-caching":
+
+        If @_useStagingBitmap() is true, we should cache even if _cacheDraw is false.
+
+        NOTE: cached bitmaps are automatically released and recycled as needed, so over-caching
+          is only a question of overhead. In the case where we are going to generate
+          a staging bitmap ANYWAY, there IS no overhead - just keep the results around in case
+          we need it next time.
+
+        HOWEVER, this is currently a problem because _generateDrawCache always generates
+        a cache bitmap at a fixed scale - the devicePixelsPerPoint.
+
+        This can be very wrong. Example: With the Flashy Text-renderer in the Kimi-editor the rendered
+        resolution becomes rediculously low - 1/10th the desired resolution or so.
+
+        So, we need the option to always cache in such a way that is pixel-exact with would would
+        have been output without caching. If the the draw-matrix changes in anyway other than whole
+        pixel translations then the cache should be invalidated and redrawn.
+
+        We still want the current mode, which renders the draw-cache at the element's logical size
+        multipled by the devicePixelsPerPoint. This is faster, and often not a quality concern.
+
+        Additional options:
+          We may add another option which lets of add a "cache-at" scale factor to force lower or
+          higher resolution caching.
+
+          In the old C++ Art.Engine we had a global "fast" mode where caches were not invalidated under
+          any draw-matrix changes until fast-mode was turned off, then a final redraw pass was made
+          where pixel-inexact caches were invalidated and redrawn. This allowed good user interactivity
+          followed by maximum quality renders. This was handy for the more general-purpose Kimi-editor,
+          for for the current purpose-built kimi-editor, it isn't needed.
+
+
+    ###
     _generateDrawCacheIfNeeded: ->
       {_cacheDraw} = @
-      if !_cacheDraw
+      if _cacheDraw && @_useStagingBitmap() && @_elementDrawChangedThisFrame
+        @_elementDrawChangedThisFrame = false
+        @_generateDrawCache()
+        true
+      else if !_cacheDraw
         @_clearDrawCache() if @_drawCacheBitmap
         false
       else if _cacheDraw == 'locked' && @_drawCacheBitmap


Leaderboard: Add to routes.

Leaderboard: Add to routes.

--- server/index.coffee
+++ server/index.coffee
@@ -11,6 +11,7 @@
 {Room} = require('./rooms')
 errors = require '../shared/errors'
 db = require('./database')
+ratings = require('./ratings')

 @createServer = (port) ->
   app = express()
@@ -34,6 +35,15 @@

   app.get("/", renderHomepage)
   app.get("/battles/:id", renderHomepage)
+
+  app.get '/leaderboard', (req, res) ->
+    page = req.param('page')
+    perPage = req.param('per_page')
+    ratings.listRatings page, perPage, (err, results) ->
+      if err
+        res.json(500, err.message)
+      else
+        res.json(results)

   lobby = new Room("Lobby")
   server.rooms.push(lobby)


Use correct path variable

Use correct path variable

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -112,10 +112,10 @@
 addCommandsToPath = (callback) ->
   installCommands = (callback) ->
     atomCommandPath = path.join(binFolder, 'atom.cmd')
-    relativeCmdPath = path.relative(binFolder, path.join(appFolder, 'cli', 'atom.cmd'))
+    relativeAtomPath = path.relative(binFolder, path.join(appFolder, 'cli', 'atom.cmd'))
     atomCommand = """
       @echo off
-      "%~dp0\\#{relativeExePath}" %*
+      "%~dp0\\#{relativeAtomPath}" %*
     """

     apmCommandPath = path.join(binFolder, 'apm.cmd')


fix(web): fixed load draft;

fix(web): fixed load draft;

--- apps/actor-web/app/shared/compose/composeController.coffee
+++ apps/actor-web/app/shared/compose/composeController.coffee
@@ -7,14 +7,10 @@
     console.log '[AW]ComposeController constructor'
     @$scope.$on 'onConversationOpen', =>
       console.log '[AW]ComposeController constructor: onConversationOpen fired.'
-      @enableCompose()
-
-  enableCompose: ->
-    console.log '[AW]ComposeController enableCompose'
-    @isEnabled = true
-    @draft = @actorService.loadDraft @actorService.currentPeer
-    console.log '[AW]ComposeController enableCompose: @draft:', @draft
-    @message = @draft if @draft
+      @$timeout =>
+        @message = @draft = @actorService.loadDraft @actorService.currentPeer
+        console.log '[AW]ComposeController constructor: @draft:', @draft
+        @isEnabled = true

   onTyping: ->
     console.log '[AW]ComposeController onTyping'


cleaned up app.coffee

cleaned up app.coffee

--- vnc-example01.framer/app.coffee
+++ vnc-example01.framer/app.coffee
@@ -13,7 +13,7 @@
 	btn.opacity = 0
 	btn.on Events.Click, ->
 		screen = Layers.get @name.replace('btn_','screen_')
-		Views.flipInRight screen
+		Views.slideInRight screen

 modalbtn_push.on Events.Click, ->
 	Views.zoomedIn modal_push
@@ -22,8 +22,8 @@
 	btn.on Events.Click, -> Views.back()
 	btn.opacity = 0

-Views.add controlcenter, {x: 0, y: 760}
-#Views.add screen_usage
-Views.switchInstant screen_settings
+Views.add controlcenter, {x: 0, y: 340}
 controlcenter_btn.on Events.Click, -> Views.slideInUp controlcenter
-controlcenter.on Events.Click, -> Views.back()+controlcenter.on Events.Click, -> Views.back()
+
+Views.switchInstant screen_settings


Serialize window state in telepath format

--- src/app/atom.coffee
+++ src/app/atom.coffee
@@ -270,10 +270,11 @@
       console.warn "Error parsing window state: #{windowStatePath}", error.stack, error

     windowState ?= {}
-    telepath.Document.create(windowState, site: telepath.createSite(1))
+    site = telepath.createSite(1)
+    telepath.Document.deserialize(windowState, {site}) ? telepath.Document.create({}, {site})

   saveWindowState: ->
-    windowStateJson = JSON.stringify(@getWindowState().toObject())
+    windowStateJson = JSON.stringify(@getWindowState().serialize())
     if windowStatePath = @getWindowStatePath()
       fsUtils.writeSync(windowStatePath, windowStateJson)
     else


Use underscore map function

Use underscore map function

--- bokehjs/src/coffee/models/annotations/color_bar.coffee
+++ bokehjs/src/coffee/models/annotations/color_bar.coffee
@@ -497,8 +497,8 @@

     # Because we want the scale to be reversed
     if @orientation == 'vertical'
-      major_coords[i] = major_coords[i].map((coord) -> return scale_length - coord)
-      minor_coords[i] = minor_coords[i].map((coord) -> return scale_length - coord)
+      major_coords[i] = _.map(major_coords[i], (coord) -> return scale_length - coord)
+      minor_coords[i] = _.map(minor_coords[i], (coord) -> return scale_length - coord)

     return {
       "major": major_coords


Made the /:username/:repo/users/invite call safer.

* The frontend passes a map of github ids to email addresses. This
  is nice because now they can only pass one email address per github
  id.
* The backend makes sure all those github ids are part of the project,
  and now fetches their name and logins from the github ids.

--- js/inner/project.coffee
+++ js/inner/project.coffee
@@ -506,11 +506,9 @@
     false

   invite_team_members: (users) =>
-    to_invite = for user in users
-      login: user.login
-      email: user.email()
-      id: user.id()
-    console.log to_invite
+    to_invite = {}
+    for user in users
+      to_invite[user.id()] = user.email()
     $.ajax
       type: "POST"
       url: "/api/v1/project/#{@project_name()}/users/invite"


wire up data for showing trial messages on all unavailable containers

--- js/inner/project.coffee
+++ js/inner/project.coffee
@@ -105,6 +105,9 @@
     @show_add_containers = @komp =>
       @paid_parallelism() < @focused_parallel() <= @plan().max_parallelism()

+    @show_upgrade_trial = @komp =>
+      @paid_parallelism() < @focused_parallel()
+
     @show_uneven_divisor_warning_p = @komp =>
       @focused_parallel() <= @paid_parallelism() && @billing.usable_containers() % @focused_parallel() isnt 0


hopefully better trial messages

hopefully better trial messages

--- js/inner/project.coffee
+++ js/inner/project.coffee
@@ -193,13 +193,15 @@
       plan_path = CI.paths.org_settings org_name, 'plan'
       days = @billing.trial_days()
       if @billing.trial_over()
-        "The #{org_name} organization's trial is over. Add a plan to continue running your builds."
+        "#{org_name}'s trial is over. Add a plan to continue running your builds."
+      else if days > 10
+        "#{org_name} is in a 2-week trial, enjoy! (or check out our plans)"
       else if days > 7
-        "The #{org_name} organization is in its 2-week trial, enjoy! Check out our plans."
-      else if days > 1
-        "The #{org_name} organization has #{days} days left in its trial.  Check out our plans."
+        "#{org_name}'s trial has #{days} days left. Check out our plans."
+      else if days > 4
+        "#{org_name}'s trial has #{days} days left. Add a plan to keep running your builds."
       else
-        "The #{org_name} organization's trial is over in #{@billing.pretty_trial_time()}.  Add a plan to keep running your builds."
+        "#{org_name}'s trial expires in #{@billing.pretty_trial_time()}! Add a plan to keep running your builds."

   @sidebarSort: (l, r) ->


close tooltip when user clicks

close tooltip when user clicks

--- js/inner/project.coffee
+++ js/inner/project.coffee
@@ -321,6 +321,7 @@

   toggle_show_branch_input: (data, event) =>
     @show_branch_input(!@show_branch_input())
+    $(event.target).tooltip('hide')
     # hasfocus binding is bad here: closes the form when you click the button
     if @show_branch_input()
       $(event.target).siblings("input").focus()


Simplification

Simplification

--- packages/shrub-user/client/index.coffee
+++ packages/shrub-user/client/index.coffee
@@ -27,11 +27,9 @@
 			_instance = name: 'loading...'

 			orm.collection('shrub-user').then (User) ->
-				__instance = User.instantiate(
+				_instance[k] = v for k, v of User.instantiate(
 					config.get 'packageConfig:shrub-user'
 				)
-
-				_instance[k] = v for k, v of __instance

 			# Log a user out if we get a socket call.
 			logout = ->


adding, removing, fixing opcodes

adding, removing, fixing opcodes

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -192,18 +192,17 @@
     b.stack.length -= method.param_bytes

     virtual = @name in ['invokevirtual', 'invokeinterface']
-    invoke_str = "rs.method_lookup(#{JSON.stringify @method_spec}).run(rs, #{virtual}, [#{params.join ","}])"
+    b.add_line "rs.method_lookup(#{JSON.stringify @method_spec}).run(rs, #{virtual}, [#{params.join ","}])"

     unless method.return_type.toString() is 'V'
       temp = b.new_temp()
-      invoke_str = "#{temp} = #{invoke_str}"

       if method.return_type.toString() in ['D', 'J']
+        b.add_line "#{temp} = rs.pop2()"
         b.push2 temp
       else
+        b.add_line "#{temp} = rs.pop()"
         b.push temp
-
-    b.add_line invoke_str

 compile_obj_handlers = {
   aconst_null: { compile: (b) -> b.push "null"; }
@@ -221,11 +220,6 @@
   fconst_2: { compile: (b) -> b.push "2"; }
   dconst_0: { compile: (b) -> b.push2 "0"; }
   dconst_1: { compile: (b) -> b.push2 "1"; }
-  istore: { compile: (b) -> b.put_cl(@var_num, b.pop()) }
-  lstore: { compile: (b) -> b.put_cl2(@var_num, b.pop2()) }
-  fstore: { compile: (b) -> b.put_cl(@var_num, b.pop()) }
-  dstore: { compile: (b) -> b.put_cl2(@var_num, b.pop2()) }
-  astore: { compile: (b) -> b.put_cl(@var_num, b.pop()) }
   iastore: {compile: (b) -> v=b.pop(); i=b.pop();"b.check_null(#{b.pop()}).array[#{i}]=#{v}"}
   lastore: {compile: (b) -> v=b.pop2();i=b.pop();"b.check_null(#{b.pop()}).array[#{i}]=#{v}"}
   fastore: {compile: (b) -> v=b.pop(); i=b.pop();"b.check_null(#{b.pop()}).array[#{i}]=#{v}"}
@@ -269,6 +263,8 @@
   fneg: { compile: (b) -> b.push "-#{b.pop()}" }
   dneg: { compile: (b) -> b.push2 "-#{rs.pop2()}" }

+  iinc: { compile: (b) -> b.put_cl @index, "wrap_int(#{b.cl(@index)}+#{@const})" }
+
   ireturn: { compile: (b) -> b.add_line "return #{b.pop()}" }
   lreturn: { compile: (b) -> b.add_line "return #{b.pop2()}" }
   freturn: { compile: (b) -> b.add_line "return #{b.pop()}" }
@@ -291,6 +287,12 @@
     temp = b.new_temp()
     b.add_line "#{temp} = rs.init_object(#{JSON.stringify @class})"
     b.push temp }
+
+  goto: { compile: (b, idx) ->
+    b.next = [@offset + idx]
+    b.compile_epilogue()
+    b.add_line "label = #{@offset + idx}; continue"
+  }

   goto_w: { compile: (b, idx) ->
     b.next = [@offset + idx]


don't wrap content in a span if we're just using the default color

--- js/libs/terminal.coffee
+++ js/libs/terminal.coffee
@@ -29,23 +29,25 @@
           when 37 then @color = "white"
           when 39 then @color = defaultColor

-      openSpan: () ->
+      classes: () ->
         classes = []
         if @bold and not @color.match(/^br/)
           classes.push("br#{@color}")
-        else
+        else if @color != defaultColor
           classes.push("#{@color}")
         if @italic
           classes.push("italic")

-        s = " 0
-          s += " class='" + classes.join(" ") + "'"
-        s += ">"
+        classes
+

       applyTo: (content) ->
         if content
-          @openSpan() + content + ""
+          classes = @classes()
+          if classes.length
+            "#{content}"
+          else
+            content
         else
           ""


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -19,7 +19,7 @@

 player.cooldown "shootTimer"

-player.I.shootTimer
+player.I.shootTimer =
 </pre></code>

 @name Cooldown


fix(form): say "Create" instead of "Submit"

--- src/components/generated-form.cjsx
+++ src/components/generated-form.cjsx
@@ -329,7 +329,7 @@
     onSubmit: ->

   render: =>
-    submitText = if @props.formType is "new" then "Submit" else "Update"
+    submitText = if @props.formType is "new" then "Create" else "Update"
     <form className="generated-form" ref="form" style={this.props.style} onSubmit={this.props.onSubmit} onKeyDown={this._onKeyDown} noValidate>
       <TabGroupRegion>
         {@_renderHeaderFormError()}


simplify

simplify

--- server/web.coffee
+++ server/web.coffee
@@ -45,11 +45,10 @@
         res.write "Building with: #{command}\n"
         log.info  "spawning build"

-        env =
+        make = spawner.spawn "bin/make \"#{id}\"", env:
           CLOUDANT_URL: process.env.CLOUDANT_URL
           PATH: process.env.PATH

-        make = spawner.spawn "bin/make \"#{id}\"", env:env
         make.on "error", (err)  -> log.error(err)
         make.on "data",  (data) -> res.write data
         make.on "exit",  (code) -> res.end())


Include commands with no category

Include commands with no category

--- src/packages/command-logger/src/command-logger.coffee
+++ src/packages/command-logger/src/command-logger.coffee
@@ -54,7 +54,10 @@
     categories = {}
     for eventName, details of @eventLog
       categoryStart = eventName.indexOf(':')
-      categoryName = _.humanizeEventName(eventName.substring(0, categoryStart))
+      if categoryStart is -1
+        categoryName = 'Uncategorized'
+      else
+        categoryName = _.humanizeEventName(eventName.substring(0, categoryStart))
       category = categories[categoryName]
       unless category
         category =


Loading overlay is now displayed when saving data to DB

--- resources/assets/coffee/react/profile-page/extra.coffee
+++ resources/assets/coffee/react/profile-page/extra.coffee
@@ -115,7 +115,7 @@
   _toggleDragging: =>
     if @state.draggingEnabled

-      osu.showLoadingOverlay
+      osu.showLoadingOverlay()

       $.ajax '/account/update-profile', {
         method: 'PUT',
@@ -124,9 +124,7 @@
           'order': @state.profileOrder,
         },
         success: (data, textStatus, jqHXR) ->
-          osu.hideLoadingOverlay
-          if data.status == 'error'
-            osu.popup Lang.get('errors.account.profile-order.error'), 'warning'
+          osu.hideLoadingOverlay()
       }

       $('#profile-extra-list').sortable('disable')


ActivityTicker: put comment view as disabled

--- client/Social/Activity/views/activitytickeritem.coffee
+++ client/Social/Activity/views/activitytickeritem.coffee
@@ -11,6 +11,7 @@
     JTag               : TagLinkView
     JGroup             : GroupLinkView
     JNewStatusUpdate   : ActivityLinkView
+    JComment           : ActivityLinkView

 class ActivityTickerFollowItem extends ActivityTickerBaseItem
   constructor: (options = {}, data) ->
@@ -120,7 +121,7 @@
   constructor: (options = {}, data) ->
     super options, data

-    {source, target, subject} = data
+    {source, target, object, subject} = data

     @avatar    = new AvatarView
       size     : width: 28, height: 28
@@ -129,7 +130,7 @@

     @actor    = new ProfileLinkView null, source
     @origin   = new ProfileLinkView null, target
-    @subj     = new ActivityLinkView null, subject
+    @subj     = new ActivityLinkView null, object

   pistachio: ->
     {source, target, subject} = @getData()


Stop native dom event when user clicked to vm shared setting's button.

--- client/app/lib/navigation/navigationmachineitem.coffee
+++ client/app/lib/navigation/navigationmachineitem.coffee
@@ -87,7 +87,7 @@
       tagName     : 'span'
       click       : (e) =>
         if @machine.isMine() then @handleMachineSettingsClick e
-        else if @machine.isApproved() then @showSidebarSharePopup()
+        else if @machine.isApproved() then @showSidebarSharePopup {}, e

   createSettingsIconPlaceholder: ->
@@ -171,7 +171,9 @@
     return data.workspaces.first?.channelId

-  showSidebarSharePopup: (options = {}) ->
+  showSidebarSharePopup: (options = {}, event) ->
+
+    kd.utils.stopDOMEvent event

     options.position  = @getPopupPosition 20
     options.channelId = @getChannelId @getData()


Refresh AppStorage after each fetch from FS

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -129,6 +129,7 @@
               manifest = JSON.parse rawManifest
               manifests["#{manifest.name}"] = manifest

+          @putAppsToAppStorage manifests
           callback? null, manifests

   fetchAppsFromDb:(callback)->
@@ -167,10 +168,8 @@

   putAppsToAppStorage:(apps)->

-    appManager.fetchStorage "KodingApps", "1.0", (err, storage)->
-      storage.update {
-        $set: { "bucket.apps" : apps }
-      }, => log arguments,"kodingAppsController storage updated"
+    @appStorage.setValue 'apps', apps, (err)=>
+      log err if err

   defineApp:(name, script)->


.

Former-commit-id: 21ced1aa8f5fe71dacc115556f66e18a28a22f8e [formerly 26d0c6ff4d275c9e46773e0d4bff4e9cdfb8c549] [formerly e59ad9cf454ef6747dd5e95fc49b1920563678d0 [formerly 076165abd603f95aaeb8e76220f914d49419d997]] [formerly f3beeb18a239d18ad3a7352edf5f1e9b1bb87eb3 [formerly 28f07ea2fb3559566a502266eb10019183550a3b] [formerly ccdc7819f20c94004f987ca0b68efdca844f747c [formerly 00bf562c57fe4a9c854a1e28c2ca75bfaba8bd86]]]
Former-commit-id: b38c6599906893292f3e68d57556787d2fd619ba [formerly 867427492f7676e89b1c7313efb8c5800d7fdbf7] [formerly b4666b13332a0586af905798cde509f64e066f34 [formerly 044e8ce6dce1bba8bbff84b4aadfbfdc9eb0130c]]
Former-commit-id: 090acfa244c3fd980dde05bfc6772a66e8483ec3 [formerly a219313570b19d4af148c0740bf0d6cb9b6ff5b4]
Former-commit-id: 1ecce1f90b7a773704948f1213cb2d831330e458
Former-commit-id: 762d1e4cd46de165793037f02bb10f6bb10dae22

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -209,10 +209,10 @@

 port_pros_cons =
-  pro: 'Favorável'
-  pros: 'Favorável'
-  con: 'Contrário'
-  cons: 'Contrário'
+  pro: 'A Favor'
+  pros: 'A Favor'
+  con: 'Contra'
+  cons: 'Contra'
   your_header: "Entrega tu --valences--"
   other_header: "Otros' --valences--"
   top_header: "Top --valences--"


disable NotificationView

disable NotificationView

--- lib/js_refactor.coffee
+++ lib/js_refactor.coffee
@@ -1,5 +1,5 @@
 Ripper = require './ripper'
-NotificationView = require './notification_view'
+#NotificationView = require './notification_view'

 { packages: packageManager } = atom

@@ -7,7 +7,7 @@
   activate: ->
     return if 'refactor' in packageManager.getAvailablePackageNames() and
               !packageManager.isPackageDisabled 'refactor'
-    new NotificationView
+    #new NotificationView
   deactivate: ->
   serialize: ->
   Ripper: Ripper


Load locals when setting up environment

Load locals when setting up environment

Now you can access modules or locals loaded via a jsonfile in your plugins

--- src/core/environment.coffee
+++ src/core/environment.coffee
@@ -13,7 +13,7 @@
 {render} = require './renderer'
 {runGenerator} = require './generator'

-{readJSON} = utils
+{readJSON, readJSONSync} = utils

 class Environment
   ### The Wintersmith environment. ###
@@ -42,9 +42,32 @@
       @logger.verbose "unloading: #{ id }"
       delete require.cache[id]

+    @setupLocals()
+
   setConfig: (@config) ->
     @contentsPath = @resolvePath @config.contents
     @templatesPath = @resolvePath @config.templates
+
+  setupLocals: ->
+    ### Resolve locals and loads any required modules. ###
+    @locals = {}
+
+    # Load locals json if neccessary
+    if typeof @config.locals == 'string'
+      filename = @resolvePath @config.locals
+      @logger.verbose "loading locals from: #{ filename }"
+      @locals = readJSONSync filename
+    else
+      @locals = @config.locals
+
+    # Load and add modules specefied with the require option to the locals context.
+    for alias, id of @config.require
+      logger.verbose "loading module '#{ id }' available in locals as '#{ alias }'"
+      if @locals[alias]?
+        logger.warn "module '#{ id }' overwrites previous local with the same key ('#{ alias }')"
+      @locals[alias] = @loadModule id
+
+    return

   resolvePath: (pathname) ->
     ### Resolve *pathname* in working directory, returns an absolute path. ###
@@ -120,46 +143,40 @@
       groups.push generator.group unless generator.group in groups
     return groups

-  loadModule: (module, callback) ->
-    ### Load a *module*, from the current working directory. ###
+  loadModule: (module) ->
+    ### Requires and returns *module*, resolved from the current working directory. ###
     require 'coffee-script' if module[-7..] is '.coffee'
     @logger.silly "loading module: #{ module }"
-    try
-      id = @resolveModule module
-      @logger.silly "resolved: #{ id }"
-      callback null, require id
-      @loadedModules.push id
-    catch error
-      callback error
+    id = @resolveModule module
+    @logger.silly "resolved: #{ id }"
+    rv = require id
+    @loadedModules.push id
+    return rv

   loadPluginModule: (module, callback) ->
-    ### Load a plugin *module*. ###
-    async.waterfall [
-      (callback) =>
-        if typeof module is 'string'
-          @loadModule module, callback
-        else
-          callback null, module
-      (fn, callback) =>
-        try
-          fn this, callback
-        catch error
-          callback error
-    ], (error) ->
+    ### Load a plugin *module*. Calls *callback* when plugin is done loading, or an error ocurred. ###
+    if typeof module is 'string'
+      try
+        module = @loadModule module
+      catch error
+        callback error
+        return
+
+    module.call null, this, (error) ->
       error.message = "Error loading plugin '#{ module }': #{ error.message }" if error?
       callback error

-  loadViewModule: (module, callback) ->
+  loadViewModule: (id, callback) ->
     ### Load a view *module* and add it to the environment. ###
-    @logger.verbose "loading view: #{ module }"
-    async.waterfall [
-      (callback) => @loadModule module, callback
-      (fn, callback) =>
-        @registerView path.basename(module), fn
-        callback()
-    ], (error) ->
-      error.message = "Error loading view '#{ module }': #{ error.message }" if error?
+    @logger.verbose "loading view: #{ id }"
+    try
+      module = @loadModule id
+    catch error
+      error.message = "Error loading view '#{ id }': #{ error.message }"
       callback error
+      return
+    @registerView path.basename(id), module
+    callback()

   loadPlugins: (callback) ->
     ### Loads any plugin found in *@config.plugins*. ###
@@ -214,36 +231,9 @@
     loadTemplates this, callback

   getLocals: (callback) ->
-    ### Resolve locals. ###
-
-    resolveLocals = (callback) =>
-      ### Load locals json if neccessary. ###
-      if typeof @config.locals == 'string'
-        filename = @resolvePath @config.locals
-        @logger.verbose "loading locals from: #{ filename }"
-        readJSON filename, callback
-      else
-        callback null, @config.locals
-
-    addModules = (locals, callback) =>
-      ### Loads and adds modules specefied with the require option to the locals context. ###
-      async.forEach Object.keys(@config.require), (alias, callback) =>
-        module = @config.require[alias]
-        logger.verbose "loading module '#{ module }' available in locals as '#{ alias }'"
-        @loadModule module, (error, result) ->
-          if not error?
-            if locals[alias]?
-              logger.warn "module '#{ module }' overwrites previous local with the same key ('#{ alias }')"
-            locals[alias] = result
-          else
-            error.message = "Error loading module '#{ module }': #{ error.message }"
-          callback error
-      , (error) -> callback error, locals
-
-    async.waterfall [
-      resolveLocals
-      addModules
-    ], callback
+    ### Returns locals. ###
+    # TODO: locals are no longer loaded async, this method should eventually be removed
+    callback null, @locals

   load: (callback) ->
     ### Convenience method to load plugins, views, contents, templates and locals. ###


Expose some api on environment

Expose some api on environment

--- src/core/environment.coffee
+++ src/core/environment.coffee
@@ -4,16 +4,23 @@
 async = require 'async'
 fs = require 'fs'

+utils = require './utils'
+
 {Config} = require './config'
-{ContentTree} = require './content'
-{readJSON} = require './utils'
+{ContentPlugin, ContentTree} = require './content'
 {render} = require './renderer'
 {loadTemplates} = require './templates'
 {buildGraph} = require './graph'
 {logger} = require './logger'

+{readJSON} = utils
+
 class Environment
   ### The Wintersmith environment. ###
+
+  utils: utils
+  ContentTree: ContentTree
+  ContentPlugin: ContentPlugin

   constructor: (@config, @workDir, @logger) ->
     @views = {}