remove exports method

remove exports method

we no longer recommend mixing it in to underscore - don't make it easier

--- lib/understream.coffee
+++ lib/understream.coffee
@@ -142,8 +142,6 @@
       @pipe instance
       debug 'created', instance.constructor.name, @_streams.length
       @
-  # For backwards compatibility and easier mixing in to underscore
-  @exports: -> stream: (head) => new @ head

 Understream.mixin _(["#{__dirname}/transforms", "#{__dirname}/readables"]).chain()
   .map (dir) ->


refactor stacktemplatehelper

refactor stacktemplatehelper

--- workers/social/testhelper/models/computeproviders/stacktemplatehelper.coffee
+++ workers/social/testhelper/models/computeproviders/stacktemplatehelper.coffee
@@ -1,28 +1,52 @@
-{ generateRandomString } = require '../../index'
-StackTemplate = require '../../../../social/lib/social/models/computeproviders/stacktemplate'
+{ _
+  withConvertedUser
+  generateRandomString } = require '../../index'
+JStackTemplate = require '../../../../social/lib/social/models/computeproviders/stacktemplate'
+

 generateStackTemplateData = (client, data) ->

-  data                         ?= {}
-  { delegate }                  = client.connection
-  { template, templateDetails } = data
-  template                     ?= 'template content'
-  templateDetails              ?= 'template details'
+  data        ?= {}
+  { delegate } = client.connection
+  details      = 'template details'
+  content      = 'template content'
+  rawContent   = 'template raw content'

-  return {
-    originId    : delegate.getId()
-    group       : client.context.group
-    title       : data.title       ? generateRandomString()
-    config      : data.config      ? {}
-    description : data.description ? 'test stack template'
-    machines    : data.machines    ? []
-    accessLevel : data.accessLevel ? 'private'
-    template    : StackTemplate.generateTemplateObject, template, templateDetails
-    credentials : data.credentials ? 'credentials'
-  }
+
+  stackTemplate =
+    group           : client.context.group
+    title           : generateRandomString()
+    config          : {}
+    originId        : delegate.getId()
+    machines        : []
+    template        : content
+    rawContent      : rawContent
+    description     : 'test stack template'
+    accessLevel     : 'private'
+    credentials     : 'credentials'
+    templateDetails : details
+
+  stackTemplate = _.extend stackTemplate, data
+
+  return stackTemplate
+
+
+withConvertedUserAndStackTemplate = (options, callback) ->
+
+  [options, callback] = [callback, options]  unless callback
+  options            ?= {}
+
+  withConvertedUser (data) ->
+    stackTemplateData = generateStackTemplateData data.client, options
+
+    JStackTemplate.create client, stackTemplateData, (err, stackTemplate) ->
+      expect(err).to.not.exist
+      data.stackTemplate = stackTemplate
+      callback data

 module.exports = {
   generateStackTemplateData
+  withConvertedUserAndStackTemplate
 }


Hiding the channel sidebar if not sees_channels

--- core/app/backbone/modules/sidebar.coffee
+++ core/app/backbone/modules/sidebar.coffee
@@ -14,10 +14,13 @@
     @activateTopic topic

   Sidebar.showForChannelsAndActivateCorrectItem = (channels, currentChannel) ->
-    channelCollectionView = new ChannelsView collection: channels
-    FactlinkApp.leftMiddleRegion.show(channelCollectionView)
+    if Factlink.Global.can_haz['sees_channels']
+      channelCollectionView = new ChannelsView collection: channels
+      FactlinkApp.leftMiddleRegion.show(channelCollectionView)

-    channelCollectionView.setActiveChannel(currentChannel)
+      channelCollectionView.setActiveChannel(currentChannel)
+    else
+      FactlinkApp.leftMiddleRegion.close()

   Sidebar.activateTopic = (topic) ->
     @sidebarView.setActiveTopic(topic)


:art: :tada: TextObjet.Fold can expand when repeated!

--- lib/text-object.coffee
+++ lib/text-object.coffee
@@ -9,6 +9,7 @@
   sortRanges, countChar, pointIsAtEndOfLine, getEolBufferPositionForRow
   getTextToPoint
   getIndentLevelForBufferRow
+  getCodeFoldRowRangesContainesForRow
 } = require './utils'

 class TextObject extends Base
@@ -405,20 +406,34 @@
   @extend()

 # -------------------------
-# TODO: make it extendable when repeated
 class Fold extends TextObject
   @extend(false)
-  getFoldRowRangeForBufferRow: (bufferRow) ->
-    for currentRow in [bufferRow..0] by -1
-      [startRow, endRow] = @editor.languageMode.rowRangeForCodeFoldAtBufferRow(currentRow) ? []
-      if startRow? and (startRow <= bufferRow <= endRow)
-        startRow += 1 if @isInner()
-        return [startRow, endRow]
-
-  selectTextObject: (selection) ->
-    [startRow, endRow] = selection.getBufferRowRange()
-    row = if selection.isReversed() then startRow else endRow
-    if rowRange = @getFoldRowRangeForBufferRow(row)
+
+  shouldExcludeEndRow: (rowRange) ->
+    return false unless @isInner()
+    [startRow, endRow] = rowRange
+    getIndentLevelForBufferRow(@editor, startRow) is getIndentLevelForBufferRow(@editor, endRow)
+
+  adjustRowRange: (rowRange) ->
+    [startRow, endRow] = rowRange
+    endRow -= 1 if @shouldExcludeEndRow(rowRange)
+    startRow += 1 if @isInner()
+    [startRow, endRow]
+
+  getFoldRowRangesContainsForRow: (row) ->
+    getCodeFoldRowRangesContainesForRow(@editor, row)?.reverse()
+
+  selectTextObject: (selection) ->
+    originalRowRange = selection.getBufferRowRange()
+    fromRow = selection.getBufferRange().start.row
+    rowRanges = @getFoldRowRangesContainsForRow(fromRow)
+    return unless rowRanges
+
+    if (rowRange = rowRanges.shift())?
+      rowRange = @adjustRowRange(rowRange)
+      if _.isEqual(originalRowRange, rowRange) and (rowRange = rowRanges.shift())?
+        rowRange = @adjustRowRange(rowRange)
+    if rowRange?
       swrap(selection).selectRowRange(rowRange)

 class AFold extends Fold
@@ -440,37 +455,29 @@
     @language = @editor.getGrammar().scopeName.replace(/^source\./, '')

   getScopesForRow: (row) ->
-    tokenizedLine = @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(row)
-    for tag in tokenizedLine.tags when tag < 0 and (tag % 2 is -1)
+    tokenizedLines = @editor.displayBuffer.getTokenizedLines()
+    for tag in tokenizedLines[row].tags when tag < 0 and (tag % 2 is -1)
       atom.grammars.scopeForId(tag)

   isFunctionScope: (scope) ->
-    regex = if @language in ['go']
-      /^entity.name.function/
-    else
-      /^meta.function/
-    regex.test(scope)
+    switch @language
+      when 'go' then /^entity.name.function/.test(scope)
+      else /^meta.function/.test(scope)

   isIncludeFunctionScopeForRow: (row) ->
     for scope in @getScopesForRow(row) when @isFunctionScope(scope)
       return true
     null

-  # Greatly depending on fold, and what range is folded is vary from languages.
-  # So we need to adjust endRow based on scope.
-  getFoldRowRangeForBufferRow: (bufferRow) ->
-    for currentRow in [bufferRow..0] by -1
-      [startRow, endRow] = @editor.languageMode.rowRangeForCodeFoldAtBufferRow(currentRow) ? []
-      unless startRow? and (startRow <= bufferRow <= endRow) and @isIncludeFunctionScopeForRow(startRow)
-        continue
-      return @adjustRowRange(startRow, endRow)
-    null
-
-  adjustRowRange: (startRow, endRow) ->
-    if @isInner()
-      startRow += 1
-      endRow -= 1 unless @language in @indentScopedLanguages
-    endRow += 1 if (@language in @omittingClosingCharLanguages)
+  getFoldRowRangesContainsForRow: (row) ->
+    if rowRanges = super(row)
+      rowRanges.filter (rowRange) =>
+        @isIncludeFunctionScopeForRow(rowRange[0])
+
+  adjustRowRange: (rowRange) ->
+    [startRow, endRow] = super
+    if @isA() and (@language in @omittingClosingCharLanguages)
+      endRow += 1
     [startRow, endRow]

 class AFunction extends Function


add postgres to healthcheck

add postgres to healthcheck

--- main/src/module.coffee
+++ main/src/module.coffee
@@ -3,43 +3,77 @@

 module.exports = (params = {}) ->
   config =
-    mongoDbs: if params.mongoDbs? then params.mongoDbs else null
+    mongoDbs:         if params.mongoDbs?    then params.mongoDbs    else null
+    postgresDbs:      if params.postgresDbs? then params.postgresDbs else null

   app = express()
-
   secondes = 0
-
   setInterval (()-> secondes++),1000

-  collectionNamesAsync = (connectionDb) ->
+  collectionNamesAsync = (mongoConnectionDb) ->
     new Promise((fulfill,reject)->
-      connectionDb.collectionNames (err,items) ->
+      mongoConnectionDb.collectionNames (err,items) ->
         if err
           return reject err
         return fulfill items
       )

+  timeQueryAsync = (postgresClient) ->
+    new Promise((fulfill,reject)->
+      postgresClient.connect (err,items)->
+        if err
+          return reject err
+        else
+          postgresClient.query 'SELECT NOW() AS "theTime"', (err, result) ->
+            if err
+              return reject err
+            else
+              postgresClient.end()
+              return fulfill result
+      )
+
   app.get "/healthcheck", (req, res, next) ->
     answer = {}
     answer['uptime'] = secondes
+    promises = []
+
+    #Check postgresDbs connection
+    if config.postgresDbs
+      postgresDbs = config.postgresDbs()
+      for postgresDb in postgresDbs
+        promises.push timeQueryAsync(postgresDb)
+
+    promises.push 'mongo'

     #Check mongoDb connection
     if config.mongoDbs
       mongoDbs = config.mongoDbs()
-      promises = []
       for mongoDb in mongoDbs
         promises.push collectionNamesAsync(mongoDb)

-    Promise.settle(promises).then (results) ->
-      mongo = {}
-      i = 0
-      for result in results
-        i++
-        if result.isFulfilled()
-          mongo['database_' + i] = true
-        else
-          mongo['database_' + i] = false
-      answer['mongo'] = mongo
+    if promises.length > 1
+      Promise.settle(promises).then (results) ->
+        mongo = {}
+        postgres = {}
+        databases = postgres
+        i = 0
+        j = 1
+
+        for result in results
+          if promises[i] == 'mongo'
+            databases = mongo
+            j = 1
+          else
+            if result.isFulfilled()
+              databases['database_' + j] = true
+            else
+              databases['database_' + j] = false
+          i++
+          j++
+
+        answer['postgres'] = postgres
+        answer['mongo'] = mongo
+        res.send(answer)
+    else
       res.send(answer)
-
   return app


fix fip;

fix fip;

--- meteor/channels.coffee
+++ meteor/channels.coffee
@@ -28,7 +28,7 @@
     'concertzender': { url: 'http://streams.greenhost.nl:8080/live', tags: ['classical'] }  # pls: 'http://www.concertzender.nl/streams/live'
     'paradise': { url: 'http://scfire-m37.websys.aol.com:80/radio_paradise_mp3_128kbps' }
     'bbcworld': { url: 'http://bbcwssc.ic.llnwd.net/stream/bbcwssc_mp1_ws-eieuk', tags: ['news'] }
-    'fip': { url: 'http://mp3.live.tv-radio.com/fip/all/fiphautdebit.mp3' }
+    'fip': { url: 'http://audio.scdn.arkena.com/11016/fip-midfi128.mp3'}
     # 'tsfjazz': { url: 'http://tsfjazz.ice.infomaniak.ch:80/tsfjazz-high', tags: ['jazz', 'ads'] }

     'radiopanik': { url: 'http://streaming.domainepublic.net:8000/radiopanik.ogg', tags: ['libre', 'bx'] }


refactor computeprovider.test file

refactor computeprovider.test file

--- workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
@@ -36,12 +36,13 @@
   describe '#ping()', ->

     it 'should be able to ping for the given provider', (done) ->
-
-      withConvertedUser {}, ({ client, account }) ->
+
+      withConvertedUser ({ client, account }) ->
         client.r  = { account }
         queue     = []

         for providerSlug, provider of PROVIDERS
+          options = { provider }
           queue.push ->
             options = { provider }
             ComputeProvider.ping client, options, (err, data) ->
@@ -58,7 +59,7 @@

     it 'should not be able to ping if user doesnt have the right to ping', (done) ->

-      withDummyClient { group : 'koding' }, ({ client }) ->
+      withDummyClient ({ client }) ->

         ComputeProvider.ping$ client, { provider : PROVIDERS.google }, (err, data) ->
           expect(err?.message).to.be.equal 'Access denied'
@@ -69,7 +70,7 @@

     it 'should fail when user is not registered', (done) ->

-      withDummyClient { group : 'koding' }, ({ client }) ->
+      withDummyClient ({ client }) ->


#48 fix couleur3

#48 fix couleur3

--- meteor/channels.coffee
+++ meteor/channels.coffee
@@ -37,7 +37,7 @@

     'klara': { url: 'http://mp3.streampower.be/klara-high.mp3', tags: ['be', 'classic'] }

-    'couleur3': { url: 'http://broadcast.infomaniak.ch/rsr-couleur3-high.mp3', tags: ['ch'] }
+    'couleur3': { url: 'http://stream.srg-ssr.ch/m/couleur3/mp3_128', tags: ['ch'] }

     #'oldmen': { url: 'http://server-10.stream-server.nl:8140/', tags: ['60s', '70s'] }


Allow arrays of keys in RocketChat.settings.onload

--- packages/rocketchat-lib/lib/settings.coffee
+++ packages/rocketchat-lib/lib/settings.coffee
@@ -41,5 +41,8 @@
 		# else if Meteor.settings?[_id]?
 		# 	callback key, Meteor.settings[_id], false

-		RocketChat.settings.callbacks[key] ?= []
-		RocketChat.settings.callbacks[key].push callback
+		keys = [].concat key
+
+		for k in keys
+			RocketChat.settings.callbacks[k] ?= []
+			RocketChat.settings.callbacks[k].push callback


:bug:Fix anonymous clan details page viewing

--- server/clans/clan_handler.coffee
+++ server/clans/clan_handler.coffee
@@ -97,8 +97,6 @@

   getMembers: (req, res, clanID) ->
     # TODO: add tests
-    return @sendForbiddenError(res) unless req.user? and not req.user.isAnonymous()
-    clanIDs = req.user.get('clans') ? []
     Clan.findById clanID, (err, clan) =>
       return @sendDatabaseError(res, err) if err
       return @sendDatabaseError(res, err) unless clan


webserver: refactor fake user creation

webserver: refactor fake user creation

--- servers/lib/server/client.coffee
+++ servers/lib/server/client.coffee
@@ -55,7 +55,7 @@
 generateFakeClientFromReq = (req, res, callback)->

   {clientId} = req.cookies
-  {name: groupName, section} = req.params
+  {section} = req.params

   # TODO change this with Team product
   groupName = 'koding'
@@ -64,26 +64,20 @@
   if not clientId and req.pendingCookies?.clientId
     clientId = req.pendingCookies.clientId

-  generateFakeClient (err, fakeClient) ->
-    return callback err  if err?
+  generateFakeClient {clientId, groupName, section}, (err, fakeClient, session) ->
+
+    return callback err  if err
+
     {delegate} = fakeClient.connection

-    # check if user stored in the session really exists
-    return callback null, fakeClient  if delegate?
+    updateCookie req, res, session

-    # when related guest user is somehow deleted and client stored in session is
-    # somehow invalid, create a new session
-    bongo.models.JSession.fetchSession {}, (err, {session, account})->
-      return callback err  if err?
+    return callback null, fakeClient

-      return callback {message: "account cannot be created"}  unless account? and session?
-      updateCookie req, res, session

-      prepareFakeClient fakeClient, {account, groupName, session}
+generateFakeClient = (options, callback) ->

-      callback null, fakeClient
-
-generateFakeClient = (callback) ->
+  {clientId, groupName, section} = options

   fakeClient    =
     context       :
@@ -95,11 +89,34 @@
     impersonating : false

-  return callback null, fakeClient
+  return callback null, fakeClient  unless clientId
+
+  bongo.models.JSession.fetchSession clientId, (err, response)->
+
+    return handleError err, callback  if err
+
+    {session} = response
+
+    return handleError new Error "Session is not set", callback  unless session
+
+    fetchGroupName { groupName, section }, (err, groupName)->
+      return handleError err, callback  if err
+
+      {username} = session
+
+      prepareFakeClient fakeClient, {groupName, session, username}
+
+      return callback null, fakeClient, session

 prepareFakeClient = (fakeClient, options) ->
-  {groupName, session, account} = options
+  {groupName, session, username} = options
+
+  {JAccount} = bongo.models
+  account = new JAccount
+  account.profile.nickname = username
+  account.type = 'unregistered'
+
   fakeClient.sessionToken = session.clientId

   # set username into context


nitrogen8mm-dwe: Fix USB warning in dashboard

USB warning does not appear in dashboard, looks like it
has to do with location of variable definition.

Changelog-entry: nitrogen8mm-dwe: Fix USB warning in dashboard
Signed-off-by: Alexandru Costache <2d2dfcfad7b9832058fd41884a8955d042a85275@balena.io>

--- nitrogen8mm-dwe.coffee
+++ nitrogen8mm-dwe.coffee
@@ -14,19 +14,19 @@
 	WRITE_BALENA_UBOOT
 ]

-imageDownloadAlerts: [
-	{
-		type: 'warning'
-		message: 'To run the hostOS from a USB stick, please first ensure latest imx-boot has been written to /dev/mmcblk0boot0, and the emmc - /dev/mmcblk0 - does not contain any balena image or balena partitions.'
-	}
-]
-
 module.exports =
 	version: 1
 	slug: 'nitrogen8mm-dwe'
 	name: 'Nitrogen8MM DWE'
 	arch: 'aarch64'
 	state: 'new'
+
+	imageDownloadAlerts: [
+		{
+		type: 'warning'
+		message: 'To run the hostOS from a USB stick, please first ensure latest imx-boot has been written to /dev/mmcblk0boot0, and the emmc - /dev/mmcblk0 - does not contain any balena image or balena partitions.'
+		}
+	]

 	stateInstructions:
 		postProvisioning: postProvisioningInstructions


Ensure license banner is at the top and preserved (and only once, not inline in the jail-iframe too)

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -1,7 +1,7 @@
 # global config:true, file:true, task:true, module: true

 banner_template = """
-/*!
+/*@license
 * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= pkg.homepage ? " * " + pkg.homepage : "" %>
 * Date: <%= grunt.template.today("m/d/yyyy") %>
 * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>
@@ -51,7 +51,6 @@
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*
 */
 """

@@ -69,8 +68,6 @@
         ]
     concat:
       jail_iframe:
-        options:
-          banner: banner_template
         src: [
           'build/js/jail_iframe/libs/*'
           'build/js/jail_iframe/core.js'
@@ -83,18 +80,24 @@
         ]
         dest: 'build/jail_iframe.js'
       loader_basic:
+        options:
+          banner: banner_template
         src: [
           'build/js/loader/loader_common.js'
           'build/js/loader/loader_basic.js'
         ]
         dest: 'build/factlink_loader_basic.js'
       loader_publishers:
+        options:
+          banner: banner_template
         src: [
           'build/js/loader/loader_common.js'
           'build/js/loader/loader_publishers.js'
         ]
         dest: 'build/factlink_loader_publishers.js'
       loader_bookmarklet:
+        options:
+          banner: banner_template
         src: [
           'build/js/loader/loader_common.js'
           'build/js/loader/loader_bookmarklet.js'
@@ -121,9 +124,8 @@
         src: '*.css'
         ext: '.min.css'
     uglify:
-      options: {
-        banner: banner_template
-      },
+      options:
+        preserveComments: (node, comment) -> !comment.value.lastIndexOf('@license', 0)
       jail_iframe:
         files:
           'build/jail_iframe.min.js':                        ['build/jail_iframe.js']


go to any panel with id number.

go to any panel with id number.

--- js/routers/AppRouter.coffee
+++ js/routers/AppRouter.coffee
@@ -9,7 +9,7 @@
     @view.render()

   goToPanel: (id) ->
-    @view = new app.Views.Panel_2
+    @view = new app.Views["Panel_" + id]
     @view.render(id)

 @app = window.app ? {}


Auto-set filename based on selected file

--- services/web/public/coffee/ide/file-tree/controllers/FileTreeController.coffee
+++ services/web/public/coffee/ide/file-tree/controllers/FileTreeController.coffee
@@ -232,6 +232,13 @@
 				$scope.data.selectedProjectEntity = null
 				$scope.getProjectEntities($scope.data.selectedProject)

+			# auto-set filename based on selected file
+			$scope.$watch 'data.selectedProjectEntity', (newVal, oldVal) ->
+				return if !newVal
+				fileName = newVal.split('/').reverse()[0]
+				if fileName
+					$scope.data.name = fileName
+
 			$scope._resetAfterResponse = (opts) ->
 				isError = opts.err == true
 				$scope.state.inFlight = false


More efficient checking

More efficient checking

--- js-library/app/js/util/scrollto.coffee
+++ js-library/app/js/util/scrollto.coffee
@@ -2,8 +2,8 @@

   scrolled = false
   scroll = ->
+    return if scrolled
     return if $("span[data-factid=#{fact_id}]").length <= 0
-    return if scrolled
     scrolled = true

     $('body')


fix a break point toggle issue.

fix a break point toggle issue.

--- lib/breakpoint-store.coffee
+++ lib/breakpoint-store.coffee
@@ -9,7 +9,8 @@

     addDecoration = true
     if breakpointSearched
-      @breakpoints.splice(breakpointSearched, 1)
+      # remove breakpoint from list
+      @breakpoints.splice(@breakpoints.indexOf(breakpointSearched), 1)
       addDecoration = false
     else
       @breakpoints.push(breakpoint)


Update extension-helper.coffee

Update extension-helper.coffee

--- lib/extension-helper.coffee
+++ lib/extension-helper.coffee
@@ -23,6 +23,7 @@
   'less': 'source.less',
   'mustache': 'text.html.mustache',
   'objc': 'source.objc',
+  'objectivec': 'source.objc',
   'objective-c': 'source.objc',
   'php': 'text.html.php',
   'py': 'source.python',


When making a pass move just push it to the boards, do not do other stuff

--- lib/javascripts/move.coffee
+++ lib/javascripts/move.coffee
@@ -7,10 +7,11 @@

 # assumes that the move is valid (doesn't bother checking)
 makeValidMove = (stone, board) ->
-  set_stone(stone, board)
-  assignGroup(stone, board)
-  captures = capture_stones_with(stone, board)
-  stone.captures = captures
+  unless is_pass_move(stone)
+    set_stone(stone, board)
+    assignGroup(stone, board)
+    captures = capture_stones_with(stone, board)
+    stone.captures = captures
   board.moves.push stone

 countLiberties = (stone, board)->


remove custom properties on marker creation

Assigning custom properties to a marker when creating/copying it is deprecated. Please, consider storing the custom properties you need in some other object in your package, keyed by the marker's id property.

--- lib/line-diff-worker.coffee
+++ lib/line-diff-worker.coffee
@@ -1,6 +1,8 @@
 {$, View} = require "atom-space-pen-views"

 class LineDiffWorker
+    markers: {}
+
     registerEditor: (@editor) ->
         @editor.editorElement.onDidChangeScrollTop @update
         @editor.onDidStopChanging @update
@@ -31,8 +33,9 @@
             @decorateDiffMarkers(details)

     clearMarkers: ->
-        markers = @editor.findMarkers({name: "line-diff"})
-        marker.destroy() for marker in markers
+        for id, marker of @markers
+            marker.destroy()
+            delete @markers[id]

     decorateDiffMarkers: (details) ->
         startPoint = [details.newStart - 1, 0]
@@ -42,7 +45,7 @@
         endPoint = [newEndBufferRow, newEndBufferRowLength]
         marker = null
         messageBubble = null
-        marker = @editor.markBufferRange([startPoint, startPoint], {name: "line-diff"})
+        marker = @editor.markBufferRange([startPoint, startPoint])
         if details.isRemoving
             messageBubble = new MessageBubble(details.originalContent, =>
                 buffer.insert([details.newStart, 0], details.originalContent)
@@ -56,6 +59,7 @@
                 @editor.setTextInBufferRange([startPoint, endPoint], details.originalContent.slice(0, -1))
             )
         @editor.decorateMarker(marker, {type: "overlay", item: messageBubble, position: "tail"})
+        @markers[marker.id] = marker

     findFileDiffs: ->
         activePath = @editor.getPath()


dont destroy the pane

dont destroy the pane

--- lib/markdown-preview.coffee
+++ lib/markdown-preview.coffee
@@ -37,7 +37,7 @@

     previewPane = atom.workspace.paneForUri(uri)
     if previewPane
-      previewPane.destroy()
+      previewPane.destroyItem(previewPane.itemForUri(uri))
       return

     previousActivePane = atom.workspace.getActivePane()


Changing text editor so that it won't prompt for save.

--- lib/repl-text-editor.coffee
+++ lib/repl-text-editor.coffee
@@ -45,6 +45,9 @@

     atom.workspace.open("Clojure REPL", split:'right').done (textEditor) =>
       @textEditor = textEditor
+      # Change the text editor so it will never require saving.
+      @textEditor.isModified = -> false
+
       grammar = atom.grammars.grammarForScopeName('source.clojure')
       @textEditor.setGrammar(grammar)
       @textEditor.onDidDestroy(closingHandler)


Add class level task helpers

Add class level task helpers

--- lib/spell-check-view.coffee
+++ lib/spell-check-view.coffee
@@ -7,14 +7,22 @@
   @content: ->
     @div class: 'spell-check'

-  @task: null
-  @instances: 0
+  @createTask: ->
+    @task ?= new Task(require.resolve('./spell-check-handler'))
+    if @activeViews?
+      @activeViews++
+    else
+      @activeViews = 1

-  views: []
+  @destroyTask: ->
+    @activeViews--
+    if @activeViews is 0
+      @task?.terminate()
+      @task = null

   initialize: (@editorView) ->
-    @constructor.task ?= new Task(require.resolve('./spell-check-handler'))
-    @constructor.instances++
+    @views = []
+    @constructor.createTask()

     @subscribe editorView, 'editor:path-changed', @subscribeToBuffer
     @subscribe editorView, 'editor:grammar-changed', @subscribeToBuffer
@@ -25,11 +33,7 @@

   beforeRemove: ->
     @unsubscribeFromBuffer()
-
-    @constructor.instances--
-    if @constructor.instances is 0
-      @constructor.task.terminate()
-      @constructor.task = null
+    @constructor.destroyTask()

   unsubscribeFromBuffer: ->
     @destroyViews()
@@ -48,7 +52,7 @@

   spellCheckCurrentGrammar: ->
     grammar = @editorView.getEditor().getGrammar().scopeName
-    _.contains atom.config.get('spell-check.grammars'), grammar
+    _.contains(atom.config.get('spell-check.grammars'), grammar)

   destroyViews: ->
     while view = @views.shift()


Fix #326

Fix #326

--- lib/zombie/resources.coffee
+++ lib/zombie/resources.coffee
@@ -234,13 +234,16 @@
     jar = Request.jar()

     params =
-      method:     method
-      url:        url
-      headers:    headers
-      body:       body
-      multipart:  multipart
-      proxy:      browser.proxy
-      jar:        jar
+      method:         method
+      url:            url
+      headers:        headers
+      body:           body
+      multipart:      multipart
+      proxy:          browser.proxy
+      jar:            jar
+      followRedirect: true
+      followAllRedirects: true
+
     Request params, (error, response)->
       if error
         browser.log -> "#{method} #{URL.format(url)} => #{error.message}"


Disable connecting to Docker on tests

Disable connecting to Docker on tests

--- libs/container/index.coffee
+++ libs/container/index.coffee
@@ -9,17 +9,17 @@

 config   = require "./config.json"

+if process.env.NODE_ENV isnt 'test'
+  # Make sure the Docker client is running, or throw an error!
+  socket = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
+  docker = new Docker
+    socketPath: socket

-# Make sure the Docker client is running, or throw an error!
-socket = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
-docker = new Docker
-  socketPath: socket
-
-status = fs.statSync(socket);
-if !status.isSocket()
-  winston.error "Docker client failed to start!"
-else
-  winston.info "Docker client successfully instantiated"
+  status = fs.statSync(socket);
+  if !status.isSocket()
+    winston.error "Docker client failed to start!"
+  else
+    winston.info "Docker client successfully instantiated"

 runContainer = (language, entrypoint, volume, cb) ->


smart weight sliders enhacement

smart weight sliders enhacement

--- source/javascripts/models/project.js.coffee
+++ source/javascripts/models/project.js.coffee
@@ -43,12 +43,11 @@
     @save morpher: @morpher.toJSON()

   weightHandler: (image) =>
-    maxW = (1-image.get('targetWeight'))/(@images.models.length-1)
-    defaultW = 1
+    totalW = 0
     for img in @images.models
-      if img isnt image && img.get('targetWeight') > 0
-        defaultW = 0
-        break
+      totalW += img.get('targetWeight') if img isnt image
+    defaultW = if totalW > 0 then 0 else 1
+    maxW = (1-image.get('targetWeight')) / (totalW || @images.models.length-1)
     for img in @images.models
       unless img is image
         img.set weight: (defaultW || img.get('targetWeight')) * maxW


Overlay should not call deactivate itself, it should react to the event. It may call turnOff though

--- plugins/overlay.coffee
+++ plugins/overlay.coffee
@@ -29,13 +29,12 @@
                 @options.bound = true
                 widget.options.editable.element.bind "halloactivated", (event, data) ->
                     widget.options.currentEditable = jQuery(event.target)
-                    widget.showOverlay()
+                    if !widget.options.visible
+                        widget.showOverlay()

-                # abort editing when pressing ESCAPE --- This should be covered in hallo core, it is just not working yet
-                widget.options.editable.element.keydown (event, data) ->
-                    if event.keyCode == 27
-                        widget.options.editable.restoreOriginalContent()
-                        widget.options.editable.element.blur()
+                widget.options.editable.element.bind "hallodeactivated", (event, data) ->
+                    widget.options.currentEditable = jQuery(event.target)
+                    if widget.options.visible
                         widget.hideOverlay()

         _init: ->
@@ -45,11 +44,11 @@
             if @options.overlay is null
                 if jQuery("#halloOverlay").length > 0
                     @options.overlay = jQuery("#halloOverlay")
-                    @options.overlay.bind 'click', jQuery.proxy @hideOverlay, @
                 else
                     @options.overlay = jQuery('<div id="halloOverlay" class="halloOverlay">')
                     jQuery(document.body).append @options.overlay
-                    @options.overlay.bind 'click', jQuery.proxy @hideOverlay, @
+
+                @options.overlay.bind 'click', jQuery.proxy(@options.editable.turnOff, @options.editable)

             @options.overlay.show()

@@ -67,8 +66,6 @@
             @options.currentEditable.css 'background-color', @options.originalBgColor
             @options.currentEditable.css 'z-index', @options.originalZIndex

-            @options.editable._deactivated {data: @options.editable}
-
         # Find the closest parent having a background color. If none, returns white.
         _findBackgroundColor: (jQueryfield) ->
             color = jQueryfield.css "background-color"


fix bug

fix bug

--- src/coffee/smart-complete.coffee
+++ src/coffee/smart-complete.coffee
@@ -38,7 +38,7 @@

"
-          scWrapDom = "

"
+          scWrapDom = "

"
           @completorWrap = $ scWrapDom
           @completor = $ scDom
           @completorWrap.append @completor


Cleanup, small square means containment problems

--- app/js/main.coffee
+++ app/js/main.coffee
@@ -23,7 +23,7 @@
     retina: true

   window.polys = [
-    new Square([290, 190], 100, "#800"),
+    new Square([290, 190], 50, "#800"),
     new Square([500, 200], 100, "#008"),
     new Triangle([200, 200], [0,0], [0,200], [100,0], "#080")
   ]
@@ -109,6 +109,7 @@

     count % 2 is 1 # odd number of line crossings is inside the polygon

+  # Separating Axis Theorem, from http://www.codezealot.org/archives/55
   intersects: (other) ->
     minAxis = null
     minOverlap = 1000000 # big enough, right?
@@ -118,7 +119,6 @@
       them = other.projectionInterval axis
       overlap = Utils.intervalOverlap us, them
       return false unless overlap > 0
-      if them[0] < us[0] then multiplier = -1 else multiplier = 1
       if overlap < minOverlap
         minUs = us
         minThem = them


Coloca funcao showPopup no $.skuSelectorPopup

--- src/coffee/portal-sku-selector-popup.coffee
+++ src/coffee/portal-sku-selector-popup.coffee
@@ -3,17 +3,17 @@
 	$el.removeClass('sku-selector-loading')
 	# If this item doesn't have variations, add it to the cart directly.
 	if json.dimensions.length == 0
-		return options.addSkuToCart json.skus[0].sku, $el
+		return options.addSkuToCart json.skus[0].sku
 	else
 		# Render the sku selector, passing the options with templates
 		skuSelector = $.skuSelector.createSkuSelector(json.name, json.dimensions, json.skus, options, $el)
 		$el.html(skuSelector)
-		$el.showPopup?()
+		$.skuSelectorPopup.showPopup()

 # Adds a given sku to the cart. On success, shows the mini-cart
 # On failure, redirects the user to the cart.
-addSkuToCart = (sku, $el) ->
-	$el.hidePopup?()
+addSkuToCart = (sku) ->
+	$.skuSelectorPopup.hidePopup()
 	console.log 'Adding SKU to cart:', sku
 	promise = $.get $.skuSelector.getAddUrlForSku(sku, 1, 1, false)
 	promise.done (data) ->
@@ -113,32 +113,32 @@
 	opts = $.extend($.skuSelectorPopup.defaults, options)
 	console.log('skuSelector', opts)

-	$.skuSelector.$overlay = $(opts.overlayTemplate)
-	$.skuSelector.$overlay.addClass(opts.overlayClass) if opts.overlayClass
-	$.skuSelector.$overlay.attr('id', opts.overlayId) if opts.overlayId
+	$.skuSelectorPopup.$overlay = $(opts.overlayTemplate)
+	$.skuSelectorPopup.$overlay.addClass(opts.overlayClass) if opts.overlayClass
+	$.skuSelectorPopup.$overlay.attr('id', opts.overlayId) if opts.overlayId
 	$el = $(opts.popupTemplate)
 	$el.addClass(opts.popupClass) if opts.popupClass
 	$el.attr('id', opts.popupId) if opts.popupId

-	$('body').append($.skuSelector.$overlay) # Adds the overlay
+	$('body').append($.skuSelectorPopup.$overlay) # Adds the overlay
 	$('body').append($el) # Adds the placeholder

 	# Adds show function
-	$el.showPopup = ->
-		$.skuSelector.$overlay?.fadeIn()
+	$.skuSelectorPopup.showPopup = ->
+		$.skuSelectorPopup.$overlay?.fadeIn()
 		$el?.fadeIn()

 	# Adds hide function
-	$el.hidePopup = ->
-		$.skuSelector.$overlay?.fadeOut()
+	$.skuSelectorPopup.hidePopup = ->
+		$.skuSelectorPopup.$overlay?.fadeOut()
 		$el?.fadeOut()

 	# Hide the popup on overlay click
-	$.skuSelector.$overlay.click $el.hidePopup
+	$.skuSelectorPopup.$overlay.click $.skuSelectorPopup.hidePopup

 	# Binds the exit handler
 	$el.on 'click', '.skuselector-close', ->
-		$el.hidePopup()
+		$.skuSelectorPopup.hidePopup()
 		console.log 'Exiting sku selector'

 	return $el


add error handler in tests

add error handler in tests

--- services/track-changes/test/acceptance/coffee/helpers/TrackChangesClient.coffee
+++ services/track-changes/test/acceptance/coffee/helpers/TrackChangesClient.coffee
@@ -106,6 +106,7 @@
 		options.encoding = null
 		request.get options, (err, res, body) ->
 			return callback(error) if error?
+			return callback(new Error("empty response from s3")) if not body?
 			zlib.gunzip body, (err, result) ->
 				return callback(err) if err?
 				callback(null, JSON.parse(result.toString()))


Refactor the way logins are finished off and sessions established

--- services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
+++ services/web/app/coffee/Features/Authentication/AuthenticationController.coffee
@@ -62,15 +62,22 @@
 			if err?
 				return next(err)
 			if user # `user` is either a user object or false
-				redir = AuthenticationController._getRedirectFromSession(req) || "/project"
-				AuthenticationController.afterLoginSessionSetup req, user, (err) ->
-					if err?
-						return next(err)
-					AuthenticationController._clearRedirectFromSession(req)
-					res.json {redir: redir}
+				AuthenticationController.finishLogin(user, req, res, next)
 			else
 				res.json message: info
 		)(req, res, next)
+
+	finishLogin: (user, req, res, next) ->
+		redir = AuthenticationController._getRedirectFromSession(req) || "/project"
+		AuthenticationController._loginAsyncHandlers(req, user)
+		AuthenticationController.afterLoginSessionSetup req, user, (err) ->
+			if err?
+				return next(err)
+			AuthenticationController._clearRedirectFromSession(req)
+			if req.headers?['accept']?.match(/^application\/json.*$/)
+				res.json {redir: redir}
+			else
+				res.redirect(redir)

 	doPassportLogin: (req, username, password, done) ->
 		email = username.toLowerCase()
@@ -83,20 +90,19 @@
 				return done(error) if error?
 				if user?
 					# async actions
-					AuthenticationController._loginAsyncHandlers(req, email, user)
 					return done(null, user)
 				else
 					AuthenticationController._recordFailedLogin()
 					logger.log email: email, "failed log in"
 					return done(null, false, {text: req.i18n.translate("email_or_password_wrong_try_again"), type: 'error'})

-	_loginAsyncHandlers: (req, email, user) ->
+	_loginAsyncHandlers: (req, user) ->
 		UserHandler.setupLoginData(user, ()->)
-		LoginRateLimiter.recordSuccessfulLogin(email)
+		LoginRateLimiter.recordSuccessfulLogin(user.email)
 		AuthenticationController._recordSuccessfulLogin(user._id)
 		Analytics.recordEvent(user._id, "user-logged-in", {ip:req.ip})
 		Analytics.identifyUser(user._id, req.sessionID)
-		logger.log email: email, user_id: user._id.toString(), "successful log in"
+		logger.log email: user.email, user_id: user._id.toString(), "successful log in"
 		req.session.justLoggedIn = true
 		# capture the request ip for use when creating the session
 		user._login_req_ip = req.ip


comment about file replacements

comment about file replacements

--- services/web/app/coffee/Features/Project/ProjectEntityMongoUpdateHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityMongoUpdateHandler.coffee
@@ -56,7 +56,10 @@
 				conditions = _id:project._id
 				inc = {}
 				inc["#{path.mongo}.rev"] = 1
-				 # we need to increment the project version number for any structure change
+				# currently we do not need to increment the project version number for changes that are replacements
+				# but when we make switch to having immutable files the replace operation will add a new file, and
+				# this will require a version increase.  We will start incrementing the project version now as it does
+				# no harm and will help to test it.
 				inc['version'] = 1
 				set = {}
 				set["#{path.mongo}.created"] = new Date()


Fix typo.

Fix typo.

--- lib/users/providers/usersProvider.coffee
+++ lib/users/providers/usersProvider.coffee
@@ -15,4 +15,4 @@
 			callback null, results.rows[0]

 exports.deleteUser = (userId, callback) ->
-	peristence.users.deleteuser callback+	peristence.users.deleteUser callback


Ajout du plugin `multi-cursor-increment`

--- atom/packages.cson
+++ atom/packages.cson
@@ -19,6 +19,7 @@
   "merge-conflicts"
   "minimap"
   "monokai"
+  "multi-cursor-increment"
   "package-sync"
   "pigments"
   "split-diff"


Add snippets for toLowerCase and toUpperCase

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -333,6 +333,16 @@
     'prefix': 'nottohaveprop'
     'body': 'not.to.have.property($1);$2'

+  # Miscellaneous
+
+  'toLowerCase':
+    'prefix': 'tlc'
+    'body': 'toLowerCase()'
+
+  'toUpperCase':
+    'prefix': 'tuc'
+    'body': 'toUpperCase()'
+
 # Awk

 '.source.awk':


Add a placeholder to rnss snippet

Add a placeholder to rnss snippet

That way, the cursor ends up at the right place for entering styles.

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -37,6 +37,7 @@
             import { StyleSheet } from 'react-native'

             export default StyleSheet.create({
+              $1
             })

             """


Location of user is written to DB

Location of user is written to DB

--- src/packages/carpool-geo/carpool-geo.coffee
+++ src/packages/carpool-geo/carpool-geo.coffee
@@ -9,12 +9,15 @@

   if Meteor.isCordova

-    GeolocationFG.get (location)->
-      da ['geoloc'], "Setting cordova location:", location.coords
+    saveLocation = (location)->
+      da ['geoloc'], "Got cordova location:", location
+      Locations.insert
+        tsi: new Date(location.timestamp)
+        userId: Meteor.userId()
+        loc: [location.coords.longitude, location.coords.latitude]
+        acc: location.coords.accuracy

-    GeolocationCallback = (location)->
-      da ['geoloc'], "Watching cordova location:", location.coords
-
-    watchId = GeolocationFG.watch GeolocationCallback, 30000,
+    GeolocationFG.get saveLocation
+    watchId = GeolocationFG.watch saveLocation, 30000,
       maximumAge: 3000,
       enableHighAccuracy: true


Moved away from initial filter array structure in favor of objects

--- spec/ecs2/new_entity_store_spec.coffee
+++ spec/ecs2/new_entity_store_spec.coffee
@@ -96,29 +96,23 @@

       comps = [c1,c2,c3,c4]

-      filter1 = [
-        ['match', 'name', 'Zelda']
-      ]
-
+      filter1 = { match: { name: 'Zelda' } }
       found1 = filterObjects comps, filter1
       expectArray found1, [c2]

-      filter2 = [
-        ['match', 'affil', 'good']
-      ]
+      filter2 = { match: { affil: 'good' } }
       found2 = filterObjects comps, filter2
       expectArray found2, [c1,c2]

-      filter3 = [
-        ['match', 'name', 'Link']
-      ]
+      filter3 = { match: { name: 'Link' } }
       found3 = filterObjects comps, filter3
       expectArray found3, [c1,c4]

-      filter4 = [
-        ['match', 'name', 'Link']
-        ['match', 'affil', 'bad']
-      ]
+      filter4 =
+        match:
+          name: 'Link'
+          affil: 'bad'
+
       found4 = filterObjects comps, filter4
       expectArray found4, [c4]

@@ -126,84 +120,32 @@
     it 'works', ->
       comps = makeZeldaComps()

-      charFilter = [
-        ['match', 'type', 'character']
-      ]
-      boxFilter = [
-        ['join', 'eid']
-        ['match', 'type', 'bbox']
-      ]
-      heroFilter = [
-        ['join', 'eid']
-        ['match', 'type', 'tag']
-        ['match', 'value', 'hero']
-      ]
+      charFilter =
+        match: { type: 'character' }
+        as: 'char'
+
+      boxFilter =
+        join: 'char.eid'
+        match: { type: 'bbox' }
+        as: 'box'
+
+      heroFilter =
+        join: 'box.eid'
+        match: { type: 'tag', value: 'hero' }
+        as: 'hero'

       found1 = joinObjects comps, [charFilter, boxFilter]
       # console.log "RESULTS:",found1
       expectArray found1, [
-        [comps[1], comps[2]]
-        [comps[5], comps[6]]
+        { char: comps[1], box: comps[2] }
+        { char: comps[5], box: comps[6] }
       ]

       found2 = joinObjects comps, [charFilter,boxFilter,heroFilter]
       # console.log "RESULTS 2:",found2
       expectArray found2, [
-        [comps[1], comps[2], comps[0]]
+        {char: comps[1], box: comps[2], hero: comps[0] }
       ]
-
-  describe 'joinObjects2', ->
-    it 'works', ->
-      comps = makeZeldaComps()
-
-      charFilter =
-        match: { type: 'character' }
-
-      boxFilter =
-        join: 'eid'
-        match: { type: 'bbox' }
-
-      heroFilter =
-        join: 'eid'
-        match: { type: 'tag', value: 'hero' }
-
-      found1 = joinObjects2 comps, [charFilter, boxFilter]
-      # console.log "RESULTS:",found1
-      expectArray found1, [
-        [comps[1], comps[2]]
-        [comps[5], comps[6]]
-      ]
-
-      found2 = joinObjects2 comps, [charFilter,boxFilter,heroFilter]
-      # console.log "RESULTS 2:",found2
-      expectArray found2, [
-        [comps[1], comps[2], comps[0]]
-      ]
-
-  describe 'expandFilter', ->
-    it 'leaves arrays as they are', ->
-      expectArray expandFilter([[]]), [[]]
-      f2 = [
-        ['match','something','interesting']
-        ['a','b']
-      ]
-      expectArray expandFilter(f2), f2
-
-    it 'turns a plain string into a "type matcher"', ->
-      expectArray expandFilter('dude'), [['match','type','dude']]
-      expectArray expandFilter('character'), [['match','type','character']]
-
-    it 'turns a key:value into a key-value filter', ->
-      expectArray expandFilter('hi:there'), [['match','hi','there']]
-      expectArray expandFilter('type:dude'), [['match','type','dude']]
-
-    it 'turns a tag:string into a "tag matcher"', ->
-      expectArray expandFilter('tag:dude'), [['match','type','tag'],['match','value','dude']]
-      expectArray expandFilter('tag:enemy'), [['match','type','tag'],['match','value','enemy']]
-    it 'stops expanding', ->
-      tagFilter = [['match','type','tag'],['match','value','enemy']]
-      expectArray expandFilter('tag:enemy'), tagFilter
-      expectArray expandFilter(expandFilter(expandFilter('tag:enemy'))), tagFilter

 makeZeldaComps = ->
@@ -219,97 +161,32 @@
     { eid: 'e2', type: 'digger', status: 'burrowing' }
   ]

-################################################################################################################################################
-#
+
 filterObjects = (comps,filter) ->
-  _.filter comps, (c) ->
-    _.every filter, (fil) ->
-      c[fil[1]] == fil[2] # so far, 'match' is the ONLY kind of condition we support currently
-      # TODO: to support different conditions, bring this switch statement back:
-      # switch fil[0]
-      #   when 'match'  #  [ 'match', key, val ]
-      #     c[fil[1]] == fil[2]
-
-joinObjects = (comps,filters,row=[]) ->
+  _.filter comps, _.matches(filter.match)
+
+joinObjects = (comps,filters,row={}) ->
   if filters.length == 0
     return [ row ]

   f0 = _.cloneDeep(_.first(filters))
   fs = _.rest(filters)

-  _.forEach f0, (cond) ->
-    switch cond[0]
-      when 'join'
-        if row.length > 0
-          lobj = row[row.length-1]
-          cond[0] = 'match'
-          # cond[1] remains the specified key, such as 'eid'
-          cond[2] = lobj[cond[1]]  # eg, converts [ 'join', 'eid' ] into [ 'match', 'eid', lc['eid'] ]
-        # TODO maybe this isn't an error after all? Would it be convenient to allow ALL filters to have a join condition?
-        # else
-        #   console.log "!! ERROR: join condition found, but no left-hand object to match on? Condition:",cond
-
-  rows = []
-  _.forEach filterObjects(comps,f0), (c) ->
-    r = _.cloneDeep(row)
-    r.push c
-    _.forEach joinObjects(comps,fs,r), (r2) ->
-      rows.push r2
-  rows
-
-
-##########################################################################################
-
-filterObjects2 = (comps,filter) ->
-  _.filter comps, _.matches(filter.match)
-# filterObjects2 = (comps,filter) ->
-#   _.filter comps, (c) ->
-#     _.every filter.match, (val,key) ->
-#       c[key] == val
-
-
-joinObjects2 = (comps,filters,row=[]) ->
-  # console.log "joinObjects2(comps,", filters, ",", row, ")"
-  if filters.length == 0
-    return [ row ]
-
-  f0 = _.cloneDeep(_.first(filters))
-  fs = _.rest(filters)
-
   if f0.join?
-    if row.length > 0
-      refObj = row[row.length-1]
-      key = f0.join
+    j = f0.join
+    [refName, key] = j.split('.')
+    refObj = row[refName]
+    if refObj?
       f0.match ||= {}
       f0.match[key] = refObj[key]

   rows = []
-  _.forEach filterObjects2(comps,f0), (c) ->
+  _.forEach filterObjects(comps,f0), (c) ->
     r = _.cloneDeep(row)
-    r.push c
-    _.forEach joinObjects2(comps,fs,r), (r2) ->
+    r[f0.as] = c
+    _.forEach joinObjects(comps,fs,r), (r2) ->
       rows.push r2
   rows
-
-
-miniFormat = /\s*^(.+):(.+)\s*$/
-expandFilter = (f) ->
-  if _.isString(f)
-    if ms = miniFormat.exec(f)
-      switch ms[1]
-        when 'tag'
-          [
-            ['match', 'type', 'tag']
-            ['match', 'value', ms[2]]
-          ]
-        else
-          [
-            ['match', ms[1], ms[2]]
-          ]
-    else
-      [ [ 'match', 'type', f ] ]
-  else
-    f

 query =


remove debug alert dialog

remove debug alert dialog

--- static/activities/src/views/activity.coffee
+++ static/activities/src/views/activity.coffee
@@ -102,7 +102,6 @@
   # to the contact. If it it succeeds it marks the error as solved else
   # it displays an error message.
   onErrorResendClicked: (event) ->
-    alert event.target.id
     if @model.getDocType() is "micropost"
       switch @model.getMethod()


fix unit tests

fix unit tests

--- services/document-updater/test/unit/coffee/RedisManager/RedisManagerTests.coffee
+++ services/document-updater/test/unit/coffee/RedisManager/RedisManagerTests.coffee
@@ -551,7 +551,7 @@
 	describe "removeDocFromMemory", ->
 		beforeEach (done) ->
 			@rclient.del = sinon.stub()
-			@rclient.srem = sinon.stub().yields()
+			@rclient.srem = sinon.stub()
 			@rclient.exec.yields()
 			@RedisManager.removeDocFromMemory @project_id, @doc_id, done


support document.on* event listeners

support document.on* event listeners

--- src/server/browser/advice.coffee
+++ src/server/browser/advice.coffee
@@ -179,14 +179,15 @@
             do (type) ->
                 name = "on#{type}"
                 # TODO: remove listener if this is set to something not a function
-                html.HTMLElement.prototype.__defineSetter__ name, (func) ->
-                    rv = this["__#{name}"] = func
-                    getBrowser(this).emit 'AddEventListener',
-                        target      : this
-                        type        : type
-                    return rv
-                html.HTMLElement.prototype.__defineGetter__ name, () ->
-                    return this["__#{name}"]
+                for eventTarget in [html.HTMLElement, html.HTMLDocument]
+                    eventTarget.prototype.__defineSetter__ name, (func) ->
+                        rv = this["__#{name}"] = func
+                        getBrowser(this).emit 'AddEventListener',
+                            target      : this
+                            type        : type
+                        return rv
+                    eventTarget.prototype.__defineGetter__ name, () ->
+                        return this["__#{name}"]

     createFrameAttrHandler = (namespace) ->
         return (elem, args, rv) ->


use object to map static options to functions

--- bin/op/args.coffee
+++ bin/op/args.coffee
@@ -1,4 +1,26 @@
 isDir  = require './isDir'
+
+commands = # some static value args
+  # quiet means we won't write out options and results to the console
+  '--quiet' : (op) -> op.quiet = true
+
+  # showgen means write out the generated input file, if there is one
+  '-showgen': (op) -> op.showGeneratedInput = true
+
+  # dryrun means we don't do the browserifying, but, we output the options
+  # and the generated file (if showgen is also set).
+  '--dryrun': (op) -> op.dryrun = true
+
+  # output the generated source map
+  '--map': (op) -> op.map = true ; op.options.debug = true
+
+  # if it's 'not' then we are doing excludes
+  'not'     : (_, info) -> info.include = false
+
+# short aliases
+commands['-q'] = commands['--quiet']
+commands['-g'] = commands['--showgen']
+commands['-m'] = commands['--map']

 module.exports = (op, info, options) ->

@@ -14,7 +36,7 @@

     # stop when we see --
     if '--' is arg then break
-
+
     # if it's an existing directory then it's our basedir
     else if isDir arg then op.options.basedir = arg

@@ -29,20 +51,20 @@
     # if it's a file ending with '.json' then it's the options file
     else if arg[-5...] is '.json' then info.optionsFile = arg

-    # if it's 'not' then we are doing excludes
-    else if arg is 'not' then info.include = false
-
-    else if arg in ['-q', '--quiet'] then op.quiet = true
-
-    else if arg in ['-g', '--showgen'] then op.showGeneratedInput = true
+    # check for a static arg
+    else if commands[arg]? then commands[arg] op, info

     # else it's a module name for either include/exclude
     else
-      info.include ?= true # optional because if we saw `not` then it's false already
+      # optional because if we saw `not` then it's false already
+      info.include ?= true
+
       # split off the variable name from the end, if it's there
       split = arg.split '#'
+
       # if there are slashes (a path into the module) get the first part as the name
       moduleName = split[0].split(/\\|\//)[0]
+
       # store in `exin` to use when generating input, and outputting info to console
       info.exin[moduleName] = path:split[0], varName:split?[1]


chore(test): Refactor test of unsupported encoding

--- src/spec/unit/reader.spec.coffee
+++ src/spec/unit/reader.spec.coffee
@@ -104,10 +104,12 @@
     writeCsv(tempFile, dataInput)
     .then ->
       resultPromise = null
+
       try
-        createReader('csv', undefined, 'unsupportedEncoding')
-        resultPromise = Promise.reject('Should throw an error with unsupported encoding')
+        createReader 'csv', undefined, 'unsupportedEncoding'
+        resultPromise = Promise.reject 'Should throw an error with unsupported encoding'
       catch e
+        expect(e.message).to.equal 'Encoding does not exist: unsupportedEncoding'
         resultPromise = Promise.resolve()

       resultPromise


Update scroll top on ::getState

Update scroll top on ::getState

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -809,22 +809,21 @@
       @emitDidUpdateState()

   setScrollTop: (scrollTop) ->
-    scrollTop = @constrainScrollTop(scrollTop)
-
-    unless @scrollTop is scrollTop or Number.isNaN(scrollTop)
-      @scrollTop = scrollTop
-      @model.setScrollTop(@scrollTop)
-      @didStartScrolling()
-      @shouldUpdateVerticalScrollState = true
-      @shouldUpdateHiddenInputState = true
-      @shouldUpdateDecorations = true
-      @shouldUpdateLinesState = true
-      @shouldUpdateCursorsState = true
-      @shouldUpdateLineNumbersState = true
-      @shouldUpdateCustomGutterDecorationState = true
-      @shouldUpdateOverlaysState = true
-
-      @emitDidUpdateState()
+    return unless scrollTop?
+
+    @pendingScrollTop = scrollTop
+
+    @shouldUpdateVerticalScrollState = true
+    @shouldUpdateHiddenInputState = true
+    @shouldUpdateDecorations = true
+    @shouldUpdateLinesState = true
+    @shouldUpdateCursorsState = true
+    @shouldUpdateLineNumbersState = true
+    @shouldUpdateCustomGutterDecorationState = true
+    @shouldUpdateOverlaysState = true
+
+    @didStartScrolling()
+    @emitDidUpdateState()

   getScrollTop: ->
     @scrollTop
@@ -848,19 +847,18 @@
     @emitDidUpdateState()

   setScrollLeft: (scrollLeft) ->
-    scrollLeft = @constrainScrollLeft(scrollLeft)
-    unless @scrollLeft is scrollLeft or Number.isNaN(scrollLeft)
-      oldScrollLeft = @scrollLeft
-      @scrollLeft = scrollLeft
-      @model.setScrollLeft(@scrollLeft)
-      @shouldUpdateHorizontalScrollState = true
-      @shouldUpdateHiddenInputState = true
-      @shouldUpdateCursorsState = true
-      @shouldUpdateOverlaysState = true
-      @shouldUpdateDecorations = true
-      @shouldUpdateLinesState = true
-
-      @emitDidUpdateState()
+    return unless scrollLeft?
+
+    @pendingScrollLeft = scrollLeft
+
+    @shouldUpdateHorizontalScrollState = true
+    @shouldUpdateHiddenInputState = true
+    @shouldUpdateCursorsState = true
+    @shouldUpdateOverlaysState = true
+    @shouldUpdateDecorations = true
+    @shouldUpdateLinesState = true
+
+    @emitDidUpdateState()

   getScrollLeft: ->
     @scrollLeft
@@ -1478,9 +1476,7 @@
   getHorizontalScrollMarginInPixels: ->
     @model.getHorizontalScrollMargin() * @baseCharacterWidth

-  updateScrollPosition: ->
-    return unless @pendingScrollLogicalPosition?
-
+  commitPendingLogicalScrollPosition: ->
     {screenRange, options} = @pendingScrollLogicalPosition

     verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
@@ -1507,14 +1503,6 @@

     desiredScrollLeft = left - horizontalScrollMarginInPixels
     desiredScrollRight = right + horizontalScrollMarginInPixels
-
-    if global.enableLogs
-      console.log "====== DB ======"
-      console.log "Screen Range: #{screenRange.toString()}"
-      console.log "Client Width: #{@getClientWidth()}"
-      console.log "#{desiredScrollLeft}/#{desiredScrollRight}"
-      console.log "#{@getScrollLeft()}/#{@getScrollRight()}"
-      console.log "================"

     if options?.reversed ? true
       if desiredScrollBottom > @getScrollBottom()
@@ -1537,4 +1525,23 @@
       if desiredScrollRight > @getScrollRight()
         @setScrollRight(desiredScrollRight)

+  commitPendingScrollLeftPosition: ->
+    scrollLeft = @constrainScrollLeft(@pendingScrollLeft)
+    if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft)
+      @scrollLeft = scrollLeft
+      @model.setScrollLeft(scrollLeft)
+
+  commitPendingScrollTopPosition: ->
+    scrollTop = @constrainScrollTop(@pendingScrollTop)
+    if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop)
+      @scrollTop = scrollTop
+      @model.setScrollTop(scrollTop)
+
+  updateScrollPosition: ->
+    @commitPendingLogicalScrollPosition() if @pendingScrollLogicalPosition?
+    @commitPendingScrollLeftPosition() if @pendingScrollLeft?
+    @commitPendingScrollTopPosition() if @pendingScrollTop?
+
+    @pendingScrollTop = null
+    @pendingScrollLeft = null
     @pendingScrollLogicalPosition = null


Adapt to new text-buffer API

Adapt to new text-buffer API

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -1042,8 +1042,8 @@
       break unless @tokenIterator.getStartScreenPosition().row is screenRow
     line

-  invalidateLines: ({start, replacedExtent, replacementExtent}) ->
-    {touch} = @lineMarkerIndex.splice(start, replacedExtent, replacementExtent)
+  invalidateLines: ({start, oldExtent, newExtent}) ->
+    {touch} = @lineMarkerIndex.splice(start, oldExtent, newExtent)
     touch.forEach (lineId) =>
       @lineMarkerIndex.delete(lineId)
       @linesById.delete(lineId)


Add http:// to redirect if not present

Add http:// to redirect if not present

--- src/coffee/donations-form-model.coffee
+++ src/coffee/donations-form-model.coffee
@@ -202,7 +202,6 @@

     subscribeToDonationChannel = (channelToken) ->
       pusher = new Pusher(config['pusherpublickey'])
-
       channel = pusher.subscribe(channelToken)
       channel.bind "charge_completed", (data) ->
         # You can also use data.message
@@ -211,6 +210,8 @@
         if data.status == "success"
           $("#donation-script").trigger("donations:success")
           if config['redirectto']?
+            unless /^https?:\/\//.test(config['redirectto'])
+              config['redirectto'] = "http://#{config['redirectto']}"
             window.location.replace(config['redirectto'])
           $("#donation-form").hide()
           $(".donations-callback-flash").show(0).delay(8000).hide(0)


remove tests relating to add button

remove tests relating to add button

--- test/components/course-calendar.spec.coffee
+++ test/components/course-calendar.spec.coffee
@@ -107,18 +107,9 @@
         done()
       , done)

-  it 'should show add plan links when add component is clicked', (done) ->
-    calendarActions
-      .clickAdd(@result)
-      .then(calendarChecks.checkDoesAddDropDownShow)
-      .then(calendarChecks.checkDoesAddMenuLinkCorrectly)
-      .then( ->
-        done()
-      , done)
-
   it 'should show today as past and tomorrow as upcoming', (done) ->
-    calendarActions
-      .clickAdd(@result)
+    calendarChecks
+      .checkIsTodayPast(@result)
       .then(calendarChecks.checkIsTodayPast)
       .then(calendarChecks.checkIsTodayNotClickable)
       .then(calendarChecks.checkIsTomorrowUpcoming)


Add Connor to Arizona

Add Connor to Arizona

--- scripts/arizona.coffee
+++ scripts/arizona.coffee
@@ -6,7 +6,7 @@
   robot.hear /arizona/i, (msg) ->
     quotes = [
       "The Arizona ridge-nosed rattlesnake is perhaps the most beautiful of all eleven species of rattlesnakes found in Arizona.",
-      "The colors blue and gold are the official state colors.",
+      "Blue and gold are the official state colors of Arizona.",
       "Located in Fountain Hills is a fountain believed to be the tallest in the world.",
       "Four Corners is noted as the spot in the United States where a person can stand in four states at the same time.",
       "The age of a saguaro cactus is determined by its height.",
@@ -29,9 +29,10 @@
       "The famous labor leader, Ceasar Estrada Chavez, was born in Yuma.",
       "Tombstone, Ruby, Gillette, and Gunsight are among the ghost towns scattered throughout the state.",
       "Harming a saguaro in any manner, including cactus plugging, is illegal by state law in Arizona, and when houses or highways are built, special permits must be obtained to move or destroy any saguaro affected.",
-      "Wesley is from Arizona.",
-      "Arizona observes DST from a distance, laughing at those who employ it."
+      "Wesley and Connor are from Arizona.",
+      "Arizona observes DST from a distance, laughing at those who employ it.",
       "The saguaro cactus is native to the Sonoran Desert in the U.S. state of Arizona, the Mexican state of Sonora, and the Whipple Mountains and Imperial County areas of California.",
-      "The saguaro blossom is the State Wildflower of Arizona."
+      "The saguaro blossom is the State Wildflower of Arizona.",
+      "Although Phoenix is the capital of Arizona, it is well known that Tucson is vastly superior in all ways."
     ]
     msg.send msg.random quotes


Update example.coffee

Update example.coffee

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -1,106 +1,97 @@
-# Description:
-#   Example scripts for you to examine and try out.
-#
-# Notes:
-#   They are commented out by default, because most of them are pretty silly and
-#   wouldn't be useful and amusing enough for day to day huboting.
-#   Uncomment the ones you want to try and experiment with.
-#
-#   These are from the scripting documentation: https://github.com/github/hubot/blob/master/docs/scripting.md

 module.exports = (robot) ->

-  # robot.hear /badger/i, (res) ->
-  #   res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
-  #
-  # robot.respond /open the (.*) doors/i, (res) ->
-  #   doorType = res.match[1]
-  #   if doorType is "pod bay"
-  #     res.reply "I'm afraid I can't let you do that."
-  #   else
-  #     res.reply "Opening #{doorType} doors"
-  #
-  # robot.hear /I like pie/i, (res) ->
-  #   res.emote "makes a freshly baked pie"
-  #
-  # lulz = ['lol', 'rofl', 'lmao']
-  #
-  # robot.respond /lulz/i, (res) ->
-  #   res.send res.random lulz
-  #
-  # robot.topic (res) ->
-  #   res.send "#{res.message.text}? That's a Paddlin'"
-  #
-  #
-  # enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
-  # leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
-  #
-  # robot.enter (res) ->
-  #   res.send res.random enterReplies
-  # robot.leave (res) ->
-  #   res.send res.random leaveReplies
-  #
-  # answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
-  #
-  # robot.respond /what is the answer to the ultimate question of life/, (res) ->
-  #   unless answer?
-  #     res.send "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
-  #     return
-  #   res.send "#{answer}, but what is the question?"
-  #
-  # robot.respond /you are a little slow/, (res) ->
-  #   setTimeout () ->
-  #     res.send "Who you calling 'slow'?"
-  #   , 60 * 1000
-  #
-  # annoyIntervalId = null
-  #
-  # robot.respond /annoy me/, (res) ->
-  #   if annoyIntervalId
-  #     res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
-  #     return
-  #
-  #   res.send "Hey, want to hear the most annoying sound in the world?"
-  #   annoyIntervalId = setInterval () ->
-  #     res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
-  #   , 1000
-  #
-  # robot.respond /unannoy me/, (res) ->
-  #   if annoyIntervalId
-  #     res.send "GUYS, GUYS, GUYS!"
-  #     clearInterval(annoyIntervalId)
-  #     annoyIntervalId = null
-  #   else
-  #     res.send "Not annoying you right now, am I?"
-  #
-  #
-  # robot.router.post '/hubot/chatsecrets/:room', (req, res) ->
-  #   room   = req.params.room
-  #   data   = JSON.parse req.body.payload
-  #   secret = data.secret
-  #
-  #   robot.messageRoom room, "I have a secret: #{secret}"
-  #
-  #   res.send 'OK'
-  #
-  # robot.error (err, res) ->
-  #   robot.logger.error "DOES NOT COMPUTE"
-  #
-  #   if res?
-  #     res.reply "DOES NOT COMPUTE"
-  #
-  # robot.respond /have a soda/i, (res) ->
-  #   # Get number of sodas had (coerced to a number).
-  #   sodasHad = robot.brain.get('totalSodas') * 1 or 0
-  #
-  #   if sodasHad > 4
-  #     res.reply "I'm too fizzy.."
-  #
-  #   else
-  #     res.reply 'Sure!'
-  #
-  #     robot.brain.set 'totalSodas', sodasHad+1
-  #
-  # robot.respond /sleep it off/i, (res) ->
-  #   robot.brain.set 'totalSodas', 0
-  #   res.reply 'zzzzz'
+   robot.hear /badger/i, (res) ->
+     res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
+
+   robot.respond /open the (.*) doors/i, (res) ->
+     doorType = res.match[1]
+     if doorType is "pod bay"
+       res.reply "I'm afraid I can't let you do that."
+     else
+       res.reply "Opening {doorType} doors"
+
+   robot.hear /I like pie/i, (res) ->
+     res.emote "makes a freshly baked pie"
+
+   lulz = ['lol', 'rofl', 'lmao']
+
+   robot.respond /lulz/i, (res) ->
+     res.send res.random lulz
+
+   robot.topic (res) ->
+     res.send "{res.message.text}? That's a Paddlin'"
+
+
+   enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
+   leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
+
+   robot.enter (res) ->
+     res.send res.random enterReplies
+   robot.leave (res) ->
+     res.send res.random leaveReplies
+
+   answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
+
+   robot.respond /what is the answer to the ultimate question of life/, (res) ->
+     unless answer?
+       res.send "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
+       return
+     res.send "{answer}, but what is the question?"
+
+   robot.respond /you are a little slow/, (res) ->
+     setTimeout () ->
+       res.send "Who you calling 'slow'?"
+     , 60 * 1000
+
+   annoyIntervalId = null
+
+   robot.respond /annoy me/, (res) ->
+     if annoyIntervalId
+       res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
+       return
+
+     res.send "Hey, want to hear the most annoying sound in the world?"
+     annoyIntervalId = setInterval () ->
+       res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
+     , 1000
+
+   robot.respond /unannoy me/, (res) ->
+     if annoyIntervalId
+       res.send "GUYS, GUYS, GUYS!"
+       clearInterval(annoyIntervalId)
+       annoyIntervalId = null
+     else
+       res.send "Not annoying you right now, am I?"
+
+
+   robot.router.post '/hubot/chatsecrets/:room', (req, res) ->
+     room   = req.params.room
+     data   = JSON.parse req.body.payload
+     secret = data.secret
+
+     robot.messageRoom room, "I have a secret: {secret}"
+
+     res.send 'OK'
+
+   robot.error (err, res) ->
+     robot.logger.error "DOES NOT COMPUTE"
+
+     if res?
+       res.reply "DOES NOT COMPUTE"
+
+   robot.respond /have a soda/i, (res) ->
+      Get number of sodas had (coerced to a number).
+     sodasHad = robot.brain.get('totalSodas') * 1 or 0
+
+     if sodasHad > 4
+       res.reply "I'm too fizzy.."
+
+     else
+       res.reply 'Sure!'
+
+       robot.brain.set 'totalSodas', sodasHad+1
+
+   robot.respond /sleep it off/i, (res) ->
+     robot.brain.set 'totalSodas', 0
+     res.reply 'zzzzz'


console.log if exists

console.log if exists

--- static/js/bookmarks.model.coffee
+++ static/js/bookmarks.model.coffee
@@ -108,4 +108,5 @@
         $.ajax ajaxSettings

     onPost: (data) ->
-        console.log data
+        if console and console.log
+            console.log data


Updated sis

Updated sis

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -283,7 +283,8 @@

Еще раз: ЛКШ — это очень и очень круто и полезно! Поэтому всем, кто имеет уровень хоть сколько-то выше начального, имеет смысл туда поехать.

-
Правда, есть проблема: участие в ЛКШ платное (ЛКШ проводится группой инициативных преподавателей и не имеет государственной поддержки), стоимость участия (19 дней: проживание, питание, занятия) будет предварительно 60 тыс. рублей.

+
Правда, есть проблема: участие в ЛКШ платное (ЛКШ проводится группой инициативных преподавателей и не имеет государственной поддержки), стоимость участия (19 дней: проживание, питание, занятия) будет предварительно 60 тыс. рублей,
+        с возможностью получения кешбека при условии оплаты картой МИР (20 тыс. рублей).


Remove test feedback and clean up tickets script.

--- scripts/tickets.coffee
+++ scripts/tickets.coffee
@@ -8,7 +8,7 @@
 #   hubot raffle draw         - Close raffle, pick winner.
 #   hubot raffle reset        - Clear entrants for the next raffle.
 #   hubot ticket              - Enters raffle and takes <cost> points from entrant.
-
+#   hubot entrants            - Admin debugging command to check for entrants.
 #
 # Author:
 #   Dave McNally
@@ -47,10 +47,7 @@
                 points[username] -= cost
                 bought.push(username)
                 save(robot)
-                msg.send "Test: raffle entered."
-            else
-                msg.send "Test: Seems you're already in the raffle."
-            return
+                return

     # Close raffle, drawn winner and reset.
     robot.respond /raffle draw$/i, (msg) ->
@@ -68,7 +65,7 @@
             save(robot)
             return

-    # Check entrants. Temp command for testing
+    # Check entrants. Admin command for testing
     robot.respond /entrants$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin'])
             tickets = [username for username in bought]


bleep

bleep

--- scripts/release.coffee
+++ scripts/release.coffee
@@ -101,7 +101,7 @@
         yield github({
           uri: "/issues/#{issue}/comments"
           method: 'POST'
-          body: { body: ":pager: bleep bloop; this is your friendly neighborhood build bot announcing new test build [#{process.env.CIRCLE_BUILD_NUM}](https://github.com/retorquere/zotero-better-bibtex/releases/download/builds/zotero-better-bibtex-#{version}.xpi)." }
+          body: { body: ":robot: bleep bloop; this is your friendly neighborhood build bot announcing new test build [#{process.env.CIRCLE_BUILD_NUM}](https://github.com/retorquere/zotero-better-bibtex/releases/download/builds/zotero-better-bibtex-#{version}.xpi)." }
         })

   return


weather script

weather script

--- scripts/weather.coffee
+++ scripts/weather.coffee
@@ -46,7 +46,7 @@
               msg.send('No weather today. Stay indoors and don\'t look outside.')
             )
         else
-          if location === 'my ass'
+          if location == 'my ass'
             msg.send('It\'s hot and moist today.')
           else
             msg.send('Nope. Sorry. Not happening.')


Remember first stage index when calculating state

--- tutor/src/components/new-course/wizard.cjsx
+++ tutor/src/components/new-course/wizard.cjsx
@@ -39,7 +39,7 @@
   propTypes:
     isLoading: React.PropTypes.bool.isRequired
   getInitialState: ->
-    currentStage: 0
+    currentStage: 0, firstStage: 0

   contextTypes:
     router: React.PropTypes.object
@@ -48,10 +48,10 @@
     if TutorRouter.currentQuery()?.courseId
       course = CourseStore.get(TutorRouter.currentQuery()?.courseId)
       NewCourseActions.setClone(course)
-      @setState({currentStage: 2})
+      @setState({firstStage: 2, currentStage: 2})
     else if isEmpty(CourseListingStore.filterTeachingCourses(is_concept_coach: true))
       NewCourseActions.set(course_type: 'tutor')
-      @setState({currentStage: 1})
+      @setState({firstStage: 2, currentStage: 1})

   mixins: [BindStoreMixin]
   bindStore: NewCourseStore
@@ -62,13 +62,13 @@
     stage = @state.currentStage
     while componentFor(stage + amt)?.shouldSkip?()
       stage += amt
+
     @setState({currentStage: stage + amt})

   BackButton: ->
-    return null if @state.currentStage is 0
-    <BS.Button onClick={@onBack} className="back"
-      disabled={@state.currentStage is 0}
-    >
+    return null if @state.currentStage is @state.firstStage
+    console.log @state.currentStage
+    <BS.Button onClick={@onBack} className="back">
       Back
     </BS.Button>

@@ -133,7 +133,7 @@
       header={<@Title />}
       className={wizardClasses}
       footer={<@Footer />}
-    >
+    >
       <div className='panel-content'>
         <OXFancyLoader isLoading={isLoading or isBuilding}/>
         {<Component/> unless isLoading}


referral: show modal in Account.kdapplication

--- client/landing/app/Applications/Account.kdapplication/views/referralsystem.coffee
+++ client/landing/app/Applications/Account.kdapplication/views/referralsystem.coffee
@@ -131,7 +131,7 @@
           """
       click       : ->
         appManager = KD.getSingleton "appManager"
-        appManager.tell "Account", "showReferrerTooltip",
+        appManager.tell "Account", "showReferrerModal",
           linkView    : getYourReferrerCode
           top         : 50
           left        : 35


CLEANUP - requires

CLEANUP - requires

--- source/Art/Engine/Forms/TextInput.coffee
+++ source/Art/Engine/Forms/TextInput.coffee
@@ -1,15 +1,15 @@
 # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input

+
+{defineModule, log, merge, select, inspect, wordsArray, timeout} = require 'art-standard-lib'
+{rgbColor} = require 'art-atomic'
+
 Foundation = require 'art-foundation'
-Atomic = require 'art-atomic'
+{createElementFromHtml} = Foundation.Browser.Dom
+{TextArea, Input} = Foundation.Browser.DomElementFactories
 SynchronizedDomOverlay = require "./SynchronizedDomOverlay"

-{rgbColor} = Atomic
-{createElementFromHtml} = Foundation.Browser.Dom
-{TextArea, Input} = Foundation.Browser.DomElementFactories
-{log, merge, select, inspect, createWithPostCreate, wordsArray, timeout} = Foundation
-
-module.exports = createWithPostCreate class TextInput extends SynchronizedDomOverlay
+defineModule module, class TextInput extends SynchronizedDomOverlay
   # options
   #   value:      ""
   #   color:      "black"


remove javascript processing

remove javascript processing

--- source/assets/javascripts/form.js.coffee
+++ source/assets/javascripts/form.js.coffee
@@ -1,12 +0,0 @@
-$('form button').on 'click', (e) ->
-  e.preventDefault()
-  thisform = $(this).closest('form')
-  $(this).attr('disabled', 'disabled').text 'Submitting'
-  d = thisform.serialize()
-  $.ajax
-    url: 'https://pixel-club-contact.herokuapp.com/'
-    type: 'POST'
-    data: d
-    dataType: 'jsonp'
-    complete: (d) ->
-      $(".contact__form-container").html("
Thank you. We'll be in touch shortly and welcome to the club.
")


Some code cleanup

Some code cleanup

--- client/list.coffee
+++ client/list.coffee
@@ -383,12 +383,12 @@

   data.form = context.form
-
-  # inlineForm stuff for a listRow
-  if context.formType is 'inline'
-    data.inlineForm = true
-  if context.formType?
-    data.inlineForm = true
+  data.inlineForm =
+    switch context.formType
+      when 'inline' then true
+      when 'full' then false
+      when 'link' then false
+      else true

   this.row = data
   this.data = data
@@ -442,7 +442,7 @@
       options = providedFields[key]
       # console.log options
       options.name = key
-      if Object.isFunction(options.transform)
+      if options.transform and Object.isFunction(options.transform)
         transform = options.transform
         options.value = transform(data[key])
       else


Unlimit rows per page.

Unlimit rows per page.

--- client/loot.coffee
+++ client/loot.coffee
@@ -18,6 +18,7 @@
     collection: Inventory.find {bag: this.bag, type: {$regex: Session.get "type"}}, {sort: order: 1}
     showNavigation: "never"
     showColumnToggles: true
+    rowsPerPage: Infinity
     fields: [
       key: "name"
       label: "Name"


codec_utils: Add getSampleRateFromFreqIndex()

--- codec_utils.coffee
+++ codec_utils.coffee
@@ -6,6 +6,24 @@
     hmac = crypto.createHmac 'sha256', key
     hmac.update data
     return hmac.digest()
+
+  getSampleRateFromFreqIndex: (freqIndex) ->
+    switch freqIndex
+      when 0x0 then 96000
+      when 0x1 then 88200
+      when 0x2 then 64000
+      when 0x3 then 48000
+      when 0x4 then 44100
+      when 0x5 then 32000
+      when 0x6 then 24000
+      when 0x7 then 22050
+      when 0x8 then 16000
+      when 0x9 then 12000
+      when 0xa then 11025
+      when 0xb then  8000
+      when 0xc then  7350
+      else
+        throw new Error "Unknown sampling_frequency_index: #{freqIndex}"

   # ISO 14496-3 - Table 1.16
   getSamplingFreqIndex: (sampleRate) ->


Update drop.coffee

Update drop.coffee

--- coffee/drop.coffee
+++ coffee/drop.coffee
@@ -35,7 +35,7 @@
   defaultOptions =
     classPrefix: 'drop'
     defaults:
-      attach: 'bottom left'
+      position: 'bottom left'
       openOn: 'click'
       constrainToScrollParent: true
       constrainToWindow: true


Draggable

Draggable

- When eating "click" after drag, be more careful. When
  the target of the "mouseup" is not in DOM anymore, do
  not kill the click. In that case the browser doesn't
  trigger it.

--- src/base/DragDropSelect/Draggable.coffee
+++ src/base/DragDropSelect/Draggable.coffee
@@ -303,10 +303,15 @@
 			if noClickKill
 				return

-			parents_now = CUI.DOM.parents(start_target)
-			for p, idx in start_target_parents
-				if parents_now[idx] != p
-					return
+			has_same_parents = =>
+				parents_now = CUI.DOM.parents(start_target)
+				for p, idx in start_target_parents
+					if parents_now[idx] != p
+						return false
+				return true
+
+			if not has_same_parents or not CUI.DOM.isInDOM(ev.getTarget())
+				return

 			Events.listen
 				type: "click"
@@ -314,7 +319,7 @@
 				only_once: true
 				node: window
 				call: (ev) ->
-					console.error "Killing click after drag"
+					console.error "Killing click after drag", ev.getTarget()
 					return ev.stop()

 			return


adjusted computation of children width to include margin

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -336,7 +336,7 @@
             o.el
                 .children()
                 .each ->
-                    totalWidth += ($ this).width()
+                    totalWidth += ($ this).outerWidth(true)

             return totalWidth


spec for uppercase JPG

spec for uppercase JPG

--- spec/imgflo-url.coffee
+++ spec/imgflo-url.coffee
@@ -1,6 +1,10 @@
 {expect} = require 'chai'
 imgflo = require '../index'

+getConfig = ->
+  server: 'https://imgflo.herokuapp.com/'
+  key: 'key'
+  secret: 'secret'

 describe 'imgflo-url', ->

@@ -152,6 +156,19 @@

           expect(url).to.equal "https://imgflo.herokuapp.com/graph/#{process.env.IMGFLO_KEY}/#{process.env.IMGFLO_TOKEN_03}/passthrough.jpg?input=https%3A%2F%2Fv.cdn.vine.co%2Fr%2Fvideos%2FB5B06468B91176403722801139712_342c9a1c624.1.5.15775156368984795444.mp4.jpg%3FversionId%3DedU_LrAtIFsGvZj.Fgi0Si1bem68tBlk"

+      context 'with a url with uppercase JPG extension', ->
+
+        it 'should produce the correct URL', ->
+          config = getConfig()
+
+          params =
+            input: 'http://1.bp.blogspot.com/-1h8jX1lfGJc/UnlTZ3Fsq6I/AAAAAAAAAOo/dw7IXnJBO5A/s1600/IMG_1722.JPG'
+
+          url = imgflo config, 'passthrough', params
+
+          expect(url).to.contain 'passthrough.jpg'
+          expect(url).to.equal 'https://imgflo.herokuapp.com/graph/key/e48c2b3866af0e91fb4b1ef4d5399b7d/passthrough.jpg?input=http%3A%2F%2F1.bp.blogspot.com%2F-1h8jX1lfGJc%2FUnlTZ3Fsq6I%2FAAAAAAAAAOo%2Fdw7IXnJBO5A%2Fs1600%2FIMG_1722.JPG'
+

     context 'specifying an image format', ->


Fix another test for decorations

Fix another test for decorations

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -259,9 +259,11 @@
         it "resets all labels even those that don't begin with a", ->
             atom.commands.dispatch textEditorElement, 'jumpy:a'
             atom.commands.dispatch textEditorElement, 'jumpy:reset'
-            expect(textEditorElement.shadowRoot
-                .querySelectorAll('.jumpy.label:not(.irrelevant)')
-                    .length).toBe NUM_TOTAL_WORDS + NUM_CAMEL_SPECIFIC_MATCHES
+            decorations = textEditor.getOverlayDecorations()
+            relevantDecorations = decorations.filter (d) ->
+                not d.getProperties().item.classList.contains 'irrelevant'
+            expect(relevantDecorations).toHaveLength NUM_TOTAL_WORDS +
+                NUM_CAMEL_SPECIFIC_MATCHES

     describe "when the a text selection has begun
     before a jumpy:toggle event is triggered", ->


test: amend tests for new beacon implementation

See #64

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -101,7 +101,7 @@
             expect(decorations[83].getProperties().item.textContent).toBe 'df'
         it "clears beacon effect", ->
             expect(textEditorElement.
-                querySelectorAll('cursors .cursor.beacon').length).toBe 0
+                querySelectorAll('span.beacon').length).toBe 0
         it "only uses jumpy keymaps", ->
             expect(atom.keymaps.keyBindings.length).toBe (26 * 2) + 5 + 1

@@ -140,12 +140,15 @@

     describe "when the jumpy:toggle event is triggered
     and hotkeys are entered", ->
-        it "jumpy is cleared", ->
+        it "jumpy is cleared", (done) ->
             atom.commands.dispatch workspaceElement, 'jumpy:a'
             atom.commands.dispatch workspaceElement, 'jumpy:c'
             expect(textEditorElement.classList
                 .contains('jumpy-jump-mode')).toBe false
-            expect(textEditor.getOverlayDecorations()).toHaveLength 0
+            setTimeout ->
+              expect(textEditor.getOverlayDecorations()).toHaveLength 0
+              done()
+            , 160

     describe "when the jumpy:toggle event is triggered
     and invalid hotkeys are entered", ->
@@ -205,7 +208,7 @@
         it "the beacon animation class is added", ->
             atom.commands.dispatch workspaceElement, 'jumpy:a'
             atom.commands.dispatch workspaceElement, 'jumpy:c'
-            expect(textEditorElement.shadowRoot
+            expect(textEditorElement
                 .querySelectorAll('.beacon').length)
                 .toBe 1
         it "the beacon animation class is removed", ->
@@ -214,7 +217,7 @@
                 ->
                     atom.commands.dispatch workspaceElement, 'jumpy:c'
             runs ->
-                expect(textEditorElement.shadowRoot
+                expect(textEditorElement
                     .querySelectorAll('.beacon').length)
                     .toBe 0


Restore another working test.

Restore another working test.

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -176,7 +176,7 @@
             expect(textEditorElement.querySelectorAll('.jumpy.label:not(.irrelevant)')
                 .length).toBe 26

-    xdescribe "when the jumpy:reset event is triggered", ->
+    describe "when the jumpy:reset event is triggered", ->
         it "clears first entered key and lets a new jump take place", ->
             atom.commands.dispatch textEditorElement, 'jumpy:a'
             atom.commands.dispatch textEditorElement, 'jumpy:reset'


Change on tests

Change on tests

--- spec/5_template.coffee
+++ spec/5_template.coffee
@@ -11,8 +11,11 @@
     spy = noop.spy

     class Template extends Atoms.Core.Class.Template
+      @attributes "title", "button"
       template: "

{{title}}

"
       title: "Hi Atoms!!!"
+      button:
+        icon: "profile"

     el = Atoms.$ "
"

@@ -35,5 +38,4 @@

   it "Templates are shielded only be assigned attributes that exist in the base", ->
     template = new Template parent: el
-
-
+    expect(template.constructor.elements).toEqual ["title", "button"]


Add visit test.

Add visit test.

--- test/e2e/collections.spec.coffee
+++ test/e2e/collections.spec.coffee
@@ -105,6 +105,8 @@
         expect(row.info, "The #{ row.name } collection #{ i }" +
                          " is missing an info button")
           .to.exist
+        expect(row.info.visit(), "The info link doesn't exist")
+          .to.eventually.exist

     xit 'should link to the Collection Detail page', ->
       for row, i in rows


fix tests

fix tests

--- test/factory/factory_test.coffee
+++ test/factory/factory_test.coffee
@@ -9,7 +9,8 @@

   subject = ->
     scheduler = new Rx.TestScheduler
-    observableFactory = Visualizer.buildObservable(uiValuator)(structure())
+    observableFactory = Visualizer.buildObservable(uiValuator, {})(structure())
+
     observable = observableFactory[targetId()](scheduler)
     results = scheduler.startWithCreate R.always(observable)
     results.messages
@@ -54,7 +55,7 @@
     context 'map', ->
       type.is -> 'map'
       rootValue.is -> '1,2,3'
-      opValue.is -> 'return value * value;'
+      opValue.is -> 'function(value) { return value * value }'

       it 'has correct values', ->
         expect(subject()).toEqual [
@@ -67,7 +68,7 @@
     context 'filter', ->
       type.is -> 'filter'
       rootValue.is -> '2,5,9,10'
-      opValue.is -> 'return value % 2 == 0'
+      opValue.is -> 'function(value) { return value % 2 == 0 }'

       it 'has correct values', ->
         expect(subject()).toEqual [
@@ -94,7 +95,7 @@
       rootValue.is -> '{50: 1, 90: 2, 100: 3, 201: 4, 205: 5}'
       opValue.is -> '100'

-      it.only 'has correct values', ->
+      it 'has correct values', ->
         expect(subject()).toEqual [
           onNext(300, [1,2])
           onNext(400, [3])
@@ -102,7 +103,31 @@
           onCompleted(405)
         ]

-    context 'flatMap', ->
-      type.is -> 'flatMap'
-      rootValue.is -> '1,2,3'
+    context.only 'flatMap', ->
+      structure.is ->
+        root:
+          type: 'of', id: 'rootId'
+        operators: [
+          {
+            type: 'flatMap'
+            id: 'flatMapId'
+            observable:
+              root:
+                type: 'of', id: 'secondRootId'
+              operators: [
+                { type: 'map', id: 'mapId' }
+              ]
+          }
+        ]

+      uiValues.is ->
+        rootId: '1,2,3'
+        flatMapId: 'function(topValue)'
+        secondRootId: "parseInt('1' + topValue)"
+        mapId: 'function(value) { return value * value }'
+
+      targetId.is -> 'mapId'
+
+      it 'has correct values', ->
+        console.log subject()
+


Test for $ne: null + another operator

Test for $ne: null + another operator

--- test/generators/flat/find.coffee
+++ test/generators/flat/find.coffee
@@ -240,6 +240,20 @@
                 assert.ok(_.isNull(models[0].get('created_at')), 'created_at null attribute')
                 done()

+    it 'Handles $ne with null and another value', (done) ->
+      Flat.findOne {$sort: '-created_at'}, (err, test_model) ->
+        assert.ok(!err, "No errors: #{err}")
+        assert.ok(!!test_model, 'test model found')
+        test_model.save {created_at: null}, (err) ->
+          assert.ok(!err, "No errors: #{err}")
+
+          Flat.find {created_at: {$ne: null, $gte: START_DATE}}, (err, models) ->
+            assert.ok(!err, "No errors: #{err}")
+            assert.equal(models.length, BASE_COUNT-1, 'all models but found null found')
+            for model in models
+              assert.ok(!_.isNull(model.get('created_at')), 'created_at null attribute')
+            done()
+
     it 'Handles $lt and $lte boundary conditions', (done) ->
       Flat.find {created_at: {$lt: START_DATE}}, (err, models) ->
         assert.ok(!err, "No errors: #{err}")
@@ -285,7 +299,6 @@
               for model in models
                 assert.ok(model.get('name') is NAME, 'matching name attribute')
               done()
-

     it 'Handles $lt and $lte with find not equal', (done) ->
       NAME = 'Bob'


Fix timing issue when doing external redirect.

--- test/integration/tool_rpc.coffee
+++ test/integration/tool_rpc.coffee
@@ -46,7 +46,8 @@
         browser.get @toolURL, ->
           wd40.switchToBottomFrame ->
             wd40.click '#redirectExternal', (err, btn) ->
-              wd40.switchToTopFrame done
+              wd40.switchToTopFrame ->
+                setTimeout done, 500

       it 'redirects the host to the specified URL', (done) ->
         wd40.trueURL (err, url) ->


jsontool re-pipe

jsontool re-pipe

--- test/mocha/ephemeris.spec.coffee
+++ test/mocha/ephemeris.spec.coffee
@@ -67,12 +67,12 @@
   describe 're-run with the extra [0]["re"]', ->
     results = []
     before (done) ->
-      beginning = "bin/precious.js f test/io/for/nativity.json
- | node_modules/jsontool/lib/jsontool.js"
+      jsontool = "node_modules/jsontool/lib/jsontool.js"
+      beginning = "bin/precious.js f test/io/for/nativity.json | #{jsontool}"
       mapExec [
         "#{beginning} -o json-0"
-        "#{beginning} 0.re | bin/precious.js -
- | node_modules/jsontool/lib/jsontool.js -o json-0"
+        "#{beginning} 0 | #{jsontool} re | bin/precious.js -
+        | #{jsontool} -o json-0"
         ], (err, stdouts) ->
           for result in stdouts
             results.push JSON.parse result


Removing redundant tags

Removing redundant tags

--- test/tests/utils/browsers.coffee
+++ test/tests/utils/browsers.coffee
@@ -52,7 +52,7 @@
 # ----------------------------------------------------------------------------
 phantomjs =
   browserName: 'phantomjs'
-  tags: ['ways-browser', 'phantomjs']
+  tags: ['phantomjs']

 # chrome
@@ -60,37 +60,37 @@
 chrome_winxp =
   browserName: 'chrome'
   platform: 'Windows XP'
-  tags: ['ways-browser', 'chrome', 'winxp']
+  tags: ['chrome', 'winxp']
   'record-video': true

 chrome_win7 =
   browserName: 'chrome'
   platform: 'Windows 7'
-  tags: ['ways-browser', 'chrome', 'win7']
+  tags: ['chrome', 'win7']
   'record-video': true

 chrome_win8 =
   browserName: 'chrome'
   platform: 'Windows 8'
-  tags: ['ways-browser', 'chrome', 'win8']
+  tags: ['chrome', 'win8']
   'record-video': true

 chrome_linux =
   browserName: 'chrome'
   platform: 'Linux'
-  tags: ['ways-browser', 'chrome', 'linux']
+  tags: ['chrome', 'linux']
   'record-video': true

 chrome_snow =
   browserName: 'chrome'
   platform: 'OS X 10.6'
-  tags: ['ways-browser', 'chrome', 'snow']
+  tags: ['chrome', 'snow']
   'record-video': true

 chrome_mountain =
   browserName: 'chrome'
   platform: 'OS X 10.8'
-  tags: ['ways-browser', 'chrome', 'mountain']
+  tags: ['chrome', 'mountain']
   'record-video': true

@@ -101,21 +101,21 @@
   version: '5'
   browserName: 'safari'
   platform: 'Windows 7'
-  tags: ['ways-browser', 'safari5', 'win7']
+  tags: ['safari5', 'win7']
   'record-video': true

 safari5_snow =
   version: '5'
   browserName: 'safari'
   platform: 'OS X 10.6'
-  tags: ['ways-browser', 'safari5', 'snow']
+  tags: ['safari5', 'snow']
   'record-video': true

 safari6_mountain =
   version: '6'
   browserName: 'safari'
   platform: 'OS X 10.8'
-  tags: ['ways-browser', 'safari6', 'mountain']
+  tags: ['safari6', 'mountain']
   'record-video': true

@@ -127,14 +127,14 @@
   version: '12'
   browserName: 'opera'
   platform: 'Linux'
-  tags: ['ways-browser', 'opera', 'linux']
+  tags: ['opera', 'linux']
   'record-video': true

 opera_win7 =
   version: '12'
   browserName: 'opera'
   platform: 'Windows 7'
-  tags: ['ways-browser', 'opera', 'win7']
+  tags: ['opera', 'win7']
   'record-video': true

@@ -145,41 +145,41 @@
   browserName: 'firefox'
   version: '22'
   platform: 'Windows XP'
-  tags: ['ways-browser', 'firefox', 'winxp']
+  tags: ['firefox', 'winxp']
   'record-video': true

 firefox_win7 =
   browserName: 'firefox'
   version: '22'
   platform: 'Windows 7'
-  tags: ['ways-browser', 'firefox', 'win7']
+  tags: ['firefox', 'win7']
   'record-video': true

 firefox_win8 =
   browserName: 'firefox'
   version: '22'
   platform: 'Windows 8'
-  tags: ['ways-browser', 'firefox', 'win8']
+  tags: ['firefox', 'win8']
   'record-video': true

 firefox_linux =
   browserName: 'firefox'
   platform: 'Linux'
-  tags: ['ways-browser', 'firefox', 'linux']
+  tags: ['firefox', 'linux']
   'record-video': true

 firefox_snow =
   browserName: 'firefox'
   version: '21'
   platform: 'OS X 10.6'
-  tags: ['ways-browser', 'firefox', 'snow']
+  tags: ['firefox', 'snow']
   'record-video': true

 firefox_mountain =
   browserName: 'firefox'
   version: '22'
   platform: 'OS X 10.8'
-  tags: ['ways-browser', 'firefox', 'snow']
+  tags: ['firefox', 'snow']
   'record-video': true

@@ -192,21 +192,21 @@
   browserName: 'internet explorer '
   version: '6'
   platform: 'Windows XP'
-  tags: ['ways-browser', 'ie6', 'winxp']
+  tags: ['ie6', 'winxp']
   'record-video': true

 ie7_winxp =
   browserName: 'internet explorer'
   version: '7'
   platform: 'Windows XP'
-  tags: ['ways-browser', 'ie7', 'winxp']
+  tags: ['ie7', 'winxp']
   'record-video': true

 ie8_winxp =
   browserName: 'internet explorer'
   version: '8'
   platform: 'Windows XP'
-  tags: ['ways-browser', 'ie8', 'winxp']
+  tags: ['ie8', 'winxp']
   'record-video': true

@@ -216,14 +216,14 @@
   browserName: 'internet explorer'
   version: '8'
   platform: 'Windows 7'
-  tags: ['ways-browser', 'ie8', 'win7']
+  tags: ['ie8', 'win7']
   'record-video': true

 ie9_win7 =
   browserName: 'internet explorer'
   version: '9'
   platform: 'Windows 7'
-  tags: ['ways-browser', 'ie9', 'win7']
+  tags: ['ie9', 'win7']
   'record-video': true

@@ -233,7 +233,7 @@
   browserName: 'internet explorer'
   version: '10'
   platform: 'Windows 8'
-  tags: ['ways-browser', 'ie10', 'win8']
+  tags: ['ie10', 'win8']
   'record-video': true

 # win 8.1
@@ -242,7 +242,7 @@
   browserName: 'internet explorer'
   version: '11'
   platform: 'Windows 8.1'
-  tags: ['ways-browser', 'ie11', 'win8.1']
+  tags: ['ie11', 'win8.1']
   'record-video': true

@@ -252,13 +252,13 @@
 ios_61_iphone =
   browserName: 'iphone'
   platform: 'OS X 10.8'
-  tags: ['ways-browser', 'ios', '6.1', 'iphone']
+  tags: ['ios', '6.1', 'iphone']
   'record-video': true

 ios_61_ipad =
   browserName: 'ipad'
   platform: 'OS X 10.8'
-  tags: ['ways-browser', 'ios', '6.1', 'ipad']
+  tags: ['ios', '6.1', 'ipad']
   'record-video': true

@@ -269,7 +269,7 @@
   browserName: 'android'
   version: '4.0'
   platform: 'Linux'
-  tags: ['ways-browser', 'android', '4.0', 'phone']
+  tags: ['android', '4.0', 'phone']
   'record-video': true

 android_40_tablet =
@@ -277,5 +277,5 @@
   version: '4.0'
   'device-type': 'tablet'
   platform: 'Linux'
-  tags: ['ways-browser', 'android', '4.0', 'tablet']
+  tags: ['android', '4.0', 'tablet']
   'record-video': true


adapt tests after changes

adapt tests after changes

--- tests/caches/bucket-cache.coffee
+++ tests/caches/bucket-cache.coffee
@@ -21,10 +21,18 @@
     cache.getBucketApproximate(oneDay, firstDay).should.deep.equal([0, false])

     # test set buckets
-    cache.setBucket(oneDay, firstDay, 4)
+    cache.setBucket(oneDay, firstDay, 5, 4)
     cache.hasBucket(oneDay, firstDay).should.be.true
-    cache.getBucket(oneDay, firstDay).should.equal(4)
-    cache.getBucketApproximate(oneDay, firstDay).should.deep.equal([4, true])
+    cache.getBucket(oneDay, firstDay).should.deep.equal({
+      "count": 4
+      "offset": 946684800000
+      "width": 5
+    })
+    cache.getBucketApproximate(oneDay, firstDay).should.deep.equal([{
+      "count": 4
+      "offset": 946684800000
+      "width": 5
+    }, true])

   it 'should allow to reserve buckets', ->
     cache = new BucketCache
@@ -35,7 +43,7 @@

   it 'should correctly approximate', ->
     cache = new BucketCache
-    cache.setBucket(twoDays, firstDay, 4)
+    cache.setBucket(twoDays, firstDay, 20, 4)
     cache.getBucketApproximate(oneDay, firstDay).should.deep.equal([2, false])
     cache.getBucketApproximate(oneDay, secondDay).should.deep.equal([2, false])
     cache.getBucketApproximate(oneDay, thirdDay).should.deep.equal([0, false])


Tests: Pricing: Payment: fixed upgradeAccount test.

--- tests/src/pricing/payment.coffee
+++ tests/src/pricing/payment.coffee
@@ -12,6 +12,7 @@
     browser
       .url                     helpers.getUrl() + '/Pricing'
       .waitForElementVisible   '.content-page.pricing', 20000
+      .waitForElementVisible   '.current', 20000
       .element 'css selector', freePlanSelector, (result) ->
         if result.status is 0
           helpers.selectPlan(browser, 'developer')
@@ -30,6 +31,7 @@
     browser
       .url                     helpers.getUrl() + '/Pricing'
       .waitForElementVisible   '.content-page.pricing', 20000
+      .waitForElementVisible   '.current', 20000
       .element 'css selector', selectedPlanSelector, (result) ->
         if result.status is 0
           browser.end()
@@ -40,8 +42,12 @@
             .waitForElementVisible   '.payment-modal', 20000
             .click                   '.payment-modal button'
             .waitForElementVisible   '.payment-modal', 20000
+            .pause                   5000
             .click                   '.payment-modal button'
-            .waitForElementVisible   '[testpath=main-sidebar]', 10000
+            .waitForElementVisible   '[testpath=main-sidebar]', 20000
+            .url                     helpers.getUrl() + '/Pricing'
+            .waitForElementVisible   '.content-page.pricing', 20000
+            .waitForElementVisible   '.single-plan.' + planType + '.current', 20000
             .end()


Use reload hack to clear periodic check after each test

--- tests/watch-web-resources.coffee
+++ tests/watch-web-resources.coffee
@@ -13,6 +13,10 @@
   beforeEach ->
     @room = helper.createRoom(httpd: false)
     process.env.HUBOT_WATCH_THRESHOLD = 90
+
+  afterEach ->
+    @room.user.say 'pchaigno', 'hubot: reload'
+

   context 'pchaigno wants to monitor a new web resource', ->
     beforeEach ->


fix small typo

fix small typo

--- src/businesscat.coffee
+++ src/businesscat.coffee
@@ -10,7 +10,7 @@
 # Commands:
 #   business jargon - summons business cat
 #
-# Notes
+# Notes:
 #   See jargon array for list of trigger phrases
 #
 # Author:


Fix removeQueryParams() to handle multiple parameters properly

--- src/code/client.coffee
+++ src/code/client.coffee
@@ -559,10 +559,10 @@
       re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "g")

       if re.test(url)
-        url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '')
-        if hash[1]?
-          url += '#' + hash[1]
-
+        hash[0] = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '')
+
+    url = hash[0] + if hash[1]? then '#' + hash[1] else ''
+
     if url isnt window.location.href
       history.pushState { originalUrl: window.location.href }, '', url