Added the delta-ktrans calculation to subject detail in the routes.

--- app/javascripts/routes.coffee
+++ app/javascripts/routes.coffee
@@ -36,6 +36,9 @@
             sess.subject = subject
             # Fix the acquisition date.
             Helpers.fixDate(sess, 'acquisition_date')
+            # Calculate delta-ktrans.
+            _.extend sess.modeling, delta_k_trans: sess.modeling.fxr_k_trans \
+                - sess.modeling.fxl_k_trans
           # Fix the encounter dates.
           Helpers.fixDate(enc, 'date') for enc in detail.encounters
           # Copy the detail content into the subject.


Fix bug

Fix bug

--- index.cjsx
+++ index.cjsx
@@ -102,6 +102,7 @@
       @handleQuestChange quest_id
     updateQuestStatus: (qid, status) ->
       quest = @state.quests[qid]
+      return unless quest?
       for pid in quest.postquest
         postq = @state.quests[pid]
         if typeFreqs[quest.type] <= typeFreqs[postq.type] and status[postq.game_id] isnt 3


Fix bug

Fix bug

Fix #3

--- index.cjsx
+++ index.cjsx
@@ -14,7 +14,7 @@
   description: "任务信息查询 & 任务面板强化"
   author: "马里酱"
   link: "https://github.com/malichan"
-  version: "1.1.1"
+  version: "1.1.2"
   reactClass: React.createClass
     getInitialState: ->
       fs = require "fs-extra"
@@ -121,7 +121,7 @@
         when "/kcsapi/api_req_quest/clearitemget"
           qid = parseInt postBody.api_quest_id
           quests_status[qid] = 1
-          for postq in @state.quests[qid].postquest
+          for postq in @state.quests[qid].postquest when quests_status[postq] is 3
             quests_status[postq] = 2
       @setState
         quests_status: quests_status


Add pattern-matching for memorandum macros


Clean up comments

Clean up comments


support for elementToParentMatrix ignoring properties which are handled indirectly

--- src/art/engine/file/v1_writer.coffee
+++ src/art/engine/file/v1_writer.coffee
@@ -125,10 +125,13 @@
     for k, v of saveProps
       switch k
         when "key" then encodedProps.name = v.toString()
+        when "elementToParentMatrix" then encodedProps.matrix = v.toString()
         when "bitmap"
           unless bitmapTag = @bitmapTags[v.uniqueId]
             console.warn "Bitmap missing: #{v.uniqueId}"
           encodedProps.bitmap_id = bitmapTag.attrs.bitmap_id
+        when "location", "children", "parent", "currentSize", "size", "axis", "color", "scale", "angle"
+          # handled elsewhere
         else
           console.warn "Art.Engine.V1Writer: ignored unsupported prop type: elementType: #{element.class.name}, propType: #{k}"
     @_encodeLayout element, encodedProps


static public folder in main

static public folder in main

--- express/index.coffee
+++ express/index.coffee
@@ -17,6 +17,9 @@
 app.use morgan 'dev' unless app.get('env') is 'production'
 LOG "use favicon"
 app.use favicon path.resolve __dirname, 'public', 'favicon.ico'
+LOG "use static for public folder"
+app.use express.static path.resolve(__dirname, 'public'),
+    redirect: false
 LOG "load routes"
 app.loadRoutes
   'mount /api': require './api'


Fix #46

Fix #46

Instead of storing the original cursor, which looses data, store the position of the orignal cursor and restore the position to that.


Updated focus-on-event with $timeout.

Updated focus-on-event with $timeout.

--- src/directives/focus_on_event.coffee
+++ src/directives/focus_on_event.coffee
@@ -8,13 +8,14 @@
 @param {String}  mac-focus-on-event Event to focus on element
 @param {Boolean} mac-focus-on-event-scroll Scroll to element location or not
 ###
-angular.module("Mac").directive "macFocusOnEvent", ->
+angular.module("Mac").directive "macFocusOnEvent", ["$timeout", ($timeout) ->
   (scope, element, attributes) ->
     scope.$on attributes.macFocusOnEvent, ->
-      setTimeout (->
+      $timeout ->
         element.focus()
         if attributes.macFocusOnEventScroll
           x = window.scrollX
           y = window.scrollY
           window.scrollTo x, y
-      ), 0
+      , 0
+]


Added shorthands for device orientation and device motion events


--failOnMissing implies --missing

--failOnMissing implies --missing

--- src/biscotto.coffee
+++ src/biscotto.coffee
@@ -199,7 +199,8 @@
             private: argv.private
             internal: argv.internal
             noOutput: argv.noOutput
-            missing: argv.missing
+            missing: argv.missing || argv.failOnMissing
+            failOnMissing: argv.failOnMissing
             verbose: argv.v
             debug: argv.d
             cautious: argv.cautious


[master] timeout

[master] timeout

--- scraper/guba.coffee
+++ scraper/guba.coffee
@@ -24,7 +24,10 @@
 requestAsync = (url) ->
   deferred = Q.defer()
   loo = (retry) ->
-    request url, (err, response, body) ->
+    request
+      uri: url
+      timeout: 5000
+    , (err, response, body) ->
       maybeRetry = (rejection) ->
         if 0 < retry
           logger.warn 'retry request',
@@ -80,15 +83,21 @@
     symbol: symbol
     startPage: startPage
   loo = (page) ->
-    unroll = 4
-    Q.all _.map([page..page + unroll - 1], (page) ->
-      parseSinglePage symbol, page, redis
-    )
+    parseSinglePage symbol, page, redis
     .then (res) ->
-      if _.min(res) == 0
+      if res == 0
         return
       else
-        loo page + unroll
+        loo page + 1
+    # unroll = 4
+    # Q.all _.map([page..page + unroll - 1], (page) ->
+    #   parseSinglePage symbol, page, redis
+    # )
+    # .then (res) ->
+    #   if _.min(res) == 0
+    #     return
+    #   else
+    #     loo page + unroll
   loo startPage

 parseAll = (redis) ->


[FIX] Bug in chainAPI when the response is too long, => buffering

--- src/chainAPI.coffee
+++ src/chainAPI.coffee
@@ -122,7 +122,13 @@
     options.path = '/sensors/?device_id='+DeviceId
     options.method = 'GET'
     req = http.request options, (res) ->
+      buf = ''
       res.on 'data', (d) =>
+        buf += d.toString()
+
+      res.on 'end', () =>
+        d = buf
+        #console.log d.toString() + '\n\n'
         try
           obj = JSON.parse(d)
           done = false


cooldown finilazation

cooldown finilazation

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

 player.update()

-
+player.

 player.update()
 # => `velocity is {x: 1, y: 0} and position is {x: 1, y: 0}`


Set white background to achieving chart

Set white background to achieving chart

--- scripts/goal.coffee
+++ scripts/goal.coffee
@@ -127,7 +127,6 @@
     bar.setBarWidth 10
     bar.setBarSpacing 5
     bar.setLegendLeft()
-    bar.setTransparentBackground()

     if embed_user?
       index = parseInt Math.random() * colors.length, 10


Fixed months too!

Fixed months too!

--- scripts/kerb.coffee
+++ scripts/kerb.coffee
@@ -18,8 +18,10 @@
     jsdom.env url, [ "http://code.jquery.com/jquery-1.5.min.js" ], (errors, window) ->
       d = new Date();
       day = d.getDate()
-      day = (day < 10 ? '0' : '') + number
-      formatted_date = d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + day
+      day = (day < 10 ? '0' : '') + day
+      month  = (d.getMonth() + 1)
+      month = (month < 10 ? '0' : '') + month
+      formatted_date = d.getFullYear() + "-" + month + "-" + day
       traders = window.$("#date-"+formatted_date+" ul li div.rota_content")
       message_str = "\n"
       traders.each (index, element) =>


Add generic highlighting for RUNOFF control-lines

--- grammars/runoff.cson
+++ grammars/runoff.cson
@@ -42,3 +42,18 @@
 	underline:
 		name: "markup.underline.link.runoff"
 		match: "[^_]\x08(?=_)|(?<=_)\x08[^_]"
+
+
+	# Control lines
+	commands:
+		patterns: [{
+
+			name: "meta.function.runoff"
+			begin: "^\x0C*((\\.)[^;\\s]+)"
+			end: "$|(;)"
+			beginCaptures:
+				1: name: "entity.function.name.runoff"
+				2: name: "punctuation.definition.macro.runoff"
+			endCaptures:
+				1: name: "punctuation.terminator.statement.runoff"
+		}]


add dynamic time to text chat

add dynamic time to text chat

--- src/frontend/modules/textChat.coffee
+++ src/frontend/modules/textChat.coffee
@@ -72,9 +72,10 @@

     # Create a new message element and append it to @jqMessageBox
     newMessageElem : (message) =>
+        d             = new Date
         jqNewMetadata = ($ '<div>', { class : 'chatmetadata' })
         jqNewAuthor   = ($ '<span>', { class : 'fontlightblue' }).text message.from
-        jqNewTime     = ($ '<span>', { class : 'time' }).text ' at 11:27'
+        jqNewTime     = ($ '<span>', { class : 'time' }).text ' at ' + ((do d.toTimeString).substr 0, 5)
         (jqNewMetadata.append jqNewAuthor).append jqNewTime
         jqNewMessage  = ($ '<div>', { class : 'chatmessage' }).text message.text
         @jqMessageBox.append (($ '<li>').append jqNewMetadata).append jqNewMessage


This should really be a period

This should really be a period

--- scripts/sync.coffee
+++ scripts/sync.coffee
@@ -36,7 +36,7 @@
           msg.send 'npm install failed'
           return

-        msg.send 'Sync successful, I regret nothing!'
+        msg.send 'Sync successful. I regret nothing!'
         setTimeout die, 1000

     die = ->


Project buttons

Project buttons

--- toolbar.cson
+++ toolbar.cson
@@ -49,4 +49,21 @@
   {
     type: "spacer"
   }
+  {
+    type: "button"
+    icon: "book"
+    iconset: "icomoon"
+    tooltip: "Save As Project"
+    callback: "project-manager:save-project"
+  }
+  {
+    type: "button"
+    icon: "books"
+    iconset: "icomoon"
+    tooltip: "Open Project"
+    callback: "project-manager:list-projects"
+  }
+  {
+    type: "spacer"
+  }
 ]


animate entire tree

animate entire tree

--- tree2.coffee
+++ tree2.coffee
@@ -92,6 +92,16 @@

   tryAll: (fn) ->
     return fn(this)
+
+  move: (newx, newy) ->
+    # move a node and its children
+    dx = newx - @x
+    dy = newy - @y
+
+    moveFn = (node) ->
+      node.x += dx
+      node.y += dy
+    @forAll(moveFn)

   radius: default_node_radius

@@ -322,16 +332,10 @@

     if @selected?
       s = @selected
-      dx = (pos.x - s.mx + s.ox) - s.node.x
-      dy = (pos.y - s.my + s.oy) - s.node.y
-
-      moveFn = (node) ->
-        node.x += dx
-        node.y += dy
-      if s.node.forAll?
-        s.node.forAll(moveFn)
-      else
-        moveFn(s.node)
+      newx = pos.x - s.mx + s.ox
+      newy = pos.y - s.my + s.oy
+
+      s.node.move(newx, newy)

     if @merging?
       @merging.x = pos.x
@@ -409,8 +413,7 @@

   setPosition: (time) ->
     pos = lerp2d(time/@duration, @origpos, @destpos)
-    @node.x = pos.x
-    @node.y = pos.y
+    @node.move(pos.x, pos.y)

   isFinished: (time) ->
     time >= @duration


fs.statSyncNoException returns false or an object

--- src/fs-utils.coffee
+++ src/fs-utils.coffee
@@ -25,7 +25,7 @@

   # Returns true if a file or folder at the specified path exists.
   exists: (pathToCheck) ->
-    pathToCheck? and fs.statSyncNoException(pathToCheck)
+    pathToCheck? and fs.statSyncNoException(pathToCheck) isnt false

   # Returns true if the specified path is a directory that exists.
   isDirectorySync: (directoryPath) ->


fixes #45

fixes #45

--- src/g/zoomer.coffee
+++ src/g/zoomer.coffee
@@ -56,15 +56,19 @@

   # @param n [int] number of residues to scroll to the right
   setLeftOffset: (n) ->
-    val = (n - 1) * @get('columnWidth')
+    val = (n)
     val = Math.max 0, val
-    @set "_alignmentScrollLeft", val
+    val -= @g.columns.calcHiddenColumns val
+    @set "_alignmentScrollLeft", val * @get('columnWidth')

   # @param n [int] row that should be on top
   setTopOffset: (n) ->
-    val = (n - 1) * @get('rowHeight')
+    val = (n - 1)
+    for i in [0..val] by 1
+      seq = @model.at i
+      height += seq.attributes.height || 1
     val = Math.max 0, val
-    @set "_alignmentScrollTop",val
+    @set "_alignmentScrollTop",val * @get("rowHeight")

   # length of all elements left to the main sequence body: labels, metacell, ..
   getLabelWidth: ->


fix concat issue

fix concat issue

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -23,7 +23,7 @@
     choice = Math.floor(Math.random() * data['words'].length)
     @current = data['words'][choice]['name']
     [1..@current.length].forEach ->
-      @reveal.concat("_")
+      @reveal = @reveal.concat("_")
     return data['words'][choice]

   update_question = (data) ->


insights: Support additional --where clause

--- src/insights.coffee
+++ src/insights.coffee
@@ -20,13 +20,14 @@
       end: new Date(e)
   return intervals

-getScaleEventsChunk = (insights, start, end, app, fields, event, callback) ->
+getScaleEventsChunk = (insights, start, end, app, fields, event, andWhere, callback) ->
   start = start.toISOString()
   end = end.toISOString()

   limit = 999
   query = "SELECT #{fields} FROM #{event}"
   query += " WHERE appName = '#{app}'" if app
+  query += " WHERE #{andWhere}" if andWhere
   query += " SINCE '#{start}' UNTIL '#{end}'"
   query += " LIMIT #{limit}"
   debug 'query', query
@@ -56,7 +57,7 @@

   # execute queries
   getChunk = (period, cb) ->
-    return getScaleEventsChunk insights, period.start, period.end, options.app, options.fields, options.event, cb
+    return getScaleEventsChunk insights, period.start, period.end, options.app, options.fields, options.event, options.where, cb

   debug "Executing #{queries.length} over #{options.period} days"
   throw new Error "Extremely high number of queries needed, over 3k: #{queries.length}" if queries.length > 3000
@@ -86,6 +87,7 @@
     .option('--end <DATETIME>', 'End time of queried period.', String, 'now')
     .option('--query-interval <minutes>', 'How big chucks to request at a time', Number, 30)
     .option('--concurrency <N>', 'Number of concurrent commands/subprocesses', Number, 5)
+    .option('--where <CLAUSE>', 'Extra where clause to further limit', String, '')
     .parse(args)

 normalize = (options) ->


Rename to avoid confusion

Rename to avoid confusion

--- app/js/directives/misc.coffee
+++ app/js/directives/misc.coffee
@@ -15,16 +15,16 @@
         v = elem.val() is $(firstPassword).val()
         ctrl.$setValidity "pwmatch", v

-angular.module("app.directives").directive "ngEnter", ->
+angular.module("app.directives").directive "myNgEnter", ->
   (scope, element, attrs) ->
       element.bind "keydown keypress", (event) ->
         if event.which is 13
           scope.$apply ->
-            scope.$eval attrs.ngEnter
+            scope.$eval attrs.myNgEnter

 angular.module("app.directives").directive "inputName", ->
-  template: '<input placeholder="Account Name (Required)" autofocus ng-model="$parent.ngModel" ng-blur="removeTrailingDashes()" ng-enter="removeTrailingDashes()" popover="Name can only contain lowercase alphanumeric characters and dashes, must start with a letter, and cannot end with a dash.  It can be at most 63 characters long.  Invalid characters are blocked and letters are lowercased automatically."  popover-trigger="focus" ng-keydown="kd()" ng-change="ku()" uncapitalize type="text" class="form-control">'
+  template: '<input placeholder="Account Name (Required)" autofocus ng-model="$parent.ngModel" ng-blur="removeTrailingDashes()" my-ng-enter="removeTrailingDashes()" popover="Name can only contain lowercase alphanumeric characters and dashes, must start with a letter, and cannot end with a dash.  It can be at most 63 characters long.  Invalid characters are blocked and letters are lowercased automatically."  popover-trigger="focus" ng-keydown="kd()" ng-change="ku()" uncapitalize type="text" class="form-control">'
   restrict: "E"
   scope:
     ngModel: "="


removed AbstractTicker and added Ticker and ContinuousTicker to API

--- bokehjs/src/coffee/api.coffee
+++ bokehjs/src/coffee/api.coffee
@@ -54,7 +54,8 @@
   Bokeh.GeoJSONDataSource = require("./source/geojson_data_source")

   # tickers and tick formatters
-  Bokeh.AbstractTicker           = require("./ticking/abstract_ticker")
+  Bokeh.Ticker                   = require("./ticking/ticker")
+  Bokeh.ContinuousTicker         = require("./ticking/continuous_ticker")
   Bokeh.AdaptiveTicker           = require("./ticking/adaptive_ticker")
   Bokeh.BasicTicker              = require("./ticking/basic_ticker")
   Bokeh.BasicTickFormatter       = require("./ticking/basic_tick_formatter")


modified

modified

--- src/jsonsina.coffee
+++ src/jsonsina.coffee
@@ -41,6 +41,7 @@
         arr = eval string
       catch error
         callback error, arr
+        console.error 'jsonsina.coffee >> history', error
         return

       if arr # 必須這樣寫,不能簡化為: for each in arr?


Log res.message cause res is circular

Log res.message cause res is circular

--- scripts/arrivals.coffee
+++ scripts/arrivals.coffee
@@ -59,7 +59,7 @@
     (msg) ->
       msg.room is 'C5F138J1K' and msg.message?.rawMessage?.upload
     (res) ->
-      console.log "Respond! %j", res
+      console.log "Respond! %j", res.message
       file = res.message.rawMessage.files[0]
       greyhound = util.slugify file.title
       name = util.capitalize file.title


deneme

deneme

--- scripts/filtered.coffee
+++ scripts/filtered.coffee
@@ -1,4 +1,4 @@
 module.exports = (robot) ->

    robot.hear /cmon/i, (res) ->
-     res.send "Goals? cmon? #{res.message.text}? WE DON'T NEED NO STINKIN BADGERS"+     res.send "Goals? cmon? {res.message.text}? WE DON'T NEED NO STINKIN BADGERS"


Fix Issue #18

Fix Issue #18

--- scripts/triggers.coffee
+++ scripts/triggers.coffee
@@ -49,4 +49,4 @@
       if user?
         msg.send "#{user}: #{triggerPhrase}"
       else
-        msg.reply triggerPhrase
+        msg.send triggerPhrase


Bootstrap message

Bootstrap message

--- server/bootstrap.coffee
+++ server/bootstrap.coffee
@@ -54,3 +54,5 @@
   Notifications.update notification._id,
     $set:
       group: message2group notification.message
+
+console.log 'Upgraded database as necessary.'


Fixed case where filename already has extension.

--- src/mustache.coffee
+++ src/mustache.coffee
@@ -16,13 +16,15 @@
 partialRE = /{{>\s*([\w-]+)\s*}}/g

 class Mustache
-  constructor: (options) ->
+  constructor: (options = {}) ->
     @extension = options.extension ? 'mustache'
     @path      = options.path      ? '.'

   parts: {}

   readFile: (filename, callback) ->
+    regex = new RegExp "\.#{@extension}$"
+    filename = filename.replace regex, ''
     paths = if Array.isArray @path then @path.slice 0 else [ @path ]
     fullPath = undefined
     findFirst = (callback) =>


use clean input cells instead of adding

use clean input cells instead of adding

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -24,7 +24,9 @@

   is_input = (cell) -> cell.type is 'input'
   is_output = (cell) -> cell.type is 'output'
+  is_clean = (cell) -> cell.is_clean()
   visible = (cell) -> cell.visible
+  identity = (cell) -> true

   init_codemirror = ->
     CodeMirror.keyMap.lead = ed.key_map
@@ -58,7 +60,7 @@
   cell_index = (cell) ->
     cell.notebook.cells.indexOf cell

-  seek = (start_cell, direction, predicate) ->
+  seek = (start_cell, direction, predicate=identity) ->
     notebook = start_cell.notebook
     index = cell_index(start_cell) + direction
     loop
@@ -104,9 +106,14 @@
     cell.rendered()

   add_input_cell = (notebook, opts={}) ->
-    cell = create_input_cell notebook
+    if opts.after?
+      cell = seek opts.after, forwards, is_input
+    else if opts.before?
+      cell = seek opts.before, backwards, is_input
+    unless cell? and is_clean cell
+      cell = create_input_cell notebook
+      insert_cell cell, opts
     set_cell_value cell, opts.code if opts.code?
-    insert_cell cell, opts
     cell

   # Add an input cell above the last input cell


Adding logic for non-telegram link options.... this should be updated when more platforms support link markdown, and a list should be kept for it

--- src/ondemand.coffee
+++ src/ondemand.coffee
@@ -130,8 +130,10 @@
           robot.logger.error err
         else if not data
           res.reply 'Not found'
+        else if robot.adapterName is 'telegram'
+          res.send util.format('[%s](%s)', data.title, data.url.replace('\\', ''))
         else
-          res.send util.format('[%s](%s)', data.title, data.url.replace('\\', ''))
+	  res.send util.format('%s: %s', data.title, data.url.replace('\\', ''))

   robot.respond /sortie/, (res) ->
     userDB.getPlatform res.message.room, (err, platform) ->


Actually fail when failing

Actually fail when failing

--- spec/Solving.coffee
+++ spec/Solving.coffee
@@ -111,9 +111,6 @@
       layout = require '../examples/helloworld/index'
       t.deliver page
       layout t
-      .then (c, d) ->
-        chai.expect(false).to.be.ok 'then reached'
-        done()
       .else (c, d) ->
         chai.expect(d).to.be.instanceof Error
         return done()
@@ -164,9 +161,6 @@
       layout = require '../examples/helloworld/index'
       t.deliver page
       layout t
-      .then (c, d) ->
-        chai.expect(false).to.be.ok 'then reached'
-        done()
       .else (c, d) ->
         chai.expect(d).to.be.instanceof Error
         return done()


Fixed broken toolbox spec.

Fixed broken toolbox spec.

--- test/spec/controllers/toolbox.coffee
+++ test/spec/controllers/toolbox.coffee
@@ -3,7 +3,9 @@
 ], (Toolbox)  ->

   describe 'Toolbox', ->
-    beforeEach -> @t = new Toolbox(commander: {})
+    beforeEach ->
+      canvas = jasmine.createSpyObj('canvas', ['clearSelection'])
+      @t = new Toolbox(commander: {}, canvas: canvas)

     it 'switches to SelectTool when initialised', ->
       expect(@t.currentTool).toBe(@t.tools.select)


[MPS/LT][106225716] Add spec for removing without-feature

--- test/spec/pinball_wizard_spec.coffee
+++ test/spec/pinball_wizard_spec.coffee
@@ -89,6 +89,13 @@
       expect(pinball.get('a')).toEqual('disabled')

     it 'adds use-feature class to css', ->
+      pinball.add
+        feature_a: 'inactive'
+      pinball.activate 'feature_a'
+      expect(document.documentElement.className).toEqual(' use-feature-a')
+
+    it 'removes without-feature', ->
+      document.documentElement.className = ' without-feature-a'
       pinball.add
         feature_a: 'inactive'
       pinball.activate 'feature_a'


Bug fix in History Show toggle

Bug fix in History Show toggle

--- client/message.coffee
+++ client/message.coffee
@@ -1324,9 +1324,12 @@
     Slider = Slider.default
     diffs = []
     @autorun =>
+      previous = messageHistory.get(@data._id)?.diffId
       if @slider?
-        previous = diffs[@slider.getValue()].diffId
         @slider.destroy()
+        @slider = null
+        ## Rehide slider's <input> in case we don't make one in this round.
+        @find('input').style.display = 'none'
       diffs = MessagesDiff.find
         id: @data._id
       ,
@@ -1361,7 +1364,7 @@
       ## Don't show a zero-length slider
       return if diffs.length < 2
       ## Draw slider
-      @slider = new Slider @$('input')[0],
+      @slider = new Slider @find('input'),
         #min: 0                 ## min and max not needed when using ticks
         #max: diffs.length-1
         #value: diffs.length-1  ## doesn't update, unlike setValue method below


Bug fix in naturallyFolded

Bug fix in naturallyFolded

--- client/message.coffee
+++ client/message.coffee
@@ -348,8 +348,8 @@
   @autorun =>
     data = Template.currentData()
     ## If message is naturally folded, don't count images it references.
-    images[@data._id].naturallyFolded = naturallyFolded data
-    if images[@data._id].naturallyFolded
+    images[data._id].naturallyFolded = naturallyFolded data
+    if images[data._id].naturallyFolded
       for id of @images
         images[id].count -= 1
         checkImage id


Move "-" coefficient normalisation logic.

I think it's better to have this special case in the Token constructor
than require a full pass of the tokens.

--- js/inequality.coffee
+++ js/inequality.coffee
@@ -24,6 +24,8 @@
 # Tokens have type and value
 class Token
   constructor: (@type, @value) ->
+    if @type == "COEFFICIENT" and @value == "-"
+      @value = "-1"

   toString: -> "[#{@type} '#{@value}']"

@@ -77,13 +79,6 @@
       return lex consume(input, result), tokens.concat(token)

   throw "Parse error at '#{input}' (tokens: #{tokens}, last token: #{previous})"
-
-normalise_minus_coefficients = (tokens) ->
-  tokens.map (t) ->
-    if t.type == "COEFFICIENT" and t.value == "-"
-      new Token "COEFFICIENT", "-1"
-    else
-      t

 # Replace all subtract operators with adds, normalising the coefficeints
 normalise_coefficients = (tokens, normalised=[]) ->
@@ -147,18 +142,13 @@

 # Test the parser
 inequation = good[1]
-console.log "\nParsing: #{inequation}\n"
+console.log "\nParsing:\n#{inequation}\n"

 tokens = lex inequation
-console.log "Tokens: #{tokens}"
+console.log "Tokens:\n#{tokens}\n"

-normalised = normalise_minus_coefficients tokens
-console.log "Normalised minus: #{normalised}"
-
-normalised = normalise_coefficients normalised
-console.log "Normalised coefficients: #{normalised}"
+normalised = normalise_coefficients tokens
+console.log "Normalised coefficients:\n#{normalised}\n"

 parsed = parse normalised
-console.log "Parsed: #{parsed}"
-
-console.log parse normalise_coefficients normalise_minus_coefficients tokens
+console.log "Parsed:\n#{parsed}\n"


Add docker-compose command

Add docker-compose command

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -163,11 +163,24 @@

     }

+    function docker_compose() {
+      if ! which docker-compose; then
+        echo 'error: docker-compose is not found'
+        echo '$ pip install docker-compose'
+        exit 1
+      fi
+
+      local ENTRYPOINT="/opt/koding/scripts/bootstrap-container $@"
+
+      docker-compose run --entrypoint $ENTRYPOINT backend
+    }
+
     function printHelp (){

       echo "Usage: "
       echo ""
       echo "  run                       : to start koding"
+      echo "  run docker-compose        : to start koding in docker-compose environment"
       echo "  run exec                  : to exec arbitrary commands"
       echo "  run install               : to compile/install client and "
       echo "  run buildclient           : to see of specified worker logs only"
@@ -438,6 +451,10 @@
       checkrunfile
       run $1

+    elif [ "$1" == "docker-compose" ]; then
+      shift
+      docker_compose
+
     elif [ "$1" == "exec" ]; then
       shift
       exec "$@"


config: migrate mongo automatically

config: migrate mongo automatically

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -459,6 +459,7 @@
     function restoredefaultmongodump () {
       removeDockerByName mongo
       runMongoDocker
+      mongomigrate up
     }

     function restoredefaultpostgresdump () {


updated atom conf

updated atom conf

--- atom/atom.symlink/config.cson
+++ atom/atom.symlink/config.cson
@@ -6,6 +6,7 @@
       brace_style: "collapse-preserve-inline"
       end_with_newline: true
   core:
+    closeDeletedFileTabs: true
     disabledPackages: [
       "whitespace"
       "linter-csslint"
@@ -23,6 +24,7 @@
     ]
     projectHome: "/Users/simon/code"
     telemetryConsent: "no"
+    warnOnLargeFileLimit: 5
   editor:
     fontSize: 16
   "exception-reporting":


Use unless instead of if for subscription check

--- src/directory.coffee
+++ src/directory.coffee
@@ -96,7 +96,7 @@

   # Private:
   subscribeToNativeChangeEvents: ->
-    if @watchSubscription?
+    unless @watchSubscription?
       @watchSubscription = pathWatcher.watch @path, (eventType) =>
         @emit "contents-changed" if eventType is "change"


Cancelling the start of a drag should also unhook the document's mousemove handlers.

--- src/draggable.coffee
+++ src/draggable.coffee
@@ -154,11 +154,19 @@
       false

     handleDocumentMouseMove: (e) =>
-      # Trigger the start event, once
-      @handleDragStart(e) unless @dragStarted
-
-      # Trigger the drag event
-      @handleDrag(e) if @dragStarted
+      if @dragStarted
+        # Trigger the drag event
+        @handleDrag(e)
+      else
+        # Trigger the start event
+        @handleDragStart(e)
+
+        if @dragStarted
+          # Trigger the drag event
+          @handleDrag(e)
+
+          # Broadcast to interested subscribers that this droppable is now in the air
+          @broadcast('start', e)

     handleDocumentMouseUp: (e) =>
       isLeftButton = e.which is 1
@@ -230,7 +238,9 @@
       eventMetadata = @getEventMetadata(startPosition, startOffset)

       # Call any user-supplied drag callback; cancel the start if it returns false
-      return if @getConfig().start?(e, eventMetadata) is false
+      if @getConfig().start?(e, eventMetadata) is false
+        @handleDragStop(e)
+        return

       @cancelAnyScheduledDrag()

@@ -266,9 +276,6 @@

       # Mark the drag as having started
       @dragStarted = true
-
-      # Broadcast to interested subscribers that this droppable is now in the air
-      @broadcast('start', e)

     handleDrag: (e) ->
       @scheduleDrag =>


Added length check on cleanup

Added length check on cleanup

--- src/esarchive.coffee
+++ src/esarchive.coffee
@@ -110,8 +110,9 @@
       (index, fn) =>
         fn(@log_rotation(node) > @parse_index_date(index))
       , (indices) =>
-        async.each indices, async.apply(@remove_index, node), (err) =>
-          logger.error err
+        if indices.length > 0
+          async.each indices, async.apply(@remove_index, node), (err) =>
+            logger.error err

   ###
   Removes a given index for a single node.


PERF: Allow getting of un-bound properties

--- src/god/world.coffee
+++ src/god/world.coffee
@@ -61,25 +61,31 @@
     value = @rx().array value if _.isArray(value)
     @doc.put(key, value)

-  get_local: (key) ->
-    if @has_local(key) then @doc.get(key) else null
+  get_local: (key, bound = true) ->
+    if @has_local(key)
+      if bound
+        @doc.get(key)
+      else
+        @doc.x[key]
+    else
+      null

   has_local: (key) ->
     @doc.has key

   # TODO: make @up a list
-  get_raw: (key, world) ->
-    value = @get_local(key)
+  get_raw: (key, world, bound = true) ->
+    value = @get_local(key, bound)
     return value if value?
-    authority = @get_local(my.key.authority)
+    authority = @get_local(my.key.authority, bound)
     if authority
       #console.log "Find #{key} in #{authority}"
-      value = authority.get_raw(key, @)
+      value = authority.get_raw(key, world, bound)
       return value if value?
-    @up.get_raw(key, world or @)
-
-  get: (key) ->
-    value = @get_raw(key)
+    @up.get_raw(key, world or this, bound)
+
+  get: (key, bound = true) ->
+    value = @get_raw(key, this, bound)
     return value(@,{}) if _.isFunction(value)
     value


Check more liberally for the nickserv notice

Also use the existential operator to check whether we have a nickserv password at all.

--- src/hubot/irc.coffee
+++ src/hubot/irc.coffee
@@ -31,9 +31,9 @@
     next_id = 1
     user_id = {}

-    if options.nickpass != 'undefined'
+    if options.nickpass?
       bot.addListener 'notice', (from, to, text) ->
-        if from == 'NickServ' and text.match new RegExp "^This nickname is registered"
+        if from == 'NickServ' and text.indexOf('registered') != -1
           bot.say 'NickServ', "identify #{options.nickpass}"

     bot.addListener 'message', (from, toRoom, message) ->


I changed it not to handle it when I pushed the tab with a suggest state in github

--- src/js/esarea.coffee
+++ src/js/esarea.coffee
@@ -3,6 +3,7 @@
 $(document).on 'keydown', 'textarea', (e) ->
   switch (e.which || e.keyCode)
     when 9
+      return if location.host.match(/(github\.com)/) and $('ul.suggestions').css('display') isnt 'none'
       handleTabKey(e)
     when 13
       handleEnterKey(e)


Added output path to Commander

Added output path to Commander

--- source/Commander.coffee
+++ source/Commander.coffee
@@ -4,6 +4,7 @@
 ###

 # Import winston & Downloader module
+path = require('path')
 winston = require('winston')
 Downloader = require('./Downloader')

@@ -17,5 +18,12 @@
 # Combine arguments to single string
 specs = components['_'].join(' ')

+# Fetch the output argument.
+output = null
+output = components['output'] if components['output']?
+
+# Parse output to absolute path
+location = path.resolve(process.cwd(), output)
+
 # Start download process
 Downloader.download(specs)


add url parameter

add url parameter

--- src/api/host.coffee
+++ src/api/host.coffee
@@ -236,6 +236,7 @@

 installPatchFromUrl.params = {
   host: { type: 'string' }
+  url: { type: 'string' }
 }

 installPatchFromUrl.resolve = {


client: add missing AccountsSwappable class

--- client/Account/AppView.coffee
+++ client/Account/AppView.coffee
@@ -51,3 +51,21 @@
     @name = @getData().title

   partial:(data)-> data.title
+
+class AccountsSwappable extends KDView
+  constructor:(options,data)->
+    options = $.extend
+      views : []          # an Array of two KDView instances
+    ,options
+    super
+    @setClass "swappable"
+    @addSubView(@view1 = @options.views[0]).hide()
+    @addSubView @view2 = @options.views[1]
+
+  swapViews:->
+    if @view1.$().is(":visible")
+      @view1.hide()
+      @view2.show()
+    else
+      @view1.show()
+      @view2.hide()


Use correct binding sets by first keystroke variable name

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -76,7 +76,7 @@

 afterEach ->
   keymap.bindingSets = bindingSetsToRestore
-  keymap.bindingSetsByFirstKeystrokeToRestore = bindingSetsByFirstKeystrokeToRestore
+  keymap.bindingSetsByFirstKeystroke = bindingSetsByFirstKeystrokeToRestore
   atom.deactivatePackages()
   if rootView?
     rootView.remove?()


Fixed bug where you would reload clouds every time.

--- app/assets/javascripts/views/clouds/show.js.coffee
+++ app/assets/javascripts/views/clouds/show.js.coffee
@@ -55,11 +55,12 @@
     $('.view-container').removeClass('active')
     $(@el).addClass('active')

-    if @.$('.chat-container').length == 0
+    if @.$('.chat-wrapper').children().length > 0
+      @chat_view.correctContainerScroll(true)
+    else
       @chat_view = new Cloudsdale.Views.CloudsChat(model: @model)
       @.$('.float-container > .container-inner > .chat-wrapper').replaceWith(@chat_view.el)
-    else
-      @chat_view.correctContainerScroll(true)
+

     if @.$('.cloud-sidebar-bottom').children().length == 0
       @users_view = new Cloudsdale.Views.CloudsUsers(model: @model)


Adding link for google authorship on blog posts

--- client/views/blog/blog.coffee
+++ client/views/blog/blog.coffee
@@ -35,6 +35,9 @@
   if author.profile and author.profile.twitter
     $('<meta>', { property: 'twitter:creator', content: author.profile.twitter }).appendTo 'head'

+  if author.profile and author.profile.profileUrl
+    $('<link>', { href: author.profile.profileUrl, rel: 'author' }).appendTo 'head'
+
   $('<meta>', { property: 'twitter:url', content: location.origin + location.pathname }).appendTo 'head'
   $('<meta>', { property: 'twitter:title', content: @data.title }).appendTo 'head'
   $('<meta>', { property: 'twitter:description', content: @data.excerpt }).appendTo 'head'


loadScript in CUI class is using CUI events now. See #49638

--- src/base/CUI.coffee
+++ src/base/CUI.coffee
@@ -845,15 +845,26 @@
 	@loadScript: (src) ->
 		deferred = new CUI.Deferred
 		script = CUI.dom.element("script", src: src)
-		script.addEventListener("load", (ev) =>
-			deferred.resolve(ev)
-			document.body.removeChild(script)
-		)
-		script.addEventListener("error", (ev) =>
-			deferred.reject(ev)
-			document.body.removeChild(script)
-		)
-		document.body.appendChild(script)
+
+		CUI.Events.listen
+			type: "load"
+			node: script
+			only_once: true
+			call: (ev) =>
+				document.head.removeChild(script)
+				deferred.resolve(ev)
+				return
+
+		CUI.Events.listen
+			type: "error"
+			node: script
+			only_once: true
+			call: (ev) =>
+				document.head.removeChild(script)
+				deferred.reject(ev)
+				return
+
+		document.head.appendChild(script)
 		return deferred.promise()

 # http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser


fixed SimpleAssigner

fixed SimpleAssigner

--- lib/assigners.coffee
+++ lib/assigners.coffee
@@ -61,7 +61,7 @@
       asst.showExitSurvey()
     else
       # Assign user to instance
-      treatment = _.sample @batch.getTreatments()
+      treatment = _.sample @batch.getTreatments() || []
       @assignToNewInstance( [asst], [treatment] )

 ###


:art: Support the upstream linter-plus API

--- lib/atom-hack.coffee
+++ lib/atom-hack.coffee
@@ -13,7 +13,9 @@
     CP = require 'child_process'
     Path = require 'path'
     return {
-      scopes: ['source.hack']
+      scopes: ['source.hack', 'source.html.hack']
+      scope: 'project'
+      lintOnFly: false
       lint: (ActiveEditor)->
         return new Promise (Resolve)->
           FilePath = ActiveEditor.getPath()
@@ -33,17 +35,17 @@
               Traces = []
               for Message in ErrorEntry
                 Traces.push(
-                  Type: 'Trace',
-                  Message: Message.descr,
-                  File: Message.path,
-                  Position: [[Message.line,Message.start],[Message.line,Message.end]]
+                  type: 'Trace',
+                  message: Message.descr,
+                  file: Message.path,
+                  position: [[Message.line,Message.start],[Message.line,Message.end]]
                 )
               ToReturn.push(
-                Type: 'Error',
-                Message: First.descr,
-                File: First.path,
-                Position: [[First.line,First.start],[First.line,First.end]]
-                Trace: Traces
+                type: 'Error',
+                message: First.descr,
+                file: First.path,
+                position: [[First.line,First.start],[First.line,First.end]]
+                trace: Traces
               )
             Resolve(ToReturn)
     }


View tells you to open a new window if you are already in a session

--- lib/atom_pair.coffee
+++ lib/atom_pair.coffee
@@ -88,6 +88,12 @@
     _.each atom.workspace.getModalPanels(), (panel) -> panel.hide()

   joinSession: ->
+
+    if @markerColour
+      alreadyPairing = new AlertView "It looks like you are already in a pairing session. Please open a new window (cmd+shift+N) to start/join a new one."
+      atom.workspace.addModalPanel(item: alreadyPairing, visible: true)
+      return
+
     @joinView = new InputView("Enter the session ID here:")
     @joinPanel = atom.workspace.addModalPanel(item: @joinView, visible: true)
     @joinView.miniEditor.focus()


Fixed invalid reference and added a new method for authorizing a user exists.

--- src/AccountsUtil.coffee
+++ src/AccountsUtil.coffee
@@ -59,11 +59,15 @@
     if predicate
       predicate(doc, user)
     else
-      isOwnerOrAdmin(user)
+      @isOwnerOrAdmin(doc, user)

   authorize: (doc, user, predicate) ->
     unless @isAuthorized(doc, user, predicate)
       throw new Meteor.Error(403, 'Access denied')
+
+  authorizeUser: (user, predicate) ->
+    predicate ?= (user) -> user?
+    unless predicate(@resolveUser(user)) then throw new Meteor.Error(403, 'Access denied')

   isAdmin: (user) ->
     user = @resolveUser(user)


Fix typo in Accuracy Corrector.

Fix typo in Accuracy Corrector.

--- coffeescripts/cards-en.coffee
+++ coffeescripts/cards-en.coffee
@@ -513,7 +513,7 @@
         "Calculation":
             text: """When attacking, you may spend a focus token to change 1 of your %FOCUS% results to a %CRIT% result."""
         "Accuracy Corrector":
-            text: """When attacking, you may cancel all dice results. Then, you may add 2 %HIT% results.%LINEBREAK%Your dice cannot be modified again during this attack."""
+            text: """When attacking, you may cancel all of your dice results. Then, you may add 2 %HIT% results.%LINEBREAK%Your dice cannot be modified again during this attack."""
         "Inertial Dampeners":
             text: """When you reveal your maneuver, you may discard this card to instead perform a white [0%STOP%] maneuver. Then receive 1 stress token."""
         "Flechette Cannon":


handle error objects slightly better

handle error objects slightly better

--- src/builtins.coffee
+++ src/builtins.coffee
@@ -131,6 +131,12 @@
       render: -> React.DOM.pre {className: 'error'}, @props.message

     component_fn 'error', 'Shows a preformatted error message', (message) ->
+      if not message?
+        message = 'Unknown error'
+        # TODO include stack trace?
+      else if not _.isString message
+        message = message.toString()
+        # TODO handle exceptions better
       ErrorComponent {message}

     ExampleComponent = React.createClass


Update find-and-replace:use-selection-as-find-pattern

This method now calls the search

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -183,8 +183,8 @@
       @resultCounter.text("#{markerIndex + 1} of #{@markers.length}")

   setSelectionAsFindPattern: =>
-    if text = @findModel.getEditSession().getSelectedText()
-      @findEditor.setText(text)
+    pattern = @findModel.getEditSession().getSelectedText()
+    @findModel.update {pattern}

   toggleRegexOption: =>
     @findModel.update {pattern: @findEditor.getText(), useRegex: !@findModel.useRegex}


Add tooltip to whole word button

Add tooltip to whole word button

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -110,6 +110,10 @@
       title: "Only In Selection",
       keyBindingCommand: 'find-and-replace:toggle-selection-option',
       keyBindingTarget: @findEditor.element
+    subs.add atom.tooltips.add @wholeWordOptionButton,
+      title: "Whole Word",
+      keyBindingCommand: 'find-and-replace:toggle-whole-word-option',
+      keyBindingTarget: @findEditor.element

     subs.add atom.tooltips.add @nextButton,
       title: "Find Next",


scroll to first result marker on type

scroll to first result marker on type

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -283,6 +283,8 @@
       results = @markers.length
       resultsStr = if results then _.pluralize(results, 'result') else 'No results'
       @setInfoMessage("#{resultsStr} found for '#{@findModel.pattern}'")
+      if @findEditor.hasFocus() && results > 0
+        @findAndSelectResult(@selectFirstMarkerStartingFromCursor, {focusEditorAfter: false})
     else
       @clearMessage()

@@ -321,7 +323,11 @@
     markerIndex = @firstMarkerIndexAfterCursor()
     @selectMarkerAtIndex(markerIndex)

-  firstMarkerIndexAfterCursor: ->
+  selectFirstMarkerStartingFromCursor: =>
+    markerIndex = @firstMarkerIndexAfterCursor(true)
+    @selectMarkerAtIndex(markerIndex)
+
+  firstMarkerIndexAfterCursor: (indexIncluded = false)->
     editor = @findModel.getEditor()
     return -1 unless editor

@@ -331,6 +337,7 @@

     for marker, index in @markers
       markerStartPosition = marker.bufferMarker.getStartPosition()
+      return index if markerStartPosition.isEqual(start) and indexIncluded
       return index if markerStartPosition.isGreaterThan(start)
     0


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -21,7 +21,7 @@
       }

     if value?
-      # Use th
+      # Use the value given
       I[name] = options.value
     else
       # Initialize field if it doesn't exist


render SyntaxErrors

render SyntaxErrors

--- lib/rendering.coffee
+++ lib/rendering.coffee
@@ -158,11 +158,25 @@
 renderError = (err, expression) ->
   error = err.error
   msg = error.errorString or err.type
+  if err.type is 'SyntaxError'
+    msg = "#{error.syntaxErrors.msg}"
   msgh = "
#{msg}
"
   lines = []

-  # if err.type == 'SyntaxError'
-  #   serr = parseSyntaxError(error.error.stdout)
+  if err.type == 'SyntaxError'
+    lines.push """
+

+
#{error.syntaxErrors.code}

+

+    """
+    if error.syntaxErrors.file != 'selected text'
+      uri = "#{error.syntaxErrors.file}:#{error.syntaxErrors.line}"
+      lines.push """
+

+          in file #{error.syntaxErrors.file
+        }

+      """
+

   # dup of errorString for now
   # lines.push "#{error.what}"


Implemented find method

Implemented find method

--- src/quo.query.coffee
+++ src/quo.query.coffee
@@ -12,6 +12,14 @@
         dom_elements = document.querySelectorAll(selector)
         dom_elements = Array::slice.call(dom_elements)
         dom_elements
+
+    $$.fn.find = (selector) ->
+        result = undefined
+        if @length is 1
+            result = Quo.query(@[0], selector)
+        else
+            result = @map(-> Quo.query @, selector )
+        $$ result

     $$.fn.parent = (selector) ->
         ancestors = (if (selector) then _findAncestors(@) else @instance(PARENT_NODE))


Use removeChild to remove root

Use removeChild to remove root

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -204,8 +204,9 @@
     @width(@list.outerWidth())

   updateRoot: (expandedEntries={}) ->
-    @root?.directory?.destroy()
-    @root?.remove()
+    if @root?
+      @root.directory.destroy()
+      @list.element.removeChild(@root)

     if projectPath = atom.project.getPath()
       directory = new Directory({


Fix drag and drop uploads from chrome download bar

--- src/dropzone.coffee
+++ src/dropzone.coffee
@@ -535,6 +535,8 @@
             noPropagation e
             @emit "dragenter", e
           "dragover": (e) =>
+            efct = e.dataTransfer.effectAllowed
+            e.dataTransfer.dropEffect = ('move' == efct || 'linkMove' == efct) ? 'move' : 'copy'
             noPropagation e
             @emit "dragover", e
           "dragleave": (e) =>


Ensure that theme styles are applied after packages

--- src/atom-package.coffee
+++ src/atom-package.coffee
@@ -64,7 +64,8 @@

   activateResources: ->
     keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps
-    applyStylesheet(stylesheetPath, content) for [stylesheetPath, content] in @stylesheets
+    type = if @metadata.theme then 'theme' else 'bundled'
+    applyStylesheet(stylesheetPath, content, type) for [stylesheetPath, content] in @stylesheets
     syntax.addGrammar(grammar) for grammar in @grammars
     for [scopedPropertiesPath, selector, properties] in @scopedProperties
       syntax.addProperties(scopedPropertiesPath, selector, properties)
@@ -139,7 +140,8 @@
     oldSheets = _.clone(@stylesheets)
     @loadStylesheets()
     removeStylesheet(stylesheetPath) for [stylesheetPath] in oldSheets
-    applyStylesheet(stylesheetPath, content) for [stylesheetPath, content] in @stylesheets
+    type = if @metadata.theme then 'theme' else 'bundled'
+    applyStylesheet(stylesheetPath, content, type) for [stylesheetPath, content] in @stylesheets

   requireMainModule: ->
     return @mainModule if @mainModule?


Move message stuff back to session

Move message stuff back to session

--- src/flowdock.coffee
+++ src/flowdock.coffee
@@ -36,6 +36,43 @@
   stream: (flows...) ->
     flows = flows[0] if flows[0] instanceof Array && flows.length == 1
     return new Stream(@auth, flows)
+
+  send: (subdomain, flow, message, callback) ->
+    json = JSON.stringify(message)
+    options =
+      host: FLOWDOCK_API_URL.hostname
+      port: FLOWDOCK_API_URL.port
+      path: '/flows/' + subdomain + '/' + flow + '/messages'
+      method: 'POST'
+      headers:
+        'Authorization': @auth
+        'Content-Type': 'application/json'
+        'Content-Length': json.length
+        'Accept': 'application/json'
+
+    req = httpClient.request options, (res) ->
+      if res.statusCode >= 500
+        @emit "error", res.statusCode, "Couldn't estabilish a connection to Flowdock Messages API"
+        return
+      if res.statusCode >= 400
+        @emit 'error', res.statusCode, "Couldn't post your #{data.event} to Flowdock Messages API"
+        return
+      callback res if callback
+    req.write(json)
+    req.end()
+
+  message: (subdomain, flow, message, tags) ->
+    data =
+      event: 'message'
+      content: message
+      tags: tags || []
+    @send(subdomain, flow, data)
+
+  status: (subdomain, flow, status) ->
+    data =
+      event: 'status'
+      content: status
+    @send(flow, data)

 class Stream extends process.EventEmitter
   constructor: (@auth, @flows) ->
@@ -79,41 +116,4 @@
   close: () ->
     @stream.abort()

-  message: (flow, message, tags) ->
-    data =
-      event: 'message'
-      content: message
-      tags: tags || []
-    @post(flow, data)
-
-  status: (flow, status) ->
-    data =
-      event: 'status'
-      content: status
-    @post(flow, data)
-
-  post: (flow, data) ->
-    post_data = querystring.stringify(data)
-    options =
-      host: FLOWDOCK_API_URL.hostname
-      port: FLOWDOCK_API_URL.port
-      path: '/flows/' + flow + '/messages'
-      method: 'POST'
-      headers:
-        'Authorization': @auth
-        'Content-Type': 'application/x-www-form-urlencoded'
-        'Content-Length': post_data.length
-        'Accept': 'application/json'
-
-    req = httpClient.request options, (res) ->
-      if res.statusCode >= 500
-        @emit "error", res.statusCode, "Couldn't estabilish a connection to Flowdock Messages API"
-        return
-      if res.statusCode >= 400
-        @emit 'error', res.statusCode, "Couldn't post your #{data.event} to Flowdock Messages API"
-        return
-    req.write(post_data)
-    req.end()
-
-
 exports.Session = Session


adding in courseId param for Toc

adding in courseId param for Toc

--- src/flux/toc.coffee
+++ src/flux/toc.coffee
@@ -12,7 +12,7 @@
     @_toc = null
     @_sections = {}

-  load: ->
+  load: (courseId) ->
   loaded: (obj) ->
     @_toc = obj
     # Load all the section id's for easy lookup later.


add ability to send caption override with a pipe delimeter

--- src/frinkiac.coffee
+++ src/frinkiac.coffee
@@ -8,7 +8,7 @@
 #   None
 #
 # Commands:
-#   hubot simpsons search <query> - displays a screenshot from the simpsons related to your search
+#   hubot simpsons search <query> | <caption override> - displays a screenshot from the simpsons related to your search;
 #
 # Notes:
 #   None
@@ -32,32 +32,36 @@
   encodeURIComponent(str).replace /[!'()*]/g, (c) ->
     '%' + c.charCodeAt(0).toString(16)

+trimWhitespace = (string) ->
+  string.replace /^\s*|\s*$/g, ''
+
 addLineBreaks = (str) ->
+  wordCount = 5
   newString = ''
   str.split(' ').forEach (word, i) ->
     i++
-    delimiter = if i % 4 then ' ' else '\n'
+    delimiter = if i % wordCount then ' ' else '\n'
     newString += word + delimiter
   newString

 getImageUrl = (episode, timestamp, caption) ->
-  encodedUrl = encode(addLineBreaks(caption))
+  encodedUrl = encode(addLineBreaks(trimWhitespace(caption)))
   "https://frinkiac.com/meme/#{episode}/#{timestamp}.jpg?lines=#{encodedUrl}"

 module.exports = (robot) ->
   robot.respond /(simpsons search|frinkiac) (.*)/i, (msg) ->
-    query = msg.match[2]
+    query = msg.match[2].split('|');

-    axios(getRequestConfig('search', {q: query}))
+    axios(getRequestConfig('search', {q: query[0]}))
       .then (response) ->
         if (response.data.length)
           episode = response.data[0].Episode
           timestamp = response.data[0].Timestamp
           axios(getRequestConfig('caption', {e: episode, t: timestamp}))
             .then (response) ->
-              caption = response.data.Subtitles[0].Content
+              caption = if query[1] then query[1] else response.data.Subtitles[0].Content
               msg.send getImageUrl(episode, timestamp, caption)
         else
-          console.log("D'oh! I couldn't find anything for `#{query}`.");
+          console.log("D'oh! I couldn't find anything for `#{query[0]}`.");
       .catch (error) ->
         console.error(error);


cors proxy change

cors proxy change

--- src/g/config.coffee
+++ src/g/config.coffee
@@ -6,7 +6,7 @@
   defaults:
     registerMouseHover: false,
     registerMouseClicks: true,
-    importProxy: "https://cors-anywhere.herokuapp.com/"
+    importProxy: "http://crossorigin.me/"
     eventBus: true
     alphabetSize: 20
     dropImport: false


Files in `src` should not use the atom export

--- src/root-view.coffee
+++ src/root-view.coffee
@@ -1,4 +1,4 @@
-{fs} = require 'atom'
+fs = require 'fs'
 ipc = require 'ipc'
 path = require 'path'
 Q = require 'q'
@@ -181,7 +181,7 @@
     promise = project.openAsync(filePath, {initialLine}) if not editSession

     fileSize = 0
-    fileSize = fs.statSync(absoluteFilePath).size if fs.exists(absoluteFilePath)
+    fileSize = fs.statSync(absoluteFilePath).size if fsUtils.exists(absoluteFilePath)

     Q(editSession ? promise)
       .then (editSession) =>


Don’t need this comment!

Don’t need this comment!

--- src/husk-cli.coffee
+++ src/husk-cli.coffee
@@ -2,11 +2,6 @@

 Utils = require './utils'

-###
-This CLI is mainly for benchmarking. While there may be useful data output to
-the console, it will probably change. The options will probably change as
-well.
-###
 main = ->
   argParser = new ArgumentParser
     version: require('../package.json').version


lower max sockets

lower max sockets

--- src/prefixer.coffee
+++ src/prefixer.coffee
@@ -1,13 +1,12 @@
-#Agent = require('agentkeepalive').HttpsAgent
-#keepAliveAgent = new Agent
-#	maxSockets: 500,
-#	maxFreeSockets: 250,
-#	timeout: 600000,
-#	keepAliveTimeout: 600000
+Agent = require('agentkeepalive').HttpsAgent
+keepAliveAgent = new Agent
+	maxSockets: 50,
+	maxFreeSockets: 0,
+	timeout: 600000,
+	keepAliveTimeout: 600000

-https = require('https');
-keepAliveAgent = new https.Agent({ keepAlive: true, maxSockets: 500 });
-
+#https = require('https');
+#keepAliveAgent = new https.Agent({ keepAlive: true, maxSockets: 30, maxFreeSockets: 30, keepAliveMsecs: 1000 });

 module.exports = (request)->
 	request.url = 'https://prod-api01.clanofthecloud.mobi'+request.url
@@ -18,3 +17,5 @@

 	# not required in browsers
 	request.agent keepAliveAgent
+
+#https.globalAgent = keepAliveAgent


Dynamic initialization

Dynamic initialization

--- src/typeahead.coffee
+++ src/typeahead.coffee
@@ -12,13 +12,15 @@
 	link: (scope, element, attributes) ->
 		$lis = $ul = currentEl = null
 		$timeout ->
-			$lis = element.find "li"
 			$ul = element.find "ul"
-			$lis.addClass "hide"
+			$ul.addClass "hide"
 			null
 		watchTerm = if scope.inputFromParent then "$parent.#{scope.inputFromParent}" else "term"
 		scope.$watch watchTerm, ->
-			return unless $lis
+			unless $lis
+				$lis = $ul.find "li"
+				$lis.addClass "hide"
+				$ul.removeClass "hide"
 			currentEl.className = "show" if currentEl
 			currentEl = null
 			term = if scope.inputFromParent then scope.$parent[scope.inputFromParent] else scope.term


Scripts: merge fuckup fixed

Scripts: merge fuckup fixed

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -96,8 +96,6 @@

     bongoModels.JCustomPartials.one selector, (err, partial)->
       customPartial = partial.data  if not err and partial
-      html = createHTML()
-      return callback null, html

       bongoModels.JGroup.one {slug : slug or 'koding'}, (err, group) ->
         console.log err if err


add aXe testing to conversation views

add aXe testing to conversation views

test plan:
- run the spec files edited in this patch normally with
  yarn run jspec-watch path/to/spec
- tests should all pass as expected
- run the spec files edited in this patch with the new command
  yarn run a11y-report path/to/spec
- you should get aXe errors on existing a11y violations

Change-Id: I4a476c5fb448462419f239741f5886a9a1f3c01d
Reviewed-on: https://gerrit.instructure.com/130951
Tested-by: Jenkins
Reviewed-by: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
Product-Review: Matt Berns <e53c8988cd428dde240324ce2797c8eb7073560f@instructure.com>
QA-Review: Matt Berns <e53c8988cd428dde240324ce2797c8eb7073560f@instructure.com>

--- spec/coffeescripts/views/conversations/CourseSelectionViewSpec.coffee
+++ spec/coffeescripts/views/conversations/CourseSelectionViewSpec.coffee
@@ -22,7 +22,8 @@
   'compiled/collections/FavoriteCourseCollection'
   'compiled/collections/GroupCollection'
   'helpers/fakeENV'
-], (Course, CourseSelectionView, CourseCollection, FavoriteCourseCollection, GroupCollection, fakeENV) ->
+  'helpers/assertions'
+], (Course, CourseSelectionView, CourseCollection, FavoriteCourseCollection, GroupCollection, fakeENV, assertions) ->
   courseSelectionView = () ->
     courses =
       favorites: new FavoriteCourseCollection()
@@ -38,6 +39,11 @@
       fakeENV.setup(CONVERSATIONS: {CAN_MESSAGE_ACCOUNT_CONTEXT: false})
     teardown: ->
       fakeENV.teardown()
+
+  test 'it should be accessible', (assert) ->
+    course = new Course
+    done = assert.async()
+    assertions.isAccessible courseSelectionView(), done, {'a11yReport': true}

   test 'does not label an un-favorited course as concluded', ->
     course = new Course


Changed string passed to element.closest() when removing errors so elements with multiple classes can have error markup removed.

--- coffeescript/rails.validations.coffee
+++ coffeescript/rails.validations.coffee
@@ -286,7 +286,7 @@

       remove: (element, settings) ->
         errorFieldClass = jQuery(settings.input_tag).attr('class')
-        inputErrorField = element.closest(".#{errorFieldClass}")
+        inputErrorField = element.closest(".#{errorFieldClass.replace(" ", ".")}")
         label = jQuery("label[for='#{element.attr('id')}']:not(.message)")
         labelErrorField = label.closest(".#{errorFieldClass}")


Try to prevent double tap to zoom on ios

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -7,7 +7,24 @@
   return [t.clientX - p.left, t.clientY - p.top]

+IS_IOS = /iphone|ipad/i.test(navigator.userAgent)
+$.fn.nodoubletapzoom = ->
+  if (IS_IOS)
+    $(this).bind 'touchstart', (e) ->
+      t2 = e.timeStamp
+      t1 = $(this).data('lastTouch') || t2
+      dt = t2 - t1
+      fingers = e.originalEvent.touches.length;
+      $(this).data('lastTouch', t2);
+      if !dt || dt > 500 || fingers > 1 then return; # not double-tap
+
+      e.preventDefault() # double tap - prevent the zoom
+      # also synthesize click events we just swallowed up
+      $(this).trigger('click').trigger('click')
+
+
 $.fn.literallycanvas = ->
+  @nodoubletapzoom()

   $c = @find('canvas')
   c = $c.get(0)


[rf] wrap img as function.

[rf] wrap img as function.

--- js/main.coffee
+++ js/main.coffee
@@ -41,6 +41,14 @@
     $('ul li span.badge').each (_, badge) ->
         parent = $(badge).parent()
         $(badge).detach().prependTo(parent)
+
+wrap_img = () ->
+    $('img').each (_, img) ->
+        alt = $(img).attr('alt')
+        if alt?.trim().length
+            $(img).wrap($('<div>').addClass('figure'))
+                  .after($('<p>').html($('<em>').html(alt)))
+                  .wrap('<p>')

 list_toggle = (it) ->
     parent = $(it).parent()
@@ -88,13 +96,8 @@
     correct_badge_position()
     make_recent_date()
     monitor_device_mode()
+    wrap_img()

-    $('img').each (_, img) ->
-        alt = $(img).attr('alt')
-        if alt?.trim().length
-            $(img).wrap($('<div>').addClass('figure'))
-                  .after($('<p>').html($('<em>').html(alt)))
-                  .wrap('<p>')

     $('.tag-sort').click (event) ->
         event.preventDefault()
@@ -105,6 +108,7 @@
     $('.list-toggle').click (event) ->
         event.preventDefault()
         list_toggle(this)
+        correct_fixed_position_width()

     $('#menu-toggle').click (event) ->
         event.preventDefault()


Updated atom-quickstart configuration: /usr/lib/atom-quickstart/config/ / CLOSE_WRITE,CLOSE config.cson

--- config/config.cson
+++ config/config.cson
@@ -37,7 +37,7 @@
   "exception-reporting":
     userId: "110bb6f9-b221-46bb-894e-cc3b978a51b6"
   "file-types":
-    template: ""
+    template: "source.yaml.cf"
   "java-generator":
     toggles:
       appendThisToGetters: true


Changed the parameters to required

Changed the parameters to required

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -4,8 +4,8 @@
 program
 	.version(pkg.version)
 	.option('-v, --verbose')
-	.option('-H, --hostname [hostname]', 'Hostname to listen [localhost]', 'localhost')
-	.option("-P, --port [port]", "Port to listen [6661]", 6661).parse process.argv
+	.option('-H, --hostname <hostname>', 'Hostname to listen [localhost]', 'localhost')
+	.option("-P, --port <port>", "Port to listen [6661]", 6661).parse process.argv

 require('./util/shellexec').sh.isVerbose = program.verbose
 require('./server').listen program.port, program.hostname, ->


(minor) Added more debugs on disconnect

(minor) Added more debugs on disconnect

--- lib/esl.coffee
+++ lib/esl.coffee
@@ -397,8 +397,12 @@
       if exports.debug
         util.log "Received ESL disconnection notice"
       switch call.headers['Content-Disposition']
-        when 'linger'      then @socket.emit 'esl_linger', call
-        when 'disconnect'  then @socket.emit 'esl_disconnect', call
+        when 'linger'
+          if exports.debug then util.log "Sending esl_linger"
+          @socket.emit 'esl_linger', call
+        when 'disconnect'
+          if exports.debug then util.log "Sending esl_disconnect"
+          @socket.emit 'esl_disconnect', call

     #### Linger
     # The default behavior in linger mode is to disconnect the call


fix the way transcripion is recorded in classification

--- app/assets/javascripts/components/image-subject-viewer-transcribe.cjsx
+++ app/assets/javascripts/components/image-subject-viewer-transcribe.cjsx
@@ -138,9 +138,10 @@
     # TODO: annotate new transcription and submit as new classification!!!

     for mark in [ @state.marks... ]
-      @state.classification.annotate
-        timestamp: mark.timestamp
-        transcription: mark.transcription
+      if mark.transcription isnt undefined
+        @state.classification.annotate
+          timestamp: mark.timestamp
+          transcription: mark.transcription

     # DEBUG CODE
     console.log 'CLASSIFICATION: ', @state.classification
@@ -336,18 +337,14 @@
       marks: marks
       selectedMark: null

-  recordTranscription: (field, content) ->
+  recordTranscription: (transcription) ->
     console.log 'SELECTED MARK: ', @state.selectedMark
-    console.log "RECORDING TRANSCRIPTION: [#{field}, #{content}]"
-    transcription =
-      field: field
-      content: content
+    console.log "RECORDING TRANSCRIPTION: #{transcription}"
     selectedMark = @state.selectedMark
     selectedMark.transcription = transcription

     @setState selectedMark: selectedMark, =>
       console.log 'SELECTED MARK: ', @state.selectedMark
-

   beginTextEntry: ->
     # console.log 'beginTextEntry()'


Refactor loadHit to use async.auto

Refactor loadHit to use async.auto

--- loadHIT.coffee
+++ loadHIT.coffee
@@ -14,316 +14,334 @@
 loadHIT = (opts, callback) ->
   async.waterfall([
     (cb) -> cb(null, opts) # Pipe opts to first func in waterfall
-    getBasicInfo,
-    getExtraInfo,
-    getCondition,
-    excludeWorkers,
-    sampleFacet,
-    getMaxedItems,
-    sampleTaskItems,
-    prepareTaskSet,
+    prepareTaskset,
     cleanForFrontEnd
   ],
   callback #Send back to the calling script
   )

-# 1. Count how many taskSets this user has done in this condition
-# Also fetch HIT info, since it can be done asynchronously
-getBasicInfo = (opts, callback) ->
-  async.parallel({
+#1. Get as much info asynchronously as we can
+prepareTaskset = (opts, callback) ->
+  async.auto({
     opts: (callback) -> callback(null, opts)
+
     # Count of all taskSets done by the user
     countUserAll: (callback) -> TaskSet.countUserAll(opts.user, callback)
-    # Count of tasksets in the given condition done
+
+    # Count of tasksets in the given hit done
     countUserHIT: (callback) ->
       TaskSet.countUserHIT(opts.user, opts.hit_id, callback)
+
     # List of user's past items completed (not specific to the current hit)
     userItemList: (callback) -> TaskSet.userItemList(opts.user, callback)
+
+    userFacetItemList: ['userItemList', 'facet', (callback, obj) ->
+      if not obj.facet
+        return callback(null, obj.userItemList.userItemList)
+
+      list = _.filter(obj.userItemList, (item) ->
+        if not item._id.facet
+          return false
+        return item._id.facet.equals(obj.facet._id)
+      )
+      callback(null, list)
+    ]
+
+
     # Counts of all the facet that have been completed for the task, if any
     countFacetsCompleted: (callback) ->
       TaskSet.countFacetsCompleted(opts.hit_id, callback)
+
+    # Count how much of each facet the user has completed
+    userFacetCounts: ['userItemList',  ((callback, obj) ->
+      counts = _.countBy(obj.userItemList, (item) ->
+        if item._id.facet
+          return item._id.facet.toString()
+        else
+          return null
+        )
+      callback(null, counts)
+    )]
+
     # Load HIT info
-    hit: (callback) -> Hit.findOne({_id: ObjectId(opts.hit_id)},
-      (err, results) ->
-        if results is null
-          callback("HIT not found", null)
-        else
+    hit: (callback) ->
+      Hit
+        .findOne({_id: ObjectId(opts.hit_id)})
+        .exec((err, results) ->
+          if results is null then return callback("HIT not found", null)
           callback(err, results)
-    ),
+        )
+
     # Clear locks
-    locksCleared: (callback) -> TaskSet.clearLocks(opts.user, callback)
-  }, callback
+    locksCleared: (callback) ->
+      TaskSet
+        .clearLocks(opts.user)
+        .exec((err, results)->callback(err, !!results))
+
+    # Get all hits with the same hit type as obj.hit
+    allHitType: ['hit', ((callback, obj) ->
+      Hit.listAllByType(obj.hit.type, callback)
+    )]
+
+    payment: ['hit', (callback, obj) ->
+      callback(null, {base: obj.hit.payment, bonus:obj.hit.bonus})
+    ]
+
+    ## Exclude workers if relevant (returns either false or an err)
+    excludeWorker: ['hit', 'opts', 'allHitType', ((callback, obj) ->
+      if not obj.hit.exclude.pastInTaskType
+        return callback(null, false)
+      if obj.hit.exclude.pastInTaskType
+        # Find hits with the same type (excluding this one)
+        hit_ids = _.filter(obj.allHitType, (id) -> id isnt obj.hit._id)
+        TaskSet.findOne(
+          {hit_id:{$in:hit_ids}, user:obj.opts.user}
+        ).exec((err, results) ->
+          if (err) then console.error err
+          if results
+            return callback("It looks like you've done a very similar " +
+              "task to this one, so it wouldn't be fun to do it again. " +
+              "Sorry to let you know after accepting, but we don't have " +
+              "information about you during the preview.", true)
+          else
+            return callback(null, false)
+        )
+    )],
+
+    condition: ['hit', 'countUserHIT', (callback, obj) ->
+      if obj.countUserHIT is 0
+        return callback(null, obj.hit.condition.firstTask)
+      else
+        return callback(null, obj.hit.condition.laterTasks)
+    ]
+
+    # Set 'design' (basic, teaching, and fast are designs, but the 'feedback'
+    # condition needs an underlying design of its own.
+    design: ['condition', (callback, obj) ->
+      if obj.condition is 'feedback'
+        # This assumes that feedback is always a laterTasks condition
+        design = obj.hit.condition.firstTask
+      else
+        design = obj.condition
+      callback(null, design)
+    ]
+
+    # Get Feedback, if required
+    performanceFeedback: ['design', 'allHitType', (callback, obj) ->
+      if obj.condition is 'feedback'
+        getFeedback = require './getFeedback'
+        return getFeedback(obj, callback)
+      else
+        return callback(null, null)
+    ]
+
+    ## Sample a facet, if relevant
+    ## Facets let us define subgroups of the data, like 'queries' for an IR hit
+    facet: ['hit', 'countFacetsCompleted', 'userFacetCounts',
+    (callback, obj) ->
+      if not (obj.hit.facets and obj.hit.facets.length > 0)
+        callback(null, null)
+
+      # Rather than randomly sampling a facet, let's sample the one with the
+      # least completed or locked items
+      facets = _.map(obj.hit.facets, (item) -> item._id.toString())
+      flist = _.map(obj.countFacetsCompleted, (item)-> item._id.toString())
+
+      # Prepend any facets that haven't been started
+      flist = _.difference(facets, flist).concat(flist)
+
+      facetItemCounts = _.map(obj.hit.facets, (facet) ->
+        {_id:facet._id.toString(), count:facet.items.length}
+      )
+      invalidFacets = []
+      for item in facetItemCounts
+        c = obj.userFacetCounts
+        if c[item._id] and (c[item._id] >= item.count)
+          invalidFacets.push item._id
+      flist = _.difference(flist, invalidFacets)
+
+      # If there are no valid facets, we know that there's nothing for the user
+      if flist.length is 0
+        msg = "No tasks available. This may be because you've finished all "+
+        "the tasks that we have ready, or other people are working on "+
+        "everything on that's available (try again later), or we screwed "+
+        "something up (sorry)."
+        return callback(msg, null)
+
+      # Sample Facet
+      f_id_str = flist[0]
+      sampled_facet = _.find(obj.hit.facets, (facet) ->
+        (facet._id.toString() == f_id_str)
+      )
+      callback(null, sampled_facet)
+    ]
+
+  samplePool: ['hit', 'facet', (callback, obj) ->
+    items = if obj.facet then obj.facet.items else obj.hit.items
+    callback(null, items)
+  ]
+
+    # Determine which items have already been done enough
+  maxed: ['opts', 'hit', 'design', 'facet', (callback, obj) ->
+    if obj.design is 'teaching'
+      ''' Don't need maxed info because teaching set is pre-determined '''
+      return callback(null, null)
+
+    if obj.opts.user == 'PREVIEWUSER'
+      console.log "Preview user, no maxed items"
+      return callback(null, [])
+
+    # Get a list of all currentHIT items completed
+    agg = [
+      {$match:
+        hit_id:obj.opts.hit_id
+        'facet._id': (if obj.facet then ObjectId(obj.facet._id) else null)
+      },
+      {$project: "tasks.item.id":1 },
+      {$unwind : "$tasks"},
+      # Matching on task item id *after* unwinding
+      {$match: 'tasks.item.id': $in: obj.samplePool },
+      {$group:
+        _id: "$tasks.item.id",
+        count: $sum:1
+      },
+      # We want to know which tasks have already be done max times
+      {$match:
+        count:
+          $gte:obj.hit.setsPerItem
+      }
+    ]
+    TaskSet.aggregate(agg).exec((err, results) ->
+      if (err) then return callback(err, null)
+      maxed = (result._id for result in results)
+      callback(null, maxed)
+    )
+    return
+  ]
+
+  # An input hit might have errors, so we're doublechecking maxSetSize,
+  # setting our own by the rules
+  maxSetSize: ['hit', 'design', (callback, obj) ->
+    # Fast design shouldn't specify a max size
+    if obj.design is 'fast' and obj.hit.maxSetSize
+      # Ignore any max set size that may be specified, and return a large
+      # number of items instead
+      console.info "Ignoring Fast design's maxSetSize"
+      return callback(null, 30)
+    else if obj.hit.maxSetSize
+      return callback(null, obj.hit.maxSetSize)
+    else
+      console.warn("No max set size set, forcing to 15")
+      return callback(null, 15)
+  ]
+  # Sample task items
+  sample : ['hit', 'maxed', 'design', 'samplePool', 'userFacetItemList',
+  (callback, obj) ->
+    # Load Item model
+    ItemModel = require('./models/' + obj.hit.itemModel)
+
+    if obj.design is 'teaching'
+      callback("Teaching set not ready yet", null)
+
+    if obj.design is 'basic' or 'fast'
+      if obj.opts.user == 'PREVIEWUSER'
+        candidates = obj.samplePool
+      else
+        # Create list of excludes
+        excludes = obj.maxed.concat(
+          item._id.item for item in obj.userFacetItemList
+        )
+        candidates = _.difference(obj.samplePool, excludes)
+
+      itemSampleIds = _.sample(candidates, obj.maxSetSize)
+
+      # Query info for all the sampled items
+      if obj.hit.itemModel is 'pin'
+        projection = { url:1, description:1, title:1,
+        image:1, likes:1, repins:1 }
+      else
+        projection = {}
+      ItemModel.find({_id:{$in:itemSampleIds}},
+        projection,
+        (err, results) ->
+          if err then return callback(err)
+          sample = results
+          if sample.length is 0
+            msg = "No tasks available. This may be because you've finished "+
+            "all the tasks that we have ready, or other people are working "+
+            "on everything on that's available (try again later), or we "+
+            "screwed something up (sorry)."
+            callback(msg, null)
+          else
+            callback(err, sample)
+        )
+  ]
+
+  timer: ['design', 'hit', (callback, obj) ->
+    if obj.design is 'fast'
+      return callback(null, obj.hit.timer)
+    else
+      return callback()
+  ]
+
+  itemTimeEstimate: ['design', (callback, obj) ->
+    if obj.design is 'basic'
+      return callback(null, obj.hit.itemTimeEstimate)
+    else
+      return callback()
+  ]
+
+
+  taskset: ['opts', 'sample', 'hit', 'facet',
+  (callback, obj) ->
+    tasks = _.map(obj.sample, (task) ->
+      type: obj.hit.type
+      item:
+        type: obj.hit.itemModel
+        id: task.id
+      timeSpent: null
+      contribution: null
+    )
+
+    # If taskset Id is "TEST", generate a unique one.
+    if obj.opts.taskset_id is "TEST"
+      obj.opts.taskset_id =  "TEST" + Math.floor(Math.random()*Math.pow(10,10))
+      console.log "Generating unique taskset_id: " + obj.opts.taskset_id
+
+    taskset =
+      _id: obj.opts.taskset_id
+      lock: obj.opts.lock
+      assignment_id: obj.opts.assignment_id
+      user: obj.opts.user
+      hit_id: obj.opts.hit_id
+      facet: ( _.pick(obj.facet, ['meta', '_id']) || null)
+      turk_hit_id: obj.opts.turk_hit_id
+      time:
+        start:(new Date())
+        submit: null
+        workTime: null
+      feedback:
+        form: null
+        satisfaction: null
+      tasks:tasks
+
+    # Lock in-progress files if locking was requested
+    if (obj.opts.lock is true) and (obj.opts.user isnt "PREVIEWUSER")
+      tasksetInstance = new TaskSet(taskset)
+      tasksetInstance.save((err, res) ->
+        callback(err, taskset)
+      )
+    else
+      callback(null, taskset)
+  ]
+
+  },
+  callback
   )
-
-# 2. getExtraInfo
-# Now that we have the hit loaded, there are a few more preparatory functions
-# that we can run in parallel rather than series
-getExtraInfo = (obj, callback) ->
-  async.parallel({
-    allHitType: (cb) -> Hit.listAllByType(obj.hit.type, cb)
-  }, (err, results) ->
-    if (err) then callback(err)
-    console.log results
-    # Merge results into main obj
-    obj = _.extend(obj, results)
-    callback(null, obj)
-  )
-
-# 2. Determine the current condition
-getCondition = (obj, callback) ->
-  if obj.countUserHIT is 0
-    obj.condition = obj.hit.condition.firstTask
-  else
-    obj.condition = obj.hit.condition.laterTasks
-
-  obj.payment =
-    base: obj.hit.payment
-    bonus: obj.hit.bonus
-
-  switch obj.condition
-    when 'feedback'
-      # We're going to stick with the design of the first task, and layer the
-      # feedback on top of that
-      obj.condition = obj.hit.condition.firstTask
-      obj.feedback = true
-    when 'fast'
-      obj.timer = obj.hit.timer
-      return callback(null, obj)
-    when 'basic'
-      obj.itemTimeEstimate = obj.hit.itemTimeEstimate
-      return callback(null, obj)
-    else
-      return callback('Condition "' + obj.condition + '" not available."', obj)
-
-# Get Feedback
-# Feedback condition needs to retrieval extra information
-getFeedback = (obj, callback) ->
-  if obj.feedback
-    getFeedback = require './getFeedback'
-    return getFeedback(obj, callback)
-  else
-    # Move along
-    return callback(null, obj)
-
-## 2.2 Exclude workers if relevant
-excludeWorkers = (obj, callback) ->
-  if not obj.hit.exclude.pastInTaskType
-    return callback(null, obj)
-  if obj.hit.exclude.pastInTaskType
-    # Find hits with the same type (excluding this one)
-    Hit.aggregate([
-      {$match:{"type": obj.hit.type, "_id":{"$ne":obj.hit._id}}},
-      {$group:{_id:null, "hits":{$addToSet:'$_id'}}}
-    ]).exec((err, results) ->
-      if (err)
-      # Skipping exclusion
-        console.log err
-        return callback(null, obj)
-      hit_ids = _.map(results[0].hits, (hitobj) -> hitobj.toString())
-      TaskSet.findOne(
-        {hit_id:{$in:hit_ids}, user:obj.opts.user}
-      ).exec((err, results) ->
-        if (err) then console.error err
-        if results
-          return callback("It looks like you've done a very similar " +
-            "task to this one, so it wouldn't be fun to do it again. " +
-            "Sorry to let you know after accepting, but we don't have " +
-            "information about you during the preview.", null)
-        else
-          return callback(null, obj)
-      )
-    )
-
-## 2.3 Sample a facet, if relevant
-## Facets let us define subgroups of the data, like 'queries' for an IR hit
-sampleFacet = (obj, callback) ->
-  if obj.hit.facets and obj.hit.facets.length > 0
-    # Rather than randomly sampling a facet, let's sample the one with the
-    # least completed or locked items
-    facets = _.map(obj.hit.facets, (item) -> item._id.toString())
-    flist = _.map(obj.countFacetsCompleted, (item)-> item._id.toString())
-
-    # Prepend any facets that haven't been started
-    flist = _.difference(facets, flist).concat(flist)
-
-    # Exclude items that the user seems to have maxed out
-    obj.userFacetCounts =  _.countBy(obj.userItemList, (item) ->
-      if item._id.facet
-        return item._id.facet.toString()
-      else
-        return null
-    )
-    obj.hit.facetItemCounts = _.map(obj.hit.facets, (facet) ->
-      {_id:facet._id.toString(), count:facet.items.length}
-    )
-    invalidFacets = []
-    for item in obj.hit.facetItemCounts
-      c = obj.userFacetCounts
-      if c[item._id] and (c[item._id] >= item.count)
-        invalidFacets.push item._id
-    flist = _.difference(flist, invalidFacets)
-
-    # If there are no valid facets, we know that there's nothing for the user
-    if flist.length is 0
-      msg = "No tasks available. This may be because you've finished all "+
-      "the tasks that we have ready, or other people are working on "+
-      "everything on that's available (try again later), or we screwed "+
-      "something up (sorry)."
-      return callback(msg, null)
-
-    # Sample Facet
-    f_id_str = flist[0]
-    obj.facet = _.find(obj.hit.facets, (facet) ->
-      (facet._id.toString() == f_id_str)
-    )
-    obj.hit.items = obj.facet.items
-    obj.userItemList = _.filter(obj.userItemList, (item) ->
-      if not item._id.facet
-        return false
-      return item._id.facet.equals(obj.facet._id)
-    )
-  else
-    # Easier to work with later if null rather than undefined
-    obj.facet = null
-  callback(null, obj)
-
-# 3. Sample items for worker to complete
-# 3.1 Determine which items have already been done enough
-getMaxedItems = (obj, callback) ->
-  if obj.condition is 'teaching'
-    ''' Don't need maxed conditions because teaching set is pre-determined '''
-    callback(null, obj)
-    return
-
-  if obj.opts.user == 'PREVIEWUSER'
-    console.log "Preview user, no maxed items"
-    obj.maxed = []
-    callback(null,obj)
-    return
-
-  facet_id = if obj.facet then obj.facet._id else null
-  # Get a list of all currentHIT items completed
-  agg = [
-    {$match:
-      hit_id:obj.opts.hit_id
-      'facet._id': (if obj.facet then ObjectId(obj.facet._id) else null)
-    },
-    {$project:
-      "tasks.item.id":1
-    },
-    {$unwind : "$tasks"},
-    # Matching on task item id *after* unwinding
-    {$match:
-      'tasks.item.id':
-        $in: obj.hit.items
-    },
-    {$group:
-      _id: "$tasks.item.id",
-      count:
-        $sum:1
-    },
-    # We want to know which tasks have already be done max times
-    {$match:
-      count:
-        $gte:obj.hit.setsPerItem
-    }
-  ]
-  TaskSet.aggregate(agg).exec((err, results) ->
-    if (err) then return callback(err, obj)
-    obj.maxed = (result._id for result in results)
-    callback(null, obj)
-  )
-  return
-
-sampleTaskItems = (obj, callback) ->
-  # Load Item model
-  ItemModel = require('./models/' + obj.hit.itemModel)
-
-  if obj.condition is 'teaching'
-    ''' TODO get teaching set '''
-    callback("Teaching set not ready yet", bj)
-
-  if obj.condition is 'basic' or 'fast'
-    if obj.opts.user == 'PREVIEWUSER'
-      candidates = obj.hit.items
-    else
-      # Create list of excludes
-      excludes = obj.maxed.concat (item._id.item for item in obj.userItemList)
-      candidates = _.difference(obj.hit.items, excludes)
-
-    # Fast condition shouldn't specify a max size
-    if obj.condition is 'fast' and obj.hit.maxSetSize
-      # Ignore any max set size that may be specified, and return a large number
-      # of items instead
-      obj.hit.maxSetSize = 30
-
-    itemSampleIds = _.sample(candidates,
-      if obj.hit.maxSetSize then obj.hit.maxSetSize else 30)
-
-    obj.locksCleared = null
-    # Query info for all the sampled items
-    if obj.hit.itemModel is 'pin'
-      projection = { url:1, description:1, title:1, image:1, likes:1, repins:1 }
-    else
-      projection = {}
-    ItemModel.find({_id:{$in:itemSampleIds}},
-      projection,
-      (err, results) ->
-        if err then return callback(err)
-        obj.sample = results
-        if obj.sample.length is 0
-          msg = "No tasks available. This may be because you've finished all "+
-          "the tasks that we have ready, or other people are working on "+
-          "everything on that's available (try again later), or we screwed "+
-          "something up (sorry)."
-          callback(msg, null)
-        else
-          callback(err, obj)
-      )
-
-prepareTaskSet = (obj, callback) ->
-  tasks = _.map(obj.sample, (task) ->
-    type: obj.hit.type
-    item:
-      type: obj.hit.itemModel
-      id: task.id
-    timeSpent: null
-    contribution: null
-  )
-
-  # If taskset Id is "TEST", generate a unique one.
-  if obj.opts.taskset_id is "TEST"
-    obj.opts.taskset_id =  "TEST" + Math.floor(Math.random()*Math.pow(10,10))
-    console.log "Generating unique taskset_id: " + obj.opts.taskset_id
-
-  obj.taskset =
-    _id: obj.opts.taskset_id
-    lock: obj.opts.lock
-    assignment_id: obj.opts.assignment_id
-    user: obj.opts.user
-    hit_id: obj.opts.hit_id
-    facet: ( _.pick(obj.facet, ['meta', '_id']) || null)
-    turk_hit_id: obj.opts.turk_hit_id
-    time:
-      start:(new Date())
-      submit: null
-      workTime: null
-    feedback:
-      form: null
-      satisfaction: null
-    tasks:tasks
-
-  # Lock in-progress files if locking was requested
-  if (obj.opts.lock is true) and (obj.opts.user isnt "PREVIEWUSER")
-    taskset = new TaskSet(obj.taskset)
-    taskset.save((err, res) ->
-      callback(err, obj)
-    )
-  else
-    callback(null, obj)

 cleanForFrontEnd = (obj, callback) ->
   # Add item information to taskSet.tasks[] as .meta field
+
   # This information shouldn't be saved to Mongo, since we already have it
   sampleRef = _.object(_.map(obj.sample, ((obj) -> obj._id)), obj.sample)
   tasks = obj.taskset.tasks
@@ -331,18 +349,9 @@
     task.meta = sampleRef[task.item.id]
     task
   )
-  # Redundant
-  delete obj.sample
-  delete obj.opts
-  delete obj.maxed
-  # Not needed by front end
-  delete obj.userItemList
-  delete obj.locksCleared
-  delete obj.userFacetCounts
-  delete obj.countFacetsCompleted
-  delete obj.hit
-  delete obj.facet
-
-  callback(null, obj)
+  response = _.pick(obj, ['taskset', 'condition', 'design', 'itemTimeEstimate',
+    'timer', 'performanceFeedback', 'payment', 'maxSetSize'])
+
+  callback(null, response)

 module.exports = loadHIT


chat: 'more' button accepts list title option

--- client/activity/lib/views/privatemessage/moreparticipantsbutton.coffee
+++ client/activity/lib/views/privatemessage/moreparticipantsbutton.coffee
@@ -14,11 +14,12 @@

   constructor: (options = {}, data) ->

-    options = _.assign {}, options,
-      title    : "+#{options.participantList.size}"
-      cssClass : 'ParticipantHeads-button ParticipantHeads-button--more MoreParticipantsButton'
-      style    : 'resurrection ParticipantHeads-moreList' # context-menu css classes.
-      menu     : createMoreListFromAccounts.bind null, options.participantList
+    options = _.assign {},
+      title         : "+#{options.participantList.size}"
+      cssClass      : 'ParticipantHeads-button ParticipantHeads-button--more MoreParticipantsButton'
+      style         : 'resurrection ParticipantHeads-moreList' # context-menu css classes.
+      moreListTitle : 'Other Participants'
+    , options

     super options, data

@@ -74,7 +75,7 @@
    *
    * @param {string} cssClass
   ###
-  setDomElement:(cssClass = '')->
+  setDomElement:(cssClass = '') ->
     $ = require 'jquery'
     @domElement = $ """
       <div class='kdbuttonwithmenu-wrapper MoreParticipantsButton-wrapper'>


AdminIntegrationDetailsView: Add instructions header.

--- client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
+++ client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
@@ -16,8 +16,14 @@
     if instructions
       @instructionsView = new KDCustomHTMLView
         tagName  : 'section'
+        cssClass : 'has-markdown instructions'
+        partial  : """
+
Setup Instructions

+
Here are the steps necessary to add the #{data.name} integration.

+

+        """
+      @instructionsView.addSubView new KDCustomHTMLView
         partial  : applyMarkdown instructions
-        cssClass : 'has-markdown instructions'
     else
       @instructionsView = new KDCustomHTMLView cssClass: 'hidden'


Classroom: Implemented goToChapter.

Classroom: Implemented goToChapter.

--- client/landing/app/Applications/Classroom.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Classroom.kdapplication/AppView.coffee
@@ -53,13 +53,26 @@
       @coursesView.noEnrolledCourse.show()

   goToCourse: (courseName, callback = noop) ->
-    @readFileContent "/#{courseName}.kdcourse/manifest.json", (manifest) =>
+    @readFileContent "/#{courseName}.kdcourse/manifest.json", (@manifest) =>
       manifest.startWithSplashView = yes
       @createCoursesView manifest
       callback()

-  goToChapter: ->
-    log "handle go to chapter"
+  goToChapter: (courseName, chapter) ->
+    if @manifest
+      @handleGoToChapter chapter
+    else
+      @readFileContent "/#{courseName}.kdcourse/manifest.json", (@manifest) =>
+        @handleGoToChapter chapter
+
+  handleGoToChapter: (chapterIndex) ->
+    courseManifest   = @manifest
+    {chapters}       = courseManifest
+    return unless chapters
+
+    configFilePath = chapters[chapterIndex].resourcesPath
+    @readFileContent "/#{courseManifest.name}.kdcourse/#{configFilePath}", (config) =>
+      new ClassroomChapterView { container: @ }, { config, courseManifest }

   createCoursesView: (manifest) ->
     @addSubView new ClassroomCourseView { delegate: this }, manifest


Support dev, fuck with postMix.

Support dev, fuck with postMix.

--- mixirc/mixirc.coffee
+++ mixirc/mixirc.coffee
@@ -35,7 +35,8 @@
 				message, channelId, info.mixlrUserLogin, info.mixlrAuthSession
 			postAddCommentHeart heartMatch[1], info, userAgent
 		when info.canHeart and mixHrtMatch?
-			xhrComm mixHrtMatch[1], info
+			console.log "IRC => xhrCommAddHeart #{mixHrtMatch[1]}, #{info}"
+			xhrCommAddHeart mixHrtMatch[1], info
 		when mixMsgMatch?
 			xhrComm mixMsgMatch[1], channelId, info
 		else
@@ -73,25 +74,29 @@
 	jar = request.jar()
 	c = request.cookie "mixlr_user_login=#{user.mixlrUserLogin}" +
 		"; mixlr_session=#{user.mixlrAuthSession}"
+	console.log "postMix: c: #{c}"
 	jar.setCookie c, url
-	opts =
-		url: url
+	form = {}
+	form[k] = v for k, v of data
+	req = request {
+		method: 'POST'
+		uri: url
 		jar: jar
-	req = request.post opts, (e, resp, body) ->
+		form: form
+	}, (e, resp, body) ->
 		return console.log "postMix: POST failed" if e
 		if resp.statusCode == 200
 			info = JSON.parse body
 			console.log "postMix: #{k}: #{v}" for k, v of info
 		else
 			console.log "postMix: server returned #{resp.statusCode}"
-	if data
-		form = req.form()
-		form.append k, v for k, v of data

 xhrComm = (comment, channelId, user) ->
 	postMix user, 'http://mixlr.com/comments',
 		"comment[content]": comment
 		"comment[broadcaster_id]": channelId
+
+data = { "comment[content]": "testing things", "comment[broadcaster_id]": 27902 }

 xhrCommAddHeart = (commentId, user) ->
 	postMix user, "http://mixlr.com/comments/#{commentId}/heart"
@@ -231,6 +236,9 @@
 	data = snapshot.val()
 	ircNick = data.config.ircNick
 	ircChannel = data.config.ircChannel
+	if 'dev' in process.argv
+		ircNick = 'devvlrbot'
+		ircChannel = '#JeffDevs'
 	ircServer = data.config.ircServer
 	channelId = data.config.channelId
 	userAgent = data.config.userAgent


simplify favorites management

simplify favorites management

--- core/plugmixer.coffee
+++ core/plugmixer.coffee
@@ -95,34 +95,23 @@
       status.unshift @active
       return status

-    isFavoriteRoom = =>
+    idToUse = (defaultRoomId = 'default') =>
       if $(ROOM_FAVORITE_DIV).hasClass 'selected'
-        if User.favorites.indexOf(@id) < 0 # If not favorited,
-          User.lastPlayedIn = @id # Update lastPlayedIn,
-          User.favorites.unshift @id # Add to favorites,
-          User.save()
-        else if User.lastPlayedIn != @id # Already favorited, but different lastPlayedIn,
-          User.lastPlayedIn = @id # Update lastPlayedIn,
-          User.save()
-        return true
-
-      else # Not selected...
-        if User.favorites.indexOf(@id) > -1 # If already favorited,
-          User.lastPlayedIn = 'default' # Update lastPlayedIn,
-          User.favorites.splice User.favorites.indexOf(@id), 1 # Remove from favorites,
-          User.save()
+        User.lastPlayedIn = @id
+        if User.favorites.indexOf(@id) < 0
+          User.favorites.unshift @id
+
+      else
+        User.lastPlayedIn = 'default'
+        if User.favorites.indexOf(@id) > -1
+          User.favorites.splice User.favorites.indexOf(@id), 1
           Storage.remove 'room', @id
-        else if User.lastPlayedIn != 'default' # Not favorited, but different lastPlayedIn,
-          User.lastPlayedIn = 'default' # Update lastPlayedIn,
-          User.save()
-        return false
-
-    idToUse = (roomId) =>
-      if isFavoriteRoom() then return @id
-      else if roomId? then roomId else 'default'
+
+      User.save()
+      return User.lastPlayedIn

     @save: ->
-      Storage.save 'room', idToUse(), getStatus()
+      Storage.save 'room', idToUse('default'), getStatus()

     @toggleActive: ->
       @active = if @active == 1 then 0 else 1


remove spurious log

remove spurious log

--- static/coffee/renderers/properties.coffee
+++ static/coffee/renderers/properties.coffee
@@ -72,7 +72,6 @@
     else if _.isString(glyph_value)
       if svg_colors[glyph_value]? or glyph_value.substring(0, 1) == "#"
         @[attrname] = {default: glyph_value}
-        console.log glyph_value
       else
         @[attrname] = {field: glyph_value, default: default_value}
     else if _.isObject(glyph_value)


customizer: add a test.

customizer: add a test.

--- app.nw/customizer.coffee
+++ app.nw/customizer.coffee
@@ -1,10 +1,34 @@
 class Customizer extends Config

-  addPatch: (key, value) ->
+  constructor: (yaml) ->
+    super(yaml)
     @root ?= {}
     @root.patch ?= {}
+
+  patch: (key, value) ->
     @root.patch[key] = value

   applyPatch: (config) ->
     for key, value of @root.patch
       config.set key, value
+
+###
+testCustomizer = ->
+  c = new Config """
+    abc:
+      abcd: 1234
+    def:
+      defg: 5678
+    """
+  console.log c.toString()
+  x = new Customizer
+  x.patch 'abc/abcd', 4321
+  x.patch 'def', null
+  x.patch 'opq', true
+  x.patch 'xyz', {uvw: 'rst'}
+  console.log x.toString()
+  x.applyPatch c
+  console.log c.toString()
+
+testCustomizer()
+###


Activate Light Box any time a change is made to the stream html.

--- app/coffee/stream.coffee
+++ app/coffee/stream.coffee
@@ -20,10 +20,7 @@
     @page = 1
     @stream = streamModel
     @$container = $('#entryField .matrix')
-    # Setup lightbox for stream
-    $('a.lightbox').each((i, el) ->
-      $(this).lightBox({containerResizeSpeed: 0});
-    )
+    @activateLightBox()
     # adds the loading wheel
     $('.filterList').click( (event) =>
       $(event.currentTarget).parent().find('.trigger').addClass('loading')
@@ -31,11 +28,14 @@
     # infinite scrolling
     $(window).scroll =>
       if ($(window).scrollTop() > $(document).height() - $(window).height() - 200)
-        # $(window).scrollTop($(document).height() - $(window).height() - 300)
         @loadNextPage()
   clear: ->
     $('#noMoreEntries, .noEntrys, #nextPageLoading').hide()
-
+  activateLightBox: ->
+    # Setup lightbox for stream
+    $('a.lightbox').each((i, el) ->
+      $(this).lightBox({containerResizeSpeed: 0});
+    )
   loadNextPage: ->
     return if @currentlyLoading
     @currentlyLoading = true
@@ -50,6 +50,7 @@
         $('#noMoreEntries').show()

       @$container.append(data.html)
+      @activateLightBox()
   update: (html) ->
     @clear()
     if html == ''
@@ -60,6 +61,7 @@
     $('.followFilter.trigger').removeClass('loading')

     @$container.html(html)
+    @activateLightBox()


upgrade atom and set a bigger font size

upgrade atom and set a bigger font size

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,11 +1,11 @@
 'release-notes':
-  'viewedVersion': '0.94.0'
+  'viewedVersion': '0.95.0'
 'welcome':
   'showOnStartup': false
 'editor':
   'showIndentGuide': false
   'fontFamily': 'Source Code Pro'
-  'fontSize': 12
+  'fontSize': 13
 'core':
   'audioBeep': false
   'disabledPackages': [


update atom config

update atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -26,6 +26,10 @@
       "atom-material-ui"
       "atom-material-syntax"
     ]
+    disabledPackages: [
+      "linter-eslint"
+      "linter-csslint"
+    ]
   "spell-check":
     grammars: [
       "source.gfm"
@@ -34,6 +38,7 @@
     ]
   "tree-view":
     hideIgnoredNames: true
+    hideVcsIgnoredFiles: false
   "autocomplete-plus":
     confirmCompletion: "tab and enter"
   docblockr: {}
@@ -63,4 +68,10 @@
       "linter-jshint"
     ]
   "status-bar": {}
-  "linter-scss-lint": {}
+  "linter-scss-lint":
+    executablePath: "/Users/mattbanks/.rbenv/shims/scss-lint"
+  "find-and-replace":
+    scrollToResultOnLiveSearch: true
+  react: {}
+  tabs:
+    usePreviewTabs: true


updated atom config

updated atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -42,3 +42,6 @@
   minimap: {}
   "base16-syntax":
     scheme: "Ocean"
+  "go-plus": {}
+  "atom-alignment": {}
+  "tree-view": {}


style(util/addInstalledPackage): in tests use "does" instead of "should do"

--- test/util/addInstalledPackage.spec.coffee
+++ test/util/addInstalledPackage.spec.coffee
@@ -9,7 +9,7 @@

 describe "util.addInstalledPackage", ->

-	it "should add specified module to 'mcpmInstalledPackages'", ->
+	it "adds specified module to 'mcpmInstalledPackages'", ->
 		addInstalledPackage = proxyquire "../../lib/util/addInstalledPackage",
 			"./getCurrentProfile": ->
 				originalInfo:
@@ -22,7 +22,7 @@

 		addInstalledPackage "fake-package", "0.1.0"

-	it "should add 'mcpmInstalledPackages' field if it's not there yet", ->
+	it "adds 'mcpmInstalledPackages' field if it's not there yet", ->
 		addInstalledPackage = proxyquire "../../lib/util/addInstalledPackage",
 			"./getCurrentProfile": ->
 				originalInfo: {}


fix bug

fix bug

--- core/static/script/panel/panel.coffee
+++ core/static/script/panel/panel.coffee
@@ -38,7 +38,7 @@
         id: id
       }
       .success (data) ->
-        $('#json').find('textarea').val JSON.stringify(data, null. " ")
+        $('#json').find('textarea').val JSON.stringify(data, null, " ")
         ($ '#nginxModal').modal 'show'


fix cloneNode and replaceChild

fix cloneNode and replaceChild

--- dom.coffee
+++ dom.coffee
@@ -153,11 +153,14 @@
 				handler(evt) for handler in list if list
 	cloneNode: (deep = false) ->
 		ret = new Node(@nodeName, @nodeValue, @nodeType, @ownerDocument)
+		for a of @_private
+			ret._private[a] = @_private[a]
 		for a of @attributes
 			ret.attributes[a] = @attributes[a]
 		if deep
 			for c in @childNodes
 				ret.childNodes.push c.cloneNode(true)
+		ret
 	hasAttributes: () ->
 		for a of @attributes
 			return true
@@ -223,7 +226,7 @@
 			return @removeChild(oldNode)
 		newNode._private.parentNode = @
 		newNode._private.childIndex = i
-		oldNode.parentNode = null
+		oldNode._private.parentNode = null
 		oldNode._private.childIndex = -1
 		@childNodes.splice(i, 1, newNode)
 	toString: (pretty=false,deep=true,indentLevel=0) ->


put the newline in the right place when pretty printing comment nodes

--- dom.coffee
+++ dom.coffee
@@ -266,9 +266,9 @@
 			when Node.COMMENT_NODE
 				body = ""
 				if @nodeName is "#CCOMMENT"
-					body = '>'+(c.toString(pretty, deep, indentLevel+1) for c in @childNodes).join('')
-					body += newline+indent+"<![endif]"
-				"#{indent}<!--#{@nodeValue}#{body}-->"
+					body = '>'+newline+(c.toString(pretty, deep, indentLevel+1) for c in @childNodes).join('')
+					body += indent+"<![endif]"
+				"#{indent}<!--#{@nodeValue}#{body}-->" + newline
 			when Node.DOCUMENT_TYPE_NODE
 				"#{indent}<!DOCTYPE #{@nodeValue}>" + newline
 			when Node.DOCUMENT_NODE