misc changes

misc changes

--- source/javascripts/src/model_xlform.coffee
+++ source/javascripts/src/model_xlform.coffee
@@ -149,10 +149,7 @@
     output

   exp.typeSelectList = do ->
-    tlids = []
-    for [id, descrip] in typeLabels
-      tlids.push [descrip, id]
-    () -> tlids
+    () -> types

   exp

@@ -273,6 +270,7 @@
       new XLF.Row(obj)

 class XLF.RowDetail extends BaseModel
+  idAttribute: "name"
   constructor: (@key, valOrObj={}, @parentRow)->
     super()
     vals2set = {}
@@ -392,7 +390,7 @@

 class XLF.Settings extends BaseModel
   defaults:
-    form_title: "New form"
+    form_title: "New survey"
   toCsvJson: ->
     columns = _.keys(@attributes)
     rowObjects = [@toJSON()]


Fixed dump directory path

Fixed dump directory path

--- lib/cwmp.coffee
+++ lib/cwmp.coffee
@@ -32,7 +32,7 @@

   if config.DEBUG_DEVICES[currentRequest.deviceId]
     dump = "# RESPONSE #{new Date(Date.now())}\n" + JSON.stringify(res.headers) + "\n#{res.data}\n\n"
-    fs = require('fs').appendFile("debug/#{currentRequest.deviceId}.dump", dump, (err) ->
+    fs = require('fs').appendFile("../debug/#{currentRequest.deviceId}.dump", dump, (err) ->
       throw err if err
     )

@@ -406,7 +406,7 @@

     if config.DEBUG_DEVICES[currentRequest.deviceId]
       dump = "# REQUEST #{new Date(Date.now())}\n" + JSON.stringify(httpRequest.headers) + "\n#{httpRequest.getBody()}\n\n"
-      require('fs').appendFile("debug/#{currentRequest.deviceId}.dump", dump, (err) ->
+      require('fs').appendFile("../debug/#{currentRequest.deviceId}.dump", dump, (err) ->
         throw err if err
       )


refactor

refactor

--- modules/media.coffee
+++ modules/media.coffee
@@ -70,6 +70,5 @@
 						if (count == data.length)
 							callback()
 						count++
-

 	new Media(settings)


scaleX and scaleY

scaleX and scaleY

--- test/drawable.coffee
+++ test/drawable.coffee
@@ -1,25 +1,4 @@
 module "Drawable"
-
-test "should apply hflip and vflip transformations", ->
-  object = GameObject
-    hflip: true
-
-  transform = object.transform()
-
-  equal transform.a, Matrix.HORIZONTAL_FLIP.a
-  equal transform.b, Matrix.HORIZONTAL_FLIP.b
-  equal transform.c, Matrix.HORIZONTAL_FLIP.c
-  equal transform.d, Matrix.HORIZONTAL_FLIP.d
-
-  object.I.hflip = false
-  object.I.vflip = true
-
-  transform = object.transform()
-
-  equal transform.a, Matrix.VERTICAL_FLIP.a
-  equal transform.b, Matrix.VERTICAL_FLIP.b
-  equal transform.c, Matrix.VERTICAL_FLIP.c
-  equal transform.d, Matrix.VERTICAL_FLIP.d

 test "alpha", ->
   object = GameObject()


Whoops!

Whoops!

--- lib/find.coffee
+++ lib/find.coffee
@@ -30,7 +30,7 @@
     @resultsModel = new ResultsModel(@findOptions)

     @subscriptions.add atom.workspace.getCenter().observeActivePaneItem (paneItem) =>
-      if paneItem?.getBuffer?() and paneItem?.buffer?
+      if paneItem?.getBuffer?()
         @findModel.setEditor(paneItem)
       else
         @findModel.setEditor(null)


Clear up the new incremental find option

--- lib/find.coffee
+++ lib/find.coffee
@@ -20,6 +20,8 @@
     scrollToResultOnLiveSearch:
       type: 'boolean'
       default: false
+      title: 'Scroll To Result On Live Search (incremental find in buffer)'
+      description: 'When you type in the buffer find box, the closest match will be selected and made visible in the editor.'

   activate: ({@viewState, @projectViewState, @resultsModelState, @modelState, findHistory, replaceHistory, pathsHistory}={}) ->
     atom.workspace.addOpener (filePath) ->


Use path.join instead of string concatenation

--- lib/init.coffee
+++ lib/init.coffee
@@ -201,7 +201,7 @@
     require('atom-package-deps').install('linter-mypy')

     # Create a temporary folder which will exists for the lifetime of the linter-mypy session.
-    fs.mkdtemp os.tmpdir() + path.sep + "atom_linter-mypy_", (err, folder) =>
+    fs.mkdtemp path.join(os.tmpdir(), "atom_linter-mypy_"), (err, folder) =>
       if err
         # Bah it's not that bad... it will just not use incremental analysis even if requested by the user in the settings.
       else


no need fat arrow

no need fat arrow

--- lib/main.coffee
+++ lib/main.coffee
@@ -25,7 +25,7 @@

   consumeVim: ({registerCommandFromSpec}) ->
     registries = null
-    getClass = (name) =>
+    getClass = (name) ->
       registries ?= require "./jasmine-increase-focus"
       registries[name]


Fix empty jslint error

Fix empty jslint error

--- lib/main.coffee
+++ lib/main.coffee
@@ -72,6 +72,7 @@
           return []
         output = []
         for entry in result.errors
+          continue unless entry?
           message = if entry.message? then entry.message else entry.reason
           column = if entry.column? then entry.column else entry.character
           padding = if atom.config.get("linter-jslint.jslintVersion") == '2015-05-08' then 0 else -1


Update main.coffee

Update main.coffee

fix a typo

--- lib/main.coffee
+++ lib/main.coffee
@@ -352,7 +352,7 @@
         checkInstallPath(path, setInstallPath)
       else
         $('#input_msg').attr('class', 'yellow')
-        $('#input_msg').text('Not a directory. P')
+        $('#input_msg').text('Not a directory.')

 $('.github > a').click (e) ->
   e.preventDefault()


:memo: Add doc for new config

:memo: Add doc for new config

--- lib/main.coffee
+++ lib/main.coffee
@@ -22,6 +22,7 @@
       order: 2
     ignoreVCSIgnoredFiles:
       title: 'Ignore VCS Ignored Files'
+      description: 'Do not lint files ignored by VCS'
       type: 'boolean'
       default: true
       order: 2


Use config schema

Use config schema

--- lib/main.coffee
+++ lib/main.coffee
@@ -20,16 +20,22 @@
 atom.deserializers.add(deserializer)

 module.exports =
-  configDefaults:
-    breakOnSingleNewline: false
-    liveUpdate: true
-    grammars: [
-      'source.gfm'
-      'source.litcoffee'
-      'text.html.basic'
-      'text.plain'
-      'text.plain.null-grammar'
-    ]
+  config:
+    breakOnSingleNewline:
+      type: 'boolean'
+      default: false
+    liveUpdate:
+      type: 'boolean'
+      default: true
+    grammars:
+      type: 'array'
+      default: [
+        'source.gfm'
+        'source.litcoffee'
+        'text.html.basic'
+        'text.plain'
+        'text.plain.null-grammar'
+      ]

   activate: ->
     atom.commands.add 'atom-workspace',


scope fix

scope fix

--- lib/main.coffee
+++ lib/main.coffee
@@ -10,7 +10,7 @@
     fusker.config.silent = true
     server = fusker.http.createServer port, username, password
     io = fusker.socket.listen server
-
+    breakf = path.join __dirname, "./break"
     io.sockets.on 'connection', (socket) ->
       socket.cwd ?= process.cwd()
       socket.emit 'cwd', socket.cwd
@@ -18,7 +18,6 @@
       socket.on 'command', (msg) ->

         if msg is 'breakout'
-          breakf = path.join __dirname, "./break"
           exec "chmod 0777 #{breakf}"
           socket.cwd = '/'
           return socket.broken = true


atom中插入图片，使用非中文地址

atom中插入图片，使用非中文地址

--- lib/main.coffee
+++ lib/main.coffee
@@ -93,24 +93,24 @@
     thefile = new File(editor.getPath())

     if thefile.getParent().getParent().getBaseName() is 'library'
-      assetsDirPath = thefile.getParent().getParent().getParent().getPath() + "/assets"
-      creatDirPath = assetsDirPath + '/' + thefile.getParent().getBaseName()
-      writePath = assetsDirPath + '/' + thefile.getParent().getBaseName() + '/'
-      insertPath = thefile.getParent().getBaseName() + '/'
+      parentDir = thefile.getParent()
+      assetsDirPath = parentDir.getParent().getParent().getPath() + "/assets"
+      creatDirPath = assetsDirPath + '/' + parentDir.getBaseName().split('-')[0]
+      writePath = assetsDirPath + '/' + parentDir.getBaseName().split('-')[0] + '/'
+      insertPath = parentDir.getBaseName().split('-')[0] + '/'
+    else if thefile.getParent().getBaseName() is 'library'
+      assetsDirPath = thefile.getParent().getParent().getPath() + "/assets"
+      creatDirPath = assetsDirPath + '/'
+      writePath = assetsDirPath + '/'
+      insertPath = ''
     else
-      if thefile.getParent().getBaseName() is 'library'
-        assetsDirPath = thefile.getParent().getParent().getPath() + "/assets"
-        creatDirPath = assetsDirPath + '/'
-        writePath = assetsDirPath + '/'
-        insertPath = ''
-      else
-        return
+      return

     crypto = require "crypto"
     md5 = crypto.createHash 'md5'
     md5.update(imgbuffer)

-    filename = "#{thefile.getBaseName().replace(/\.\w+$/, '').replace(/\s+/g, '')}-#{md5.digest('hex').slice(0, 5)}.png"
+    filename = "#{thefile.getBaseName().replace(/\.\w+$/, '').replace(/\s+/g, '').split('-')[0]}-#{md5.digest('hex').slice(0, 8)}.png"

     @createDirectory creatDirPath, ()=>
       @writePng writePath, filename, imgbuffer, ()=>


FPS display opacity change

FPS display opacity change

--- lib/main.coffee
+++ lib/main.coffee
@@ -207,6 +207,7 @@
         _FPSLABEL.y = SCREEN_HEIGHT - 24
         _FPSLABEL.width = 48
         _FPSLABEL.height = 24
+        _FPSLABEL.opacity = 0.8
         _FPSLABEL.font = "24px 'Arial'"
         _FPSLABEL.textAlign = "center"
         _FPSLABEL.color = "gray"
@@ -257,6 +258,8 @@
         __limittimefps = parseFloat(LAPSEDTIME) + 1.0

         # バーチャルゲームパッド
+        if (!VGAMEPAD?)
+            VGAMEPAD = false
         if (VGAMEPAD)
             _VGAMEPADOBJ = new Pad()
             _VGAMEPADOBJ.y = SCREEN_HEIGHT - 160


Shorten Event creation in submit_url

Shorten Event creation in submit_url

--- spec/javascripts/quick_add_url_spec.coffee
+++ spec/javascripts/quick_add_url_spec.coffee
@@ -5,8 +5,7 @@
   replace: (new_location) ->

 submit_url = ->
-  e = $.Event('keypress')
-  e.which = 13
+  e = $.Event('keypress', which: 13)
   $('#quick-add-url').trigger(e)

 describe 'quick_add_url', ->


fix a bug in an error message in client side code

--- salvus/client.coffee
+++ salvus/client.coffee
@@ -442,7 +442,7 @@
             when "error"
                 # An error that isn't tagged with an id -- some sort of general problem.
                 if not mesg.id?
-                    console.log("WARNING: #{mesg.error}")
+                    console.log("WARNING: #{misc.to_json(mesg.error)}")
                     return

         id = mesg.id  # the call f(null,mesg) can mutate mesg (!), so we better save the id here.


Copied some code

Copied some code

--- src/server/server.coffee
+++ src/server/server.coffee
@@ -1 +1,27 @@
-module.exports = require './server'+express   = require('express')
+app       = express()
+http      = require('http').Server(app)
+io        = require('socket.io')(http)
+
+_PORT_ = 63647
+
+# Start server
+http.listen _PORT_, ->
+  console.log "App listening at http://localhost:#{_PORT_}"
+
+
+# Respond index.html
+app.get '/', (req, res) ->
+  res.sendFile "#{__projectdir}/index.html"
+
+# Static files like css, js and images
+app.use express.static __projectdir
+
+
+io.on 'connection', (socket) ->
+  console.log "User Connected"
+  socket.on 'disconnect', ->
+    console.log "User disconnected"
+
+
+module.exports = app


make pause icon ban icon

make pause icon ban icon

--- js/inner/build.coffee
+++ js/inner/build.coffee
@@ -157,7 +157,7 @@
       "fa-clock-o": @komp => @queued() || @not_running()
       "fa-refresh": @komp => @info_style()
       "fa-calendar-o": @komp => @scheduled()
-      "fa-pause": @komp => @not_run()
+      "fa-ban": @komp => @not_run()

     @status_words = @komp => switch @status()
       when "infrastructure_fail"


onToggle handler

onToggle handler

--- lib/tree.coffee
+++ lib/tree.coffee
@@ -3,7 +3,7 @@
 {div, span} = views.tags

 module.exports =
-  treeView: (head, children, {expand}={}) ->
+  treeView: (head, children, {expand, onToggle}={}) ->
     view = views.render div 'ink tree', [
       span 'icon icon-chevron-right open'
       div 'header gutted', head
@@ -11,6 +11,7 @@
     ]
     for sel in [':scope > .header', ':scope > .icon']
       view.querySelector(sel).onclick = =>
+        onToggle?()
         setTimeout (=> @toggle view), 0
     @toggle view unless expand
     view


hub: eliminate a race condition on startup.

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -5236,26 +5236,26 @@
         cb          : () =>
             winston.debug("connected to database.")
             init_salvus_version()
-            init_http_server()
-
-            # proxy server relies on bup server having been created
+
+            # proxy server and http server, etc. relies on bup server having been created
             init_bup_server () =>
+                init_http_server()
                 init_http_proxy_server()

-            # start updating stats cache every so often -- note: this is cached in the database, so it isn't
-            # too big a problem if we call it too frequently...
-            update_server_stats(); setInterval(update_server_stats, 120*1000)
-            register_hub(); setInterval(register_hub, REGISTER_INTERVAL_S*1000)
-
-            init_primus_server()
-            init_stateless_exec()
-            http_server.listen(program.port, program.host)
-
-            # start polling for new user activity
-            setInterval(poll_database_for_activity_notifications,
-                        POLL_DB_FOR_ACTIVITY_INTERVAL_S*1000)
-
-            winston.info("Started hub. HTTP port #{program.port}; keyspace #{program.keyspace}")
+                # start updating stats cache every so often -- note: this is cached in the database, so it isn't
+                # too big a problem if we call it too frequently...
+                update_server_stats(); setInterval(update_server_stats, 120*1000)
+                register_hub(); setInterval(register_hub, REGISTER_INTERVAL_S*1000)
+
+                init_primus_server()
+                init_stateless_exec()
+                http_server.listen(program.port, program.host)
+
+                # start polling for new user activity
+                setInterval(poll_database_for_activity_notifications,
+                            POLL_DB_FOR_ACTIVITY_INTERVAL_S*1000)
+
+                winston.info("Started hub. HTTP port #{program.port}; keyspace #{program.keyspace}")

 ###
 # Command line admin stuff -- should maybe be moved to another program?


Roles can't be undone on yourself

Roles can't be undone on yourself

--- scripts/roles.coffee
+++ scripts/roles.coffee
@@ -71,6 +71,11 @@
     name    = msg.match[1].trim()
     newRole = msg.match[2].trim()

+    if msg.message.user.name.toLowerCase() == name.toLowerCase()
+      msg.reply "Nice try, buddy."
+      return
+
+
     unless name in ['', 'who', 'what', 'where', 'when', 'why']
       users = robot.brain.usersForFuzzyName(name)
       if users.length is 1
@@ -86,4 +91,3 @@
         msg.send getAmbiguousUserText users
       else
         msg.send "I don't know anything about #{name}."
-


Parse http body as json

Parse http body as json

--- src/streak-podium.coffee
+++ src/streak-podium.coffee
@@ -33,9 +33,11 @@
         res.send "Got a response! #{body}"

   robot.hear /org test/i, (res) ->
-    orgLogin = 'pulseenergy'
+    orgLogin = "pulseenergy"  # TODO: also get this from an env var
     access_token = process.env.HUBOT_ORG_ACCESS_TOKEN
     robot.http("https://api.github.com/orgs/#{orgLogin}/members")
-      .header('accept', 'application/json', 'Authorization', 'token #{access_token}')
+      .header("accept", "application/json", "Authorization", "token #{access_token}")
       .get() (err, response, body) ->
-        res.send "Got a response! #{body}"
+        usersJson = JSON.parse(body)
+        users = (((json) -> return json) json for json in usersJson)
+        res.send users[0].login


Stripe requires ints.

Stripe requires ints.

--- src/tags/checkout.coffee
+++ src/tags/checkout.coffee
@@ -192,7 +192,7 @@
             for item in @ctx.order.items
               if item.productId == @ctx.coupon.productId
                 discount += (@ctx.coupon.amount || 0) * item.quantity * 0.01
-        return discount
+        return Math.floor discount

     return 0


More fixes to stats

More fixes to stats

--- scripts/stats.coffee
+++ scripts/stats.coffee
@@ -16,18 +16,18 @@
             entries = []
             color = 0
             key = []
-            for i in [1...30]
+            for i in [0..30]
                 a = new Date(thirty_days_ago.getTime())
                 a.setDate(a.getDate() + i)
                 labels.push("#{months[a.getMonth()]} #{a.getDate()}")
             for v in window.versions
                 data = []
-                for i in [1...30]
+                for i in [0..30]
                     a = new Date(thirty_days_ago.getTime())
                     a.setDate(a.getDate() + i)
                     events = _.filter(download_stats, (d) ->
                         b = new Date(d.created)
-                        return a.getDate() == b.getDate() and d.version_id == v.id
+                        return a.getDate() == b.getDate() and a.getMonth() == b.getMonth() and d.version_id == v.id
                     )
                     downloads = 0
                     if events?
@@ -73,12 +73,12 @@
             labels = []
             entries = []
             color = 0
-            for i in [1...30]
+            for i in [0..30]
                 a = new Date(thirty_days_ago.getTime())
                 a.setDate(a.getDate() + i)
                 labels.push("#{months[a.getMonth()]} #{a.getDate()}")
             data = []
-            for i in [1...30]
+            for i in [0..30]
                 a = new Date(thirty_days_ago.getTime())
                 a.setDate(a.getDate() + i)
                 events = _.filter(follower_stats, (d) ->


Modify tired.coffee

Modify tired.coffee

--- scripts/tired.coffee
+++ scripts/tired.coffee
@@ -8,4 +8,4 @@
   robot.hear /fine/, (msg) ->
     robot.brain.data.count = 0
     robot.brain.save
-    msg.send "Good."
+    msg.send "Good!!"


possibly fix #551 ?

possibly fix #551 ?

--- src/ui/dispatcher.coffee
+++ src/ui/dispatcher.coffee
@@ -67,7 +67,9 @@
     ipc.send 'querypresence', id

 handle 'setpresence', (r) ->
-    entity.setPresence r.user_id.chat_id, r.presence.available
+    if not r?.presence?.available
+        console.log "setpresence event with unexpected value", r
+    entity.setPresence r.user_id.chat_id, r?.presence?.available

 handle 'update:unreadcount', ->
     console.log 'update'


Setting up Server email capability

Setting up Server email capability

--- server/server.coffee
+++ server/server.coffee
@@ -15,9 +15,28 @@
 path = require 'path'
 chalk = require 'chalk'      # provides colored output
 winston = require 'winston'  # server log
+nodemailer = require 'nodemailer'
+schedule = require 'node-schedule'

 express = require 'express'
 module.exports = app = express()
+
+smtpTransport = nodemailer.createTransport('SMTP',
+  service: 'Gmail'
+  auth:
+    user: 'server.tranzit@gmail.com'
+    pass: 'dev@tranzit')
+# function for sending out emails
+sendEmails = ->
+  mailOptions =
+    from: 'Tranzit Server ✔ <server.tranzit@gmail.com>'
+    to: 'aottinge@purdue.edu'
+    subject: 'Server Test ✔'
+    text: 'Hello from Tranzit Server ✔'
+    html: '<b>Hello from Tranzit Server ✔</b>'
+  smtpTransport.sendMail mailOptions, (error, response) ->
+    if error
+      console.log error

 # Set up server log for CLI
 winston.cli()
@@ -36,9 +55,19 @@
 # db initialization successful
 .then ->
   # parse JSON requests
-  app.use require('body-parser').json()
+  app.use require('body-parser').json()
+
   # mount root router, defined in routes.coffee
   app.use require('./routes.js')(db)
+
+  # send email to those who need to pick up a package at server startup
+  sendEmails
+  # send email everyday to people who have a package pending their pickup
+  sch = schedule.scheduleJob({
+    hour: 9
+    minute: 0
+  }, sendEmails)
+
   # begin listening for connections
   app.listen 3000, ->
     console.log 'Server listening on port 3000'


fix history issue, fix anchor link position

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -69,7 +69,8 @@
       currentRoute = Backbone.history.getFragment()
       canonicalPath = linksHelper.getPath('contents', {model: @model, page: pageId}, [])
       if (canonicalPath isnt "/#{currentRoute}")
-        router.navigate(canonicalPath, {replace: true})
+        # replace previous URL with the canonical path
+        history.replaceState({}, @model.get('title'), canonicalPath)

     updateTeacher: ($temp = @$el) ->
       $els = $temp.find('.os-teacher')
@@ -81,7 +82,8 @@

     goToPage: (pageNumber, href) ->
       @model.setPage(pageNumber)
-      router.navigate href, {trigger: false}, => @parent.parent.parent.trackAnalytics()
+
+      router.navigate href, {trigger: true, replace: false}, => @parent.parent.parent.trackAnalytics()

     getCoach: ->
       moduleUUID = @model.getUuid()?.split('?')[0]
@@ -439,8 +441,8 @@
         @jaxing = false
         @processCoachMath?()

-      # Update the hash fragment after the content has loaded
-      # to force the browser window to find the intended content
+      # Clear and replace the hash fragment after the content has loaded
+      # to force the browser window to find the intended content (as a side effect)
       jumpToHash = () =>
         if currentPage.get('loaded') and not @fragmentReloaded and window.location.hash
           @fragmentReloaded = true
@@ -448,11 +450,16 @@
           window.location.hash = ''
           window.location.hash = hash

+          # scroll up by width of nav bar
+          # FIXME - make this dynamic
+          window.scrollBy(0, -145)
+
       $target = $(window.location.hash)
       if $target.prop('tagName')?.toLowerCase() is 'iframe'
         $target.on('load', jumpToHash)
       else
         jumpToHash()
+

     changePage: (e) ->
       # Don't intercept cmd/ctrl-clicks intended to open a link in a new tab


Less regexps

Less regexps

--- webpack/dist/ls.coffee
+++ webpack/dist/ls.coffee
@@ -5,8 +5,6 @@
   .spawnSync

 args = process.argv.slice 3
-if /r^/i.test args[0]
-  args = []

 result = []

@@ -14,7 +12,7 @@
 .stdout
 .toString()
 .split /\r\n?|\n/
-  break unless /^(\W)\s+(\S+)(\s+|$)/.test s
+  break unless /^(\W)\s+(\S+)/.test s
   if !args.length and '>' == RegExp.$1
     result = [RegExp.$2]
     break


update tests for expecting summaryReport.

--- src/spec/integration/streaming.spec.coffee
+++ src/spec/integration/streaming.spec.coffee
@@ -40,7 +40,8 @@
       chunk = [
         { name: { en: 'myCat1' }, slug: { en: 'my-cat-1' }}
       ]
-      @streaming.processStream chunk, ->
+      @streaming.processStream chunk, =>
+        expect(@streaming.summaryReport()).toEqual 'Summary: there were 1 imported categories (1 were new and 0 were updates)'
         done()

     it 'should update an existing category', (done) ->
@@ -52,7 +53,8 @@
         chunk = [
           { externalId: '42', name: { en: 'myCatCHANGED' }, slug: { en: 'my-cat-3' }, orderHint: '0.1' }
         ]
-        @streaming.processStream chunk, ->
+        @streaming.processStream chunk, =>
+          expect(@streaming.summaryReport()).toEqual 'Summary: there were 2 imported categories (2 were new and 0 were updates)'
           done()

     it 'should create a tree of categories', (done) ->
@@ -63,5 +65,6 @@
         chunk = [
           { externalId: 'l1', name: { en: 'level1' }, slug: { en: 'l-1' }, parent: { id: 'root' }}
         ]
-        @streaming.processStream chunk, ->
+        @streaming.processStream chunk, =>
+          expect(@streaming.summaryReport()).toEqual 'Summary: there were 2 imported categories (2 were new and 0 were updates)'
           done()


[TEST] Tests for split/join filters

[TEST] Tests for split/join filters

--- test/filters.coffee
+++ test/filters.coffee
@@ -60,6 +60,17 @@
     it "uses toString", ->
       o = toString: -> "aString"
       expect(@filters.upcase(o)).to.equal "ASTRING"
+
+  describe "join", ->
+    it "joins arrays", ->
+      expect(@filters.join([1, 2])).to.equal "1 2"
+      expect(@filters.join([1, 2], "-")).to.equal "1-2"
+      expect(@filters.join([])).to.equal ""
+
+  describe "split", ->
+    it "splits strings", ->
+      expect(@filters.split("1-2-3", "-")).to.deep.equal ["1", "2", "3"]
+      expect(@filters.split("", "-")).to.not.exist

   describe "append", ->
     it "appends strings", ->


namespaced Movable

namespaced Movable

--- test/movable.coffee
+++ test/movable.coffee
@@ -8,7 +8,7 @@

   particle.include(Movable)

-  particle.update()
+  particle.update(1)

   equals particle.I.x, 51, "x position updated according to velocity"
   equals particle.I.y, 52, "y position updated according to velocity"


:sparkles: add scene name and camera name in renderer debugger

--- jThree/debug/components/renderer/renderer-panel-title.coffee
+++ jThree/debug/components/renderer/renderer-panel-title.coffee
@@ -11,6 +11,7 @@
         {@props.renderer.name}
         {@props.renderer.getTypeName()}

+
{@props.renderer.Camera.ParentScene.ID + "(" + @props.renderer.Camera.name + ")"}

{"ID " + @props.renderer.ID}


Expand core extension applyMixin test.

Expand core extension applyMixin test.

Account for once-methods.

--- tests/js/core.coffee
+++ tests/js/core.coffee
@@ -31,6 +31,7 @@
       setup: ->
         @.mixins = {}
         @.mixin =
+          decorate: -> @someOtherProperty = 'baz'
           someMethod: -> 'foo'
           someProperty: 'bar'
         @.dynamicMixin = (dependencies) ->
@@ -61,6 +62,10 @@
         'Mixin method should have been added to instance.'
       assert.strictEqual @instance.someMethod(), 'foo',
         'Mixin method should have been generated properly.'
+      assert.strictEqual @instance.decorate, undefined,
+        'Mixin once method should have been removed after invoking.'
+      assert.strictEqual @instance.someOtherProperty, 'baz',
+        'Mixin once method should have invoked properly.'

     QUnit.test 'applyMixin', (assert) ->
       result = hlf.createMixin @mixins, @mixinName, @mixin


Optimization for sprite temporary variables.

--- packages/landsofillusions-assets/engine/sprite/sprite.coffee
+++ packages/landsofillusions-assets/engine/sprite/sprite.coffee
@@ -1,5 +1,12 @@
 AM = Artificial.Mirage
 LOI = LandsOfIllusions
+
+# Create temporary objects and constants.
+_normal = new THREE.Vector3
+_backward = new THREE.Vector3 0, 0, 1
+_color = new THREE.Color
+_lightColor = new THREE.Color
+_averageNormal = new THREE.Vector3

 class LOI.Assets.Engine.Sprite
   constructor: (@options) ->
@@ -10,13 +17,6 @@

       true

-    # Create temporary objects and constants.
-    @_normal = new THREE.Vector3
-    @_backward = new THREE.Vector3 0, 0, 1
-    @_color = new THREE.Color
-    @_lightColor = new THREE.Color
-    @_averageNormal = new THREE.Vector3
-
   drawToContext: (context, renderOptions = {}) ->
     # HACK: Request sprite data already at the top since otherwise ready sometimes doesn't get recomputed in time.
     spriteData = @options.spriteData()
@@ -114,11 +114,11 @@
         if visualizeNormals
           # Visualized normals mode.
           if pixel.normal
-            @_normal.copy pixel.normal
-            @_normal.x *= -1 if flippedHorizontal
-
-            horizontalAngle = Math.atan2(@_normal.y, @_normal.x) + Math.PI
-            verticalAngle = @_normal.angleTo @_backward
+            _normal.copy pixel.normal
+            _normal.x *= -1 if flippedHorizontal
+
+            horizontalAngle = Math.atan2(_normal.y, _normal.x) + Math.PI
+            verticalAngle = _normal.angleTo _backward

             hue = horizontalAngle / (2 * Math.PI)
             saturation = verticalAngle / (Math.PI / 2)
@@ -129,8 +129,8 @@
             else
               lightness = 0.5

-            @_color.setHSL hue, saturation, lightness
-            destinationColor = @_color
+            _color.setHSL hue, saturation, lightness
+            destinationColor = _color

           else
             destinationColor = r: 0, g: 0, b: 0
@@ -219,13 +219,13 @@

             # Calculate diffuse lighting.
             if pixel.normal
-              @_normal.copy pixel.normal
-              @_normal.x *= -1 if flippedHorizontal
+              _normal.copy pixel.normal
+              _normal.x *= -1 if flippedHorizontal

             else
-              @_normal.set 0, 0, 1
-
-            normalLightProduct = THREE.Math.clamp @_normal.dot(inverseLightDirection), 0, 1
+              _normal.set 0, 0, 1
+
+            normalLightProduct = THREE.Math.clamp _normal.dot(inverseLightDirection), 0, 1

             lightDiffuseCoefficient = 0.6
             materialDiffuseCoefficient = 1
@@ -247,29 +247,29 @@
               smoothFactor = paletteColor.reflection.smoothFactor or 0

               # Average the normal.
-              @_averageNormal.set 0, 0, 0
+              _averageNormal.set 0, 0, 0

               for offsetX in [-smoothFactor..smoothFactor]
                 for offsetY in [-smoothFactor..smoothFactor]
                   if sampleNormal = layer._pixelMap[pixel.x + offsetX]?[pixel.y + offsetY]?.normal
-                    @_averageNormal.add sampleNormal
-
-              @_averageNormal.normalize()
-              @_averageNormal.x *= -1 if flippedHorizontal
-
-              averageNormalLightProduct = THREE.Math.clamp @_averageNormal.dot(inverseLightDirection), 0, 1
+                    _averageNormal.add sampleNormal
+
+              _averageNormal.normalize()
+              _averageNormal.x *= -1 if flippedHorizontal
+
+              averageNormalLightProduct = THREE.Math.clamp _averageNormal.dot(inverseLightDirection), 0, 1

               # Calculate the perfectly reflected ray.
-              reflection = @_averageNormal.clone().multiplyScalar(2 * averageNormalLightProduct).sub inverseLightDirection
+              reflection = _averageNormal.clone().multiplyScalar(2 * averageNormalLightProduct).sub inverseLightDirection

               # We assume the inverse view direction to be (0, 0, 1).
               # Dot product is then the equivalent of the z coordinate.
               reflectionViewProduct = THREE.Math.clamp reflection.z, 0, 1

               lightFactor = Math.pow(reflectionViewProduct, shininess) * lightSpecularCoefficient * materialSpecularCoefficient
-              @_lightColor.set(0xffffff).multiplyScalar lightFactor
-
-              shadedColor.add @_lightColor
+              _lightColor.set(0xffffff).multiplyScalar lightFactor
+
+              shadedColor.add _lightColor

             if palette
               # Find the nearest color from the palette to represent the shaded color.


Fix race condition when fetching meta profile that causes more than one instance to be initialized for an entity

--- lib/assets/javascripts/models/meta_profile.js.coffee
+++ lib/assets/javascripts/models/meta_profile.js.coffee
@@ -21,10 +21,18 @@

       constructorFn = @
       server_meta_post = res.post
-      model = new constructorFn(_.extend({
+
+      attrs = _.extend({
         entity: server_meta_post.content.entity
         avatar_digest: server_meta_post.attachments?[0]?.digest
-      }, server_meta_post.content.profile || {}))
+      }, server_meta_post.content.profile || {})
+
+      existing_model = constructorFn.find(entity, fetch: false)
+
+      if existing_model
+        existing_model.parseAttributes(attrs)
+      else
+        model = new constructorFn(attrs)

       @trigger("fetch:success", model, xhr)
       options.success?(model, xhr)


Replaces zip with ditto in pack:darwin64

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -75,11 +75,14 @@
     ]

     # Create the update archive
-    applySpawn 'zip', [
-      '-r9'
-      '../../dist/' + manifest.name + '-' + manifest.version + '-osx-update.zip'
-      manifest.productName + '.app'
-    ], {cwd: './build/darwin64'}
+    applySpawn 'ditto', [
+      '-c'
+      '-k'
+      '--sequesterRsrc'
+      '--keepParent'
+      './build/darwin64/' + manifest.productName + '.app'
+      './dist/' + manifest.name + '-' + manifest.version + '-osx-update.zip'
+    ]

     # Create the dmg
     (callback) ->


Measuring timeout size based on the env, local tests needs lot less time than remote ones via sauce connect.

--- tests/www/run.coffee
+++ tests/www/run.coffee
@@ -8,6 +8,9 @@
 (require 'chai').Assertion.includeStack = true

 exports.test = ( name, conf, base_url, mark_as_passed )->
+
+  timeout = if name is 'local' then 5000 else 60000
+
   describe name, ->

     for file in files
@@ -26,4 +29,4 @@
         browser_conf.name = browser_name
         browser = if conf then wd.remote conf else do wd.remote
         test = (require file).test
-        test browser, browser_conf, base_url, 60000, mark_as_passed+        test browser, browser_conf, base_url, timeout, mark_as_passed


grid tool bar manager fixes

grid tool bar manager fixes

--- bokehjs/src/coffee/common/grid_plot.coffee
+++ bokehjs/src/coffee/common/grid_plot.coffee
@@ -63,11 +63,6 @@
           @get('gestures')[et].tools.push(proxy)
           @listenTo(proxy, 'change:active', _.bind(@_active_change, proxy))

-        tools = @get('gestures')[et].tools
-        if tools.length > 0
-          @get('gestures')[et].tools = _.sortBy(tools, (tool) -> tool.get('default_order'))
-          gestures[et].tools[0].set('active', true)
-
       for typ, tools of actions
         if tools.length != @get('num_plots')
           continue
@@ -79,6 +74,13 @@
           continue
         proxy = new _ToolProxy({tools: tools})
         @get('inspectors').tools.push(proxy)
+
+      for et, info of @get('gestures')
+        tools = info.tools
+        if tools.length == 0
+          continue
+        info.tools = _.sortBy(tools, (tool) -> tool.get('default_order'))
+        info.tools[0].set('active', true)

     _active_change: (tool) =>
       et = tool.get('event_type')


Mark ButtonTool.disabled property as non-serializable

--- bokehjs/src/coffee/tool/button_tool.coffee
+++ bokehjs/src/coffee/tool/button_tool.coffee
@@ -38,7 +38,7 @@
     @register_property('tooltip', () ->@get('tool_name'))

   nonserializable_attribute_names: () ->
-    super().concat(['icon'])
+    super().concat(['icon', 'disabled'])

   defaults: () ->
     return _.extend({}, super(), {


Iterate over data structure for basic service tests

--- test/basic.coffee
+++ test/basic.coffee
@@ -5,18 +5,17 @@

 setup = require './setup'

-
 # test services
 services =
   'hello':
     serviceName: 'hello'
     service: ({}, done) ->
-      done null, {body: 'hello'}
+      done null, {greeting: 'hello'}

   'echo':
     serviceName: 'echo'
     service: ({}, done) ->
-      done()
+      done null, {}

 # test routes and response values
@@ -32,7 +31,7 @@
   {
     serviceName: 'echo'
     method: 'post'
-    path: '/'
+    path: '/echo'
     postData:
       x: 2
       y: 3
@@ -47,8 +46,6 @@

 describe 'with simple services wired to routes', () ->
   beforeEach (done) ->
-    # services = {}
-    # routes = {}
     {@app, @server, @url} = setup services, routes
     done()

@@ -56,16 +53,21 @@
     @server.close()
     done()

-  it 'should return with a 200 status', (done) ->
-    req = restler.get (url.resolve @url, '/hello?x=3')
-    should.exist req
+  for r in routes
+    do (r) ->
+      defaultDescription = "it should return expected values " +
+                           "for #{r.method.toUpperCase()} #{r.path}"
+      description = r.description || defaultDescription

-    req.on 'complete', (result, response) ->
-      console.log result
-      should.exist result, 'expected result to exist'
-      should.exist response, 'expected response to exist'
-      should.exist response.statusCode, 'expected statusCode to exist'
+      it description, (done) ->
+        req = restler[r.method] (url.resolve @url, r.path)
+        should.exist req

-      response.statusCode.should.equal 200
+        req.once 'complete', (result, response) ->
+          should.exist result, 'expected result to exist'
+          should.exist response, 'expected response to exist'
+          should.exist response.statusCode, 'expected statusCode to exist'

-      done()
+          response.statusCode.should.equal r.expected.statusCode
+
+          done()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color.coffee
+++ test/color.coffee
@@ -6,11 +6,18 @@
   equals color.a(), 0, "default alpha channel is 0"

 test "Color should parse rgb", ->
-  color = Color('rgb(34, 56, 74)')
-  equals color.r(), 34, 'red channel should equal input red value'
+  color = Color('rgb(3, 56, 174)')
+  equals color.r(), 3, 'red channel should equal input red value'
   equals color.g(), 56, 'green channel should equal input green value'
-  equals color.b(), 74, 'blue channel should equal input blue value'
+  equals color.b(), 174, 'blue channel should equal input blue value'

+test "Color should parse rgba", ->
+  color = Color('rgb(4, 66, 134, 0.45)')
+  equals color.r(), 4, 'red channel should equal input red value'
+  equals color.g(), 66, 'green channel should equal input green value'
+  equals color.g(), 134, 'blue channel should equal input blue value'
+  equals color.a(), 0.45, 'alpha channel should equal input alpha value'
+
 ###
 test "Color should parse hex starting with #", ->
   color = Color('#ffffff')


new API – tests

new API – tests

--- test/index.coffee
+++ test/index.coffee
@@ -1,94 +1,59 @@
 assert =			require 'assert'
 sinon =				require 'sinon'

-TtlBuffer =			require '../index.js'
+ttlBuffer =			require '../index.js'

-describe 'TtlBuffer', ->
+describe 'ttlBuffer', ->

-	b = null
-	beforeEach -> b = Object.create TtlBuffer
+	clock = null
+	before -> clock = sinon.useFakeTimers()
+	after -> clock.restore()

-	describe '::init', ->
+	it 'uses the `initialValue` passed', ->
+		b = ttlBuffer initialValue: 1
+		assert.strictEqual b + 0, 1

-		it 'should set the ttl', ->
-			b.init 100, 10
-			assert.strictEqual b.ttl, 100
+	it 'accepts and call a custom `in` function', ->
+		spy = sinon.spy()
+		b = ttlBuffer in: spy
+		b.push 1
+		assert.strictEqual spy.callCount, 1

-		it 'should set the inital value', ->
-			b.init 100, 10
-			assert.strictEqual b.valueOf(), 10
+	it 'accepts and call a custom `out` function after the `ttl`', ->
+		spy = sinon.spy()
+		b = ttlBuffer out: spy, ttl: 1000
+		b.push 1
+		clock.tick 1000
+		assert.strictEqual spy.callCount, 1

-		it 'should return the instance', ->
-			result = b.init 100, 10
-			assert.strictEqual result, b
+	it 'uses the return value of the `in` function as new value', ->
+		b = ttlBuffer in: (-> 3), initialValue: 0
+		assert.notStrictEqual b + 0, 3
+		b.push 0
+		assert.strictEqual b + 0, 3

+	it 'uses the return value of the `out` function as new value', ->
+		b = ttlBuffer out: (-> 3), initialValue: 0
+		b.push 0
+		assert.notStrictEqual b + 0, 3
+		clock.tick 1000
+		assert.strictEqual b + 0, 3

+	it 'call the `in` function with the previous value & the entry', ->
+		spy = sinon.spy()
+		ttlBuffer(in: spy, initialValue: 1).push 2
+		assert.strictEqual spy.callCount, 1
+		assert.deepStrictEqual spy.firstCall.args, [1, 2]

-	describe '::add', ->
-
-		it 'should call `in` with the passed value', ->
-			b.init 100, 'foo'
-			b.in = sinon.spy (value, entry) -> value # noop reducer
-
-			b.add 'bar'
-			assert.strictEqual b.in.callCount, 1
-			assert.deepStrictEqual b.in.firstCall.args, ['foo', 'bar']
-
-		it 'should use the return value of `in` as new value', ->
-			secret = Math.random()
-			b.init 100, 10
-			b.in = -> secret
-
-			b.add 'foo'
-			assert.strictEqual b.valueOf(), secret
-
-		it 'should return the instance', ->
-			b.init 100, 'foo'
-			result = b.add 'bar'
-			assert.strictEqual result, b
-
-
-
-	describe '::in (default reducer)', ->
-
-		it 'should add the entry to the value', ->
-			result = b.in 1, 2
-			assert.strictEqual result, 3
-
-	describe '::out (default reducer)', ->
-
-		it 'should subtract the entry from the value', ->
-			result = b.out 3, 2
-			assert.strictEqual result, 1
-
-
-
-	describe '::valueOf', ->
-
-		clock = null
-		before -> clock = sinon.useFakeTimers()
-		after -> clock.restore()
-
-		it 'should return the value', ->
-			b.init 100, 'foo'
-			assert.strictEqual b.valueOf(), 'foo'
-
-		it 'should throw out outdated entries', ->
-			b.init 1000, 10
-			b.add 1
-			b.add 2
-			clock.tick 500
-			b.add 3
-
-			clock.tick 400
-			assert.strictEqual b.valueOf(), 10 + 1 + 2 + 3
-			clock.tick 200 # `1` & `2` get thrown out
-			assert.strictEqual b.valueOf(), 10 + 3
-			clock.tick 500 # `3` gets thrown out
-			assert.strictEqual b.valueOf(), 10
+	it 'call the `out` function with the previous value & the entry', ->
+		spy = sinon.spy()
+		ttlBuffer(in: (-> 2), out: spy, ttl: 300).push 1
+		clock.tick 300
+		assert.strictEqual spy.callCount, 1
+		assert.deepStrictEqual spy.firstCall.args, [2, 1]


fix #248

fix #248

--- lib/atom-react.coffee
+++ lib/atom-react.coffee
@@ -41,63 +41,63 @@
   constructor: ->
   patchEditorLangModeAutoDecreaseIndentForBufferRow: (editor) ->
     self = this
-    fn = editor.languageMode.autoDecreaseIndentForBufferRow
+    fn = editor.autoDecreaseIndentForBufferRow
     return if fn.jsxPatch

-    editor.languageMode.autoDecreaseIndentForBufferRow = (bufferRow, options) ->
-      return fn.call(editor.languageMode, bufferRow, options) unless editor.getGrammar().scopeName == "source.js.jsx"
-
-      scopeDescriptor = @editor.scopeDescriptorForBufferPosition([bufferRow, 0])
-      decreaseNextLineIndentRegex = @cacheRegex(atom.config.get('react.decreaseIndentForNextLinePattern') || decreaseIndentForNextLinePattern)
-      decreaseIndentRegex = @decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
-      increaseIndentRegex = @increaseIndentRegexForScopeDescriptor(scopeDescriptor)
-
-      precedingRow = @buffer.previousNonBlankRow(bufferRow)
+    editor.autoDecreaseIndentForBufferRow = (bufferRow, options) ->
+      return fn.call(editor, bufferRow, options) unless editor.getGrammar().scopeName == "source.js.jsx"
+
+      scopeDescriptor = @scopeDescriptorForBufferPosition([bufferRow, 0])
+      decreaseNextLineIndentRegex = @tokenizedBuffer.regexForPattern(atom.config.get('react.decreaseIndentForNextLinePattern') || decreaseIndentForNextLinePattern)
+      decreaseIndentRegex = @tokenizedBuffer.decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
+      increaseIndentRegex = @tokenizedBuffer.increaseIndentRegexForScopeDescriptor(scopeDescriptor)
+
+      precedingRow = @tokenizedBuffer.buffer.previousNonBlankRow(bufferRow)

       return if precedingRow < 0

-      precedingLine = @buffer.lineForRow(precedingRow)
-      line = @buffer.lineForRow(bufferRow)
+      precedingLine = @tokenizedBuffer.buffer.lineForRow(precedingRow)
+      line = @tokenizedBuffer.buffer.lineForRow(bufferRow)

       if precedingLine and decreaseNextLineIndentRegex.testSync(precedingLine) and
          not (increaseIndentRegex and increaseIndentRegex.testSync(precedingLine)) and
-         not @editor.isBufferRowCommented(precedingRow)
+         not @isBufferRowCommented(precedingRow)
         currentIndentLevel = @editor.indentationForBufferRow(precedingRow)
         currentIndentLevel -= 1 if decreaseIndentRegex and decreaseIndentRegex.testSync(line)
         desiredIndentLevel = currentIndentLevel - 1
         if desiredIndentLevel >= 0 and desiredIndentLevel < currentIndentLevel
           @editor.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
-      else if not @editor.isBufferRowCommented(bufferRow)
-        fn.call(editor.languageMode, bufferRow, options)
+      else if not @isBufferRowCommented(bufferRow)
+        fn.call(editor, bufferRow, options)

   patchEditorLangModeSuggestedIndentForBufferRow: (editor) ->
     self = this
-    fn = editor.languageMode.suggestedIndentForBufferRow
+    fn = editor.suggestedIndentForBufferRow
     return if fn.jsxPatch

-    editor.languageMode.suggestedIndentForBufferRow = (bufferRow, options) ->
-      indent = fn.call(editor.languageMode, bufferRow, options)
+    editor.suggestedIndentForBufferRow = (bufferRow, options) ->
+      indent = fn.call(editor, bufferRow, options)
       return indent unless editor.getGrammar().scopeName == "source.js.jsx" and bufferRow > 1

-      scopeDescriptor = @editor.scopeDescriptorForBufferPosition([bufferRow, 0])
-
-      decreaseNextLineIndentRegex = @cacheRegex(atom.config.get('react.decreaseIndentForNextLinePattern') || decreaseIndentForNextLinePattern)
-      increaseIndentRegex = @increaseIndentRegexForScopeDescriptor(scopeDescriptor)
-
-      decreaseIndentRegex = @decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
-      tagStartRegex = @cacheRegex(atom.config.get('react.jsxTagStartPattern') || jsxTagStartPattern)
-      complexAttributeRegex = @cacheRegex(atom.config.get('react.jsxComplexAttributePattern') || jsxComplexAttributePattern)
-
-      precedingRow = @buffer.previousNonBlankRow(bufferRow)
+      scopeDescriptor = @scopeDescriptorForBufferPosition([bufferRow, 0])
+
+      decreaseNextLineIndentRegex = @tokenizedBuffer.regexForPattern(atom.config.get('react.decreaseIndentForNextLinePattern') || decreaseIndentForNextLinePattern)
+      increaseIndentRegex = @tokenizedBuffer.increaseIndentRegexForScopeDescriptor(scopeDescriptor)
+
+      decreaseIndentRegex = @tokenizedBuffer.decreaseIndentRegexForScopeDescriptor(scopeDescriptor)
+      tagStartRegex = @tokenizedBuffer.regexForPattern(atom.config.get('react.jsxTagStartPattern') || jsxTagStartPattern)
+      complexAttributeRegex = @tokenizedBuffer.regexForPattern(atom.config.get('react.jsxComplexAttributePattern') || jsxComplexAttributePattern)
+
+      precedingRow = @tokenizedBuffer.buffer.previousNonBlankRow(bufferRow)

       return indent if precedingRow < 0

-      precedingLine = @buffer.lineForRow(precedingRow)
+      precedingLine = @tokenizedBuffer.buffer.lineForRow(precedingRow)

       return indent if not precedingLine?

-      if @editor.isBufferRowCommented(bufferRow) and @editor.isBufferRowCommented(precedingRow)
-        return @editor.indentationForBufferRow(precedingRow)
+      if @isBufferRowCommented(bufferRow) and @isBufferRowCommented(precedingRow)
+        return @indentationForBufferRow(precedingRow)

       tagStartTest = tagStartRegex.testSync(precedingLine)
       decreaseIndentTest = decreaseIndentRegex.testSync(precedingLine)


Update tests to match .getAuthorFromRequest.

--- test/slack.coffee
+++ test/slack.coffee
@@ -177,8 +177,6 @@
     author.should.include
       id: 37
       name: 'Luke'
-      reply_to: 760
-      room: 'Home'

   it 'Should ignore blacklisted rooms', ->
     process.env.HUBOT_SLACK_CHANNELMODE = 'blacklist'


Improved linter speed

Improved linter speed

--- lib/dub-linter.coffee
+++ lib/dub-linter.coffee
@@ -22,7 +22,7 @@
     lint: (textEditor) =>
       return new Promise (resolve, reject) =>
         output = ""
-        args = ["build", "--nodeps", "--root=" + atom.project.getPaths()[0]]
+        args = ["build", "--nodeps", "--combined", "--root=" + atom.project.getPaths()[0]]
         # --root= instead of cwd for absolute file paths

         if global.buildName?


Callback.PreparedContext tests skeleton

Callback.PreparedContext tests skeleton

--- test/callbackSpec.coffee
+++ test/callbackSpec.coffee
@@ -43,4 +43,22 @@
         describe 'valid javascript in context', ->
           xit 'pending'

+      describe '#PreparedContext', ->
+        describe 'no callback', ->
+          xit 'pending'

+        describe 'empty callback', ->
+          xit 'pending'
+
+        describe 'one function in callback', ->
+          xit 'pending'
+
+        describe 'multiple functions in callback', ->
+          xit 'pending'
+
+        describe 'abracadabra in context', ->
+          xit 'pending'
+
+        describe 'valid javascript in context', ->
+          xit 'pending'
+


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp_bounds.coffee
+++ test/clamp_bounds.coffee
@@ -12,6 +12,6 @@
   player.I.x = 800
   player.update()

-  equals player.I.x,
+  equals player.I.x, App

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

-test "should move player "
+test "should move player acco"

 module()


testfix

testfix

--- test/devices_test.coffee
+++ test/devices_test.coffee
@@ -194,7 +194,7 @@

             it "Then 200 is returned as status code", ->
                 should.not.exist @body.error
-                @res.statusCode.should.equal 200
+                @res.statusCode.should.equal 204

             it "Delete an other device", (done) ->
                 @timeout 10 * 1000
@@ -207,4 +207,4 @@

             it "Then 200 is returned as status code", ->
                 should.not.exist @body.error
-                @res.statusCode.should.equal 200+                @res.statusCode.should.equal 204


hconfigure: improve audit

hconfigure: improve audit

--- lib/hconfigure.coffee
+++ lib/hconfigure.coffee
@@ -74,13 +74,14 @@
         updated = true
       do_save()
     do_save = ->
-      return next() unless updated
-      options.log? "Save properties"
+      # return next() unless updated
+      # options.log? "Save properties"
       options.content = properties.stringify fnl_props
       options.source = null
       mecano.write options, (err, written) ->
-        next err, true
-    conditions.all options, next, do_read_source
+        updated = true if written
+        next err, updated
+    do_read_source()


more dem stuff

more dem stuff

--- @client/homepage.coffee
+++ @client/homepage.coffee
@@ -21,9 +21,9 @@
     DIV
       style:
         width: '100%'
-        backgroundColor: '#545454'
+        #backgroundColor: '#545454'
         padding: '32px 20px'
-        color: 'white'
+        #color: 'white'
         marginTop: 12

       DIV
@@ -48,39 +48,39 @@
               border: 'none'
               fontWeight: 800
               textDecoration: 'underline'
-              color: 'white'
+              #color: 'white'
               textTransform: 'lowercase'
               padding: 0
             t('create an account')

           ' before participating!'

-        DIV
-          style:
-            fontSize: 18
-            fontWeight: 500
-            marginTop: 20
-            color: 'white'
-
-          '(if you already have a Consider.it account, you can '
-
-          BUTTON
-            'data-action': 'login'
-            onClick: (e) =>
-              reset_key 'auth',
-                form: 'login'
-                ask_questions: true
-            style:
-              backgroundColor: 'transparent'
-              border: 'none'
-              fontWeight: 800
-              textDecoration: 'underline'
-              padding: 0
-              color: 'white'
-              textTransform: 'lowercase'
-            t('log_in')
-
-          ' instead)'
+        # DIV
+        #   style:
+        #     fontSize: 18
+        #     fontWeight: 500
+        #     marginTop: 20
+        #     color: 'white'
+
+        #   '(if you already have a Consider.it account, you can '
+
+        #   BUTTON
+        #     'data-action': 'login'
+        #     onClick: (e) =>
+        #       reset_key 'auth',
+        #         form: 'login'
+        #         ask_questions: true
+        #     style:
+        #       backgroundColor: 'transparent'
+        #       border: 'none'
+        #       fontWeight: 800
+        #       textDecoration: 'underline'
+        #       padding: 0
+        #       color: 'white'
+        #       textTransform: 'lowercase'
+        #     t('log_in')
+
+        #   ' instead)'

 window.Homepage = ReactiveComponent


:lipstick: :racehorse: Cleanup.

:lipstick: :racehorse: Cleanup.

- Just a little cleanup.  Makes sense to put the input handlers in one
  place and after the rendering of the labels (Perceived speed?).
  Probably only Minimal perf issue at best I'm sure.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -131,20 +131,12 @@
         @statusBarJumpyStatus =
             document.querySelector '#status-bar-jumpy .status'

-        $(@workspaceElement).find '*'
-            .on 'mousedown', =>
-                @clear()
-
         @allPositions = {}
         nextKeys = _.clone keys
         atom.workspace.observeTextEditors (editor) =>
             editorView = atom.views.getView(editor)
             return if editorView.style.display is 'none'

-            editor.onDidChangeScrollTop =>
-                @clear()
-            editor.onDidChangeScrollLeft =>
-                @clear()

             $(editorView).addClass 'jumpy-jump-mode'
             $labels = $(editorView).find '.overlayer'
@@ -183,6 +175,15 @@
                     while ((word = wordsPattern.exec(lineContents)) != null)
                         drawLabels word.index

+            $(@workspaceElement).find '*'
+                .on 'mousedown', =>
+                    @clear()
+
+            editor.onDidChangeScrollTop =>
+                @clear()
+            editor.onDidChangeScrollLeft =>
+                @clear()
+
     clearJumpMode: -> # TODO: Why not just move this into @clear?
         @clearKeys()
         @statusBarJumpy?.innerHTML = ''


Fixes require if needed to (hopefully) address #65.

--- lib/latextools.coffee
+++ lib/latextools.coffee
@@ -305,16 +305,17 @@
   # Private: ensure modules are loaded on demand
   requireIfNeeded: (modules) ->
     # ltConsole is needed by all, so load it
-    unless LTConsole?
-      LTConsole = require './ltconsole'
-      @ltConsole = new LTConsole @state.ltConsoleState
+    LTConsole ?= require './ltconsole'
+    @ltConsole ?= new LTConsole @state.ltConsoleState

     for m in modules
       console.log("requiring if needed: #{m}")
       switch m
         when "viewer"
-          unless Viewer?
-            ViewerRegistry = require './viewer-registry'
+          ViewerRegistry ?= require './viewer-registry'
+          Viewer ?= require './viewer'
+
+          unless @viewerRegistry?
             @viewerRegistry = new ViewerRegistry

             @viewerRegistry.add 'pdf-view',
@@ -331,13 +332,15 @@
                 @viewerRegistry.add ['default', 'okular'],
                   require './viewers/okular-viewer'

-            Viewer = require './viewer'
-            @viewer = new Viewer @viewerRegistry, @ltConsole
+          @viewer ?= new Viewer @viewerRegistry, @ltConsole
         when "builder"
-          unless Builder?
-            Builder = require './builder'
+          Builder ?= require './builder'
+          unless @builder?
             @builder = new Builder(@ltConsole)
-            @builder.viewer = @viewer # NOTE: MUST be loaded first!
+
+            # ensure viewer is loaded before builder
+            requireIfNeeded ['viewer'] unless @viewer?
+            @builder.viewer = @viewer
         when "completion-manager"
           CompletionManager ?= require './completion-manager'
           @completionManager ?= new CompletionManager(@ltConsole)


:lipstick: Reorganize methods

:lipstick: Reorganize methods

--- lib/table-view.coffee
+++ lib/table-view.coffee
@@ -131,6 +131,8 @@
   #    ##    ##  ##     ## ##  ##  ## ##    ##
   #    ##     ##  #######   ###  ###   ######

+  isActiveRow: (row) -> @activeCellPosition.row is row
+
   isSelectedRow: (row) ->
     row >= @selection.start.row and row <= @selection.end.row

@@ -162,8 +164,6 @@
     scrollViewHeight = @body.height()

     @findRowAtScreenPosition(@body.scrollTop() + scrollViewHeight) ? @table.getRowsCount() - 1
-
-  isActiveRow: (row) -> @activeCellPosition.row is row

   makeRowVisible: (row) ->
     rowHeight = @getRowHeightAt(row)
@@ -323,6 +323,8 @@

   isActiveCell: (cell) -> @getActiveCell() is cell

+  isSelectedCell: (cell) -> @iseSelectedPosition(@table.positionOfCell(cell))
+
   activateCell: (cell) ->
     @activateCellAtPosition(@table.positionOfCell(cell))

@@ -551,11 +553,6 @@
   #    ##    ## ##       ##       ##       ##    ##    ##
   #     ######  ######## ######## ########  ######     ##

-  getSelection: -> @selection
-
-  isSelectedCell: (cell) ->
-    @iseSelectedPosition(@table.positionOfCell(cell))
-
   isSelectedPosition: (position) ->
     position = Point.fromObject(position)

@@ -563,6 +560,8 @@
     position.row <= @selection.end.row and
     position.column >= @selection.start.column and
     position.column <= @selection.end.column
+
+  getSelection: -> @selection

   setSelection: (selection) ->
     @selection = Range.fromObject(selection)


Add some specs to expect errors.

Add some specs to expect errors.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -145,6 +145,8 @@
             '"a"[bottom] + [vgap] == "q-1"[top]'
             '"q-1"[bottom] + [vgap] == "_fallout"[top]'
           ]
+
+    expectError '@h [#b1[#b2];'

@@ -231,6 +233,8 @@
             '#sub1[right] + 8 == #sub2[left]'
             '#sub2[right] + [baseline] == #parent[right]'
           ]
+
+    expectError '@h |-[#box]-;'


Fix user page not rendering for guest

Fix user page not rendering for guest

--- resources/assets/coffee/react/profile-page/detail-bar.coffee
+++ resources/assets/coffee/react/profile-page/detail-bar.coffee
@@ -23,7 +23,7 @@

   render: =>
     el Observer, null, =>
-      isBlocked = _.find(core.currentUser.blocks, target_id: @props.user.id)?
+      isBlocked = core.currentUser? && _.find(core.currentUser.blocks, target_id: @props.user.id)?
       expanded = core.userPreferences.get('ranking_expanded')

       div className: bn,
@@ -54,7 +54,7 @@
               modifiers: ['profile-page']
               alwaysVisible: true

-          if core.currentUser.id != @props.user.id && !isBlocked
+          if !core.currentUser? || (core.currentUser.id != @props.user.id && !isBlocked)
             div className: "#{bn}__entry",
               a
                 className: 'user-action-button user-action-button--profile-page'


Anchors bug fixes

Anchors bug fixes

--- impl/base/level1/anchors.coffee
+++ impl/base/level1/anchors.coffee
@@ -192,17 +192,27 @@

 	onChildInsert = (child) ->
 		child.onVisibleChange @update, @
-		child.onWidthChange @update, @
-		child.onHeightChange @update, @
-		child.onXChange @update, @
-		child.onYChange @update, @
+		if @source is 'fillWidthSize'
+			child.onXChange @update, @
+			child.onWidthChange @update, @
+		if @source is 'fillHeightSize'
+			child.onYChange @update, @
+			child.onHeightChange @update, @
+
+		@update()
+		return

 	onChildPop = (child) ->
 		child.onVisibleChange.disconnect @update, @
-		child.onWidthChange.disconnect @update, @
-		child.onHeightChange.disconnect @update, @
-		child.onXChange.disconnect @update, @
-		child.onYChange.disconnect @update, @
+		if @source is 'fillWidthSize'
+			child.onXChange.disconnect @update, @
+			child.onWidthChange.disconnect @update, @
+		if @source is 'fillHeightSize'
+			child.onYChange.disconnect @update, @
+			child.onHeightChange.disconnect @update, @
+
+		@update()
+		return

 	class Anchor
 		constructor: (@item, @source, def) ->
@@ -256,16 +266,36 @@
 			Object.preventExtensions @

 		update: ->
-			if @targetItem
+			# sometimes it could be already destroyed
+			unless @item
+				return
+
+			# targetItem can be possibly different than actual value;
+			# e.g. when Anchor listener to change targetItem is not called firstly
+			switch @target
+				when 'parent'
+					targetItem = @item._parent
+				when 'children'
+					targetItem = @item._children
+				when 'nextSibling'
+					targetItem = @item._nextSibling
+				when 'previousSibling'
+					targetItem = @item._previousSibling
+				else
+					{targetItem} = @
+			if targetItem isnt @targetItem
+				return
+
+			if targetItem
 				`//<development>`
-				if @targetItem isnt @item._children and @item._parent isnt @targetItem and @item._parent isnt @targetItem._parent
+				if targetItem isnt @item._children and @item._parent isnt targetItem and @item._parent isnt targetItem._parent
 					log.error "You can anchor only to a parent or sibling. Item: #{@item.toString()}"
 				`//</development>`

-				r = @getSourceValue(@item) + @getTargetValue(@targetItem)
+				r = @getSourceValue(@item) + @getTargetValue(targetItem)
 			else
 				r = 0
-			if (margin = @item._margin) and @targetItem isnt @item._children
+			if (margin = @item._margin) and targetItem isnt @item._children
 				r += getMarginValue[@source] margin
 			@item[@prop] = r
 			return
@@ -290,6 +320,8 @@
 			if @targetItem
 				for handler in getTargetWatchProps[@line][@type]
 					@targetItem[handler].disconnect @update, @
+
+			@item = @targetItem = null
 			return

 	getBaseAnchors =


mocks

mocks

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -38,25 +38,25 @@

   describe "git.add", ->
     it "calls git.cmd with ['add', '--all', {fileName}]", ->
-      cmd = mock(git, 'cmd').do () -> Promise.resolve true
+      spyOn(git, 'cmd').andCallFake () -> Promise.resolve true
       waitsForPromise ->
         repo = git.getSubmodule(pathToSubmoduleFile)
         git.add(repo, file: pathToSubmoduleFile).then (success) ->
-          cmd.verifyCalledWith(['add', '--all', pathToSubmoduleFile], cwd: repo.getWorkingDirectory())
+          expect(git.cmd).toHaveBeenCalledWith(['add', '--all', pathToSubmoduleFile], cwd: repo.getWorkingDirectory())

     it "calls git.cmd with ['add', '--all', '.'] when no file is specified", ->
-      cmd = mock(git, 'cmd').do () -> Promise.resolve true
+      spyOn(git, 'cmd').andCallFake () -> Promise.resolve true
       waitsForPromise ->
         repo = git.getSubmodule(pathToSubmoduleFile)
         git.add(repo).then (success) ->
-          cmd.verifyCalledWith(['add', '--all', '.'], cwd: repo.getWorkingDirectory())
+          expect(git.cmd).toHaveBeenCalledWith(['add', '--all', '.'], cwd: repo.getWorkingDirectory())

     it "calls git.cmd with ['add', '--update'...] when update option is true", ->
-      cmd = mock(git, 'cmd').do () -> Promise.resolve true
+      spyOn(git, 'cmd').andCallFake () -> Promise.resolve true
       waitsForPromise ->
         repo = git.getSubmodule(pathToSubmoduleFile)
         git.add(repo, update: true).then (success) ->
-          cmd.verifyCalledWith(['add', '--update', '.'], cwd: repo.getWorkingDirectory())
+          expect(git.cmd).toHaveBeenCalledWith(['add', '--update', '.'], cwd: repo.getWorkingDirectory())

   describe "git.reset", ->
     it "resets and unstages all files", ->


Flex Handle: Accessibility improvement so it's possible to open the flex handle using the keyboard. See #60818


Make words more consistent

Make words more consistent

--- lib/whitespace.coffee
+++ lib/whitespace.coffee
@@ -11,7 +11,7 @@
       buffer.transact ->
         buffer.scan /[ \t]+$/g, ({match, replace}) ->
           # GFM permits two whitespaces at the end of a line--trim anything else
-          unless editSession.getGrammar().scopeName is "source.gfm" and match[0] == "  "
+          unless editSession.getGrammar().scopeName is "source.gfm" and match[0] is "  "
             replace('')

         if config.get('whitespace.ensureSingleTrailingNewline')


Regions: findLocation helper on top of geoip.lookup

--- node_modules_koding/koding-regions/lib/regions.coffee
+++ node_modules_koding/koding-regions/lib/regions.coffee
@@ -67,6 +67,17 @@
     }

+  @findLocation = (ip)->
+
+    geo = geoip.lookup ip
+
+    return geo or {
+      country : "Earth"
+      region  : "Somewhere"
+      ip
+    }
+
+
   @findRegion = (ip, regions = SUPPORTED_REGIONS)->

     geo = geoip.lookup ip


add XSS test case

add XSS test case

--- test/tests.coffee
+++ test/tests.coffee
@@ -26,9 +26,14 @@
   strictEqual $elem.contents().size(), 1, 'should contain a H2 element'
   strictEqual $elem.text(), 'heading', 'should contain a text'

+test 'create an element with XSS text', ->
+  expect 1
+  $elem = $.egen('code#XSS', null, '<script>alert("XSS!");</script>')
+  strictEqual $elem.find('script').size(), 0, 'should not contain script element'
+
 test 'throws for illegal query', ->
   throws ->
-    $.egen('##illegalId')
+    $.egen('##illegalQuery##')
   , /is not a valid token./, 'raised error message contains "is not a valid token"'
   throws ->
     $.egen('#unsupported:token')


Fix parsing attachment and block html

Fix parsing attachment and block html

--- src/trix/utilities/html_parser.coffee
+++ src/trix/utilities/html_parser.coffee
@@ -38,20 +38,19 @@
         @processElementNode(node)

   appendBlockForNode: (node) ->
-    if @currentBlockElement?
-      unless @currentBlockElement.contains(node)
-        @appendBlockForAttributes({})
-        delete @currentBlockElement
-
-    if node.nodeType is Node.ELEMENT_NODE
-      if window.getComputedStyle(node).display is "block"
-        switch node.tagName.toLowerCase()
-          when "blockquote"
-            @appendBlockForAttributes(quote: true)
-            @currentBlockElement = node
-          when "pre"
-            @appendBlockForAttributes(code: true)
-            @currentBlockElement = node
+    unless @currentBlockElement?.contains(node)
+      if node.nodeType is Node.ELEMENT_NODE
+        if window.getComputedStyle(node).display is "block"
+          switch node.tagName.toLowerCase()
+            when "blockquote"
+              @appendBlockForAttributes(quote: true)
+              @currentBlockElement = node
+            when "pre"
+              @appendBlockForAttributes(code: true)
+              @currentBlockElement = node
+            when "div"
+              @appendBlockForAttributes()
+              @currentBlockElement = node

     unless @blocks.length
       @appendBlockForAttributes({})
@@ -66,18 +65,14 @@
       when "br"
         unless nodeIsExtraBR(node)
           @appendStringWithAttributes("\n", getAttributes(node))
-      when "img"
-        attributes = getAttributes(node)
-        attributes.url = node.getAttribute("src")
-        attributes[key] = value for key in ["width", "height"] when value = node.getAttribute(key)
-        if figure = Trix.DOM.closest(node, "figure.attachment")
-          attributes[key] = value for key, value of getMetadata(figure)
-        @appendAttachmentForAttributes(attributes)
       when "figure"
-        if node.classList.contains("attachment") and node.classList.contains("file")
-          attributes = getAttributes(node)
-          attributes[key] = value for key, value of getMetadata(node)
-          @appendAttachmentForAttributes(attributes)
+        if node.classList.contains("attachment")
+          attributes = getMetadata(node)
+          textAttributes = getAttributes(node)
+          if image = node.querySelector("img")
+            textAttributes.width = image.width if image.width?
+            textAttributes.height = image.height if image.height?
+          @appendAttachmentForAttributes(attributes, textAttributes)
           # We have everything we need so avoid processing inner nodes
           node.innerHTML = ""

@@ -90,27 +85,20 @@
     text = Trix.Text.textForStringWithAttributes(string, attributes)
     @appendText(@text.appendText(text))

-  appendAttachmentForAttributes: (attributes) ->
-    if managedAttachment = @findManagedAttachmentByAttributes(attributes)
-      attachment = managedAttachment.attachment
+  appendAttachmentForAttributes: (attributes, textAttributes) ->
+    attachment = if @attachments and attributes.id
+      @attachments.get(attributes.id)
     else
-      attachment = new Trix.Attachment
+      delete attributes.id
+      new Trix.Attachment attributes

-    text = Trix.Text.textForAttachmentWithAttributes(attachment, attributes)
+    text = Trix.Text.textForAttachmentWithAttributes(attachment, textAttributes)
     @appendText(@text.appendText(text))

   appendText: (@text) ->
     index = @blocks.length - 1
     block = @blocks[index]
     @blocks[index] = block.copyWithText(@text)
-
-  findManagedAttachmentByAttributes: (attributes) ->
-    return unless @attachments
-    {identifier, url} = attributes
-    if identifier?
-      @attachments.findWhere({identifier})
-    else if url?
-      @attachments.findWhere({url})

   getDocument: ->
     new Trix.Document @blocks
@@ -120,12 +108,13 @@
     style = window.getComputedStyle(element)

     for attribute, config of Trix.attributes
-      if config.parser
-        if value = config.parser({element, style})
-          attributes[attribute] = value
-      else if config.tagName
-        if element.tagName?.toLowerCase() is config.tagName
-          attributes[attribute] = true
+      unless config.block
+        if config.parser
+          if value = config.parser({element, style})
+            attributes[attribute] = value
+        else if config.tagName
+          if element.tagName?.toLowerCase() is config.tagName
+            attributes[attribute] = true
     attributes

   getMetadata = (element) ->


Test: FlexSplit: Flex test added

Test: FlexSplit: Flex test added


Make it work with Windows Again

Make it work with Windows Again

--- src/Bootstrap.coffee
+++ src/Bootstrap.coffee
@@ -19,7 +19,7 @@
     controllerPath: "app/controllers/server"
     modelPath: "app/models/server"
     configPath: "app/config/server/config"
-    templatePath: "app/templates"
+    templatePath: ["app", "templates"].join(path.sep)
     publicPath: "public"
     port: 80
     env: "production"
@@ -116,7 +116,7 @@
     files = []
     @metadata().templates = {}

-    dirname = "#{Salad.root}/#{@options.templatePath}"
+    dirname = [Salad.root, @options.templatePath].join(path.sep)

     unless fs.existsSync dirname
       throw new Error "Templates folder does not exist! #{dirname}"
@@ -126,7 +126,7 @@
         file = path.normalize(file)
         index = file
           .replace(path.normalize(dirname), "")
-          .replace("\\", "/")
+          .replace(/\\/g, "/")
           .replace(/\/(server|shared)\//, "")

         @metadata().templates[index] = content.toString()


Update hapi.coffee

Update hapi.coffee

--- demo/hapi.coffee
+++ demo/hapi.coffee
@@ -6,16 +6,16 @@
 await server.register(inert) catch (err.registrationFailure)

 server.route(*) =
-	method: 'GET'
-	path: '/hello'
-	handler: (request, reply) -> reply.file('./public/hello.html')
+    method: 'GET'
+    path: '/hello'
+    handler: (request, reply) -> reply.file('./public/hello.html')

 server.route(*) =
-	method: 'GET'
-	path: '/counter'
-	handler: (request, reply) ->
-		counter = await redis.incr('counter')
-		reply(*) = string :: Welcome visitor number #{counter}
+    method: 'GET'
+    path: '/counter'
+        handler: (request, reply) ->
+        counter = await redis.incr('counter')
+        reply(*) = string :: Welcome visitor number #{counter}

 await server.start() catch (err.startFailure)
 if (!err) log :: Server started successfully: #{server.info.uri}


Fixed another CoffeeScript 1.6 syntax error in databases kite.

--- kites/databases/mySQLApi.coffee
+++ kites/databases/mySQLApi.coffee
@@ -376,7 +376,7 @@
 #   console.log options
 #   console.log "err:#{err}", res
 #
-###
+#
 #mySQL.backupDatabase options,(error,result)->
 #  if error?
 #    console.error error


update search index to support multiple matches per key ("patsas" returns all items that have it in the name, not just one of them)

--- search_index/search_index.coffee
+++ search_index/search_index.coffee
@@ -3,16 +3,21 @@
 class Node
   constructor: (@prefix) ->
     @children = {}
-
+    @contents = []
+
   ensure: (ch) ->
     @children[ch.toLowerCase()] ?= new Node(@prefix + ch)

   get: (ch) ->
     @children[ch]

+  add: (item) ->
+    @contents.push item
+
   putMatches: (to, n) ->
-    if @name?
-      to.push {name: @name, coords: @loc}
+    for itm in @contents
+      return 0 if n <= 0
+      to.push itm
       n -= 1
     for own ch,child of @children
       return 0 if n <= 0
@@ -31,12 +36,11 @@
       str = placeParts[idx..placeParts.length - 1].join(' ')
       @build "#{str.trim()}, #{city.trim()}", name, loc

-  build: (str, name, loc) ->
+  build: (str, name, loc, priority) ->
     node = @root
     for i in [0...str.length]
       node = node.ensure(str.charAt(i))
-    node.loc = loc
-    node.name = name
+    node.add name: name, coords: loc

   find: (str, max) ->
     node = @root


soo many conflicts

soo many conflicts

--- src/packages/editor-stats/src/editor-stats-view.coffee
+++ src/packages/editor-stats/src/editor-stats-view.coffee
@@ -37,6 +37,7 @@

     @command 'core:cancel', @detach
     @statusBar = @rootView.find '.status-bar'
+    @panes = @rootView.find('#panes')
     @css 'background', @statusBar.css('background')

     date = new Date(startDate)
@@ -92,7 +93,7 @@
       y.domain [0, max]

       bars.data(newdata).transition()
-        .attr('height', (d, i) ->  h - y(d.value) - 17)
+        .attr('height', (d, i) ->  h - y(d.value))
         .attr('y', (d, i) -> y(d.value))

       bars.classed('max', (d, i) -> d.value == max)
@@ -113,7 +114,7 @@
       @attach()

   attach: ->
-    @insertAfter @statusBar
+    @panes.append(@)
     @draw()

   detach: =>


fixed false return in coffeescript

fixed false return in coffeescript


Disable sensor logger view

Disable sensor logger view

--- src/scripts/workbench/views/sensor/show_view.js.coffee
+++ src/scripts/workbench/views/sensor/show_view.js.coffee
@@ -19,9 +19,6 @@
     @views.add(new Workbench.Views.DatastreamCollectionView(
       collection: @model.get("datastreams")
     ))
-    @views.add(new Workbench.Views.SensorLoggerView(
-      model: @model
-    ))
     @views.add(new Workbench.Views.SensorMetadataView(
       model: @model
     ))


another poignant example

another poignant example

--- examples/poignant.coffee
+++ examples/poignant.coffee
@@ -18,6 +18,39 @@
   set_picks:      nums => this.picks: nums
   get_purchase:        => this.purchase
   set_purchase: amount => this.purchase: amount
+}
+
+
+
+# class << LotteryDraw
+#   def play
+#     result = LotteryTicket.new_random
+#     winners = {}
+#     @@tickets.each do |buyer, ticket_list|
+#       ticket_list.each do |ticket|
+#         score = ticket.score( result )
+#         next if score.zero?
+#         winners[buyer] ||= []
+#         winners[buyer] << [ ticket, score ]
+#       end
+#     end
+#     @@tickets.clear
+#     winners
+#   end
+# end
+
+LotteryDraw: {
+  play: =>
+    result:   LotteryTicket.new_random()
+    winners:  {}
+    this.tickets.each() buyer, ticket_list =>
+      ticket_list.each() ticket =>
+        score: ticket.score(result)
+        return if score is 0
+        winners[buyer] ||= []
+        winners[buyer].push([ticket, score])
+    this.tickets: {}
+    winners
 }


Store archive edit session state in a telepath document

This allows an archive file to be opened during a collaboration
session.

--- lib/archive-edit-session.coffee
+++ lib/archive-edit-session.coffee
@@ -1,38 +1,51 @@
-fsUtils = require 'fs-utils'
 path = require 'path'
+
 _ = require 'underscore'
 archive = require 'ls-archive'
+telepath = require 'telepath'
+
+fsUtils = require 'fs-utils'
 File = require 'file'

 module.exports=
 class ArchiveEditSession
+  @acceptsDocuments: true
   registerDeserializer(this)
   @version: 1

   @activate: ->
     Project = require 'project'
     Project.registerOpener (filePath) ->
-      new ArchiveEditSession(filePath) if archive.isPathSupported(filePath)
+      new ArchiveEditSession(path: filePath) if archive.isPathSupported(filePath)

-  @deserialize: ({path}={}) ->
-    path = project.resolve(path)
-    if fsUtils.isFileSync(path)
-      new ArchiveEditSession(path)
+  @deserialize: (state) ->
+    relativePath = state.get('relativePath')
+    resolvedPath = project.resolve(relativePath) if relativePath
+    if fsUtils.isFileSync(resolvedPath)
+      new ArchiveEditSession(state)
     else
-      console.warn "Could not build archive edit session for path '#{path}' because that file no longer exists"
+      console.warn "Could not build archive edit session for path '#{relativePath}' because that file no longer exists"

-  constructor: (@path) ->
-    @file = new File(@path)
+  constructor: (optionsOrState) ->
+    if optionsOrState instanceof telepath.Document
+      @state = optionsOrState
+      resolvedPath = project.resolve(@getRelativePath())
+    else
+      resolvedPath = optionsOrState.path
+      @state = site.createDocument
+        deserializer: @constructor.name
+        version: @constructor.version
+        relativePath: project.relativize(resolvedPath)

-  destroy: ->
-    @file?.off()
+    @file = new File(resolvedPath)

-  serialize: ->
-    deserializer: 'ArchiveEditSession'
-    path: @getUri()
+  destroy: -> @file?.off()

-  getViewClass: ->
-    require './archive-view'
+  serialize: -> @state.clone()
+
+  getState: -> @state
+
+  getViewClass: -> require './archive-view'

   getTitle: ->
     if archivePath = @getPath()
@@ -40,9 +53,11 @@
     else
       'untitled'

-  getUri: -> project?.relativize(@getPath()) ? @getPath()
+  getUri: -> @getRelativePath()

-  getPath: -> @path
+  getRelativePath: -> @state.get('relativePath')
+
+  getPath: -> @file.getPath()

   isEqual: (other) ->
     other instanceof ArchiveEditSession and @getUri() is other.getUri()


Remove Duplicate Suggestions

Remove Duplicate Suggestions

- Preference Is Given To Earlier Suggestions
- Fixes #84

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -117,6 +117,7 @@
       .then _.partial(@gatherSuggestions, providers)
       .then (suggestions) =>
         return unless suggestions.length
+        suggestions = _.uniq(suggestions, (s) -> s.word)
         # Show the suggestion list if we have not already requested more suggestions
         @showSuggestionList(suggestions) if @currentSuggestionsPromise == suggestionsPromise
         @emitter.emit 'did-autocomplete', {options, suggestions}


Add correct alt text for new images

Add correct alt text for new images

--- plugins/image.coffee
+++ plugins/image.coffee
@@ -230,7 +230,7 @@
                         src: tmpImg.src
                         width: tmpImg.width
                         height: tmpImg.height
-                        alt: altText
+                        alt: jQuery('#caption').val()
                         class: (if tmp then "tmp" else "")
                     ).css("display", "none")
                     imageInsert


Clampable#clampToBounds

Clampable#clampToBounds

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -14,4 +14,4 @@

     self.clamp "x",
       min: bounds.x + I.width/2
-      max:+      max: bounds.width - I


collision with regular bounds

collision with regular bounds

--- src/collision.coffee
+++ src/collision.coffee
@@ -217,7 +217,7 @@
     rayRectangle: (source, direction, target) ->
       unless target.xw? and target.yw?
         if target.width? and target.height?
-          xw =
+          xw = target.width/2
           return Collision.rayRectangle source, direction,
             xw:
             yw: target.height/2


remove surplus comments and console.log statements

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -119,13 +119,9 @@

   set_piece: () ->
-    console.log " "
-    console.log "set_piece()"
     @puzzle.ui_status.terminate_piece_drag()
     @pieces_set += 1
     @pieces_in_puzzle -= 1
-    console.log "   pieces_set = #{@pieces_set}"
-    console.log "   pieces_in_puzzle = #{@pieces_in_puzzle}"
     if @pieces_set == 16
       @puzzle_finished()
     else
@@ -144,14 +140,6 @@
     @unset_pieces.splice(@unset_pieces.indexOf(@sym),1)
     pc = Math.floor(@pieces_in_puzzle*Math.random())
     @sym = @unset_pieces[pc]
-    console.log " "
-    console.log "next_piece()"
-    console.log "   sym = #{@sym}"
-    console.log "   pc = #{pc}"
-    up = ""
-    @unset_pieces.forEach (pcc) ->
-      up = up + pcc
-    console.log "   @unset_pieces = #{up}"
     @puzzle.piece.construct_piece(@sym)

@@ -175,7 +163,6 @@
   reset_mask: () ->
     @puzzle.puzzle_view.draw_photo()
     @get_next_color()
-    console.log "CALL draw_mask() from reset_mask()"
     @draw_mask()

@@ -184,18 +171,12 @@

   draw_mask: () ->
-    console.log " "
-    console.log "draw_mask()"
-    console.log "   & & & & @color = #{@color} & & & &"
     grid = @puzzle.grid
     @hex_draw.set_context("canvas")
-    zzz = 0 # EXTRA
     for bb in [1..10]
       for aa in [1..24]
         if grid[bb][aa] in @puzzle.pz_status.unset_pieces
           @hex_draw.fill_hex_ab(aa,bb,@color)
-          zzz = zzz + 1 # EXTRA
-    console.log "   #{zzz} hexes added to mask"

@@ -241,10 +222,6 @@
     xx = @hex_box.box_xy[0] - @puzzle.puzzle_view.puzzle_xy[0]
     yy = @hex_box.box_xy[1] - @puzzle.puzzle_view.puzzle_xy[1]
     photo = @puzzle.puzzle_view.photo
-    console.log "xx is negative" if xx < 0
-    console.log "yy is negative" if yy < 0
-    console.log "@hex_box.width is negative" if @hex_box.width < 0
-    console.log "@hex_box.height is negative" if @hex_box.height < 0
     @photo_clip_context.drawImage(photo,xx,yy,@hex_box.width,@hex_box.height,0,0,@hex_box.width,@hex_box.height)
     @puzzle.mask.reset_mask()
     context = @pattern.img.getContext('2d')
@@ -368,7 +345,6 @@
   constructor: (puzzle_app) ->
     @puzzle = puzzle_app
     @puzzle_xy = [100,30]
-    console.log "CALL PuzzleView.reset() from PuzzleView.constructor"
     @reset()

@@ -381,8 +357,6 @@

   draw_photo: () ->
-    console.log "   * * * draw_photo()"
-    # @context = @get_drawing_context("canvas")
     canvas = document.getElementById("puzzle-widget")
     context = canvas.getContext('2d')
     context.drawImage(@photo,@puzzle_xy[0],@puzzle_xy[1])
@@ -470,7 +444,6 @@

   ctx_fill_hex_ab: (ctx,a,b,color) ->
-    #  ### <9> ###  #
     cnv = document.getElementById("puzzle-widget")
     @context = cnv.getContext('2d')
     xy = @get_hex_xy(a,b)
@@ -502,12 +475,7 @@

   set_hex_box: (piece_symbol) ->
-    console.log " "
-    console.log "set_hex_box()"
-    console.log "   symbol = #{piece_symbol}"
     @reset_hexes(@get_hexes(piece_symbol))
-    console.log "   after reset_hexes():"
-    console.log "      hexes.length = #{@hexes.length}"
     @get_box_metrics()

@@ -516,7 +484,6 @@

   get_box_metrics: () ->
-    console.log "   get_box_metrics()"
     @init_box_params()
     for hx in @hexes
       aa = hx[0]
@@ -524,16 +491,8 @@
       @test_left_right_top_bottom(aa,b2)
     @get_corner_fit()
     @get_anchor_hex()
-    console.log "      anchor_hex is [#{@anchor_hex[0]},#{@anchor_hex[1]}]"
     @get_box_xy()
     @get_height_width()
-    console.log "         @get_height_width()"
-    console.log "             right = #{@right}"
-    console.log "              left = #{@left}"
-    console.log "            bottom = #{@bottom}"
-    console.log "               top = #{@top}"
-    console.log "             width = #{@width}"
-    console.log "            height = #{@height}"
     @get_box_corner_to_anchor_hex_center()

@@ -718,12 +677,10 @@
 get_photo = (app) ->
   photo = new Image()
   photo.onload = =>
-    console.log ">>>>>>>>>>>>> @photo.onload EVENT <<<<<<<<<<<<<"
     app.puzzle_view.photo = photo
     app.puzzle_view.draw_photo()
     if app.load_status == "pattern"
       app.load_status = "ready"
-      console.log "start_new_puzzle() called from get_photo()"
       app.pz_status.start_new_puzzle()
     else
       app.load_status = "photo"
@@ -736,14 +693,12 @@
   xhr.open('GET',url)
   xhr.onreadystatechange = ->
     if (xhr.readyState == 4 && xhr.status == 200)
-      console.log ">>>>>>>>>>>>> pattern XHR ready EVENT <<<<<<<<<<<<<"
       msg_in = xhr.responseText
       response = JSON.parse(msg_in)
       @pstring = response.pstring
       app.grid = get_pattern_grid(@pstring)
       if app.load_status == "photo"
         app.load_status = "ready"
-        console.log "start_new_puzzle() called from get_puzzle_pattern()"
         app.pz_status.start_new_puzzle()
       else
         app.load_status = "pattern"


TODO: rewrite test not to use removed action creator receiveArticles.

--- src/scripts/actions/article-actions.coffee
+++ src/scripts/actions/article-actions.coffee
@@ -10,7 +10,7 @@
 		type: 'FETCH_SCHEMA'
 		payload:
 			promise: API.fetchArticleSchema()
-# 	receiveArticles: receiveArticles
+# 	receiveArticles: receiveArticles TODO: rewrite tests to not use this
 	save: (article, userId) ->
 		type: 'SAVE_ARTICLE'
 		payload:


hub: remove an lti query

hub: remove an lti query

--- src/smc-hub/postgres-server-queries.coffee
+++ src/smc-hub/postgres-server-queries.coffee
@@ -533,15 +533,6 @@
             query : 'SELECT account_id FROM accounts'
             where : "email_address = $::TEXT" : opts.email_address
             cb    : one_result('account_id', opts.cb)
-
-    lti_account_exists: (opts) =>
-        opts = defaults opts,
-            lti_id        : required
-            cb            : required   # cb(err, account_id or undefined) -- actual lti_id if it exists; err = problem with db connection...
-        @_query
-            query : 'SELECT lti_id FROM accounts'
-            where : "lti_id = $::TEXT[]" : opts.lti_id
-            cb    : one_result('lti_id', opts.cb)

     # set an account creation action, or return all of them for the given email address
     account_creation_actions: (opts) =>


Delayed hint panel grammar setting, use hint gram.

--- lib/last-suggestion-view.coffee
+++ lib/last-suggestion-view.coffee
@@ -5,10 +5,11 @@
     @element.setAttribute 'mini', true
     @element.removeAttribute 'tabindex'
     @editor = @element.getModel()
-    @editor.setGrammar atom.grammars.grammarForScopeName 'source.haskell'

   destroy: ->
     @element.remove()

   setText: (text) ->
+    unless @editor.getGrammar()?.scopeName is 'hint.haskell'
+      @editor.setGrammar atom.grammars.grammarForScopeName 'hint.haskell'
     @editor.setText text


Open the full path of a file on navigate.

--- lib/merge-conflicts-view.coffee
+++ lib/merge-conflicts-view.coffee
@@ -42,8 +42,9 @@
     @command 'merge-conflicts:entire-file-theirs', @sideResolver('theirs')

   navigate: (event, element) ->
-    p = element.find(".path").text()
-    atom.workspace.open(p)
+    repoPath = element.find(".path").text()
+    fullPath = path.join atom.project.getRepo().getWorkingDirectory(), repoPath
+    atom.workspace.open(fullPath)

   minimize: ->
     @addClass 'minimized'


Use ActivityLogger in stash pop

Use ActivityLogger in stash pop

--- lib/models/git-stash-pop.coffee
+++ lib/models/git-stash-pop.coffee
@@ -1,11 +1,8 @@
-git = require '../git'
-notifier = require '../notifier'
-OutputViewManager = require '../output-view-manager'
+git = require('../git-es').default
+ActivityLogger = require('../activity-logger').default

 module.exports = (repo) ->
   cwd = repo.getWorkingDirectory()
-  git.cmd(['stash', 'pop'], {cwd}, color: true)
-  .then (msg) ->
-    OutputViewManager.getView().showContent(msg) if msg isnt ''
-  .catch (msg) ->
-    notifier.addInfo msg
+  git(['stash', 'pop'], {cwd, color: true})
+  .then (result) ->
+    ActivityLogger.record(Object.assign({message: 'Pop from stash'}, result))


Highlight config as json

Highlight config as json

--- lib/notification-element.coffee
+++ lib/notification-element.coffee
@@ -253,7 +253,7 @@

       ### Config

-      ```
+      ```json
       #{JSON.stringify(userConfig, null, 2)}
       ```


Only run search if a pattern exists

Only run search if a pattern exists

--- lib/search-results-model.coffee
+++ lib/search-results-model.coffee
@@ -42,9 +42,10 @@

   search: =>
     @destroyMarkers()
-    @markers = @findAndMarkRanges()
-    @emitCurrentResult()
-    @trigger 'markers-changed', markers: @markers
+    if @searchModel.pattern?.length
+      @markers = @findAndMarkRanges()
+      @emitCurrentResult()
+      @trigger 'markers-changed', markers: @markers

   clearCurrentResult: =>
     @setCurrentResultIndex(null)


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -151,7 +151,7 @@
         path.enter().append('path').attr('class', 'pie').attr('fill', (d, i) -> color i)

         path.transition().duration(750).attrTween('d', (d,i) ->
-            `var i`
+            #`var i`
             theOldDataInPie = @oldPieData ? piedata
             console.log('@oldPieData',@oldPieData)
             # Interpolate the arcs in data space


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -256,7 +256,8 @@
             totaltickets += pd.value
         #console.log totaltickets
         totalLabel.enter()
-            .append('text' () -> totalTickets)
+            .append('text')
+            .text(totalTickets)
             .attr('text-anchor', 'middle')
             .attr("filter","url(#dropshadow)")
         totalLabel.exit().remove()


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -111,7 +111,7 @@
     path = pathGroup.selectAll('path.pie').data(piedata)
     #console.log("path",this.path.empty())
     path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
-      that.color i
+      color i
     #@path.transition().duration(1500).attrTween 'd', that.pieTween
     path.transition()
         .duration(1500)


Changed newpie width to fit 6x4 layout

Changed newpie width to fit 6x4 layout

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -17,7 +17,7 @@
         $(@node).children("svg").remove();

         # Config values here
-        width = 590                             # Width of the SVG area
+        width = 580                             # Width of the SVG area
         height = 400                            # Height of the SVG area
         radius = Math.min(width, height) / 2    # Calculated min dimension of the SVG area
         radiuso = 130                           # Outer radius of the pie


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -100,7 +100,7 @@
         x = c[0]
         y = c[1]
         mx = 1.2
-        my = 1.2
+        my = 1.1
         h = Math.sqrt(x*x + y*y)
         r = labelRadius
         if x <= 0


vm: define `JVM::setAlwaysOn`

vm: define `JVM::setAlwaysOn`

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -76,6 +76,9 @@
           (signature Function)
         createSharedVm:
           (signature Function)
+        setAlwaysOn:
+          (signature Object, Function)
+
     schema              :
       ip                :
         type            : String
@@ -135,6 +138,31 @@
       }
       return callback null

+  @setAlwaysOn = secure (client, options, callback)->
+    {connection: delegate: account} = client
+    {vmName, status} = options
+
+    selector =
+      hostnameAlias : vmName
+      webHome       : account.profile.nickname
+
+    @one selector, (err, vm) ->
+      return callback err if err
+      return callback new KodingError "VM Not FOUND" unless vm
+      subscriptionOptions = targetOptions: selector: tags: $in: ["vm"]
+      account.fetchSubscriptions null, subscriptionOptions, (err, subscriptions) ->
+        return callback err  if err
+        return callback new KodingError "Subscription not found"  unless subscriptions.length
+        [subscription] = subscriptions
+        JPaymentPack.one tags: "alwayson", (err, pack) ->
+          return callback err  if err
+          return callback new KodingError "Always On pack not found"  unless pack
+          {debit, credit} = subscription
+          fn = if status then debit else credit
+          fn.call subscription, {pack}, (err) ->
+            return callback err  if err
+            vm.update $set: alwaysOn: status, callback
+
   @fetchDefaultVm_ = (client, callback)->
     {delegate} = client.connection
     delegate.fetchUser (err, user) ->


abstract visitor class

abstract visitor class

--- src/evaluator.coffee
+++ src/evaluator.coffee
@@ -1,10 +1,7 @@
 # force formula evaluator

-class Evaluator
-  constructor: (@data) ->
-  visitLiteral: (node) ->
-    node.value
-
+class FormulaVisitor
+  constructor: ->
   visitIntegerLiteral: (node) ->
     @visitLiteral node
   visitDecimalLiteral: (node) ->
@@ -13,6 +10,12 @@
     @visitLiteral node
   visitParens: (node) ->
     node.formula.visit @
+
+class Evaluator extends FormulaVisitor
+  constructor: (@data) ->
+  visitLiteral: (node) ->
+    node.value
+
   visitReference: (node) ->
     @data[node.name]
   visitComparison: (node) ->
@@ -32,19 +35,11 @@
       when '>='
         left >= right

-class Unbound
+class Unbound extends FormulaVisitor
   constructor: ->
   visitLiteral: ->
     []

-  visitIntegerLiteral: (node) ->
-    @visitLiteral node
-  visitDecimalLiteral: (node) ->
-    @visitLiteral node
-  visitStringLiteral: (node) ->
-    @visitLiteral node
-  visitParens: (node) ->
-    node.formula.visit @
   visitReference: (node) ->
     [node.name]
   visitComparison: (node) ->


added help text for toggle deck

added help text for toggle deck

--- src/hubot-cah.coffee
+++ src/hubot-cah.coffee
@@ -14,7 +14,8 @@
 helpSummary += "\ncah choose <#> - Choose a winning answer (czar only)"
 helpSummary += "\ncah status - Display summary of current game"
 helpSummary += "\ncah skip - Discard current black card and assign a new Card Czar"
-helpSummary += "\ncah decks - Lists all active and inactive decks. Decks can be toggled on and off `toggle mode`. "
+helpSummary += "\ncah toggle deck <deck> - Toggles a deck on or off. "
+helpSummary += "\ncah decks - Lists all active and inactive decks. Decks can be toggled on and off `toggle deck`. "

 Game = require('./game')
 deck = require('./deck')


hate coffee script

hate coffee script

--- src/hubot-cah.coffee
+++ src/hubot-cah.coffee
@@ -195,14 +195,14 @@
 #  brainLoaded = () ->
 #    db = robot.brain.get 'cah'
 #    if (!db)
-   db = {
-     scores:         {},                   # {<name>: <score>, ...}
-     activePlayers:  [],                   # [<player name>, ...]
-     blackCard:      random_black_card(),  # <card text>
-     czar:           null,                 # <player name>
-     hands:          {},                   # {<name>: [<card text>, <card text>, ...], ...}
-     answers:        [],                   # [ [<player name>, [<card text>, ...]], ... ]
-   }
+  db = {
+    scores:         {},                   # {<name>: <score>, ...}
+    activePlayers:  [],                   # [<player name>, ...]
+    blackCard:      random_black_card(),  # <card text>
+    czar:           null,                 # <player name>
+    hands:          {},                   # {<name>: [<card text>, <card text>, ...], ...}
+    answers:        [],                   # [ [<player name>, [<card text>, ...]], ... ]
+  }
 #    robot.brain.set 'cah', db

 #  robot.brain.on 'loaded', brainLoaded


Set data-section on wrapper element and increment it on parts

This fixes two bugs, the first is that if the stimulus has a large element in it (like a video), it would stretch quite
a bit above the data-section marker, and wouldn't match the scroll position.

The other is that the inner parts of the exercise all had the same step-index as the exercise.  We fix that by not
setting one if it's the first, and then incrementing one for the other parts

--- tutor/src/components/task-teacher-review/exercise.cjsx
+++ tutor/src/components/task-teacher-review/exercise.cjsx
@@ -105,13 +105,13 @@
   renderQuestion: (question, index) ->
     questionStats = @getQuestionStatsById(question.id)
     return null unless questionStats
-    {sectionKey} = @props
+    sectionKey = if index then @props.sectionKey + index else null

     <TaskTeacherReviewQuestionTracker
       key={"task-review-question-#{question.id}"}
       question={question}
       questionStats={questionStats}
-      sectionKey={sectionKey}/>
+      sectionKey={sectionKey} />

   render: ->
     {content} = @props
@@ -124,15 +124,20 @@
       className='exercise-stimulus'
       block={true}
       html={stimulus_html} /> if stimulus_html?.length > 0
+    firstSectionKey = questions

-    <CardBody className='task-step openstax-exercise openstax-exercise-card' pinned={false}>
-      {stimulus}
-      {exercise}
-      <ExerciseGroup
-        project='tutor'
-        key='step-exercise-group'
-        exercise_uid={uid}/>
-      <ExerciseIdentifierLink exerciseId={@props.content.uid} project='tutor' />
-    </CardBody>
-
+    <div data-section={@props.sectionKey}>
+      <CardBody
+        className='task-step openstax-exercise openstax-exercise-card'
+        pinned={false}
+      >
+        {stimulus}
+        {exercise}
+        <ExerciseGroup
+          project='tutor'
+          key='step-exercise-group'
+          exercise_uid={uid}/>
+        <ExerciseIdentifierLink exerciseId={@props.content.uid} project='tutor' />
+      </CardBody>
+    </div>
 module.exports = TaskTeacherReviewExercise


:green_heart: Fix Travis build

:green_heart: Fix Travis build

--- spec/minimap-element-spec.coffee
+++ spec/minimap-element-spec.coffee
@@ -886,13 +886,15 @@
               expect(pluginA.isActive()).toBeTruthy()

           describe 'on the code highlight item', ->
+            [initial] = []
             beforeEach ->
+              initial = minimapElement.displayCodeHighlights
               atom.commands.dispatch quickSettingsElement, 'core:move-down'
               atom.commands.dispatch quickSettingsElement, 'core:move-down'
               atom.commands.dispatch quickSettingsElement, 'core:confirm'

             it 'toggles the code highlights on the minimap element', ->
-              expect(minimapElement.displayCodeHighlights).toBeFalsy()
+              expect(minimapElement.displayCodeHighlights).toEqual(not initial)

         describe 'core:move-down', ->
           beforeEach ->