Rename test context

Rename test context

--- test/elvis-backbone-test.coffee
+++ test/elvis-backbone-test.coffee
@@ -5,7 +5,7 @@
   event.initEvent(type, true, false)
   event

-describe 'el.backbone.model', ->
+describe 'Elvis Backbone.Model', ->
   it 'can handle basic bindings', ->
     model = new Backbone.Model(foo: 'bar')
     element = el('div', model.bindTo('foo'))


Fix path.extname received undefined

Fix path.extname received undefined

Fixes #298

--- lib/helpers/template-helper.coffee
+++ lib/helpers/template-helper.coffee
@@ -50,13 +50,15 @@
   path.relative(siteDir, fileDir)

 getEditor = (editor) ->
+  filePath = editor.getPath() || "" # getPath is undefined when editor opens in an empty window/workspace
   frontMatter = new FrontMatter(editor, { silent: true })
+
   data = frontMatter.getContent()
   data["category"] = frontMatter.getArray(config.get("frontMatterNameCategories", allow_blank: false))[0]
   data["tag"] = frontMatter.getArray(config.get("frontMatterNameTags", allow_blank: false))[0]
-  data["directory"] = getFileRelativeDir(editor.getPath())
-  data["slug"] = getFileSlug(editor.getPath()) || utils.slugize(data["title"], config.get("slugSeparator"))
-  data["extension"] = path.extname(editor.getPath()) || config.get("fileExtension")
+  data["directory"] = getFileRelativeDir(filePath)
+  data["slug"] = getFileSlug(filePath) || utils.slugize(data["title"], config.get("slugSeparator"))
+  data["extension"] = path.extname(filePath) || config.get("fileExtension")
   data

 module.exports =


video: add camera question dialog step

video: add camera question dialog step

--- client/app/lib/videocollaboration/views/baseview.coffee
+++ client/app/lib/videocollaboration/views/baseview.coffee
@@ -2,6 +2,7 @@
 getNick = require 'app/util/nick'
 VideoControlView = require './controlview'
 isMyChannel = require 'app/util/isMyChannel'
+NoiseView = require 'app/commonviews/noiseview'

 module.exports = class ChatVideoView extends kd.View

@@ -15,6 +16,7 @@

     @createContainer()
     @createOfflineUserContainer()
+    @createCameraDialogContainer()
     @createControls()

@@ -26,8 +28,23 @@

   createOfflineUserContainer: ->

-    @offlineUserContainer = new kd.CustomHTMLView { cssClass: 'ChatVideo-offlineUserContainer' }
+    @offlineUserContainer = createAbsoluteContainer 'ChatVideo-offlineUserContainer'
     @addSubView @offlineUserContainer
+
+
+  createCameraDialogContainer: ->
+
+    cssClass = 'ChatVideo-cameraDialogContainer hidden'
+    @cameraDialogContainer = createAbsoluteContainer cssClass
+
+    title = k 'span', 'title'
+    title.updatePartial 'Default text'
+
+    @cameraDialogContainer.title = title
+    @cameraDialogContainer.addSubView title
+
+    @addSubView @cameraDialogContainer
+

   createControls: ->
@@ -54,6 +71,30 @@

   getOfflineUserContainer: -> @offlineUserContainer
+
+
+  getCameraDialogContainer: -> @cameraDialogContainer
+
+
+  showCameraDialog: (title) ->
+
+    width = 325
+    height = 265
+
+    @_noiseView = new NoiseView { size: { width, height } }
+
+    @cameraDialogContainer.addSubView @_noiseView
+
+    @cameraDialogContainer.title.updatePartial title  if title?
+    @cameraDialogContainer.show()
+
+
+  hideCameraDialog: ->
+
+    @_noiseView.destroy()
+    @_noiseView = null
+
+    @cameraDialogContainer.hide()

   ###*
@@ -100,3 +141,25 @@
   new VideoControlView { cssClass, active, activeTooltipText, deactiveTooltipText }

+createAbsoluteContainer = (cssClass) -> k 'div', "ChatVideo-absoluteContainer #{cssClass}"
+
+
+###*
+ * Create a KDView with given tag, css class, and subviews.
+ *
+ * @param {string} tagName
+ * @param {string} cssClass
+ * @param {Array.<(KDView|string)>} subviews
+###
+k = (tagName, cssClass, subviews = []) ->
+
+  view = new kd.CustomHTMLView { tagName, cssClass }
+
+  for subview in subviews
+    if 'string' is typeof subview
+    then view.setPartial subview
+    else view.addSubView subview
+
+  return view
+
+


adjust height to be a bit taller

adjust height to be a bit taller

--- client/lanes/access/screens/user-management/Editor.cjsx
+++ client/lanes/access/screens/user-management/Editor.cjsx
@@ -17,4 +17,4 @@
         </form>

     render: ->
-        @renderPopover()
+        @renderPopover(height: 380)


client: home: initiate chatlio on bootup

--- client/app/lib/index.coffee
+++ client/app/lib/index.coffee
@@ -7,6 +7,7 @@
 ConnectionChecker      = require './connectionchecker'
 lazyrouter             = require './lazyrouter'
 setupAnalytics         = require './setupanalytics'
+setupChatlio           = require './setupchatlio'
 os                     = require 'os'
 localStorage           = require './localstorage'

@@ -70,6 +71,8 @@
   mainController.tempStorage = {}

   ConnectionChecker.listen()
+
+  mainController.ready -> setupChatlio()

   ###
   # CONNECTIVITY EVENTS


whitespace fixes in dataset integration test

--- test/integration/dataset.coffee
+++ test/integration/dataset.coffee
@@ -133,7 +133,7 @@

     it 'shows an undo button', (done) ->
       @dataset.text (err, text) ->
-        text.should.include 'Undo'
+        text.should.include 'Undo'
         done()

     context "When I click the undo button", ->
@@ -143,12 +143,12 @@

       it 'no longer shows an undo button', (done) ->
         @dataset.text (err, text) ->
-          text.should.not.include 'Undo'
+          text.should.not.include 'Undo'
           done()

       it "shows the dataset title", (done) ->
         @dataset.text (err, text) ->
-          text.should.include 'Prune'
+          text.should.include 'Prune'
           done()

   context "When I click on the Prune dataset", ->
@@ -160,7 +160,7 @@
         , 500

     context "When I delete the dataset using the toolbar menu", ->
-      before (done) ->
+      before (done) ->
         wd40.click '#dataset-meta .dropdown-toggle', ->
           wd40.click '#dataset-meta .hide-dataset', done


Do not overwrite workspace snapshot if IDE is destroyed

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -680,7 +680,7 @@

   writeSnapshot: ->

-    return  unless @isMachineRunning()
+    return  if @isDestroyed or not @isMachineRunning()

     name  = @getWorkspaceSnapshotName()
     value = @getWorkspaceSnapshot()


Fix sharing tests

Fix sharing tests

* some tests were skipped because they require the branch "sharing" of
  the data-system. See Travis on my repository on the branch
  "dev-sharing-tests";
* changed stubs to reflect name change "isSharingAuthenticated" to
  "isAuthenticated";

--- test/sharing_test.coffee
+++ test/sharing_test.coffee
@@ -10,11 +10,13 @@
 sharing      = rewire  "#{helpers.prefix}server/controllers/sharing"
 remoteAccess = require "#{helpers.prefix}server/lib/remote_access"

+# Proxy
 client = helpers.getClient()
-client.setBasicAuth "home", "token"
-
+
+# Connect to the Data-System with the credentials of the Proxy
 Client = require('request-json').JsonClient
 clientDS = new Client urlHelper.dataSystem.url()
+clientDS.setBasicAuth "proxy", "token"

 describe 'sharing unit tests', ->
@@ -33,7 +35,7 @@
         shareID     : 18

-    describe 'createSharing inner module', ->
+    describe.skip 'createSharing inner module', ->

         createSharing = sharing.__get__ "createSharing"

@@ -41,22 +43,14 @@
             createSharing sharing_request, (err, docInfo) =>
                 should.not.exist err
                 @docInfo = docInfo
-                should.exist @docInfo._id
                 done()

         it 'then a new document is inserted in the database', (done) ->
-            clientDS.get "data/#{@docInfo._id}", (err, res) ->
-                doc = JSON.parse(res.body)
-                doc.desc.should.equal         sharing_request.desc
-                doc.rules.should.deep.equal   sharing_request.rules
-                doc.sharerUrl.should.equal    sharing_request.sharerUrl
-                doc.recipientUrl.should.equal sharing_request.recipientUrl
-                doc.preToken.should.equal     sharing_request.preToken
-                doc.shareID.should.equal      sharing_request.shareID
-            done()
-
-
-    describe 'revokeFromRecipient inner module', ->
+            should.exist @docInfo._id
+            done()
+
+
+    describe.skip 'revokeFromRecipient inner module', ->

         # we are a sharer and one of the targets has canceled the share
         sharing_doc =
@@ -212,14 +206,14 @@
                 err.should.deep.equal error
                 done()

-        it 'When a correct request is made then 200 is returned', (done) ->
+        it.skip 'When a correct request is made then 200 is returned', (done) ->
             sharing.request req, res, ->
             res.on 'send', ->
                 res.statusCode.should.equal 200
                 done()

-    describe 'revoke module', ->
+    describe.skip 'revoke module', ->

         req = httpMocks.createRequest
             method : 'DELETE'
@@ -227,17 +221,17 @@
             headers:
                 authorization: "1:A ring to rule them all"

-        stubIsSharingAuthenticated = {}
+        stubIsAuthenticated = {}

         before (done) ->
-            stubIsSharingAuthenticated = sinon.stub remoteAccess,
-                'isSharingAuthenticated', (header, callback) ->
+            stubIsAuthenticated = sinon.stub remoteAccess,
+                'isAuthenticated', (header, callback) ->
                     callback false

             done()

         after (done) ->
-            stubIsSharingAuthenticated.restore()
+            stubIsAuthenticated.restore()
             done()

@@ -293,7 +287,7 @@
             done()

-        it 'When a successful authorized request is made then 200 is returned',
+        it.skip 'When a successful authorized request is made then 200 is returned',
         (done) ->
             # since no error should be returned then there is no callback made
             sharing.revokeTarget req, res
@@ -415,17 +409,17 @@
             headers:
                 authorization: 'james-bond@mi6.cozy.uk:my_name_is_bond'

-        stubIsSharingAuthenticated = {}
+        stubIsAuthenticated = {}

         before (done) ->
-            stubIsSharingAuthenticated = sinon.stub remoteAccess,
-                'isSharingAuthenticated', (credentials, callback) ->
+            stubIsAuthenticated = sinon.stub remoteAccess,
+                'isAuthenticated', (credentials, callback) ->
                     callback false

             done()

         after (done) ->
-            stubIsSharingAuthenticated.restore()
+            stubIsAuthenticated.restore()
             done()


If areas are close enough, we see them as overlapping.

--- client/lib/annotator.coffee
+++ client/lib/annotator.coffee
@@ -1,6 +1,7 @@
-LINE_HEIGHT_THRESHOLD = 0.5 # To allow for small variations (half of the unit of difference)
+LINE_HEIGHT_THRESHOLD = 0.5 # To allow for small variations (in units)
 LARGEST_LINE_HEIGHT_WINDOW = 0.07 # Heuristic
 SPLIT_SECTION_THRESHOLD = 1.01 # To allow for small rounding errors
+FUZZY_OVERLAP_THRESHOLD = 2.5 # If areas are close enough, we see them as overlapping (in units)
 SECTION_INDENT_THRESHOLD = 1.0 / 40.0
 SPLIT_PARAGRAPH_THRESHOLD = 0.9

@@ -134,7 +135,8 @@
     area1bottom = area1.top + area1.height
     area2bottom = area2.top + area2.height

-    area1.left < area2right and area1right > area2.left and area1.top < area2bottom and area1bottom > area2.top
+    # We scale the threshold
+    area1.left - (FUZZY_OVERLAP_THRESHOLD * SCALE) < area2right and area1right + (FUZZY_OVERLAP_THRESHOLD * SCALE) > area2.left and area1.top - (FUZZY_OVERLAP_THRESHOLD * SCALE) < area2bottom and area1bottom + (FUZZY_OVERLAP_THRESHOLD * SCALE) > area2.top

   # TODO: We could cache bounding boxes and not recompute every time for the same section (of course we have to udpate it when we merge sections)
   _mergeOverlappingSections: (pageNumber, sections) =>


okay, cant do that much async work in parallel

--- scripts/create_previews.coffee
+++ scripts/create_previews.coffee
@@ -56,16 +56,15 @@
     dst: mkPath root, dstPathOnServer

   fileExists(resizeOpts.dst).then (exists) ->
-    magickSemaphore.push ->
-      if exists
-        getImageInfo(resizeOpts.dst).spread (existing) ->
-          process.stdout.write '-' unless quiet
-          existing
-      else
-        makeDirectories(path.dirname(resizeOpts.dst)).then ->
-          resizeImage(resizeOpts).spread (resized) ->
-            process.stdout.write '.' unless quiet
-            resized
+    if exists
+      getImageInfo(resizeOpts.dst).spread (existing) ->
+        process.stdout.write '-' unless quiet
+        existing
+    else
+      makeDirectories(path.dirname(resizeOpts.dst)).then ->
+        resizeImage(resizeOpts).spread (resized) ->
+          process.stdout.write '.' unless quiet
+          resized
   .then (imageInfo) ->
     medium =
       width: parseInt imageInfo.width
@@ -89,13 +88,14 @@
     Q.all album.pictures.map (picture) ->
       Q.all sizes.map (size) ->
         do (album, picture, size) ->
-          createPreview
-            albumPath: album.path
-            picture: picture
-            size: size
-            root: root
-            output: output
-            quiet: quiet
+          magickSemaphore.push ->
+            createPreview
+              albumPath: album.path
+              picture: picture
+              size: size
+              root: root
+              output: output
+              quiet: quiet

 if require.main is module
   argv = require('optimist')


Loosen matching rules on google visit

Loosen matching rules on google visit

Depending on the driver that is running the tests google redirects the
requests.

--- test/integration/steps/util.coffee
+++ test/integration/steps/util.coffee
@@ -11,4 +11,4 @@

   @Then /^the driver should get that URL$/, ->
     @driver.getCurrentUrl()
-    .should.eventually.eql("http://www.google.com/")
+    .should.eventually.contain("www.google.com")


Update default options to speed up bar on fast pages

--- pace.coffee
+++ pace.coffee
@@ -1,24 +1,25 @@
 defaultOptions =
   # How long should it take for the bar to animate to a new
   # point after receiving it
-  catchupTime: 500
+  catchupTime: 100

   # How quickly should the bar be moving before it has any progress
   # info from a new source in %/ms
   initialRate: .03

   # What is the minimum amount of time the bar should be on the
-  # screen
-  minTime: 500
+  # screen.  Irrespective of this number, the bar will always be on screen for
+  # 33 * (100 / maxProgressPerFrame) + ghostTime ms.
+  minTime: 250

   # What is the minimum amount of time the bar should sit after the last
   # update before disappearing
-  ghostTime: 500
+  ghostTime: 100

   # Its easy for a bunch of the bar to be eaten in the first few frames
   # before we know how much there is to load.  This limits how much of
   # the bar can be used per frame
-  maxProgressPerFrame: 10
+  maxProgressPerFrame: 20

   # This tweaks the animation easing
   easeFactor: 1.25


log number of updates for future debugging

--- services/document-updater/app/coffee/UpdateManager.coffee
+++ services/document-updater/app/coffee/UpdateManager.coffee
@@ -40,6 +40,7 @@
 	fetchAndApplyUpdates: (project_id, doc_id, callback = (error) ->) ->
 		RealTimeRedisManager.getPendingUpdatesForDoc doc_id, (error, updates) =>
 			return callback(error) if error?
+			logger.log {project_id: project_id, doc_id: doc_id, count: updates.length}, "processing updates"
 			if updates.length == 0
 				return callback()
 			async.eachSeries updates,


Fixed a bug with more than a page full of entities

--- sli/databrowser/app/assets/javascripts/entities.js.coffee
+++ sli/databrowser/app/assets/javascripts/entities.js.coffee
@@ -25,12 +25,3 @@
     else
       table.fnOpen(@, details.find('td.hidden').html(), "details")
       $(@).find("td:first-child.expandable").toggleClass("expanded")
-
-jQuery ->
-  if $('.pagination').length
-    $(window).scroll ->
-      url = $('div.pagination #paginate-next').attr('href')
-      if url && $(window).scrollTop() > $(document).height() - $(window).height() - 50
-        $('div.pagination').text("Fetching more results...")
-        $.getScript(url)
-    $(window).scroll()


add source mapping url comment to files with source maps

--- lib/compiler.coffee
+++ lib/compiler.coffee
@@ -204,6 +204,12 @@
    * override and there was a compile, otherwise any extensions are preserved as
    * is.
    *
+   * If there is a sourcemap for one of the files being written, two things need
+   * to happen. First, the sourcemap needs to be written with a .map extension.
+   * Second, the output file needs to get a source mapping url comment so that
+   * it knows where the sourcemap is. Both of these things happen as well in
+   * this method.
+   *
    * @param  {Object} obj - object with `path` and `content` properties
    * @return {Promise} a promise for the written file
   ###
@@ -223,6 +229,14 @@
       obj.path = new File(base: @roots.root, path: obj.path)

     obj.path = @roots.config.out(obj.path, obj.extension)
+
+    if @sourcemap and not obj.sourcemap?
+      if @out_ext is 'css'
+        obj.content = "#{obj.content}\n
+        /*# sourceMappingURL=#{path.basename(obj.path)}.map */"
+      if @out_ext is 'js'
+        obj.content = "#{obj.content}\n
+        //# sourceMappingURL=#{path.basename(obj.path)}.map"

     nodefn.call(mkdirp, path.dirname(obj.path))
       .then(-> nodefn.call(fs.writeFile, obj.path, obj.content))


Bump upload chunk size to 4MB

Bump upload chunk size to 4MB

--- app/assets/service/store.coffee
+++ app/assets/service/store.coffee
@@ -155,7 +155,7 @@
     @flowOptions =
       target: router.controllers.AssetApi.uploadChunk().url
       method: 'octet'
-      chunkSize: 1048576
+      chunkSize: 4194304
       simultaneousUploads: 3
       testChunks: false
       chunkRetryInterval: 5000


reduced spawn wait time

reduced spawn wait time

--- coffee/enemy_spawner.coffee
+++ coffee/enemy_spawner.coffee
@@ -7,7 +7,7 @@

   constructor: ->
     super()
-    @spawnTime_ = 1
+    @spawnTime_ = .9
     @timeUntilSpawn_ = 0

   update: (dt) ->
@@ -19,7 +19,6 @@
   spawn_: ->
     type = util.randElement ['enemy', 'bat', 'ogre', 'spectre', 'deathknight',
         'skeleton']
-    type = 'bat'
     enemy = EntityFactory.create type
     enemyPos = @getRandomBorderPos_ enemy.getRadius()
     enemy.setPos enemyPos


Fix regex when using the staging proxy server

--- coffee/installHelper.coffee
+++ coffee/installHelper.coffee
@@ -64,7 +64,7 @@

     ready ->
       if options.trigger is 'first-page-visit'
-        if location.href.match(/https:\/\/.+\.p\.eager\.works\//i)
+        if location.href.match(/https:\/\/.+\.(p|s)\.eager\.works\//i)
           tour.start()

         else if window.localStorage?.eagerShepherdHasRun isnt 'true'


util: re-ordered code.

util: re-ordered code.

--- coffee/src/util/util.coffee
+++ coffee/src/util/util.coffee
@@ -85,48 +85,6 @@
       css.attr 'href', src
       $('body').append css

-# converts human-readable timestring to seconds and back
-# human-readable format is: [[hh:]mm:]ss[.SSS]
-class util.Time
-  constructor: (time) ->
-    @time = @constructor.timestringToSeconds time
-
-  seconds: => @time
-  timestring: => @constructor.secondsToTimestring @time
-
-  @timestringToSeconds: (timestring) =>
-    timestring = String(timestring ? 0)
-
-    # handle subsec [.SSS]
-    [rest, subsec] = timestring.split '.'
-    subsec = parseFloat "0.#{subsec ? '0'}"
-
-    # handle [[hh:]mm:]ss
-    rest = rest.split(':').reverse()
-    [sec, min, hrs] = _.map [0, 1, 2], (n) -> parseInt(rest[n], 10) or 0
-
-    # convert to seconds
-    (hrs * 60 * 60) + (min * 60) + sec + subsec
-
-  @secondsToTimestring: (seconds) =>
-    sec = parseInt seconds, 10
-
-    hrs = parseInt sec / (60 * 60), 10
-    sec -= hrs * 60 * 60
-
-    min = parseInt sec / 60, 10
-    sec -= min * 60
-
-    subsec = seconds % 1
-    if subsec
-      subsec = Math.round(subsec * 1000) / 1000
-      subsec = String(subsec).substr 1, 4 # remove first 0
-      subsec = subsec.replace /0+$/, ''
-
-    hrs = if hrs == 0 then '' else "#{hrs}:"
-    pad = (n) -> if n < 10 then "0#{n}" else "#{n}"
-
-    "#{hrs}#{pad min}:#{pad sec}#{subsec or ''}"

 # Originally from StackOverflow
 # http://stackoverflow.com/questions/736513
@@ -163,6 +121,50 @@
       result[key + '_'] = val.toLowerCase()

   result
+
+
+# converts human-readable timestring to seconds and back
+# human-readable format is: [[hh:]mm:]ss[.SSS]
+class util.Time
+  constructor: (time) ->
+    @time = @constructor.timestringToSeconds time
+
+  seconds: => @time
+  timestring: => @constructor.secondsToTimestring @time
+
+  @timestringToSeconds: (timestring) =>
+    timestring = String(timestring ? 0)
+
+    # handle subsec [.SSS]
+    [rest, subsec] = timestring.split '.'
+    subsec = parseFloat "0.#{subsec ? '0'}"
+
+    # handle [[hh:]mm:]ss
+    rest = rest.split(':').reverse()
+    [sec, min, hrs] = _.map [0, 1, 2], (n) -> parseInt(rest[n], 10) or 0
+
+    # convert to seconds
+    (hrs * 60 * 60) + (min * 60) + sec + subsec
+
+  @secondsToTimestring: (seconds) =>
+    sec = parseInt seconds, 10
+
+    hrs = parseInt sec / (60 * 60), 10
+    sec -= hrs * 60 * 60
+
+    min = parseInt sec / 60, 10
+    sec -= min * 60
+
+    subsec = seconds % 1
+    if subsec
+      subsec = Math.round(subsec * 1000) / 1000
+      subsec = String(subsec).substr 1, 4 # remove first 0
+      subsec = subsec.replace /0+$/, ''
+
+    hrs = if hrs == 0 then '' else "#{hrs}:"
+    pad = (n) -> if n < 10 then "0#{n}" else "#{n}"
+
+    "#{hrs}#{pad min}:#{pad sec}#{subsec or ''}"

 # -- regular expressions


some cleanup

some cleanup

--- lib/eslogger.coffee
+++ lib/eslogger.coffee
@@ -12,21 +12,15 @@

   missingEnvironmentForApi: (msg) ->
     missingAnything = false
-    unless logESUrl?
+    unless @logESUrl?
       msg.send "Ensure that GANDI_LOG_ES_URL is set."
       missingAnything |= true
-    unless logRooms?
+    unless @logRooms?
       msg.send "Ensure that GANDI_LOG_ROOMS is set."
       missingAnything |= true
-    unless logIndexName?
+    unless @logIndexName?
       msg.send "Ensure that GANDI_LOG_INDEX_NAME is set."
       missingAnything |= true
-    unless logKibanaUrlName?
-      msg.send "Ensure that GANDI_LOG_KIBANA_URL is set."
-      missingAnything |= true
-    unless logKibanaTemplateName?
-      msg.send "Ensure that GANDI_LOG_KIBANA_TEMPLATE is set."
-      missingAnything |= true
     missingAnything

@@ -34,21 +28,21 @@
     process.env.HUBOT_BASE_URL + path.join(robot.name, 'logs', room.slice(1))

   logMessageES: (log, room, msg) ->
-    if !missingEnvironmentForApi(msg)
-      if room in logRooms
+    unless missingEnvironmentForApi(msg)
+      if room in @logRooms
         date = moment.utc()
         log['@timestamp'] = date.format()
         json = JSON.stringify(log)

-        index = logIndexName
+        index = @logIndexName
         index = index + '-' + date.format('YYYY.MM.DD')

-        msg.http(logESUrl)
+        @robot.http(@logESUrl)
           .path(index + '/irclog/')
           .post(json) (err, res, body) ->
             if res.statusCode > 299
-              robot.logger.warning res.statusCode
-              robot.logger.warning body
+              @robot.logger.warning res.statusCode
+              @robot.logger.warning body

@@ -81,17 +75,17 @@
       size: 1000
     }
     json = JSON.stringify(query)
-    index = logIndexName + '-' + start.format('YYYY.MM.DD')
-    index_end = logIndexName + '-' + stop.format('YYYY.MM.DD')
+    index = @logIndexName + '-' + start.format('YYYY.MM.DD')
+    index_end = @logIndexName + '-' + stop.format('YYYY.MM.DD')
     if index == index_end
-      searchES index, json, (body) ->
+      @searchES index, json, (body) ->
         cb body.hits.hits
     else
-      searchES index, json, (body) ->
-        searchES index_end, json, (body_end) ->
+      @searchES index, json, (body) ->
+        @searchES index_end, json, (body_end) ->
           cb body.hits.hits.concat(body_end.hits.hits)

-  getLastTerm = (room, term, cb) ->
+  getLastTerm: (room, term, cb) ->
     query = {
       query: {
         bool: {
@@ -123,12 +117,11 @@
     }
     json = JSON.stringify(query)
     # console.log json
-    searchAllES json, (body) ->
+    @searchAllES json, (body) ->
       cb body.hits.hits

-
-  searchAllES = (json, cb) ->
-    robot.http(logESUrl)
+  searchAllES: (json, cb) ->
+    @robot.http(@logESUrl)
       .path('/logstash-*/_search')
       .get(json) (err, res, body) ->
         switch res.statusCode
@@ -139,8 +132,8 @@
             json_body = null
         cb json_body

-  searchES = (index, json, cb) ->
-    robot.http(logESUrl)
+  searchES: (index, json, cb) ->
+    @robot.http(@logESUrl)
       .path(index + '/_search')
       .post(json) (err, res, body) ->
         switch res.statusCode
@@ -151,113 +144,11 @@
             json_body = null
         cb json_body

-
-
-  getLogs = (room, start, stop, cb) ->
-    # would be good to replace this with a filter, we don't need relevance
-    query = {
-      query: {
-        bool: {
-          must: [
-            {
-              range:
-                "@timestamp": {
-                  from: start,
-                  to: stop
-                }
-            },
-            {
-              match_phrase: {
-                room: room
-              }
-            }
-          ]
-        }
-      },
-      sort: {
-        "@timestamp": {
-          order: "asc"
-        }
-      },
-      size: 1000
-    }
-    json = JSON.stringify(query)
-    index = logIndexName + '-' + start.format('YYYY.MM.DD')
-    index_end = logIndexName + '-' + stop.format('YYYY.MM.DD')
-    if index == index_end
-      searchES index, json, (body) ->
-        cb body.hits.hits
-    else
-      searchES index, json, (body) ->
-        searchES index_end, json, (body_end) ->
-          cb body.hits.hits.concat(body_end.hits.hits)
-
-  getLastTerm = (room, term, cb) ->
-    query = {
-      query: {
-        bool: {
-          must: [
-            {
-              match_phrase: {
-                message: term
-              }
-            },
-            {
-              match_phrase: {
-                room: room
-              }
-            }
-          ],
-          must_not: {
-            match_phrase: {
-              message: '.recall '
-            }
-          }
-        }
-      },
-      sort: {
-        "@timestamp": {
-          order: "desc"
-        }
-      },
-      size: 1
-    }
-    json = JSON.stringify(query)
-    # console.log json
-    searchAllES json, (body) ->
-      cb body.hits.hits
-
-
-  searchAllES = (json, cb) ->
-    robot.http(logESUrl)
-      .path('/logstash-*/_search')
-      .get(json) (err, res, body) ->
-        switch res.statusCode
-          when 200 then json_body = JSON.parse(body)
-          else
-            console.log res.statusCode
-            console.log body
-            json_body = null
-        cb json_body
-
-  searchES = (index, json, cb) ->
-    robot.http(logESUrl)
-      .path(index + '/_search')
-      .post(json) (err, res, body) ->
-        switch res.statusCode
-          when 200 then json_body = JSON.parse(body)
-          else
-            console.log res.statusCode
-            console.log body
-            json_body = null
-        cb json_body
-
-
-  logContent = (room, lines, start, stop) ->
+  logContent: (room, lines, start, stop) ->
     time = moment().utc().format("HH:mm")
     start_date = start.format("MMM, ddd Do HH:mm")
     stop_date = stop.format("MMM, ddd Do HH:mm")
-    content = html_head("Irc Logs for #{room}")
+    content = @html_head("Irc Logs for #{room}")
     content += """

from #{start_date} to #{stop_date} - Times are UTC (now is #{time} UTC)

@@ -267,10 +158,10 @@
       time = moment(line._source["@timestamp"]).utc().format("HH:mm")
       content += "
#{time} #{escape line._source.nick}: #{escape line._source.message}
"
     content += "
"
-    content += foot_html(logKibanaUrlName + '/#/dashboard/file/' + logKibanaTemplateName+ '.json?room=' + room)
+    content += @foot_html()
     content

-  html_head = (title) ->
+  html_head: (title) ->
     """

@@ -291,10 +182,14 @@

#{title}

     """

-  foot_html = (url) ->
-    "
More Power on #{url}
"
-
-  escape = (message) ->
+  foot_html: ->
+    if @logKibanaUrlName?
+      url = logKibanaUrlName + '/#/dashboard/file/' + logKibanaTemplateName + '.json?room=' + room
+      "
More Power on #{url}
"
+    else
+      ""
+
+  escape: (message) ->
     return message.replace(/&/g, '&')
       .replace(/</g, '<')
       .replace(/>/g, '>')


Create new model for diff tool from context menu.

Split out the using of tree-view into a separate module that
calls the regular GitDiffTool function.

--- lib/git-plus.coffee
+++ lib/git-plus.coffee
@@ -136,7 +136,7 @@
     @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:cherry-pick', -> git.getRepo().then((repo) -> GitCherryPick(repo))
     @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:diff', -> git.getRepo().then((repo) -> GitDiff(repo, file: currentFile(repo)))
     @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:difftool', -> git.getRepo().then((repo) -> GitDifftool(repo, file: currentFile(repo)))
-    @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:difftool-context', -> git.getRepo().then((repo) -> GitDifftoolContext(repo, contextCommandMap))
+    @subscriptions.add atom.commands.add '.tree-view', 'git-plus:difftool-context', -> git.getRepo().then((repo) -> GitDifftoolContext(repo, contextCommandMap))
     @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:diff-all', -> git.getRepo().then((repo) -> GitDiffAll(repo))
     @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:fetch', -> git.getRepo().then((repo) -> GitFetch(repo))
     @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:fetch-prune', -> git.getRepo().then((repo) -> GitFetchPrune(repo))


Update build renderer structure

Update build renderer structure

--- grunt/amd_tamer.coffee
+++ grunt/amd_tamer.coffee
@@ -13,9 +13,9 @@
   graphics:
     files: [{
       expand: true
-      cwd: 'src/graphics/'
+      cwd: 'src/renderer/'
       src: '*.js'
       dest: 'dist/files/'
       rename: (dest, src) ->
-        "#{dest}/snowflake.graphics.#{src}"
+        "#{dest}/snowflake.renderer.#{src}"
     }]


Fix form-valid test

Fix form-valid test

--- app/partials/register-form.cjsx
+++ app/partials/register-form.cjsx
@@ -187,7 +187,7 @@
   isFormValid: ->
     {badLoginChars, loginConflict, passwordsDontMatch, emailConflict} = @state
     agreesToPrivacyPolicy = @refs.agreesToPrivacyPolicy?.getDOMNode().checked
-    badLoginChars?.length is 0 and loginConflict?.length is 0 and not passwordsDontMatch and not emailConflict and agreesToPrivacyPolicy
+    badLoginChars?.length is 0 and not loginConflict and not passwordsDontMatch and not emailConflict and agreesToPrivacyPolicy

   handleSubmit: ->
     login = @refs.login.getDOMNode().value


add (failing) tests for homepage view toggle

--- test/integration/home.coffee
+++ test/integration/home.coffee
@@ -17,9 +17,9 @@
       h.should.equal 'ScraperWiki'
       done()

-  it 'contains a list of my datasets', (done) ->
+  it 'shows my datasets as big rectangular tiles', (done) ->
     browser.waitForVisibleByCss '.dataset', 4000, ->
-      browser.elementsByCss '.dataset', (err, datasets) ->
+      browser.elementsByCss '.dataset.tile', (err, datasets) ->
         should.exist datasets
         datasets.length.should.be.above 0
         done()
@@ -41,6 +41,57 @@
       text.split('Prune')[1].should.include('Apricot')
       done()

+  it 'it lets me switch to a more information-dense list view', (done) ->
+    browser.elementsByCss '#tile-view, #list-view', (err, elements) ->
+      should.exist elements
+      elements.length.should.equal 2
+      done()
+
+  it 'it shows that the tile view is currently active', (done) ->
+    browser.elementsByCss '#tile-view.active', (err, elements) ->
+      should.exist elements
+      elements.length.should.equal 1
+      done()
+
+  context 'when I click the list view button', (done) ->
+    before (done) ->
+      wd40.click '#list-view', done
+
+    it 'it shows my datasets in a list', (done) ->
+      wd40.elementByCss 'table.dataset-list', (err, element) ->
+        should.exist element
+        browser.elementsByCss 'table.dataset-list tbody tr', (err, elements) ->
+          elements.length.should.equal 2
+          done()
+
+    it 'each dataset has an icon, a name, owner, date created and a status', (done) ->
+      browser.elementsByCss 'table.dataset-list td.icon', (err, elements) ->
+        elements.length.should.equal 2
+        browser.elementsByCss 'table.dataset-list td.name', (err, elements) ->
+          elements.length.should.equal 2
+          browser.elementsByCss 'table.dataset-list td.owner', (err, elements) ->
+            elements.length.should.equal 2
+            browser.elementsByCss 'table.dataset-list td.created', (err, elements) ->
+              elements.length.should.equal 2
+              browser.elementsByCss 'table.dataset-list td.status', (err, elements) ->
+                elements.length.should.equal 2
+                done()
+
+    it 'the list is ordered by date created, newest at the top', (done) ->
+      wd40.getText 'table.dataset-list', (err, text) ->
+        text.split('Prune')[1].should.include('Apricot')
+        done()
+
+  context 'when I reload the page', ->
+
+    before (done) ->
+      browser.refresh(done)
+
+    it 'it still shows my datasets in a list', (done) ->
+      wd40.elementByCss 'table.dataset-list', (err, element) ->
+        should.exist element
+        done()
+
   context 'when I click the "new dataset" button', ->
     before (done) ->
       wd40.click '.new-dataset', done


more coverage for feeds

more coverage for feeds

--- test/phabs_feeds_test.coffee
+++ test/phabs_feeds_test.coffee
@@ -87,6 +87,59 @@
         .catch (e) ->
           expect(e).to.eql 'no room to announce in'

+
+  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+  context 'it is a task but there is a problem contacting phabricator', ->
+    beforeEach ->
+      room.robot.brain.data.phabricator.projects = {
+        'Bug Report': {
+          phid: 'PHID-PROJ-qhmexneudkt62wc7o3z4',
+          feeds: [
+            'room1'
+          ]
+        },
+        'project with phid': { phid: 'PHID-PROJ-1234567' },
+      }
+      room.robot.brain.data.phabricator.aliases = {
+        bugs: 'Bug Report',
+        bug: 'Bug Report'
+      }
+      nock(process.env.PHABRICATOR_URL)
+        .get('/api/maniphest.search')
+        .query({
+          'constraints[phids][0]': 'PHID-TASK-sx2g66opn67h4yfl7wk6',
+          'attachments[projects]': '1',
+          'api.token': 'xxx'
+        })
+        .reply(500, { error: { code: 500, message: 'oops' } })
+
+      @postData = '{
+        "storyID": "7297",
+        "storyType": "PhabricatorApplicationTransactionFeedStory",
+        "storyData": {
+          "objectPHID": "PHID-TASK-sx2g66opn67h4yfl7wk6",
+          "transactionPHIDs": {
+            "PHID-XACT-TASK-fkyairn5ltzbzkj": "PHID-XACT-TASK-fkyairn5ltzbzkj",
+            "PHID-XACT-TASK-dh5r5rtwa5hpfia": "PHID-XACT-TASK-dh5r5rtwa5hpfia"
+          }
+        },
+        "storyAuthorPHID": "PHID-USER-qzoqvowxnb5k5screlji",
+        "storyText": "mose triaged T2569: setup webhooks as High priority.",
+        "epoch": "1469085410"
+      }'
+
+    afterEach ->
+      room.robot.brain.data.phabricator = { }
+      room.destroy()
+
+    it 'should not react', ->
+      expected = { }
+      phab = new Phabricator room.robot, process.env
+      phab.getFeed(JSON.parse(@postData))
+        .then (announce) ->
+          expect(announce).to.eql expected
+        .catch (e) ->
+          expect(e).to.eql 'http error 500'

   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   context 'it is a task but is not in any feed', ->


TEST 100% Neck.Helper coverage

TEST 100% Neck.Helper coverage

--- test/spec/neck/helper.coffee
+++ test/spec/neck/helper.coffee
@@ -7,7 +7,7 @@

   describe "Accessors", ->

-    it "should create main accesor", ->
+    it "should create main accessor", ->
       callback = sinon.spy()

       class Helper extends Neck.Helper
@@ -31,123 +31,229 @@
       assert.equal callback.args[1][0], 'one'
       assert.equal callback.args[1][1], 'text'

-    describe "parsing evaluate", ->
-
-      it "should return properties in scope context", ->
-        h = new Neck.Helper el: container, mainAttr: '', parent: new Neck.Controller
-
-        pairs = [
-          {
-            in: "true;false + this.something('asd').else"
-            out: "true;false + this.something('asd').else"
-            listeners: []
-            triggers: []
-          }
-          {
-            in: "one.two.three"
-            out: "scope.one.two.three"
-            listeners: [
-              "one"
-              "one.two"
-              "one.two.three"
-            ]
-            triggers: [
-              "one.two.three"
-            ]
-          }
-          {
-            in: "some + other['something().asd'].other() + 'dsa'"
-            out: "scope.some + scope.other['something().asd'].other() + 'dsa'"
-            listeners: [
-              "some"
-              "other"
-              "other['something().asd']"
-              "other['something().asd'].other"
-            ]
-            triggers: [
-              "some"
-              "other['something().asd'].other"
-            ]
-          }
-          {
-            in: "{property: some == 'asd'}"
-            out: "{property: scope.some == 'asd'}"
-            listeners: ["some"]
-            triggers: ["some"]
-          }
-          {
-            in: "{  property  : some == 'asd' }"
-            out: "{  property  : scope.some == 'asd' }"
-            listeners: ["some"]
-            triggers: ["some"]
-          }
-          {
-            in: "some[other + else[elseTwo]].method(true)"
-            out: "scope.some[scope.other + scope.else[scope.elseTwo]].method(true)"
-            listeners: ["some", "some[other + else[elseTwo]]", "some[other + else[elseTwo]].method", "other", "else", "else[elseTwo]", "elseTwo"]
-            triggers: ["some[other + else[elseTwo]].method", "other", "else[elseTwo]", "elseTwo"]
-          }
-          {
-            in: "some ? 'asd' : otherMethod()"
-            out: "scope.some ? 'asd' : scope.otherMethod()"
-            listeners: ['some', 'otherMethod']
-            triggers: ['some', 'otherMethod']
-          }
-          {
-            in: "some ? one.two : otherMethod()"
-            out: "scope.some ? scope.one.two : scope.otherMethod()"
-            listeners: ['some', 'one', 'one.two', 'otherMethod']
-            triggers: ['some', 'one.two', 'otherMethod']
-          }
-          {
-            in: "@controllerMethod()"
-            out: "scope._context.controllerMethod()"
-            listeners: []
-            triggers: []
-          }
-        ]
-
-        for pair in pairs
-          listeners = []
-          triggers = []
+    it "should be related to controller scope properties", ->
+      c = new Neck.Controller
+      h = new Neck.Helper parent: c
+
+      c.scope.property = "text"
+      h._setAccessor "test", "property"
+
+      assert.equal h.scope.test, "text"
+
+    it "should listen to controller scope property changes",->
+      c = new Neck.Controller
+      h = new Neck.Helper parent: c
+
+      c.scope.property = "text"
+      h._setAccessor "test", "property"
+
+      callback = sinon.spy()
+
+      h.watch 'test', callback, false
+      c.scope.property = "new text"
+
+      assert.ok callback.calledWith "new text"
+
+    it "should listen to nested accesor from other helper", ->
+      c = new Neck.Controller()
+      c.scope.property = "text"
+      h = new Neck.Helper parent: c
+      h._setAccessor 'someAccesor', "property"
+
+      child = new Neck.Helper parent: h
+      child._setAccessor 'otherAccesor', "someAccesor"
+
+      assert.equal child.scope.otherAccesor, "text"
+      callback = sinon.spy()
+      child.watch 'otherAccesor', callback, false
+
+      c.scope.property = "new text"
+      assert.ok callback.calledWith "new text"
+
+    it "should work with deep properties of controller scope", ->
+      c = new Neck.Controller()
+      c.scope.one = {two: three: 'text'}
+      h = new Neck.Helper parent: c
+
+      h._setAccessor 'some', 'one.two.three'
+      assert.equal h.scope.some, 'text'
+
+    it "should work with not initialized properties", ->
+      c = new Neck.Controller()
+      h = new Neck.Helper parent: c
+
+      h._setAccessor 'some', 'one.two.three'
+      assert.equal h.scope.some, undefined
+      c.scope.one.two.three = 'text'
+      assert.equal h.scope.some, 'text'
+
+    it "should listen to proper changes", ->
+      c = new Neck.Controller()
+      h = new Neck.Helper parent: c
+
+      h._setAccessor 'some', 'one + two + one.two.three'
+      callback = sinon.spy()
+      h.watch 'some', callback, false
+
+      c.trigger 'refresh:one'
+      assert.equal callback.callCount, 1
+      c.trigger 'refresh:two'
+      assert.equal callback.callCount, 2
+      c.trigger 'refresh:one.two'
+      assert.equal callback.callCount, 3
+      c.trigger 'refresh:one.two.three'
+      assert.equal callback.callCount, 4
+
+    it "should apply changes to properties setting accessor value", ->
+      c = new Neck.Controller()
+      h = new Neck.Helper parent: c
+
+      h._setAccessor 'some', 'one.two.three'
+      h._setAccessor 'other', 'one'
+      callback = sinon.spy()
+      c.watch 'one', callback, false
+
+      h.scope.some = 'text'
+      assert.equal callback.callCount, 0
+
+      h.scope.other = 'new one'
+      assert.ok callback.calledWith 'new one'
+
+    it "should not change value of statement, but create strict value", ->
+      c = new Neck.Controller()
+      c.scope.one = 'one'
+      c.scope.two = 'two'
+      h = new Neck.Helper parent: c
+
+      h._setAccessor 'some', 'one + two'
+      h.scope.some = 'new some'
+
+      assert.equal c.scope.one, 'one'
+      assert.equal c.scope.two, 'two'
+      assert.equal h.scope.some, 'new some'
+
+    it "parsing evaluate should return properties in scope context", ->
+      h = new Neck.Helper el: container, mainAttr: '', parent: new Neck.Controller
+
+      pairs = [
+        {
+          in: "true;false + this.something('asd').else"
+          out: "true;false + this.something('asd').else"
+          listeners: []
+          triggers: []
+        }
+        {
+          in: "one.two.three"
+          out: "scope.one.two.three"
+          listeners: [
+            "one"
+            "one.two"
+            "one.two.three"
+          ]
+          triggers: [
+            "one.two.three"
+          ]
+        }
+        {
+          in: "some + other['something().asd'].other() + 'dsa'"
+          out: "scope.some + scope.other['something().asd'].other() + 'dsa'"
+          listeners: [
+            "some"
+            "other"
+            "other['something().asd']"
+            "other['something().asd'].other"
+          ]
+          triggers: [
+            "some"
+            "other['something().asd'].other"
+          ]
+        }
+        {
+          in: "{property: some == 'asd'}"
+          out: "{property: scope.some == 'asd'}"
+          listeners: ["some"]
+          triggers: ["some"]
+        }
+        {
+          in: "{  property  : some == 'asd' }"
+          out: "{  property  : scope.some == 'asd' }"
+          listeners: ["some"]
+          triggers: ["some"]
+        }
+        {
+          in: "some[other + else[elseTwo]].method(true)"
+          out: "scope.some[scope.other + scope.else[scope.elseTwo]].method(true)"
+          listeners: ["some", "some[other + else[elseTwo]]", "some[other + else[elseTwo]].method", "other", "else", "else[elseTwo]", "elseTwo"]
+          triggers: ["some[other + else[elseTwo]].method", "other", "else[elseTwo]", "elseTwo"]
+        }
+        {
+          in: "some ? 'asd' : otherMethod()"
+          out: "scope.some ? 'asd' : scope.otherMethod()"
+          listeners: ['some', 'otherMethod']
+          triggers: ['some', 'otherMethod']
+        }
+        {
+          in: "some ? one.two : otherMethod()"
+          out: "scope.some ? scope.one.two : scope.otherMethod()"
+          listeners: ['some', 'one', 'one.two', 'otherMethod']
+          triggers: ['some', 'one.two', 'otherMethod']
+        }
+        {
+          in: "@controllerMethod()"
+          out: "scope._context.controllerMethod()"
+          listeners: []
+          triggers: []
+        }
+      ]
+
+      for pair in pairs
+        listeners = []
+        triggers = []
+
+        out = h._parseEvaluate(pair.in, listeners, triggers)
+
+        assert.equal out, pair.out
+        assert.deepEqual listeners, pair.listeners
+        assert.deepEqual triggers, pair.triggers

-          out = h._parseEvaluate(pair.in, listeners, triggers)
-
-          assert.equal out, pair.out
-          assert.deepEqual listeners, pair.listeners
-          assert.deepEqual triggers, pair.triggers
-
-
-    describe "helper methods", ->
-
-      it "should create property chain", ->
-        text = "one.two.three"
-        chain = Neck.Helper.prototype._propertyChain text
-        assert.deepEqual chain, ["one", "one.two", "one.two.three"]
-
-        text = "one['asd'].two.three"
-        chain = Neck.Helper.prototype._propertyChain text
-        assert.deepEqual chain, ["one", "one['asd']", "one['asd'].two", "one['asd'].two.three"]
-
-      it "should chreate object chain", ->
-        c = new Neck.Controller()
-        Neck.Helper.prototype._createObjectChain c.scope, "one.two.three"
-        assert.deepEqual c.scope.one, two: three: undefined
-
-        # Don't change values set already
-        c = new Neck.Controller()
-        c.scope.one = two: three: 'asd'
-        Neck.Helper.prototype._createObjectChain c.scope, "one.two.three"
-        assert.deepEqual c.scope.one, two: three: 'asd'
-
-        # Throw error for chaining not set array
-        c = new Neck.Controller()
-        assert.throw ->
-          Neck.Helper.prototype._createObjectChain c.scope, "one['asd'].two.three"
-
-        # Return for chaining set array
-        c = new Neck.Controller()
-        c.scope.one = []
-        Neck.Helper.prototype._createObjectChain c.scope, "one[1].two.three"
-        assert.deepEqual c.scope.one, []
+
+    it "should create property chain", ->
+      text = "one.two.three"
+      chain = Neck.Helper.prototype._propertyChain text
+      assert.deepEqual chain, ["one", "one.two", "one.two.three"]
+
+      text = "one['asd'].two.three"
+      chain = Neck.Helper.prototype._propertyChain text
+      assert.deepEqual chain, ["one", "one['asd']", "one['asd'].two", "one['asd'].two.three"]
+
+    it "should chreate object chain", ->
+      c = new Neck.Controller()
+      Neck.Helper.prototype._createObjectChain c.scope, "one.two.three"
+      assert.deepEqual c.scope.one, two: three: undefined
+
+      # Don't change values set already
+      c = new Neck.Controller()
+      c.scope.one = two: three: 'asd'
+      Neck.Helper.prototype._createObjectChain c.scope, "one.two.three"
+      assert.deepEqual c.scope.one, two: three: 'asd'
+
+      # Throw error for chaining not set array
+      c = new Neck.Controller()
+      assert.throw ->
+        Neck.Helper.prototype._createObjectChain c.scope, "one['asd'].two.three"
+
+      # Return for chaining set array
+      c = new Neck.Controller()
+      c.scope.one = []
+      Neck.Helper.prototype._createObjectChain c.scope, "one[1].two.three"
+      assert.deepEqual c.scope.one, []
+
+    it "should watch and apply on own scope properties", ->
+      h = new Neck.Helper parent: new Neck.Controller()
+      callback = sinon.spy()
+      h.watch 'some', callback, false
+      h.scope.some = "test"
+      assert.ok callback.calledWith "test"
+
+


Better recipients address interpretation

--- app/src/models/operation.coffee
+++ app/src/models/operation.coffee
@@ -30,6 +30,9 @@

   @_createOperationFromTransaction: (uid, type, tx, value, account) ->
     try
+      recipients = _(tx.outputs).chain().filter((o) -> !_(o.nodes).some((n) -> n?[1] is 1)).map((o) -> o.addresses).flatten().value()
+      if recipients?.length is 0
+        recipients = _(tx.outputs).chain().map((o) -> o.addresses).flatten().value()
       @findOrCreate(uid: uid)
         .set 'hash', tx['hash']
         .set 'fees', tx['fees']
@@ -38,7 +41,7 @@
         .set 'value', value.toString()
         .set 'confirmations', tx['confirmations']
         .set 'senders', _(tx.inputs).chain().map((i) -> i.addresses).flatten().value()
-        .set 'recipients', _(tx.outputs).chain().filter((o) -> !_(o.nodes).some((n) -> n?[1] is 1)).map((o) -> o.addresses).flatten().value()
+        .set 'recipients', recipients
         .set 'inputs_hash', (input.output_hash for input in tx.inputs)
         .set 'inputs_index', (input.output_index for input in tx.inputs)
         .set 'inputs_value', (input.value for input in tx.inputs)


Turn off optimistic UI in message updates to avoid flicker

Unfortunately this seems like the best option.
Also avoids false sense of updates in disconnected state.

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -737,6 +737,9 @@
 ## The following should be called directly only on the server;
 ## clients should use the corresponding method.
 _messageUpdate = (id, message, authors = null, old = null) ->
+  ## Turn off optimistic UI to avoid flicker and false sense of updates :-(
+  return unless Meteor.isServer
+
   ## Compare with 'old' if provided (in cases when it's already been
   ## fetched by the server); otherwise, load id from Messages.
   old = Messages.findOne id unless old?
@@ -782,33 +785,36 @@
       break
   return unless difference

-  now = new Date
-  if message.published == true
-    message.published = now
-  message.updated = now
-  message.updators = authors
-  diff = _.clone message
-  for author in authors
-    message["authors.#{escapeUser author}"] = now
+  ## Don't simulate changes involving date, which will be invalidated by server
+  if Meteor.isServer
+    now = new Date
+    if message.published == true
+      message.published = now
+    message.updated = now
+    message.updators = authors
+    diff = _.clone message
+    for author in authors
+      message["authors.#{escapeUser author}"] = now
   Messages.update id,
     $set: message
-  diff.id = id
-  diff.finished = authors if finished
-  diffid = MessagesDiff.insert diff
-  diff._id = diffid
-  #_submessagesChanged old.root ? id
-  ## In this special case, we can efficiently simulate the behavior of
-  ## _submessagesChanged via a direct update to the root:
-  if Meteor.isServer and (not old.root? or _consideredSubmessage message, old)
-    rootUpdate = $max: submessageLastUpdate: message.updated
-    if old.root? and not _consideredSubmessage old
-      rootUpdate.$inc = submessageCount: 1  ## considered a new submessage
-    Messages.update (old.root ? id), rootUpdate
-  else if _consideredSubmessage old
-    ## If this message is no longer considered a submessage, we need to
-    ## recompute from scratch in order to find the new last update.
-    _submessagesChanged old.root ? id
-  notifyMessageUpdate message, old if Meteor.isServer  ## client in simulation
+  if Meteor.isServer
+    diff.id = id
+    diff.finished = authors if finished
+    diffid = MessagesDiff.insert diff
+    diff._id = diffid
+    #_submessagesChanged old.root ? id
+    if not old.root? or _consideredSubmessage message, old
+      ## In this special case, we can efficiently simulate the behavior of
+      ## _submessagesChanged via a direct update to the root:
+      rootUpdate = $max: submessageLastUpdate: message.updated
+      if old.root? and not _consideredSubmessage old
+        rootUpdate.$inc = submessageCount: 1  ## considered a new submessage
+      Messages.update (old.root ? id), rootUpdate
+    else if _consideredSubmessage old
+      ## If this message is no longer considered a submessage, we need to
+      ## recompute from scratch in order to find the new last update.
+      _submessagesChanged old.root ? id
+    notifyMessageUpdate message, old
   diffid

 _messageAddChild = (child, parent, position = null) ->


make intension of staging rsync clear

make intension of staging rsync clear

--- app/templates/_Gruntfile.coffee
+++ app/templates/_Gruntfile.coffee
@@ -166,6 +166,8 @@
 					syncDestIgnoreExcl: true

 			# staging dry-run down
+			#
+			# only sync files that have been uploaded
 			stagingdowndry:
 				options:
 					args: ["--dry-run", "--verbose"]
@@ -174,6 +176,8 @@
 					syncDestIgnoreExcl: true

 			# sync staging to local
+			#
+			# only sync files that have been uploaded
 			stagingdown:
 				options:
 					src: "<%= pkg.domain.username %>@<%= pkg.domain.name %>:public_html/staging.<%= pkg.domain.name %>/site/assets/files/"


chore(tests): extend specs for routes

chore(tests): extend specs for routes

--- test/unit/routes_spec.coffee
+++ test/unit/routes_spec.coffee
@@ -1,37 +1,50 @@
 describe "Application routes", ->
+
+  # stub `Products` service
+  beforeEach module "myApp.resources", ($provide) ->
+    Products = sinon.stub(query: angular.noop)
+
+    products = [{ id: 1, name: "foo" }, { id: 2, name: "bar" }]
+    Products.query.returns $promise: then: (callback) -> callback(products)
+
+    $provide.value "Products", Products
+
+    return
+
   beforeEach module "myApp"

-  beforeEach inject ($location, $rootScope) ->
-    @navigateTo = (path) ->
-      $rootScope.$apply ->
-        $location.path(path)
+  navigateTo = (path) ->
+    # for some reason `$route` has to be injected here
+    beforeEach inject ($location, $route, $rootScope) ->
+      $rootScope.$apply -> $location.path(path)

-  # TODO mock `Products` service
-  # TODO it "resolves products"
-  it "recognizes '/'", inject ($route, Products) ->
-    # Given
-    stub = sinon.stub(Products, "query")
-    stub.returns $promise: then: (callback) -> callback([{ id: 1, name: "foo" }, { id: 2, name: "bar" }])
+  describe "route `/`", ->
+    navigateTo "/"

-    # When
-    @navigateTo "/"
+    it "is recognized", inject ($route) ->
+      expect($route.current.templateUrl).to.equal "templates/views/main.html"
+      expect($route.current.controller).to.equal "MainCtrl as main"
+      expect($route.current.resolve.products).to.not.be.undefined

-    # Then
-    expect(stub).to.be.called
+    it "queries for products", inject (Products) ->
+      expect(Products.query).to.be.called

-    expect($route.current.templateUrl).to.equal "templates/views/main.html"
-    expect($route.current.controller).to.equal "MainCtrl as main"
-    expect($route.current.resolve.products).to.not.be.undefined
+    it "loads products", inject ($route) ->
+      products = $route.current.locals.products
+      expect(products).to.have.length 2
+      expect(products).to.satisfy (collection) -> _.findWhere(collection, id: 1)
+      expect(products).to.satisfy (collection) -> _.findWhere(collection, id: 2)

-    products = $route.current.locals.products
-    expect(products).to.have.length 2
+  describe "route `/other`", ->
+    navigateTo "/other"

-  it "recognizes '/other'", inject ($route) ->
-    @navigateTo "/other"
-    expect($route.current.templateUrl).to.equal "templates/views/other.html"
-    expect($route.current.controller).to.equal "OtherCtrl as other"
+    it "is recognized", inject ($route) ->
+      expect($route.current.templateUrl).to.equal "templates/views/other.html"
+      expect($route.current.controller).to.equal "OtherCtrl as other"

-  it "recognizes '/tasks'", inject ($route) ->
-    @navigateTo "/tasks"
-    expect($route.current.templateUrl).to.equal "templates/views/tasks.html"
-    expect($route.current.controller).to.equal "TasksCtrl as tasks"
+  describe "route `/tasks`", ->
+    navigateTo "/tasks"
+
+    it "is recognized", inject ($route) ->
+      expect($route.current.templateUrl).to.equal "templates/views/tasks.html"
+      expect($route.current.controller).to.equal "TasksCtrl as tasks"


Cleanup

Cleanup

--- packages/shrub-orm/client/index.coffee
+++ packages/shrub-orm/client/index.coffee
@@ -101,7 +101,6 @@
 		waterline.loadCollection Collection

 	waterline.initialize config, (error, data) ->
-
 		return reject error if error?

 		collections = data.collections


Add more helpers

Add more helpers

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -90,7 +90,7 @@
     for attribute, options of @completions.attributes
       completions.push({word: attribute, prefix: ''}) if options.global

-    tagAttributes = @completions.tags[@getPreviousTag(editor, cursor)]?.attributes ? []
+    tagAttributes = @getTagAttributes(editor, cursor)
     for attribute in tagAttributes
       completions.push({word: attribute, prefix: ''})

@@ -102,7 +102,7 @@
     for attribute, options of @completions.attributes when attribute.indexOf(prefix) is 0
       completions.push({word: attribute, prefix}) if options.global

-    tagAttributes = @completions.tags[@getPreviousTag(editor, cursor)]?.attributes ? []
+    tagAttributes = @getTagAttributes(editor, cursor)
     for attribute in tagAttributes when attribute.indexOf(prefix) is 0
       completions.push({word: attribute, prefix})

@@ -110,16 +110,16 @@

   getAllAttributeValueCompletions: ({editor, cursor}) ->
     completions = []
-    attribute = @completions.attributes[@getPreviousAttribute(editor, cursor)]
-    for option in attribute?.attribOption ? []
-      completions.push({word: option, prefix: ''})
+    values = @getAttributeValues(editor, cursor)
+    for value in values
+      completions.push({word: value, prefix: ''})
     completions

   getAttributeValueCompletions: ({editor, cursor, prefix}) ->
     completions = []
-    attribute = @completions.attributes[@getPreviousAttribute(editor, cursor)]
-    for option in attribute?.attribOption ? [] when option.indexOf(prefix) is 0
-      completions.push({word: option, prefix})
+    values = @getAttributeValues(editor, cursor)
+    for value in values when value.indexOf(prefix) is 0
+      completions.push({word: value, prefix})
     completions

   loadCompletions: ->
@@ -146,3 +146,11 @@
     line = line.substring(0, quoteIndex)

     attributePattern.exec(line)?[1]
+
+  getAttributeValues: (editor, cursor) ->
+    attribute = @completions.attributes[@getPreviousAttribute(editor, cursor)]
+    attribute?.attribOption ? []
+
+  getTagAttributes: (editor, cursor) ->
+    tag = @getPreviousTag(editor, cursor)
+    @completions.tags[tag]?.attributes ? []


Add support for embedded scss and sass

Add support for embedded scss and sass

Fixes #2

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -1,5 +1,11 @@
 module.exports =
-  selector: ['.source.css.scss', '.source.sass', '.text.html.basic'] # .text.html.basic is a temporary hack to support inline
+  selector: [
+    '.source.css.scss',
+    '.source.sass',
+    '.source.css.embedded.html', # .source.css.embedded.html is a temporary hack to support inline
+    '.source.sass.embedded.html',
+    '.source.css.scss.embedded.html'
+  ]
   id: 'aligner-scss' # package name
   config:
     ':-enabled':


for/of bug

for/of bug

--- components/SetupPeer.coffee
+++ components/SetupPeer.coffee
@@ -84,7 +84,7 @@
         return
       # TODO if group defined, only send to that peer
       sentCount = 0
-      for conn, id of c.connections
+      for own id, conn of c.connections
         if conn.type is 'data' and conn.open
           conn.send payload
           sentCount++


Warn people who try to run the headless client, but webworkers didn't install.

--- headless_client.coffee
+++ headless_client.coffee
@@ -45,7 +45,17 @@
 GLOBAL.window = GLOBAL
 GLOBAL.document = location: pathname: 'headless_client'
 GLOBAL.console.debug = console.log
-GLOBAL.Worker = require('webworker-threads').Worker
+try
+  GLOBAL.Worker = require('webworker-threads').Worker
+catch
+  console.log ""
+  console.log "Headless client needs the webworker-threads package from NPM to function."
+  console.log "Try installing it with the command:"
+  console.log ""
+  console.log "    npm install webworker-threads"
+  console.log ""
+  process.exit(1)
+
 Worker::removeEventListener = (what) ->
   if what is 'message'
     @onmessage = -> #This webworker api has only one event listener at a time.


Add language info

Add language info

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -49,6 +49,7 @@
       params =
         t: 'appview'
         cd: @viewNameForPaneItem(item)
+        dt: item.getGrammar?().name
       @send(params)

     @send: (params) ->


Prevent double attachment issue

Prevent double attachment issue

--- apps/artwork/client/view.coffee
+++ apps/artwork/client/view.coffee
@@ -66,9 +66,9 @@

     @listenTo @artwork, 'change:sale_message', @renderDetail
     @listenTo @artwork, 'change:ecommerce', @renderDetail
-    @listenTo @artwork.related().sales, 'sync', @handleSales
-    @listenTo @artwork.related().fairs, 'sync', @handleFairs
-    @listenTo @artwork.related().shows, 'sync', @handleShows
+    @listenToOnce @artwork.related().sales, 'sync', @handleSales
+    @listenToOnce @artwork.related().fairs, 'sync', @handleFairs
+    @listenToOnce @artwork.related().shows, 'sync', @handleShows

     $.when.apply(null, relatedContentFetches).done =>
       relatedCollections = _.values _.pick(@artwork.related(), ['sales', 'features', 'fairs', 'shows'])


debounce probably doesn't need to be so long since it's all triggered by component mounting?

--- resources/assets/coffee/_classes/sticky-header.coffee
+++ resources/assets/coffee/_classes/sticky-header.coffee
@@ -7,7 +7,7 @@
 # 2. Add 'js-sticky-header' class to a marker element that should cause the sticky to show.
 class @StickyHeader
   constructor: ->
-    @debouncedOnScroll = _.debounce @onScroll, 100
+    @debouncedOnScroll = _.debounce @onScroll, 20
     @header = document.getElementsByClassName('js-pinned-header')
     @marker = document.getElementsByClassName('js-sticky-header')
     @pinnedSticky = document.getElementsByClassName('js-pinned-header-sticky')
@@ -36,6 +36,7 @@

   onScroll: =>
+    console.log('scroll')
     @pin()
     @stickOrUnstick()


:lipstick: Refactor test for item path and base name call

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -47,10 +47,8 @@
     return if @updatingDataAttributes
     @updatingDataAttributes = true

-    if @item.getPath?
-      itemPath = @item.getPath()
-      name = path.basename(itemPath)
-      @title.attr('data-name', name)
+    if itemPath = @item.getPath?()
+      @title.attr('data-name', path.basename(itemPath))
       @title.attr('data-path', itemPath)

     @updatingDataAttributes = false


make coors monospace, and no SVG entities

--- httpdd.fsm.html.coffee
+++ httpdd.fsm.html.coffee
@@ -23,6 +23,7 @@
 fontSize = gridMultiplier * .5
 # http://www.w3schools.com/cssref/css_websafe_fonts.asp
 fontFamily = '"Trebuchet MS", Helvetica, sans-serif'
+fontFamilyMonospace = '"Lucida Console", Monaco, monospace'
 colorGrid = '#EEEEEE'
 colorCoord = '#AAAAAA'
 colorStatusCodeText = '#000000'
@@ -78,7 +79,7 @@
             text: String.fromCharCode("A".charCodeAt(0) + i - 1)
             transform: "translate(#{-fontSize / 2}, 0)"
             'font-size': fontSize
-            'font-family': fontFamily
+            'font-family': fontFamilyMonospace
             'font-weight': 'bold'
             fill: colorCoord
       }
@@ -94,7 +95,7 @@
             text: String.fromCharCode("A".charCodeAt(0) + i - 1)
             transform: "translate(#{-fontSize / 2}, #{-fontSize})"
             'font-size': fontSize
-            'font-family': fontFamily
+            'font-family': fontFamilyMonospace
             'font-weight': 'bold'
             fill: colorCoord
       }
@@ -109,10 +110,10 @@
           height: 'auto'
         attrs:
           text:
-            text: if i > 9 then '' + i else ' ' + i
+            text: if i > 9 then '' + i else '0' + i
             transform: "translate(0, #{-fontSize / 2})"
             'font-size': fontSize
-            'font-family': fontFamily
+            'font-family': fontFamilyMonospace
             'font-weight': 'bold'
             fill: colorCoord
       }
@@ -125,10 +126,10 @@
           height: 'auto'
         attrs:
           text:
-            text: if i > 9 then '' + i else ' ' + i
+            text: if i > 9 then '' + i else '0' + i
             transform: "translate(#{-fontSize}, #{-fontSize / 2})"
             'font-size': fontSize
-            'font-family': fontFamily
+            'font-family': fontFamilyMonospace
             'font-weight': 'bold'
             fill: colorCoord
       }
@@ -479,8 +480,8 @@
       v.next_state = declarations.state[v.next_state]

     transitions[k] = addArrow v
-
   render()
+

   # FOREWORD
   $('#title').text vars.Title


Suppress log

Suppress log

--- lib/util/git.coffee
+++ lib/util/git.coffee
@@ -145,7 +145,6 @@
   logcb "> git #{cmd}"

   deferred = q.defer()
-  console.log "lock", lock
   lock.acquire "git", (callback) ->
     checkLockFileDoesntExist()
       .then () ->


abc order

abc order

--- filter-registry.cson
+++ filter-registry.cson
@@ -81,6 +81,17 @@
       task: 'meshblu-core-task-check-discover-as-whitelist'
       datastoreCollection: 'devices'

+EnforceMessageAsWhitelists:
+  start: 'check-message-as-protect-your-as'
+  tasks:
+    'check-message-as-protect-your-as':
+      task: 'meshblu-core-task-protect-your-as'
+      on:
+        204: 'check-whitelist-message-as'
+    'check-whitelist-message-as':
+      task: 'meshblu-core-task-check-whitelist-message-as'
+      datastoreCollection: 'devices'
+
 EnforceMessageSentWhitelists:
   start: 'enforce-message-sent-as-whitelist'
   tasks:
@@ -90,17 +101,6 @@
         204: 'check-whitelist-message-sent'
     'check-whitelist-message-sent':
       task: 'meshblu-core-task-check-whitelist-message-sent'
-      datastoreCollection: 'devices'
-
-EnforceMessageAsWhitelists:
-  start: 'check-message-as-protect-your-as'
-  tasks:
-    'check-message-as-protect-your-as':
-      task: 'meshblu-core-task-protect-your-as'
-      on:
-        204: 'check-whitelist-message-as'
-    'check-whitelist-message-as':
-      task: 'meshblu-core-task-check-whitelist-message-as'
       datastoreCollection: 'devices'

 EnforceSendWhitelists:


Move and fix test for feature

Move and fix test for feature

--- tests/unit_tests/utils_test.coffee
+++ tests/unit_tests/utils_test.coffee
@@ -50,18 +50,16 @@
     assert.equal "https://www.google.com/search?q=go+ogle.com", Utils.convertToUrl("go ogle.com")
     assert.equal "https://www.google.com/search?q=%40twitter", Utils.convertToUrl("@twitter")

+context "createSearchUrl",
+  should "replace %S without encoding", ->
+    assert.equal "https://www.github.com/philc/vimium/pulls", Utils.createSearchUrl "vimium/pulls", "https://www.github.com/philc/%S"
+
 context "extractQuery",
   should "extract queries from search URLs", ->
     assert.equal "bbc sport 1", Utils.extractQuery "https://www.google.ie/search?q=%s", "https://www.google.ie/search?q=bbc+sport+1"
     assert.equal "bbc sport 2", Utils.extractQuery "http://www.google.ie/search?q=%s", "https://www.google.ie/search?q=bbc+sport+2"
     assert.equal "bbc sport 3", Utils.extractQuery "https://www.google.ie/search?q=%s", "http://www.google.ie/search?q=bbc+sport+3"
     assert.equal "bbc sport 4", Utils.extractQuery "https://www.google.ie/search?q=%s", "http://www.google.ie/search?q=bbc+sport+4&blah"
-
-  should "replace %S without encoding", ->
-    assert.equal "vimium/pulls", Utils.extractQuery "https://www.github.com/philc/%S", "https://www.github.com/philc/vimium/pulls"
-
-  should "extract not queries from incorrect search URLs", ->
-    assert.isFalse Utils.extractQuery "https://www.google.ie/search?q=%s&foo=bar", "https://www.google.ie/search?q=bbc+sport"

 context "hasChromePrefix",
   should "detect chrome prefixes of URLs", ->


Show dates starting from the first of week

Luckily the store grabs all events for the week so at least this was
showing all the events, but was confusing for the title to be incorrect.

--- src/components/student-dashboard/this-week-panel.cjsx
+++ src/components/student-dashboard/this-week-panel.cjsx
@@ -12,7 +12,7 @@
     courseId: React.PropTypes.string.isRequired

   render: ->
-    startAt = moment(TimeStore.getNow())
+    startAt = moment(TimeStore.getNow()).startOf('isoweek')
     events  = StudentDashboardStore.weeklyEventsForDay(@props.courseId, startAt)
     if events.length
       <Events


nothin to see here

nothin to see here

(fixing csv parser's double quotes escape issue)

--- jsapp/xlform_model_view/csv.coffee
+++ jsapp/xlform_model_view/csv.coffee
@@ -97,8 +97,7 @@
     if cell is undefined
       ""
     else if ///\W|\w|#{csv.settings.delimiter}///.test cell
-      outstr = _remove_extra_escape_chars(cell)
-      outstr = JSON.stringify("" + outstr)
+      outstr = JSON.stringify("" + cell)
       _remove_extra_escape_chars outstr
     else
       cell
@@ -156,7 +155,7 @@
   removeTrailingNewlines = (str)-> str.replace(/(\n+)$/g, "")

   csv._parse_string = (c)->
-    JSON.parse('"' + c.replace(/\\/g, '\\\\') + '"')
+    JSON.parse('"' + c.replace(/\\/g, '\\\\').replace(/\\\\"/g, '\\"') + '"')

   # The `csv.toArray` function, pulled from this [stackoverflow comment](http://stackoverflow.com/questions/1293147/javascript-code-to-parse-csv-data)
   # will parse a delimited string into an array of


avoid using isolateScope in sms directive test

--- karma/flows/test_directives.coffee
+++ karma/flows/test_directives.coffee
@@ -87,8 +87,8 @@
       $timeout.flush()

       html = ele.html()
-      result = ele.children().isolateScope()

+      result = scope.$$childHead
       expect(html).toContain(132) # 132 characters for the counter
       expect(html).toContain(" \\/ 1") # of one message

@@ -108,7 +108,7 @@
       scope.$digest()
       $timeout.flush()

-      result = ele.children().isolateScope()
+      result = scope.$$childHead
       expect(result.characters).toBe(160)
       expect(result.messages).toBe(0)
       expect(result.message).not.toBe(undefined)


Updated builtin-funcs and class-types

Updated builtin-funcs and class-types


Move Antimatter task to level 8C

Move Antimatter task to level 8C

--- server/materials/data/topics/sparse_tables.coffee
+++ server/materials/data/topics/sparse_tables.coffee
@@ -12,6 +12,8 @@
             ruen("Sparse tables, двоичный подъем", "Sparse tables, binary ascent"),
             ruen("Задачи на sparse tables", "Problems on sparse tables"),
         [   problem(113919),
+        ], "sparse_tables"),
+        advancedProblems: [
             problem(113550),
-        ], "sparse_tables"),
+        ]
     }


builder: use minifyCss flag to minify styles, not debugCss

--- client/builder/lib/index.coffee
+++ client/builder/lib/index.coffee
@@ -368,7 +368,7 @@

       opts_ =
         use       : nib()
-        compress  : yes  unless opts.debugCss
+        compress  : if opts.minifyCss then yes else no
         import    : includes
         sourcemap :
           inline: if opts.debugCss then yes else no


AppController: Added an existential check.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -374,7 +374,7 @@
           ace.setSyntax value

   getActivePaneView: ->
-    return @activeTabView.getActivePane().getSubViews().first
+    return @activeTabView.getActivePane()?.getSubViews().first

   saveFile: ->
     @getActivePaneView().emit 'SaveRequested'


AppController: Write snapshot when ideView requested.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -567,6 +567,9 @@
     ideView.on 'ChangeHappened', (change) =>
       @syncChange change  if @rtm

+    ideView.on 'UpdateWorkspaceSnapshot', =>
+      @writeSnapshot()
+

   registerPane: (pane) ->


revpi-core-3.coffee: Set yocto version to thud

Signed-off-by: Andrei Gherzan <8dc7c09356db0b1762fb140617d7375c0a5a4131@balena.io>

--- revpi-core-3.coffee
+++ revpi-core-3.coffee
@@ -35,7 +35,7 @@
 		machine: 'revpi-core-3'
 		image: 'resin-image'
 		fstype: 'resinos-img'
-		version: 'yocto-sumo'
+		version: 'yocto-thud'
 		deployArtifact: 'resin-image-revpi-core-3.resinos-img'
 		compressed: true


better failure handling

better failure handling

--- routes/index.coffee
+++ routes/index.coffee
@@ -2,17 +2,25 @@
 log = debug 'app:log'
 errorLog = debug 'app:error'

-fail = (msg) ->
-  errorLog msg
-  throw new Error msg
+fail = (err, res) ->
+  errorLog err
+  if typeof err is 'string'
+    msg = err
+    err = new Error err
+  else
+    msg = err.error
+  if res?
+    res.status(500).send msg
+  else
+    throw err

 express = require 'express'
 parse = require '../lib/pegg-parse'
 router = express.Router()

-validateClient = (secret) ->
+validateClient = (secret, res) ->
   if secret isnt CLIENT_SECRET
-    fail "invalid client secret, aborting"
+    fail "invalid client secret, aborting", res

 CLIENT_SECRET = process.env.CLIENT_SECRET or fail "cannot have an empty CLIENT_SECRET"

@@ -22,16 +30,20 @@

 ### New Card ###
 router.post '/card', (req, res) ->
-  res.setHeader('Content-Type', 'application/json');
-  validateClient req.body.secret
+  validateClient req.body.secret, res
   req.body.secret = undefined

   if req.body?.card?.objectId
     parse.updateCard req.body
-    res.send 'Updated'
+      .then =>
+        res.setHeader 'Content-Type', 'application/json'
+        res.send 'Updated'
+      .catch (err) => fail err, res
   else
     parse.createCard req.body
-    res.send 'Created'
-
+      .then =>
+        res.setHeader 'Content-Type', 'application/json'
+        res.send 'Created'
+      .catch (err) => fail err, res

 module.exports = router


Added pins prefill

Added pins prefill

--- client/src/app/util/pins.coffee
+++ client/src/app/util/pins.coffee
@@ -45,4 +45,7 @@
     # Fill in metrics pins
     for pin in ["down", "position", "cursor", "justDown", "justUp"] then keys.push("io.mouse.#{pin}")

+    # Fill in location pins
+    for pin in ["url", "protocol", "host", "port", "path", "query", "hash"] then keys.push("io.location.#{pin}")
+
     return keys


Turn Keep-Alive off

Turn Keep-Alive off

--- test/appveyor.coffee
+++ test/appveyor.coffee
@@ -16,6 +16,7 @@
   uri = url.parse api
   data = JSON.stringify data
   uri.method = 'POST'
+  uri.agent = false
   uri.path = path
   uri.pathname = path
   uri.headers =


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -49,10 +49,9 @@
     ok true

   o.trigger ".TestNamespace"
-  o.trigger "test"

   o.unbind ".TestNamespace", ->
-  o.trigger "test"
+  o.trigger ".TestNamespace"

 test "#unbind namespaced", 1, ->
   o = Object.extend({}, Bindable())


cooldown finilazation

cooldown finilazation

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -98,7 +98,7 @@

   equals obj.I.age, 1, "should successfully update"

-test "use existing value of instance "
+test "use existing value of instance variab"

 test "initialize property to 0 if no current value", ->
   obj = GameObject()


Multiline WIP

Multiline WIP

--- repl.coffee
+++ repl.coffee
@@ -1,5 +1,5 @@
 vm = require 'vm'
-repl = require 'repl'
+nodeREPL = require 'repl'
 CoffeeScript = require './coffee-script'
 {merge} = require './helpers'

@@ -7,12 +7,52 @@
   prompt: 'coffee> ',
   eval: (code, context, file, cb) ->
     try
-      code = '' if /^\(\s+\)$/.test code # Empty command won't parse
+      return cb(null) if /^\(\s+\)$/.test code # Empty command
+      code = code.replace /子/mg, '\n' # Temporary hack, see TODO below
       code = CoffeeScript.compile(code, {filename: file, bare: true})
       cb(null, vm.runInContext(code, context, file))
     catch err
       cb(err)

+# TODO: how to test?
+addMultilineHandler = (repl) ->
+  {rli, inputStream, outputStream} = repl
+
+  multiline =
+    enabled: off
+    prompt: new Array(repl.prompt.length).join('.') + ' '
+    buffer: ''
+
+  # Proxy node's line listener
+  nodeLineListener = rli.listeners('line')[0]
+  rli.removeListener 'line', nodeLineListener
+  rli.on 'line', (cmd) ->
+    if multiline.enabled is on
+      multiline.buffer += "#{cmd}\n"
+      rli.prompt true
+    else
+      nodeLineListener(cmd)
+
+  # Handle Ctrl-v
+  inputStream.on 'keypress', (char, key) ->
+    return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
+    multiline.enabled = !multiline.enabled
+    if multiline.enabled is off
+      unless multiline.buffer.match /\n/
+        rli.setPrompt repl.prompt
+        rli.prompt true
+        return
+      # TODO: how to encode line breaks so the node repl will pass the complete multiline to our eval?
+      multiline.buffer = multiline.buffer.replace /\n/mg, '子'
+      rli.emit 'line', multiline.buffer
+      multiline.buffer = ''
+    else
+      rli.setPrompt multiline.prompt
+      rli.prompt true
+
 module.exports =
   start: (opts = {}) ->
-    repl.start merge(replDefaults, opts)
+    opts = merge(replDefaults, opts)
+    repl = nodeREPL.start opts
+    addMultilineHandler(repl)
+    repl


use .html() instead of [0].innerHTML

use .html() instead of [0].innerHTML

--- scripts/models/content/book.coffee
+++ scripts/models/content/book.coffee
@@ -216,7 +216,7 @@
       @getChildren().forEach (child) => recBuildList($navOl, child)
       $nav.append($navOl)
       # Trim the HTML and put newlines between elements
-      html =  $wrapper[0].innerHTML
+      html =  $wrapper.html()
       html = html.replace(/></g, '>\n<')
       return html


Make the ToC editable

Make the ToC editable

--- scripts/views/workspace/toc.coffee
+++ scripts/views/workspace/toc.coffee
@@ -15,6 +15,105 @@

     initialize: () ->
       @collection = @model.get('contents')
+
+    events:
+      # The `.editor-node-body` is needed because `li` elements render differently
+      # when there is a space between `<li>` and the first child.
+      # `.editor-node-body` ensures there is never a space.
+      'click > .editor-node-body > .editor-expand-collapse': 'toggleExpanded'
+      'click > .editor-node-body > .no-edit-action': 'toggleExpanded'
+      'click > .editor-node-body > .edit-action': 'editAction'
+      'click > .editor-node-body > .edit-settings': 'editSettings'
+
+    # Toggle expanded/collapsed in the View
+    # -------
+    #
+    # The first time a node is rendered it is collapsed so we lazily load the
+    # `ItemView` children.
+    #
+    # Initially, the node is collapsed and the following occurs:
+    #
+    # 1. `@render()` calls `@_renderChildren()`
+    # 2. Since `@isExpanded == false` the children are not rendered
+    #
+    # When the node is expanded:
+    #
+    # 1. `@isExpanded` is set to `true`
+    # 2. `@render()` is called and calls `@_renderChildren()`
+    # 3. Since `@isExpanded == true` the children are rendered and attached to the DOM
+    # 4. `@hasRendered` is set to `true` so we know not to generate the children again
+    #
+    #
+    # When an expanded node is collapsed:
+    #
+    # 1. `@isExpanded` is set to `false`
+    # 2. A CSS class is set on the `<li>` item to hide children
+    # 3. CSS rules hide the children and change the expand/collapse icon
+    #
+    # When a node that was expanded and collapsed is re-expanded:
+    #
+    # 1. `@isExpanded` is set to `true`
+    # 2. Since `@hasRendered == true` there is no need to call `@render()`
+    # 3. The CSS class is removed to show the children again
+
+    isExpanded: false
+    hasRendered: false
+
+    # Called from UI when user clicks the collapse/expando buttons
+    toggleExpanded: -> @expand !@isExpanded
+
+    # Pass in `false` to collapse
+    expand: (@isExpanded) ->
+      @$el.toggleClass 'editor-node-expanded', @isExpanded
+      # (re)render the model and children if the node is expanded
+      # and has not been rendered yet.
+      if @isExpanded and !@hasRendered
+        @render()
+
+    # From `Marionette.CompositeView`.
+    # Added check to only render when the model `@isExpanded`
+    _renderChildren: ->
+      if @isRendered
+        if @isExpanded
+          Marionette.CollectionView.prototype._renderChildren.call(@)
+          this.triggerMethod('composite:collection:rendered')
+        # Remember that the children have been rendered already
+        @hasRendered = @isExpanded
+
+
+    # Perform the edit action and then expand the node to show children.
+    editAction: -> @model.editAction(); @expand(true)
+
+    editSettings: ->
+      newTitle = prompt 'Edit Title. Enter a single "-" to delete this node in the ToC', @model.get('title')
+
+      if '-' is newTitle
+        @model.parent?.children()?.remove @model
+      else if newTitle == contentModel?.get('title')
+        @model.unset 'title'
+      else if newTitle
+        @model.set 'title', newTitle
+
+      @render()
+      ###
+      if @model != @model.dereference()
+        contentModel = @model.dereference()
+        originalTitle = contentModel?.get('title') or @model.get 'title'
+        newTitle = prompt 'Edit Title. Enter a single "-" to delete this node in the ToC', originalTitle
+        if '-' == newTitle
+          @model.parent?.children()?.remove @model
+        else if newTitle == contentModel?.get('title')
+          @model.unset 'title'
+        else if newTitle
+          @model.set 'title', newTitle
+      else
+        originalTitle = @model.get 'title'
+        newTitle = prompt 'Edit Title. Enter a single "-" to delete this node in the ToC', originalTitle
+        if '-' == newTitle
+          @model.parent?.children()?.remove @model
+        else
+          @model.set 'title', newTitle if newTitle
+      ###

   return Marionette.CompositeView.extend
     template: tocTemplate
@@ -35,3 +134,13 @@
     addItemView: (item, ItemView, index) ->
       if item.branch
         Marionette.CompositeView::addItemView.call(@, item, ItemView, index)
+
+    events:
+      'click .editor-content-title': 'changeTitle'
+      #'click .editor-go-workspace': 'goWorkspace'
+
+    changeTitle: ->
+      title = prompt 'Enter a new Title', @model.get('title')
+      @model.set 'title', title if title
+
+    #goWorkspace: -> Controller.workspace()


select the first table on startup if a table exists

--- sqliteweb-web/js/app.coffee
+++ sqliteweb-web/js/app.coffee
@@ -122,6 +122,16 @@
   getTables (data) ->
     data.tables.forEach (item) ->
       $('
' +  item + '
').appendTo('#tables')
+
+    # Select the first table
+    if data.tables.length > 0
+      $('#tables li.selected').removeClass 'selected'
+      table = $('#tables li:first')
+      $(table).addClass 'selected'
+
+      showTableInfo()
+      showTableStructure()
+
     cb()

 showDatabaseInfo = () ->


:bug: apply correct link in inheritedfrom

--- src/renderer/components/doc-detail-title-component.coffee
+++ src/renderer/components/doc-detail-title-component.coffee
@@ -17,9 +17,9 @@
   constructLink = (name) ->
     match = name.match(/^(.+)\.(.+)$/)

-      <Link style={styles.link} href={"/class/#{match[1]}"}>{match[1]}</Link>
+      <Link style={styles.link} to={"/class/?.*/#{match[1]}"}>{match[1]}</Link>
       .
-      <Link style={styles.link} href={"/class/#{match[1]}/#{match[2].replace(/__constructor/, 'constructor')}"}>{match[2].replace(/__constructor/, 'constructor')}</Link>
+      <Link style={styles.link} to={"/class/?.*/#{match[1]}/#{match[2].replace(/__constructor/, 'constructor')}"}>{match[2].replace(/__constructor/, 'constructor')}</Link>

   render: ->


bugfix: subsequent debug sessions not working

--- lib/debugger-view.coffee
+++ lib/debugger-view.coffee
@@ -91,7 +91,7 @@

   toggleSession: (port) ->
     if @bug?
-      return endSession()
+      return @endSession()

     atom.workspaceView.prependToBottom(this)

@@ -178,6 +178,7 @@
     @updateMarkers()
     @childprocess?.kill()
     @bug?.close() # TODO: unregister handlers orelse memory leak!
+    @bug = null
     @detach()

   serialize: ->
@@ -213,7 +214,6 @@
       atom.workspaceView.addClass('and--paused')
       @setCurrentPause(breakInfo)
       @openPath @getCurrentPauseLocations()[0], =>
-        console.log 'paused, cached breaks:',breaks
         if breaks?.length > 0
           @setBreakpoints(breaks, => @updateMarkers())
           breaks = null
@@ -239,13 +239,11 @@
   # array of {breakpointId:id, locations:array of {scriptPath, lineNumber}}
   breakpoints: []
   setBreakpoints: (breakpoints, done) ->
-    console.log 'set', breakpoints
     if not @bug?
       @breakpoints.push(bp)  for bp in breakpoints
       return done()

     setNext = (breakpoints, done) =>
-      console.log 'setting', breakpoints
       return done?() if not breakpoints? or breakpoints.length is 0
       [{breakpointId,locations},tail...] = breakpoints
       [{lineNumber, scriptPath},blah...] = locations
@@ -277,7 +275,6 @@
     @bug.removeBreakpoint({breakpointId: id})
     @breakpoints = @breakpoints.filter ({breakpointId})->breakpointId isnt id
   toggleBreakpoint: ({scriptPath, lineNumber}, done)->
-    console.log @breakpoints
     toRemove = @breakpoints.filter (bp)->
       (scriptPath is bp.locations[0].scriptPath and
       lineNumber is bp.locations[0].lineNumber)


:new: Add status to editor-linter

:new: Add status to editor-linter

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -4,6 +4,7 @@
 class EditorLinter
   constructor: (@linter, @editor) ->
     @_messages = new Map # Consumed by LinterViews::render
+    @_status = true
     @_inProgress = false
     @_inProgressFly = false

@@ -20,6 +21,17 @@
     @_subscriptions.add(
       @editor.onDidStopChanging => @lint(true) if @linter.lintOnFly
     )
+
+  toggleStatus: ->
+    @setStatus !@_status
+
+  getStatus: ->
+    @_status
+
+  setStatus: (status) ->
+    @_status = status
+    if not status
+      @_messages.clear()

   getMessages: ->
     @_messages
@@ -42,6 +54,7 @@
     @_emitter.on 'did-destroy', callback

   lint: (wasTriggeredOnChange) ->
+    return unless @_status
     return unless @editor is atom.workspace.getActiveTextEditor()
     return unless @editor.getPath()
     return if @_lock(wasTriggeredOnChange)


hg repo type

hg repo type

--- lib/hg-repository.coffee
+++ lib/hg-repository.coffee
@@ -119,7 +119,7 @@
   # Public: A {String} indicating the type of version control system used by
   # this repository.
   #
-  # Returns `"svn"`.
+  # Returns `"hg"`.
   getType: -> 'hg'

   # Public: Returns the {String} path of the repository.
@@ -191,7 +191,7 @@
   #   * `behind` The {Number} of commits behind.
   getCachedUpstreamAheadBehindCount: (path) -> null

-  # Public: Returns the svn property value specified by the key.
+  # Public: Returns the hg property value specified by the key.
   #
   # * `path` An optional {String} path in the repository to get this information
   #   for, only needed if the repository has submodules.
@@ -301,7 +301,7 @@
   Section: Retrieving Diffs
   ###

-  # Retrieves the file content from latest svn revision and cache it.
+  # Retrieves the file content from latest hg revision and cache it.
   #
   # * `path` The {String} path for retrieving file contents.
   #
@@ -406,7 +406,7 @@
   # last time the index was read.
   refreshIndex: -> null

-  # Refreshes the current svn status in an outside process and asynchronously
+  # Refreshes the current hg status in an outside process and asynchronously
   # updates the relevant properties.
   refreshStatus: ->
     new Promise((resolve, reject) =>


Fix message of achieved a last goal

Fix message of achieved a last goal

--- scripts/goal.coffee
+++ scripts/goal.coffee
@@ -166,7 +166,7 @@
       robot.brain.data.goals[user].goals = []
       robot.brain.data.goals[user].achieved_goals.push goal
       msg.send "#{user}さんの目標は。。。\n「#{goal}」ですね。\n" +
-        goalsToString [], robot.brain.data.goals[user].achieved_goals
+        goalsToString([], robot.brain.data.goals[user].achieved_goals) + "\n" +
         msg.random congraturations
       return


Worst Code review ever

Worst Code review ever

See title.

--- scripts/hype.coffee
+++ scripts/hype.coffee
@@ -10,8 +10,9 @@
     overHyped = "ヽヽ༼༼ຈຈل͜ل͜ຈຈ༽༽ﾉﾉ TOO MUCH HYPE TO HANDLE! ヽヽ༼༼ຈຈل͜ل͜ຈຈ༽༽ﾉﾉ"

     resetHype ->
-        total = total - ((Date.now() - lastTime)%timeDecay)*hypeIncrement
-        lastTime = Date.now()
+        curTime = Date.now()
+        total = total - ((curTime - lastTime)%timeDecay)*hypeIncrement
+        lastTime = curTime
         total = 0 if total < 0

     robot.hear /hype/i, (res) ->


trace

trace

--- scripts/jira.coffee
+++ scripts/jira.coffee
@@ -79,7 +79,7 @@
                         if error
                                 msg.send "ERROR: #{error}"
                         else
-                                util.inspect(data)
+                                msg.send(util.inspect(data))
                                 for issue in data['issues']
                                         msg.send "#{issue['key']}: #{issue['fields']['summary']}"
                         msg.send "Searched!"


Fix scale function (#596)

Fix scale function (#596)

The function wasn't working in these cases:
scale(xFactor, yFactor)
scale(xyFactor, options)

--- lib/mixins/vector.coffee
+++ lib/mixins/vector.coffee
@@ -191,9 +191,9 @@
     @transform cos, sin, -sin, cos, x, y

   scale: (xFactor, yFactor = xFactor, options = {}) ->
-    if arguments.length is 2
+    if typeof yFactor is "object"
+      options = yFactor
       yFactor = xFactor
-      options = yFactor

     x = y = 0
     if options.origin?


Prevent Ralph from committing suicide

Prevent Ralph from committing suicide

* remove `die` command from ping.coffee

--- scripts/ping.coffee
+++ scripts/ping.coffee
@@ -5,7 +5,6 @@
 #   hubot ping - Reply with pong
 #   hubot echo <text> - Reply back with <text>
 #   hubot time - Reply with current time
-#   hubot die - End hubot process

 module.exports = (robot) ->
   robot.respond /PING$/i, (msg) ->
@@ -19,8 +18,3 @@

   robot.respond /TIME$/i, (msg) ->
     msg.send "Server time is: #{new Date()}"
-
-  robot.respond /DIE$/i, (msg) ->
-    msg.send "Goodbye, cruel world."
-    process.exit 0
-


tabs are spaces, spaces are tabs. what?

tabs are spaces, spaces are tabs. what?

--- scripts/test.coffee
+++ scripts/test.coffee
@@ -1,29 +1,29 @@

 INJECTOR = new Injector
-
+
 class MainPresenter extends Presenter

     on_bind: () ->
-
+
         @register_handler("error", (event) ->
             INJECTOR.get_logger().log("ERROR: " + event.data, 5)
         , true)
-
-
-        @register_handler("click", (event) ->
+
+
+        @register_handler("click", (event) ->
             INJECTOR.get_event_bus().fire "display_message", "alert button clicked"
         , @display.get_alert_button())
-
+
 class MainDisplay extends Display
-
+
     container = $('<div/>')
-
-    constructor: () ->
-        @button = $('<input/>', {type: 'submit'})
+
+    constructor: () ->
+        @button = $('<input/>', {type: 'submit'})
         @button.appendTo(container)
-
+
     get_alert_button: -> @button[0]
-
+
     as_widget: -> container

 class MessageCentre extends Presenter
@@ -35,51 +35,50 @@
         , true)

 class MessageCentreDisplay extends Display
-
+
     container = $('<div/>')
-
+
     constructor: () ->
         @num_messages = 0
         @message_holder = $('<h1/>')
         @message_holder.appendTo(container)
-
+
     show_message: (message) ->
         @message_holder.animate opacity: '0.5'
-
+
         @message_holder.text message + @num_messages
-
+
         @num_messages += 1
-
+
         @message_holder.animate opacity: '1.0'
-
+
     as_widget: -> container

 class Application

     event_bus = new EventBus()
     logger = new Logger()
-
+
     run: ->
         INJECTOR.register("get_event_bus", -> return event_bus)
         INJECTOR.register("get_logger", -> return logger)
         INJECTOR.register("get_root_panel", -> return $("#application"))
-
+
         main_presenter = new MainPresenter(new MainDisplay())
         main_presenter.bind()
-
+
         message_presenter = new MessageCentre(new MessageCentreDisplay())
         message_presenter.bind()
-
+
         event_bus.fire('display_message', "Globally Listening ... ");
-
+
         INJECTOR.get_root_panel().append(main_presenter.get_display().as_widget())
         INJECTOR.get_root_panel().append(message_presenter.get_display().as_widget())

 $(document).ready ->
-
+
     app = new Application()

     if window
         window.APP = app
     app.run()
-


Handle file preparation in a closure

Handle file preparation in a closure

--- scripts/home.coffee
+++ scripts/home.coffee
@@ -128,22 +128,24 @@
     if Object.keys(uploadedFiles).length == 0
         document.getElementById('files').innerHTML = ''
     for file in files
-        mediaFile = new MediaFile(file)
-        mediaFile.preview = templates.preview(mediaFile).toDOM()
-        fileList.appendChild(mediaFile.preview)
-        mediaFile.preview = fileList.lastElementChild
-        mediaFile.loadPreview()
-        mediaFile.hash = new String(guid())
-        mediaFile.updateStatus('preparing')
-        uploadedFiles[mediaFile.hash] = mediaFile
-        reader = new FileReader()
-        reader.onloadend = (e) ->
-            try
-                data = e.target.result
-                worker.postMessage({ action: 'compute-hash', data: data, callback: 'hashCompleted', id: mediaFile.hash })
-            catch e # Too large
-                uploadFile(mediaFile)
-        reader.readAsBinaryString(file)
+        ((file) ->
+            mediaFile = new MediaFile(file)
+            mediaFile.preview = templates.preview(mediaFile).toDOM()
+            fileList.appendChild(mediaFile.preview)
+            mediaFile.preview = fileList.lastElementChild
+            mediaFile.loadPreview()
+            mediaFile.hash = new String(guid())
+            mediaFile.updateStatus('preparing')
+            uploadedFiles[mediaFile.hash] = mediaFile
+            reader = new FileReader()
+            reader.onloadend = (e) ->
+                try
+                    data = e.target.result
+                    worker.postMessage({ action: 'compute-hash', data: data, callback: 'hashCompleted', id: mediaFile.hash })
+                catch e # Too large
+                    uploadFile(mediaFile)
+            reader.readAsBinaryString(file)
+        )(file)

 hashCompleted = (id, result) ->
     file = uploadedFiles[id]


Remove preprocessing dependency in view.coffee

--- scripts/view.coffee
+++ scripts/view.coffee
@@ -50,8 +50,7 @@
         size = mediaSizeReporter()
         size.height += 5
         embed = document.getElementById('embed-value')
-        # TODO: fix {{ protocol }} and {{ domain }}
-        embed.value = ''
+        embed.value = ''

     if window.can_delete == 'check'
         history = UserHistory.getHistory()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/tweening.coffee
+++ test/tweening.coffee
@@ -10,6 +10,7 @@
   10.times (i) ->
     o.update()

+    equals o.I.x

 test "testing boolean values", ->


Refactor

Refactor

--- scripts/jerk.coffee
+++ scripts/jerk.coffee
@@ -7,9 +7,8 @@
       .get() (err, res, body) ->
         results = JSON.parse(body)
         post = msg.random results.data.children
+        msg.send post.data.title
         if (post.data.domain == "self.circlejerk")
-          msg.send post.data.title
           msg.send post.data.selftext
         else
-          msg.send post.data.title
           msg.send post.data.url


Removed Problems link from menu

Removed Problems link from menu

--- web/coffee/navbar.coffee
+++ web/coffee/navbar.coffee
@@ -33,7 +33,7 @@

 userLoggedIn =
   Game: "/game/"
-  Problems: "/problems"
+  #Problems: "/problems"
   Shell: "/shell"
   Team: "/team"
   Scoreboard: "/scoreboard"


Put back the sort for the leaderboard handler.

--- server/levels/level_handler.coffee
+++ server/levels/level_handler.coffee
@@ -136,10 +136,11 @@
       'creatorName'
       'creator'
     ]
-
+
     query = Session
       .find(sessionsQueryParameters)
       .limit(req.query.limit)
+      .sort(sortParameters)
       .select(selectProperties.join ' ')

     query.exec (err, resultSessions) =>


More fixing with ListenNetwork

More fixing with ListenNetwork

--- components/ListenNetwork.coffee
+++ components/ListenNetwork.coffee
@@ -21,23 +21,22 @@
     datatype: 'object'

   c.updateListeners = (runtime, graph) ->
-    @runtime.removeListener 'network', @onNetworkPacket if @runtime
-    @runtime = runtime
-    @graph = graph
-    @runtime.on 'network', @onNetworkPacket if @runtime
+    c.runtime.removeListener 'network', c.onNetworkPacket if c.runtime
+    c.runtime = runtime
+    c.graph = graph
+    c.runtime.on 'network', c.onNetworkPacket if c.runtime

-  c.onNetworkPacket = ({command, payload}) =>
-    return unless command is 'network'
-    return unless payload.id and @graph and payload.id is @graph.name
-    @outPorts.packet.send
+  c.onNetworkPacket = ({command, payload}) ->
+    return if not payload.graph or not c.graph or payload.graph isnt c.graph.name
+    c.outPorts.packet.send
       edge: payload.id
       type: command
       group: if payload.group? then payload.group else ''
       data: if payload.data? then payload.data else ''
       subgraph: if payload.subgraph? then payload.subgraph else ''
-      runtime: @runtime.definition.id
+      runtime: c.runtime.definition.id

-  c.shutdown = () ->
-    @updateListeners null, null
+  c.shutdown = ->
+    c.updateListeners null, null

   c