fix deletion by toolbar

fix deletion by toolbar

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -275,9 +275,9 @@
             ref: 'footer'

-    toggleHeaders: (e) ->
-        e.preventDefault()
-        e.stopPropagation()
+    toggleHeaders: (event) ->
+        event.preventDefault()
+        event.stopPropagation()
         state =
             headers: not @state.headers
         if @props.inConversation and not @props.active
@@ -285,15 +285,15 @@
         @setState state

-    toggleActive: (e) ->
+    toggleActive: (event) ->
         if @props.inConversation
-            e.preventDefault()
-            e.stopPropagation()
+            event.preventDefault()
+            event.stopPropagation()
             @props.toggleActive()
             @setState headers: false

-    onDelete: (e) ->
+    onDelete: (event) ->
         event.preventDefault()
         event.stopPropagation()
         needConfirmation = @props.settings.get('messageConfirmDelete')


Client: Fix Pokemon forme icons.

Client: Fix Pokemon forme icons.

--- client/app/js/concerns/images.coffee
+++ client/app/js/concerns/images.coffee
@@ -7,10 +7,11 @@
 @PokemonSprite = (id, forme, options = {}) ->
   front = options.front ? true
   shiny = options.shiny ? false
-  kind  = if shiny && front  then 's' else if shiny && !front then 'bs' else if !shiny && front then 'i' else 'b'
+  kind  = (if front then "front" else "back")
+  kind += "-s"  if shiny
   id    = "000#{id}".substr(-3)
   if forme && forme != 'default' then id += "-#{forme}"
-  "http://sprites.pokecheck.org/#{kind}/#{id}.gif"
+  "http://s3.pokebattle.com.s3.amazonaws.com/sprites/#{kind}/#{id}.gif"

 generation = Generations[DEFAULT_GENERATION.toUpperCase()]
 maxSpeciesId = Math.max((p.id  for n, p of generation.SpeciesData)...)
@@ -199,8 +200,8 @@
   },
   "Burmy": {
     "default": 412,
-    "sandy": 649+39,
-    "trash": 649+40
+    "sandy": NON_DEFAULT_FORMES_OFFSET + 9,
+    "trash": NON_DEFAULT_FORMES_OFFSET + 10
   },
   "Butterfree": {
     "default": 12
@@ -363,7 +364,7 @@
   },
   "Darmanitan": {
     "default": 555,
-    "zen": 649+63
+    "zen": NON_DEFAULT_FORMES_OFFSET + 81
   },
   "Darumaka": {
     "default": 554
@@ -382,9 +383,9 @@
   },
   "Deoxys": {
     "default": 386,
-    "attack": 649+34,
-    "defense": 649+35,
-    "speed": 649+37
+    "attack": NON_DEFAULT_FORMES_OFFSET + 52,
+    "defense": NON_DEFAULT_FORMES_OFFSET + 53,
+    "speed": NON_DEFAULT_FORMES_OFFSET + 55
   },
   "Dewgong": {
     "default": 87
@@ -622,7 +623,7 @@
   },
   "Giratina": {
     "default": 487,
-    "origin": 649+56
+    "origin": NON_DEFAULT_FORMES_OFFSET + 74
   },
   "Glaceon": {
     "default": 471
@@ -830,7 +831,7 @@
   },
   "Keldeo": {
     "default": 647,
-    "resolute": 649+92
+    "resolute": NON_DEFAULT_FORMES_OFFSET + 103
   },
   "Kingdra": {
     "default": 230
@@ -873,8 +874,8 @@
   },
   "Kyurem": {
     "default": 646,
-    "black": 649+90,
-    "white": 649+91
+    "black": NON_DEFAULT_FORMES_OFFSET + 101,
+    "white": NON_DEFAULT_FORMES_OFFSET + 102
   },
   "Lairon": {
     "default": 305
@@ -884,7 +885,7 @@
   },
   "Landorus": {
     "default": 645,
-    "therian": 649+89
+    "therian": NON_DEFAULT_FORMES_OFFSET + 100
   },
   "Lanturn": {
     "default": 171
@@ -1065,7 +1066,7 @@
   },
   "Meloetta": {
     "default": 648,
-    "pirouette": 649+75
+    "pirouette": NON_DEFAULT_FORMES_OFFSET + 93
   },
   "Meowth": {
     "default": 52
@@ -1417,11 +1418,11 @@
   },
   "Rotom": {
     "default": 479,
-    "fan": 649+50,
-    "frost": 649+51,
-    "heat": 649+52,
-    "mow": 649+53,
-    "wash": 649+54
+    "fan": NON_DEFAULT_FORMES_OFFSET + 68,
+    "frost": NON_DEFAULT_FORMES_OFFSET + 69,
+    "heat": NON_DEFAULT_FORMES_OFFSET + 70,
+    "mow": NON_DEFAULT_FORMES_OFFSET + 71,
+    "wash": NON_DEFAULT_FORMES_OFFSET + 72
   },
   "Rufflet": {
     "default": 627
@@ -1506,7 +1507,7 @@
   },
   "Shaymin": {
     "default": 492,
-    "sky": 649+58
+    "sky": NON_DEFAULT_FORMES_OFFSET + 76
   },
   "Shedinja": {
     "default": 292
@@ -1741,7 +1742,7 @@
   },
   "Thundurus": {
     "default": 642,
-    "therian": 649+88
+    "therian": NON_DEFAULT_FORMES_OFFSET + 99
   },
   "Timburr": {
     "default": 532
@@ -1766,7 +1767,7 @@
   },
   "Tornadus": {
     "default": 641,
-    "therian": 649+87
+    "therian": NON_DEFAULT_FORMES_OFFSET + 98
   },
   "Torterra": {
     "default": 389
@@ -1941,8 +1942,8 @@
   },
   "Wormadam": {
     "default": 413,
-    "sandy": 649+42,
-    "trash": 649+43
+    "sandy": NON_DEFAULT_FORMES_OFFSET + 60,
+    "trash": NON_DEFAULT_FORMES_OFFSET + 61
   },
   "Wurmple": {
     "default": 265


cache on for literal optimized test

cache on for literal optimized test

--- test/literal.coffee
+++ test/literal.coffee
@@ -10,4 +10,4 @@
   describe '#text(value)', ->
     it 'should render just plain text', ->
       t = -> text 'foobar'
-      cc.render(t, optimized: true).should.equal 'foobar'
+      cc.render(t, optimized: true, cache: on).should.equal 'foobar'


revert the unnecesary workaround introduced in e1633d604fe62fae79fd7bf9865f8b7433a7c9c8

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -915,8 +915,6 @@
         unsubscribeInput(input)
         Bacon.noMore
       else
-        # Force evaluate, because Buses may have circular refs. (#345)
-        event.value() if event.hasValue()
         sink event
     unsubAll = ->
       sub.unsub?() for sub in subscriptions


delay updates only if the dependency tree contains duplicates

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1035,9 +1035,8 @@
     if !sources.length
       return Bacon.never()

-    needsBarrier = _.any sources, (s) -> s.flatten
     sources = _.map Source.fromObservable, sources
-    observables = _.map ((s) -> s.obs), sources
+    needsBarrier = (_.any sources, (s) -> s.flatten) and (containsDuplicateDeps (_.map ((s) -> s.obs), sources))

     resultStream = new EventStream describe(Bacon, "when", patterns...), (sink) ->
       triggers = []
@@ -1103,6 +1102,21 @@

       compositeUnsubscribe (part s for s in sources)...

+containsDuplicateDeps = (observables, state = []) ->
+  checkObservable = (obs) ->
+    if Bacon._.contains(state, obs)
+      true
+    else
+      deps = obs.internalDeps()
+      if deps.length
+        state.push(obs)
+        Bacon._.any(deps, checkObservable)
+      else
+        state.push(obs)
+        false
+
+  Bacon._.any observables, checkObservable
+
 Bacon.update = (initial, patterns...) ->
   lateBindFirst = (f) -> (args...) -> (i) -> f([i].concat(args)...)
   i = patterns.length - 1


fix for #447, tests pending

fix for #447, tests pending

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -898,7 +898,7 @@
   justInitValue = new EventStream describe(property, "justInitValue"), (sink) ->
     value = undefined
     unsub = property.subscribeInternal (event) ->
-      if event.hasValue()
+      if event.hasValue() or event.isError()
         value = event
       Bacon.noMore
     UpdateBarrier.whenDoneWith justInitValue, ->


fix #338

fix #338

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -822,6 +822,7 @@
           if @hasSubscribers()
             Bacon.more
           else
+            unsubscribeFromSource()
             Bacon.noMore
         else
           queue = (queue or []).concat([event])
@@ -842,7 +843,10 @@
         subscription = { sink: sink }
         subscriptions = subscriptions.concat(subscription)
         if subscriptions.length == 1
-          unsubscribeFromSource = subscribe @handleEvent
+          unsubSrc = subscribe @handleEvent
+          unsubscribeFromSource = ->
+            unsubSrc()
+            unsubscribeFromSource = nop
         assertFunction unsubscribeFromSource
         =>
           removeSub subscription


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -1,5 +1,6 @@
 module "Metered"

-test "should exist"
+test "should exist", ->
+

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -1,4 +1,4 @@
-module "Metered'
+module 'Metered'

 test 'should exist', ->
   obj = GameObject()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -1,6 +1,6 @@
 module "Metered"

 test "should exist", ->
-
+  obj = G

 module()


Update ConnectedComponent.coffee

Update ConnectedComponent.coffee

--- client/lib/ConnectedComponent.coffee
+++ client/lib/ConnectedComponent.coffee
@@ -10,6 +10,28 @@
 import awaitAll from '../lib/awaitAll'

 export default ConnectedComponent = (Component, options) ->
+       `class ErrorBoundary extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = { error: null, errorInfo: null };
+  }
+
+  componentDidCatch(error, errorInfo) {
+    this.setState({
+      error: error,
+      errorInfo: errorInfo
+    })
+  }
+
+  render() {
+    if (this.state.errorInfo) {
+      return (
+
<FontAwesome name="exclamation-circle"/>

+      );
+    }
+    return this.props.children;
+  }
+}`
     class Result extends React.Component
         constructor: (props) ->
             super(props)
@@ -48,7 +70,7 @@
                 delete componentProps.saveDataPromises
                 for key, url of @urls()
                     componentProps[key] = @props.data(url)
-                return `<Component  {...componentProps}/>`
+                return `<ErrorBoundary><Component  {...componentProps}/></ErrorBoundary>`

         componentWillMount: ->
             if not window?


test/ide/workspace: remove the test suite

--- client/test/lib/ide/workspace.coffee
+++ client/test/lib/ide/workspace.coffee
@@ -1,76 +0,0 @@
-helpers = require '../helpers/helpers.js'
-assert  = require 'assert'
-
-
-module.exports =
-
-
-  createWorkspaceFromSidebar: (browser) ->
-
-    helpers.beginTest(browser)
-    helpers.waitForVMRunning(browser)
-    helpers.createWorkspace(browser)
-    browser.end()
-
-
-  deleteWorkspace: (browser) ->
-
-    helpers.beginTest(browser)
-    helpers.waitForVMRunning(browser)
-
-    workspaceName = helpers.createWorkspace(browser)
-
-    helpers.deleteWorkspace(browser, workspaceName)
-    browser.end()
-
-
-  createWorkspaceFromFileTree: (browser) ->
-
-    user = helpers.beginTest(browser)
-    helpers.waitForVMRunning(browser)
-
-    folderData = helpers.createFolder(browser, user)
-    name       = '~/' + folderData.name
-
-    browser
-      .waitForElementVisible  folderData.selector, 50000
-      .click                  folderData.selector
-      .waitForElementVisible  folderData.selector + ' + .chevron', 20000
-      .click                  folderData.selector + ' + .chevron'
-      .waitForElementVisible  '.context-list-wrapper', 20000
-      .click                  '.context-list-wrapper li.workspace-from-here'
-      .url (data) ->
-        url    = data.value
-
-        vmName   = url.split('/IDE/')[1].split('/')[0]
-        selector = '.activity-sidebar .kdlistitemview-main-nav.workspace'
-        path     = ' a[href="/IDE/' + vmName + '/' + folderData.name + '"]'
-
-        browser
-          .pause                  5000 # wait for new workspace on side bar
-          .waitForElementVisible  selector + path, 20000 #Assertion
-          .waitForElementVisible  '.vm-info', 20000
-          .assert.containsText    '.vm-info', name # Assertion
-          .end()
-
-
-  switchAnotherWorkspace: (browser, workspaceName) ->
-
-    helpers.beginTest(browser)
-    helpers.waitForVMRunning(browser)
-    workspaceName1 = helpers.createWorkspace(browser)
-    workspaceName2 = helpers.createWorkspace(browser)
-    vmName         = 'koding-vm-0'
-    workspaceLink1 = "a[href='/IDE/#{vmName}/#{workspaceName1}']"
-    workspaceLink2 = "a[href='/IDE/#{vmName}/#{workspaceName2}']"
-
-    browser
-      .waitForElementPresent  workspaceLink1, 20000 # Assertion
-      .click                  workspaceLink1
-      .pause                  300
-      .assert.containsText    '.vm-info', '~/Workspaces/' + workspaceName1 # Assertion
-      .waitForElementPresent  workspaceLink2, 20000 # Assertion
-      .click                  workspaceLink2
-      .pause                  300
-      .assert.containsText    '.vm-info', '~/Workspaces/' + workspaceName2 # Assertion
-      .end()


More robust addSemanticUiStyles()

More robust addSemanticUiStyles()

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -759,7 +759,7 @@
   addSemanticUiStyles: ($element) ->
     $form = if $element.is('form') then $element else $('form', $element)
     # Only apply to root form-group elements to avoid matching other elements.
-    $('> .form-group', $form).addClass('ui field')
+    $('> .form-group, .ui.segment > .form-group', $form).addClass('ui field')

 ####################################################################################################
 # MISC AUXILIARY


Ensured onSubmit() triggers an onSuccess(). Prevented duplicate keys in modifiers. Allowed overriding formType with null.

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -27,6 +27,9 @@
         # Perform logic for submitting bulk forms.
         if Form.isBulk(template)
           Form.submitBulkForm(insertDoc, updateDoc, currentDoc, @, template)
+        else if !onSubmit?
+          # Ensure onSuccess() is called.
+          @done()
         # If no result is provided, prevent the form submission from refreshing the page.
         return (result ? false)

@@ -86,6 +89,9 @@
           doc = Collections.simulateModifierUpdate(@template.data.doc, modifier)
           doc = formToDoc.call(@, doc)
           modifier.$set = Objects.flattenProperties(doc)
+          # Ensure keys in $set are not present in $unset.
+          if modifier.$unset
+            _.each modifier.$set, (value, key) -> delete modifier.$unset[key]
           delete modifier.$set._id
           modifier
       AutoForm.addHooks name, hooks
@@ -109,9 +115,11 @@
       formType: ->
         return if Form.isBulk()
         doc = Form.getDocs()[0]
-        type = formArgs.type
-        return type if type
-        if doc then 'update' else 'insert'
+        type = Form.getTemplate().settings.formType
+        # Allow passing type = null to trigger onSubmit() hook.
+        if type == undefined then type = formArgs.type
+        if type == undefined then type = if doc then 'update' else 'insert'
+        type
       submitText: -> if Form.getDocs().length > 0 then 'Save' else 'Create'
       hasDoc: -> Form.hasDoc()
       isBulk: -> Form.isBulk()


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -175,7 +175,7 @@

   self

-Machine = function(name, object, options, block) {
+Machine = (name, object, options, block) ->
   events = EventCollection()
   states = StateCollection()
   callbacks = CallbackCollection()


[Logging] Fix IP logging for sendCode

[Logging] Fix IP logging for sendCode

View the database, "ip" field is now logged.

--- server/codes/Codes.coffee
+++ server/codes/Codes.coffee
@@ -18,6 +18,7 @@
     numberOfDigits
     userMobileNumber
     message
+    ip
   } = options

   numberOfDigits ?= DEFAULT_NUMBER_OF_DIGITS
@@ -29,6 +30,7 @@
     message
     code
     sentTime: new Date
+    ip
   }

   message ?= 'Code:'


Logger: Log incoming socket events

Logger: Log incoming socket events

--- server/connections.coffee
+++ server/connections.coffee
@@ -1,6 +1,7 @@
 {_} = require 'underscore'
 sockjs = require 'sockjs'
 {User} = require './user'
+logger = require './logger'

 # A wrapper for sockjs which manages multiple connections with a higher level of abstraction.
 # Todo: Don't use array parameters and use object parameters instead
@@ -53,6 +54,7 @@
     @callbacks[type].push(callback)

   trigger: (user, eventName, args...) ->
+    logger.log('ConnectionServer#trigger', eventName: eventName, args: args)
     for callback in (@callbacks[eventName] || [])
       callback.apply(user, [user, args...])


SummaryView: listen to thumbnail changes

--- coffee/src/views/summary_view.coffee
+++ coffee/src/views/summary_view.coffee
@@ -63,10 +63,11 @@
     @model = model

     @listenTo @model, 'change', =>
+      thumbChanged = @model.thumbnail() isnt @$('img').attr('src')
       titleChanged = @model.title() isnt @value 'title'
       descChanged = @model.description() isnt @value 'description'

-      if @rendering and (titleChanged or descChanged)
+      if @rendering and (titleChanged or descChanged or thumbChanged)
         @renderData()

     if @rendering


fix test again

fix test again

--- components/embedded_map/index.coffee
+++ components/embedded_map/index.coffee
@@ -1,10 +1,10 @@
 sd = require('sharify').data
-mapapi = require('google-maps-api')(sd.GOOGLE_MAPS_API_KEY)
-geocode = require('google-maps-api/geocode')

 module.exports =
   init: ({ location, id }) ->
     return unless sd.GOOGLE_MAPS_API_KEY
+    mapapi = require('google-maps-api')(sd.GOOGLE_MAPS_API_KEY)
+    geocode = require('google-maps-api/geocode')

     mapapi().then ->
       if location.get('coordinates')


#322 Scroll to active tree, not active node

--- app/assets/javascripts/oxalis/view/skeletontracing/skeletontracing_view.coffee
+++ app/assets/javascripts/oxalis/view/skeletontracing/skeletontracing_view.coffee
@@ -164,14 +164,13 @@
       newIcon = activeHref.parent("li").children("i")
       newIcon.toggleClass("fa-angle-right", true)

-      # animate scrolling to the new comment
-      $("#comment-container").animate({
-        scrollTop: newIcon.offset().top - $("#comment-container").offset().top + $("#comment-container").scrollTop()}, 250)
-    else
-      activeTree = $("#comment-container span[data-treeid=#{@model.skeletonTracing.getActiveTreeId()}]")
-      if activeTree.length
-        $("#comment-container").animate({
-          scrollTop: activeTree.offset().top - $("#comment-container").offset().top + $("#comment-container").scrollTop()}, 250)
+    # animate scrolling to the active tree
+    activeTree = $("#comment-container span[data-treeid=#{@model.skeletonTracing.getActiveTreeId()}]")
+    if activeTree.length
+      $("#comment-container").animate(
+        scrollTop: activeTree.offset().top - $("#comment-container").offset().top + $("#comment-container").scrollTop()
+        250
+      )

   updateActiveTree : ->


db tests moved to integration-test section

--- tests/dbtest.coffee
+++ tests/dbtest.coffee
@@ -6,22 +6,23 @@
 buildModels = require('./../src/server/models/BuildModels')
 rebuildCache = require('./../src/server/models/RebuildCache')

-describe('models/db object', ()->
-  it('should contain sequelize object', ()->
-    db.should.have.property('sequelize')
+if process.env.MASONS_ENV == 'TEST'
+
+  describe('models/db object', ()->
+    it('should contain sequelize object', ()->
+      db.should.have.property('sequelize')
+    )
+    it('should contain Player model', ()->
+      db.should.have.property('Player')
+    )
+    it('should contain Game model', ()->
+      db.should.have.property('Game')
+    )
+    it('should contain PlayerGame model', ()->
+      db.should.have.property('PlayerGame')
+    )
   )
-  it('should contain Player model', ()->
-    db.should.have.property('Player')
-  )
-  it('should contain Game model', ()->
-    db.should.have.property('Game')
-  )
-  it('should contain PlayerGame model', ()->
-    db.should.have.property('PlayerGame')
-  )
-)
-
-if process.env.MASONS_ENV == 'TEST'
+
   describe('Test games', ()->

     beforeEach((done)->


Fix stealth rock message

Fix stealth rock message

--- shared/canned_text.coffee
+++ shared/canned_text.coffee
@@ -47,7 +47,7 @@
       SPIKES_START: "Spikes were scattered all around $ts's feet!"
       SPIKES_HURT: "$p is hurt by the spikes!"
       STEALTH_ROCK_START: "Pointed stones float in the air around $ts!"
-      STEALTH_ROCK_HURT: "$p is hurt by the spikes!"
+      STEALTH_ROCK_HURT: "Pointed stones dug into $p!"
       TOXIC_SPIKES_START: "Poison spikes were scattered all around $ts's feet!"
       TOXIC_SPIKES_END: "The poison spikes disappeared from around $ts's feet!"
       TRAP_HURT: "$p is hurt by $m!"


Code style, and trying to catch a userselection that's not a range.

--- hallo.coffee
+++ hallo.coffee
@@ -140,17 +140,17 @@

         # Only supports one range for now (i.e. no multiselection)
         getSelection: ->
-            if ( jQuery.browser.msie )
+            if jQuery.browser.msie
                 range = document.selection.createRange()
             else
-                if ( window.getSelection )
+                if window.getSelection
                     userSelection = window.getSelection()
                 else if (document.selection) #opera
                     userSelection = document.selection.createRange()
                 else
                     throw "Your browser does not support selection handling"

-                if ( userSelection.getRangeAt )
+                if userSelection.rangeCount > 0
                     range = userSelection.getRangeAt(0)
                 else
                     range = userSelection


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -232,7 +232,7 @@
     red = Color(255, 0, 0)

     # modifies red in place to make it into cyan
-    red.complement()
+    red.complement$()

     red.toString()
     # => 'rgba(0, 255, 255, 1)'


adding xkcd colors

adding xkcd colors

--- src/color.coffee
+++ src/color.coffee
@@ -114,14 +114,14 @@
           other.g() == self.g() &&
           other.b() == self.b() &&
           other.a() == self.a()
-
+
       hslToRgb: (hsl) ->
         h = hsl[0] / 360.0
         s = hsl[1]
         l = hsl[2]
-
+
         r = g = b = null
-
+
         hueToRgb = (p, q, t) ->
           t += 1 if t < 0
           t -= 1 if t > 1
@@ -130,7 +130,7 @@
           return q if t < 1/2
           return p + (q - p) * (2/3 - t) * 6 if t < 2/3
           return p
-
+
         if s == 0
           r = g = b = l
         else
@@ -139,23 +139,23 @@
           r = hueToRgb(p, q, h + 1/3)
           g = hueToRgb(p, q, h)
           b = hueToRgb(p, q, h - 1/3)
-
+
         return Color([(r * 0xFF).round(), (g * 0xFF).round(), (b * 0xFF).round()])
-
+
       lighten: (amount) ->
         hsl = self.toHsl()
         hsl[0] = hsl[0].round()
         hsl[2] = hsl[2] + amount
-
+
         return Color(self.hslToRgb(hsl))

       darken: (amount) ->
         hsl = self.toHsl()
         hsl[0] = hsl[0].round()
         hsl[2] = hsl[2] - amount
-
+
         return Color(self.hslToRgb(hsl))
-
+
       rgba: -> "rgba(#{self.r()}, #{self.g()}, #{self.b()}, #{self.a()})"

       toHex: ->
@@ -171,12 +171,12 @@
           return padString(hexString(number))

         "##{hexFromNumber(channels[0])}#{hexFromNumber(channels[1])}#{hexFromNumber(channels[2])}"
-
+
       toHsl: ->
         r = channels[0] / 255.0
         g = channels[1] / 255.0
         b = channels[2] / 255.0
-
+
         min = Math.min(r, g, b)
         max = Math.max(r, g, b)

@@ -187,16 +187,16 @@
         else
           delta = max - min
           saturation = (if lightness > 0.5 then delta / (2 - max - min) else delta / (max + min))
-
+
           switch max
             when r then hue = (g - b) / delta + (if g < b then 6 else 0)
             when g then hue = (b - r) / delta + 2
             when b then hue = (r - g) / delta + 4
-
+
           hue *= 60
-
+
         return [hue.round(), saturation, lightness, channels[3]]
-
+
       toString: -> self.rgba()

     return self
@@ -1771,6 +1771,955 @@
     ["FFFFB4", "Portafino"]
     ["FFFFF0", "Ivory"]
     ["FFFFFF", "White"]
+    ["acc2d9", "cloudy blue"]
+    ["56ae57", "dark pastel green"]
+    ["b2996e", "dust"]
+    ["a8ff04", "electric lime"]
+    ["69d84f", "fresh green"]
+    ["894585", "light eggplant"]
+    ["70b23f", "nasty green"]
+    ["d4ffff", "really light blue"]
+    ["65ab7c", "tea"]
+    ["952e8f", "warm purple"]
+    ["fcfc81", "yellowish tan"]
+    ["a5a391", "cement"]
+    ["388004", "dark grass green"]
+    ["4c9085", "dusty teal"]
+    ["5e9b8a", "grey teal"]
+    ["efb435", "macaroni and cheese"]
+    ["d99b82", "pinkish tan"]
+    ["0a5f38", "spruce"]
+    ["0c06f7", "strong blue"]
+    ["61de2a", "toxic green"]
+    ["3778bf", "windows blue"]
+    ["2242c7", "blue blue"]
+    ["533cc6", "blue with a hint of purple"]
+    ["9bb53c", "booger"]
+    ["05ffa6", "bright sea green"]
+    ["1f6357", "dark green blue"]
+    ["017374", "deep turquoise"]
+    ["0cb577", "green teal"]
+    ["ff0789", "strong pink"]
+    ["afa88b", "bland"]
+    ["08787f", "deep aqua"]
+    ["dd85d7", "lavender pink"]
+    ["a6c875", "light moss green"]
+    ["a7ffb5", "light seafoam green"]
+    ["c2b709", "olive yellow"]
+    ["e78ea5", "pig pink"]
+    ["966ebd", "deep lilac"]
+    ["ccad60", "desert"]
+    ["ac86a8", "dusty lavender"]
+    ["947e94", "purpley grey"]
+    ["983fb2", "purply"]
+    ["ff63e9", "candy pink"]
+    ["b2fba5", "light pastel green"]
+    ["63b365", "boring green"]
+    ["8ee53f", "kiwi green"]
+    ["b7e1a1", "light grey green"]
+    ["ff6f52", "orange pink"]
+    ["bdf8a3", "tea green"]
+    ["d3b683", "very light brown"]
+    ["fffcc4", "egg shell"]
+    ["430541", "eggplant purple"]
+    ["ffb2d0", "powder pink"]
+    ["997570", "reddish grey"]
+    ["ad900d", "baby shit brown"]
+    ["c48efd", "liliac"]
+    ["507b9c", "stormy blue"]
+    ["7d7103", "ugly brown"]
+    ["fffd78", "custard"]
+    ["da467d", "darkish pink"]
+    ["410200", "deep brown"]
+    ["c9d179", "greenish beige"]
+    ["fffa86", "manilla"]
+    ["5684ae", "off blue"]
+    ["6b7c85", "battleship grey"]
+    ["6f6c0a", "browny green"]
+    ["7e4071", "bruise"]
+    ["009337", "kelley green"]
+    ["d0e429", "sickly yellow"]
+    ["fff917", "sunny yellow"]
+    ["1d5dec", "azul"]
+    ["054907", "darkgreen"]
+    ["b5ce08", "green/yellow"]
+    ["8fb67b", "lichen"]
+    ["c8ffb0", "light light green"]
+    ["fdde6c", "pale gold"]
+    ["ffdf22", "sun yellow"]
+    ["a9be70", "tan green"]
+    ["6832e3", "burple"]
+    ["fdb147", "butterscotch"]
+    ["c7ac7d", "toupe"]
+    ["fff39a", "dark cream"]
+    ["850e04", "indian red"]
+    ["efc0fe", "light lavendar"]
+    ["40fd14", "poison green"]
+    ["b6c406", "baby puke green"]
+    ["9dff00", "bright yellow green"]
+    ["3c4142", "charcoal grey"]
+    ["f2ab15", "squash"]
+    ["ac4f06", "cinnamon"]
+    ["c4fe82", "light pea green"]
+    ["2cfa1f", "radioactive green"]
+    ["9a6200", "raw sienna"]
+    ["ca9bf7", "baby purple"]
+    ["875f42", "cocoa"]
+    ["3a2efe", "light royal blue"]
+    ["fd8d49", "orangeish"]
+    ["8b3103", "rust brown"]
+    ["cba560", "sand brown"]
+    ["698339", "swamp"]
+    ["0cdc73", "tealish green"]
+    ["b75203", "burnt siena"]
+    ["7f8f4e", "camo"]
+    ["26538d", "dusk blue"]
+    ["63a950", "fern"]
+    ["c87f89", "old rose"]
+    ["b1fc99", "pale light green"]
+    ["ff9a8a", "peachy pink"]
+    ["f6688e", "rosy pink"]
+    ["76fda8", "light bluish green"]
+    ["53fe5c", "light bright green"]
+    ["4efd54", "light neon green"]
+    ["a0febf", "light seafoam"]
+    ["7bf2da", "tiffany blue"]
+    ["bcf5a6", "washed out green"]
+    ["ca6b02", "browny orange"]
+    ["107ab0", "nice blue"]
+    ["2138ab", "sapphire"]
+    ["719f91", "greyish teal"]
+    ["fdb915", "orangey yellow"]
+    ["fefcaf", "parchment"]
+    ["fcf679", "straw"]
+    ["1d0200", "very dark brown"]
+    ["cb6843", "terracota"]
+    ["31668a", "ugly blue"]
+    ["247afd", "clear blue"]
+    ["ffffb6", "creme"]
+    ["90fda9", "foam green"]
+    ["86a17d", "grey/green"]
+    ["fddc5c", "light gold"]
+    ["78d1b6", "seafoam blue"]
+    ["13bbaf", "topaz"]
+    ["fb5ffc", "violet pink"]
+    ["20f986", "wintergreen"]
+    ["ffe36e", "yellow tan"]
+    ["9d0759", "dark fuchsia"]
+    ["3a18b1", "indigo blue"]
+    ["c2ff89", "light yellowish green"]
+    ["d767ad", "pale magenta"]
+    ["720058", "rich purple"]
+    ["ffda03", "sunflower yellow"]
+    ["01c08d", "green/blue"]
+    ["ac7434", "leather"]
+    ["014600", "racing green"]
+    ["9900fa", "vivid purple"]
+    ["02066f", "dark royal blue"]
+    ["8e7618", "hazel"]
+    ["d1768f", "muted pink"]
+    ["96b403", "booger green"]
+    ["fdff63", "canary"]
+    ["95a3a6", "cool grey"]
+    ["7f684e", "dark taupe"]
+    ["751973", "darkish purple"]
+    ["089404", "true green"]
+    ["ff6163", "coral pink"]
+    ["598556", "dark sage"]
+    ["214761", "dark slate blue"]
+    ["3c73a8", "flat blue"]
+    ["ba9e88", "mushroom"]
+    ["021bf9", "rich blue"]
+    ["734a65", "dirty purple"]
+    ["23c48b", "greenblue"]
+    ["8fae22", "icky green"]
+    ["e6f2a2", "light khaki"]
+    ["4b57db", "warm blue"]
+    ["d90166", "dark hot pink"]
+    ["015482", "deep sea blue"]
+    ["9d0216", "carmine"]
+    ["728f02", "dark yellow green"]
+    ["ffe5ad", "pale peach"]
+    ["4e0550", "plum purple"]
+    ["f9bc08", "golden rod"]
+    ["ff073a", "neon red"]
+    ["c77986", "old pink"]
+    ["d6fffe", "very pale blue"]
+    ["fe4b03", "blood orange"]
+    ["fd5956", "grapefruit"]
+    ["fce166", "sand yellow"]
+    ["b2713d", "clay brown"]
+    ["1f3b4d", "dark blue grey"]
+    ["699d4c", "flat green"]
+    ["56fca2", "light green blue"]
+    ["fb5581", "warm pink"]
+    ["3e82fc", "dodger blue"]
+    ["a0bf16", "gross green"]
+    ["d6fffa", "ice"]
+    ["4f738e", "metallic blue"]
+    ["ffb19a", "pale salmon"]
+    ["5c8b15", "sap green"]
+    ["54ac68", "algae"]
+    ["89a0b0", "bluey grey"]
+    ["7ea07a", "greeny grey"]
+    ["1bfc06", "highlighter green"]
+    ["cafffb", "light light blue"]
+    ["b6ffbb", "light mint"]
+    ["a75e09", "raw umber"]
+    ["152eff", "vivid blue"]
+    ["8d5eb7", "deep lavender"]
+    ["5f9e8f", "dull teal"]
+    ["63f7b4", "light greenish blue"]
+    ["606602", "mud green"]
+    ["fc86aa", "pinky"]
+    ["8c0034", "red wine"]
+    ["758000", "shit green"]
+    ["ab7e4c", "tan brown"]
+    ["030764", "darkblue"]
+    ["fe86a4", "rosa"]
+    ["d5174e", "lipstick"]
+    ["fed0fc", "pale mauve"]
+    ["680018", "claret"]
+    ["fedf08", "dandelion"]
+    ["fe420f", "orangered"]
+    ["6f7c00", "poop green"]
+    ["ca0147", "ruby"]
+    ["1b2431", "dark"]
+    ["00fbb0", "greenish turquoise"]
+    ["db5856", "pastel red"]
+    ["ddd618", "piss yellow"]
+    ["41fdfe", "bright cyan"]
+    ["cf524e", "dark coral"]
+    ["21c36f", "algae green"]
+    ["a90308", "darkish red"]
+    ["6e1005", "reddy brown"]
+    ["fe828c", "blush pink"]
+    ["4b6113", "camouflage green"]
+    ["4da409", "lawn green"]
+    ["beae8a", "putty"]
+    ["0339f8", "vibrant blue"]
+    ["a88f59", "dark sand"]
+    ["5d21d0", "purple/blue"]
+    ["feb209", "saffron"]
+    ["4e518b", "twilight"]
+    ["964e02", "warm brown"]
+    ["85a3b2", "bluegrey"]
+    ["ff69af", "bubble gum pink"]
+    ["c3fbf4", "duck egg blue"]
+    ["2afeb7", "greenish cyan"]
+    ["005f6a", "petrol"]
+    ["0c1793", "royal"]
+    ["ffff81", "butter"]
+    ["f0833a", "dusty orange"]
+    ["f1f33f", "off yellow"]
+    ["b1d27b", "pale olive green"]
+    ["fc824a", "orangish"]
+    ["71aa34", "leaf"]
+    ["b7c9e2", "light blue grey"]
+    ["4b0101", "dried blood"]
+    ["a552e6", "lightish purple"]
+    ["af2f0d", "rusty red"]
+    ["8b88f8", "lavender blue"]
+    ["9af764", "light grass green"]
+    ["a6fbb2", "light mint green"]
+    ["ffc512", "sunflower"]
+    ["750851", "velvet"]
+    ["c14a09", "brick orange"]
+    ["fe2f4a", "lightish red"]
+    ["0203e2", "pure blue"]
+    ["0a437a", "twilight blue"]
+    ["a50055", "violet red"]
+    ["ae8b0c", "yellowy brown"]
+    ["fd798f", "carnation"]
+    ["bfac05", "muddy yellow"]
+    ["3eaf76", "dark seafoam green"]
+    ["c74767", "deep rose"]
+    ["b9484e", "dusty red"]
+    ["647d8e", "grey/blue"]
+    ["bffe28", "lemon lime"]
+    ["d725de", "purple/pink"]
+    ["b29705", "brown yellow"]
+    ["673a3f", "purple brown"]
+    ["a87dc2", "wisteria"]
+    ["fafe4b", "banana yellow"]
+    ["c0022f", "lipstick red"]
+    ["0e87cc", "water blue"]
+    ["8d8468", "brown grey"]
+    ["ad03de", "vibrant purple"]
+    ["8cff9e", "baby green"]
+    ["94ac02", "barf green"]
+    ["c4fff7", "eggshell blue"]
+    ["fdee73", "sandy yellow"]
+    ["33b864", "cool green"]
+    ["fff9d0", "pale"]
+    ["758da3", "blue/grey"]
+    ["f504c9", "hot magenta"]
+    ["77a1b5", "greyblue"]
+    ["8756e4", "purpley"]
+    ["889717", "baby shit green"]
+    ["c27e79", "brownish pink"]
+    ["017371", "dark aquamarine"]
+    ["9f8303", "diarrhea"]
+    ["f7d560", "light mustard"]
+    ["bdf6fe", "pale sky blue"]
+    ["75b84f", "turtle green"]
+    ["9cbb04", "bright olive"]
+    ["29465b", "dark grey blue"]
+    ["696006", "greeny brown"]
+    ["adf802", "lemon green"]
+    ["c1c6fc", "light periwinkle"]
+    ["35ad6b", "seaweed green"]
+    ["fffd37", "sunshine yellow"]
+    ["a442a0", "ugly purple"]
+    ["f36196", "medium pink"]
+    ["947706", "puke brown"]
+    ["fff4f2", "very light pink"]
+    ["1e9167", "viridian"]
+    ["b5c306", "bile"]
+    ["feff7f", "faded yellow"]
+    ["cffdbc", "very pale green"]
+    ["0add08", "vibrant green"]
+    ["87fd05", "bright lime"]
+    ["1ef876", "spearmint"]
+    ["7bfdc7", "light aquamarine"]
+    ["bcecac", "light sage"]
+    ["bbf90f", "yellowgreen"]
+    ["ab9004", "baby poo"]
+    ["1fb57a", "dark seafoam"]
+    ["00555a", "deep teal"]
+    ["a484ac", "heather"]
+    ["c45508", "rust orange"]
+    ["3f829d", "dirty blue"]
+    ["548d44", "fern green"]
+    ["c95efb", "bright lilac"]
+    ["3ae57f", "weird green"]
+    ["016795", "peacock blue"]
+    ["87a922", "avocado green"]
+    ["f0944d", "faded orange"]
+    ["5d1451", "grape purple"]
+    ["25ff29", "hot green"]
+    ["d0fe1d", "lime yellow"]
+    ["ffa62b", "mango"]
+    ["01b44c", "shamrock"]
+    ["ff6cb5", "bubblegum"]
+    ["6b4247", "purplish brown"]
+    ["c7c10c", "vomit yellow"]
+    ["b7fffa", "pale cyan"]
+    ["aeff6e", "key lime"]
+    ["ec2d01", "tomato red"]
+    ["76ff7b", "lightgreen"]
+    ["730039", "merlot"]
+    ["040348", "night blue"]
+    ["df4ec8", "purpleish pink"]
+    ["6ecb3c", "apple"]
+    ["8f9805", "baby poop green"]
+    ["5edc1f", "green apple"]
+    ["d94ff5", "heliotrope"]
+    ["c8fd3d", "yellow/green"]
+    ["070d0d", "almost black"]
+    ["4984b8", "cool blue"]
+    ["51b73b", "leafy green"]
+    ["ac7e04", "mustard brown"]
+    ["4e5481", "dusk"]
+    ["876e4b", "dull brown"]
+    ["58bc08", "frog green"]
+    ["2fef10", "vivid green"]
+    ["2dfe54", "bright light green"]
+    ["0aff02", "fluro green"]
+    ["9cef43", "kiwi"]
+    ["18d17b", "seaweed"]
+    ["35530a", "navy green"]
+    ["1805db", "ultramarine blue"]
+    ["6258c4", "iris"]
+    ["ff964f", "pastel orange"]
+    ["ffab0f", "yellowish orange"]
+    ["8f8ce7", "perrywinkle"]
+    ["24bca8", "tealish"]
+    ["3f012c", "dark plum"]
+    ["cbf85f", "pear"]
+    ["ff724c", "pinkish orange"]
+    ["280137", "midnight purple"]
+    ["b36ff6", "light urple"]
+    ["48c072", "dark mint"]
+    ["bccb7a", "greenish tan"]
+    ["a8415b", "light burgundy"]
+    ["06b1c4", "turquoise blue"]
+    ["cd7584", "ugly pink"]
+    ["f1da7a", "sandy"]
+    ["ff0490", "electric pink"]
+    ["805b87", "muted purple"]
+    ["50a747", "mid green"]
+    ["a8a495", "greyish"]
+    ["cfff04", "neon yellow"]
+    ["ffff7e", "banana"]
+    ["ff7fa7", "carnation pink"]
+    ["ef4026", "tomato"]
+    ["3c9992", "sea"]
+    ["886806", "muddy brown"]
+    ["04f489", "turquoise green"]
+    ["fef69e", "buff"]
+    ["cfaf7b", "fawn"]
+    ["3b719f", "muted blue"]
+    ["fdc1c5", "pale rose"]
+    ["20c073", "dark mint green"]
+    ["9b5fc0", "amethyst"]
+    ["0f9b8e", "blue/green"]
+    ["742802", "chestnut"]
+    ["9db92c", "sick green"]
+    ["a4bf20", "pea"]
+    ["cd5909", "rusty orange"]
+    ["ada587", "stone"]
+    ["be013c", "rose red"]
+    ["b8ffeb", "pale aqua"]
+    ["dc4d01", "deep orange"]
+    ["a2653e", "earth"]
+    ["638b27", "mossy green"]
+    ["419c03", "grassy green"]
+    ["b1ff65", "pale lime green"]
+    ["9dbcd4", "light grey blue"]
+    ["fdfdfe", "pale grey"]
+    ["77ab56", "asparagus"]
+    ["464196", "blueberry"]
+    ["990147", "purple red"]
+    ["befd73", "pale lime"]
+    ["32bf84", "greenish teal"]
+    ["af6f09", "caramel"]
+    ["a0025c", "deep magenta"]
+    ["ffd8b1", "light peach"]
+    ["7f4e1e", "milk chocolate"]
+    ["bf9b0c", "ocher"]
+    ["6ba353", "off green"]
+    ["f075e6", "purply pink"]
+    ["7bc8f6", "lightblue"]
+    ["475f94", "dusky blue"]
+    ["f5bf03", "golden"]
+    ["fffeb6", "light beige"]
+    ["fffd74", "butter yellow"]
+    ["895b7b", "dusky purple"]
+    ["436bad", "french blue"]
+    ["d0c101", "ugly yellow"]
+    ["c6f808", "greeny yellow"]
+    ["f43605", "orangish red"]
+    ["02c14d", "shamrock green"]
+    ["b25f03", "orangish brown"]
+    ["2a7e19", "tree green"]
+    ["490648", "deep violet"]
+    ["536267", "gunmetal"]
+    ["5a06ef", "blue/purple"]
+    ["cf0234", "cherry"]
+    ["c4a661", "sandy brown"]
+    ["978a84", "warm grey"]
+    ["1f0954", "dark indigo"]
+    ["03012d", "midnight"]
+    ["2bb179", "bluey green"]
+    ["c3909b", "grey pink"]
+    ["a66fb5", "soft purple"]
+    ["770001", "blood"]
+    ["922b05", "brown red"]
+    ["7d7f7c", "medium grey"]
+    ["990f4b", "berry"]
+    ["8f7303", "poo"]
+    ["c83cb9", "purpley pink"]
+    ["fea993", "light salmon"]
+    ["acbb0d", "snot"]
+    ["c071fe", "easter purple"]
+    ["ccfd7f", "light yellow green"]
+    ["00022e", "dark navy blue"]
+    ["828344", "drab"]
+    ["ffc5cb", "light rose"]
+    ["ab1239", "rouge"]
+    ["b0054b", "purplish red"]
+    ["99cc04", "slime green"]
+    ["937c00", "baby poop"]
+    ["019529", "irish green"]
+    ["ef1de7", "pink/purple"]
+    ["000435", "dark navy"]
+    ["42b395", "greeny blue"]
+    ["9d5783", "light plum"]
+    ["c8aca9", "pinkish grey"]
+    ["c87606", "dirty orange"]
+    ["aa2704", "rust red"]
+    ["e4cbff", "pale lilac"]
+    ["fa4224", "orangey red"]
+    ["0804f9", "primary blue"]
+    ["5cb200", "kermit green"]
+    ["76424e", "brownish purple"]
+    ["6c7a0e", "murky green"]
+    ["fbdd7e", "wheat"]
+    ["2a0134", "very dark purple"]
+    ["044a05", "bottle green"]
+    ["fd4659", "watermelon"]
+    ["0d75f8", "deep sky blue"]
+    ["fe0002", "fire engine red"]
+    ["cb9d06", "yellow ochre"]
+    ["fb7d07", "pumpkin orange"]
+    ["b9cc81", "pale olive"]
+    ["edc8ff", "light lilac"]
+    ["61e160", "lightish green"]
+    ["8ab8fe", "carolina blue"]
+    ["920a4e", "mulberry"]
+    ["fe02a2", "shocking pink"]
+    ["9a3001", "auburn"]
+    ["65fe08", "bright lime green"]
+    ["befdb7", "celadon"]
+    ["b17261", "pinkish brown"]
+    ["885f01", "poo brown"]
+    ["02ccfe", "bright sky blue"]
+    ["c1fd95", "celery"]
+    ["836539", "dirt brown"]
+    ["fb2943", "strawberry"]
+    ["84b701", "dark lime"]
+    ["b66325", "copper"]
+    ["7f5112", "medium brown"]
+    ["5fa052", "muted green"]
+    ["6dedfd", "robin's egg"]
+    ["0bf9ea", "bright aqua"]
+    ["c760ff", "bright lavender"]
+    ["ffffcb", "ivory"]
+    ["f6cefc", "very light purple"]
+    ["155084", "light navy"]
+    ["f5054f", "pink red"]
+    ["645403", "olive brown"]
+    ["7a5901", "poop brown"]
+    ["a8b504", "mustard green"]
+    ["3d9973", "ocean green"]
+    ["000133", "very dark blue"]
+    ["76a973", "dusty green"]
+    ["2e5a88", "light navy blue"]
+    ["0bf77d", "minty green"]
+    ["bd6c48", "adobe"]
+    ["ac1db8", "barney"]
+    ["2baf6a", "jade green"]
+    ["26f7fd", "bright light blue"]
+    ["aefd6c", "light lime"]
+    ["9b8f55", "dark khaki"]
+    ["ffad01", "orange yellow"]
+    ["c69c04", "ocre"]
+    ["f4d054", "maize"]
+    ["de9dac", "faded pink"]
+    ["05480d", "british racing green"]
+    ["c9ae74", "sandstone"]
+    ["60460f", "mud brown"]
+    ["98f6b0", "light sea green"]
+    ["8af1fe", "robin egg blue"]
+    ["2ee8bb", "aqua marine"]
+    ["11875d", "dark sea green"]
+    ["fdb0c0", "soft pink"]
+    ["b16002", "orangey brown"]
+    ["f7022a", "cherry red"]
+    ["d5ab09", "burnt yellow"]
+    ["86775f", "brownish grey"]
+    ["c69f59", "camel"]
+    ["7a687f", "purplish grey"]
+    ["042e60", "marine"]
+    ["c88d94", "greyish pink"]
+    ["a5fbd5", "pale turquoise"]
+    ["fffe71", "pastel yellow"]
+    ["6241c7", "bluey purple"]
+    ["fffe40", "canary yellow"]
+    ["d3494e", "faded red"]
+    ["985e2b", "sepia"]
+    ["a6814c", "coffee"]
+    ["ff08e8", "bright magenta"]
+    ["9d7651", "mocha"]
+    ["feffca", "ecru"]
+    ["98568d", "purpleish"]
+    ["9e003a", "cranberry"]
+    ["287c37", "darkish green"]
+    ["b96902", "brown orange"]
+    ["ba6873", "dusky rose"]
+    ["ff7855", "melon"]
+    ["94b21c", "sickly green"]
+    ["c5c9c7", "silver"]
+    ["661aee", "purply blue"]
+    ["6140ef", "purpleish blue"]
+    ["9be5aa", "hospital green"]
+    ["7b5804", "shit brown"]
+    ["276ab3", "mid blue"]
+    ["feb308", "amber"]
+    ["8cfd7e", "easter green"]
+    ["6488ea", "soft blue"]
+    ["056eee", "cerulean blue"]
+    ["b27a01", "golden brown"]
+    ["0ffef9", "bright turquoise"]
+    ["fa2a55", "red pink"]
+    ["820747", "red purple"]
+    ["7a6a4f", "greyish brown"]
+    ["f4320c", "vermillion"]
+    ["a13905", "russet"]
+    ["6f828a", "steel grey"]
+    ["a55af4", "lighter purple"]
+    ["ad0afd", "bright violet"]
+    ["004577", "prussian blue"]
+    ["658d6d", "slate green"]
+    ["ca7b80", "dirty pink"]
+    ["005249", "dark blue green"]
+    ["2b5d34", "pine"]
+    ["bff128", "yellowy green"]
+    ["b59410", "dark gold"]
+    ["2976bb", "bluish"]
+    ["014182", "darkish blue"]
+    ["bb3f3f", "dull red"]
+    ["fc2647", "pinky red"]
+    ["a87900", "bronze"]
+    ["82cbb2", "pale teal"]
+    ["667c3e", "military green"]
+    ["fe46a5", "barbie pink"]
+    ["fe83cc", "bubblegum pink"]
+    ["94a617", "pea soup green"]
+    ["a88905", "dark mustard"]
+    ["7f5f00", "shit"]
+    ["9e43a2", "medium purple"]
+    ["062e03", "very dark green"]
+    ["8a6e45", "dirt"]
+    ["cc7a8b", "dusky pink"]
+    ["9e0168", "red violet"]
+    ["fdff38", "lemon yellow"]
+    ["c0fa8b", "pistachio"]
+    ["eedc5b", "dull yellow"]
+    ["7ebd01", "dark lime green"]
+    ["3b5b92", "denim blue"]
+    ["01889f", "teal blue"]
+    ["3d7afd", "lightish blue"]
+    ["5f34e7", "purpley blue"]
+    ["6d5acf", "light indigo"]
+    ["748500", "swamp green"]
+    ["706c11", "brown green"]
+    ["3c0008", "dark maroon"]
+    ["cb00f5", "hot purple"]
+    ["002d04", "dark forest green"]
+    ["658cbb", "faded blue"]
+    ["749551", "drab green"]
+    ["b9ff66", "light lime green"]
+    ["9dc100", "snot green"]
+    ["faee66", "yellowish"]
+    ["7efbb3", "light blue green"]
+    ["7b002c", "bordeaux"]
+    ["c292a1", "light mauve"]
+    ["017b92", "ocean"]
+    ["fcc006", "marigold"]
+    ["657432", "muddy green"]
+    ["d8863b", "dull orange"]
+    ["738595", "steel"]
+    ["aa23ff", "electric purple"]
+    ["08ff08", "fluorescent green"]
+    ["9b7a01", "yellowish brown"]
+    ["f29e8e", "blush"]
+    ["6fc276", "soft green"]
+    ["ff5b00", "bright orange"]
+    ["fdff52", "lemon"]
+    ["866f85", "purple grey"]
+    ["8ffe09", "acid green"]
+    ["eecffe", "pale lavender"]
+    ["510ac9", "violet blue"]
+    ["4f9153", "light forest green"]
+    ["9f2305", "burnt red"]
+    ["728639", "khaki green"]
+    ["de0c62", "cerise"]
+    ["916e99", "faded purple"]
+    ["ffb16d", "apricot"]
+    ["3c4d03", "dark olive green"]
+    ["7f7053", "grey brown"]
+    ["77926f", "green grey"]
+    ["010fcc", "true blue"]
+    ["ceaefa", "pale violet"]
+    ["8f99fb", "periwinkle blue"]
+    ["c6fcff", "light sky blue"]
+    ["5539cc", "blurple"]
+    ["544e03", "green brown"]
+    ["017a79", "bluegreen"]
+    ["01f9c6", "bright teal"]
+    ["c9b003", "brownish yellow"]
+    ["929901", "pea soup"]
+    ["0b5509", "forest"]
+    ["a00498", "barney purple"]
+    ["2000b1", "ultramarine"]
+    ["94568c", "purplish"]
+    ["c2be0e", "puke yellow"]
+    ["748b97", "bluish grey"]
+    ["665fd1", "dark periwinkle"]
+    ["9c6da5", "dark lilac"]
+    ["c44240", "reddish"]
+    ["a24857", "light maroon"]
+    ["825f87", "dusty purple"]
+    ["c9643b", "terra cotta"]
+    ["90b134", "avocado"]
+    ["01386a", "marine blue"]
+    ["25a36f", "teal green"]
+    ["59656d", "slate grey"]
+    ["75fd63", "lighter green"]
+    ["21fc0d", "electric green"]
+    ["5a86ad", "dusty blue"]
+    ["fec615", "golden yellow"]
+    ["fffd01", "bright yellow"]
+    ["dfc5fe", "light lavender"]
+    ["b26400", "umber"]
+    ["7f5e00", "poop"]
+    ["de7e5d", "dark peach"]
+    ["048243", "jungle green"]
+    ["ffffd4", "eggshell"]
+    ["3b638c", "denim"]
+    ["b79400", "yellow brown"]
+    ["84597e", "dull purple"]
+    ["411900", "chocolate brown"]
+    ["7b0323", "wine red"]
+    ["04d9ff", "neon blue"]
+    ["667e2c", "dirty green"]
+    ["fbeeac", "light tan"]
+    ["d7fffe", "ice blue"]
+    ["4e7496", "cadet blue"]
+    ["874c62", "dark mauve"]
+    ["d5ffff", "very light blue"]
+    ["826d8c", "grey purple"]
+    ["ffbacd", "pastel pink"]
+    ["d1ffbd", "very light green"]
+    ["448ee4", "dark sky blue"]
+    ["05472a", "evergreen"]
+    ["d5869d", "dull pink"]
+    ["3d0734", "aubergine"]
+    ["4a0100", "mahogany"]
+    ["f8481c", "reddish orange"]
+    ["02590f", "deep green"]
+    ["89a203", "vomit green"]
+    ["e03fd8", "purple pink"]
+    ["d58a94", "dusty pink"]
+    ["7bb274", "faded green"]
+    ["526525", "camo green"]
+    ["c94cbe", "pinky purple"]
+    ["db4bda", "pink purple"]
+    ["9e3623", "brownish red"]
+    ["b5485d", "dark rose"]
+    ["735c12", "mud"]
+    ["9c6d57", "brownish"]
+    ["028f1e", "emerald green"]
+    ["b1916e", "pale brown"]
+    ["49759c", "dull blue"]
+    ["a0450e", "burnt umber"]
+    ["39ad48", "medium green"]
+    ["b66a50", "clay"]
+    ["8cffdb", "light aqua"]
+    ["a4be5c", "light olive green"]
+    ["cb7723", "brownish orange"]
+    ["05696b", "dark aqua"]
+    ["ce5dae", "purplish pink"]
+    ["c85a53", "dark salmon"]
+    ["96ae8d", "greenish grey"]
+    ["1fa774", "jade"]
+    ["7a9703", "ugly green"]
+    ["ac9362", "dark beige"]
+    ["01a049", "emerald"]
+    ["d9544d", "pale red"]
+    ["fa5ff7", "light magenta"]
+    ["82cafc", "sky"]
+    ["acfffc", "light cyan"]
+    ["fcb001", "yellow orange"]
+    ["910951", "reddish purple"]
+    ["fe2c54", "reddish pink"]
+    ["c875c4", "orchid"]
+    ["cdc50a", "dirty yellow"]
+    ["fd411e", "orange red"]
+    ["9a0200", "deep red"]
+    ["be6400", "orange brown"]
+    ["030aa7", "cobalt blue"]
+    ["fe019a", "neon pink"]
+    ["f7879a", "rose pink"]
+    ["887191", "greyish purple"]
+    ["b00149", "raspberry"]
+    ["12e193", "aqua green"]
+    ["fe7b7c", "salmon pink"]
+    ["ff9408", "tangerine"]
+    ["6a6e09", "brownish green"]
+    ["8b2e16", "red brown"]
+    ["696112", "greenish brown"]
+    ["e17701", "pumpkin"]
+    ["0a481e", "pine green"]
+    ["343837", "charcoal"]
+    ["ffb7ce", "baby pink"]
+    ["6a79f7", "cornflower"]
+    ["5d06e9", "blue violet"]
+    ["3d1c02", "chocolate"]
+    ["82a67d", "greyish green"]
+    ["be0119", "scarlet"]
+    ["c9ff27", "green yellow"]
+    ["373e02", "dark olive"]
+    ["a9561e", "sienna"]
+    ["caa0ff", "pastel purple"]
+    ["ca6641", "terracotta"]
+    ["02d8e9", "aqua blue"]
+    ["88b378", "sage green"]
+    ["980002", "blood red"]
+    ["cb0162", "deep pink"]
+    ["5cac2d", "grass"]
+    ["769958", "moss"]
+    ["a2bffe", "pastel blue"]
+    ["10a674", "bluish green"]
+    ["06b48b", "green blue"]
+    ["af884a", "dark tan"]
+    ["0b8b87", "greenish blue"]
+    ["ffa756", "pale orange"]
+    ["a2a415", "vomit"]
+    ["154406", "forrest green"]
+    ["856798", "dark lavender"]
+    ["34013f", "dark violet"]
+    ["632de9", "purple blue"]
+    ["0a888a", "dark cyan"]
+    ["6f7632", "olive drab"]
+    ["d46a7e", "pinkish"]
+    ["1e488f", "cobalt"]
+    ["bc13fe", "neon purple"]
+    ["7ef4cc", "light turquoise"]
+    ["76cd26", "apple green"]
+    ["74a662", "dull green"]
+    ["80013f", "wine"]
+    ["b1d1fc", "powder blue"]
+    ["ffffe4", "off white"]
+    ["0652ff", "electric blue"]
+    ["045c5a", "dark turquoise"]
+    ["5729ce", "blue purple"]
+    ["069af3", "azure"]
+    ["ff000d", "bright red"]
+    ["f10c45", "pinkish red"]
+    ["5170d7", "cornflower blue"]
+    ["acbf69", "light olive"]
+    ["6c3461", "grape"]
+    ["5e819d", "greyish blue"]
+    ["601ef9", "purplish blue"]
+    ["b0dd16", "yellowish green"]
+    ["cdfd02", "greenish yellow"]
+    ["2c6fbb", "medium blue"]
+    ["c0737a", "dusty rose"]
+    ["d6b4fc", "light violet"]
+    ["020035", "midnight blue"]
+    ["703be7", "bluish purple"]
+    ["fd3c06", "red orange"]
+    ["960056", "dark magenta"]
+    ["40a368", "greenish"]
+    ["03719c", "ocean blue"]
+    ["fc5a50", "coral"]
+    ["ffffc2", "cream"]
+    ["7f2b0a", "reddish brown"]
+    ["b04e0f", "burnt sienna"]
+    ["a03623", "brick"]
+    ["87ae73", "sage"]
+    ["789b73", "grey green"]
+    ["ffffff", "white"]
+    ["98eff9", "robin's egg blue"]
+    ["658b38", "moss green"]
+    ["5a7d9a", "steel blue"]
+    ["380835", "eggplant"]
+    ["fffe7a", "light yellow"]
+    ["5ca904", "leaf green"]
+    ["d8dcd6", "light grey"]
+    ["a5a502", "puke"]
+    ["d648d7", "pinkish purple"]
+    ["047495", "sea blue"]
+    ["b790d4", "pale purple"]
+    ["5b7c99", "slate blue"]
+    ["607c8e", "blue grey"]
+    ["0b4008", "hunter green"]
+    ["ed0dd9", "fuchsia"]
+    ["8c000f", "crimson"]
+    ["ffff84", "pale yellow"]
+    ["bf9005", "ochre"]
+    ["d2bd0a", "mustard yellow"]
+    ["ff474c", "light red"]
+    ["0485d1", "cerulean"]
+    ["ffcfdc", "pale pink"]
+    ["040273", "deep blue"]
+    ["a83c09", "rust"]
+    ["90e4c1", "light teal"]
+    ["516572", "slate"]
+    ["fac205", "goldenrod"]
+    ["d5b60a", "dark yellow"]
+    ["363737", "dark grey"]
+    ["4b5d16", "army green"]
+    ["6b8ba4", "grey blue"]
+    ["80f9ad", "seafoam"]
+    ["a57e52", "puce"]
+    ["a9f971", "spring green"]
+    ["c65102", "dark orange"]
+    ["e2ca76", "sand"]
+    ["b0ff9d", "pastel green"]
+    ["9ffeb0", "mint"]
+    ["fdaa48", "light orange"]
+    ["fe01b1", "bright pink"]
+    ["c1f80a", "chartreuse"]
+    ["36013f", "deep purple"]
+    ["341c02", "dark brown"]
+    ["b9a281", "taupe"]
+    ["8eab12", "pea green"]
+    ["9aae07", "puke green"]
+    ["02ab2e", "kelly green"]
+    ["7af9ab", "seafoam green"]
+    ["137e6d", "blue green"]
+    ["aaa662", "khaki"]
+    ["610023", "burgundy"]
+    ["014d4e", "dark teal"]
+    ["8f1402", "brick red"]
+    ["4b006e", "royal purple"]
+    ["580f41", "plum"]
+    ["8fff9f", "mint green"]
+    ["dbb40c", "gold"]
+    ["a2cffe", "baby blue"]
+    ["c0fb2d", "yellow green"]
+    ["be03fd", "bright purple"]
+    ["840000", "dark red"]
+    ["d0fefe", "pale blue"]
+    ["3f9b0b", "grass green"]
+    ["01153e", "navy"]
+    ["04d8b2", "aquamarine"]
+    ["c04e01", "burnt orange"]
+    ["0cff0c", "neon green"]
+    ["0165fc", "bright blue"]
+    ["cf6275", "rose"]
+    ["ffd1df", "light pink"]
+    ["ceb301", "mustard"]
+    ["380282", "indigo"]
+    ["aaff32", "lime"]
+    ["53fca1", "sea green"]
+    ["8e82fe", "periwinkle"]
+    ["cb416b", "dark pink"]
+    ["677a04", "olive green"]
+    ["ffb07c", "peach"]
+    ["c7fdb5", "pale green"]
+    ["ad8150", "light brown"]
+    ["ff028d", "hot pink"]
+    ["000000", "black"]
+    ["cea2fd", "lilac"]
+    ["001146", "navy blue"]
+    ["0504aa", "royal blue"]
+    ["e6daa6", "beige"]
+    ["ff796c", "salmon"]
+    ["6e750e", "olive"]
+    ["650021", "maroon"]
+    ["01ff07", "bright green"]
+    ["35063e", "dark purple"]
+    ["ae7181", "mauve"]
+    ["06470c", "forest green"]
+    ["13eac9", "aqua"]
+    ["00ffff", "cyan"]
+    ["d1b26f", "tan"]
+    ["00035b", "dark blue"]
+    ["c79fef", "lavender"]
+    ["06c2ac", "turquoise"]
+    ["033500", "dark green"]
+    ["9a0eea", "violet"]
+    ["bf77f6", "light purple"]
+    ["89fe05", "lime green"]
+    ["929591", "grey"]
+    ["75bbfd", "sky blue"]
+    ["ffff14", "yellow"]
+    ["c20078", "magenta"]
+    ["96f97b", "light green"]
+    ["f97306", "orange"]
+    ["029386", "teal"]
+    ["95d0fc", "light blue"]
+    ["e50000", "red"]
+    ["653700", "brown"]
+    ["ff81c0", "pink"]
+    ["0343df", "blue"]
+    ["15b01a", "green"]
+    ["7e1e9c", "purple"]
   ]

   names.each (element) ->


fixing color bug in hueToRgb that occurred when s == 0

--- src/color.coffee
+++ src/color.coffee
@@ -74,7 +74,7 @@
       g = hueToRgb(p, q, h)
       b = hueToRgb(p, q, h - 120)

-      rgbMap = ((channel * 255).round() for channel in [r, g, b])
+    rgbMap = ((channel * 255).round() for channel in [r, g, b])

     return rgbMap.concat(a)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -951,6 +951,15 @@

     transparentColor.a
     # => 0.5
+
+    # to see what they look like
+    for color, index in [color, transparentColor]
+      canvas.drawRect
+        color: color
+        x: 20 + (60 * index)
+        y: 20 + (60 * index)
+        width: 60
+        height: 60
     </pre></code>

     @name transparentize


fix query match index

fix query match index

given the current regexp, match[4] is undefined.

--- src/scripts/dribbble.coffee
+++ src/scripts/dribbble.coffee
@@ -26,7 +26,7 @@

 module.exports = (robot) ->
   robot.respond /(depress|dribbble|inspire)( me)? (.*)/i, (msg) ->
-    query = msg.match[4]
+    query = msg.match[3]
     query ||= 'popular'
     msg.http("http://api.dribbble.com/shots/#{query}")
       .get() (err, res, body) ->


Add retry behavior to all dynamo requests.

We will try every operation 5 times, with an exponentially growing
time between them. It will start at 500 milliseconds and grow by a
factor of 1.5.

--- src/server/db/amazon.coffee
+++ src/server/db/amazon.coffee
@@ -3,8 +3,8 @@
 # It uses Dynamo as the metadata store and it uses S3 for snapshot storage as
 # Dynamo objects are limited to 64KB in size.
 #
-# In order to use this backend you must require the 'awssum' and 'async' npm
-# packages.
+# In order to use this backend you must require the 'awssum', 'retry', and
+# 'async' npm packages.
 #
 # Example usage:
 #
@@ -58,6 +58,7 @@

 util = require('util')
 async = require('async')
+retry = require('retry')
 zlib = require('zlib')

 defaultOptions =
@@ -77,19 +78,62 @@
       task(callback)
     , concurrency)

+  # Public: Executes the given function in the order which it was received.
+  #
+  # If the function returns an error it is retried as described by
+  # @_retryableOperation
+  #
+  # fn - The function to execute
+  # description - A brief description of the operation
+  # callback - The callback to notify when the operation has completed.
+  #
+  # Returns nothing.
   push: (fn, description, callback) =>
-    start = new Date().getTime()
-    @queue.push(fn, (error, results) =>
-      if @timing
+    operation = @_retryableOperation()
+
+    operation.attempt((currentAttempt) =>
+      start = new Date().getTime()
+      @queue.push(fn, (error, results) =>
         elapsed = new Date().getTime() - start
-        if results?
-          capacity = results.ConsumedCapacityUnits
+        attempt = operation.attempts()
+        capacity = if results? then results.Body.ConsumedCapacityUnits else -1
+
+        if @_shouldRetry(error)
+          retried = operation.retry(error)
         else
-          capacity = -1
-        console.log('Dynamo['+elapsed+'ms,'+capacity+'] '+@name+': '+description)
-
-      callback(error, results)
+          retried = false
+
+        if retried
+          console.error('Dyname[#'+attempt+','+elapsed+'ms,'+capacity+'] '+@name+': Retrying +'+description+'+ due to '+util.inspect(error))
+        else
+          console.log('Dynamo[#'+attempt+','+elapsed+'ms,'+capacity+'] '+@name+': '+description) if @timing
+          callback(error, results)
+      )
     )
+
+   # Private: Determine if an error is should be retried
+   #
+   # error - The error object to evaluate
+   #
+   # Returns the error object if it should be retried and an empty object
+   # otherwise.
+   _shouldRetry: (error) ->
+     if error and error.Body? and error.Body.message.match('The conditional request failed')
+       false
+     else
+       true
+
+   # Private: Creates an operation that will be retried with the default timing
+   # values
+   #
+   # Returns a retry.Operation.
+   _retryableOperation: ->
+     retry.operation
+       retries: 5
+       factor: 1.5
+       minTimeout: 500
+       maxTimeout: 10 * 1000
+       randomize: false

 class S3Queue
   constructor: (@name, concurrency, @timing) ->


postgres: stripe sync/integration

postgres: stripe sync/integration

--- src/smc-hub/postgres.coffee
+++ src/smc-hub/postgres.coffee
@@ -261,7 +261,7 @@
         @_query
             query : "SELECT COUNT(*) FROM #{opts.table}"
             where : opts.where
-            cb    : (err, result) => opts.cb(err, parseInt(result?.rows[0].count))
+            cb    : count_result(opts.cb)

     _validate_opts: (opts) =>
         for k, v of opts
@@ -602,8 +602,7 @@
                 'time  <= $::TIMESTAMP' : opts.end
                 'event  = $::TEXT'      : opts.event
                 'value @> $::JSONB'     : opts.where
-            cb     : (err, result) =>
-                opts.cb(err, result?.rows)
+            cb     : all_results(opts.cb)

     # Return every entry x in central_log in the given period of time for
     # which x.event==event and x.value.account_id == account_id.
@@ -635,7 +634,7 @@
                 'error      :: TEXT'      : opts.error
                 'account_id :: UUID'      : opts.account_id
                 'time       :: TIMESTAMP' : 'NOW()'
-            cb     : (err) => opts.cb?(err)
+            cb     : opts.cb

     get_client_error_log: (opts) =>
         opts = defaults opts,
@@ -667,8 +666,7 @@
             query : 'SELECT value FROM server_settings'
             where :
                 "name = $::TEXT" : opts.name
-            cb    : (err, result) =>
-                opts.cb(err, result?.rows[0]?.value)
+            cb    : one_result(opts.cb, 'value')

     # TODO: optimization -- this could be done as a changefeed (and is in rethink.coffee)
     get_site_settings: (opts) =>
@@ -695,11 +693,12 @@
             conf     : required
             cb       : required
         @_query
-            query : 'INSERT into passport_settings'
-            values :
+            query    : 'INSERT into passport_settings'
+            values   :
                 'strategy::TEXT ' : opts.strategy
                 'conf    ::JSONB' : opts.conf
             conflict : 'strategy'
+            cb       : opts.cb

     get_passport_settings: (opts) =>
         opts = defaults opts,
@@ -709,8 +708,7 @@
             query : 'SELECT conf FROM passport_settings'
             where :
                 "strategy = $::TEXT" : opts.strategy
-            cb    : (err, result) =>
-                opts.cb(err, result?.rows[0]?.conf)
+            cb    : one_result(opts.cb, 'conf')

     ###
     Account creation, deletion, existence
@@ -844,8 +842,7 @@
         @_query
             query : 'SELECT account_id FROM accounts'
             where : "email_address = $::TEXT" : opts.email_address
-            cb    : (err, result) =>
-                opts.cb?(err, result?.rows[0]?.account_id)
+            cb    : one_result(opts.cb, 'account_id')

     # set an account creation action, or return all of them for the given email address
     account_creation_actions: (opts) =>
@@ -871,11 +868,7 @@
                 where :
                     'email_address  = $::TEXT'       : opts.email_address
                     'expire        >= $::TIMESTAMP'  : new Date()
-                cb    : (err, result) =>
-                    if err
-                        opts.cb(err)
-                    else
-                        opts.cb(undefined, (x.action for x in result.rows))
+                cb    : all_results(opts.cb, 'action')

     account_creation_actions_success: (opts) =>
         opts = defaults opts,
@@ -947,10 +940,64 @@
         @_query
             query : 'SELECT stripe_customer_id FROM accounts'
             where : 'account_id = $::UUID' : opts.account_id
-            cb    : (err, result) => opts.cb(err, result?.rows[0]?.stripe_customer_id)
-
+            cb    : one_result(opts.cb, 'stripe_customer_id')
+
+    ###
+    Stripe integration/sync:
+    Get all info about the given account from stripe and put it in our own local database.
+    Call it with force right after the user does some action that will change their
+    account info status.  This will never touch stripe if the user doesn't have
+    a stripe_customer_id.   TODO: This should be replaced by webhooks...
+    ###
     stripe_update_customer: (opts) =>
-        throw Error("NotImplementedError")
+        opts = defaults opts,
+            account_id  : required   # user's account_id
+            stripe      : undefined  # api connection to stripe
+            customer_id : undefined  # will be looked up if not known
+            cb          : undefined
+        customer = undefined
+        dbg = @_dbg("stripe_update_customer(account_id='#{opts.account_id}')")
+        async.series([
+            (cb) =>
+                if opts.customer_id?
+                    cb(); return
+                dbg("get_stripe_customer_id")
+                @get_stripe_customer_id
+                    account_id : opts.account_id
+                    cb         : (err, x) =>
+                        dbg("their stripe id is #{x}")
+                        opts.customer_id = x; cb(err)
+            (cb) =>
+                if opts.customer_id? and not opts.stripe?
+                    @get_server_setting
+                        name : 'stripe_secret_key'
+                        cb   : (err, secret) =>
+                            if err
+                                cb(err)
+                            else if not secret
+                                cb("stripe must be configured")
+                            else
+                                opts.stripe = require("stripe")(secret)
+                                cb()
+                else
+                    cb()
+            (cb) =>
+                if opts.customer_id?
+                    opts.stripe.customers.retrieve opts.customer_id, (err, x) =>
+                        dbg("got stripe info -- #{err}")
+                        customer = x; cb(err)
+                else
+                    cb()
+            (cb) =>
+                if opts.customer_id?
+                    @_query
+                        query : 'UPDATE accounts'
+                        set   : 'stripe_customer::JSONB' : customer
+                        where : 'account_id = $::UUID'   : opts.account_id
+                        cb    : opts.cb
+                else
+                    cb()
+        ], opts.cb)

     account_ids_to_usernames: (opts) =>
         throw Error("NotImplementedError")
@@ -1326,3 +1373,66 @@
 # Timestamp the given number of seconds **in the future**.
 exports.expire_time = expire_time = (ttl) ->
     if ttl then new Date((new Date() - 0) + ttl*1000)
+
+# Returns a function that takes as input the output of doing a SQL query.
+# If there are no results, returns undefined.
+# If there is exactly one result, what is returned depends on pattern:
+#     'a_field' --> returns the value of this field in the result
+# If more than one result, an error
+one_result = (cb, pattern) ->
+    if not cb?
+        return ->  # do nothing -- return function that ignores result
+    return (err, result) ->
+        if err
+            cb(err)
+            return
+        if not result?.rows?
+            cb()
+            return
+        switch result.rows.length
+            when 0
+                cb()
+            when 1
+                obj = result.rows[0]
+                switch typeof(pattern)
+                    when 'string'
+                        x = obj[pattern]
+                        if not x?  # null or undefined -- SQL returns null, but we want undefined
+                            cb()
+                        else
+                            cb(undefined, x)
+                    when 'object'
+                        x = {}
+                        for p in pattern
+                            if obj[p]?
+                                x[p] = obj[p]
+                        cb(undefined, x)
+                    else
+                        cb("BUG: unknown pattern -- #{pattern}")
+            else
+                cb("more than one result")
+
+all_results = (cb, pattern) ->
+    if not cb?
+        return ->  # do nothing -- return function that ignores result
+    return (err, result) ->
+        if err
+            cb(err)
+        else
+            rows = result.rows
+            if not pattern?
+                cb(undefined, rows)
+            else if typeof(pattern) == 'string'
+                cb(undefined, ((x[pattern] ? undefined) for x in rows))
+            else
+                cb("unsupported pattern type '#{typeof(pattern)}'")
+
+
+count_result = (cb) ->
+    if not cb?
+        return ->  # do nothing -- return function that ignores result
+    return (err, result) ->
+        if err
+            cb(err)
+        else
+            cb(undefined, parseInt(result?.rows?[0]?.count))


Add coupon option to student pay

Add coupon option to student pay

--- src/smc-webapp/billing.cjsx
+++ src/smc-webapp/billing.cjsx
@@ -157,7 +157,7 @@
             last_subscription_attempt = {timestamp:misc.server_time(), plan:plan}
             @track_subscription(plan)

-    get_coupon: (id) =>
+    apply_coupon: (id) =>
         cb = (err, coupon) =>
             if err
                 @setState(coupon_error: JSON.stringify(err))
@@ -168,7 +168,7 @@
         opts =
             coupon_id : id
             cb        : cb
-        @_action('get_coupon', "Getting coupon: #{id}", opts)
+        @_action('get_coupon', "Applying coupon: #{id}", opts)

     clear_coupon_error: =>
         @setState(coupon_error : '')
@@ -974,7 +974,7 @@
                     /> if @props.selected_plan isnt ''}
                     <Row>
                         <Col sm={5} smOffset={7}>
-                            {<CouponAdder applied_coupons={@props.applied_coupons} coupon_error={@props.coupon_error} />}
+                            <CouponAdder applied_coupons={@props.applied_coupons} coupon_error={@props.coupon_error} />
                         </Col>
                     </Row>
                     {@render_create_subscription_buttons()}
@@ -1042,7 +1042,7 @@

     submit: (e) ->
         e?.preventDefault()
-        @actions('billing').get_coupon(@state.coupon_id) if @state.coupon_id
+        @actions('billing').apply_coupon(@state.coupon_id) if @state.coupon_id

     render_well_header: ->
         if @props.applied_coupons?.size > 0
@@ -1855,7 +1855,16 @@
             </Well>

     render: ->
+        store = @props.redux.getStore('billing')
+        applied_coupons = store.get('applied_coupons')
+        coupon_error    = store.get('coupon_error')
+
         <span>
+            <Row>
+                <Col sm={5}>
+                    <CouponAdder applied_coupons={applied_coupons} coupon_error={coupon_error} />
+                </Col>
+            </Row>
             {@render_buy_button()}
             {@render_confirm_button()}
         </span>


fix https://github.com/sagemathinc/smc/issues/895 -- "Embedded Codemirror in Tasks editor doesn't respect account settings default font size"

--- src/smc-webapp/tasks.coffee
+++ src/smc-webapp/tasks.coffee
@@ -793,9 +793,8 @@
         if task.deleted
             desc = "<del>#{desc}</del>"

+        task.element.find(".salvus-tasks-desc-column").css({fontSize: "#{@default_font_size ? 14}px"})
         e = task.element.find(".salvus-task-desc")
-        e.css({fontSize: "#{@default_font_size ? 14}px"})
-
         e.html(desc)
         if has_mathjax
             # .mathjax() does the above optimization, but it first does e.html(), so is a slight waste -- most


Little refactor

Little refactor

--- lib/Bumped.config.coffee
+++ lib/Bumped.config.coffee
@@ -14,8 +14,7 @@

   constructor: (bumped) ->
     @bumped = bumped
-    @rc = require('rc') bumped.pkg.name, DEFAULT.scaffold(), null, (config) ->
-      CSON.parse config
+    @rc = require('rc') bumped.pkg.name, DEFAULT.scaffold(), CSON.parse

   ###*
    * Special '.add' action that try to autodetect common configuration files


refactor: assign default params

refactor: assign default params

--- lib/Bumped.logger.coffee
+++ lib/Bumped.logger.coffee
@@ -16,8 +16,8 @@
     cb = opts
     opts = optsDefault
   else
-    opts or= optsDefault
-    cb or= noop
+    opts = existsDefault opts, optsDefault
+    cb = existsDefault cb, noop

   return cb err if @level is 'silent'
   err = MSG.NOT_PROPERLY_FINISHED err if isBoolean err


Changed external link to DR P1

Changed external link to DR P1

--- app/client/lib/layout.coffee
+++ app/client/lib/layout.coffee
@@ -60,7 +60,7 @@

 Template.navbar.helpers

-  externallink: -> 'http://www.dr.dk/sporten/fifavm2014'
+  externallink: -> 'http://www.dr.dk/p1'

 Template.currentUser.helpers


Run copy-i18n-tags (#5747)

Run copy-i18n-tags (#5747)

--- app/locale/rot13.coco.coffee
+++ app/locale/rot13.coco.coffee
@@ -1109,7 +1109,7 @@
     chelsea_title: "Bcrengvbaf Znantre"
     claire_title: "Rkrphgvir Nffvfgnag"
     bobby_title: "Tnzr Qrfvtare"
-    brian_title: "Fravbe Tnzr Qrfvta Znantre" # {punatr}
+    brian_title: "Fravbe Tnzr Qrfvta Znantre"
     andrew_title: "Fbsgjner Ratvarre"
     stephanie_title: "Phfgbzre Fhccbeg Fcrpvnyvfg"
     rob_title: "Fnyrf Qrirybczrag Ercerfragngvir"


stupid bug fixed

stupid bug fixed

--- lib/auth/Identity.coffee
+++ lib/auth/Identity.coffee
@@ -61,10 +61,10 @@
             manager.removeListener 'destroy:'+id, destroy

         floyd.tools.objects.process @,
-            next: (key, value)=>
+            each: (key, value, next)=>
                 @[key] = null
                 next()
-
+
         emitter.emit 'destroyed'


slight cleanup

slight cleanup

--- lib/editor/result.coffee
+++ lib/editor/result.coffee
@@ -74,23 +74,13 @@
       class: 'ink-underlay'
       invalidate: 'never'
     @expDecoration = @editor.decorateMarker @expMarker, mark
-    setTimeout (=>
-      elRect = @editor.editorElement.getBoundingClientRect()
-      w = elRect.width + elRect.left - 40 -
-          @view.parentElement.getBoundingClientRect().left
-      if w < 100 then w = 100
-      @view.style.maxWidth = w + 'px'), 50
+    @updateWidth()

   collapseView: () ->
     @expanded = false
     @expMarker?.destroy()
     @decorateMarker()
-    setTimeout (=>
-      elRect = @editor.editorElement.getBoundingClientRect()
-      w = elRect.width + elRect.left - 40 -
-          @view.parentElement.getBoundingClientRect().left
-      if w < 100 then w = 100
-      @view.style.maxWidth = w + 'px'), 50
+    @updateWidth()

   createView: (opts) ->
     {content, fade, loading} = opts
@@ -139,20 +129,21 @@
       when 'inline' then mark.type = 'overlay'; mark.class = 'ink-overlay'
       when 'block' then mark.type = 'block'; mark.position = 'after'
     @decoration = @editor.decorateMarker @marker, mark
+    if @type == 'inline'
+      setTimeout (=> resizer.listenTo @editor.editorElement, => @updateWidth()), 50

   initMarker: ->
     @marker = @editor.markBufferRange @lineRange(@start, @end)
     @marker.result = @
     @decorateMarker()
     @disposables.add @marker.onDidChange (e) => @checkMarker e
-    if @type == 'inline'
-      resizer.listenTo @editor.editorElement, (el) =>
-        setTimeout (=>
-          elRect = el.getBoundingClientRect()
-          w = elRect.width + elRect.left - 40 -
-              @view.parentElement.getBoundingClientRect().left
-          if w < 100 then w = 100
-          @view.style.maxWidth = w + 'px'), 50
+
+  updateWidth: ->
+    elRect = @editor.editorElement.getBoundingClientRect()
+    w = elRect.width + elRect.left - 40 -
+        @view.parentElement.getBoundingClientRect().left
+    if w < 100 then w = 100
+    @view.style.maxWidth = w + 'px'

   toggleTree: ->
     trees.toggle $(@view).find('> .tree')


any live cell with more than three neighbors should die

--- spec/lib/grid-spec.coffee
+++ spec/lib/grid-spec.coffee
@@ -41,3 +41,12 @@
     Given -> @grid.live 5,4
     When -> @grid = @grid.generate()
     Then -> expect(@grid.cell(4,4)).toBe true
+
+  describe 'with any live cell with with more than three live neighbors dies', ->
+
+    Given -> @grid.live 4,4
+    Given -> @grid.live 4,3
+    Given -> @grid.live 5,4
+    Given -> @grid.live 4,5
+    When -> @grid = @grid.generate()
+    Then -> expect(@grid.cell(4,4)).toBe false


Add specs for PR #50

Add specs for PR #50

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -92,6 +92,12 @@
             body: """
               with placeholder ${1:test}
               without placeholder $1
+            """
+
+          "large indices":
+            prefix: "t10"
+            body: """
+              hello${10} ${11:large} indices${1}
             """

     describe "when the letters preceding the cursor trigger a snippet", ->
@@ -371,6 +377,18 @@
         editor.insertText('hello')
         expect(buffer.lineForRow(0)).toBe "with placeholder hello"
         expect(buffer.lineForRow(1)).toBe "without placeholder hellovar quicksort = function () {"
+
+    describe "when the snippet contains tab stops with an index >= 10", ->
+      it "parses and orders the indices correctly", ->
+        editor.setText('t10')
+        editor.setCursorScreenPosition([0, 3])
+        editorView.trigger keydownEvent('tab', target: editorView[0])
+        expect(editor.getText()).toBe "hello large indices"
+        expect(editor.getCursorBufferPosition()).toEqual [0, 19]
+        editorView.trigger keydownEvent('tab', target: editorView[0])
+        expect(editor.getCursorBufferPosition()).toEqual [0, 5]
+        editorView.trigger keydownEvent('tab', target: editorView[0])
+        expect(editor.getSelectedBufferRange()).toEqual [[0, 6], [0, 11]]

   describe "snippet loading", ->
     [configDirPath, packageWithSnippets, packageWithBrokenSnippets] = []


hooks

hooks

--- src/hooks.coffee
+++ src/hooks.coffee
@@ -22,11 +22,14 @@

     # additional hooks which are useful for debugging:

+    # called when an instance is found
+    # instanceFound: (
+
     # called right before resolved dependencies are injected into a function:
-    # inject: (
+    # inject: () ->
     #
     # called when no instance
-    # noInstance:
+    # noInstance: () ->
     #
     # called right before an instance is added to instances
     # newInstance: (id, instance) ->


Fix sudden unit details content closing

Fix sudden unit details content closing

--- src/views/navigation.coffee
+++ src/views/navigation.coffee
@@ -197,10 +197,11 @@
                         collection: new models.UnitList()

                 when 'search'
-                    view = new SearchResultsSummaryLayout
-                        collection: @searchResults
-                    if opts?.disableAutoFocus
-                        view.disableAutoFocus()
+                    if @searchResults.length > 0
+                        view = new SearchResultsSummaryLayout
+                            collection: @searchResults
+                        if opts?.disableAutoFocus
+                            view.disableAutoFocus()
                 when 'service-node-units'
                     view = new UnitListingView
                         model: new Backbone.Model


Fix test again

Fix test again

Apparently form.data returns a string for the 'number' field


Switch all http requests to http/1.1

Switch all http requests to http/1.1

--- src/zombie/resources.coffee
+++ src/zombie/resources.coffee
@@ -617,9 +617,6 @@
       comment:          ''

     protocol = @resources.browser.getProtocol()
-    # Convert headers for the special case of h2
-    if protocol == 'h2'
-        httpRequest.headers = HTTP2.convertHeadersToH2(request.headers)

     prxy = @resources.browser.getProxy()
     if prxy
@@ -645,12 +642,13 @@
       return

     #console.log 'REQUEST '+Date.now()+' '+request.url
-    if protocol == 'h2'
+    if protocol == 'h2' and ! httpRequest.plain
+      httpRequest.headers = HTTP2.convertHeadersToH2(request.headers)
       if httpRequest.plain
         req = HTTP2.raw.request httpRequest
       else
         req = HTTP2.request httpRequest
-    else if protocol == 'http/1.1'
+    else if protocol == 'http/1.1' or httpRequest.plain

       if ! @resources.h1_total_connections[httpRequest.host+httpRequest.port]
         @resources.h1_avail_connections[httpRequest.host+httpRequest.port] = 0


Prevent double call of 'initPylama'

Prevent double call of 'initPylama'

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -26,8 +26,11 @@

     @subscriptions.add atom.config.observe 'linter-pylama.executablePath',
     (executablePath) =>
-      @executablePath = executablePath
-      do @initPylama
+      if @executablePath
+        @executablePath = executablePath
+        do initPylama
+      else
+        @executablePath = executablePath

     @subscriptions.add atom.config.observe 'linter-pylama.interpreter',
     (interpreter) =>


Remove line-length limit of tokenised Vim script

Might help improve the highlighting of Vim syntax definitions, which are
sometimes seen sporting long hairy regular expressions or keyword lists.
(I haven't tested this, but it's unlikely to do any harm).

--- grammars/viml.cson
+++ grammars/viml.cson
@@ -41,6 +41,7 @@
 """
 foldingStartMarker: "^(?:if|while|for|fu|function|augroup|aug)"
 foldingStopMarker: "(?:endif|endwhile|endfor|endf|endfunction|augroup\\.END|aug\\.END)$"
+limitLineLength: no
 patterns: [include: "#main"]

 repository:


PrefetchFeed: disable member and topic prefetching feed

--- workers/social/lib/social/cache/feed.coffee
+++ workers/social/lib/social/cache/feed.coffee
@@ -45,17 +45,17 @@
     skip  : 0
     sort  : 'counts.followers' : -1

-  queue.push ->
-    fetchMembersFromGraph bongoModels, client, (err, members)->
-      localPrefetchedFeeds['members.main'] = members  if members
-      queue.fin()
+  # queue.push ->
+  #   fetchMembersFromGraph bongoModels, client, (err, members)->
+  #     localPrefetchedFeeds['members.main'] = members  if members
+  #     queue.fin()

   # Modified this function to fetch groups' tags
   # also we can return topics for all groups
-  queue.push ->
-    JTag._some client, {}, defaultOptions, (err, topics)->
-      localPrefetchedFeeds['topics.main'] = topics  if topics
-      queue.fin()
+  # queue.push ->
+  #   JTag._some client, {}, defaultOptions, (err, topics)->
+  #     localPrefetchedFeeds['topics.main'] = topics  if topics
+  #     queue.fin()

   # This is not koding specific so we can return this to every group
   # queue.push ->


"Moves," not "attacks."

"Moves," not "attacks."

--- src/index.coffee
+++ src/index.coffee
@@ -3,7 +3,7 @@
 #
 # Configuration:
 #   HUBOT_HAMMERSPORT_ROOMS - comma-separated list of rooms to restrict hammersport chatter to.
-#   HUBOT_HAMMERSPORT_ATTACKS - path to a file containing available attacks.
+#   HUBOT_HAMMERSPORT_MOVES - path to a file containing available moves.
 #
 # Commands:
 #   hubot hammersport <user> - Challenge another user to a duel


don't use highlight if language is not given

--- src/compiler/template-compiler.coffee
+++ src/compiler/template-compiler.coffee
@@ -116,7 +116,11 @@
   renderMarkedDown: (input,cb) ->
     marked input, {
       highlight:  (code, lang) ->
-        hljs.highlight(lang, code).value
+        if lang
+          hljs.highlight(lang, code).value
+        else
+          # FIXME do i need to escape here?
+          code
       }, cb

   # cb(err,streamData:String)


Lower-case HTML

Lower-case HTML

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -242,7 +242,7 @@
     url = urlFor 'group',
       group: group
     if html
-      "#{group}"
+      "#{group}"
     else
       "#{group} [#{url}]"

@@ -255,7 +255,7 @@
       if quote
         """“#{formatTitleOrFilename msg, true, true}”"""
       else
-        #"#{_.escape titleOrUntitled msg}"
+        #"#{_.escape titleOrUntitled msg}"
         """#{formatTitleOrFilename msg, true, true}"""
     else
       if quote


More consistent watch tasks

More consistent watch tasks

#8 QueryHandlers should also handle query transmission

--- grunt/watch.coffee
+++ grunt/watch.coffee
@@ -1,6 +1,6 @@
 module.exports = {

-  sourcesAndUnitTests:
+  unit:
     files: [
       'src/*.coffee'
       'src/**/*.coffee'
@@ -12,8 +12,10 @@
       'jasmine_nodejs:unit'
     ]

-  integrationTests:
+  integration:
     files: [
+      'src/*.coffee'
+      'src/**/*.coffee'
       'test/integration/*.coffee'
       'test/integration/**/*.coffee'
     ]


Bug fix for peek

Bug fix for peek

--- src/core/collection-observable.coffee
+++ src/core/collection-observable.coffee
@@ -186,10 +186,10 @@
   # Can be called directly, via kb.release(object) or as a consequence of ko.releaseNode(element).
   destroy: ->
     observable = kb.utils.wrappedObservable(@)
-    collection = @_collection.peek()
+    collection = kb.peek(@_collection)
     if collection
       collection.unbind('all', @__kb._onCollectionChange)
-      array = observable.peek(); array.splice(0, array.length) # clear the view models or models
+      array = kb.peek(observable); array.splice(0, array.length) # clear the view models or models
     @collection.dispose(); @_collection = observable.collection = @collection = null
     @_mapper.dispose(); @_mapper = null
     kb.release(@_filters); @_filters = null


Fix issue with undefined Accounts variable

--- files.coffee
+++ files.coffee
@@ -484,7 +484,7 @@
       delete @interceptDownload
       delete @continueUploadTTL

-      if _.has(Package, 'accounts-base') and Accounts
+      if _.has(Package, 'accounts-base')
         setTokenCookie = ->
           if (not cookie.has('meteor_login_token') and Accounts._lastLoginTokenWhenPolled) or (cookie.has('meteor_login_token') and (cookie.get('meteor_login_token') isnt Accounts._lastLoginTokenWhenPolled))
             cookie.set 'meteor_login_token', Accounts._lastLoginTokenWhenPolled, null, '/'
@@ -1024,13 +1024,13 @@
     if Meteor.isServer
       if http
         cookie = http.request.Cookies
-        if _.has(Package, 'accounts-base') and Accounts and cookie.has 'meteor_login_token'
+        if _.has(Package, 'accounts-base') and cookie.has 'meteor_login_token'
           user = Meteor.users.findOne 'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken cookie.get 'meteor_login_token'
           if user
             result.user = () -> return user
             result.userId = user._id
     else
-      if _.has(Package, 'accounts-base') and Accounts and Meteor.userId()
+      if _.has(Package, 'accounts-base') and Meteor.userId()
         result.user = -> return Meteor.user()
         result.userId = Meteor.userId()


hopefully fixes invites

hopefully fixes invites

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -15,7 +15,6 @@

   JAccount = require './account'
   JLimit = require './limit'
-  JUser = require './user'
   JLimit = require './limit'

   @share()
@@ -263,6 +262,7 @@
                     inviter   : delegate.getFullName()
                     url       : "#{protocol}//#{host}/invitation/#{encodeURIComponent code}"

+                  JUser = require './user'
                   JUser.fetchUser client,(err,user)=>
                     inviterEmail = user.email


social: fix isEligible being member of undefined

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -937,7 +937,7 @@
     JRegistrationPreferences.one {}, (err, prefs) ->

       return callback err  if err
-      return callback null if not prefs
+      return callback null, { isEligible : yes } if not prefs

       unless prefs.isRegistrationEnabled
         return callback new Error 'Registration is currently disabled!'


NumberInput

NumberInput

- Allow numbers to start with decimal point.

--- src/elements/Input/NumberInput.coffee
+++ src/elements/Input/NumberInput.coffee
@@ -189,11 +189,10 @@
 			points = v.substring(point_idx+1)

 		# console.debug "v:", v, "number:", number, "points:", points, "decimalpoint", @_decimalpoint, "separator", @_separator
-
 		if points.length > @_decimals
 			return false

-		if not number.match(/^((0|[1-9]+[0-9]*)|(-|-[1-9]|-[1-9][0-9]*))$/)
+		if number.length > 0 and not number.match(/^((0|[1-9]+[0-9]*)|(-|-[1-9]|-[1-9][0-9]*))$/)
 			# CUI.debug "number not matched", number
 			return false

@@ -215,7 +214,7 @@
 		if points.length > @_decimals
 			return false

-		return v.replace(".", @_decimalpoint)
+		return true # v.replace(".", @_decimalpoint)

 	@format: (v, opts={}) ->


Only check for tool change during drawing tasks

--- app/classifier/index.cjsx
+++ app/classifier/index.cjsx
@@ -144,14 +144,15 @@
     @checkToolChange classification

   checkToolChange: (classification) ->
-    lastAnnotationIndex = classification.annotations.length - 1
-    lastAnnotation = classification.annotations[lastAnnotationIndex]
-
-    toolIdentifier = "#{lastAnnotationIndex}-#{lastAnnotation._toolIndex}"
-
-    if Array.isArray(lastAnnotation.value) and toolIdentifier isnt @_lastAnnotationAndTool
-      @handleToolChange lastAnnotation, @_lastAnnotationAndTool.split('-').pop() ? '-1'
-      @_lastAnnotationAndTool = toolIdentifier
+    if @props.workflow.tasks[annotation.task].type is 'drawing'
+      lastAnnotationIndex = classification.annotations.length - 1
+      lastAnnotation = classification.annotations[lastAnnotationIndex]
+
+      toolIdentifier = "#{lastAnnotationIndex}-#{lastAnnotation._toolIndex}"
+
+      if Array.isArray(lastAnnotation.value) and toolIdentifier isnt @_lastAnnotationAndTool
+        @handleToolChange lastAnnotation, @_lastAnnotationAndTool.split('-').pop() ? '-1'
+        @_lastAnnotationAndTool = toolIdentifier

   handleToolChange: (annotation, oldToolIndex) ->
     lastMark = annotation.value[annotation.value.length - 1]


Remove experimental code.

Remove experimental code.

--- app/client/js/zoom.coffee
+++ app/client/js/zoom.coffee
@@ -1,26 +0,0 @@
-# TODO(steida): Move to este library once stabilized.
-goog.provide 'app.Zoom'
-
-class app.Zoom
-
-  ###*
-    TODO(steida): Experimental, don't use it.
-    PATTERN(steida): It would be awesome to be able to control which pages
-    should be zoomable, and which not. Unfortunatelly, in iOS it seems to be
-    impossible to reset scale back to 1 after user leaved scaled page via JS.
-    Also, positioning sucks. But users are used to do two fingers scroll, so we
-    need to provide them alternative way to scale content.
-    @constructor
-  ###
-  constructor: ->
-    @meta = document.querySelector 'meta[name=viewport]'
-
-  ###*
-    @param {boolean} enable
-  ###
-  enable: (enable) ->
-    # TODO(steida): Check Android etc. http://stackoverflow.com/a/21721335
-    @meta.setAttribute 'content', if enable
-      'width=device-width, initial-scale=1.0'
-    else
-      'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'


oops, missed configurable path

oops, missed configurable path

--- lib/services/load.coffee
+++ lib/services/load.coffee
@@ -26,7 +26,7 @@

     # run any additional middleware that the consumer would like
     try
-      middleware = @retrieve('middleware')
+      middleware = @retrieve(@config.middlewareLocation)

     middleware?(app)


fix style heading on empty line

fix style heading on empty line

--- lib/style-heading.coffee
+++ lib/style-heading.coffee
@@ -42,7 +42,7 @@
     @getStylePattern().test(text)

   addStyle: (text) ->
-    text = /^#*\s?(.+)/.exec(text)[1]
+    text = /^#*\s?(.*)/.exec(text)[1]
     "#{@style.before}#{text}#{@style.after}"

   removeStyle: (text) ->


Updated Bitbucket staging URL

Updated Bitbucket staging URL

--- editors/atom/config.cson
+++ editors/atom/config.cson
@@ -40,7 +40,7 @@
     v4Preview: true
   "one-dark-ui": {}
   "open-on-bitbucket":
-    cloudUrlOverride: "git-staging@staging.bitbucket.org"
+    cloudUrlOverride: "git-staging@staging.bb-inf.net"
   tabs:
     usePreviewTabs: true
   "tree-view": {}


TOUCH IT #6

TOUCH IT #6

--- lib/touch-gesture.coffee
+++ lib/touch-gesture.coffee
@@ -34,22 +34,31 @@

   startTouches: null
   fortifyEvent: (event,callFunction) ->
+    sum : (touches) ->
+      sumX = 0
+      sumY = 0
+      for touch in touches
+        sumX+=touch.clientX
+        sumY+=touch.clientY
+      return {} =
+        sumX : sumX/touches.lenght
+        sumY : sumY/touches.lenght
     console.log event
     if event.type=='touchstart' and @startTouches==null
-      @startTouches = event.touches
+      @startTouches = sum event.touches
       event.start = true

     @currentTouch = event.touches[0] if event.touches.length>0
-
-    event.left = @currentTouch.clientX
+    s = sum @currentTouch
+    event.left = s.sumX
     event.right = @element.offsetWidth-event.left
-    event.top = @currentTouch.clientY
+    event.top = s.sumY
     event.bottom = @element.offsetHeight-event.top

     event.diff = {} =
-      left: event.left-@startTouches[0].clientX
+      left: event.left-@startTouches.sumX
       right: event.left*-1
-      top: event.top-@startTouches[0].clientY
+      top: event.top-@startTouches.sumY
       bottom: event.top*-1

     if event.type =='touchend' and event.touches.lenght>0


Fixed merge conflicts.

Fixed merge conflicts.

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -105,7 +105,7 @@
   recover:
     recover_account_title: "找回账户"
     send_password: "发送重置链接"
-#    recovery_sent: "Recovery email sent."
+    recovery_sent: "找回账户邮件已发送."

   items:
     armor: "盔甲"