Test fixes

Test fixes

--- test/02-subtree.coffee
+++ test/02-subtree.coffee
@@ -59,7 +59,6 @@
 				getSubtree: -> {
 					foo: new Subcomponent()
 					bar: Subcomponent
-					baz: -> Subcomponent
 					quux: (state = {}, action) -> action?.payload or state
 					deep: {
 						zazz: Subcomponent
@@ -84,15 +83,10 @@
 			assert(not(Subcomponent instanceof Subcomponent))
 			assert(rootComponentInstance.bar instanceof Subcomponent)

-		it 'should hoist functional subtree descriptors', ->
-			assert(rootComponentInstance.baz instanceof Subcomponent)
-
 		it 'should scope actions and selectors', ->
 			store.dispatch(rootComponentInstance.foo.setValue('foo'))
 			store.dispatch(rootComponentInstance.bar.setValue('bar'))
-			store.dispatch(rootComponentInstance.baz.setValue('baz'))
 			store.dispatch(rootComponentInstance.deep.zazz.setValue('deep.zazz'))
 			expect(rootComponentInstance.foo.getValue()).to.equal('foo')
 			expect(rootComponentInstance.bar.getValue()).to.equal('bar')
-			expect(rootComponentInstance.baz.getValue()).to.equal('baz')
 			expect(rootComponentInstance.deep.zazz.getValue()).to.equal('deep.zazz')


Replacing heredoc in test with simple string

--- test/formatting.coffee
+++ test/formatting.coffee
@@ -119,7 +119,5 @@

 # Nested blocks caused by paren unwrapping
 test "#1492: Nested blocks don't cause double semicolons", ->
-  js = CoffeeScript.compile """
-    (0;0)
-    """
+  js = CoffeeScript.compile '(0;0)'
   eq -1, js.indexOf ';;'


Oscillator

Oscillator


Sort categories alphabetically

Sort categories alphabetically

--- resources/assets/coffee/react/_components/build.coffee
+++ resources/assets/coffee/react/_components/build.coffee
@@ -21,6 +21,9 @@

 class @Build extends React.PureComponent
   render: =>
+    entries = _.groupBy(@props.build.changelog_entries, 'category')
+    categories = _(entries).keys().sort().value()
+
     div className: "build t-changelog-stream--#{_.kebabCase @props.build.update_stream.display_name}",
       div className: 'build__version',
         @renderNav version: 'previous', icon: 'fas fa-chevron-left'
@@ -34,13 +37,13 @@

         @renderNav version: 'next', icon: 'fas fa-chevron-right'

-      for own category, entries of _.groupBy(@props.build.changelog_entries, 'category')
+      for category in categories
         div
           key: category
           className: 'build__changelog-entries-container'
           div className: 'build__changelog-entries-category', category
           div className: 'build__changelog-entries',
-            for entry in entries
+            for entry in entries[category]
               div
                 key: entry.id
                 className: 'build__changelog-entry'


Better recipients address interpretation

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

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


Context menu item: jump to pdf

Context menu item: jump to pdf

--- menus/latextools.cson
+++ menus/latextools.cson
@@ -2,8 +2,8 @@
 'context-menu':
   'atom-text-editor': [
     {
-      'label': 'Toggle latextools'
-      'command': 'latextools:toggle'
+      'label': 'Jump to PDF'
+      'command': 'latextools:jump-to-pdf'
     }
   ]
 'menu': [


[master] fix

[master] fix

--- merger/compact.coffee
+++ merger/compact.coffee
@@ -26,9 +26,9 @@
           deferred = Q.defer()
           loo = ->
             val = symbolKeys.pop()
-            logger.info 'processing',
-              key: val
             if val
+              logger.info 'processing',
+                key: val
               Q.ninvoke keyRedis, 'get', val
               .then (item) ->
                 payload = JSON.parse item


modal hide fixed

modal hide fixed

call to modal.loading doesn't create modal and call to modal.hide fails

--- sources/tuktuk.modal.coffee
+++ sources/tuktuk.modal.coffee
@@ -22,7 +22,7 @@
   ###
   hide = ->
     lock.removeClass("active").attr("data-loading", "false")
-    modal.removeClass "active"
+    modal?.removeClass "active"
     @

   ###


Managing slides subfolders

Managing slides subfolders

--- app/templates/_Gruntfile.coffee
+++ app/templates/_Gruntfile.coffee
@@ -10,8 +10,7 @@
                     livereload: true
                 files: [
                     'index.html'
-                    'slides/*.md'
-                    'slides/*.html'
+                    'slides/{,*/}*.{md,html}'
                     'js/*.js'<% if (config.get('useSass')) { %>
                     'css/*.css'<% } %>
                 ]


improve rsync task description

improve rsync task description

--- app/templates/_Gruntfile.coffee
+++ app/templates/_Gruntfile.coffee
@@ -135,7 +135,7 @@
 					dest: "src"
 					syncDestIgnoreExcl: true

-			# staging dry run down
+			# staging dry-run down
 			stagingdowndry:
 				options:
 					args: ["--dry-run", "--verbose"]
@@ -143,7 +143,7 @@
 					dest: "src/site/assets/files"
 					syncDestIgnoreExcl: true

-			# sync local with remote
+			# sync staging to local
 			stagingdown:
 				options:
 					src: "<%= pkg.domain.username %>@<%= pkg.domain.name %>:public_html/staging.<%= pkg.domain.name %>/site/assets/files/"


utils: Added formatBytesToHumanReadable method.

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -740,6 +740,18 @@
     request.error ({status, statusText, responseText})->
       error "url shorten error, returing self as fallback.", status, statusText, responseText
       callback url
+
+  formatBytesToHumanReadable: (bytes) ->
+    thresh    = 1024
+    units     = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
+    unitIndex = -1
+    return "#{bytes} B"  if bytes < thresh
+    loop
+      bytes /= thresh
+      ++unitIndex
+      break unless bytes >= thresh
+
+    return "#{bytes.toFixed 2} #{units[unitIndex]}"

   # deprecated ends


tests: teams: replace redis with postgresql since we removed redis

--- client/test/lib/teams/teams.coffee
+++ client/test/lib/teams/teams.coffee
@@ -80,7 +80,7 @@
     configurationSelector = "#{modalSelector} .configuration .server-configuration"
     inputSelector         = "#{configurationSelector} .Database"
     mysqlSelector         = "#{inputSelector} .mysql input.checkbox + label"
-    redisSelector         = "#{inputSelector} .redis input.checkbox + label"
+    postgresqlSelector    = "#{inputSelector} .postgresql input.checkbox + label"
     server1PageSelector   = "#{modalSelector} .code-setup .server-1"
     githubSelector        = "#{server1PageSelector} .box-wrapper .github"
     bitbucketSelector     = "#{server1PageSelector} .box-wrapper .bitbucket"
@@ -106,10 +106,10 @@
       .waitForElementVisible  mysqlSelector, 20000
       .click                  mysqlSelector
       .pause                  2000 # wait for animation
-      .waitForElementVisible  redisSelector, 20000
-      .click                  redisSelector
+      .waitForElementVisible  postgresqlSelector, 20000
+      .click                  postgresqlSelector
       .waitForElementVisible  stackPreview, 20000
-      .assert.containsText    codeSelector, 'mysql redis'
+      .assert.containsText    codeSelector, 'mysql postgresql'
       .waitForElementVisible  nextButtonSelector, 20000
       .pause                  2000 # wait for animation
       .click                  nextButtonSelector


Added renderSelectors. Fixed getContext pick function.

--- app/views/kinds/BaseView.coffee
+++ app/views/kinds/BaseView.coffee
@@ -62,7 +62,7 @@
     @subviews = {}

     # render to element
-    html = if _.isString(@template) then @template else @template(@getContext())
+    html = @getTemplateResult()
     @$el.html html

     # loading screen
@@ -71,13 +71,27 @@
     @afterRender()
     @

+  renderSelectors: (selectors...) ->
+    newTemplate = $(@getTemplateResult())
+
+    for selector in selectors
+      @$el.find(selector).replaceWith(newTemplate.find(selector))
+
+    @delegateEvents()
+
+  getTemplateResult: ->
+    if _.isString(@template)
+      return @template
+    else
+      return @template(@getContext())
+
   afterRender: ->

   getContext: (pick) ->
     context = {}
     context.pathname = document.location.pathname  # ex. '/play/level'
     context.moment = moment
-    context = _.extend @, _.pick(pick) if pick
+    context = _.extend context, _.pick(@, pick) if pick
     context

   afterInsert: ->


Don't require browser loader to be async

--- spec/ComponentLoader.coffee
+++ spec/ComponentLoader.coffee
@@ -77,7 +77,10 @@
       chai.expect(l.ready, 'should have been set ready').to.equal true
       chai.expect(ready, 'should have emitted ready').to.equal true
       done()
-    chai.expect(l.processing, 'should have started processing').to.equal true
+
+    unless noflo.isBrowser()
+      # Browser component registry can be synchronous
+      chai.expect(l.processing, 'should have started processing').to.equal true

   describe 'after listing components', ->
     it 'should have the Graph component registered', ->


Better error messages for tests

Better error messages for tests

--- spec/acceptance.spec.coffee
+++ spec/acceptance.spec.coffee
@@ -93,7 +93,7 @@
       $ = @browser.window.jQuery
       expect($("link[rel='stylesheet']").size()).toBeGreaterThan 0

-    it "We have valid CSS", ->
+    it "has valid CSS", ->
       $ = @browser.window.jQuery

       cssUrls = $("link[rel='stylesheet']").map -> this.href
@@ -102,10 +102,12 @@

       async.reduce cssUrls, "", (memo, path, cb) =>
         request @httpRoot + path, (err, res, body) ->
+          # console.log res.statusCode, path
+          expect(res.statusCode).toBe 200, "#{ path } is missing"
           return cb err if err
           cb null, memo + body
       , (err, css) ->
-        throw err if err
+        expect(err).toBeFalsy()

         # Just use csslint to validate that we have sane css here until we come
         # up with something better. This will at least confirm that


Fix trailing semicolon

Fix trailing semicolon

--- src/beautifiers/nginx-beautify.coffee
+++ src/beautifiers/nginx-beautify.coffee
@@ -13,7 +13,7 @@

     return new @Promise((resolve, reject) ->
       Beautify = require("nginxbeautify")
-      instance = new Beautify(options);
+      instance = new Beautify(options)
       try
         resolve(instance.parse(text))
       catch error


Fix 'getStep' test failure.

Fix 'getStep' test failure.

--- src/coffee/bootstrap-tour.spec.coffee
+++ src/coffee/bootstrap-tour.spec.coffee
@@ -209,6 +209,7 @@
       container: 'body'
       backdrop: false
       backdropPadding: 0
+      backdropContainer: 'body'
       redirect: true
       orphan: false
       duration: false


Corrige erro de ordenação de preços

Corrige erro de ordenação de preços

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -48,7 +48,7 @@

 	findPrices: (skus = undefined) =>
 		skus or= @findSelectableSkus()
-		$.map(skus, (sku) -> sku.bestPrice ).sort()
+		$.map(skus, (sku) -> sku.bestPrice).sort( (a,b) -> return parseInt(a) - parseInt(b) )

 	searchDimensions: (fn = ()->true) =>
 		$.grep(@dimensions, fn)


Whitespace

Whitespace

--- models/badge.coffee
+++ models/badge.coffee
@@ -3,13 +3,13 @@
 db = mongoose.createConnection 'mongodb://localhost:27017/badges'

 BadgeSchema = new Schema
-  name: String
-  created_at: Date
-  updated_at: Date
-  image: String
-  description: String
-  criteria: String
-  issuer: Schema.ObjectId
+  name:         String
+  created_at:   Date
+  updated_at:   Date
+  image:        String
+  description:  String
+  criteria:     String
+  issuer:       Schema.ObjectId

 # Define additional methods for the Badge class below like so:
 # BadgeSchema.methods.awesome = -> "awesome"


ItemList

ItemList

- Fixed logic for ItemList activation state management: Don't activate
  if non of the items set an "active" state.

  This bug was introduced by 8c17e5e3a8b37e214c643faa14ae1453eaf7c6db.

--- src/elements/ItemList/ItemList.coffee
+++ src/elements/ItemList/ItemList.coffee
@@ -98,12 +98,8 @@

 	__initActiveIdx: ->

-		active_idx = @_active_item_idx
-
 		if isUndef(active_idx)
 			items = @__getItems()
-
-			active_idx = null

 			for item, idx in items
 				if not item
@@ -114,6 +110,12 @@
 					active_idx = idx
 					break

+				# we only get here, if any
+				# of out items has an
+				# "active" property
+				if isUndef(active_idx)
+			        active_idx = null
+
 		if not isUndef(active_idx)
 			@__active_idx = active_idx
 			@__radio = "item-list--"+@getUniqueId()


[Gutter] Make the GutterContainerComponent able to create custom gutters

--- src/gutter-container-component.coffee
+++ src/gutter-container-component.coffee
@@ -1,3 +1,4 @@
+CustomGutterComponent = require './custom-gutter-component'
 LineNumberGutterComponent = require './line-number-gutter-component'

 # The GutterContainerComponent manages the GutterComponents of a particular
@@ -13,6 +14,7 @@

     @domNode = document.createElement('div')
     @domNode.classList.add('gutter-container')
+    @domNode.style.display = 'flex';

   getDomNode: ->
     @domNode
@@ -34,8 +36,7 @@
           gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, name: gutter.name})
           @lineNumberGutterComponent = gutterComponent
         else
-          # TODO (jessicalin) Implement non-line-number gutters.
-          continue
+          gutterComponent = new CustomGutterComponent({name: gutter.name})
       newGutterComponents.push(gutterComponent)
       newGutterComponentsByGutterName[gutter.name] = gutterComponent


dont autofocus url

dont autofocus url

--- src/layout/mixins/custom_level.coffee
+++ src/layout/mixins/custom_level.coffee
@@ -12,7 +12,10 @@
       tag_name: 'input'
       name:     changelog.url
       init:     (world, element) ->
-        $(element).on 'focus', -> $(this).select()
+        $(element).on 'click', (event) -> event.stopPropagation()
+        $(element).on 'focus', (event) ->
+          event.stopPropagation()
+          $(this).select()
     }
     {
       tag_name: 'button'
@@ -28,6 +31,7 @@
   {
     _EXPORTS: ['save', 'edit']
     save:     (world) ->
+      $(world.element).find('span').attr('autofocus', 'autofocus')
       dialog = $(world.element).dialog
         width:    400
         title:    'Custom Level'


Use relative require in spec

Use relative require in spec

--- spec/go-to-line-spec.coffee
+++ spec/go-to-line-spec.coffee
@@ -1,5 +1,5 @@
 RootView = require 'root-view'
-GoToLineView = require 'go-to-line/lib/go-to-line-view'
+GoToLineView = require '../lib/go-to-line-view'

 describe 'GoToLine', ->
   [goToLine, editor] = []


Fixed toHaveSchema() assertion

Fixed toHaveSchema() assertion

--- spec/jasmine-helpers.coffee
+++ spec/jasmine-helpers.coffee
@@ -2,6 +2,10 @@
   tv4 = require('tv4').freshApi()
   tv4.addSchema(id, schema) for id, schema of refs

-  result = tv4.validateResult(@actual, expected)
+  result = tv4.validateResult(@actual, expected, on)

-  not (result.error or result.missing.length) and result.valid
+  throw 'Missing ' + result.missing.join(', ') if result.missing.length
+
+  throw result.error if result.error
+
+  result.valid


Fix component editing.

Fix component editing.

In 82036681539fc1c536e957ee79beada6974426ef this code was changed and in 80ac7f802bc925673a2e1a17ef738e5570760280 the condition was removed.

Fixes issue #455

--- components/ContextToRuntime.coffee
+++ components/ContextToRuntime.coffee
@@ -95,14 +95,13 @@
     process: (event, payload) ->
       return unless event is 'data'
       return unless payload.runtime
-      if currentContext?.runtime
+      if currentContext?.runtime and currentContext.graphs[0] isnt payload.graphs[0]
+        currentContext.runtime.removeListener 'capabilities', sender
         if currentContext.runtime is payload.runtime
           # Same runtime, different graph. Reconnect to clear caches
-          currentContext.runtime.removeListener 'capabilities', sender
           currentContext.runtime.reconnect()
         else
           # Different runtime, different graph. Disconnect old runtime connection
-          currentContext.runtime.removeListener 'capabilities', sender
           currentContext.runtime.disconnect()

       # Prepare to send data


Remove log

Remove log

--- components/invest_cta/index.coffee
+++ components/invest_cta/index.coffee
@@ -10,7 +10,6 @@
   $el = $('.js-invest-cta')

   $el.one 'click', '.js-close', ->
-    console.log('should close')
     analytics.track.click 'Closed Invest CTA'
     dismisser.dismiss()
     $el.remove()


linear chart

linear chart

--- components/tick_chart/index.coffee
+++ components/tick_chart/index.coffee
@@ -5,14 +5,14 @@
   height = $container.height()
   margin = {top: 20, right: 20, bottom: 30, left: 50}

-  x = d3.time.scale().range [0, width - 52]
+  x = d3.scale.linear().range [0, width - 52]
   y = d3.scale.linear().range [height, 0]

   xAxis = d3.svg.axis().scale(x).orient("bottom")
   yAxis = d3.svg.axis().scale(y).orient("left")

   line = d3.svg.line()
-    .x((point) -> x(Date.parse(point.get('timestamp'))))
+    .x((point, index) -> x(index))
     .y((point) -> y(parseInt(point.get('close'))))

   svg = d3.select("#chart")
@@ -22,7 +22,7 @@
     .append("g")
     .attr("transform", "translate(#{margin.left}, #{margin.top})")

-  x.domain d3.extent chart.models, (point) -> Date.parse(point.get('timestamp'))
+  x.domain d3.extent chart.models, (point, index) -> index
   y.domain d3.extent chart.models, (point) -> parseInt(point.get('close'))

   svg.append("g")


maringa teste

maringa teste

--- app.coffee
+++ app.coffee
@@ -85,7 +85,7 @@

 app.get '/maringafm', (req, res) -> #
 	tagName = 'maringafm'
-	res.sendfile './public/one.html'
+	res.sendfile './public/maringafm.html'

 app.get '/add/:tagname', (req, res) -> #
 	console.log 'Notification for', req.params.tagname


add optional packages

add optional packages

--- app.coffee
+++ app.coffee
@@ -347,11 +347,11 @@
 										currentVersion.versionnumber = versionNumber
 										currentVersion.license = versionPackageXml.package.packageinformation[0].license?[0]
 										currentVersion.fromversions = (instruction.$.fromversion for instruction in versionPackageXml.package.instructions when instruction.$.fromversion?)
+										currentVersion.optionalpackages = versionPackageXml.package?.optionalpackages?[0]
 										currentVersion.requiredpackages = versionPackageXml.package?.requiredpackages?[0]
 										currentVersion.excludedpackages = versionPackageXml.package?.excludedpackages?[0]
 										currentVersion.timestamp = versionFileStat.mtime
 										currentPackage.versions[versionNumber] = currentVersion
-										# TODO: optionals

 										logger.log "debug", "Finished parsing #{versionFile}"
 										versionsCallback null
@@ -430,6 +430,15 @@
 					writer.startElement 'fromversions'
 					for fromVersion in version.fromversions
 						writer.writeElement 'fromversion', fromVersion
+					do writer.endElement
+
+				# write <optionalpackages>
+				if version.optionalpackages?.optionalpackage?.length
+					writer.startElement 'optionalpackages'
+					for optionalpackage in version.optionalpackages.optionalpackage
+						writer.startElement 'optionalpackage'
+						writer.text optionalpackage._ ? optionalpackage
+						do writer.endElement
 					do writer.endElement

 				# write <requiredpackages>


Added simple HTTP security headers

Added simple HTTP security headers

--- app.coffee
+++ app.coffee
@@ -16,7 +16,6 @@
 fs = require("fs")
 express = require("express")
 coffee = require('coffee-script')
-through = require("through")
 browserify = require("browserify")

@@ -39,11 +38,16 @@
 app.set("view engine", "jade")
 app.use(express.static(__dirname + "/static")) if env is "development"

+# load all resources only from current origin (but not its sub-domains)
+app.use (req, res, next) ->
+  res.setHeader("Content-Security-Policy", "default-src 'self'")
+  res.setHeader("X-Frame-Options", "sameorigin")
+  next()
+
 app.use "/", (req, res) ->
   res.render "play",
     title: "Play"

-### Middleware ###
 # catch 404 and forward to error handler
 app.use (req, res, next) ->
   err = new Error("Not Found")


Add warning for crappy handlers that do not properly call the callback

--- app.coffee
+++ app.coffee
@@ -74,11 +74,23 @@
 			do handlers.loadHandlers
 			api.sendMessage i18n.__("I'm here!"), yes, ->
 				api.recursiveFetchMessages (data) ->
+					completedUsers = no
+					completedMessages = no
+
 					async.each data.messages, (item, callback) ->
 						handlers.handleMessage item, callback
 					, (err) ->
+						completedMessages = yes
 						#console.log "Handled each message"
 					async.each data.users, (item, callback) ->
 						handlers.handleUser item, callback
 					, (err) ->
+						completedUsers = yes
 						#console.log "Handled each message"
+
+					setTimeout ->
+						unless completedMessages
+							winston.warn "Handling messages takes very long, there might be a bug in a handler!"
+						unless completedUsers
+							winston.warn "Handling users takes very long, there might be a bug in a handler!"
+					, 10e3


fix twilio crash

fix twilio crash

--- app.coffee
+++ app.coffee
@@ -1,7 +1,7 @@
 express = require 'express'
 http = require 'http'
 path = require 'path'
-twilio_client = require 'twilio'
+twilio = require 'twilio'
 nconf = require 'nconf'
 aws = require 'aws-sdk'

@@ -27,8 +27,6 @@
 config.twilio.accountSid = nconf.get 'TWILIO_ACCOUNT_SID'
 config.twilio.authToken = nconf.get 'TWILIO_AUTH_TOKEN'

-twilio = new twilio_client(config.twilio.accountSid, config.twilio.authToken)
-
 # Set up AWS
 config.sqs = {}
 config.sqs.apiVersion = nconf.get 'AWS_API_VERSION'
@@ -40,7 +38,7 @@
 sqs = new aws.SQS(config.sqs)

 app.post '/twiml', (res, req) ->
-  if twilio_client.validateExpressRequest req, config.twilio.authToken
+  if twilio.validateExpressRequest req, config.twilio.authToken
     # resp = new twilio.TwimlResponse()
     # resp.say 'Hello, Twilio.'


Fallback to the Google Geocoder if necessary.

--- content/scripts/application.coffee
+++ content/scripts/application.coffee
@@ -10,7 +10,7 @@
         url = "http://api.dribbble.com/shots/popular?callback=?"
     get_shots(url)
   )
-
+
   $("#list button").click ->
     $("#list button").removeClass("blue")
     $(this).addClass("blue")
@@ -20,7 +20,7 @@
     jQuery.history.load(list)

-initialize_map = (callback) ->
+initialize_map = (callback) ->
   latlng = new google.maps.LatLng(56, 9)

   map_options =
@@ -35,9 +35,10 @@
     scaleControl: true
     streetViewControl: false
     overviewMapControl: false
-
+
   window.map_overlays = []
-  window.google_map = new google.maps.Map(document.getElementById("map"), map_options);
+  window.google_map = new google.maps.Map(document.getElementById("map"), map_options)
+  window.google_geocoder = new google.maps.Geocoder()
   callback()

 get_shots = (url) ->
@@ -82,17 +83,23 @@
     lang: "en"
     username: "matias"
     type: "json"
-
+
   local_value = store.get(location)
-
+
   if local_value?
     success(new google.maps.LatLng(local_value[0], local_value[1]))
   else
     window.setTimeout(->
-      $.getJSON(url, geonames_options, (data) ->
+      $.getJSON(url, geonames_options, (data) ->
         if data["status"]?
-          if data["status"]["value"] in [18, 19, 20]
-            $("#error").text(data["status"]["message"]).slideDown()
+          google_geocoder.geocode(
+            address: location
+            , (results, status) ->
+              if status == google.maps.GeocoderStatus.OK
+                latlng = results[0].geometry.location
+                store.set(location, [latlng.lat(), latlng.lng()])
+                success(latlng)
+          )
         else
           $("#error").text("").slideUp()
           if data["geonames"].length > 0
@@ -103,4 +110,3 @@
       )
     , 200
     )
-


Expire connections after 5s trying to connect

--- packages/rocketchat-webrtc/WebRTCClass.coffee
+++ packages/rocketchat-webrtc/WebRTCClass.coffee
@@ -149,6 +149,8 @@
 		@transport.onRemoteDescription @onRemoteDescription.bind @
 		@transport.onRemoteStatus @onRemoteStatus.bind @

+		Meteor.setInterval @checkPeerConnections.bind(@), 1000
+
 		Meteor.setInterval @broadcastStatus.bind(@), 1000

 	log: ->
@@ -157,6 +159,11 @@

 	onError: ->
 		console.error.apply(console, arguments)
+
+	checkPeerConnections: ->
+		for id, peerConnection of @peerConnections
+			if peerConnection.iceConnectionState not in ['connected', 'completed'] and peerConnection.createdAt + 5000 < Date.now()
+				@stopPeerConnection id

 	updateRemoteItems: ->
 		items = []
@@ -243,6 +250,7 @@

 		peerConnection = new RTCPeerConnection @config

+		peerConnection.createdAt = Date.now()
 		peerConnection.remoteMedia = {}

 		@peerConnections[id] = peerConnection


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -2,6 +2,7 @@
 Returns a copy of the array without null and undefined values.

+
    [null, undefined, 3, 3, undefined, 5].compact()
 => [3, 3, 5]

@@ -19,6 +20,19 @@
 ###*
 Creates and returns a copy of the array. The copy contains
 the same objects.
+
+

+
+   a = ["a", "b", "c"]
+   b = a.copy()
+
+   a == b
+=> true
+
+   a === b
+=> false
+
+

 @name copy
 @methodOf Array#


See #733. No options are supported yet by dfmt

--- src/beautifiers/dfmt.coffee
+++ src/beautifiers/dfmt.coffee
@@ -8,7 +8,7 @@
   name: "dfmt"

   options: {
-    D: true
+    D: false
   }

   beautify: (text, language, options) ->


Add show, hide, and transition events to Component class.

--- src/common/Component.coffee
+++ src/common/Component.coffee
@@ -15,7 +15,7 @@
 TRANS_OUT_CLS = vars.read 'common/transitioning-out'

-makeTransitionFunc = (func, transitionClass, removeClass, test) ->
+makeTransitionFunc = (func, transitionClass, removeClass, event, test) ->
   (t = 0, cb) ->
     setTimeout =>
       if @rendered and test.call @
@@ -25,6 +25,7 @@
         @_transitionTimer = setTimeout =>
           @[func] cb
         , t
+        @fire event
     , 0
     @

@@ -105,8 +106,9 @@
   #
   # @constructor

-  constructor: ->
-    super arguments...
+  constructor: (template, model, opts, init, events = []) ->
+    super template, model, opts, init, events.concat [
+      'show', 'hide', 'transition:in', 'transition:out']

     @prefix = PREFIX

@@ -120,11 +122,13 @@
     @classes.add PREFIX + 'component'

-  transitionIn: makeTransitionFunc 'show', TRANS_IN_CLS, TRANS_OUT_CLS, ->
+  transitionIn: makeTransitionFunc 'show', TRANS_IN_CLS, TRANS_OUT_CLS,
+  'transition:in', ->
     @hidden or @transitioningOut

-  transitionOut: makeTransitionFunc 'hide', TRANS_OUT_CLS, TRANS_IN_CLS, ->
+  transitionOut: makeTransitionFunc 'hide', TRANS_OUT_CLS, TRANS_IN_CLS,
+  'transition:out', ->
     @visible or @transitioningIn

@@ -156,6 +160,7 @@
         @_stopTransition()
         @prefixedClasses.remove HIDDEN_CLS
         @prefixedClasses.add VISIBLE_CLS
+        @fire 'show'
         cb?.call null
     , 0
     @
@@ -180,6 +185,7 @@
         @_stopTransition()
         @prefixedClasses.remove VISIBLE_CLS
         @prefixedClasses.add HIDDEN_CLS
+        @fire 'hide'
         cb?.call null
     , 0
     @


Fix selection of unit in Mekko chart

Fix selection of unit in Mekko chart

Closes #1937

--- app/assets/javascripts/d3/mekko.coffee
+++ app/assets/javascripts/d3/mekko.coffee
@@ -79,7 +79,7 @@

     col_value_scaler: =>
       max_y_value = _.max(@sector_list.map((a) -> a.total_value()))
-      Quantity.scaleAndFormatBy(max_y_value, @model.get('unit').toUpperCase())
+      Quantity.scaleAndFormatBy(max_y_value, @model.get('unit'))

     draw: =>
       @prepare_data()


Some client side logic for opening and closing new organ dialog

--- app/assets/javascripts/pages.js.coffee
+++ app/assets/javascripts/pages.js.coffee
@@ -2,20 +2,26 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

+openNewOrganDialog = ->
+  $("#create-new-organ").fadeIn('fast')
+
+closeNewOrganDialog = ->
+  $("#create-new-organ").fadeOut('fast')
+
 index = ->

 newOrgan = ->
-   alert "new event"
+  openNewOrganDialog()

 $(document).ready ->
   $("select, input:checkbox, input:radio, input:file, input:text, textarea, submit").uniform()

   $("#sidebar a.create-new-organ").click () ->
-    $("#create-new-organ").fadeIn('fast')
+    router.setRoute '/organ/new'
     return false

   $("#create-new-organ a.close-modal").click () ->
-    $("#create-new-organ").fadeOut('fast')
+    router.setRoute '/'
     return false

   $("#single-organ a.application-call").click () ->
@@ -40,7 +46,9 @@

   router = Router(
     "" : index
-    "/organ/new" : newOrgan
+    "/organ/new" :
+      on:    newOrgan
+      after: closeNewOrganDialog
   )

   router.init()


Fixed missing draggable bug.

Fixed missing draggable bug.

Newly scheduled events are now draggable as well.

--- app/assets/javascripts/schedule.coffee
+++ app/assets/javascripts/schedule.coffee
@@ -30,6 +30,9 @@
       success: ->
         event.effect('highlight')
     )
+
+make_draggable = (element) ->
+  element.draggable(revert: "invalid", opacity: 0.4, cursorAt: {left: 5, top: 5})

 $ ->
   $("body").delegate("div.event", "mouseenter", ->
@@ -94,6 +97,7 @@
       new_event.data("update-url", li.data("update-url"))
       $("#event-pane").append(new_event)
       add_event_to_slot(new_event, td)
+      make_draggable(new_event)
       li.remove()
       $("#add-event-modal").modal('hide')
       click_event.preventDefault()
@@ -105,5 +109,5 @@
     if $(event).data("room") and $(event).data("time")
       starting_cell = $("table[data-room='" + $(event).data("room") + "']").find("td[data-time='" + $(event).data("time") + "']")
       add_event_to_slot(event, starting_cell, false)
-    $(event).draggable(revert: "invalid", opacity: 0.4, cursorAt: {left: 5, top: 5})
+    make_draggable($(event))


Reutilize existing method to update param value

--- app/assets/javascripts/todos.js.coffee
+++ app/assets/javascripts/todos.js.coffee
@@ -93,20 +93,11 @@
     if newPages isnt currPages
       # Redirect to previous page if there's one available
       if currPages > 1 and currPage is currPages
-        url = @updateQueryStringParameter(url, 'page', currPages - 1)
+        pageParams =
+          page: currPages - 1
+        url = gl.utils.mergeUrlParams(pageParams, url)

       location.replace url
-
-  updateQueryStringParameter: (uri, key, value) ->
-    separator = if uri.indexOf('?') isnt -1 then '&' else '?'
-
-    # Matches key and value
-    regex = new RegExp('([?&])' + key + '=.*?(&|#|$)', 'i')
-
-    if uri.match(regex)
-      return uri.replace(regex, '$1' + key + '=' + value + '$2')
-
-    uri + separator + key + '=' + value

   goToTodoUrl: (e)->
     todoLink = $(this).data('url')


Changed coffescript to toggle on click of username

--- app/assets/javascripts/users.js.coffee
+++ app/assets/javascripts/users.js.coffee
@@ -16,5 +16,8 @@
 # adds bootstrap popovers to user names
 $ ->
   $(".comment-user-name").popover
+    trigger: "manual"
     placement: "top"
-    delay: { show: 50, hide: 1500 }
+  $(".comment-user-name").click((event, ui) ->
+    $(".comment-user-name").popover('toggle')
+  )


Support logging correctly when msg is mutated

--- app/assets/javascripts/utils.js.coffee
+++ app/assets/javascripts/utils.js.coffee
@@ -39,5 +39,5 @@
   debug: (description, obj) ->
     if ETahi.environment == 'development'
       console.groupCollapsed(description)
-      console.log(obj)
+      console.log(Em.copy(obj, true))
       console.groupEnd()


handle disconnection/reconnecting better

--- client/front.coffee
+++ client/front.coffee
@@ -578,14 +578,22 @@
 class Connection

     constructor: ->
+        @lastPong = Date.now()
+        afterInterval 1000, =>
+            lastPongTime = Date.now() - @lastPong
+            if lastPongTime > 10 * 1000 or @ws.readyState == 3
+                print "no pongs, trying to reconnect", lastPongTime
+                @ws.close()
+                @reconnect()
+            else
+                print "last pong", lastPongTime
+                @ws.safeSend("ping", {})
+        @reconnect()
+
+    reconnect: ->
+        @lastPong = Date.now()
         host = window.document.location.host.replace(/:.*/, '')
         @ws = new WebSocket 'ws://' + location.hostname + ":" + 1977
-
-        afterInterval 1000, =>
-            @ws.safeSend("ping", {})
-            if @lastPong < Date.now() - 30 * 1000
-                print "no pongs"
-                @ws.close()

         @ws.safeSend = (msg, kargs) =>
             print "sending", msg, kargs, @ws.readyState, WebSocket.OPEN
@@ -604,7 +612,7 @@
             @connectionError()
             #editor.auth.think()

-        @ws.onmessage = (e) ->
+        @ws.onmessage = (e) =>
             packet = JSON.parse(e.data)
             msg = packet.msg
             kargs = packet.kargs
@@ -613,6 +621,7 @@
             switch msg
                 when 'pong'
                     @lastPong = Date.now()
+                    print "got pong"
                 when 'open-push'
                     editor.open_cmd.open_push(kargs)
                 when 'loggedin'


Work around preventing submit handler

Work around preventing submit handler

--- app/classifier/tasks/survey/index.cjsx
+++ app/classifier/tasks/survey/index.cjsx
@@ -51,11 +51,12 @@
     </div>

   handleFilter: (characteristicID, valueID) ->
-    if valueID?
-      @state.filters[characteristicID] = valueID
-    else
-      delete @state.filters[characteristicID]
-    @setState filters: @state.filters
+    setTimeout =>
+      if valueID?
+        @state.filters[characteristicID] = valueID
+      else
+        delete @state.filters[characteristicID]
+      @setState filters: @state.filters

   handleChoice: (choiceID) ->
     @props.annotation._choiceInProgress = true


bind to load event

bind to load event

--- app/javascript/lazy_load_images.coffee
+++ app/javascript/lazy_load_images.coffee
@@ -34,7 +34,7 @@
   noscript_wrapped_images = document.querySelectorAll(ELEMENTS_QUERY)
   noscript_wrapped_images.forEach(lazy_load_image_in_noscript_wrapper)

-document.addEventListener "ready", ->
+document.addEventListener "load", ->
   lazy_load_images()

 document.addEventListener "turbolinks:load", ->


reset keepUpdating flag as soon as possible to avoid race condition.

It was possible for two updaters to run because the 15 second timeout runs
before the flag is set to false.

--- scripts/gh-book/remote-updater.coffee
+++ scripts/gh-book/remote-updater.coffee
@@ -34,10 +34,10 @@
       return @_runningPromise

     stop: () ->
+      @keepUpdating = false
       promise = @_runningPromise or (new $.Deferred()).resolve(@)

       return promise.always () =>
-        @keepUpdating = false
         @lastSeenSha = null


Update link_bank_operation.coffee

Update link_bank_operation.coffee

Fixes https://forum.cozy.io/t/lien-pdf-dans-kresus-nok/1392/10

--- server/lib/link_bank_operation.coffee
+++ server/lib/link_bank_operation.coffee
@@ -81,7 +81,7 @@
     # operation.
     linkOperation: (operation, entry, callback) =>
         date = new Date entry.date
-        key = "#{moment(date).format "YYYY-MM-DDT00:00:00.000"}Z"
+        key = "#{moment(date)}"

         @model.request 'byDate', key: key, (err, entries) =>


Do not set outcome on informatics

Do not set outcome on informatics

--- server/testSystems/Informatics.coffee
+++ server/testSystems/Informatics.coffee
@@ -117,6 +117,7 @@
         "#{BASE_URL}/moodle/mod/statements/view3.php?" + "chapterid=#{id}&submit&user_id=#{userId}"

     setOutcome: (submitId, outcome, comment) ->
+        throw "Will not set outcome on Informatice"
         adminUser = await @_getAdmin()
         [fullSubmitId, contest, run, problem] = submitId.match(/(\d+)r(\d+)p(\d+)/)
         outcomeCode = switch outcome


Log client side errors as warns so they don't show in Sentry

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -232,7 +232,7 @@
 			res.send()

 		webRouter.post '/error/client', (req, res, next) ->
-			logger.error err: req.body.error, meta: req.body.meta, "client side error"
+			logger.warn err: req.body.error, meta: req.body.meta, "client side error"
 			res.sendStatus(204)

 		webRouter.get '*', ErrorController.notFound


move this elsewhere

move this elsewhere

--- bad.coffee
+++ bad.coffee
@@ -1,11 +0,0 @@
-# good
-x = if y then 1 else 2
-# bad
-x = if y then
-  1 else 2
-
-# good
-x = 1 if y
-# bad
-x = 1
-  if y


Fix confirm dialog spec

Fix confirm dialog spec

--- spec/git-repository-async-spec.coffee
+++ spec/git-repository-async-spec.coffee
@@ -215,7 +215,7 @@
       runs ->
         expect(statusHandler.callCount).toBe 1

-  fdescribe ".checkoutHeadForEditor(editor)", ->
+  describe ".checkoutHeadForEditor(editor)", ->
     [filePath, editor] = []

     beforeEach ->
@@ -234,9 +234,10 @@
       spyOn(atom, 'confirm').andCallFake ({buttons}) -> buttons.OK()
       atom.config.set('editor.confirmCheckoutHeadRevision', true)

-      repo.checkoutHeadForEditor(editor)
-
-      expect(fs.readFileSync(filePath, 'utf8')).toBe ''
+      waitsForPromise ->
+        repo.checkoutHeadForEditor(editor)
+      runs ->
+        expect(fs.readFileSync(filePath, 'utf8')).toBe ''

     it "does not display a dialog when confirmation is disabled", ->
       spyOn(atom, 'confirm')


Update "No Results Found" response, add future support for random responses

--- src/hubot-confluence.coffee
+++ src/hubot-confluence.coffee
@@ -9,6 +9,10 @@
 nconf.argv()
     .env()
     .file('defaults', DEFAULTS_FILE)
+
+noResultsFound = [
+  'I have no idea'
+]

 sanity_check_args = (msg) ->
   required_args = [
@@ -58,7 +62,8 @@
         search(msg, query, true)
         return
       else
-        msg.reply "No results found"
+        #msg.reply "No results found"
+        msg.reply msg.random noResultsFound
         return

     count = 0


working...

working...

--- views/coffeescript/neo4j.coffee
+++ views/coffeescript/neo4j.coffee
@@ -9,7 +9,7 @@
       for relData in data.rels
         if relData.end_node == node.id || relData.start_node == node.id
           rel = new Relationship(relData.id, relData.data.rel_type, relData.start_node, relData.end_node)
-          @rels.push(rel)
+          @relationships.push(rel)
           if relData.end_node == node.id then node.addIncoming(rel) else node.addOutgoing(rel)

   nodes: ->
@@ -20,6 +20,7 @@

   load: (nodeId) ->
     node for node in @nodes when node.id == nodeId
+    return node

 class Node

@@ -53,3 +54,6 @@
     throw "not implemented"

+root = exports ? this
+root.Graph = Graph
+


added support to view jobs/folders of a server

--- src/jenkins-enhanced.coffee
+++ src/jenkins-enhanced.coffee
@@ -67,21 +67,61 @@
   url: null
   auth: null
   _hasListed: false
-  _jobs: null
+  _rootFolder: null
   _querystring: null

   constructor: (url, auth) ->
     @url = url
     @auth = auth
-    @_jobs = []
     @_querystring = require 'querystring'

   hasInitialized: ->
     @_hasListed

+  setFolder: (folder) =>
+    @_hasListed = true
+    @_rootFolder = folder
+
+  getFolder: =>
+    @_rootFolder
+
+  hasFolder: =>
+    true if @_rootFolder
+
+  getFolderByName: (folderName) =>
+    @_rootFolder.getFolderByName(folderName)
+
+  hasFolderByName: (folderName) =>
+    @_rootFolder.hasFolderByName(folderName)
+
+  getJobByName: (jobName) =>
+    @_rootFolder.getJobByName(jobName)
+
+  hasFolderByName: (jobName) =>
+    @_rootFolder.hasJobByName(jobName)
+
+class JenkinsFolder
+  name: null
+  path: null
+  depth: null
+  _jobs: null
+  _folders: null
+  _querystring: null
+
+  constructor: (name, path, depth) ->
+    @name = name
+    @path = path
+    @depth = depth
+    @_jobs = []
+    @_folders = []
+    @_querystring = require 'querystring'
+
+  hasInitialized: ->
+    @_hasListed
+
   addJob: (job) =>
     @_hasListed = true
-    @_jobs.push job if not @hasJobByName job.name
+    @_jobs.push job if not @hasJobByName(job.name, false)

   getJobs: =>
     @_jobs
@@ -89,9 +129,52 @@
   hasJobs: =>
     @_jobs.length > 0

-  hasJobByName: (jobName) =>
+  getJobByName: (jobName, recursive=true) =>
     jobName = @_querystring.unescape(jobName).trim()
-    @_jobs.where(name: jobName).length > 0
+    job = @_jobs.where(name: jobName)
+    return job if job.length > 0
+    # otherwise we must start searching the other folders
+    if (recursive)
+      for folder in @_folders
+        job = folder.getJobByName(jobName)
+        return job if job.length > 0
+    null
+
+  hasJobByName: (jobName, recursive=true) =>
+    @getJobByName(jobName, recursive) != null
+
+  addFolder: (folder) =>
+    @_hasListed = true
+    @_folders.push folder if not @hasFolderByName(folder.name, false)
+
+  getFolders: =>
+    @_folders.sort((a,b) => true if (a.name > b.name))
+
+  hasFolders: =>
+    @_folders.length > 0
+
+  getFolderByName: (folderName, recursive=true) =>
+    folderName = @_querystring.unescape(folderName).trim()
+    folder = @_folders.where(name: folderName)
+    return folder if folder.length > 0
+    if (recursive)
+      for folder in @_folders
+        folder = folder.getFolderByName(folderName)
+        return folder if folder.length > 0
+    null
+
+  hasFolderByName: (folderName, recursive=true) =>
+    @getFolderByName(folderName, recursive) != null
+
+class JenkinsJob
+  name: null
+  path: null
+  state: null
+
+  constructor: (name, path, state) ->
+    @name = name
+    @path = path
+    @state = state

 class JenkinsServerManager extends HubotMessenger
@@ -104,7 +187,8 @@
   getServerByJobName: (jobName) =>
     @send "ERROR: Make sure to run a 'list' to update the job cache" if not @serversHaveJobs()
     for server in @_servers
-      return server if server.hasJobByName(jobName)
+      for folder in server.getFolders()
+        return server if folder.hasJobByName(jobName)
     null

   hasInitialized: =>
@@ -117,7 +201,8 @@

   serversHaveJobs: =>
     for server in @_servers
-      return true if server.hasJobs()
+      for folder in server.getFolders()
+        return true if folder.hasJobs()
     false

   servers: =>
@@ -150,12 +235,11 @@
   _serverManager: null
   _querystring: null
   # stores jobs, across all servers, in flat list to support 'buildById'
+  _folderList: []
   _jobList: []
   _params: null
   # stores a function to be called after the initial 'list' has completed
   _delayedFunction: null
-
-
   # Init
   # ----

@@ -196,14 +280,14 @@

   build: (buildWithEmptyParameters) =>
     return if not @_init(@build)
-    job = @_getJob(true)
+    job = @_getJob()
     server = @_serverManager.getServerByJobName(job)
     command = if buildWithEmptyParameters then "buildWithParameters" else "build"
     path = if @_params then "job/#{job}/buildWithParameters?#{@_params}" else "job/#{job}/#{command}"
     if !server
       @msg.send "I couldn't find any servers with a job called #{@_getJob()}.  Try `jenkins servers` to get a list."
       return
-    @_requestFactorySingle server, path, @_handleBuild, "post"
+    @_requestFactorySingle server, null, path, @_handleBuild, "post"

   describeById: =>
     return if not @_init(@describeById)
@@ -216,12 +300,13 @@

   describe: =>
     return if not @_init(@describe)
-    job = @_getJob(true)
+    job = @_getJob()
+    console.log(job)
     server = @_serverManager.getServerByJobName(job)
     if !server
       @msg.send "I couldn't find any servers with a job called #{@_getJob()}.  Try `jenkins servers` to get a list."
       return
-    @_requestFactorySingle server, "job/#{job}/api/json", @_handleDescribe
+    @_requestFactorySingle server, null, "#{job.path}/api/json", @_handleDescribe

   getAlias: =>
     aliases    = @_getSavedAliases()
@@ -246,9 +331,11 @@
     if !server
       @msg.send "I couldn't find any servers with a job called #{@_getJob()}.  Try `jenkins servers` to get a list."
       return
-    @_requestFactorySingle server, path, @_handleLast
+    @_requestFactorySingle server, null, path, @_handleLast

   list: (isInit = false) =>
+    @_requestsRunning = 0
+    @_jobList = []
     @_requestFactory "api/json", if isInit then @_handleListInit else @_handleList

   listAliases: =>
@@ -292,30 +379,51 @@
   # Utility Methods
   # ---------------

-  _addJobsToJobsList: (jobs, server, outputStatus = false) =>
+  _makeRootFolderForServer: (items, server) =>
     response = ""
-    filter = new RegExp(@msg.match[2], 'i')
-
-    for job in jobs
-      # Add the job to the @_jobList
-      server.addJob(job)
-      index = @_jobList.indexOf(job.name)
-      if index == -1
-        @_jobList.push job.name
-        index = @_jobList.indexOf(job.name)
-
-      state = if job.color == "red" then "FAIL" else "PASS"
-      if filter.test job.name
-        response += "[#{index + 1}] #{state} #{job.name} on #{server.url}\n"
-
-    @send response if outputStatus
+    @_requestsRunning = 1
+    # make the default/root level folder
+    rootFolder = server.getFolder()
+    if rootFolder == null
+      rootFolder = new JenkinsFolder("Root", "", -1)
+      server.setFolder(rootFolder)
+
+    @_addJobsToFoldersList(items, server, rootFolder)
+
+  _addJobsToFoldersList: (items, server, folder) =>
+    for item in items
+      itemType = item._class
+      if (itemType == 'com.cloudbees.hudson.plugins.folder.Folder')
+        newFolder = new JenkinsFolder(item.name, folder.path+"/job/"+@_querystring.escape(item.name), folder.depth+1)
+        folder.addFolder(newFolder)
+        @_requestsRunning++
+        @_requestFactorySingle server, newFolder, "#{newFolder.path}/api/json", @_handleNewFolder
+      else
+        newJob = new JenkinsJob(item.name, folder.path+""+@_querystring.escape(item.name), if item.color == "red" then "FAIL" else "PASS")
+        folder.addJob(newJob)
+    # count our current folder as processed
+    @_requestsRunning--
+    @_listJobs(server, server.getFolder(), true) if @_requestsRunning == 0
+
+  _listJobs: (server, folder, isRoot=false) =>
+    response = ""
+    if isRoot
+      response += "Server: #{server.url}\n"
+    # go through each folder and list jobs first then other folders, sorted alphabetically for consistency
+    for job in folder.getJobs()
+      @_jobList.push(job) if @_jobList.indexOf(job) == -1
+      response += "\t".repeat(folder.depth+2)+"[#{@_jobList.indexOf(job) + 1}] #{job.state} #{job.name}\n"
+    for subFolder in folder.getFolders()
+      response += "\t".repeat(subFolder.depth+1)+"Folder: #{subFolder.name}\n" if subFolder.name != ""
+      response += @_listJobs(server, subFolder)
+    if isRoot and @_outputStatus
+      @send response
+    else
+      return response

   _configureRequest: (request, server = null) =>
     defaultAuth = process.env.HUBOT_JENKINS_AUTH
     return if not server and not defaultAuth
-    selectedAuth = if server then server.auth else defaultAuth
-    #auth = new Buffer(selectedAuth).toString('base64')
-    #request.headers Authorization: "Basic #{auth}"
     request.header('Content-Length', 0)
     request

@@ -374,20 +482,21 @@
     job = @_getJob()
     server = @_serverManager.getServerByJobName(job)
     path = "job/#{job}/#{lastBuild.number}/api/json"
-    @_requestFactorySingle server, path, @_handleLastBuildStatus
-
-  _requestFactorySingle: (server, endpoint, callback, method = "get") =>
+    @_requestFactorySingle server, null, path, @_handleLastBuildStatus
+
+  _requestFactorySingle: (server, folder, endpoint, callback, method = "get") =>
     user = server.auth.split(":")
     if server.url.indexOf('https') == 0 then http = 'https://' else http = 'http://'
     url = server.url.replace /^https?:\/\//, ''
     path = "#{http}#{user[0]}:#{user[1]}@#{url}/#{endpoint}"
+    console.log(path)
     request = @msg.http(path)
     @_configureRequest request, server
-    request[method]() ((err, res, body) -> callback(err, res, body, server))
+    request[method]() ((err, res, body) -> callback(err, res, body, server, folder))

   _requestFactory: (endpoint, callback, method = "get") =>
     for server in @_serverManager.listServers()
-      @_requestFactorySingle server, endpoint, callback, method
+      @_requestFactorySingle server, server.getFolder(), endpoint, callback, method

   _setJob: (job) =>
     @msg.match[1] = job
@@ -395,8 +504,19 @@

   # Handlers
   # --------
-
-  _handleBuild: (err, res, body, server) =>
+  _handleNewFolder: (err, res, body, server, folder) =>
+    if err
+      @send err
+      return
+
+    try
+      content = JSON.parse(body)
+      @_addJobsToFoldersList content.jobs, server, folder
+      @_initComplete() if @_serverManager.hasInitialized()
+    catch error
+      @send error
+
+  _handleBuild: (err, res, body, server, folder) =>
     if err
       @reply err
     else if 200 <= res.statusCode < 400 # Or, not an error code.
@@ -408,7 +528,7 @@
     else
       @reply "Status #{res.statusCode} #{body}"

-  _handleDescribe: (err, res, body, server) =>
+  _handleDescribe: (err, res, body, server, folder) =>
     if err
       @send err
       return
@@ -422,7 +542,7 @@
     catch error
       @send error

-  _handleLast: (err, res, body, server) =>
+  _handleLast: (err, res, body, server, folder) =>
     if err
       @send err
       return
@@ -438,7 +558,7 @@
     catch error
       @send error

-  _handleLastBuildStatus: (err, res, body, server) =>
+  _handleLastBuildStatus: (err, res, body, server, folder) =>
     if err
       @send err
       return
@@ -454,10 +574,10 @@
     catch error
       @send error

-  _handleList: (err, res, body, server) =>
+  _handleList: (err, res, body, server, folder) =>
     @_processListResult err, res, body, server

-  _handleListInit: (err, res, body, server) =>
+  _handleListInit: (err, res, body, server, folder) =>
     @_processListResult err, res, body, server, false

   _processListResult: (err, res, body, server, print = true) =>
@@ -467,7 +587,8 @@

     try
       content = JSON.parse(body)
-      @_addJobsToJobsList content.jobs, server, print
+      @_outputStatus = print
+      @_makeRootFolderForServer content.jobs, server
       @_initComplete() if @_serverManager.hasInitialized()
     catch error
       @send error


Issue #1: Updated 'build' task's output to unescape job name

--- src/jenkins-enhanced.coffee
+++ src/jenkins-enhanced.coffee
@@ -316,8 +316,9 @@
     if err
       @reply err
     else if 200 <= res.statusCode < 400 # Or, not an error code.
-      job = @_getJob(true)
-      @reply "(#{res.statusCode}) Build started for #{job} #{server.url}/job/#{job}"
+      job     = @_getJob(true)
+      jobName = @_getJob(false)
+      @reply "(#{res.statusCode}) Build started for #{jobName} #{server.url}/job/#{job}"
     else if 400 == res.statusCode
       @build true
     else


AppController: Added makeReadOnly/makeEditable methods.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1761,3 +1761,23 @@
                 title    : "@#{@collaborationHost} has left the session."
                 duration : 3000
               KD.singletons.router.handleRoute '/IDE'
+
+
+  makeReadOnly: ->
+
+    return  if @isReadOnly
+
+    @isReadOnly = yes
+    @forEachSubViewInIDEViews_ (pane) -> pane.makeReadOnly()
+    @finderPane.makeReadOnly()
+    @getView.setClass 'read-only'
+
+
+  makeEditable: ->
+
+    return  unless @isReadOnly
+
+    @isReadOnly = no
+    @forEachSubViewInIDEViews_ (pane) -> pane.makeEditable()
+    @finderPane.makeEditable()
+    @getView.unsetClass 'read-only'


Update change when an output is removed

Update change when an output is removed

--- client/tx/lib/tx-builder.coffee
+++ client/tx/lib/tx-builder.coffee
@@ -24,7 +24,9 @@
     addresses.append addr_tmpl.clone()

   # Delete address
-  el.on 'click', '.del-address', -> $(this).closest('.address').remove()
+  el.on 'click', '.del-address', ->
+    $(this).closest('.address').remove()
+    do update_change

   # Address dropdown items
   el.on 'click', '[data-address]', ->


Uninstall editor before changing attributes

--- src/trix/attachment_controller.coffee
+++ src/trix/attachment_controller.coffee
@@ -39,9 +39,11 @@
     @element.removeEventListener("mousemove", @didMoveMouseToResize)
     document.removeEventListener("mouseup", @didMouseUpToEndResize)

-    @delegate?.attachmentControllerDidChangeAttributesAtPosition(getDimensions(@resizing.image), @resizing.image.trixPosition)
-    uninstallImageEditor(@resizing.image)
+    {image} = @resizing
     delete @resizing
+
+    uninstallImageEditor(image)
+    @delegate?.attachmentControllerDidChangeAttributesAtPosition(getDimensions(image), image.trixPosition)

   installImageEditor = (image) ->
     editor = document.createElement("div")


Hide build spinner on canceled builds

Hide build spinner on canceled builds

Closes #19651

--- app/assets/javascripts/ci/build.coffee
+++ app/assets/javascripts/ci/build.coffee
@@ -46,13 +46,15 @@
       , 4000

   getInitialBuildTrace: ->
+    removeRefreshStatuses = ['success', 'failed', 'canceled']
+
     $.ajax
       url: @build_url
       dataType: 'json'
       success: (build_data) ->
         $('.js-build-output').html build_data.trace_html

-        if build_data.status is 'success' or build_data.status is 'failed'
+        if removeRefreshStatuses.indexOf(build_data.status) >= 0
           $('.js-build-refresh').remove()

   getBuildTrace: ->


toggle w/in modal is working

toggle w/in modal is working

--- app/assets/javascripts/projects.coffee
+++ app/assets/javascripts/projects.coffee
@@ -25,50 +25,47 @@
     upper_project_id = $project.data('project-upper-id')
     favorite_lower = $project.data('favorite-lower')
     favorite_upper = $project.data('favorite-upper')
-    console.log(favorite_lower)
+
+    # clean up a little bit - dry up handling favorites

     handleFavorite = (level, level_project_id, favorite_or_unfavorite) ->
-      if favorite_or_unfavorite == 'favorite'
-        star_text = '★'
-      else if favorite_or_unfavorite == 'unfavorite'
-        star_text = '☆'
-      $('.save-project-' + level).off().on 'click', (event) ->
-        $('#modal-' + level + '-favorite-star span').text('')
-        $('#modal-' + level + '-favorite-star span').text(star_text)
-        event.preventDefault()
-        $.ajax
-          type: 'PUT'
-          url: '/projects/' + level_project_id + '/favorite?type=' + favorite_or_unfavorite
-        if favorite_or_unfavorite == 'favorite'
-          $project.data('favorite-' + level, true)
-          favorite_lower = true
-          console.log('favoriting')
-          console.log(favorite_lower)
-        else if favorite_or_unfavorite == 'unfavorite'
-          $project.data('favorite-' + level, false)
-          favorite_lower = false
-          console.log('unfavoriting')
-          console.log(favorite_lower)
+      $('.save-project-' + level).off().on 'click', ->
+        iteration = $(this).data('iteration') or 1
+        switch iteration
+          when 1
+            $('#modal-' + level + '-favorite-star span').text('☆')
+            $project.data('favorite-' + level, false)
+            $.ajax
+              type: 'PUT'
+              url: '/projects/' + level_project_id + '/favorite?type=unfavorite'
+          when 2
+            $('#modal-' + level + '-favorite-star span').text('★')
+            $project.data('favorite-' + level, true)
+            $.ajax
+              type: 'PUT'
+              url: '/projects/' + level_project_id + '/favorite?type=favorite'
+        iteration++
+        if iteration > 2
+          iteration = 1
+        $(this).data 'iteration', iteration
         return

-    # need to toggle b/w fave & unfave in modal
-
     if favorite_lower
-      $('#modal-lower-favorite-star span').text('')
+      $('.save-project-lower').data('iteration', 1)
       $('#modal-lower-favorite-star span').text('★')
       handleFavorite 'lower', lower_project_id, 'unfavorite'
     else
-      $('#modal-lower-favorite-star span').text('')
+      $('.save-project-lower').data('iteration', 2)
       $('#modal-lower-favorite-star span').text('☆')
       handleFavorite 'lower', lower_project_id, 'favorite'
     toggleAcademyModal(true)

     if favorite_upper
-      $('#modal-upper-favorite-star span').text('')
+      $('.save-project-upper').data('iteration', 1)
       $('#modal-upper-favorite-star span').text('★')
       handleFavorite 'upper', upper_project_id, 'unfavorite'
     else
-      $('#modal-upper-favorite-star span').text('')
+      $('.save-project-upper').data('iteration', 2)
       $('#modal-upper-favorite-star span').text('☆')
       handleFavorite 'upper', upper_project_id, 'favorite'
     toggleAcademyModal(true)


OX-135 adjusted range of routeClippingDistance, one voxel now possible

--- app/assets/javascripts/view/gui.coffee
+++ app/assets/javascripts/view/gui.coffee
@@ -115,7 +115,7 @@
                           .step(0.25)
                           .name("Move Value")
                           .onChange(@setMoveValue)
-    (fView.add @settings, "routeClippingDistance", 1, 500)
+    (fView.add @settings, "routeClippingDistance", 0.1, 200)
                           .name("Clipping Distance")
                           .onChange(@setRouteClippingDistance)
     (fView.add @settings, "displayCrosshairs")


Configure data source before making glyphs

This is needed to avoid passing 'source' to glyphs.

--- bokehjs/src/coffee/api/plotting.coffee
+++ bokehjs/src/coffee/api/plotting.coffee
@@ -205,6 +205,10 @@

     @_fixup_values(cls, data, attrs)

+    source = attrs.source ? new models.ColumnDataSource()
+    source.data = _.extend({}, source.data, data)
+    delete attrs.source
+
     _make_glyph = (cls, attrs, extra_attrs) =>
       new cls(_.extend({}, attrs, extra_attrs))

@@ -212,10 +216,6 @@
     nsglyph = _make_glyph(cls, attrs, nsglyph_ca)
     sglyph  = if has_sglyph then _make_glyph(cls, attrs,  sglyph_ca) else null
     hglyph  = if has_hglyph then _make_glyph(cls, attrs,  hglyph_ca) else null
-
-    source = attrs.source ? new models.ColumnDataSource()
-    source.data = _.extend({}, source.data, data)
-    delete attrs.source

     glyph_renderer = new models.GlyphRenderer({
       data_source:        source


fix coffee

fix coffee

--- both/methods/shift/shiftRequest.coffee
+++ both/methods/shift/shiftRequest.coffee
@@ -54,7 +54,7 @@
 						else throw new Meteor.Error 500, TAPi18n.__('modal.shift.maximumReached')
 					else if team.pending.length >= team.min - 1

-						teamleader = team.pending.find(user => user.teamleader) || team.pending.find(user => user.substituteTeamleader)
+						teamleader = team.pending.find((user) => user.teamleader) || team.pending.find((user) => user.substituteTeamleader)

 						approvedUsers = [ ]
 						declinedUsers = [ ]


Don't set restricted XHR headers.

Don't set restricted XHR headers.

--- src/coffee/download_controller.coffee
+++ src/coffee/download_controller.coffee
@@ -37,6 +37,7 @@
       dbXhr = new Dropbox.Xhr file.httpMethod, file.url
       dbXhr.setResponseType 'blob'
       for own name, value of file.headers
+        continue if name.toLowerCase() of @restrictedHeaders
         dbXhr.setHeader name, value
       dbXhr.prepare()
       @xhrs[file.uid] = dbXhr.xhr
@@ -137,4 +138,31 @@
         chrome.permissions.remove origins: ['<all_urls>'], -> callback()
     @

+  # Headers that cannot be set via XHR.
+  #
+  # List lifted from the W3C spec:
+  #   http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method
+  restrictedHeaders:
+      'accept-charset': true
+      'accept-encoding': true
+      'access-control-request-headers': true
+      'access-control-request-method': true
+      'connection': true
+      'content-length': true
+      'cookie': true
+      'cookie2': true
+      'date': true
+      'dnt': true
+      'expect': true
+      'host': true
+      'keep-alive': true
+      'origin': true
+      'referer': true
+      'te': true
+      'trailer': true
+      'transfer-encoding': true
+      'upgrade': true
+      'user-agent': true
+      'via': true
+
 window.DownloadController = DownloadController


formats amount on Update Vote confirmation dialog

--- app/js/controllers/account_vote.coffee
+++ app/js/controllers/account_vote.coffee
@@ -1,4 +1,4 @@
-angular.module("app").controller "AccountVoteController", ($scope, Wallet, WalletAPI, Info, $modal, Blockchain, Growl) ->
+angular.module("app").controller "AccountVoteController", ($scope, Wallet, WalletAPI, Info, $modal, Blockchain, Growl, Utils) ->
     $scope.votes=[]
     balMinusFee=0
     $scope.accounts = Wallet.accounts
@@ -53,5 +53,5 @@
             controller: "DialogConfirmationController"
             resolve:
                 title: -> "Are you sure?"
-                message: -> "This will send " + balMinusFee + " " + Info.symbol + " to " + $scope.account_name + ". It will charge a fee of " + Wallet.info.transaction_fee.amount / myBal.precision + " " + Info.symbol + "."
+                message: -> "This will send " + Utils.formatDecimal(balMinusFee,myBal.precision,true) + " " + Info.symbol + " to " + $scope.account_name + ". It will charge a fee of " + Wallet.info.transaction_fee.amount / myBal.precision + " " + Info.symbol + "."
                 action: -> yesSend


Have to return self on controllers

Have to return self on controllers

--- app/modules/home/HomeController.coffee
+++ app/modules/home/HomeController.coffee
@@ -2,6 +2,7 @@
 	"use strict"
 	self = this
 	self.login = LoginService
+	self

 HomeCtrl.$inject = ["LoginService"]


[fix] change address events

[fix] change address events

--- app/scripts/views/location_form.coffee
+++ app/scripts/views/location_form.coffee
@@ -59,13 +59,13 @@

     locationReverseGeocoding: (position) ->
       @model.unset 'address'
-      @model.unset 'position',
+      @model.touch 'position', position
+      dfd = @model.fetch
         silent: true
-      @model.set 'position', position,
-        silent: true
-      @model.fetch({silent: true})
-        .done =>
-          @model.trigger 'change:address', @model, @model.get('address')
+      dfd.done =>
+        @model.touch 'address'
+        @model.touch 'position'
+      dfd

     setSpinner: (spin = true) ->
       el = @ui.geolocate.find('i')
@@ -83,10 +83,13 @@
       @locationFix(address)

     locationFix: (address) ->
+      @model.unset 'position',
+        silent: true
       dfd = @model.fetch
-        attrs:
-          address: address
+        silent: true
       dfd.done (response) =>
+        @model.touch 'address'
+        @model.touch 'position'
         if _.any response.results
           @setValidForm true
         else


Move loadCollectionWithFirstLayer to CollectionLoader

--- vendor/assets/javascripts/brainstem/data-loader.coffee
+++ vendor/assets/javascripts/brainstem/data-loader.coffee
@@ -1,4 +1,101 @@
 window.Brainstem ?= {}
+
+class Brainstem.CollectionLoader
+  constructor: (options = {}) ->
+    @storageManager = options.storageManager
+    @_success = options.successFunction
+
+  _parseLoadOptions: (loadOptions) ->
+    @loadOptions = $.extend {}, loadOptions
+
+  load: (loadOptions) ->
+    @_parseLoadOptions(loadOptions)
+
+    options = @loadOptions
+    only = if options.only then _.map((Brainstem.Utils.extractArray "only", options), (id) -> String(id)) else null
+    search = options.search
+    include = _(options.include).map((i) -> _.keys(i)[0]) # pull off the top layer of includes
+    filters = options.filters || {}
+    order = options.order || "updated_at:desc"
+    filterKeys = _.map(filters, (v, k) -> "#{k}:#{v}").join(',')
+    cacheKey = [order, filterKeys, options.page, options.perPage, options.limit, options.offset].join('|')
+
+    cachedCollection = @storageManager.storage @loadOptions.name
+    collection = @storageManager.createNewCollection @loadOptions.name, []
+
+    unless options.cache == false
+      if only?
+        alreadyLoadedIds = _.select only, (id) => cachedCollection.get(id)?.associationsAreLoaded(include)
+        if alreadyLoadedIds.length == only.length
+          # We've already seen every id that is being asked for and have all the associated data.
+          @_success options, collection, _.map only, (id) => cachedCollection.get(id)
+          return collection
+      else
+        # Check if we have, at some point, requested enough records with this this order and filter(s).
+        if @storageManager.getCollectionDetails(@loadOptions.name).cache[cacheKey]
+          subset = _(@storageManager.getCollectionDetails(@loadOptions.name).cache[cacheKey]).map (result) => @storageManager.storage(result.key).get(result.id)
+          if (_.all(subset, (model) => model.associationsAreLoaded(include)))
+            @_success options, collection, subset
+            return collection
+
+    # If we haven't returned yet, we need to go to the server to load some missing data.
+    syncOptions =
+      data: {}
+      parse: true
+      error: options.error
+      success: (resp, status, xhr) =>
+        # The server response should look something like this:
+        #  {
+        #    count: 200,
+        #    results: [{ key: "tasks", id: 10 }, { key: "tasks", id: 11 }],
+        #    time_entries: [{ id: 2, title: "te1", project_id: 6, task_id: [10, 11] }]
+        #    projects: [{id: 6, title: "some project", time_entry_ids: [2] }]
+        #    tasks: [{id: 10, title: "some task" }, {id: 11, title: "some other task" }]
+        #  }
+        # Loop over all returned data types and update our local storage to represent any new data.
+
+        results = resp['results']
+        keys = _.reject(_.keys(resp), (key) -> key == 'count' || key == 'results')
+        unless _.isEmpty(results)
+          keys.splice(keys.indexOf(@loadOptions.name), 1) if keys.indexOf(@loadOptions.name) != -1
+          keys.push(@loadOptions.name)
+
+        for underscoredModelName in keys
+          @storageManager.storage(underscoredModelName).update _(resp[underscoredModelName]).values()
+
+        unless options.cache == false || only?
+          @storageManager.getCollectionDetails(@loadOptions.name).cache[cacheKey] = results
+
+        if only?
+          @_success options, collection, _.map(only, (id) -> cachedCollection.get(id))
+        else
+          @_success options, collection, _(results).map (result) -> base.data.storage(result.key).get(result.id)
+
+
+    syncOptions.data.include = include.join(",") if include.length
+    syncOptions.data.only = _.difference(only, alreadyLoadedIds).join(",") if only?
+    syncOptions.data.order = options.order if options.order?
+    _.extend(syncOptions.data, _(filters).omit('include', 'only', 'order', 'per_page', 'page', 'limit', 'offset', 'search')) if _(filters).keys().length
+
+    unless only?
+      if options.limit? && options.offset?
+        syncOptions.data.limit = options.limit
+        syncOptions.data.offset = options.offset
+      else
+        syncOptions.data.per_page = options.perPage
+        syncOptions.data.page = options.page
+
+    syncOptions.data.search = search if search
+
+    modelOrCollection = collection
+    modelOrCollection = options.model if options.only && options.model
+
+    jqXhr = Backbone.sync.call collection, 'read', modelOrCollection, syncOptions
+
+    if options.returnValues
+      options.returnValues.jqXhr = jqXhr
+
+    collection

 class Brainstem.DataLoader
   constructor: (options = {}) ->
@@ -53,6 +150,7 @@
     else
       @_loadCollectionWithFirstLayer($.extend({}, options, include: include, success: ((firstLayerCollection) =>
         expectedAdditionalLoads = @_countRequiredServerRequests(include) - 1
+
         if expectedAdditionalLoads > 0
           timesCalled = 0
           @_handleNextLayer collection: firstLayerCollection, include: include, error: options.error, success: =>
@@ -66,92 +164,8 @@
     collection

   _loadCollectionWithFirstLayer: (options) =>
-    options = $.extend({}, options)
-    name = options.name
-    only = if options.only then _.map((Brainstem.Utils.extractArray "only", options), (id) -> String(id)) else null
-    search = options.search
-    include = _(options.include).map((i) -> _.keys(i)[0]) # pull off the top layer of includes
-    filters = options.filters || {}
-    order = options.order || "updated_at:desc"
-    filterKeys = _.map(filters, (v, k) -> "#{k}:#{v}").join(',')
-    cacheKey = [order, filterKeys, options.page, options.perPage, options.limit, options.offset].join('|')
-
-    cachedCollection = @storageManager.storage name
-    collection = @storageManager.createNewCollection name, []
-
-    unless options.cache == false
-      if only?
-        alreadyLoadedIds = _.select only, (id) => cachedCollection.get(id)?.associationsAreLoaded(include)
-        if alreadyLoadedIds.length == only.length
-          # We've already seen every id that is being asked for and have all the associated data.
-          @_success options, collection, _.map only, (id) => cachedCollection.get(id)
-          return collection
-      else
-        # Check if we have, at some point, requested enough records with this this order and filter(s).
-        if @storageManager.getCollectionDetails(name).cache[cacheKey]
-          subset = _(@storageManager.getCollectionDetails(name).cache[cacheKey]).map (result) => @storageManager.storage(result.key).get(result.id)
-          if (_.all(subset, (model) => model.associationsAreLoaded(include)))
-            @_success options, collection, subset
-            return collection
-
-    # If we haven't returned yet, we need to go to the server to load some missing data.
-    syncOptions =
-      data: {}
-      parse: true
-      error: options.error
-      success: (resp, status, xhr) =>
-        # The server response should look something like this:
-        #  {
-        #    count: 200,
-        #    results: [{ key: "tasks", id: 10 }, { key: "tasks", id: 11 }],
-        #    time_entries: [{ id: 2, title: "te1", project_id: 6, task_id: [10, 11] }]
-        #    projects: [{id: 6, title: "some project", time_entry_ids: [2] }]
-        #    tasks: [{id: 10, title: "some task" }, {id: 11, title: "some other task" }]
-        #  }
-        # Loop over all returned data types and update our local storage to represent any new data.
-
-        results = resp['results']
-        keys = _.reject(_.keys(resp), (key) -> key == 'count' || key == 'results')
-        unless _.isEmpty(results)
-          keys.splice(keys.indexOf(name), 1) if keys.indexOf(name) != -1
-          keys.push(name)
-
-        for underscoredModelName in keys
-          @storageManager.storage(underscoredModelName).update _(resp[underscoredModelName]).values()
-
-        unless options.cache == false || only?
-          @storageManager.getCollectionDetails(name).cache[cacheKey] = results
-
-        if only?
-          @_success options, collection, _.map(only, (id) -> cachedCollection.get(id))
-        else
-          @_success options, collection, _(results).map (result) -> base.data.storage(result.key).get(result.id)
-
-
-    syncOptions.data.include = include.join(",") if include.length
-    syncOptions.data.only = _.difference(only, alreadyLoadedIds).join(",") if only?
-    syncOptions.data.order = options.order if options.order?
-    _.extend(syncOptions.data, _(filters).omit('include', 'only', 'order', 'per_page', 'page', 'limit', 'offset', 'search')) if _(filters).keys().length
-
-    unless only?
-      if options.limit? && options.offset?
-        syncOptions.data.limit = options.limit
-        syncOptions.data.offset = options.offset
-      else
-        syncOptions.data.per_page = options.perPage
-        syncOptions.data.page = options.page
-
-    syncOptions.data.search = search if search
-
-    modelOrCollection = collection
-    modelOrCollection = options.model if options.only && options.model
-
-    jqXhr = Backbone.sync.call collection, 'read', modelOrCollection, syncOptions
-
-    if options.returnValues
-      options.returnValues.jqXhr = jqXhr
-
-    collection
+    cl = new Brainstem.CollectionLoader(storageManager: @storageManager, successFunction: @_success)
+    cl.load(options)

   _handleNextLayer: (options) =>
     # Collection is a fully populated collection of tasks whose first layer of associations are loaded.


Enable broken class names

Enable broken class names

--- vendor/assets/javascripts/gemojifyjs/emojify.js.coffee
+++ vendor/assets/javascripts/gemojifyjs/emojify.js.coffee
@@ -22,9 +22,9 @@
             [/<\/3/g, 'broken_heart'],
             [/:\+1:/g, 'thumbsup'],
             [/:-1:/g, 'thumsdown'],
-            [/:1234/g, 'one234'],
-            [/:100/g, 'one00'],
-            [/:8ball/g, 'eightball']
+            [/:1234:/g, 'one234'],
+            [/:100:/g, 'one00'],
+            [/:8ball:/g, 'eightball']
         ]
     @valid_tags = ['+1', '-1', 'a', 'ab', 'abc', 'abcd', 'accept', 'aerial_tramway', 'airplane', 'alarm_clock', 'alien', 'ambulance', 'anchor', 'angel', 'anger', 'angry', 'anguished', 'ant', 'apple', 'aquarius', 'aries', 'arrow_backward', 'arrow_double_down', 'arrow_double_up', 'arrow_down', 'arrow_down_small', 'arrow_forward', 'arrow_heading_down', 'arrow_heading_up', 'arrow_left', 'arrow_lower_left', 'arrow_lower_right', 'arrow_right', 'arrow_right_hook', 'arrow_up', 'arrow_up_down', 'arrow_up_small', 'arrow_upper_left', 'arrow_upper_right', 'arrows_clockwise', 'arrows_counterclockwise', 'art', 'articulated_lorry', 'astonished', 'atm', 'b', 'baby', 'baby_bottle', 'baby_chick', 'baby_symbol', 'baggage_claim', 'balloon', 'ballot_box_with_check', 'bamboo', 'banana', 'bangbang', 'bank', 'bar_chart', 'barber', 'baseball', 'basketball', 'bath', 'bathtub', 'battery', 'bear', 'beer', 'beers', 'beetle', 'beginner', 'bell', 'bento', 'bicyclist', 'bike', 'bikini', 'bird', 'birthday', 'black_circle', 'black_joker', 'black_nib', 'black_square', 'black_square_button', 'blossom', 'blowfish', 'blue_book', 'blue_car', 'blue_heart', 'blush', 'boar', 'boat', 'bomb', 'book', 'bookmark', 'bookmark_tabs', 'books', 'boom', 'boot', 'bouquet', 'bow', 'bowling', 'bowtie', 'boy', 'bread', 'bride_with_veil', 'bridge_at_night', 'briefcase', 'broken_heart', 'bug', 'bulb', 'bullettrain_front', 'bullettrain_side', 'bus', 'busstop', 'bust_in_silhouette', 'busts_in_silhouette', 'cactus', 'cake', 'calendar', 'calling', 'camel', 'camera', 'cancer', 'candy', 'capital_abcd', 'capricorn', 'car', 'card_index', 'carousel_horse', 'cat', 'cat2', 'cd', 'chart', 'chart_with_downwards_trend', 'chart_with_upwards_trend', 'checkered_flag', 'cherries', 'cherry_blossom', 'chestnut', 'chicken', 'children_crossing', 'chocolate_bar', 'christmas_tree', 'church', 'cinema', 'circus_tent', 'city_sunrise', 'city_sunset', 'cl', 'clap', 'clapper', 'clipboard', 'clock1', 'clock10', 'clock1030', 'clock11', 'clock1130', 'clock12', 'clock1230', 'clock130', 'clock2', 'clock230', 'clock3', 'clock330', 'clock4', 'clock430', 'clock5', 'clock530', 'clock6', 'clock630', 'clock7', 'clock730', 'clock8', 'clock830', 'clock9', 'clock930', 'closed_book', 'closed_lock_with_key', 'closed_umbrella', 'cloud', 'clubs', 'cn', 'cocktail', 'coffee', 'cold_sweat', 'collision', 'computer', 'confetti_ball', 'confounded', 'confused', 'congratulations', 'construction', 'construction_worker', 'convenience_store', 'cookie', 'cool', 'cop', 'copyright', 'corn', 'couple', 'couple_with_heart', 'couplekiss', 'cow', 'cow2', 'credit_card', 'crocodile', 'crossed_flags', 'crown', 'cry', 'crying_cat_face', 'crystal_ball', 'cupid', 'curly_loop', 'currency_exchange', 'curry', 'custard', 'customs', 'cyclone', 'dancer', 'dancers', 'dango', 'dart', 'dash', 'date', 'de', 'deciduous_tree', 'department_store', 'diamond_shape_with_a_dot_inside', 'diamonds', 'disappointed', 'dizzy', 'dizzy_face', 'do_not_litter', 'dog', 'dog2', 'dollar', 'dolls', 'dolphin', 'door', 'doughnut', 'dragon', 'dragon_face', 'dress', 'dromedary_camel', 'droplet', 'dvd', 'e-mail', 'ear', 'ear_of_rice', 'earth_africa', 'earth_americas', 'earth_asia', 'egg', 'eggplant', 'eight', 'eight_pointed_black_star', 'eight_spoked_asterisk', 'electric_plug', 'elephant', 'email', 'end', 'envelope', 'es', 'euro', 'european_castle', 'european_post_office', 'evergreen_tree', 'exclamation', 'expressionless', 'eyeglasses', 'eyes', 'facepunch', 'factory', 'fallen_leaf', 'family', 'fast_forward', 'fax', 'fearful', 'feelsgood', 'feet', 'ferris_wheel', 'file_folder', 'finnadie', 'fire', 'fire_engine', 'fireworks', 'first_quarter_moon', 'first_quarter_moon_with_face', 'fish', 'fish_cake', 'fishing_pole_and_fish', 'fist', 'five', 'flags', 'flashlight', 'floppy_disk', 'flower_playing_cards', 'flushed', 'foggy', 'football', 'fork_and_knife', 'fountain', 'four', 'four_leaf_clover', 'fr', 'free', 'fried_shrimp', 'fries', 'frog', 'frowning', 'fuelpump', 'full_moon', 'full_moon_with_face', 'game_die', 'gb', 'gem', 'gemini', 'ghost', 'gift', 'gift_heart', 'girl', 'globe_with_meridians', 'goat', 'goberserk', 'godmode', 'golf', 'grapes', 'green_apple', 'green_book', 'green_heart', 'grey_exclamation', 'grey_question', 'grimacing', 'grin', 'grinning', 'guardsman', 'guitar', 'gun', 'haircut', 'hamburger', 'hammer', 'hamster', 'hand', 'handbag', 'hankey', 'hash', 'hatched_chick', 'hatching_chick', 'headphones', 'hear_no_evil', 'heart', 'heart_decoration', 'heart_eyes', 'heart_eyes_cat', 'heartbeat', 'heartpulse', 'hearts', 'heavy_check_mark', 'heavy_division_sign', 'heavy_dollar_sign', 'heavy_exclamation_mark', 'heavy_minus_sign', 'heavy_multiplication_x', 'heavy_plus_sign', 'helicopter', 'herb', 'hibiscus', 'high_brightness', 'high_heel', 'hocho', 'honey_pot', 'honeybee', 'horse', 'horse_racing', 'hospital', 'hotel', 'hotsprings', 'hourglass', 'hourglass_flowing_sand', 'house', 'house_with_garden', 'hurtrealbad', 'hushed', 'ice_cream', 'icecream', 'id', 'ideograph_advantage', 'imp', 'inbox_tray', 'incoming_envelope', 'information_desk_person', 'information_source', 'innocent', 'interrobang', 'iphone', 'it', 'izakaya_lantern', 'jack_o_lantern', 'japan', 'japanese_castle', 'japanese_goblin', 'japanese_ogre', 'jeans', 'joy', 'joy_cat', 'jp', 'key', 'keycap_ten', 'kimono', 'kiss', 'kissing', 'kissing_cat', 'kissing_closed_eyes', 'kissing_heart', 'kissing_smiling_eyes', 'koala', 'koko', 'kr', 'large_blue_circle', 'large_blue_diamond', 'large_orange_diamond', 'last_quarter_moon', 'last_quarter_moon_with_face', 'laughing', 'leaves', 'ledger', 'left_luggage', 'left_right_arrow', 'leftwards_arrow_with_hook', 'lemon', 'leo', 'leopard', 'libra', 'light_rail', 'link', 'lips', 'lipstick', 'lock', 'lock_with_ink_pen', 'lollipop', 'loop', 'loudspeaker', 'love_hotel', 'love_letter', 'low_brightness', 'm', 'mag', 'mag_right', 'mahjong', 'mailbox', 'mailbox_closed', 'mailbox_with_mail', 'mailbox_with_no_mail', 'man', 'man_with_gua_pi_mao', 'man_with_turban', 'mans_shoe', 'maple_leaf', 'mask', 'massage', 'meat_on_bone', 'mega', 'melon', 'memo', 'mens', 'metal', 'metro', 'microphone', 'microscope', 'milky_way', 'minibus', 'minidisc', 'mobile_phone_off', 'money_with_wings', 'moneybag', 'monkey', 'monkey_face', 'monorail', 'moon', 'mortar_board', 'mount_fuji', 'mountain_bicyclist', 'mountain_cableway', 'mountain_railway', 'mouse', 'mouse2', 'movie_camera', 'moyai', 'muscle', 'mushroom', 'musical_keyboard', 'musical_note', 'musical_score', 'mute', 'nail_care', 'name_badge', 'neckbeard', 'necktie', 'negative_squared_cross_mark', 'neutral_face', 'new', 'new_moon', 'new_moon_with_face', 'newspaper', 'ng', 'nine', 'no_bell', 'no_bicycles', 'no_entry', 'no_entry_sign', 'no_good', 'no_mobile_phones', 'no_mouth', 'no_pedestrians', 'no_smoking', 'non-potable_water', 'nose', 'notebook', 'notebook_with_decorative_cover', 'notes', 'nut_and_bolt', 'o', 'o2', 'ocean', 'octocat', 'octopus', 'oden', 'office', 'ok', 'ok_hand', 'ok_woman', 'older_man', 'older_woman', 'on', 'oncoming_automobile', 'oncoming_bus', 'oncoming_police_car', 'oncoming_taxi', 'one', 'open_file_folder', 'open_hands', 'open_mouth', 'ophiuchus', 'orange_book', 'outbox_tray', 'ox', 'page_facing_up', 'page_with_curl', 'pager', 'palm_tree', 'panda_face', 'paperclip', 'parking', 'part_alternation_mark', 'partly_sunny', 'passport_control', 'paw_prints', 'peach', 'pear', 'pencil', 'pencil2', 'penguin', 'pensive', 'performing_arts', 'persevere', 'person_frowning', 'person_with_blond_hair', 'person_with_pouting_face', 'phone', 'pig', 'pig2', 'pig_nose', 'pill', 'pineapple', 'pisces', 'pizza', 'point_down', 'point_left', 'point_right', 'point_up', 'point_up_2', 'police_car', 'poodle', 'poop', 'post_office', 'postal_horn', 'postbox', 'potable_water', 'pouch', 'poultry_leg', 'pound', 'pouting_cat', 'pray', 'princess', 'punch', 'purple_heart', 'purse', 'pushpin', 'put_litter_in_its_place', 'question', 'rabbit', 'rabbit2', 'racehorse', 'radio', 'radio_button', 'rage', 'rage1', 'rage2', 'rage3', 'rage4', 'railway_car', 'rainbow', 'raised_hand', 'raised_hands', 'ram', 'ramen', 'rat', 'recycle', 'red_car', 'red_circle', 'registered', 'relaxed', 'relieved', 'repeat', 'repeat_one', 'restroom', 'revolving_hearts', 'rewind', 'ribbon', 'rice', 'rice_ball', 'rice_cracker', 'rice_scene', 'ring', 'rocket', 'roller_coaster', 'rooster', 'rose', 'rotating_light', 'round_pushpin', 'rowboat', 'ru', 'rugby_football', 'runner', 'running', 'running_shirt_with_sash', 'sa', 'sagittarius', 'sailboat', 'sake', 'sandal', 'santa', 'satellite', 'satisfied', 'saxophone', 'school', 'school_satchel', 'scissors', 'scorpius', 'scream', 'scream_cat', 'scroll', 'seat', 'secret', 'see_no_evil', 'seedling', 'seven', 'shaved_ice', 'sheep', 'shell', 'ship', 'shipit', 'shirt', 'shit', 'shoe', 'shower', 'signal_strength', 'six', 'six_pointed_star', 'ski', 'skull', 'sleeping', 'sleepy', 'slot_machine', 'small_blue_diamond', 'small_orange_diamond', 'small_red_triangle', 'small_red_triangle_down', 'smile', 'smile_cat', 'smiley', 'smiley_cat', 'smiling_imp', 'smirk', 'smirk_cat', 'smoking', 'snail', 'snake', 'snowboarder', 'snowflake', 'snowman', 'sob', 'soccer', 'soon', 'sos', 'sound', 'space_invader', 'spades', 'spaghetti', 'sparkler', 'sparkles', 'sparkling_heart', 'speak_no_evil', 'speaker', 'speech_balloon', 'speedboat', 'squirrel', 'star', 'star2', 'stars', 'station', 'statue_of_liberty', 'steam_locomotive', 'stew', 'straight_ruler', 'strawberry', 'stuck_out_tongue', 'stuck_out_tongue_closed_eyes', 'stuck_out_tongue_winking_eye', 'sun_with_face', 'sunflower', 'sunglasses', 'sunny', 'sunrise', 'sunrise_over_mountains', 'surfer', 'sushi', 'suspect', 'suspension_railway', 'sweat', 'sweat_drops', 'sweat_smile', 'sweet_potato', 'swimmer', 'symbols', 'syringe', 'tada', 'tanabata_tree', 'tangerine', 'taurus', 'taxi', 'tea', 'telephone', 'telephone_receiver', 'telescope', 'tennis', 'tent', 'thought_balloon', 'three', 'thumbsdown', 'thumbsup', 'ticket', 'tiger', 'tiger2', 'tired_face', 'tm', 'toilet', 'tokyo_tower', 'tomato', 'tongue', 'top', 'tophat', 'tractor', 'traffic_light', 'train', 'train2', 'tram', 'triangular_flag_on_post', 'triangular_ruler', 'trident', 'triumph', 'trolleybus', 'trollface', 'trophy', 'tropical_drink', 'tropical_fish', 'truck', 'trumpet', 'tshirt', 'tulip', 'turtle', 'tv', 'twisted_rightwards_arrows', 'two', 'two_hearts', 'two_men_holding_hands', 'two_women_holding_hands', 'u5272', 'u5408', 'u55b6', 'u6307', 'u6708', 'u6709', 'u6e80', 'u7121', 'u7533', 'u7981', 'u7a7a', 'uk', 'umbrella', 'unamused', 'underage', 'unlock', 'up', 'us', 'v', 'vertical_traffic_light', 'vhs', 'vibration_mode', 'video_camera', 'video_game', 'violin', 'virgo', 'volcano', 'vs', 'walking', 'waning_crescent_moon', 'waning_gibbous_moon', 'warning', 'watch', 'water_buffalo', 'watermelon', 'wave', 'wavy_dash', 'waxing_crescent_moon', 'waxing_gibbous_moon', 'wc', 'weary', 'wedding', 'whale', 'whale2', 'wheelchair', 'white_check_mark', 'white_circle', 'white_flower', 'white_square', 'white_square_button', 'wind_chime', 'wine_glass', 'wink', 'wolf', 'woman', 'womans_clothes', 'womans_hat', 'womens', 'worried', 'wrench', 'x', 'yellow_heart', 'yen', 'yum', 'zap', 'zero', 'zzz']


Referral: update campaign end date

Referral: update campaign end date

--- workers/social/lib/social/models/referral/index.coffee
+++ workers/social/lib/social/models/referral/index.coffee
@@ -278,7 +278,7 @@
   CAMPAIGN_NAME                   = "100_TB_CAMPAIGN"
   CAMPAIGN_TOTAL_DISK_SIZE_IN_MB  = 1024*1024*100 # 100TB
   CAMPAIGN_DISK_SIZE_IN_MB        = 1024
-  CAMPAIGN_START_DATE             = new Date("Jan 29 2014 16:00:00")
+  CAMPAIGN_END_DATE               = new Date("Feb 04 2014 16:00:00 GMT")
   OLD_DISK_SIZE_IN_MB             = 250

   # this functions checks default vm and updates disk size
@@ -366,7 +366,7 @@
           name: CAMPAIGN_NAME
           content:
             diskSpaceLeftMB: CAMPAIGN_TOTAL_DISK_SIZE_IN_MB
-            endDate: CAMPAIGN_START_DATE
+            endDate: CAMPAIGN_END_DATE

         cmp.save (err)->
           return callback err if err


Simplified cleanup process

Simplified cleanup process

--- phantom.coffee
+++ phantom.coffee
@@ -24,7 +24,6 @@

 onSignalClean = (signal) ->
   return ->
-    cleanUp()
     if process.listeners(signal).length == 1
       process.exit(0)


Nicer map menu

Nicer map menu

--- web/src/views/map.cjsx
+++ web/src/views/map.cjsx
@@ -170,20 +170,24 @@
                             selected={@props.selectedTypes} toggle={typeToggle}/>

                     <form onSubmit={@handleSubmit}>
-

+

                             Exclude finds
-                            <input
-                                type="text"
-                                id="username"
-                                placeholder="Username"
-                                ref="username"
-                            />
+

+                                <input
+                                    type="text"
+                                    id="username"
+                                    placeholder="Username"
+                                    ref="username"
+                                />
+                                <button className="ui icon button">
+
+                                </button>
+

                                 { React.createElement(UserFilter, key: username, username: username, remove: @handleRemoveUser) for username in @props.filterUsers.toArray() }

                     </form>
-                    {locateButton}

                         Location


no wrap for mobile

no wrap for mobile

Former-commit-id: c0fdc8b48d3904db0b581d1dfdb071757c161cb3 [formerly 37f87dd1add9ba2432b326370d4c860906b7f166] [formerly c5fba00225651cf90e4f6761ec885c7cb60a3be1 [formerly 6e34b77479de235f5749936786d7a8b9e77e11bc]] [formerly 937dedcad63fdf62a7e16c559235a8b750755c4d [formerly 8e07d2f15efe077aa71cff365bf53da608931f44] [formerly f811ac3e554847f4de4008dab402025622fdc140 [formerly f811ac3e554847f4de4008dab402025622fdc140 [formerly 324cfd7894504e213322bd76fefe476535490414]]]]
Former-commit-id: 0197f748cb8a51ffea08b122e71ab40e6fdeb8bc [formerly c3e4216419e79d521bcfa9d86d6e2fa95ed4dbd8] [formerly 220c10dd03f6820917d7eb2ec9824e457a538c98 [formerly f79193a5c561b1f2c920ffa65471e8fc41f5b9a5]]
Former-commit-id: 0446d43cf5eb49571525a0493f73afdacefb1def [formerly d6f808f43ee3be81592f5054aa1798ba7f5c1b04]
Former-commit-id: bdaf96c892b654a593668b8ec88f67b0a0f2554a
Former-commit-id: e101c1b0e77a22bc2ba4c80e0123b530b234f6a7

--- @client/saas_landing_page.coffee
+++ @client/saas_landing_page.coffee
@@ -202,11 +202,13 @@
       DIV
         style:
           marginTop: 25
+          width: SAAS_PAGE_WIDTH

         # window bar / url
         DIV
           style: _.extend {}, h1,
             color: 'white'
+            whiteSpace: 'nowrap'
           chapter.text

         DIV


updating copy

updating copy

Former-commit-id: 0a51bd819387b3208a2d6f3557811401050731c5 [formerly a999b8ac986403d19e9de4f95f1f04b772a66c32] [formerly be9ee83d00b3cb68edc8665ae31b5d75ea7e194e [formerly 7e74311f8140ec6082de8324211f78ba6c405dc0]] [formerly 99e99949f08693dda258d33adfba846f85598a56 [formerly 7d31ab66932c001e8b9e9bc8186c7d71b216b69c] [formerly 4acb17bddffbbc93ae584ba1ca76e2c959a11d02 [formerly 4acb17bddffbbc93ae584ba1ca76e2c959a11d02 [formerly 5f14bb3064e7dde50033a811e2a31a8354e7d0cb]]]]
Former-commit-id: acc1891145176b54e43cf21112be97e658f23bb7 [formerly 4710c6a128be0fb033a1983a9b8698c81659d20c] [formerly 6b1980892250409eba6d0565f20cfe28ebfc26c1 [formerly d97428545c3f209c99c62cc3e60f223215c1af86]]
Former-commit-id: 696eee45e2ddc6d7181f19be49f04f492d01d286 [formerly c3dcd3f549927a5a4ffbeb67932b8b8b97ea200f]
Former-commit-id: 035ef943b0b2a4b1d3060ddd233f17e1c1d6f87e
Former-commit-id: 74a6292e0a33201b3d8451e5ee080436d0c83dde

--- @client/saas_landing_page.coffee
+++ @client/saas_landing_page.coffee
@@ -78,7 +78,7 @@
         style: _.extend h1,
           margin: '50px 0'

-        'Champion focused discussion in your'
+        'Focused discussion for your'
         BR null,
         'community, organization or classroom'

@@ -755,8 +755,8 @@
           loved ones can easily degenerate. Humanity's ability
           to listen and learn is ever so fragile, easily broken by poor habits and
           flawed tools. Travis knew he was no exception to the problem;
-          but that day, at least, Travis was happy to have turned frustration
-          into a blueprint for improvement.
+          but that day, at least, Travis was happy to turn his frustration into
+          a blueprint for improvement.
           """

       DIV
@@ -776,10 +776,9 @@
           style: caption_text

           """
-          The Web is only 2.0 years old (or for some pundits, 3.0).
+          The Web is only 2.0 years old (3.0 claim some pundits).
           It's social. It's getting good at speaking. But it’s not yet very
-          good at listening. Our interfaces for communicating are much better
-          at speaking into than listening through.
+          good at listening.
           """

@@ -890,8 +889,8 @@
           Michael's incisive and persistent feedback helped Travis transform his
           abstract knowledge and ideas into concrete designs. They started co-advising
           each other and helping with each others' projects. Bits and pieces of ideas
-          that Michael and Travis had kicked
-          around before had suddenly coalesced in that moment Consider.it was born.
+          that Michael and Travis had kicked around before had suddenly coalesced in
+          that moment Consider.it was born outside the bike shop.
           """

@@ -1008,6 +1007,14 @@

           ' delivered by Seattle Public Librarians since 2012.'

+        P
+          style:
+            paddingBottom: 15
+          """
+          Travis recognized that the technology had broad applicability
+          beyond civic engagement, and he generalized the technology.
+          """
+

     DIV
       style: section_style
@@ -1111,9 +1118,13 @@
             href:'http://www.washingtonpost.com/wp-dyn/content/article/2011/02/02/AR2011020203272.html'
             "first openly gay presidential candidate"

-          """
-          . The icy mornings in New Hampshire and the afternoons driving a sound
-          truck blasting reggaeton through Puerto Rico had affirmed his love for
+          '. The icy mornings in New Hampshire and the afternoons driving a sound
+          truck blasting reggaeton through '
+          A
+            href: 'http://news.yahoo.com/blogs/ticket/ron-paul-topped-ran-fred-karger-puerto-rican-153210979.html'
+            style: story_link
+            'Puerto Rico'
+          """ had affirmed his love for
           this country, but did little to cure his skepticism about its politics.
           Kevin saw that there was a deeper problem of listening and critical
           thinking behind our political strife. While trudging through the
@@ -1138,14 +1149,16 @@
             paddingBottom: 15

           """
-          And here we are now! We've been adding new functionality and
-          simplifying the design. We are exploring the ways Consider.it
-          can be used outside of civic engagement, such as: to smooth
-          organizational change efforts, to allow decentralized online
-          communities to deliberate, and to help schools teach critical
-          thinking skills in classrooms.
-          """
-
+          And here we are now!
+          As a startup, we are exploring different markets beyond civic
+          engagement. We're using Consider.it to
+          align the opinions of employees during organizational change
+          efforts, where Consider.it's ability to surface knowledge and
+          identify sticking points across a large group of people can help
+          the planning process. We're applying Consider.it to decision-making
+          and deliberation in decentralized online communities. Finally,
+          Consider.it's concentration on tradeoffs is a good fit for schools
+          teaching the new Common Core critical thinking skills."""
         P
           style:
             paddingBottom: 15
@@ -1156,7 +1169,7 @@
             href: 'mailto:admin@consider.it'
             style: story_link
             'send us a message'
-          '. Maybe we can collaborate!'
+          '. Maybe we can collaborate! Or send us witty hate mail if you wish, we welcome adversaries.'

       DIV


tweak

tweak

Former-commit-id: f468245555a19f1f0d22727fb0ef7d59c86e0d94 [formerly d030cc418fbbe63bc3a215ab4ba0bb8e1e35cab2] [formerly e656b68dd4f4343f5e3c29df8cbba80d1a3edb3d [formerly ed151e2e35059002e1f3a6bfa593db1a789c8d4a]] [formerly 7c5e24d0e14faa1003855fd1d5de035a01dc2cda [formerly 6bfb4b5f4e90136443e5f82a14d34ab5b1166be3] [formerly 3e454e6e612e6bbbeec2b761bbcd262f2798dee7 [formerly 3e454e6e612e6bbbeec2b761bbcd262f2798dee7 [formerly 923773cb3aa469c22d02f2a91f18718ad041e309]]]]
Former-commit-id: 4c1c3968fcf97c1967dadd5f5dd243a0d4cee310 [formerly 4d2c62714f664386e6a4ee50973085375bdae622] [formerly d95c75912c13ac1afd8084df316e536a6ffaa3b9 [formerly e90cb5fa2516e90a5d82ee95d7d8d96ac01c56f1]]
Former-commit-id: 252e15e1d0607120d5ed70d3fe30e0c04139616d [formerly 3175ceb6369009aa1399fb063cf6d9bcb87b8f98]
Former-commit-id: 3d1a7bb40ecea0086048fe1ba1227430a6f5b4ac
Former-commit-id: 129976039e34621f4afdfcf7b1a05b9a64478395

--- @client/saas_landing_page.coffee
+++ @client/saas_landing_page.coffee
@@ -153,7 +153,7 @@
             A
               style: nav_link_style
               href: "#video"
-              'Demo'
+              'demo'

             # A
             #   style: nav_link_style
@@ -168,15 +168,15 @@
             A
               style: nav_link_style
               href: "#pricing"
-              'Price'
+              'price'
             A
               style: nav_link_style
               href: "#contact"
-              'Contact'
+              'contact'
             A
               style: nav_link_style
               href: "#story"
-              'Bio'
+              'story'

         # DIV
         #   style:
@@ -626,7 +626,7 @@
       point_style: 'bullet'
       strong: "Teach critical thinking"
       body: """. Students learn how to develop and express a considered opinion while listening
-                 to and engaging with other’s ideas. Works well for Common Core aligned exercises
+                 to and engaging with others' ideas. Supports Common Core aligned exercises
                  in English and Social Studies.
             """


add timeout on chart zoom update

add timeout on chart zoom update

--- src/components/widgets/accounts-cash-projection/accounts-cash-projection.directive.coffee
+++ src/components/widgets/accounts-cash-projection/accounts-cash-projection.directive.coffee
@@ -1,5 +1,5 @@
 module = angular.module('impac.components.widgets.accounts-cash-projection', [])
-module.controller('WidgetAccountsCashProjectionCtrl', ($scope, $q, $filter, ImpacKpisSvc, ImpacWidgetsSvc, ImpacAssets, HighchartsFactory, BoltResources) ->
+module.controller('WidgetAccountsCashProjectionCtrl', ($scope, $q, $filter, $timeout  , ImpacKpisSvc, ImpacWidgetsSvc, ImpacAssets, HighchartsFactory, BoltResources) ->

   w = $scope.widget

@@ -133,13 +133,19 @@
     return imgTemplate(imgSrc(name), name) unless name == 'Projected cash'
     imgTemplate(imgSrc(name), name) + '<br>' + imgTemplate(imgSrc('cashFlow'), 'Cash flow')

+  updateLocked = false
+  zoomMetadata = {}
   onZoom = (event) ->
-    metadataHash = angular.merge w.metadata, {
+    zoomMetadata = angular.merge w.metadata, {
       xAxis:
         max: event.max
         min: event.min
     }
-    ImpacWidgetsSvc.update(w, { metadata: metadataHash }, false)
+    unless updateLocked
+      updateLocked = true
+      $timeout ->
+        ImpacWidgetsSvc.update(w, { metadata: zoomMetadata }, false).finally(-> updateLocked = false)
+      , 1000

   w.format = ->
     # Chart basic options


update url to dev

update url to dev

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -512,8 +512,8 @@
           $("textarea[id='cartObject']").val(JSON.stringify(products))
           formData = $(form).serialize()

-          production_url = "https://script.google.com/macros/s/AKfycbx8LH0V-1gd_JSCbQItjtGlTQCNhNpWwFVd7IkW0E_uzmQj1pWP/exec"
-          url = production_url
+          dev_url = "https://script.google.com/macros/s/AKfycbyPEqIjIfyWR09vPhd5HcP7jB9KIjng0YzEwo2tjWOk8aEscM4/exec"
+          url = dev_url

           $.ajax
             type: 'POST'


New thermos version automatically passes locals.

--- assets/js/templates/teambuilder.coffee
+++ assets/js/templates/teambuilder.coffee
@@ -5,5 +5,4 @@
       klass += '.selected'  if i == locals.selected
       @div klass, pokemon.name
     # @div '.builder_add_pokemon', '+'
-  # TODO: Don't use bind. Honestly, thermos should be passing locals for you.
-  @div '.builder_detail', JST['teambuilder_detail'].bind(this, locals)
+  @div '.builder_detail', JST['teambuilder_detail']