JReferral: addCustomReferral and fetchReferrals implemented

--- workers/social/lib/social/models/referral/index.coffee
+++ workers/social/lib/social/models/referral/index.coffee
@@ -20,10 +20,12 @@
     sharedMethods     :

       static          :
-        addExtraReferral:
+        addCustomReferral:
           (signature Object, Function)
         fetchClaimedAmount:
           (signature Object, Function)
+        fetchReferrals:
+          (signature Object, Object, Function)
         fetchReferredAccounts:
           (signature Object, Object, Function)

@@ -52,45 +54,62 @@
         required      : yes

-  @fetchReferredAccounts$ = secure (client, query, options, callback)->
-    @fetchReferredAccounts client, query, options, callback
-
-  @fetchReferredAccounts = (client, query, options, callback)->
+  useDefault = (options)->
+
+    options.unit ?= "MB"
+    options.type ?= "disk"
+
+    return options
+
+
+  @addCustomReferral = secure (client, options, callback)->
+
+    {delegate} = client.connection
+    unless delegate.can 'administer accounts'
+      return callback new KodingError "You can not add extra referral to users"
+
+    { username, type, unit } = useDefault options
+
+    unless username
+      return callback new KodingError "Please set username"
+
+    referredBy = client.connection.delegate.getId()
+
+    JAccount.one 'profile.nickname': username, (err, account)->
+
+      return callback err if err
+
+      unless account
+        return callback new KodingError "Account not found"
+
+      originId = account.getId()
+
+      referral = new JReferral {
+        amount         : options.amount         or 512
+        sourceCampaign : options.sourceCampaign or "register"
+        referredBy, originId, type, unit
+      }
+
+      referral.save (err) ->
+
+        return callback err if err
+
+        options = { unit, type, originId }
+
+        JReferral.calculateAndUpdateClaimedAmount options, (err)->
+
+          return callback err if err
+          callback null, referral
+
+
+  @fetchReferredAccounts = secure (client, query, options, callback)->
     username = client.connection.delegate.profile.nickname
     JAccount.some { referrerUsername : username }, options, callback

-  @addExtraReferral = secure (client, options, callback)->
-
-    {delegate} = client.connection
-    unless delegate.can 'administer accounts'
-      return callback {message: "You can not add extra referral to users"}
-
-    {username} = options
-    return callback {message: "Please set username"}  unless username
-
-    referral = new JReferral
-      amount         : options.amount         or 256
-      type           : options.type           or "disk"
-      unit           : options.unit           or "MB"
-      sourceCampaign : options.sourceCampaign or "register"
-
-    referral.save (err) ->
-      return callback err if err
-      JAccount.one {'profile.nickname': username}, (err, account)->
-        return callback err if err
-        return callback {message:"Account not found"} unless account
-        account.addReferrer referral, (err)->
-          return callback err if err
-          return callback null, referral
-
-
-  useDefault = (options)->
-
-    options.unit ?= "MB"
-    options.type ?= "disk"
-
-    return options
+  @fetchReferrals = secure (client, query, options, callback)->
+    query.originId = client.connection.delegate.getId()
+    JReferral.some query, options, callback

   fetchClaimedReferral = (options, callback)->
@@ -154,7 +173,7 @@
   @fetchClaimedAmount = secure (client, options, callback)->

     options = useDefault options
-    options.originId = originId = client.connection.delegate.getId()
+    options.originId = client.connection.delegate.getId()

     fetchClaimedReferral options, (err, claimedReferral)->
       return callback err  if err?
@@ -162,7 +181,10 @@
       if claimedReferral?
         return callback null, claimedReferral.amount

-      JReferral.calculateAndUpdateClaimedAmount options, callback
+      callback null, 0
+
+      # We can manually call this if we need.
+      # JReferral.calculateAndUpdateClaimedAmount options, callback

   do ->


opt spinner style [by elegant-spinner]

opt spinner style [by elegant-spinner]

--- src/spinner.coffee
+++ src/spinner.coffee
@@ -5,7 +5,11 @@

 Spinner = require('cli-spinner').Spinner

-spinner = new Spinner chalk.green.bold '>> Fetching... %s'
+spinner = new Spinner chalk.green.bold('>> Fetching ') + chalk.cyan '%s '
+
+if process.platform isnt 'win32'
+    # https://github.com/sindresorhus/elegant-spinner/blob/master/index.js
+    spinner.setSpinnerString ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'].join ''

 module.exports =
     start: -> spinner.start()


property names not compatible with jekyll

--- mdps.coffee
+++ mdps.coffee
@@ -1,4 +1,5 @@
-# Extract basic information from a web page and summarize in markdown.
+# Extract basic information from a web page and summarize in markdown for use
+# in static site generators.
 # Open graph information has the highest priority.

@@ -54,7 +55,7 @@
         paragraphs.push p.textContent.trim()

-# Create markdown summary compatible with jekyll and logya.
+# Create markdown summary compatible with logya.
 headers = []
 headers.push markdown_property 'title', title
 headers.push markdown_property 'url', url


Tweak.

Tweak.

--- coffee/lib/OutlineWriter.coffee
+++ coffee/lib/OutlineWriter.coffee
@@ -30,10 +30,8 @@
     formatted = []
     lines = string.split "\n"
     return unless lines.length
-    firstLine = lines.shift()
-    formatted.push @firstPrefix + firstLine
-    for line in lines
-      formatted.push @furtherPrefix + line
+    formatted.push @firstPrefix + lines.shift()
+    formatted.push @furtherPrefix + line for line in lines
     formatted.join "\n"

 makePadding = (n) ->


Add missing l

Add missing l

--- src/buffered-process.coffee
+++ src/buffered-process.coffee
@@ -74,7 +74,7 @@
       @process = @spawn(command, args, options)

     @killed = false
-    @handeEvents(stdout, stderr, exit) if @process?
+    @handleEvents(stdout, stderr, exit) if @process?

   ###
   Section: Event Subscription


take out promises as it wasn't being used anyways, Promise incompatible in latest IEs

--- src/api/loader.coffee
+++ src/api/loader.coffee
@@ -55,7 +55,6 @@
         completedData = getResponseDataByEnv(isLocal, eventData, response)

         apiEventChannel.emit(completedEvent, completedData)
-        Promise.resolve(response)

       ).catch((response) ->
         if _.isString(setting.failedEvent)
@@ -63,7 +62,6 @@
           apiEventChannel.emit(failedEvent, response)
         setting.onFail?(response) or defaultFail(response)
         apiEventChannel.emit('error', response: response, apiSetting: apiSetting)
-        Promise.reject(response)
       )
   , delay


Fix syntax error in helper

Fix syntax error in helper

--- generators/application/test/support/helper.coffee
+++ generators/application/test/support/helper.coffee
@@ -2,7 +2,7 @@
 # This is an example file you can use to add common methods
 # to your test suite. Here's an example
 #
-#   window.chatWindow ->
+#   window.chatWindow = ->
 #     $("#chat-window")


Minor refactor

Minor refactor

--- src/api/statsd.coffee
+++ src/api/statsd.coffee
@@ -17,16 +17,14 @@
 exports.retrieveTransactionCountPerHour = `function *() {

   var path = "/render?target=transformNull(summarize(stats.counters." + domain + ".channels.count,'1hour'))&from=-1days&format=json";
-  var data = [];
   var raw = yield exports.fetchData(path);

-  _.forEach(raw.data, function (item) {
-    data.push({
+  this.body = raw.data.map(function(item) {
+    return {
       load: item[0],
       timestamp: moment.unix(item[1])
-    });
+    };
   });
-  this.body = data
 }`

 # Retrives Global Status Metrics from the StatsD API
@@ -77,37 +75,34 @@
 }`

 exports.retrieveChannelMetrics = `function *(type, channelId) {
-  var data = [];
-  var status_array = ['Processing', 'Failed', 'Completed', 'Successful', 'Completed with error(s)']
-  var results = {}, path = ''
-  var render_url = "/render?target=transformNull(summarize(stats.counters." + domain + ".channels." + channelId
-  var promises = []
-  var final = {}
-  if (type == 'status') {
-    for (i = 0; i < status_array.length; i++) {
-      path = render_url + ".statuses." + status_array[i] + ".count,'1week'))&format=json";
-      results[status_array[i]] = yield exports.fetchData(path)
-      if (results[status_array[i]]) {
-        final[status_array[i]] = 'data' in results[status_array[i]] ? results[status_array[i]].data[0][0] : 0;
+  var statusArray = ['Processing', 'Failed', 'Completed', 'Successful', 'Completed with error(s)'];
+  var renderUrl = "/render?target=transformNull(summarize(stats.counters." + domain + ".channels." + channelId
+  var path = '';
+  var total = {};
+  if (type === 'status') {
+    statusArray.forEach(co.wrap(function*(statusType) {
+      path = renderUrl + ".statuses." + statusType + ".count,'1week'))&format=json";
+      var result = yield exports.fetchData(path);
+      if (result && result.data) {
+        total[statusType] = result.data[0][0];
       } else {
-        final[status_array[i]] = 0 ;
+        total[statusType] = 0;
       }
-    }
+    }));

-
-    data.push({
-      _id: {"channelID": channelId},
-      failed: final.Failed,
-      successful: final.Successful,
-      processing: final.Processing,
-      completed: final.Completed,
-      completedWErrors: final['Completed with error(s)']
-    });
-
-    return this.body = data
+    return this.body = [
+      {
+        _id: {"channelID": channelId},
+        failed: total.Failed,
+        successful: total.Successful,
+        processing: total.Processing,
+        completed: total.Completed,
+        completedWErrors: total['Completed with error(s)']
+      }
+    ];
   } else {

-    path = render_url + ".count,'1day'))&from=-7days&format=json";
+    path = renderUrl + ".count,'1day'))&from=-7days&format=json";
     path += "&target=transformNull(summarize(stats.timers." + domain + ".channels." + channelId + ".sum,'1day','avg'))";
     var raw = yield exports.fetchData(path);
     return this.body = exports.convertToRequiredFormat(raw, 'retrieveChannelMetrics');
@@ -144,22 +139,16 @@
 }`

 convertToRequiredFormat = (raw, requiredFormat) ->
-  data = []
-  i = 0
   switch requiredFormat
-    when "retrieveAverageLoadTimePerHour" then _.forEach raw.data, (item, i) ->
-      data.push
-        avgResp: item[0]
-        timestamp: moment.unix item[1]
+    when "retrieveAverageLoadTimePerHour" then return raw.data.map (item) ->
+      avgResp: item[0]
+      timestamp: moment.unix item[1]

-    when "retrieveChannelMetrics", "transactionsPerChannelPerHour" then _.forEach raw.data, (item, i) ->
-      data.push
-        load: item[0]
-        avgResp: raw.data1[i][0]
-        timestamp: moment.unix item[1]
-
-  data
+    when "retrieveChannelMetrics", "transactionsPerChannelPerHour" then return raw.data.map (item, i) ->
+      load: item[0]
+      avgResp: raw.data1[i][0]
+      timestamp: moment.unix item[1]

 exports.fetchData = fetchData
 exports.convertToRequiredFormat = convertToRequiredFormat
-exports.timer = this.timer+exports.timer = this.timer


Use cached disk contents instead of signature for `Buffer.isModified`

Computing the signature was really slow for large files. Keeping a cached copy of the disk contents and comparing directly costs less in the benchmark. We'll have to turn on the status bar in benchmarks to see if the comparison is too expensive.

--- src/app/buffer.coffee
+++ src/app/buffer.coffee
@@ -8,14 +8,12 @@
 BufferChangeOperation = require 'buffer-change-operation'
 Anchor = require 'anchor'
 AnchorRange = require 'anchor-range'
-{hex_md5}  = require 'md5'

 module.exports =
 class Buffer
   @idCounter = 1
   undoManager: null
-  diskContentSignature: null
-  memoryContentSignature: null
+  cachedDiskContents: null
   conflict: false
   lines: null
   file: null
@@ -32,19 +30,11 @@
     if path
       throw "Path '#{path}' does not exist" unless fs.exists(path)
       @setPath(path)
-      @setText(fs.read(@getPath()))
-      @computeDiskContentSignature()
+      @reload()
     else
       @setText('')

-    @computeMemoryContentSignature()
     @undoManager = new UndoManager(this)
-
-  computeDiskContentSignature: ->
-    @diskContentSignature = fs.md5ForPath(@getPath())
-
-  computeMemoryContentSignature: ->
-    @memoryContentSignature = hex_md5(@getText())

   destroy: ->
     throw new Error("Destroying buffer twice with path '#{@getPath()}'") if @destroyed
@@ -65,11 +55,10 @@
     @file.on "contents-change", =>
       if @isModified()
         @conflict = true
-        @computeDiskContentSignature()
+        @updateCachedDiskContents()
         @trigger "contents-change-on-disk"
       else
-        @setText(fs.read(@file.getPath()))
-        @computeDiskContentSignature()
+        @reload()

     @file.on "remove", =>
       @file = null
@@ -79,7 +68,11 @@
       @trigger "path-change", this

   reload: ->
-    @setText(fs.read(@getPath()))
+    @updateCachedDiskContents()
+    @setText(@cachedDiskContents)
+
+  updateCachedDiskContents: ->
+    @cachedDiskContents = fs.read(@getPath())

   getBaseName: ->
     @file?.getBaseName()
@@ -212,8 +205,7 @@

   replaceLines: (startRow, endRow, newLines) ->
     @lines[startRow..endRow] = newLines
-    @computeMemoryContentSignature()
-    @conflict = false unless @isModified()
+    @conflict = false if @conflict and !@isModified()

   pushOperation: (operation, editSession) ->
     if @undoManager
@@ -237,15 +229,16 @@
     if not path then throw new Error("Can't save buffer with no file path")

     @trigger 'before-save'
-    fs.write path, @getText()
+    @cachedDiskContents = @getText()
+    fs.write path, @cachedDiskContents
     @file?.updateMd5()
     @setPath(path)
-    @computeDiskContentSignature()
+
     @trigger 'after-save'

   isModified: ->
     if @file
-      @memoryContentSignature != @diskContentSignature
+      @getText() != @cachedDiskContents
     else
       not @isEmpty()


fixed building upgrade bug

fixed building upgrade bug

--- commands/create_building.coffee
+++ commands/create_building.coffee
@@ -1,5 +1,6 @@
 async = require 'async'
 db = require '../db'
+queries = require '../queries'
 create_tile = require './create_tile'

 module.exports = (tile, building, cb) ->
@@ -23,12 +24,19 @@
         x: tile.x
         y: tile.y
         z: 1
-      create_tile coords, building.interior, (err, tile) ->
+      queries.get_tile_by_coords coords, (err, tile) ->
         return cb(err) if err?
-        query =
-          _id: tile._id
-        update =
-          $set:
-            building: building.id
-        db.tiles.update query, update, cb
+        update_tile = (err, tile) ->
+          return cb(err) if err?
+          query =
+            _id: tile._id
+          update =
+            $set:
+              building: building.id
+              terrain: building.interior
+          db.tiles.update query, update, cb
+        if tile?
+          update_tile null, tile
+        else
+          create_tile coords, building.interior, update_tile
   ], cb


tiny comments

tiny comments

--- coffee/jquery.contactform.coffee
+++ coffee/jquery.contactform.coffee
@@ -129,8 +129,9 @@
       recaptcha_response_field: Recaptcha.get_response()
       recaptcha_challenge_field: Recaptcha.get_challenge()

-    # check recaptcha
+    # verify recaptcha
     $.post config.recaptcha_verification_url, recaptcha_params, (response) ->
+      # recaptcha is correct...
       if response is 1
         # set mandrill params
         mandrill_params =
@@ -156,6 +157,7 @@
           config.form_el[0].reset()
         , (err) ->
           console.log err if console?
+      # recaptcha is wrong...
       else
         console.log "the recaptcha solution is wrong" if console?


Use fs.chmod() to make atom.sh executable

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -1,8 +1,8 @@
+fs = require 'fs'
 fsUtils = require 'fs-utils'
 $ = require 'jquery'
 _ = require 'underscore'
 {less} = require 'less'
-{spawn} = require 'child_process'
 require 'jquery-extensions'
 require 'underscore-extensions'
 require 'space-pen-extensions'
@@ -78,7 +78,7 @@
   bundledCommandPath = fsUtils.resolve(window.resourcePath, 'atom.sh')
   if bundledCommandPath?
     fsUtils.write(commandPath, fsUtils.read(bundledCommandPath))
-    spawn('chmod', ['u+x', commandPath])
+    fs.chmod(commandPath, 0o755, commandPath)

 window.handleWindowEvents = ->
   $(window).command 'window:toggle-full-screen', => atom.toggleFullScreen()


Use atom@github.com in crash message instead of repo

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -104,7 +104,7 @@
         type: 'warning'
         buttons: ['Close Window', 'Reload', 'Keep It Open']
         message: 'The editor has crashed'
-        detail: 'Please report this issue to https://github.com/atom/atom/issues'
+        detail: 'Please report this issue to atom@github.com'
       switch chosen
         when 0 then @browserWindow.destroy()
         when 1 then @browserWindow.restart()


restrict compile check options

restrict compile check options

allowed options are validate/error/silent

validate = only run chktex, exit status 0
error = run compilation, exit(1) if chktex fails
silent = run chktex, but always do full compilation

--- services/web/app/coffee/Features/Compile/CompileController.coffee
+++ services/web/app/coffee/Features/Compile/CompileController.coffee
@@ -29,8 +29,8 @@
 				options.compiler = req.body.compiler
 			if req.body?.draft
 				options.draft = req.body.draft
-			if req.body?.check
-				options.check = if req.body.check is "validate" then "validate" else undefined
+			if req.body?.check in ['validate', 'error', 'silent']
+				options.check = req.body.check
 			logger.log {options:options, project_id:project_id, user_id:user_id}, "got compile request"
 			CompileManager.compile project_id, user_id, options, (error, status, outputFiles, clsiServerId, limits, validationProblems) ->
 				return next(error) if error?


[fix] don't handle other click events after handling .cancel-button to abort an edit

--- vendor/assets/javascripts/neat/model_editor.coffee
+++ vendor/assets/javascripts/neat/model_editor.coffee
@@ -39,6 +39,7 @@

   cancelEdit: (e)->
     e?.preventDefault()
+    e?.stopImmediatePropagation()
     if @inEdit()
       $(@el).removeClass('editor').addClass('editable')
       @render()


Keep the cursor in the same position on screen when the editor resizes

--- services/web/public/coffee/ide/editor/directives/aceEditor.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor.coffee
@@ -167,10 +167,22 @@
 					if arg == "/"
 						ace.require("ace/ext/searchbox").Search(editor, true)

+				getCursorScreenPosition = () ->
+					session = editor.getSession()
+					cursorPosition = session.selection.getCursor()
+					sessionPos = session.documentToScreenPosition(cursorPosition.row, cursorPosition.column)
+					screenPos = editor.renderer.textToScreenCoordinates(sessionPos.row, sessionPos.column)
+					return sessionPos.row * editor.renderer.lineHeight - session.getScrollTop()
+
 				if attrs.resizeOn?
 					for event in attrs.resizeOn.split(",")
 						scope.$on event, () ->
+							previousScreenPosition = getCursorScreenPosition()
 							editor.resize()
+							# Put cursor back to same vertical position on screen
+							newScreenPosition = getCursorScreenPosition()
+							session = editor.getSession()
+							session.setScrollTop(session.getScrollTop() + newScreenPosition - previousScreenPosition)

 				scope.$watch "theme", (value) ->
 					editor.setTheme("ace/theme/#{value}")


bootstrap input fields cause a page reload on ENTER

--- vendor/assets/javascripts/wice_grid_init.js.coffee
+++ vendor/assets/javascripts/wice_grid_init.js.coffee
@@ -84,8 +84,15 @@
     """
     return

-  $('.wice-grid-container .date-filter div[id$=_date_placeholder] input:text[data-provide=datepicker]').each (index, removeLink) ->
-    $(removeLink).datepicker().on 'hide', (event) ->
+  $('.wice-grid-container .date-filter div[id$=_date_placeholder] input:text[data-provide=datepicker]').each (index, dateField) ->
+
+    $(dateField).keydown (event) ->
+      if event.keyCode == 13
+        $self = $(event.currentTarget)
+        eventToTriggerOnChange = $self.data('close-calendar-event-name')
+        $(dateField).trigger(eventToTriggerOnChange)
+
+    $(dateField).datepicker().on 'hide', (event) ->
       $self = $(event.currentTarget)

       eventToTriggerOnChange = $self.data('close-calendar-event-name')


Update example service criteria.

Update example service criteria.


Fix hr spec with new indented code

Fix hr spec with new indented code

--- spec/markdown-hr-spec.coffee
+++ spec/markdown-hr-spec.coffee
@@ -45,12 +45,14 @@

     # http://spec.commonmark.org/0.22/#example-13
     {tokens} = grammar.tokenizeLine('    ***')
-    expect(tokens[0]).toEqual value: '    ***', scopes: ['text.md']
+    expect(tokens[0]).toEqual value: '    ', scopes: ['text.md', 'indented-code.md']
+    expect(tokens[1]).toEqual value: '***', scopes: ['text.md', 'indented-code.md', 'code.md']

     # http://spec.commonmark.org/0.22/#example-14
     [firstLineTokens, secondLineTokens] = grammar.tokenizeLines("Foo\n    ***")
     expect(firstLineTokens[0]).toEqual value: 'Foo', scopes: ['text.md']
-    expect(secondLineTokens[0]).toEqual value: '    ***', scopes: ['text.md']
+    expect(secondLineTokens[0]).toEqual value: '    ', scopes: ['text.md', 'indented-code.md']
+    expect(secondLineTokens[1]).toEqual value: '***', scopes: ['text.md', 'indented-code.md', 'code.md']

     # http://spec.commonmark.org/0.22/#example-15
     {tokens} = grammar.tokenizeLine('_____________________________________')


More output.

More output.

--- src/coffee/pricesync.coffee
+++ src/coffee/pricesync.coffee
@@ -44,7 +44,12 @@
       @getPublishedProducts @retailerClient, ((page, count) -> console.error "Page #{page} processed - #{count} price update(s) done."), (retailerProduct) =>
         current = retailerProduct.masterData.current
         current.variants or= []
-        variants = [current.masterVariant].concat current.variants
+        variants = [current.masterVariant].concat(current.variants)
+        stagedVariants = [retailerProduct.masterData.staged.masterVariant].concat(retailerProduct.masterData.staged.variants)
+
+        console.error "C %j", _.map(variants, (a) -> a.sku)
+        console.error "S %j", _.map(stagedVariants, (a) -> a.sku)
+
         v = _.map variants, (retailerVariant) =>
           @taskQueue.addTask _.bind(@_processVariant, this, retailerVariant, retailerCustomerGroup, masterCustomerGroup, retailerChannelInMaster)
         Q.all(v)


:white_check_mark: for tunable-f

:white_check_mark: for tunable-f

--- spec/motion-find-spec.coffee
+++ spec/motion-find-spec.coffee
@@ -287,3 +287,83 @@
       other.ensure ';', cursor: [0, 4]
       other.ensure ';', cursor: [0, 8]
       other.ensure ',', cursor: [0, 4]
+
+  describe "vmp unique feature of `f` family", ->
+    describe "ignoreCaseForFind", ->
+      beforeEach ->
+        settings.set("ignoreCaseForFind", true)
+
+      it "ignore case to find", ->
+        set textC: "|    A    ab    a    Ab    a"
+        ensure "f a", textC: "    |A    ab    a    Ab    a"
+        ensure ";",   textC: "    A    |ab    a    Ab    a"
+        ensure ";",   textC: "    A    ab    |a    Ab    a"
+        ensure ";",   textC: "    A    ab    a    |Ab    a"
+
+    describe "useSmartcaseForFind", ->
+      beforeEach ->
+        settings.set("useSmartcaseForFind", true)
+
+      it "ignore case when input is lower char", ->
+        set textC: "|    A    ab    a    Ab    a"
+        ensure "f a", textC: "    |A    ab    a    Ab    a"
+        ensure ";",   textC: "    A    |ab    a    Ab    a"
+        ensure ";",   textC: "    A    ab    |a    Ab    a"
+        ensure ";",   textC: "    A    ab    a    |Ab    a"
+
+      it "find case-sensitively when input is lager char", ->
+        set textC: "|    A    ab    a    Ab    a"
+        ensure "f A", textC: "    |A    ab    a    Ab    a"
+        ensure "f A", textC: "    A    ab    a    |Ab    a"
+        ensure ",",   textC: "    |A    ab    a    Ab    a"
+        ensure ";",   textC: "    A    ab    a    |Ab    a"
+
+    describe "reuseFindForRepeatFind", ->
+      beforeEach ->
+        settings.set("reuseFindForRepeatFind", true)
+
+      it "can reuse f and t as ;, F and T as ',' respectively", ->
+        set textC: "|    A    ab    a    Ab    a"
+        ensure "f a", textC: "    A    |ab    a    Ab    a"
+        ensure "f",   textC: "    A    ab    |a    Ab    a"
+        ensure "f",   textC: "    A    ab    a    Ab    |a"
+        ensure "F",   textC: "    A    ab    |a    Ab    a"
+        ensure "F",   textC: "    A    |ab    a    Ab    a"
+        ensure "t",   textC: "    A    ab   | a    Ab    a"
+        ensure "t",   textC: "    A    ab    a    Ab   | a"
+        ensure "T",   textC: "    A    ab    a|    Ab    a"
+        ensure "T",   textC: "    A    a|b    a    Ab    a"
+
+    describe "findByTwoChars", ->
+      beforeEach ->
+        settings.set("findByTwoChars", true)
+
+      describe "can find one or two char", ->
+        it "can find by two char", ->
+          set             textC: "|    a    ab    a    cd    a"
+          ensure "f a b", textC: "    a    |ab    a    cd    a"
+          ensure "f c d", textC: "    a    ab    a    |cd    a"
+
+        it "can find by one-char by confirming explicitly", ->
+          set                 textC: "|    a    ab    a    cd    a"
+          ensure "f a enter", textC: "    |a    ab    a    cd    a"
+          ensure "f c enter", textC: "    a    ab    a    |cd    a"
+
+      describe "autoConfirmTimeout", ->
+        beforeEach -> settings.set("findByTwoCharsAutoConfirmTimeout", 500)
+        it "auto-confirm single-char input on timeout", ->
+          set             textC: "|    a    ab    a    cd    a"
+
+          ensure "f a",   textC: "|    a    ab    a    cd    a"
+          advanceClock(500)
+          ensure          textC: "    |a    ab    a    cd    a"
+
+          ensure "f c d", textC: "    a    ab    a    |cd    a"
+
+          ensure "f a",   textC: "    a    ab    a    |cd    a"
+          advanceClock(500)
+          ensure          textC: "    a    ab    a    cd    |a"
+
+          ensure "F b",   textC: "    a    ab    a    cd    |a"
+          advanceClock(500)
+          ensure          textC: "    a    a|b    a    cd    a"


Add compilation errors

Add compilation errors

--- src/coffee/view/View.coffee
+++ src/coffee/view/View.coffee
@@ -35,11 +35,15 @@
     for team in which
       code = @editors.getCode team
       bot = @model.Bot.fromString code, @model.Bot, @model.Hex
-      $(".editortitle.#{team}").html(
-        bot.name
-      )
-      @model.setBot(team, bot)
-      @persistence.save team, code
+      if bot instanceof Array
+        view.error(bot[0], bot[1])
+      else
+        $(".editortitle.#{team}").html(
+          bot.name
+        )
+        @model.setBot(team, bot)
+        @persistence.save team,
+

   view.resetBots = (which)->
     for team in which
@@ -56,6 +60,13 @@
   view.pause = ->
     $('#TogglePlay').html 'play_arrow'
     view.running = false
+
+  view.error = (title, message) ->
+    view.notifications.post(title, message)
+    view.pause()
+
+  view.warn = (title, message) ->
+    view.notifications.post("Warning", title)

   view.loop = ->
     if @running and not @won
@@ -94,12 +105,9 @@
   view.model.onWin = view.onWin.bind(view)
   view.model.onTake = view.onTake.bind(view)

-  view.model.error = (title, message) ->
-    view.notifications.post(title, message)
-    view.pause()
+  view.model.error = view.error

-  view.model.warn = (title, message) ->
-    view.notifications.post("Warning", title)
+  view.model.warn = view.warn
   #Start The Loop
   view.loop()


don't use cdn for blog posts which have a http on them

--- services/web/app/coffee/Features/Blog/BlogController.coffee
+++ services/web/app/coffee/Features/Blog/BlogController.coffee
@@ -28,7 +28,7 @@
 			try
 				data = JSON.parse(data)
 				if settings.cdn?.web?.host?
-					data?.content = data?.content?.replace(/src="([^"]+)"/g, "src='#{settings.cdn?.web?.host}$1'");
+					data?.content = data?.content?.replace(/src="([^"\:]+)"/g, "src='#{settings.cdn?.web?.host}$1'");
 			catch err
 				logger.err err:err, data:data, "error parsing data from data"
 			res.render "blog/blog_holder", data


Update Checkins limit

Update Checkins limit

--- server/models/Checkin.coffee
+++ server/models/Checkin.coffee
@@ -37,4 +37,4 @@

 export default Checkin

-export MAX_CHECKIN_PER_SESSION = [21, 13]
+export MAX_CHECKIN_PER_SESSION = [21, 21]


Fixed CheckoutCompleteCtrl order dependency name

--- app/assets/javascripts/sprangular/controllers/checkout/complete.coffee
+++ app/assets/javascripts/sprangular/controllers/checkout/complete.coffee
@@ -1,2 +1,2 @@
-Sprangular.controller 'CheckoutCompleteCtrl', ($scope, orders, Account, Cart, Checkout) ->
+Sprangular.controller 'CheckoutCompleteCtrl', ($scope, order, Account, Cart, Checkout) ->
   $scope.order = order


- fixed search bug

- fixed search bug

--- src/code/crystal/search.coffee
+++ src/code/crystal/search.coffee
@@ -2,6 +2,8 @@
 request = require 'request'

 search = (opts) ->
+  crystal = this
+
   if typeof opts == 'object'
     if opts._ && opts._[1]
       name = opts._[1]


Fixed the request's body. (no setBody method existed)

--- HARImporter.coffee
+++ HARImporter.coffee
@@ -23,7 +23,7 @@

       # Add body if present in HAR data
       if entry.request.postData?.text?
-        request.setBody entry.request.postData.text
+        request.body = entry.request.postData.text

       # Add the request inside the group
       group.appendChild(request)


:green_heart:

--- spec/spell-check-spec.coffee
+++ spec/spell-check-spec.coffee
@@ -156,10 +156,6 @@
           expect(correctionsElement.querySelectorAll('li').length).toBeGreaterThan 0
           expect(correctionsElement.querySelectorAll('li')[0].textContent).toBe "together"

-        waitsFor ->
-          correctionsElement.contains(document.activeElement)
-
-        runs ->
           atom.commands.dispatch correctionsElement, 'core:confirm'

           expect(editor.getText()).toBe 'together'


Routes should work without trailing slashes

--- server/route_bindings.coffee
+++ server/route_bindings.coffee
@@ -12,10 +12,10 @@
   app.resource 'api/narrative', narrativeApi, { format: 'json' }

   app.get "/", dashboardRoutes.index
-  app.get "/dashboard/", dashboardRoutes.index
-  app.get "/indicators/", indicatorRoutes.index
-  app.get "/reports/", reportRoutes.index
-  app.get "/bookmarks/", bookmarkRoutes.index
+  app.get "/dashboard", dashboardRoutes.index
+  app.get "/indicators", indicatorRoutes.index
+  app.get "/reports", reportRoutes.index
+  app.get "/bookmarks", bookmarkRoutes.index

   app.get "/indicator/:id", indicatorRoutes.show


Pull out the hasDimensions option

Pull out the hasDimensions option

--- components/carousel/view.coffee
+++ components/carousel/view.coffee
@@ -35,7 +35,7 @@
     @$window = $(window)
     @$document = $(document)
     @length = @collection?.length or @$('.carousel-figures .carousel-figure').length
-    { @height } = options
+    { @height, @hasDimensions } = options

   cacheSelectors: ->
     @$track = @$('.carousel-track')


trying to fix cache issues

trying to fix cache issues

--- server/startup/avatar.coffee
+++ server/startup/avatar.coffee
@@ -42,7 +42,7 @@

 		if not file?
 			res.setHeader 'content-type', 'image/svg+xml'
-			res.setHeader 'cache-control', 'public'
+			res.setHeader 'Cache-Control', 'public, max-age=31536000'
 			res.setHeader 'Last-Modified', "Thu, 01 Jan 2015 00:00:00 GMT"

 			reqModifiedHeader = req.headers["if-modified-since"];


Update dispatch signup link

Update dispatch signup link

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -173,7 +173,7 @@
   model = new Backbone.Model
     id: 'arena_dispatch'
     title: '📬'
-    body: "Are.na Dispatch is a biweekly selection of channels and blog posts delivered to your inbox.
Subscribe here</>."
+    body: "Are.na Dispatch is a biweekly selection of channels and blog posts delivered to your inbox.
Subscribe here</>."

   messageView = new MessageView model: model


very minor fix

very minor fix

--- web/site/src/javascript/status/StatusDrawer.coffee
+++ web/site/src/javascript/status/StatusDrawer.coffee
@@ -199,7 +199,7 @@
         chart_dict[__.id_of d] = chart
     @sensor_sel.select '.' + chart_class
       .attr "transform", translation
-      .each (d) =>
+      .each (d) ->
         chart = chart_dict[__.id_of d]
         chart.chart
           .width width


Fixing admin tags area

Fixing admin tags area

--- app/coffee/modules/admin/project-values.coffee
+++ app/coffee/modules/admin/project-values.coffee
@@ -719,14 +719,16 @@
         "$tgConfirm",
         "$tgResources",
         "$tgModel",
+        "tgProjectService"
     ]

-    constructor: (@scope, @rootscope, @repo, @confirm, @rs, @model) ->
+    constructor: (@scope, @rootscope, @repo, @confirm, @rs, @model, @projectService) ->
         @.loading = true
-        @rootscope.$on("project:loaded", @.loadTags)
+        @.loadTags()

     loadTags: =>
-        return @rs.projects.tagsColors(@scope.projectId).then (tags) =>
+        project = @projectService.project.toJS()
+        return @rs.projects.tagsColors(project.id).then (tags) =>
             @scope.projectTagsAll = _.map tags.getAttrs(), (color, name) =>
                 @model.make_model('tag', {name: name, color: color})
             @.filterAndSortTags()


Trying some error handling for a missing TaskLog error I saw.

--- server/queues/scoring.coffee
+++ server/queues/scoring.coffee
@@ -480,11 +480,9 @@
     callback null, responseObject

 fetchTaskLog = (responseObject, callback) ->
-  findParameters =
-    _id: responseObject.taskID
-  query = TaskLog
-  .findOne(findParameters)
+  query = TaskLog.findOne _id: responseObject.taskID
   query.exec (err, taskLog) =>
+    return callback new Error("Couldn't find TaskLog for _id #{responseObject.taskID}!") unless taskLog
     @taskLog = taskLog
     #log.info "Fetched task log!"
     callback err, taskLog.toObject()


SocialWorker: JInvitation: replace dash with async.parallel

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -6,9 +6,10 @@
 Tracker     = require './tracker'
 KodingError = require '../error'
 { extend }  = require 'underscore'
+async       = require 'async'

 { protocol, hostname } = KONFIG
-{ secure, signature, dash } = Bongo
+{ secure, signature }  = Bongo

 emailsanitize = require './user/emailsanitize'

@@ -165,23 +166,23 @@
         return callback new KodingError err                   if err
         return callback new KodingError 'group doesnt exist'  if not group

-        queue = invitations.map (invitation) -> ->
+        queue = invitations.map (invitation) -> (fin) ->

           { email, firstName, lastName, role } = invitation

           JInvitation.one { email, groupName }, (err, invitation) ->
-            return callback new KodingError err  if err
+            return fin new KodingError err  if err

             # do not send another invitation if the user is already invited
             # before
-            return queue.fin()  if invitation
+            return fin()  if invitation

             isAlreadyMember group, email, (err, isMember) ->
-              return callback new KodingError err  if err
+              return fin new KodingError err  if err

               # do not send another invitation if the user is already a member
               # of the group
-              return queue.fin()  if isMember
+              return fin()  if isMember

               hash = JUser.getHash email

@@ -201,11 +202,13 @@

               invite = new JInvitation data
               invite.save (err) ->
-                return callback new KodingError err  if err
-
-                JInvitation.sendInvitationEmail client, invite, -> queue.fin()
-
-        dash queue, callback
+                return fin new KodingError err  if err
+
+                JInvitation.sendInvitationEmail client, invite, -> fin()
+
+        async.parallel queue, (err) ->
+          return callback err  if err
+          callback null

   # isAlreadyMember checks if the given email is already member of the given


social: call afterLogin with updated signature

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -648,7 +648,7 @@
               @persistOauthInfo user.username, sessionToken, kallback
         else
           if user
-            afterLogin client.connection, user, sessionToken, session, kallback
+            afterLogin user, sessionToken, session, kallback
           else
             info = session.foreignAuth[provider]
             {username, email, firstName, lastName} = info


Smarter export in commonJS environments

Smarter export in commonJS environments

--- src/udefine.coffee
+++ src/udefine.coffee
@@ -2,9 +2,10 @@

 # Module switch
 hasModule = module? and module.exports
+exportObject = {}

 # Root object hook
-do (root = if hasModule then module.exports else this) ->
+do (root = if hasModule then exportObject else this) ->
   root.udefine or= (name, deps, factory) ->
     throw new Error 'A udefine module needs to have a name' unless name?

@@ -69,4 +70,7 @@

   # Configuration helper function
   root.udefine.configure = (configFunc) ->
-    configFunc.apply root.udefine, [if hasModule then {} else root]+    configFunc.apply root.udefine, [if hasModule then {} else root]
+
+  # Export udefine function on CommonJS environments
+  module.exports = exportObject.udefine if hasModule


More css changes.

More css changes.

--- app/controllers/interactive/my_galaxies.coffee
+++ app/controllers/interactive/my_galaxies.coffee
@@ -8,6 +8,10 @@
   active: ->
     super
     @render()
+    $('[data-link="my_galaxies"]').addClass 'active'
+
+  deactivate: ->
+    $('[data-link="my_galaxies"]').removeClass 'active'

   render: ->
     @html require('views/interactive/my_galaxies')(@)


switch to execPath rather than cwd()

switch to execPath rather than cwd()

This mirrors what AutoLaunch does for electron/nw apps.

--- src/ui/util.coffee
+++ src/ui/util.coffee
@@ -197,7 +197,7 @@
 if versions? and (versions.nw? or versions['node-webkit']? or versions.electron?)
     autoLaunchPath = undefined
 else
-    autoLaunchPath = process.cwd()
+    autoLaunchPath = process.execPath
 autoLauncher = new AutoLaunch({
     name: 'YakYak',
     path: autoLaunchPath


Remove display program

Remove display program

--- src/config/inspector.coffee
+++ src/config/inspector.coffee
@@ -54,17 +54,6 @@
         world.get('current').call 'perform', signals[command]
     }
   {_LABEL: name, _AUTHORITY: BUTTON_AUTHORITY, _CHILDREN: children}
-
-display_program = (name, children) ->
-  children = children.all() unless _.isArray(children)
-  children.unshift {name: name, fill: "white", stroke: "white"}
-  {
-    _LABEL: name,
-    selected: (world) ->
-      name == world.get('current').get('programs').get('current')
-    _AUTHORITY: COMMAND_AUTHORITY
-    _CHILDREN: children
-  }

 display_strategy = (strategy, programs) ->
   strategy.reset_children()


Disable touch icons always on

Disable touch icons always on

--- app/js/jail_iframe/classes/icon_buttons.coffee
+++ app/js/jail_iframe/classes/icon_buttons.coffee
@@ -152,7 +152,7 @@

     FactlinkJailRoot.on 'factlink.factsLoaded factlinkAdded', @_destroyUnlessValid

-    if FactlinkJailRoot.isTouchDevice()
+    if FactlinkJailRoot.isTouchDevice() && false # quick disable until it is more acceptable to publishers
       @_iconButton.fadeIn()
     else
       @_attentionSpan = new FactlinkJailRoot.AttentionSpan


added calling context to some function options

--- src/backbone.giraffe.coffee
+++ src/backbone.giraffe.coffee
@@ -174,21 +174,17 @@

     $el = Giraffe.View.to$El(el)

-    if !$el
-      error 'No such `el` to attach to', el
-      return @
-
-    # $el and $container differ for jQuery methods that operate on siblings
-    $container = if _.contains(@_siblingAttachMethods, method) then $el.parent() else $el
-
-    # The methods 'insertAfter' and 'insertBefore' become 'after' and 'before' because we always call $el[method] @$el
-    method = 'after' if method is 'insertAfter'
-    method = 'before' if method is 'insertBefore'
-
     # Make sure we're attaching to a single element
     if $el.length isnt 1
       error('Expected to render to a single element but found ' + $el.length, el)
       return @
+
+    # $el and $container differ for jQuery methods that operate on siblings
+    $container = if _.contains(@_siblingAttachMethods, method) then $el.parent() else $el
+
+    # The methods 'insertAfter' and 'insertBefore' become 'after' and 'before' because we always call $el[method] @$el
+    method = 'after' if method is 'insertAfter'
+    method = 'before' if method is 'insertBefore'

     # Detach the view so it can move around freely, preserving it so it's not disposed.
     @detach true
@@ -504,7 +500,7 @@
           when 'string'
             @$(selector)
           when 'function'
-            selector()
+            selector.call @
           else
             selector
     @
@@ -521,8 +517,8 @@
   # Inserts the `ui` names into `events`.
   _createEventsFromUIElements: ->
     return @ unless @events and @ui
-    @ui = @ui() if typeof @ui is 'function'
-    @events = @events() if typeof @events is 'function'
+    @ui = @ui.call(@) if typeof @ui is 'function'
+    @events = @events.call(@) if typeof @events is 'function'
     for eventKey, method of @events
       newEventKey = @_getEventKeyFromUIElements(eventKey)
       if newEventKey isnt eventKey
@@ -577,10 +573,10 @@

   # Binds the `dataEvents` hash that allows any arbitrary instance property of the view to be bound to easily.
   # Expects the form {'event targetObj': 'handler'}
-  _bindDataEvents: ->
-    return @ unless @dataEvents
-    @dataEvents = @dataEvents() if typeof @dataEvents is 'function'
-    for eventKey, cb of @dataEvents
+  _bindDataEvents: (dataEvents = @dataEvents) ->
+    return @ unless dataEvents
+    dataEvents = dataEvents.call(@) if typeof dataEvents is 'function'
+    for eventKey, cb of dataEvents
       pieces = eventKey.split(' ')
       if pieces.length < 2
         error 'Data event must specify target object, ex: {\'change collection\': \'handler\'}'
@@ -1084,8 +1080,7 @@
     @app.addChild @ # disposes of the router when its app is removed
     Giraffe.bindEventMap @, @app, @appEvents

-    if typeof @triggers is 'function'
-      @triggers = @triggers()
+    @triggers = @triggers.call(@) if typeof @triggers is 'function'
     if !@triggers
       return error 'Giraffe routers require a `triggers` map of routes to app events.'

@@ -1449,8 +1444,7 @@

 _setEventMapBindings = (contextObj, targetObj, eventMap, bindOrUnbindFnName) ->
-  if typeof eventMap is 'function'
-    eventMap = eventMap()
+  eventMap = eventMap.call(contextObj) if typeof eventMap is 'function'
   return unless eventMap
   for eventName, cb of eventMap
     _setEventBindings contextObj, targetObj, eventName, cb, bindOrUnbindFnName


removing forced watch on docpad ready

removing forced watch on docpad ready

This chokes current versions of DocPad even when running `docpad server`.

Fixes #19, credit to @SteveMcArthur

--- src/minicms.plugin.coffee
+++ src/minicms.plugin.coffee
@@ -32,13 +32,6 @@

             # Default sanitizer for all fields
             sanitize: require './utils/sanitize'
-
-
-        # When dopac is ready, force it to watch files
-        docpadReady: (opts) ->
-            @docpad.action 'watch', {}, (err) ->
-                if err then process.stderr.write ("#{err.message ? err}").trim()+"\n"
-                @docpad.log "Force watching file for minicms."

         # Server Extend


mpegts: Fix typo

mpegts: Fix typo

--- mpegts.coffee
+++ mpegts.coffee
@@ -480,7 +480,7 @@
     info.fixed_flag = bits.read_bit()
     info.CSPS_flag = bits.read_bit()
     info.system_audio_lock_flag = bits.read_bit()
-    info.system_video_lock_flag = bits.raed_bit()
+    info.system_video_lock_flag = bits.read_bit()
     marker_bit = bits.read_bit()
     info.video_bound = bits.read_bits 5
     info.packet_rate_restriction_flag = bits.read_bit()


more html stats

more html stats

--- shared/IdleController.coffee
+++ shared/IdleController.coffee
@@ -257,7 +257,7 @@
         null

       $scope.isHtmlStat = (stat) ->
-        stat in ['calculated kills']
+        stat in ['calculated kills', 'calculated kills by class', 'class changes']

       $scope.statsObj = (player, key) ->
         val = player.statistics[key] or 0


ADD v0.0.5

ADD v0.0.5

--- module.coffee
+++ module.coffee
@@ -10,11 +10,11 @@

   constructor: ->
     super("HelloWorld")
-
-  init: (options, moduleRouter) ->
+
+  init: (options, moduleRouter, folder) ->
     Q = require("q")
     deferred = Q.defer()
-    super(options, moduleRouter).then =>
+    super(options, moduleRouter, folder).then =>  # <- call super(options).then -> first
       moduleRouter.get @createModuleRoute("custom"), (req, res) =>
         res.render(@getTemplatePath("custom"),
           modulename: @name


Convert address and employers to many to many

--- src/models/address.coffee
+++ src/models/address.coffee
@@ -24,4 +24,7 @@
       allowNull: true,
       defaultValue: null,
       validate: { min: -180, max: 180 }
+  }, classMethods : {
+    associate: (models) ->
+      Address.hasMany(models.Employer)
   }


bugfix select form component

bugfix select form component

--- lib/assets/javascripts/cmi/form_components/select.coffee
+++ lib/assets/javascripts/cmi/form_components/select.coffee
@@ -149,7 +149,7 @@
     selected = $('option:selected', @getSelect())
     return if selected.length <= 0

-    content = selected.html()
+    content = selected.text()
     value = selected.val()

     if selected.data('cmi-input-content') && selected.data('cmi-input-content').length > 0


Composer now focuses on cc and bbc field onclick

Summary: Refactored focus delays to focus function

Test Plan: none

Reviewers: bengotow, evan

Reviewed By: evan

Subscribers: evan

Differential Revision: https://review.inboxapp.com/D1329

--- internal_packages/composer/lib/composer-view.cjsx
+++ internal_packages/composer/lib/composer-view.cjsx
@@ -57,9 +57,7 @@
     if @props.mode is "fullwindow"
       # Need to delay so the component can be fully painted. Focus doesn't
       # work unless the element is on the page.
-      _.delay =>
-        @focus("textFieldTo")
-      , 500
+      @focus "textFieldTo"

   componentWillUnmount: ->
     @_teardownForDraft()
@@ -128,11 +126,11 @@

            @setState {showcc: true}}>Cc
+                onClick={=> @_showAndFocusCc()}>Cc

            @setState {showbcc: true}}>Bcc
+                onClick={=> @_showAndFocusBcc()}>Bcc

+      return unless @isMounted()
+      @refs[field]?.focus?()
+    , 150

   isForwardedMessage: ->
     draft = @_proxy.draft()


[backend] fixed bug

[backend] fixed bug

--- src/coffee/data/data.coffee
+++ src/coffee/data/data.coffee
@@ -106,7 +106,7 @@
             @err = err
             @data = data or null

-        getKey: @key
+        getKey: -> @key
         getTag: -> @data and @data.getTag()
         getVersion: -> @data and @data.getVersion()
         getFaction: -> @data and @data.getFaction()


Update paper-elements.cson

Update paper-elements.cson

Fix an escaping issue.

--- snippets/paper-elements.cson
+++ snippets/paper-elements.cson
@@ -96,7 +96,7 @@
     """
   "paper slider":
     "prefix": "paper-slider"
-    "body": "<paper-slider min="${0:10}" max="${1:200}" value="${2:110}"></paper-slider>"
+    "body": """<paper-slider min="${0:10}" max="${1:200}" value="${2:110}"></paper-slider>"""
   "paper spinner":
     "prefix": "paper-spinner"
     "body": "<paper-spinner${1: active}></paper-spinner>"


Undocumented API that lets you set the text in the editor throught JS

--- lib/written/app/assets/javascripts/written/core/content.coffee
+++ lib/written/app/assets/javascripts/written/core/content.coffee
@@ -49,6 +49,9 @@
   dispatch: (name, data = {}) =>
     event = new CustomEvent(name, bubbles: true, detail: data)
     @element().dispatchEvent(event)
+
+  changeTo: (text) =>
+    @update(new Written.Document(text, @parsers))

   changed: (e) =>
     oldDocument = @history.current


Don't query form reposts when not authenticated

--- lib/assets/javascripts/views/posts_feed.js.coffee
+++ lib/assets/javascripts/views/posts_feed.js.coffee
@@ -13,6 +13,11 @@
   initialize: (options = {}) =>
     @entity = options.entity || TentStatus.config.meta.content.entity
     @types = options.types || TentStatus.config.feed_types
+
+    unless TentStatus.config.authenticated
+      @types = _.reject @types, (type) =>
+        TentStatus.config.repost_types.indexOf(type) != -1
+
     @feed_headers = options.headers || {}

     default_queries = [


letter-level fading

letter-level fading

--- app/vis.coffee
+++ app/vis.coffee
@@ -263,6 +263,34 @@
                         (t) -> this.children.forEach (mesh) ->
                             mesh.material.uniforms.opacity.value = i(t)
                     .each "end", resolve
+
+    fadeToArray = (to, duration) ->
+        (array) ->
+            new Promise (resolve) ->
+                d3.selectAll array
+                    .transition()
+                    .duration duration
+                    .delay (_, i) -> i * 10
+                    .tween "fadeOpacity", ->
+                        from = this.material.uniforms.opacity.value
+                        i = d3.interpolate from, to
+                        (t) ->
+                            this.material.uniforms.opacity.value = i(t)
+                    .each "end", resolve
+
+    getFadeLettersPromise = (to, duration) ->
+        (text_object) ->
+            new Promise (resolve) ->
+                d3.selectAll(text_object.children)
+                    .transition()
+                    .duration duration
+                    .tween "fadeOpacity", ->
+                        from = this.material.uniforms.opacity.value
+                        i = d3.interpolate from, to
+                        (t) ->
+                            this.material.uniforms.opacity.value = i(t)
+                    .each "end", resolve
+

     fade = (from, to, duration) ->
         (text_object) ->
@@ -408,7 +436,7 @@

     animateLines: (root) =>

-        fadeTo(1, 1000) root._text_object
+        fadeToArray(1, 1000) root._text_object.children
         @panCameraToBBox root._text_object
             .then -> traverse root

@@ -434,14 +462,15 @@
                         promises = node.children.map (child) =>

                             # Get the array of letters for the target word only
-                            word = @getWordObjects child._text_object, node.word
+                            children = @getWordObjects child._text_object, node.word

                             # The fade functions expect a "children" property
-                            fadeTo(1, 1000) children: word
+                            # fadeTo(1, 1000) children: word
+                            fadeToArray(1, 1000) children
                         return Promise.all promises
                 .then =>
                     if next_child?
-                        fadeTo(1, 1000) next_child._text_object
+                        fadeToArray(1, 1000) next_child._text_object.children
                         @panCameraToBBox next_child._text_object
                 .then ->
                     traverse next_child unless ! next_child?


abstract exec into execAndSendOutput function

--- src/tf.coffee
+++ src/tf.coffee
@@ -47,6 +47,14 @@
     return true
   return false  # file exists

+execAndSendOutput = (msg, cmd) ->
+  exec cmd, (error, stdout, stderr) ->
+    if stderr
+      msg.send {room: msg.message.user.name}, "stderr:\n```\n#{stderr}\n```"
+    else if error
+      msg.send {room: msg.message.user.name}, "Error: #{error}"
+    if stdout
+      msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"

 module.exports = (robot) ->

@@ -108,13 +116,8 @@
     #fn.replace /\//, "_"
     fp = basepath + "/hubot-tf"

-    exec "GIT_SSH_COMMAND='ssh -i #{privatekey} -F /dev/null -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' git clone #{url} #{projpath}", (error, stdout, stderr) ->
-      if stderr
-        msg.send {room: msg.message.user.name}, "stderr:\n```\n#{stderr}\n```"
-      else if error
-        msg.send {room: msg.message.user.name}, "Error: #{error}"
-      if stdout
-        msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"
+    cmd = "GIT_SSH_COMMAND='ssh -i #{privatekey} -F /dev/null -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' git clone #{url} #{projpath}"
+    execAndSendOutput msg, cmd

   robot.respond /tf list(\sprojects)?$/i, (msg) ->
     return unless isAuthorized robot, msg
@@ -127,18 +130,16 @@
     out = dir.join "`, `"
     return msg.send {room: msg.message.user.name}, "Projects: `#{out}`"

-  robot.respond /tf remote ([^\s]+)$/i, (msg) ->
-    projname = msg.match[1].replace /\//, "_"
-    return unless isAuthorized robot, msg
-    return if fileMissingSendAndReturnTrue msg, "#{basepath}/#{projname}", "Invalid project name: `#{projname}`"
-
-    exec "cd #{basepath}/#{projname} ; git remote -v", (error, stdout, stderr) ->
-      if stderr
-        msg.send {room: msg.message.user.name}, "stderr:\n```\n#{stderr}\n```"
-      else if error
-        msg.send {room: msg.message.user.name}, "Error: #{error}"
-      if stdout
-        msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"
+  robot.respond /tf (remote|pull) ([^\s]+)$/i, (msg) ->
+    action = msg.match[1]
+    projname = msg.match[2].replace /\//, "_"
+    return unless isAuthorized robot, msg
+    return if fileMissingSendAndReturnTrue msg, "#{basepath}/#{projname}", "Invalid project name: `#{projname}`"
+
+    gitcmd = "git remote -v" if action == 'remote'
+    gitcmd = "git pull" if action == 'pull'
+    cmd = "cd #{basepath}/#{projname} ; #{gitcmd}"
+    execAndSendOutput msg, cmd

   robot.respond /tf (get) ([^\s]+)$/i, (msg) ->
     action = msg.match[1]
@@ -146,13 +147,8 @@
     return unless isAuthorized robot, msg
     return if fileMissingSendAndReturnTrue msg, "#{basepath}/#{projname}", "Invalid project name: `#{projname}`"

-    exec "cd #{basepath}/#{projname}; terraform #{action} -no-color", (error, stdout, stderr) ->
-      if stderr
-        msg.send {room: msg.message.user.name}, "stderr:\n```\n#{stderr}\n```"
-      else if error
-        msg.send {room: msg.message.user.name}, "error:\n```\n#{error}\n```"
-      if stdout
-        msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"
+    cmd = "cd #{basepath}/#{projname}; terraform #{action} -no-color"
+    execAndSendOutput msg, cmd

   robot.respond /tf delete ([^\s]+)$/i, (msg) ->
     projname = msg.match[1].replace /\//, "_"


just dont ever wrap sims for now

just dont ever wrap sims for now

--- src/helpers/html-videos.coffee
+++ src/helpers/html-videos.coffee
@@ -14,6 +14,7 @@
 wrapFrames = (dom) ->
   _.each(dom.getElementsByTagName('iframe'), (frame) ->
     if (frame.parentNode?.classList.contains('embed-responsive')) then return
+    if (/cnx.org\/specials\//.test(frame.src)) then return

     wrapper = document.createElement("div")
     wrapper.className = "frame-wrapper embed-responsive #{getRatioClass(frame)}"


Add comments

Add comments

--- src/scripts/modules/search/results/breadcrumbs/breadcrumbs.coffee
+++ src/scripts/modules/search/results/breadcrumbs/breadcrumbs.coffee
@@ -29,14 +29,16 @@
     search: (query) ->
       router.navigate("search?q=#{query}", {trigger: true})

+    # Transform the query.limits object into the original query string
     formatQuery: (obj) ->
       format = (obj) ->
         _.map obj, (limit) ->
           key = _.keys(limit)[0]

+          # Values with spaces in them must have been surrounded by quote strings
           if /\s/g.test(limit[key]) and not /"/g.test(limit[key])
             limit[key] = "\"#{limit[key]}\""

-          return "#{key}:#{limit[key]}"
+          return "#{key}:#{limit[key]}" # Limit strings are in the format `limit:value`

       return _.compact(_.flatten(format(obj))).join(' ')


Force imagePath for Leaflet because of Error without it in Production

--- app/assets/javascripts/routers/abs_map_router.js.coffee
+++ app/assets/javascripts/routers/abs_map_router.js.coffee
@@ -1,5 +1,6 @@
 class Shothere.Routers.AbsMapRouter extends Backbone.Router
   initialize: (options) ->
+    L.Icon.Default.imagePath = '/assets/' # Because of strange error from Leaflet on SetView later
     @center = options.center if options.center
     @map = L.map('map')
     cloudmadeUrl = 'http://{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg';


Support one or more child components in Base

--- base/Base.cjsx
+++ base/Base.cjsx
@@ -21,7 +21,10 @@
     displayName: 'Base'

     propTypes:
-        children        : React.PropTypes.arrayOf(React.PropTypes.element).isRequired
+        children        : React.PropTypes.oneOfType([
+            React.PropTypes.element
+            React.PropTypes.arrayOf(React.PropTypes.element)
+        ]).isRequired
         canonical       : React.PropTypes.string
         className       : React.PropTypes.oneOfType([
             React.PropTypes.string


修复Model和DataModel的整体重构后遗留的BUG

修复Model和DataModel的整体重构后遗留的BUG

--- src/core/dom-binding.coffee
+++ src/core/dom-binding.coffee
@@ -103,8 +103,9 @@

 	clone: (dom, scope) ->
 		features = []
-		for feature in @features
-			features.push(feature.clone())
+		if @features
+			for feature in @features
+				features.push(feature.clone())

 		return new @constructor(dom, scope, features, @forceInit, true)


Refactored code to make it more readable

--- src/chalkboard.coffee
+++ src/chalkboard.coffee
@@ -74,15 +74,10 @@
 languages   = require "./resources/languages.json"
 definitions = require "./resources/definitions.json"

-packages = [
-  "fs"
-  "path"
-  "wrench"
-  "marked"
-]
+packages        = ["fs", "path", "wrench", "marked"]
 lib             = {}
 lib[requirePkg] = require requirePkg for requirePkg in packages
-lib["_"]        = require "underscore"
+_               = require "underscore"

 commentRegexStr = "\\s*(?:@(\\w+))?(?:\\s*(.*))?"
 lnValueRegexStr = "\\s*(.*)"
@@ -155,7 +150,7 @@
     keys[index]

   _multiLineSetAttribute = (value) ->
-    object = if lib._(argObject).isEmpty() then currentSection else argObject
+    object = if _(argObject).isEmpty() then currentSection else argObject
     value += "  \n" if value
     _setAttribute(object,
       _getMultiLineKey(-1),
@@ -164,7 +159,7 @@
     )

   _setArgObject = ->
-    if multiLineKey and not lib._(argObject).isEmpty()
+    if multiLineKey and not _(argObject).isEmpty()
       _setAttribute(currentSection,
         _getMultiLineKey(0),
         argObject,
@@ -184,7 +179,7 @@
   # push object to the correct array if necessary
   #
   _updateSection = ->
-    if hasComment and not lib._(currentSection).isEmpty()
+    if hasComment and not _(currentSection).isEmpty()

       # Check if there is remaining argObject to be cleared
       _setArgObject()
@@ -383,7 +378,7 @@
     # Copyright, license, email and author should only be parsed once
     omitList.push "copyright", "license", "author", "email"

-    for key, value of lib._(section).omit omitList
+    for key, value of _(section).omit omitList
       output += util.formatKeyValue(key, value)

   output += footer
@@ -434,7 +429,7 @@

   # Check if output folder is specify
   else if options.output?
-    base     = lib._(options.files).find (file) -> source.indexOf file is 0
+    base     = _(options.files).find (file) -> source.indexOf file is 0
     filename = lib.path.basename source, lib.path.extname(source)
     relative = lib.path.relative base, lib.path.dirname(source)
     filePath = lib.path.join(relative, filename) + ".md"
@@ -459,7 +454,8 @@
 # @param {Object} options User configurations
 #
 configure = (options) ->
-  opts = lib._.extend {}, defaults, lib._(options).pick(lib._(defaults).keys())
+  optsKeys = _(defaults).keys()
+  opts     = _.extend {}, defaults, _.pick(options, optsKeys)

   if opts.output and opts.join
     throw new Error "Cannot use both output and join option at the same time"
@@ -497,14 +493,12 @@

     if stat.isDirectory()
       documents = lib.wrench.readdirSyncRecursive userFile
-      documents = lib._(documents).chain()
-                    .flatten()
-                    .unique()
-                    .value()
+      findAll   = _.compose _.unique, _.flatten
+      documents = findAll documents

       for doc in documents
         docPath = lib.path.join cwd, userFile, doc
-        stat    = lib.fs.existsSync(docPath) && lib.fs.statSync(docPath)
+        stat    = lib.fs.existsSync(docPath) and lib.fs.statSync(docPath)
         process docPath if stat.isFile()

     else if stat.isFile()


make sure to always return responses

make sure to always return responses

--- controllers/bands.coffee
+++ controllers/bands.coffee
@@ -5,32 +5,32 @@
 exports.index = (req, res) ->
   Band.find().sort(created_at: 'descending').exec(
     (err, results) ->
-      res.status(500).json error: err if err?
-      res.status(200).json results
+      return res.status(500).json error: err if err?
+      return res.status(200).json results
   )

 # GET /api/bands/:id
 exports.show = (req, res) ->
   Band.findById req.params.id, (err, result) ->
-    res.status(500).json error: err if err?
-    res.status(200).json results
+    return res.status(500).json error: err if err?
+    return res.status(200).json results

 # POST /api/bands
 exports.create = (req, res) ->
   data = _.merge {}, req.body, {submitted_by: req.user._id}
   new Band(data)
     .save (err, results) ->
-      res.status(500).json error: err if err?
-      res.status(200).json results
+      return res.status(500).json error: err if err?
+      return res.status(200).json results

 # PUT /api/bands/:id
 exports.update = (req, res) ->
   Band.update {_id: req.params.id}, req.body, (err, count) ->
-    res.status(500).json error: err if err?
-    res.status(200).json message: "#{count} records updated"
+    return res.status(500).json error: err if err?
+    return res.status(200).json message: "#{count} records updated"

 # DELETE /api/bands/:id
 exports.delete = (req, res) ->
   Band.remove {_id: req.params.id}, (err, count) ->
-    res.status(500).json error: err if err?
-    res.status(200).json message: "#{count} records deleted"
+    return res.status(500).json error: err if err?
+    return res.status(200).json message: "#{count} records deleted"


Do not reuse paper or task ids as they bleed across qunit setups

--- test/javascripts/integration/super_ad_hoc_test.js.coffee
+++ test/javascripts/integration/super_ad_hoc_test.js.coffee
@@ -1,8 +1,8 @@
 module 'Integration: Super AdHoc Card',
   setup: ->
     setupApp integration: true
-    TahiTest.paperId = 4245
-    TahiTest.adHocTaskId = 19347
+    TahiTest.paperId = 4243
+    TahiTest.adHocTaskId = 197

     paperResponse =
       phases: [
@@ -121,3 +121,4 @@
   fillIn '.inline-edit-form textarea', "New text block, yahoo!"
   click '.task-body .inline-edit-form .button--green:contains("Save")'
   andThen -> ok Em.$.trim(find('p.inline-edit').text()).indexOf('yahoo') isnt -1
+  click '.overlay-close-button:first'


Fix sort of collection in database

Fix sort of collection in database

--- src/collection.coffee
+++ src/collection.coffee
@@ -12,10 +12,10 @@
   cacheJsonPath = "#{__dirname}/../cache.json"

   saveCollection = (data, callback) ->
-    db.insert data, (err, collection) ->
+    db.insert data, (err) ->
       if err then throw err
       console.log "Music list cached."
-      callback collection
+      callback()

   downloadTrack = (data, callback) ->
     filename = "#{data.artist} - #{data.title}.mp3"
@@ -90,6 +90,7 @@
           console.log "No tracks in your collection."

     getCollectionFromServer: (callback) ->
+      getCachedCollection = @getCachedCollection
       options =
         host: 'api.vk.com'
         port: 443
@@ -101,6 +102,7 @@
         res.on 'data', (chunk) -> response += chunk
         res.on 'end', ->
           musicJson = (JSON.parse response).response
-          saveCollection musicJson, (collection) ->
-            callback collection
+          saveCollection musicJson, ->
+            getCachedCollection (collection) ->
+              callback collection
   }


chat: fix code style

chat: fix code style

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -9,7 +9,6 @@
     options.itemClass         or= PrivateMessageListItemView

     super options, data
-

     # To keep track of who are the shown participants
     # This way we are preventing to be duplicates
@@ -41,7 +40,6 @@
     @listenWindowResize()

-
   handleAutoGrow: ->

     headerHeight = @participantsView.getHeight()
@@ -103,8 +101,10 @@

   replaceFakeItemView: (message) ->
+
     index = @listController.getListView().getItemIndex @fakeMessageMap[message.clientRequestId]
     item  = @putMessage message, index
+
     @removeFakeMessage message.clientRequestId
     @scrollDown item

@@ -139,7 +139,6 @@
     if data.clientRequestId and not data.isFake
       fakeItem = @fakeMessageMap[data.clientRequestId]
       applyConsequency fakeItem.hasClass('consequent'), item
-
       return

     @applyDayMark item, index
@@ -279,8 +278,7 @@
   createInputWidget: ->

     channel = @getData()
-
-    @input = new ReplyInputWidget {channel, cssClass : 'private'}
+    @input  = new ReplyInputWidget {channel, cssClass : 'private'}

     @input.on 'EditModeRequested', @bound 'editLastMessage'

@@ -302,6 +300,7 @@

   removeParticipant: (participant) ->
+
     return  unless participant
     return  unless @participantMap[participant._id]?

@@ -359,7 +358,6 @@
         recipient        :
           itemClass      : KDView
       submit             : (e) -> e.preventDefault()
-

     @autoComplete = new KDAutoCompleteController
       name                : 'userController'
@@ -389,7 +387,6 @@

   viewAppended: ->

-
     @addSubView @participantsView
     @addSubView @autoCompleteForm

@@ -403,8 +400,6 @@
     @setScrollTops()

-
-
   #
   # UI EVENTS/DIRECTIVES
   #
@@ -505,6 +500,3 @@

     parse : (args...) -> args.map (item) -> parseInt item

-
-
-


Fix: JNewStatusUpdate notifications are filtered

--- client/app/MainApp/avatararea/avatarareaiconmenu.coffee
+++ client/app/MainApp/avatararea/avatarareaiconmenu.coffee
@@ -58,6 +58,11 @@

     @attachListeners()

+  filterNotifications=(notifications)->
+    notifications.filter (notification) ->
+      snapshot = JSON.parse Encoder.htmlDecode notification.snapshot
+      snapshot.anchor.constructorName isnt "JNewStatusUpdate"
+
   attachListeners:->
     KD.getSingleton('notificationController').on 'NotificationHasArrived', ({event})=>
       # No need the following
@@ -66,7 +71,7 @@
         @notificationsPopup.listController.fetchNotificationTeasers (notifications)=>
           @notificationsPopup.noNotification.hide()
           @notificationsPopup.listController.removeAllItems()
-          @notificationsPopup.listController.instantiateListItems notifications
+          @notificationsPopup.listController.instantiateListItems filterNotifications notifications

     @notificationsPopup.listController.on 'NotificationCountDidChange', (count)=>
       @utils.killWait @notificationsPopup.loaderTimeout
@@ -102,7 +107,7 @@

       # Fetch Notifications
       notificationsPopup.listController.fetchNotificationTeasers (teasers)=>
-        notificationsPopup.listController.instantiateListItems teasers
+        notificationsPopup.listController.instantiateListItems filterNotifications teasers

       # Fetch Private Messages
       messagesPopup.listController.fetchMessages()


accountChanged event should be fired after KD.config.roles is set and not in parallel

--- client/app/MainApp/maincontroller/maincontroller.coffee
+++ client/app/MainApp/maincontroller/maincontroller.coffee
@@ -99,39 +99,39 @@

     {entryPoint} = KD.config
     slug = if entryPoint?.type is 'group' then entryPoint.slug else 'koding'
-    KD.remote.cacheable slug, (err, [group])->
+    KD.remote.cacheable slug, (err, [group])=>
       return warn err  if err
-      group.fetchMyRoles (err, roles)->
+      group.fetchMyRoles (err, roles)=>
         return warn err  if err
         KD.config.roles = roles

-    @accountReady @emit.bind @, "AccountChanged", account, firstLoad
-
-    unless @mainViewController
-
-      @loginScreen = new LoginView
-      KDView.appendToDOMBody @loginScreen
-
-      @mainViewController  = new MainViewController
-        view    : mainView = new MainView
-          domId : "kdmaincontainer"
-
-      KDView.appendToDOMBody mainView
-
-      @appReady()
-
-    @decorateBodyTag()
-
-    eventPrefix = if firstLoad then "pageLoaded.as" else "accountChanged.to"
-    eventSuffix = if @isUserLoggedIn() then "loggedIn" else "loggedOut"
-
-    # this emits following events
-    # -> "pageLoaded.as.loggedIn"
-    # -> "pageLoaded.as.loggedOut"
-    # -> "accountChanged.to.loggedIn"
-    # -> "accountChanged.to.loggedOut"
-
-    @emit "#{eventPrefix}.#{eventSuffix}", account, connectedState, firstLoad
+        @accountReady @emit.bind @, "AccountChanged", account, firstLoad
+
+        unless @mainViewController
+
+          @loginScreen = new LoginView
+          KDView.appendToDOMBody @loginScreen
+
+          @mainViewController  = new MainViewController
+            view    : mainView = new MainView
+              domId : "kdmaincontainer"
+
+          KDView.appendToDOMBody mainView
+
+          @appReady()
+
+        @decorateBodyTag()
+
+        eventPrefix = if firstLoad then "pageLoaded.as" else "accountChanged.to"
+        eventSuffix = if @isUserLoggedIn() then "loggedIn" else "loggedOut"
+
+        # this emits following events
+        # -> "pageLoaded.as.loggedIn"
+        # -> "pageLoaded.as.loggedOut"
+        # -> "accountChanged.to.loggedIn"
+        # -> "accountChanged.to.loggedOut"
+
+        @emit "#{eventPrefix}.#{eventSuffix}", account, connectedState, firstLoad

   doLogout:->


Animate font sizes

Animate font sizes

--- src/controller.coffee
+++ src/controller.coffee
@@ -2537,7 +2537,6 @@
   # ================================

   Editor::copyAceEditor = ->
-    @setFontSize_raw @aceEditor.getFontSize()
     @gutter.style.width = @aceEditor.renderer.$gutterLayer.gutterWidth + 'px'
     @resize()

@@ -2616,6 +2615,8 @@
           @gutter.offsetWidth + 5 # TODO see above
     }

+    @mainCtx.font = @aceEditor.getFontSize() + ' ' + @fontFamily
+
     until head is @tree.end
       switch head.type
         when 'text'
@@ -2719,7 +2720,7 @@
         div.style.top = "#{textElement.bounds[0].y - @scrollOffsets.main.y - @fontAscent}px"

         div.className = 'ice-transitioning-element'
-        div.style.transition = "left #{translateTime}ms, top #{translateTime}ms"
+        div.style.transition = "left #{translateTime}ms, top #{translateTime}ms, font-size #{translateTime}ms"
         translatingElements.push div

         @transitionContainer.appendChild div
@@ -2728,6 +2729,7 @@
           setTimeout (=>
             div.style.left = (textElement.bounds[0].x - @scrollOffsets.main.x + translationVectors[i].x) + 'px'
             div.style.top = (textElement.bounds[0].y - @scrollOffsets.main.y + translationVectors[i].y) + 'px'
+            div.style.fontSize = @aceEditor.getFontSize()
           ), fadeTime

       top = Math.max @aceEditor.getFirstVisibleRow(), 0
@@ -2751,7 +2753,7 @@
         translatingElements.push div

         div.className = 'ice-transitioning-element ice-transitioning-gutter'
-        div.style.transition = "left #{translateTime}ms, top #{translateTime}ms"
+        div.style.transition = "left #{translateTime}ms, top #{translateTime}ms, font-size #{translateTime}ms"

         @mainScrollerStuffing.appendChild div

@@ -2760,6 +2762,7 @@
           setTimeout (=>
             div.style.left = '0px'
             div.style.top = (line * lineHeight - aceScrollTop + @scrollOffsets.main.y) + 'px'
+            div.style.fontSize = @aceEditor.getFontSize()
           ), fadeTime

       @lineNumberWrapper.style.display = 'none'
@@ -2834,7 +2837,6 @@
         @mainScroller.style.overflow = 'hidden'
         @iceElement.style.width = @wrapperElement.offsetWidth + 'px'

-        @setFontSize @aceEditor.getFontSize()
         @redrawMain noText: true

         @currentlyUsingBlocks = true
@@ -2868,14 +2870,14 @@

           div.innerText = textElement.model.value

-          div.style.font = @fontSize + 'px ' + @fontFamily
+          div.style.font = @aceEditor.getFontSize() + ' ' + @fontFamily
           div.style.position = 'absolute'

           div.style.left = "#{textElement.bounds[0].x - @scrollOffsets.main.x + translationVectors[i].x}px"
           div.style.top = "#{textElement.bounds[0].y - @scrollOffsets.main.y + translationVectors[i].y}px"

           div.className = 'ice-transitioning-element'
-          div.style.transition = "left #{translateTime}ms, top #{translateTime}ms"
+          div.style.transition = "left #{translateTime}ms, top #{translateTime}ms, font-size #{translateTime}ms"
           translatingElements.push div

           @transitionContainer.appendChild div
@@ -2884,6 +2886,7 @@
             setTimeout (=>
               div.style.left = "#{textElement.bounds[0].x - @scrollOffsets.main.x}px"
               div.style.top = "#{textElement.bounds[0].y - @scrollOffsets.main.y - @fontAscent}px"
+              div.style.fontSize = @fontSize + 'px'
             ), 0

         top = Math.max @aceEditor.getFirstVisibleRow(), 0
@@ -2900,14 +2903,14 @@

           div.innerText = line + 1

-          div.style.font = @fontSize + 'px ' + @fontFamily
+          div.style.font = @aceEditor.getFontSize() + ' ' + @fontFamily
           div.style.width = "#{@aceEditor.renderer.$gutter.offsetWidth}px"

           div.style.left = 0
           div.style.top = "#{lineHeight * line - aceScrollTop + @scrollOffsets.main.y}px"

           div.className = 'ice-transitioning-element ice-transitioning-gutter'
-          div.style.transition = "left #{translateTime}ms, top #{translateTime}ms"
+          div.style.transition = "left #{translateTime}ms, top #{translateTime}ms, font-size #{translateTime}ms"
           translatingElements.push div

           @mainScrollerStuffing.appendChild div
@@ -2916,6 +2919,7 @@
             setTimeout (=>
               div.style.left = 0
               div.style.top = "#{treeView.bounds[line].y + treeView.distanceToBase[line].above - @view.opts.textHeight - @fontAscent}px"
+              div.style.fontSize = @fontSize + 'px'
             ), 0

         for el in [@mainCanvas, @highlightCanvas]


change timeout in migrations-1 to 0ms

change timeout in migrations-1 to 0ms

--- server-ce/migrations/1_move_doc_lines_to_doc_collection.coffee
+++ server-ce/migrations/1_move_doc_lines_to_doc_collection.coffee
@@ -139,7 +139,7 @@
 					markProjectAsProcessed project_id, (err)->
 						setTimeout(
 							-> callback(err)
-						,500)
+						,0)


Autofocus the first input in the details form

--- drawing-tools/tool-controls.coffee
+++ drawing-tools/tool-controls.coffee
@@ -32,6 +32,11 @@

   onToolSelect: =>
     @current_focus = document.activeElement
+    # autofocus the first field in the details form.
+    # doesn't seem to work without the 250ms delay.
+    setTimeout =>
+      @el.querySelector('input, select, textarea')?.focus()
+    , 250

   onToolDeselect: =>
     @current_focus.focus()


Fix typo

Fix typo

--- teaspoon-jasmine/spec/javascripts/jasmine1/responder_spec.coffee
+++ teaspoon-jasmine/spec/javascripts/jasmine1/responder_spec.coffee
@@ -9,12 +9,12 @@
       getFullName: -> "Jasmine 1 describe"
     @reportSpecStartingDetails =
       id: 1
-      getFullName: -> "Jasmine 2 describe has an it"
+      getFullName: -> "Jasmine 1 describe has an it"
       description: "has an it"
       suite: @reportSuiteResultsDetails
     @reportSpecResultsDetails =
       id: 1
-      getFullName: -> "Jasmine 2 describe has an it"
+      getFullName: -> "Jasmine 1 describe has an it"
       description: "has an it"
       suite: @reportSuiteResultsDetails


fix build

fix build

--- src/core/actor.coffee
+++ src/core/actor.coffee
@@ -52,8 +52,8 @@
               else
                 throw exceptions.FilteredMessageException(receiver)
               ).catch((error)->
-              message.callback(error)
-              false
+                message.callback(error)
+                false
               )).filter((message)->message!=false)
           else
             if result


remove parens from the list comp

remove parens from the list comp

--- src/app/theme-manager.coffee
+++ src/app/theme-manager.coffee
@@ -46,7 +46,7 @@
       null

   getImportPaths: ->
-    (theme.directoryPath for theme in @loadedThemes when theme.directoryPath)
+    theme.directoryPath for theme in @loadedThemes when theme.directoryPath

   loadUserStylesheet: ->
     if userStylesheetPath = @getUserStylesheetPath()


New chemistry (cloning objects base)

New chemistry (cloning objects base)

--- source/core/chemistry.coffee
+++ source/core/chemistry.coffee
@@ -10,9 +10,7 @@

 Atoms.Core.Chemistry =

-  bindings: null
-
-  chemistry: ->
+  chemistry: (elements) ->
     # Assign parent
     if @attributes.parent?
       @parent = Atoms.$ @attributes.parent
@@ -20,24 +18,27 @@
       @parent[method] @el

     # Define context & type
-    context = if @atoms? then "atoms" else "molecules"
-    type = if @atoms? then "Atom" else "Molecule"
+    context = if @type is "Molecule" then "atoms" else "molecules"
+    @[context] = {} unless @[context]?
+    type = if @type is "Molecule" then "Atom" else "Molecule"

     # Read Attributes
     for attr of @attributes
       className = Atoms.Core.className(attr)
-      if Atoms[type][className]?
+      if Atoms[type][className]? and @[context][attr]?
         @attributes[attr] = [@attributes[attr]] unless Atoms.Core.isArray @attributes[attr]
         base = @[context][attr]
         @[context][attr] = (@_mix(item, base) for item in @attributes[attr])

     # Normalize values
     for index of @[context]
+      @[index] = null
       className = Atoms.Core.className(index)
       if Atoms[type][className]?
         item = @[context][index]
         item = [item] unless Atoms.Core.isArray item
         @[index] = (@_instance(type, className, child) for child in item)
+

   _instance: (type, className, attributes) ->
     attributes.parent = @el
@@ -48,6 +49,25 @@
         instance.bind event, @["#{className}#{Atoms.Core.className(event)}"]
     instance

-  _mix: (a, base={}) ->
-    base[property] = a[property] for property of a
-    base
+
+  _mix: (extend, base) ->
+    result = @_clone(base)
+    if result? then result[prop] = extend[prop] for prop of extend else result = extend
+    result
+
+
+  _clone: (obj) ->
+    return obj if not obj? or typeof obj isnt 'object'
+    return new Date(obj.getTime()) if obj instanceof Date
+
+    if obj instanceof RegExp
+      flags = ''
+      flags += 'g' if obj.global?
+      flags += 'i' if obj.ignoreCase?
+      flags += 'm' if obj.multiline?
+      flags += 'y' if obj.sticky?
+      return new RegExp(obj.source, flags)
+
+    newInstance = new obj.constructor()
+    newInstance[key] = @_clone obj[key] for key of obj
+    newInstance


Check for function type instead of truthyness before calling

--- src/chaplin/lib/utils.coffee
+++ src/chaplin/lib/utils.coffee
@@ -24,9 +24,9 @@

     # Simple duck-typing serializer for models and collections.
     serialize: (data) ->
-      if data.serialize
+      if typeof data.serialize is 'function'
         data.serialize()
-      else if data.toJSON
+      else if typeof data.toJSON is 'function'
         data.toJSON()
       else
         throw new TypeError 'utils.serialize: Unknown data was passed'


remove useless file

remove useless file

--- src/fetch-refs.coffee
+++ src/fetch-refs.coffee
@@ -1,22 +0,0 @@
-{ Promise } = require 'es6-promise'
-cheerio = require 'cheerio'
-request = require './gh-request'
-
-module.exports = (issue) ->
-  return Promise.resolve(issue.refs) if issue.refs?
-  { number, repo, user } = issue
-  request
-    method: 'GET'
-    url: "https://github.com/#{user}/#{repo}/issues/#{number}"
-  .then ({ body }) ->
-    $ = cheerio.load body
-    refs = []
-    $('.discussion-item-ref').each ->
-      $e = $ @
-      $l = $e.find '.title-link'
-      numberString = $e.find('.issue-num').text().trim().substring(1)
-      number = parseInt numberString, 10
-      title = $l.contents().first().text().trim()
-      url = 'https://github.com' + $l.attr 'href'
-      refs.push { number, repo, title, user, url }
-    refs


Fix grouping

Fix grouping

--- lib/blame-gutter-view.coffee
+++ lib/blame-gutter-view.coffee
@@ -57,7 +57,7 @@
           else
             rowCls = 'blame-odd'
         else
-          neStr= 'removebr>'
+          lineStr= '<br>'

         lastHash = hash


add color param to log

add color param to log

--- src/gulp-print.coffee
+++ src/gulp-print.coffee
@@ -3,8 +3,9 @@
 path = require 'path'

 log = ->
-  sig = "[#{green('gulp')}]"
-  args = Array.prototype.slice.call arguments
+  color = arguments[0]
+  sig = if color then "[#{green('gulp')}]" else "[gulp]"
+  args = Array.prototype.slice.call arguments, 1
   args.unshift sig

   console.log.apply console, args
@@ -19,7 +20,7 @@
     filepath = path.relative process.cwd(), file.path
     filepath = magenta(filepath) if colors isnt false
     formatted = format filepath
-    print.log formatted if formatted
+    print.log colors, formatted if formatted
     cb null, file

 print.log = log


set view to be the default 'task'

set view to be the default 'task'

--- src/concept-coach/index.cjsx
+++ src/concept-coach/index.cjsx
@@ -67,9 +67,7 @@

 coachAPI.open = (mountNode, props) ->
   props = _.clone(props)
-
-  if componentModel.isSame
-    props.defaultView ?= componentModel.view
+  props.defaultView ?= if componentModel.isSame then componentModel.view else 'task'

   componentModel.update(
     mounter: mountNode


checker: Fix bug deleting against file

checker: Fix bug deleting against file

--- src/equality-checker.coffee
+++ src/equality-checker.coffee
@@ -34,7 +34,7 @@
         attempt execSync, "#{programs.original.command} > #{outputFileOriginal}"
     else
         outputFileOriginal = against
-
+
     for program in programs.others
         continue unless program.ensureExecutable()

@@ -83,4 +83,5 @@

         attemptShell 'rm', outputFile

-    attemptShell 'rm', outputFileOriginal
+    if against is no
+        attemptShell 'rm', outputFileOriginal


use shallow to test component is rendered

--- tutor/specs/components/new-course/build-course.spec.cjsx
+++ tutor/specs/components/new-course/build-course.spec.cjsx
@@ -13,13 +13,13 @@
     NewCourseActions.save.restore()

   it 'it transitions to ready after save', ->
-    wrapper = mount(<BuildCourse />)
+    wrapper = shallow(<BuildCourse />)
     expect(NewCourseActions.save.calledOnce).to.be.true
     expect(wrapper.text()).to.include('building')
-    expect(wrapper.find('.ox-loader')).to.have.length(1)
+    expect(wrapper.find('OXFancyLoader[isLoading=true]')).to.have.length(1)
     NewCourseActions.created({id: '1'})
     wrapper.setState({})
-    expect(wrapper.find('.ox-loader')).to.have.length(0)
+    expect(wrapper.find('OXFancyLoader')).to.have.length(0)
     expect(wrapper.text()).to.include('course is ready')
     undefined


remove old test file to get rid of warnings in test


[config] change number of workers to 4 per broker machine

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -117,7 +117,7 @@
     watch                : yes
     cronSchedule         : '00 00 00 * * *'
   graphFeederWorker:
-    numberOfWorkers: 2
+    numberOfWorkers: 4
   social        :
     login       : 'prod-social'
     numberOfWorkers: 7


Problem mentioned in TODOs

Problem mentioned in TODOs

--- crawlers/nextbike.coffee
+++ crawlers/nextbike.coffee
@@ -64,7 +64,8 @@
               snetwork = JSON.stringify(network)

               # Push data using API
-              s = new StringStream(snetwork)
+              s = new StringStream(snetwork) # TODO that doesn't work!
+              # fs.createReadStream('some.json') # TODO while this does, no idea why :/
               s.pipe (request.post 'http://localhost:3000/public_bikes?api_key=' + NEXTBIKE_API_KEY, (error, resp, body) ->
                 if error || resp.statusCode < 200 || resp.statusCode > 300
                   status[network.alias] = 'err' # statusCode


Better embedding support

Better embedding support

--- grammars/embedded_latex.cson
+++ grammars/embedded_latex.cson
@@ -1,3 +1,4 @@
-injectionSelector: 'source.embedded.latex' # when this scope is present in another grammar, inject this grammar
-scopeName: 'source.embedded.latex'
-patterns: [{ include: 'text.tex.latex' }]
+name: 'Embedded LaTeX' # doesn't appear to be used anywhere
+injectionSelector: 'source.embedded.latex'
+scopeName: "plain.latex" # doesn't appear to be used anywhere
+patterns: [{ include: "text.tex.latex#syntaxPlain"}]


Switching to using alpha value on stars.

--- src/game/states/game.coffee
+++ src/game/states/game.coffee
@@ -81,15 +81,13 @@
      	 @xdistance = Math.abs(@player.x - star.x)
      	 @ydistance = Math.abs(@player.y - star.y)

-     	 if @xdistance < 10 && @ydistance < 10
+     	 if @xdistance < 20 && @ydistance < 20
      	    if not @printed[i]
-     	       star.destroy()
-     	       star = @add.sprite star.x, star.y, 'litstar'
+     	       star.alpha = 1
      	       @printed[i] = true
      	 else
      	    if @printed[i]
-     	       star.destroy()
-     	       star = @add.sprite star.x, star.y, 'unlitstar'
+     	       star.alpha = 0.3
      	       @printed[i] = false

      if @cursors.left.isDown
@@ -112,7 +110,9 @@
   drawConstellation: (level) ->
      console.log @constellation
      for star in level.starsArray
-        @constellation.push(@add.sprite star.x, star.y, 'unlitstar')
+        star = @add.sprite star.x, star.y, 'litstar'
+        star.alpha = 0.3
+        @constellation.push(star)
         @printed.push(false)

 module.exports = Game


Fixed centering of stars.

Fixed centering of stars.

--- src/game/states/game.coffee
+++ src/game/states/game.coffee
@@ -57,7 +57,6 @@
     #Plot empty stars
     @player = @add.sprite x, y, 'player'
     @constellation = []
-    @printed = []
     @drawConstellation(LEVELS[0])

     #add sounds
@@ -82,13 +81,9 @@
      	 @ydistance = Math.abs(@player.y - star.y)

      	 if @xdistance < 20 && @ydistance < 20
-     	    if not @printed[i]
      	       star.alpha = 1
-     	       @printed[i] = true
      	 else
-     	    if @printed[i]
      	       star.alpha = 0.3
-     	       @printed[i] = false

      if @cursors.left.isDown
         @player.body.moveLeft(200)
@@ -112,7 +107,7 @@
      for star in level.starsArray
         star = @add.sprite star.x, star.y, 'litstar'
         star.alpha = 0.3
+        star.anchor.setTo 0.5, 0.5
         @constellation.push(star)
-        @printed.push(false)

 module.exports = Game


flux/teams: member can leave team

flux/teams: member can leave team

--- client/app/lib/flux/teams/actions.coffee
+++ client/app/lib/flux/teams/actions.coffee
@@ -12,7 +12,8 @@
 isKoding    = require 'app/util/isKoding'
 isEmailValid = require 'app/util/isEmailValid'
 s3upload = require 'app/util/s3upload'
-
+kookies = require 'kookies'
+Tracker = require 'app/util/tracker'

 loadTeam = ->

@@ -276,13 +277,15 @@

   { groupsController, reactor } = kd.singletons
   team = groupsController.getCurrentGroup()
-  options =
-    id: id
-    removeUserFromTeam: yes
-
-  team.unblockMember id, (err) ->
+
+  team.leave (err) ->
     console.log 'err', err
-
+    if err
+        new kd.NotificationView { title : 'You need to transfer ownership of team before leaving team' }
+
+    Tracker.track Tracker.USER_LEFT_TEAM
+    kookies.expire 'clientId'
+    global.location.replace '/'


add link to sourcemaps article; TODO: fix sourcemap treatment


welcome: avoided a race condition

welcome: avoided a race condition

--- client/welcome/lib/welcomeappview.coffee
+++ client/welcome/lib/welcomeappview.coffee
@@ -23,16 +23,6 @@
       cssClass : 'boxes clearfix'
       click    : @bound 'handleClicks'

-    whoami().fetchOtaToken (err, token) =>
-
-      return @cmd = null  if err
-
-      kontrolUrl = if globals.config.environment in ['dev', 'sandbox']
-      then "export KONTROLURL=#{globals.config.newkontrol.url}; "
-      else ''
-
-      @cmd = "#{kontrolUrl}curl -sL https://kodi.ng/d/kd | bash -s #{token}"
-

   handleClicks: (event) ->

@@ -55,7 +45,7 @@
         el.classList.add 'dim'

       when HANDLERS.messageAdmin then @messageAdmin()
-      when HANDLERS.installKd then @installKd el
+      when HANDLERS.installKd then @showKdInstallStep el
       # when HANDLERS.buildStack then @buildStack()
       else console.log "#{handler} not yet implemented"

@@ -72,6 +62,23 @@

     ActivityFlux = require 'activity/flux'
     ActivityFlux.actions.thread.switchToDefaultChannelForStackRequest()
+
+
+  showKdInstallStep: (el) ->
+
+    return @installKd el  if @cmd
+
+    whoami().fetchOtaToken (err, token) =>
+
+      return @cmd = null  if err
+
+      kontrolUrl = if globals.config.environment in ['dev', 'sandbox']
+      then "export KONTROLURL=#{globals.config.newkontrol.url}; "
+      else ''
+
+      @cmd = "#{kontrolUrl}curl -sL https://kodi.ng/d/kd | bash -s #{token}"
+
+      @installKd el

   installKd: (el) ->


use trashcan icon

use trashcan icon

--- lib/notifications-log.coffee
+++ lib/notifications-log.coffee
@@ -49,7 +49,7 @@
       header.appendChild(button)

     button = document.createElement('button')
-    button.classList.add('notifications-clear-log', 'btn', 'icon', 'icon-dash')
+    button.classList.add('notifications-clear-log', 'btn', 'icon', 'icon-trashcan')
     button.addEventListener 'click', (e) -> atom.commands.dispatch(atom.views.getView(atom.workspace), "notifications:clear-log")
     @subscriptions.add atom.tooltips.add(button, {title: "Clear notifications"})
     header.appendChild(button)


fixed formatting of download output in util

--- util/command-download.coffee
+++ util/command-download.coffee
@@ -18,15 +18,14 @@
   downloadFileById: =>
     sharefileService = new ShareFileService {@token, @sharefileDomain}
     sharefileService.downloadFileById {@itemId}, (error, result) =>
-      return console.log colors.red "Error: #{error.message}" if error?
-      console.log JSON.stringify result.body, null, 2
+      return console.error colors.red "Error: #{error.message}" if error?
+      console.log result.body

   downloadFileByPath: =>
     sharefileService = new ShareFileService {@token, @sharefileDomain}
     sharefileService.downloadFileByPath {@path}, (error, result) =>
-      return console.log colors.red "Error: #{error.message}" if error?
-
-      console.log JSON.stringify result.body, null, 2
+      return console.error colors.red "Error: #{error.message}" if error?
+      console.log result.body

   parseOptions: =>
     commander
@@ -43,10 +42,10 @@

     unless @sharefileDomain? and @token?
       commander.outputHelp()
-      process.exit 0
+      process.exit 1

     unless @path? or @itemId?
       commander.outputHelp()
-      process.exit 0
+      process.exit 1

 (new DownloadCommand()).run()


Add env var HUBOT_ARCHER_SFW to turn off a few of the archer responses

--- src/scripts/archer.coffee
+++ src/scripts/archer.coffee
@@ -16,17 +16,11 @@

 module.exports = (robot) ->

-  robot.hear /^benoit/i, (msg) ->
-    msg.send "balls"
-
   robot.hear /^loggin/i, (msg) ->
     msg.reply "call Kenny Loggins, 'cuz you're in the DANGER ZONE."

   robot.hear /^sitting down/i, (msg) ->
     msg.reply "What?! At the table? Look, he thinks he's people!"
-
-  robot.hear /love/i, (msg) ->
-    msg.reply "And I love that I have an erection... that doesn't involve homeless people."

   robot.hear /archer/i, (msg) ->

@@ -44,3 +38,13 @@
       quote = quotes[Math.floor(Math.random()*quotes.length)]
       dialog += jQuery(quote).text().trim() + "\n"
       msg.send dialog
+
+  # Make it possible to turn off a few of the more NSFW ones
+  if !process.env.HUBOT_ARCHER_SFW
+
+    robot.hear /^benoit/i, (msg) ->
+      msg.send "balls"
+
+    robot.hear /love/i, (msg) ->
+      msg.reply "And I love that I have an erection... that doesn't involve homeless people."
+


Store fabric.js Canvas in Player class

Store fabric.js Canvas in Player class

--- player.coffee
+++ player.coffee
@@ -19,7 +19,7 @@

   class Player

-    constructor: (@name, pos) ->
+    constructor: (@name, pos, @canvas) ->
       @positions = []
       @positions.push(pos)
       @current_line = new fabric.Polyline([], {


Add some fuzzing to the buffers.

Add some fuzzing to the buffers.

This is in response to a weird Firefox issue with Float32Array and AudioContext buffers.

--- player.coffee
+++ player.coffee
@@ -10,7 +10,7 @@
   overscan: false
   can_dupe: true
   latency: 180
-  bufferSize: 256
+  bufferSize: 22048

   constructor: (@gl, @audio, @inputs, @core, @game, @save) ->
     @initGL()
@@ -170,8 +170,8 @@
       fill = @buffers[@bufIndex].length - @bufOffset
       if fill > frames
         fill = frames
-      if @bufOffset == @bufferSize
-        if @bufIndex == @numBuffers - 1
+      if @bufOffset >= @bufferSize - 1000
+        if @bufIndex >= @numBuffers - 1
           break
         if @bufIndex
           startTime = @buffers[@bufIndex - 1].endTime


Fixing errors for Firefox, where the bounding box reported by  would always increase even when the window size is made smaller.

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -55,8 +55,12 @@

         # default options and other variables for later
         if @useBBox
-            @options.width = @svg[0][0].getBBox().width
-            @options.height = @svg[0][0].getBBox().height
+            if @svg[0][0].getBoundingClientRect
+                @options.width = @svg[0][0].getBoundingClientRect().width
+                @options.height = @svg[0][0].getBoundingClientRect().height
+            else
+                @options.width = @svg[0][0].getBBox().width
+                @options.height = @svg[0][0].getBBox().height
         else
             @options.width = @svg[0][0].clientWidth
             @options.height = @svg[0][0].clientHeight
@@ -240,9 +244,16 @@
             .on('resize', =>
                 # update the width of the element and the scales
                 svg = d3.select(@element).select('svg.timeslider')[0][0]
-                @options.width = if @useBBox then svg.getBBox().width else svg.clientWidth
+
+                if @useBBox
+                    if svg.getBoundingClientRect
+                        @options.width = svg.getBoundingClientRect().width
+                    else
+                        @options.width = svg.getBBox().width
+                else
+                    @options.width = svg.clientWidth
+
                 @scales.x.range([0, @options.width])
-
                 @redraw()
             )


extend "excuse" for our technically savvy designer mates

--- src/scripts/excuse.coffee
+++ src/scripts/excuse.coffee
@@ -1,11 +1,5 @@
 # Description:
-#   Get a random developer excuse
-#
-# Commands:
-#   hubot excuse me - Get a random developer excuse
-
-# Description:
-#   Get a random developer excuse
+#   Get a random developer or designer excuse
 #
 # Dependencies:
 #   None
@@ -14,17 +8,53 @@
 #   None
 #
 # Commands:
-#   hubot excuse me - Get a random developer excuse
-#   hubot excuse - Get a random developer excuse
+#   hubot developer excuse me - Get a random developer excuse
+#   hubot developer excuse - Get a random developer excuse
+#
+#   hubot designer excuse me - Get a random designer excuse
+#   hubot designer excuse - Get a random designer excuse
 #
 # Author:
-#   ianmurrays
+#   ianmurrays, hopkinschris
+
+DESIGNER_EXCUSES = [
+  "That won't fit the grid.",
+  "That's not in the wireframes.",
+  "That's a developer thing.",
+  "I didn't mock it up that way.",
+  "The developer must have changed it.",
+  "Did you try hitting refresh?",
+  "No one uses IE anyway.",
+  "That's not how I designed it.",
+  "That's way too skeuomorphic.",
+  "That's way too flat.",
+  "Just put a long shadow on it.",
+  "It wasn't designed for that kind of content.",
+  "Josef Müller-Brockmann.",
+  "That must be a server thing.",
+  "It only looks bad if it's not on Retina.",
+  "Are you looking at it in IE or something?",
+  "That's not a recognised design pattern.",
+  "It wasn't designed to work with this content.",
+  "The users will never notice that.",
+  "The users might not notice it, but they'll feel it.",
+  "These brand guidelines are shit.",
+  "You wouldn't get it, it's a design thing.",
+  "Jony wouldn't do it like this.",
+  "That's a dark pattern.",
+  "I don't think that's very user friendly.",
+  "That's not what the research says.",
+  "I didn't get a change request for that."
+]

 module.exports = (robot) ->
-  robot.respond /excuse(?: me)?/i, (msg) ->
+  robot.respond /developer excuse(?: me)?/i, (msg) ->
     robot.http("http://developerexcuses.com")
       .get() (err, res, body) ->
         matches = body.match /<a [^>]+>(.+)<\/a>/i

         if matches and matches[1]
           msg.send matches[1]
+
+  robot.respond /designer excuse(?: me)?/i, (msg) ->
+    msg.send msg.random(DESIGNER_EXCUSES)


remove htmlparser dependency

remove htmlparser dependency

htmlparser no longer required

--- src/scripts/reddit.coffee
+++ src/scripts/reddit.coffee
@@ -2,7 +2,6 @@
 #   None
 #
 # Dependencies:
-#   "htmlparser": "1.7.6"
 #   "soupselect: "0.2.0"
 #
 # Configuration:
@@ -15,7 +14,6 @@
 #   EnriqueVidal

 Select      = require( "soupselect" ).select
-HTMLParser  = require "htmlparser"

 lookup_site = "http://www.reddit.com/"