use correct score for league leaderboard

--- app/views/ladder/components/Leaderboard.coffee
+++ app/views/ladder/components/Leaderboard.coffee
@@ -140,7 +140,7 @@
           model.get('submittedCodeLanguage'),
           model.rank || index+1,
           (model.get('fullName') || model.get('creatorName') || $.i18n.t("play.anonymous")),
-          model.get('totalScore') * 100 | 0,
+          @correctScore(model),
           @getAgeBracket(model),
           moment(model.get('submitDate')).fromNow().replace('a few ', ''),
           model.get('_id')
@@ -213,3 +213,7 @@

   getAgeBracket: (model) ->
     $.i18n.t("ladder.bracket_#{(model.get('ageBracket') || 'open').replace(/-/g, '_')}")
+
+  correctScore: (model) ->
+    sessionStats = if @league then _.find(model.get('leagues'), {leagueID: @league.id})?.stats else model.attributes
+    return (sessionStats?.totalScore || model.get('totalScore')/2) * 100 | 0


on second thought....

on second thought....

--- apps/artwork/client/setup_artwork_rails.coffee
+++ apps/artwork/client/setup_artwork_rails.coffee
@@ -54,10 +54,10 @@
       else
         options = railwayMap artwork

-        # if the artwork in question is sold,
+        # if the artwork in question is sold or not for sale,
         # pop the first rail above the artwork related information
         # and continue the rest below
-        if model.get('sold')
+        if model.get('sold')  or not model.get('forsale')
           firstRail = _.first(_.keys(rails))
           artworks = new Artworks rails[firstRail]
           { title } = railwayMap(artwork, ' For Sale')[firstRail]


update atom cson

update atom cson

ugh I hate having this stuff in dotfiles. Should pull it out eventually.

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -24,6 +24,7 @@
       "exception-reporting"
       "metrics"
     ]
+    followSymlinks: false
   "spell-check":
     grammars: [
       "text.plain"
@@ -34,3 +35,5 @@
     showOnStartup: false
   "release-notes":
     viewedVersion: "0.89.0"
+  "go-plus":
+    goPath: "/Users/holman/Code/go"


remove markdown preview plus package because it hasn't fixed depreciations and ghost is better ;P

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -93,6 +93,8 @@
     "nuclide-code-format": {}
     "nuclide-flow":
       enableStructuredTypeHints: true
+    "nuclide-health":
+      toolbarJewel: "None"
     "nuclide-home":
       showHome: false
     use:


context menus can be sticky

context menus can be sticky

--- components/contextmenu/contextmenu.coffee
+++ components/contextmenu/contextmenu.coffee
@@ -8,17 +8,19 @@
     options.offset.left   or= 0
     options.offset.top    or= 0
     options.arrow          ?= no
+    options.sticky         ?= no

     super options, data

     @topMargin  = 0
     @leftMargin = 0
+    @sticky     = no

     o = @getOptions()

     KD.getSingleton("windowController").addLayer @

-    @on 'ReceivedClickElsewhere', => @destroy()
+    @on 'ReceivedClickElsewhere', => @destroy() unless @sticky

     if data
       @treeController = new JContextMenuTreeViewController
@@ -34,7 +36,7 @@
         lazyLoad          : o.lazyLoad ? no
       , data
       @addSubView @treeController.getView()
-      @treeController.getView().on 'ReceivedClickElsewhere', => @destroy()
+      @treeController.getView().on 'ReceivedClickElsewhere', => @destroy() unless @sticky

       @treeController.on "NodeExpanded", @bound "positionSubMenu"

@@ -42,6 +44,9 @@
       @on "viewAppended", @bound "addArrow"

     @appendToDomBody()
+
+  setSticky:   -> @sticky = yes
+  unsetSticky: -> @sticky = no

   childAppended:->


rangeLength validation default message fix

--- components/inputs/KDInputValidator.coffee
+++ components/inputs/KDInputValidator.coffee
@@ -66,7 +66,7 @@
     if doesValidate
       return null
     else
-      return ruleSet.messages?.rangeLength or "Please enter a value that includes more than #{rangeLength[0]} and less than #{rangeLength[0]} characters!"
+      return ruleSet.messages?.rangeLength or "Please enter a value that includes more than #{rangeLength[0]} and less than #{rangeLength[1]} characters!"

   @ruleMatch = (input, event)->


Prevent scrolling when a modal is open.

Prevent scrolling when a modal is open.

--- _source/client/templates/components/modal.coffee
+++ _source/client/templates/components/modal.coffee
@@ -44,6 +44,8 @@
           opacity: 1
         ,
           duration: 250
+          complete: (elements) ->
+            $('html').addClass 'modal--opened'
       super

     else
@@ -57,6 +59,8 @@
           opacity: 1
         ,
           duration: 250
+          complete: (elements) ->
+            $('html').addClass 'modal--opened'

   removeDOMElement: (parent, node) ->
@@ -78,6 +82,7 @@
         duration: 250
         complete: (elements) ->
           $(node).remove()
+          $('html').removeClass 'modal--opened'

     else
       # slide out panel to the bottom
@@ -93,3 +98,4 @@
         duration: 250
         complete: (elements) ->
           $(node).remove()
+          $('html').removeClass 'modal--opened'


add disabled state for modal

add disabled state for modal

--- _source/client/templates/components/modal.coffee
+++ _source/client/templates/components/modal.coffee
@@ -1,12 +1,21 @@
 class Apollos.modal extends Apollos.Component

   @register "Apollos.modal"
+
+  vars: -> [
+    disabled: @.data().disabled or false
+  ]

   events: -> [
     "click": @.destroy
   ]

   destroy: ->
+    self = @
+
+    if self.disabled.get()
+      return
+
     Blaze.remove @._internals.templateInstance.view

   renderContent: ->


Fetching TODO

Fetching TODO

--- scripts/rc.coffee
+++ scripts/rc.coffee
@@ -7,6 +7,11 @@

 module.exports = (robot) ->
+  robot.respond /(?:rc)\s+for\s([^\s]+)$/i, (msg) ->
+    who = msg.match[1]
+    new RCScore(who).fetch (err, info, self) =>
+      msg.reply "TODO(sshirokov): Err: #{err} Info: #{util.inspect info}: Self: #{self}"
+
   robot.respond /(?:rc)\s+(\d+.?\d*)$/i, (msg) ->
     who = msg.message.user.name.toLowerCase()
     score = msg.match[1]
@@ -35,10 +40,9 @@
     @storage.auth info.auth.split(":")[1] if info.auth

 class RCScore extends RCBase
-  constructor: (@who=null, @options={}) -> super()
+  constructor: (@who, @options={}) -> super()

   set: (score, cb) =>
-    cb(new RCError("Setting score of nobody"), null, this) unless @who
     now = Date.now()

     @storage.multi([
@@ -54,5 +58,8 @@
   score: -> @info?.score
   timestamp: -> @info?.timestamp

-  fetch: (cb) ->
-    throw RCError("TODO(sshirokov): Update the latest scores and stamps, the call the CB")
+  fetch: (cb) =>
+    @storage.zscore @key("latest"), @who, (err, stamp) =>
+      return cb(err, null, this) if err
+      # Use `stamp` to fetch from `@key("@{who}:scores")`
+      cb(new RCError("TODO(sshirokov): Got score of #{stamp} for #{@who}"), null, this)


refactor TableView.coffee

refactor TableView.coffee

--- src/intermediate/TableView.coffee
+++ src/intermediate/TableView.coffee
@@ -1,26 +1,24 @@
 class @TableView
-  _data = []
-  _reverse = false
+  _data: []
+  _reverse: false

   constructor: (@$el, @model) ->
-    _data = @model.get('data')
+    @_data = @model.get('data')
     @$tbody = @$el.querySelector('.js-tbody')
     @$sortBtns = @$el.querySelectorAll('.js-btn-sort')
     @render()
     @setEventListeners()

   setEventListeners: ->
-    @model.onChange('data', =>
-      _data = @model.get('data')
+    @model.onChange 'data', =>
+      @_data = @model.get('data')
       @render()
-    )

     for $sortBtn in @$sortBtns
-      $sortBtn.addEventListener('click', (e) =>
+      $sortBtn.addEventListener 'click', (e) =>
         sortkey = e.target.dataset.sortkey
         @_sortByKey(sortkey)
         @render()
-      )

   addComma: (num) ->
     String(num).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,')
@@ -29,12 +27,12 @@
     "
#{data.name}	#{@addComma(data.amount)}
"

   _sortByKey: (key) ->
-    _reverse = not _reverse
-    _data.sort((a, b) ->
+    @_reverse = not @_reverse
+    @_data.sort (a, b) =>
       x = a[key]
       y = b[key]

-      if _reverse
+      if @_reverse
         if x > y
           return 1
         if x < y
@@ -46,12 +44,11 @@
         if x < y
           return 1
         return 0
-    )

   render: ->
     tableEl = []

-    for data in _data
+    for data in @_data
       tableEl.push(@template(data))

     @$tbody.innerHTML = tableEl.join('')


add back tick event

add back tick event

--- src/layout/mixins/events.coffee
+++ src/layout/mixins/events.coffee
@@ -1,6 +1,6 @@
 # http://stackoverflow.com/questions/879152/ how-do-i-make-javascript-beep
 # http://jsfiddle.net/55Kfu/
-#
+#

 {my} = require '../../my'

@@ -23,15 +23,16 @@
   interval: my.duration.step
   speed: 0
   _EXPORTS: ['step', 'stop', 'run', 'error', 'done']
-
+
   step: (world, args) ->
     console.log 'stepping'
+    world.send 'tick'
     world.send 'fetch'
     world.send 'execute'
     world.send 'prefetch'
-
+
   stop: (world, args) -> world.put('speed', 0)
-
+
   run: (world, args) ->
     world.put('speed', 1)
     step_and_repeat = (self) ->
@@ -41,7 +42,7 @@
         world.send 'step'
         setTimeout((-> self(self)), delay)
     step_and_repeat(step_and_repeat)
-
+
   error: (world, message) ->
     console.error message
     beep(my.duration.tone, 2)


smooth scrolling

smooth scrolling

--- app/assets/javascripts/components/song.js.coffee
+++ app/assets/javascripts/components/song.js.coffee
@@ -1,6 +1,6 @@
 # # app/assets/javascripts/components/song.js.coffee
 pageScroll = () ->
-  $('.tabs').animate({scrollTop: 10000000}, 10000000)
+  $('.tabs').animate({scrollTop: 10000000}, 600000000, "linear")

 $(document).on "click", "#play", ->
   console.log("scrolling!!")


EDSC-245: Fix test failures caused by map.loadingLayers not being set.

--- app/assets/javascripts/modules/map/map.js.coffee
+++ app/assets/javascripts/modules/map/map.js.coffee
@@ -29,6 +29,7 @@
       $(el).data('map', this)
       @layers = []
       map = @map = new L.Map(el, zoomControl: false, attributionControl: false)
+      map.loadingLayers = 0

       @_buildLayerSwitcher()
       map.addLayer(new GranuleVisualizationsLayer())


load more data on pan left/right

load more data on pan left/right

--- app/assets/javascripts/monitoring/util.js.coffee
+++ app/assets/javascripts/monitoring/util.js.coffee
@@ -43,10 +43,11 @@
     # set range if known
     opts.valueRange = [ 0, 100 ]

-  # custom drag interaction - allow toggling pan mode
   opts.interactionModel = _.clone(Dygraph.Interaction.defaultModel)
+
+  # override mousedown to allow toggling pan mode
   opts.interactionModel.mousedown = (event, g, context) ->
-    # // Right-click should not initiate a zoom.
+    # Right-click should not initiate a zoom.
     if event.button && event.button == 2
       return

@@ -57,29 +58,44 @@
     else
       Dygraph.startZoom(event, g, context)

+  # override mouseup to load more data as we pan in either direction
   opts.interactionModel.mouseup = (event, g, context) ->
     if context.isZooming
       Dygraph.endZoom(event, g, context)
+
     else if context.isPanning
       Dygraph.endPan(event, g, context)

       # check if we need more data
       [minX, maxX] = g.xAxisRange()
-      start_diff = g.xAxisExtremes()[0] - minX
-      end_diff = maxX - g.xAxisExtremes()[1]
+      [dMinX, dMaxX] = g.xAxisExtremes()
+      start_diff = dMinX - minX
+      end_diff = maxX - dMaxX

-      if start_diff > 100000 || end_diff > 100000
-        # sufficiently panned, fetch more data
-        new_met = new Bixby.model.Metric({
-          id: metric.id
-          host_id: metric.get("metadata").host_id
-          start: parseInt(minX / 1000)
-          end: parseInt(maxX / 1000)
-          downsample: "1m-avg"
-        })
-        Backbone.multi_fetch [ new_met ], (err, results) ->
-          # don't replace data... add on to existing data
-          g.updateOptions({ file: new_met.tuples() })
+      startX = null
+      if start_diff > 100000
+        startX = minX
+        endX = dMinX
+
+      else if end_diff > 100000
+        startX = dMaxX
+        endX = maxX
+
+      return if startX == null
+
+      query = metric.get("query")
+      new_met = new Bixby.model.Metric({
+        id: metric.id
+        host_id: metric.get("metadata").host_id
+        start: parseInt(startX / 1000)
+        end: parseInt(endX / 1000)
+        downsample: query.downsample || "5m-avg"
+      })
+      Backbone.multi_fetch [ new_met ], (err, results) ->
+        # don't replace data... add on to existing data
+        all_data = g.file_.concat(new_met.tuples()).sort (a,b) ->
+          return a[0] - b[0]
+        g.updateOptions({ file: all_data })

   # draw
@@ -122,6 +138,7 @@
           downsample: "1m-avg"
         })
         Backbone.multi_fetch [ new_met ], (err, results) ->
+          metric.set({query: new_met.get("query")})
           g.updateOptions({ file: new_met.tuples() })

   }


add faq to header

add faq to header

--- app/assets/javascripts/partials/yale-header.cjsx
+++ app/assets/javascripts/partials/yale-header.cjsx
@@ -39,6 +39,10 @@
               About

+
+              FAQ
+

+


add test

add test

--- jThree/test/Goml/GomlLoaderTest.coffee
+++ jThree/test/Goml/GomlLoaderTest.coffee
@@ -1,22 +1,34 @@
 jsdom = require('jsdom').jsdom
+
+global.document = jsdom ''
+global.window = document.parentWindow
+
+$ = require 'jquery'

 GomlLoader = require '../../src/Goml/GomlLoader'

 describe 'GomlLoader', ->
   describe 'loadScriptTag', ->
-    global.document = jsdom ''
-    global.window = global.document.parentWindow
-    $ = global.jQuery = require('jquery')(global.window)
     instance = null
-
-    before ->

     beforeEach ->
       instance = new GomlLoader()

-    it 'should return goml source string when jQuery object which has src attribute is given', ->
+    it 'should return goml source string when jQuery object which does not have src attribute is given', ->
       obj = $('')
       mock = sinon.mock(instance)
       mock.expects('scriptLoaded').withArgs('{{goml}}')
       instance.loadScriptTag(obj)
       assert(mock.verify() == true)
+
+  describe 'scriptLoaded', ->
+    instance = null
+
+    beforeEach ->
+      instance = new GomlLoader()
+
+    it 'should throw error when goml source is empty', ->
+      source = ''
+      spy = sinon.spy(instance, 'scriptLoaded')
+      try spy(source)
+      assert(spy.threw() == true)


fix link to repo_url push view

fix link to repo_url push view

--- coffee/views.coffee
+++ coffee/views.coffee
@@ -82,7 +82,7 @@
 views["push"] = """

-
Pushed {{num}} commit(s) to {{repo}}

+
Pushed {{num}} commit(s) to {{repo}}

     {{#commits}}

{{commit}}


Added #walk() example

Added #walk() example

--- src/dahelpers.coffee
+++ src/dahelpers.coffee
@@ -726,10 +726,22 @@
     #
     # The callback function takes two arguments. The first argument is the
     # property currently being iterated, and the second argument is the name of
-    # the key.
+    # the key. The key name will be a full property path. For example a key
+    # name for `obj.foo.bar.baz` would be 'foo.bar.baz' (see example).
     #
     # The undocumented `key` argument is an internal implementation detail, and
     # should not be be passed under normal circumstances.
+    #
+    # Example:
+    #
+    #     var obj = {a: 1, b: 2, c: {a: 1, b: 2}};
+    #     dahelpers.walk(obj, console.log);
+    #     // Logs:
+    #     // 1 a
+    #     // 2 b
+    #     // Object { a=1, b=2 } c
+    #     // 1 c.a
+    #     // 2 c.b
     #
     walk: (obj, cb, key=null) ->
       if obj is Object(obj) and obj.constructor isnt Array


no need for huge buffer if value is null

--- src/data-type.coffee
+++ src/data-type.coffee
@@ -289,6 +289,8 @@
         length = parameter.length
       else if parameter.value?
         length = parameter.value.toString().length
+      else if parameter.value is null
+        length = 1
       else
         length = @.maximumLength

@@ -347,6 +349,8 @@
         length = parameter.length
       else if parameter.value?
         length = parameter.value.toString().length
+      else if parameter.value is null
+        length = 1
       else
         length = @maximumLength


Fix deb deploy not working anymore.

Fix deb deploy not working anymore.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -445,6 +445,7 @@
                     console.log 'fpm arguments: ' + fpmArgs.join(' ')
                     process.exit(1)
             return child
+
         names['linux'].push 'deploy:linux-' + arch + ':' + target
         fullNames.push('deploy:linux-' + arch + ':' + target)

@@ -488,8 +489,10 @@
         options.rename = (dest, src) -> path.join(dest, "#{json.name}-#{json.version}-linux-#{options.arch}.deb")
         debian options

-        names['linux'].push 'deploy:linux-' + arch + ':deb'
-        fullNames.push('deploy:linux-' + arch + ':deb')
+        done()
+
+    names['linux'].push 'deploy:linux-' + arch + ':deb'
+    fullNames.push('deploy:linux-' + arch + ':deb')

     gulp.task "deploy:linux-#{arch}:deb",
         gulp.series("deploy:linux-#{arch}", "deploy:linux-#{arch}:deb:nodep")


Run gulp build tasks in sequence to debug heroku postinstall script

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -33,10 +33,10 @@
 gulp.task "webpack-hotdev", plugins.shell.task ["npm run hotdev"]

 gulp.task "build", (cb) ->
-  runSequence "clean", [
-    "i18n"
-    "copy-static"
-    "bower"
-    "stylesheets-fingerprint"
-  ], "webpack-build", "minify", cb
+  runSequence "clean",
+    "i18n",
+    "copy-static",
+    "bower",
+    "stylesheets-fingerprint",
+    "webpack-build", "minify", cb


Gulp: removing not necessary empty spaces

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -20,8 +20,6 @@
     br.bundle()
         .pipe source 'bundle.js'
         .pipe gulp.dest './smsgw/static/build/'
-
-

 ###
     Watching files


Fix format message helper

Fix format message helper

--- assets/scripts/app/helpers/format_message.coffee
+++ assets/scripts/app/helpers/format_message.coffee
@@ -1,4 +1,4 @@
-formatMessageHelper = Travis.Helpers.formatMessageHelper
+formatMessageHelper = Travis.Helpers.formatMessage
 safe = Travis.Helpers.safe

 formatMessage = (message, options) ->


Fix distribution assets task

Fix distribution assets task

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -113,9 +113,9 @@
   del(['./dist/*.css'], done)

 gulp.task '_copyResources', ['_cleanResources'], ->
-  destDir = './dist/'
-  gulp.src('./resources/images/**/*.svg')
-    .pipe(flatten())
+  destDir = './dist/style/resources'
+  baseDir = './resources/images'
+  gulp.src("#{baseDir}/**/*.svg", base: baseDir)
     .pipe(gulp.dest(destDir))

 gulp.task '_cleanResources', (done) ->
@@ -180,7 +180,7 @@
     './dist/tutor.min-*.js',
     './dist/tutor.min-*.css',
     './dist/fonts/*',
-    './dist/*.svg'], {base: './dist'})
+    './dist/style/resources/**/*.svg'], {base: './dist'})
     .pipe(tar('archive.tar'))
     .pipe(gzip())
     .pipe(gulp.dest('./dist/'))
@@ -221,7 +221,7 @@
             if req.url.match(/\.ttf$/)
                 res.setHeader('Content-Type', 'application/octet-stream')
             next()
-
+
     ]
   connect.server(config)


Minor: typo in function name

Minor: typo in function name

--- src/resources/forms/orbeon/builder/resources/cell/control-resources-editor/placeholders.coffee
+++ src/resources/forms/orbeon/builder/resources/cell/control-resources-editor/placeholders.coffee
@@ -37,7 +37,7 @@
         elementInDiv.attr('placeholder', placeholderTextForResource[lhha])

 # Reads and stores the placeholder text
-updatePlacehodlerText = ->
+updatePlaceholderText = ->
     foundDifference = false
     _.each resourceNames, (lhha) ->
         newText = $(".fb-message-enter-#{lhha}").children().text()
@@ -53,9 +53,9 @@

 $ ->
     # Do initial update when the page is loaded
-    updatePlacehodlerText()
+    updatePlaceholderText()
     # Update on Ajax response in case the language changed
-    Events.ajaxResponseProcessedEvent.subscribe updatePlacehodlerText
+    Events.ajaxResponseProcessedEvent.subscribe updatePlaceholderText

     # When label/hint empty, show placeholder hinting users can click to edit the label/hint
     Builder.mouseEntersGridTdEvent.subscribe ({gridTd}) ->


comment authorization token

comment authorization token

--- public/init.coffee
+++ public/init.coffee
@@ -2,7 +2,7 @@

 # TODO remove this before going live
 $.ajaxSettings.headers =
-  "Authorization": "Bearer M0mPgTiGPtw5LkdCGwhel3gcGc5PqIPF"
+  # "Authorization": "Bearer M0mPgTiGPtw5LkdCGwhel3gcGc5PqIPF"
   "Accept": "application/vnd.epages.v1+json"

 App.start()


show number of tokens for `dockerhub trigger show`

--- src/dockerhub.coffee
+++ src/dockerhub.coffee
@@ -95,9 +95,10 @@
   robot.respond /dockerhub\s+trigger\s+show\s*$/, (res) ->
     robot.logger.debug("hubot-docker build-trigger show")
     trigger_tokens = (robot.brain.get("dockerhub-trigger-tokens") || {})
-    msg = ""
+    num_tokens = Object.keys(trigger_tokens).length
+    msg = "#{num_tokens} 件のトークンが設定されています\n"
     for repo, token of trigger_tokens
-      msg += "#{repo} の build trigger のトークンは #{token} です\n"
+      msg += "#{repo} : #{token}\n"
     res.send msg

   robot.respond /dockerhub\s+trigger\s+invoke\s+(\S+)\s*$/, (res) ->


play, pause和stop方法不会被反复调用

play, pause和stop方法不会被反复调用

--- src/js/player.coffee
+++ src/js/player.coffee
@@ -197,21 +197,24 @@
                     def.resolve()
                 , 0)

-            st = @getState()
-            # 只有如下3种情况会触发_fetch选链：
-            # 1) 内核首次使用或被reset过 (STATES.STOP)
-            # 2) 上一首歌播放完成自动触发下一首的播放 (STATES.END)
-            # 3) 某些移动浏览器无交互时不能触发自动播放 (会被卡在STATES.BUFFERING)
-            if st in [STATES.STOP, STATES.END] or st is STATES.BUFFERING and @curPos() is 0
-                # XXX: 应该在_fetch中决定是否发起选链。
-                # 即是否从cache中取, 是否setUrl都是依据_fetch的实现去决定。
-                # 如果继承时覆盖重写_fetch, 这些都要自己权衡。
-                @trigger('player:fetch:start')
-                @_fetch().done ->
-                    self.trigger('player:fetch:done')
+            if @_st isnt 'play'
+                @_st = 'play'
+
+                st = @getState()
+                # 只有如下3种情况会触发_fetch选链：
+                # 1) 内核首次使用或被reset过 (STATES.STOP)
+                # 2) 上一首歌播放完成自动触发下一首的播放 (STATES.END)
+                # 3) 某些移动浏览器无交互时不能触发自动播放 (会被卡在STATES.BUFFERING)
+                if st in [STATES.STOP, STATES.END] or st is STATES.BUFFERING and @curPos() is 0
+                    # XXX: 应该在_fetch中决定是否发起选链。
+                    # 即是否从cache中取, 是否setUrl都是依据_fetch的实现去决定。
+                    # 如果继承时覆盖重写_fetch, 这些都要自己权衡。
+                    @trigger('player:fetch:start')
+                    @_fetch().done ->
+                        self.trigger('player:fetch:done')
+                        play()
+                else
                     play()
-            else
-                play()

             return def.promise()

@@ -220,8 +223,10 @@
          * @return {player}
         ###
         pause: ->
-            @engine.pause()
-            @trigger('player:pause')
+            if @_st isnt 'pause'
+                @_st = 'pause'
+                @engine.pause()
+                @trigger('player:pause')
             @

         ###*
@@ -229,6 +234,8 @@
          * @return {player}
         ###
         stop: ->
+            if @_st isnt 'stop'
+                @_st = 'stop'
             @engine.stop()
             @trigger('player:stop')
             @


node and edge metadata change methods

node and edge metadata change methods

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -179,6 +179,20 @@
       group.nodes[index] = newId

     @emit 'renameNode', oldId, newId
+
+  # ## Changing a node's metadata
+  #
+  # Node metadata can be set or changed by calling this method.
+  setNodeMetadata: (id, metadata) ->
+    node = @getNode id
+    return unless node
+    node.metadata = {} unless node.metadata
+
+    for item, val of metadata
+      node.metadata[item] = val
+
+    @emit 'changeNode', node
+

   # ## Connecting nodes
   #
@@ -214,7 +228,7 @@
   #
   # or:
   #
-  #     myGraph.removeEdge 'Display', 'in', 'Foo', 'out'
+  #     myGraph.removeEdge 'Display', 'out', 'Foo', 'in'
   #
   # Removing a connection will emit the `removeEdge` event.
   removeEdge: (node, port, node2, port2) ->
@@ -232,6 +246,32 @@
             continue
         @emit 'removeEdge', edge
         @edges.splice index, 1
+
+  # ## Getting an edge
+  #
+  # Edge objects can be retrieved from the graph by the node and port IDs:
+  #
+  #     myEdge = myGraph.getEdge 'Read', 'out', 'Write', 'in'
+  getEdge: (node, port, node2, port2) ->
+    for edge,index in @edges
+      continue unless edge
+      if edge.from.node is node and edge.from.port is port
+        if edge.to.node is node2 and edge.to.port is port2
+          return edge
+    return null
+
+  # ## Changing an edge's metadata
+  #
+  # Edge metadata can be set or changed by calling this method.
+  setEdgeMetadata: (node, port, node2, port2, metadata) ->
+    edge = @getEdge node, port, node2, port2
+    return unless edge
+    edge.metadata = {} unless edge.metadata
+
+    for item, val of metadata
+      edge.metadata[item] = val
+
+    @emit 'changeEdge', edge

   # ## Adding Initial Information Packets
   #


Emit removeNode only after node has been removed

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -94,10 +94,10 @@
       if initializer.to.node is node.id
         @removeInitial initializer.to.node, initializer.to.port

-    @emit 'removeNode', node
-
     if -1 isnt @nodes.indexOf node
       @nodes.splice @nodes.indexOf(node), 1
+
+    @emit 'removeNode', node

   # ## Getting a node
   #


Performance tuning

Performance tuning

No need for a recursive, arbitrary dimension,
unoptimized carthesian product function here (slow!).
Avoid creating Buffer instances if not needed.

runtime ~2,5s -> 650 ms -- pure rendering

--- src/prerenderer/tile_tools.coffee
+++ src/prerenderer/tile_tools.coffee
@@ -7,8 +7,9 @@
 exports.addTile = (source, src_x, src_y, target, trg_x, trg_y) ->
   [x_off_src, y_off_src] = _upperRightCornerPixelOfEnclosingRectangleInTilemap src_x, src_y
   [x_off_trg, y_off_trg] = _upperRightCornerPixelOfEnclosingRectangleInIsoGrid trg_x, trg_y
-  for [x,y] in js.carthesianProduct [0..tileWidth-1], [0..tileHeight-1]
-    _addPixelInPngs source, x_off_src+x, y_off_src+y, target, x_off_trg+x, y_off_trg+y
+  for y in [0..tileHeight-1] # performance critical pixel loop
+    for x in [0..tileWidth-1]
+      _addPixelInPngs source, x_off_src+x, y_off_src+y, target, x_off_trg+x, y_off_trg+y

 _upperRightCornerPixelOfEnclosingRectangleInTilemap = (x, y) ->
   [
@@ -25,28 +26,22 @@
 _addPixelInPngs = (source, x, y, target, u, v) ->
   src_idx = source.width*y + x << 2
   dst_idx = target.width*v + u << 2
-  addPixel = source.data.slice src_idx, src_idx+4
-  originalPixel = target.data.slice dst_idx, dst_idx+4
-  newPixel = _addPixelOver addPixel, originalPixel
-  newPixel.copy target.data, dst_idx
+  pixel_a = source.data
+  pixel_b = target.data

-_addPixelOver = (pixel_a, pixel_b) ->
-  red_a = pixel_a.readUInt8 0
-  green_a = pixel_a.readUInt8 1
-  blue_a = pixel_a.readUInt8 2
-  alpha_a = pixel_a.readUInt8 3
+  red_a   = pixel_a.readUInt8 src_idx
+  green_a = pixel_a.readUInt8 src_idx + 1
+  blue_a  = pixel_a.readUInt8 src_idx + 2
+  alpha_a = pixel_a.readUInt8 src_idx + 3

-  red_b = pixel_b.readUInt8 0
-  green_b = pixel_b.readUInt8 1
-  blue_b = pixel_b.readUInt8 2
-  alpha_b = pixel_b.readUInt8 3
+  red_b   = pixel_b.readUInt8 dst_idx
+  green_b = pixel_b.readUInt8 dst_idx + 1
+  blue_b  = pixel_b.readUInt8 dst_idx + 2
+  alpha_b = pixel_b.readUInt8 dst_idx + 3

-  alpha_o = alpha_a + alpha_b*(255-alpha_a)/255
-  new Buffer [
-    (red_a*alpha_a + red_b*alpha_b*(255-alpha_a)/255)/alpha_o,
-    (green_a*alpha_a + green_b*alpha_b*(255-alpha_a)/255)/alpha_o,
-    (blue_a*alpha_a + blue_b*alpha_b*(255-alpha_a)/255)/alpha_o,
-    alpha_o,
-  ]
-
-exports._addPixelOver = _addPixelOver
+  chi = alpha_b*(255-alpha_a)/255
+  alpha_o = alpha_a + chi
+  pixel_b[dst_idx    ] = (red_a  *alpha_a + red_b  *chi)/alpha_o
+  pixel_b[dst_idx + 1] = (green_a*alpha_a + green_b*chi)/alpha_o
+  pixel_b[dst_idx + 2] = (blue_a *alpha_a + blue_b *chi)/alpha_o
+  pixel_b[dst_idx + 3] = alpha_o


fix(attachments): Properly handle HTTP errors in addition to socket errors #2136

--- src/flux/stores/file-download-store.coffee
+++ src/flux/stores/file-download-store.coffee
@@ -54,9 +54,25 @@
     # initiate multiple downloads for the same file
     return @promise if @promise

+    # Note: we must resolve or reject with `this`
     @promise = new Promise (resolve, reject) =>
       stream = fs.createWriteStream(@targetPath)
       @state = State.Downloading
+
+      onFailed = (err) =>
+        @request = null
+        stream.end()
+        @state = State.Failed
+        if fs.existsSync(@targetPath)
+          fs.unlinkSync(@targetPath)
+        reject(err)
+
+      onSuccess = =>
+        @request = null
+        stream.end()
+        @state = State.Finished
+        @percent = 100
+        resolve(@)

       NylasAPI.makeRequest
         json: false
@@ -70,21 +86,19 @@
             @percent = progress.percent
             @progressCallback()

-          .on "error", (err) =>
-            @request = null
-            @state = State.Failed
-            stream.end()
-            if fs.existsSync(@targetPath)
-              fs.unlinkSync(@targetPath)
-            reject(@)
+          # This is a /socket/ error event, not an HTTP error event. It fires
+          # when the conn is dropped, user if offline, but not on HTTP status codes.
+          # It is sometimes called in place of "end", not before or after.
+          .on("error", onFailed)

           .on "end", =>
             return if @state is State.Failed
-            @request = null
-            @state = State.Finished
-            @percent = 100
-            stream.end()
-            resolve(@) # Note: we must resolve with this
+
+            statusCode = @request.response?.statusCode
+            if [200, 202, 204].includes(statusCode)
+              onSuccess()
+            else
+              onFailed(new Error("Server returned a #{statusCode}"))

           .pipe(stream)


new dat dudes

new dat dudes

--- scripts/name-expander.coffee
+++ scripts/name-expander.coffee
@@ -14,6 +14,8 @@
 nmathrani = "Neeraj Mathrani"
 steve = "@srude"
 fei = "@fei"
+brad = "@brad"
+

 noc_members = ["Carter1", "Cames1", "jdowns", "jdowns1", "jtramullas", "nstuhring", "ctrytten1", "ctrytten", "akim", "nsanches", "jkim", "ebednik", "nhooper"]

@@ -24,7 +26,10 @@
   josiah,
   steve,
   kyle,
-  fei
+  fei,
+  cliff,
+  chong,
+  brad
 ]

 chef_repo_mergers = [
@@ -61,29 +66,29 @@
   robot.hear /^@dat:? (.+)/i, (msg) ->
     msg.send dat_members.join(", ") + ": ^^"

-  robot.hear /^(mashion)( team| dudes| guys)*:(.+)/i, (msg) ->
-    msg.send mashion_devs.join(", ") + ": ^^"
+  # robot.hear /^(mashion)( team| dudes| guys)*:(.+)/i, (msg) ->
+  #   msg.send mashion_devs.join(", ") + ": ^^"

-  robot.hear /^@?(noc)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
-    msg.send noc_members.join(", ") + ": ^^"
+  # robot.hear /^@?(noc)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
+  #   msg.send noc_members.join(", ") + ": ^^"

-  robot.hear /^mergers:(.+)/i, (msg) ->
+  robot.hear /^@mergers:? (.+)/i, (msg) ->
     msg.send chef_repo_mergers.join(", ") + ": ^^"

-  robot.hear /^@?(build)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
-    msg.send build_members.join(", ") + ": ^^"
+  # robot.hear /^@?(build)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
+  #   msg.send build_members.join(", ") + ": ^^"

-  robot.hear /^(di|devinf)( team| dudes| guys)*:(.+)/i, (msg) ->
-    msg.send devinf_members.join(", ") + ": ^^"
+  # robot.hear /^(di|devinf)( team| dudes| guys)*:(.+)/i, (msg) ->
+  #   msg.send devinf_members.join(", ") + ": ^^"

-  robot.hear /^@?(sysadmin)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
-    msg.send sysadmin_members.join(", ") + ": ^^"
+  # robot.hear /^@?(sysadmin)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
+  #   msg.send sysadmin_members.join(", ") + ": ^^"

-  robot.hear /^@?(ops)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
-    msg.send sysadmin_members.join(", ") + ": ^^"
+  # robot.hear /^@?(ops)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
+  #   msg.send sysadmin_members.join(", ") + ": ^^"

-  robot.hear /^@?(neteng)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
-    msg.send neteng_members.join(", ") + ": ^^"
+  # robot.hear /^@?(neteng)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
+  #   msg.send neteng_members.join(", ") + ": ^^"

-  robot.hear /^@?(dba)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
-    msg.send dba_members.join(", ") + ": ^^"
+  # robot.hear /^@?(dba)( team| dudes| guys)*(:| )(.+)/i, (msg) ->
+  #   msg.send dba_members.join(", ") + ": ^^"


Remove zombie test file

Remove zombie test file

--- test.coffee
+++ test.coffee
@@ -1,42 +0,0 @@
-
-class Dog
-    dogName: "fido"
-    constructor: (@dogName) ->
-
-    doStuff: ->
-        console.log('the dog is walking');
-        sayHello.call(this);
-
-    sayHello = ->
-        console.log("Hi! I'm "+@dogName);
-
-ralph = new Dog("ralph");
-ralph.doStuff();
-
-peter = new Dog("peter");
-ralph.doStuff();
-peter.doStuff();
-ralph.doStuff();
-
-###
-class GetSet
-
-    schema = {}
-
-    constructor: (sch = {}) ->
-        schema = sch
-
-    set: (key, value) ->
-        schema[key] = value
-
-    get: (key) ->
-        schema[key]
-
-gs1 = new GetSet
-gs2 = new GetSet
-
-gs1.set 'a', 'b'
-gs2.set 'a', 'c'
-
-console.log gs1.get 'a'
-###


a few more ipv6 changes

a few more ipv6 changes


waypoint -- docc'd down to the SplatNode

--- nodes.coffee
+++ nodes.coffee
@@ -37,6 +37,10 @@
   # the top level of a block (which would be unnecessary), and we haven't
   # already been asked to return the result (because statements know how to
   # return results).
+  #
+  # If a Node is *top_sensitive*, that means that it needs to compile differently
+  # depending on whether it's being used as part of a larger expression, or is a
+  # top-level statement within the function body.
   compile: (o) ->
     @options: merge o or {}
     @tab:     o.indent
@@ -471,9 +475,10 @@
     arr:  Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, literal(name))])
     (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)

-
-# An array slice literal. Unlike JavaScript's Array#slice, the second parameter
-# specifies the index of the end of the slice (just like the first parameter)
+#### SliceNode
+
+# An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter
+# specifies the index of the end of the slice, just as the first parameter
 # is the index of the beginning.
 exports.SliceNode: class SliceNode extends BaseNode
   type: 'Slice'
@@ -488,8 +493,9 @@
     plus_part:  if @range.exclusive then '' else ' + 1'
     ".slice($from, $to$plus_part)"

-
-# An object literal.
+#### ObjectNode
+
+# An object literal, nothing fancy.
 exports.ObjectNode: class ObjectNode extends BaseNode
   type: 'Object'

@@ -498,7 +504,9 @@

   # All the mucking about with commas is to make sure that CommentNodes and
   # AssignNodes get interleaved correctly, with no trailing commas or
-  # commas affixed to comments. TODO: Extract this and add it to ArrayNode.
+  # commas affixed to comments.
+  #
+  # *TODO: Extract this and add it to ArrayNode*.
   compile_node: (o) ->
     o.indent: @idt(1)
     non_comments: prop for prop in @properties when not (prop instanceof CommentNode)
@@ -513,49 +521,7 @@
     inner: if props then '\n' + props + '\n' + @idt() else ''
     "{$inner}"

-
-# A class literal, including optional superclass and constructor.
-exports.ClassNode: class ClassNode extends BaseNode
-  type: 'Class'
-
-  constructor: (variable, parent, props) ->
-    @children: compact flatten [@variable: variable, @parent: parent, @properties: props or []]
-
-  compile_node: (o) ->
-    extension:   @parent and new ExtendsNode(@variable, @parent)
-    constructor: null
-    props:       new Expressions()
-    o.top:       true
-    ret:         del o, 'returns'
-
-    for prop in @properties
-      if prop.variable and prop.variable.base.value is 'constructor'
-        func: prop.value
-        func.body.push(new ReturnNode(literal('this')))
-        constructor: new AssignNode(@variable, func)
-      else
-        if prop.variable
-          val: new ValueNode(@variable, [new AccessorNode(prop.variable, 'prototype')])
-          prop: new AssignNode(val, prop.value)
-        props.push prop
-
-    if not constructor
-      if @parent
-        applied: new ValueNode(@parent, [new AccessorNode(literal('apply'))])
-        constructor: new AssignNode(@variable, new CodeNode([], new Expressions([
-          new CallNode(applied, [literal('this'), literal('arguments')])
-        ])))
-      else
-        constructor: new AssignNode(@variable, new CodeNode())
-
-    construct:                       @idt() + constructor.compile(o) + ';\n'
-    props:     if props.empty() then '' else props.compile(o) + '\n'
-    extension: if extension     then @idt() + extension.compile(o) + ';\n' else ''
-    returns:   if ret           then '\n' + @idt() + 'return ' + @variable.compile(o) + ';' else ''
-    "$construct$extension$props$returns"
-
-statement ClassNode
-
+#### ArrayNode

 # An array literal.
 exports.ArrayNode: class ArrayNode extends BaseNode
@@ -578,9 +544,60 @@
     ending: if objects.indexOf('\n') >= 0 then "\n$@tab]" else ']'
     "[$objects$ending"

+#### ClassNode
+
+# The CoffeeScript class definition.
+exports.ClassNode: class ClassNode extends BaseNode
+  type: 'Class'
+
+  # Initialize a **ClassNode** with its name, an optional superclass, and a
+  # list of prototype property assignments.
+  constructor: (variable, parent, props) ->
+    @children: compact flatten [@variable: variable, @parent: parent, @properties: props or []]
+
+  # Instead of generating the JavaScript string directly, we build up the
+  # equivalent syntax tree and compile that, in pieces. You can see the
+  # constructor, property assignments, and inheritance getting built out below.
+  compile_node: (o) ->
+    extension:   @parent and new ExtendsNode(@variable, @parent)
+    constructor: null
+    props:       new Expressions()
+    o.top:       true
+    ret:         del o, 'returns'
+
+    for prop in @properties
+      if prop.variable and prop.variable.base.value is 'constructor'
+        func: prop.value
+        func.body.push(new ReturnNode(literal('this')))
+        constructor: new AssignNode(@variable, func)
+      else
+        if prop.variable
+          val: new ValueNode(@variable, [new AccessorNode(prop.variable, 'prototype')])
+          prop: new AssignNode(val, prop.value)
+        props.push prop
+
+    if not constructor
+      if @parent
+        applied: new ValueNode(@parent, [new AccessorNode(literal('apply'))])
+        constructor: new AssignNode(@variable, new CodeNode([], new Expressions([
+          new CallNode(applied, [literal('this'), literal('arguments')])
+        ])))
+      else
+        constructor: new AssignNode(@variable, new CodeNode())
+
+    construct:                       @idt() + constructor.compile(o) + ';\n'
+    props:     if props.empty() then '' else props.compile(o) + '\n'
+    extension: if extension     then @idt() + extension.compile(o) + ';\n' else ''
+    returns:   if ret           then '\n' + @idt() + 'return ' + @variable.compile(o) + ';' else ''
+    "$construct$extension$props$returns"
+
+statement ClassNode
+
+#### PushNode

 # A faux-node that is never created by the grammar, but is used during
-# code generation to generate a quick "array.push(value)" tree of nodes.
+# code generation to generate a quick `array.push(value)` tree of nodes.
+# Helpful for recording the result arrays from comprehensions.
 PushNode: exports.PushNode: {

   wrap: (array, expressions) ->
@@ -592,6 +609,7 @@

 }

+#### ClosureNode

 # A faux-node used to wrap an expressions body in a closure.
 ClosureNode: exports.ClosureNode: {
@@ -603,11 +621,14 @@

 }

-
-# Setting the value of a local variable, or the value of an object property.
+#### AssignNode
+
+# The **AssignNode** is used to assign a local variable to value, or to set the
+# property of an object -- including within object literals.
 exports.AssignNode: class AssignNode extends BaseNode
   type: 'Assign'

+  # Matchers for detecting prototype assignments.
   PROTO_ASSIGN: /^(\S+)\.prototype/
   LEADING_DOT:  /^\.(prototype\.)?/

@@ -624,6 +645,10 @@
   is_statement: ->
     @is_value() and (@variable.is_array() or @variable.is_object())

+  # Compile an assignment, delegating to `compile_pattern_match` or
+  # `compile_splice` if appropriate. Keep track of the name of the base object
+  # we've been assigned to, for correct internal references. If the variable
+  # has not been seen yet within the current scope, declare it.
   compile_node: (o) ->
     top:    del o, 'top'
     return  @compile_pattern_match(o) if @is_statement()
@@ -645,9 +670,10 @@
     val: "${@tab}return $val" if o.returns
     val

-  # Implementation of recursive pattern matching, when assigning array or
+  # Brief implementation of recursive pattern matching, when assigning array or
   # object literals to a value. Peeks at their properties to assign inner names.
-  # See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
+  # See the [ECMAScript Harmony Wiki](http://wiki.ecmascript.org/doku.php?id=harmony:destructuring)
+  # for details.
   compile_pattern_match: (o) ->
     val_var: o.scope.free_variable()
     value: if @value.is_statement() then ClosureNode.wrap(@value) else @value
@@ -668,6 +694,8 @@
     code += "\n${@tab}return ${ @variable.compile(o) };" if o.returns
     code

+  # Compile the assignment from an array splice literal, using JavaScript's
+  # `Array#splice` method.
   compile_splice: (o) ->
     name:   @variable.compile(merge(o, {only_first: true}))
     l:      @variable.properties.length
@@ -678,9 +706,11 @@
     val:    @value.compile(o)
     "$name.splice.apply($name, [$from, $to].concat($val))"

-
-# A function definition. The only node that creates a new Scope.
-# A CodeNode does not have any children -- they're within the new scope.
+#### CodeNode
+
+# A function definition. This is the only node that creates a new Scope.
+# When for the purposes of walking the contents of a function body, the CodeNode
+# has no *children* -- they're within the inner scope.
 exports.CodeNode: class CodeNode extends BaseNode
   type: 'Code'

@@ -689,6 +719,11 @@
     @body:    body or new Expressions()
     @bound:   tag is 'boundfunc'

+  # Compilation creates a new scope unless explicitly asked to share with the
+  # outer scope. Handles splat parameters in the parameter list by peeking at
+  # the JavaScript `arguments` objects. If the function is bound with the `=>`
+  # arrow, generates a wrapper that saves the current value of `this` through
+  # a closure.
   compile_node: (o) ->
     shared_scope: del o, 'shared_scope'
     top:          del o, 'top'
@@ -715,9 +750,12 @@
   top_sensitive: ->
     true

+  # When traversing (for printing or inspecting), return the real children of
+  # the function -- the parameters and body of expressions.
   real_children: ->
     flatten [@params, @body.expressions]

+  # Custom `traverse` implementation that uses the `real_children`.
   traverse: (block) ->
     block this
     block(child) for child in @real_children()
@@ -727,6 +765,7 @@
     children: (child.toString(idt + TAB) for child in @real_children()).join('')
     "\n$idt$children"

+#### SplatNode

 # A splat, either as a parameter to a function, an argument to a call,
 # or in a destructuring assignment.


Core Compiler is JSLint-free again, after removing a couple extra semicolons that snuck in when if statements compiled at the top level.

--- nodes.coffee
+++ nodes.coffee
@@ -46,7 +46,7 @@
       del @options, 'operation'
       del @options, 'chainRoot' unless this instanceof AccessorNode or this instanceof IndexNode
     top     = if @topSensitive() then @options.top else del @options, 'top'
-    closure = @isStatement() and not @isPureStatement() and not top and
+    closure = @isStatement(o) and not @isPureStatement() and not top and
               not @options.asStatement and not (this instanceof CommentNode) and
               not @containsPureStatement()
     if closure then @compileClosure(@options) else @compileNode(@options)
@@ -221,7 +221,7 @@
   compileExpression: (node, o) ->
     @tab = o.indent
     compiledNode = node.compile merge o, top: true
-    if node.isStatement() then compiledNode else "#{@idt()}#{compiledNode};"
+    if node.isStatement(o) then compiledNode else "#{@idt()}#{compiledNode};"

 # Wrap up the given nodes as an **Expressions**, unless it already happens
 # to be one.
@@ -250,8 +250,8 @@
   isPureStatement: LiteralNode::isStatement

   compileNode: (o) ->
-    idt = if @isStatement() then @idt() else ''
-    end = if @isStatement() then ';' else ''
+    idt = if @isStatement(o) then @idt() else ''
+    end = if @isStatement(o) then ';' else ''
     idt + @value + end

   toString: (idt) ->
@@ -279,7 +279,7 @@
     super o

   compileNode: (o) ->
-    o.asStatement = true if @expression.isStatement()
+    o.asStatement = true if @expression.isStatement(o)
     "#{@tab}return #{@expression.compile(o)};"

 #### ValueNode
@@ -323,8 +323,8 @@
     if @properties.length then this else @base

   # Values are considered to be statements if their base is a statement.
-  isStatement: ->
-    @base.isStatement and @base.isStatement() and not @hasProperties()
+  isStatement: (o) ->
+    @base.isStatement and @base.isStatement(o) and not @hasProperties()

   isNumber: ->
     @base instanceof LiteralNode and @base.value.match NUMBER
@@ -775,7 +775,7 @@
   # has not been seen yet within the current scope, declare it.
   compileNode: (o) ->
     top    = del o, 'top'
-    return   @compilePatternMatch(o) if @isStatement()
+    return   @compilePatternMatch(o) if @isStatement(o)
     return   @compileSplice(o) if @isValue() and @variable.isSplice()
     stmt   = del o, 'asStatement'
     name   = @variable.compile(o)
@@ -798,7 +798,7 @@
   # for details.
   compilePatternMatch: (o) ->
     valVar        = o.scope.freeVariable()
-    value         = if @value.isStatement() then ClosureNode.wrap(@value) else @value
+    value         = if @value.isStatement(o) then ClosureNode.wrap(@value) else @value
     assigns       = ["#{@tab}#{valVar} = #{ value.compile(o) };"]
     o.top         = true
     o.asStatement = true
@@ -1233,8 +1233,8 @@

   constructor: (@expression) ->

-  isStatement: ->
-    @expression.isStatement()
+  isStatement: (o) ->
+    @expression.isStatement(o)

   makeReturn: ->
     @expression.makeReturn()
@@ -1245,7 +1245,7 @@
   compileNode: (o) ->
     top  = del o, 'top'
     code = @expression.compile(o)
-    if @isStatement()
+    if @isStatement(o)
       return (if top then @tab + code + ';' else code)
     l    = code.length
     code = code.substr(o, l-1) if code.substr(l-1, 1) is ';'
@@ -1404,14 +1404,14 @@

   # The **IfNode** only compiles into a statement if either of its bodies needs
   # to be a statement. Otherwise a ternary is safe.
-  isStatement: ->
-    @statement or= !!(@tags.statement or @bodyNode().isStatement() or (@elseBody and @elseBodyNode().isStatement()))
+  isStatement: (o) ->
+    @statement or= !!((o and o.top) or @tags.statement or @bodyNode().isStatement(o) or (@elseBody and @elseBodyNode().isStatement(o)))

   compileCondition: (o) ->
     (cond.compile(o) for cond in flatten([@condition])).join(' || ')

   compileNode: (o) ->
-    if o.top or @isStatement() then @compileStatement(o) else @compileTernary(o)
+    if @isStatement(o) then @compileStatement(o) else @compileTernary(o)

   makeReturn: ->
     if @isStatement()
@@ -1433,7 +1433,7 @@
     condO    = merge o
     o.indent = @idt 1
     o.top    = true
-    ifDent   = if child or (top and not @isStatement()) then '' else @idt()
+    ifDent   = if child or (top and not @isStatement(o)) then '' else @idt()
     comDent  = if child then @idt() else ''
     body     = @body.compile(o)
     ifPart   = "#{ifDent}if (#{ @compileCondition(condO) }) {\n#{body}\n#{@tab}}"


output formatting cleanup; fixes #1254: improve `when` compilation; also causes single-line, single-statement conditional bodies to sit next to their conditions

--- nodes.coffee
+++ nodes.coffee
@@ -1226,7 +1226,11 @@
         rvar = o.scope.freeVariable 'results'
         set  = "#{@tab}#{rvar} = [];\n"
         body = Push.wrap rvar, body if body
-      body = Block.wrap [new If @guard, body] if @guard
+      if @guard
+        if body.expressions.length > 1
+          body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
+        else
+          body = Block.wrap [new If @guard, body] if @guard
       body = "\n#{ body.compile o, LEVEL_TOP }\n#{@tab}"
     code = set + @tab + "while (#{ @condition.compile o, LEVEL_PAREN }) {#{body}}"
     if @returns
@@ -1561,7 +1565,10 @@
       returnResult = "\n#{@tab}return #{rvar};"
       body         = Push.wrap rvar, body
     if @guard
-      body         = Block.wrap [new If @guard, body]
+      if body.expressions.length > 1
+        body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
+      else
+        body = Block.wrap [new If @guard, body] if @guard
     if @pattern
       body.expressions.unshift new Assign @name, new Literal "#{svar}[#{ivar}]"
     defPart     += @pluckDirectCall o, body
@@ -1689,9 +1696,12 @@

     cond     = @condition.compile o, LEVEL_PAREN
     o.indent += TAB
-    body     = @ensureBlock(@body).compile o
-    body     = "\n#{body}\n#{@tab}" if body
-    ifPart   = "if (#{cond}) {#{body}}"
+    body     = @ensureBlock(@body)
+    bodyc    = body.compile o
+    if body.expressions.length is 1 and !@elseBody and !child and bodyc and -1 is bodyc.indexOf '\n'
+      return "#{@tab}if (#{cond}) #{bodyc.replace /^\s+/, ''}"
+    bodyc    = "\n#{bodyc}\n#{@tab}" if bodyc
+    ifPart   = "if (#{cond}) {#{bodyc}}"
     ifPart   = @tab + ifPart unless child
     return ifPart unless @elseBody
     ifPart + ' else ' + if @isChain


Removing obsolete delete calls.

Removing obsolete delete calls.

--- nodes.coffee
+++ nodes.coffee
@@ -325,8 +325,6 @@
         baseline: + part
         complete: + part
         @last: part
-
-    del o, 'chain_root'

     if op and @wrapped then "($complete)" else complete

@@ -387,7 +385,6 @@
       args: (arg.compile(o) for arg in @args).join(', ')
       compilation: if @is_super then @compile_super(args, o)
       else "${@prefix()}${@variable.compile(o)}($args)"
-    del o, 'chain_root'
     if o.operation and @wrapped then "($compilation)" else compilation

   # `super()` is converted into a call against the superclass's implementation


Use and produce jpegs

Use and produce jpegs

--- scripts/ayp.coffee
+++ scripts/ayp.coffee
@@ -71,9 +71,9 @@
         return msg.reply "SOMETHING TERRIBLE HAPPENED: #{err}" if err

         # Save locally, upload, cleanup
-        name = "ayp-#{Date.now()}.png"
+        name = "ayp-#{Date.now()}.jpg"
         outPath = path.resolve("/tmp", name)
-        image.savePng outPath, 0, (err) ->
+        image.saveJpeg outPath, 95, (err) ->
           return console.error "Failed to write result:", err if err

           fs.readFile outPath, (err, data) ->
@@ -84,7 +84,7 @@

             info =
               headers:
-                'Content-Type': 'image/png'
+                'Content-Type': 'image/jpeg'
               body: data
             s3.put name, info, (err) ->
               return msg.reply "Woooops! Failed to upload: #{err}" if err


Calculator fixes #3739126, #3805646

Calculator fixes #3739126, #3805646

Scrollbars no longer cover equation
Equation display scrolls to the right as new numbers are entered
fixed appearance on IE

--- calculator_spec.coffee
+++ calculator_spec.coffee
@@ -108,7 +108,7 @@
       @handle.press_buttons("pi pi =")
       expect(@handle.output_content()).toMatch /9.86/

-    it "handles the sample instance presented by mario", ->
+    it "handles a more complicated example", ->
       @handle.press_buttons("2 pi ( 1 + 3 ) =")
       expect(@handle.output_content()).toMatch /25.13/


skip ExtractColors graph spec for now

skip ExtractColors graph spec for now

--- spec/ExtractColors.coffee
+++ spec/ExtractColors.coffee
@@ -1,60 +1,60 @@
-noflo = require 'noflo'
-unless noflo.isBrowser()
-  chai = require 'chai' unless chai
-  path = require 'path'
-  baseDir = path.resolve __dirname, '../'
-else
-  baseDir = '/noflo-image'
+# noflo = require 'noflo'
+# unless noflo.isBrowser()
+#   chai = require 'chai' unless chai
+#   path = require 'path'
+#   baseDir = path.resolve __dirname, '../'
+# else
+#   baseDir = '/noflo-image'

-describe 'ExtractColors graph', ->
-  c = null
-  ins = null
-  out = null
-  beforeEach (done) ->
-    @timeout 10000
-    loader = new noflo.ComponentLoader baseDir
-    loader.load 'image/ExtractColors', (instance) ->
-      c = instance
-      c.once 'ready', ->
-        ins = noflo.internalSocket.createSocket()
-        out = noflo.internalSocket.createSocket()
-        c.inPorts.url.attach ins
-        c.outPorts.colors.attach out
-        done()
+# describe 'ExtractColors graph', ->
+#   c = null
+#   ins = null
+#   out = null
+#   beforeEach (done) ->
+#     @timeout 10000
+#     loader = new noflo.ComponentLoader baseDir
+#     loader.load 'image/ExtractColors', (instance) ->
+#       c = instance
+#       c.once 'ready', ->
+#         ins = noflo.internalSocket.createSocket()
+#         out = noflo.internalSocket.createSocket()
+#         c.inPorts.url.attach ins
+#         c.outPorts.colors.attach out
+#         done()

-  describe 'with remote JPG image', ->
-    url = 'http://farm8.staticflickr.com/7395/12952090783_ce023450da_b.jpg'
-    unless noflo.isBrowser()
-      it 'should have the correct group', (done) ->
-        out.once 'begingroup', (group) ->
-          chai.expect(group).to.equal url
-          done()
-        ins.send url
-        ins.disconnect()
-      it 'should find colors', (done) ->
-        @timeout 10000
-        out.once 'data', (data) ->
-          chai.expect(data).to.be.an 'array'
-          chai.expect(data[0]).to.be.an 'array'
-          chai.expect(data[0].length).to.equal 3
-          done()
-        ins.send url
-        ins.disconnect()
+#   describe 'with remote JPG image', ->
+#     url = 'http://farm8.staticflickr.com/7395/12952090783_ce023450da_b.jpg'
+#     unless noflo.isBrowser()
+#       it 'should have the correct group', (done) ->
+#         out.once 'begingroup', (group) ->
+#           chai.expect(group).to.equal url
+#           done()
+#         ins.send url
+#         ins.disconnect()
+#       it 'should find colors', (done) ->
+#         @timeout 10000
+#         out.once 'data', (data) ->
+#           chai.expect(data).to.be.an 'array'
+#           chai.expect(data[0]).to.be.an 'array'
+#           chai.expect(data[0].length).to.equal 3
+#           done()
+#         ins.send url
+#         ins.disconnect()

-  describe 'with local JPG image', ->
-    url = 'http://localhost:8000/spec/fixtures/extract.jpg'
-    it 'should have the correct group', (done) ->
-      out.once 'begingroup', (group) ->
-        chai.expect(group).to.equal url
-        done()
-      ins.send url
-      ins.disconnect()
-    it 'should find colors', (done) ->
-      @timeout 10000
-      out.once 'data', (data) ->
-        chai.expect(data).to.be.an 'array'
-        chai.expect(data[0]).to.be.an 'array'
-        chai.expect(data[0].length).to.equal 3
-        done()
-      ins.send url
-      ins.disconnect()
+#   describe 'with local JPG image', ->
+#     url = 'http://localhost:8000/spec/fixtures/extract.jpg'
+#     it 'should have the correct group', (done) ->
+#       out.once 'begingroup', (group) ->
+#         chai.expect(group).to.equal url
+#         done()
+#       ins.send url
+#       ins.disconnect()
+#     it 'should find colors', (done) ->
+#       @timeout 10000
+#       out.once 'data', (data) ->
+#         chai.expect(data).to.be.an 'array'
+#         chai.expect(data[0]).to.be.an 'array'
+#         chai.expect(data[0].length).to.equal 3
+#         done()
+#       ins.send url
+#       ins.disconnect()


You need to be logged in to open Develop tab

--- client/app/Applications/StartTab.kdapplication/AppController.coffee
+++ client/app/Applications/StartTab.kdapplication/AppController.coffee
@@ -5,6 +5,9 @@
     route        : "/Develop"
     behavior     : "application"
     multiple     : yes
+    preCondition :
+      condition  : (cb)-> cb KD.isLoggedIn()
+      failure    : (cb)-> KD.getSingleton('router').handleRoute "/Activity"

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


add a test

add a test

--- client/apps/edit/components/content/sections/text/test/index.coffee
+++ client/apps/edit/components/content/sections/text/test/index.coffee
@@ -456,4 +456,11 @@

     it 'strips or converts unsupported html and linebreaks', ->
       @component.onPaste('Here is a caption about an image yep.', '<meta>
Here is a

caption

about an
image

yep.

')
+      @component.state.html.should.containEql '
Here is a

caption

about an image

'
+
+    it 'does not overwrite existing content', ->
+      @component.onPaste('Here is a caption about an image yep.', '<meta>
Here is a

caption

about an
image

yep.

')
       @component.state.html.should.startWith '
Here is a

caption

about an image

'
+      @component.state.html.should.containEql '
yep.01  here is a toc.

In 2016, K mounted a <a'
+
+


Add spec for markdown:outdent-list-item

Add spec for markdown:outdent-list-item

--- spec/commands-spec.coffee
+++ spec/commands-spec.coffee
@@ -15,6 +15,7 @@
     editor.setText('')
     editor.setCursorBufferPosition(0, 0)

+    # Set default config so we can 'assume' these values later on
     editor.config.set('editor.softTabs', true)
     editor.config.set('editor.tabLength', 2)
     editor.config.set('editor.tabType', 'soft')
@@ -53,6 +54,7 @@

     it 'indents a valid list-item', ->
       editor.setText('- item')
+      editor.setCursorBufferPosition(0, 0)
       atom.commands.dispatch(editorElement, "markdown:indent-list-item")
       expect(editor.getText()).toBe('  - item')

@@ -77,7 +79,7 @@
       atom.commands.dispatch(editorElement, "markdown:indent-list-item")
       expect(editor.getText()).toBe('-item')

-    it 'indents a list-item with a single leading space', ->
+    it 'indents a partially indented list-item', ->
       editor.setText(' - item')
       atom.commands.dispatch(editorElement, "markdown:indent-list-item")
       expect(editor.getText()).toBe('   - item')
@@ -98,13 +100,62 @@
       atom.commands.dispatch(editorElement, "markdown:indent-list-item")
       expect(editor.getText()).toBe('  - [ ] task')

-    # TODO it should not indent definition-lists
-    # xit 'does NOT indent definition-lists', ->
-    #   editor.setText(': definition')
-    #   atom.commands.dispatch(editorElement, "markdown:indent-list-item")
-    #   expect(editor.getText()).toBe(': definition')
+    it 'indents definition-lists', ->
+      editor.setText(': definition')
+      atom.commands.dispatch(editorElement, "markdown:indent-list-item")
+      expect(editor.getText()).toBe('  : definition')

-  # describe 'outdenting list-items', ->
+  describe 'outdenting list-items', ->

-    # TODO
-    # outdent-list-item
+    it 'outdents a valid list-item', ->
+      editor.setText('  - item')
+      editor.setCursorBufferPosition(0, 0)
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe('- item')
+
+    it 'outdents a list-item when the cursor is not at the start of a line', ->
+      editor.setText('  - item')
+      editor.setCursorBufferPosition(0, 3)
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe('- item')
+
+    it 'does nothing on an unindented list-item', ->
+      editor.setText('- item')
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe('- item')
+
+    it 'outdents a tabbed indented list-item', ->
+      editor.setText('\t- item')
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe('- item')
+
+    it 'does NOT outdent an invalid list-item', ->
+      editor.setText('  -item')
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe('  -item')
+
+    it 'outdents a partially (3 spaces) indented list-item', ->
+      editor.setText('   - item')
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe(' - item')
+
+    it 'does NOT outdent a seemingly valid list-item as part of fenced-code', ->
+      editor.setText('```\n  - item\n```')
+      editor.setCursorBufferPosition(1, 3)
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe('```\n  - item\n```')
+
+    it 'indents a valid numbered list-item', ->
+      editor.setText('  1. item')
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe('1. item')
+
+    it 'outdents a task-list-item', ->
+      editor.setText('  - [ ] task')
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe('- [ ] task')
+
+    it 'outdents definition-lists', ->
+      editor.setText('  : definition')
+      atom.commands.dispatch(editorElement, "markdown:outdent-list-item")
+      expect(editor.getText()).toBe(': definition')


Client: Site.Landing: Deleted unnecessary line

--- client/landing/site.landing/coffee/team/tabs/teamusernametab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamusernametab.coffee
@@ -1,6 +1,5 @@
 kd                               = require 'kd'
 utils                            = require './../../core/utils'
-MainHeaderView                   = require './../../core/mainheaderview'
 TeamJoinTab                      = require './teamjointab'
 TeamUsernameTabForm              = require './../forms/teamusernametabform'
 TeamLoginAndCreateTabForm        = require './../forms/teamloginandcreatetabform'


node margin included in hittesting

node margin included in hittesting

--- public/coffeescripts/renderer.coffee
+++ public/coffeescripts/renderer.coffee
@@ -68,6 +68,7 @@
       height = Math.min(height, maxHeight)

       # Store dims so we can use them in hit testing later.
+      # TODO sort out the MARGIN business (separate textSize from nodeSize)
       node.data.width = width + MARGIN*2;
       node.data.height = height + MARGIN*2;

@@ -96,9 +97,6 @@
     disty = Math.sqrt(Math.pow(point.y - p.y, 2))
     width = node.data.width
     height = node.data.height
-
-    console.log {"distx":distx, "disty":disty}
-    console.log {"width":width, "height":height}

     (distx <= width/2) && (disty <= height/2)


Uncommented broken tests

Uncommented broken tests

Almost there, just need to get nested TinFoil objects back together

--- spec/tin-foil_spec.coffee
+++ spec/tin-foil_spec.coffee
@@ -141,7 +141,7 @@
     it 'should be chainable', ->
       @Base.define('property', as: String).should.equal @Base

-  xdescribe 'nested definitions', ->
+  describe 'nested definitions', ->

     beforeEach ->
       @SecondNest = TinFoil.extend()
@@ -303,13 +303,13 @@

       @Base.add_to_collection 'collection value 1'
       @Base.add_to_collection (event) -> event.collectionValue2
-#      @Base.nest_add_to_collection 'nested collection value 1'
-#      @Base.nest_add_to_collection (event) -> event.nestedCollectionValue2
+      @Base.nest_add_to_collection 'nested collection value 1'
+      @Base.nest_add_to_collection (event) -> event.nestedCollectionValue2

       @Base.add_to_map 'map_value_1', 'map value 1'
       @Base.add_to_map 'map_value_2', (event) -> event.mapValue2
-#      @Base.nest_add_to_map 'nested_map_value_1', 'nested map value 1'
-#      @Base.nest_add_to_map 'nested_map_value_2', (event) -> event.nestedMapValue2
+      @Base.nest_add_to_map 'nested_map_value_1', 'nested map value 1'
+      @Base.nest_add_to_map 'nested_map_value_2', (event) -> event.nestedMapValue2

       @compiled = @Base.compile @event


Fix typo in source

Fix typo in source

--- src/objects/abbrev.proto.coffee
+++ src/objects/abbrev.proto.coffee
@@ -42,7 +42,7 @@
   list = {}
   keys = []
   for k, v of @z when begins k, v
-    v || = k
+    v ||= k
     i = 0
     while ++i <= k.length
       if v == @$ test = k.substring 0, i


Adding McKayla, GoT, Morpheus, and Bear Grylls

#   hubot memegen <text> NOT IMPRESSED - Generates McKayla Maroney
#   hubot memegen PREPARE YOURSELF <text> - Generates GoT
#   hubot memegen WHAT IF I TOLD YOU <text> - Generates Morpheus
#   hubot memegen <text> BETTER DRINK MY OWN PISS - Generates Bear Grylls

--- src/scripts/meme_generator.coffee
+++ src/scripts/meme_generator.coffee
@@ -27,8 +27,11 @@
 #   hubot memegen <text> you're gonna have a bad time - Bad Time Ski Instructor
 #   hubot memegen one does not simply <text> - Lord of the Rings Boromir
 #   hubot memegen it looks like (you|you're) <text> - Generates Clippy
-#	hubot memegen AM I THE ONLY ONE AROUND HERE <text> - The Big Lebowski
-#
+#   hubot memegen AM I THE ONLY ONE AROUND HERE <text> - The Big Lebowski
+#   hubot memegen <text> NOT IMPRESSED - Generates McKayla Maroney
+#   hubot memegen PREPARE YOURSELF <text> - Generates GoT
+#   hubot memegen WHAT IF I TOLD YOU <text> - Generates Morpheus
+#   hubot memegen <text> BETTER DRINK MY OWN PISS - Generates Bear Grylls
 # Author:
 #   skalnik

@@ -111,9 +114,29 @@
         imageID: 1159769
       },
       {
-      	regex: /(memegen )?(AM I THE ONLY ONE AROUND HERE) (.*)/i,
-      	generatorID: 953639,
-      	imageID: 4240352
+        regex: /(memegen )?(AM I THE ONLY ONE AROUND HERE) (.*)/i,
+        generatorID: 953639,
+        imageID: 4240352
+      }
+      {
+        regex: /(memegen)?(.*)(NOT IMPRESSED*)/i,
+        generatorID: 1420809,
+        imageID: 5883168
+      },
+      {
+        regex: /(memegen)?(PREPARE YOURSELF) (.*)/i,
+        generatorID: 414926,
+        imageID: 2295701
+      },
+      {
+        regex: /(memegen)?(WHAT IF I TOLD YOU) (.*)/i,
+        generatorID: 1118843,
+        imageID: 4796874
+      },
+      {
+        regex: /(memegen)?(.*) (BETTER DRINK MY OWN PISS)/i,
+        generatorID: 92,
+        imageID: 89714
       }
     ]


make the window more bigger

make the window more bigger

--- src/launcher.coffee
+++ src/launcher.coffee
@@ -16,14 +16,16 @@
   Launcher.hide()

 module.exports = Launcher =
-  show: ->
+  show: (display) ->
     if window?
       window.show()
-      return
+    else
+      window = new BrowserWindow
+        width: 800
+        height: display?.workArea?.height ? 400
+        frame: true

-    window = new BrowserWindow
-      frame: true
-
+    # load/reload regardless
     window.loadURL "file://#{path.resolve viewsPath, 'launcher.html'}"

     # window.webContents.openDevTools()


add comments

add comments

--- src/shared/component/login.coffee
+++ src/shared/component/login.coffee
@@ -11,9 +11,11 @@
         page: {}
       command.page[event.target.name] = $set: event.target.value
       this.update command
+      # TODO this is where we'd validate
     handleClick: (event) ->
       event.preventDefault()
-      console.log 'ComponentLogin', 'handleClick', event
+      console.log 'ComponentLogin', 'handleClick', this.state.page
+      # TODO this is where we'd send this.state.page to the server
     render: ->
       that = this
       reactKup (k) ->


fetch and show actual users

fetch and show actual users

--- src/shared/component/users.coffee
+++ src/shared/component/users.coffee
@@ -3,21 +3,33 @@
   reactKup
   Cursors
   urlUser
+  getUsers
 ) ->
   React.createClass
     mixins: [Cursors]
+    componentWillMount: ->
+      console.log 'ComponentUsers', 'componentWillMount', this.state
     componentDidMount: ->
-      console.log 'ComponentUsers', 'componentDidMount'
-      # TODO
-      # this is where we will fetch the users from the server
-      # and put them into the page state that has been passed to us
-      # from above
+      console.log 'ComponentUsers', 'componentDidMount', this.state
+      that = this
+      getUsers()
+        .then (data) ->
+          that.update
+            users: {$set: data}
+        .catch (error) ->
+          that.update {error: {$set: error}}
     render: ->
+      console.log 'ComponentUsers', 'render', this.state
       that = this
+      users = that.state.users
       reactKup (k) ->
         k.div {className: 'container ComponentUsers'}, ->
           k.h1 "Users"
-          k.ul ->
-            k.li -> k.a {href: urlUser.stringify(id: 1)}, 'Alice'
-            k.li -> k.a {href: urlUser.stringify(id: 2)}, 'Bob'
-            k.li -> k.a {href: urlUser.stringify(id: 3)}, 'Charlie'
+          unless users?
+            k.p "loading"
+          else if users.length is 0
+            k.p "no users"
+          else
+            k.ul ->
+              that.state.users.forEach (user) ->
+                k.li -> k.a {href: urlUser.stringify(id: user.id)}, user.name


compute nodes: program._name must be compute in order to start on compute nodes

--- src/smc-hub/compute-server.coffee
+++ src/smc-hub/compute-server.coffee
@@ -1237,7 +1237,7 @@
 ###########################

 if not program._name
-    program._name = 'x'  # so this module is possible to import with latest node version
+    program._name = 'compute'  # so this module is possible to import with latest node version

 program.usage('[start/stop/restart/status] [options]')
     .option('--pidfile [string]',        'store pid in this file', String, "#{CONF}/compute.pid")


[bufix] Messages with same subject were grouped as one conversation

--- server/models/message.coffee
+++ server/models/message.coffee
@@ -547,6 +547,8 @@
     if mail.headers['x-gm-thrid']
         return callback null, mail.headers['x-gm-thrid']

+    # is reply or forward
+    isReplyOrForward = mailutils.isReplyOrForward mail.subject

     # try to find by references
     references = mail.references or []
@@ -563,16 +565,15 @@
             Message.pickConversationID rows, callback

     # no references, try to find by subject
-    # @TODO, should only do this if subject start with variation of Re:
-    else if mail.normSubject?.length > 3
+    else if mail.normSubject?.length > 3 and isReplyOrForward
         key = [mail.accountID, 'subject', mail.normSubject]
         Message.rawRequest 'dedupRequest', {key}, (err, rows) ->
             return callback err if err
             Message.pickConversationID rows, callback

+    # give it a random uid
     else
         callback null, uuid.v4()
-        # give it a random uid


fix horrible security bug with pbkdf#2

fix horrible security bug with pbkdf#2

--- src/lib/derive.iced
+++ src/lib/derive.iced
@@ -145,7 +145,7 @@

     ret = if not dk?           then null
     else if @is_internal_key() then dk
-    else                            @format @finalize dk
+    else                       @format @finalize dk

     cb ret

@@ -242,21 +242,19 @@
     # Make a copy of the original block....
     intermediate = block.clone()

-
     i = 0
     while i < @_limit
       await @delay i, defer()
       if compute_hook i, @_limit
         intermediate = hmac.finalize intermediate
         hmac.reset()
-        block[j] ^= w for w,j in intermediate
+        block.words[j] ^= w for w,j in intermediate.words
         i++
       else
         break

     ret = if i isnt @_limit then null
-    else if @is_internal_key() then block
-    else block.toString C.enc.Base64
+    else block

     cb ret


fixed textwrap not fetching the correct next character

--- src/textwrap/helpers/tspan.coffee
+++ src/textwrap/helpers/tspan.coffee
@@ -12,6 +12,7 @@

   xPosition = vars.container.value.attr("x") or "0px"
   words = vars.text.words.slice(0)
+  progress = words[0]
   fontSize = if vars.resize.value then vars.size.value[1] + "px" else vars.container.fontSize or vars.size.value[0] + "px"
   textBox = newLine words.shift()
   textHeight = textBox.node().offsetHeight or textBox.node().getBoundingClientRect().height
@@ -63,13 +64,13 @@

     current   = textBox.text()
     lastChar  = current.slice(-1)
-    next_char = vars.text.current.charAt(vars.text.current.indexOf(current) + current.length)
+    next_char = vars.text.current.charAt(progress.length)
     joiner    = if next_char is " " then " " else ""

     textBox.text current + joiner + word
+    progress += joiner + word

     if textBox.node().getComputedTextLength() > lineWidth()
       textBox.text current
-      textBox = newLine()
-      textBox.text word
+      textBox = newLine(word)
       line++


decrease indent for function(    args )

decrease indent for
function(
   args
)

--- settings/language-stata.cson
+++ settings/language-stata.cson
@@ -1,4 +1,6 @@
 '.source.stata':
+  'editor':
+    'decreaseIndentPattern': '^\\s*\\}|^\\s*\\)'
   "bracket-matcher":
     autocompleteCharacters: [
       "()"


[Fixes #80439442] reset 'set blank' checkboxes

--- app/assets/javascripts/trade/models/shipment_batch_update.js.coffee
+++ app/assets/javascripts/trade/models/shipment_batch_update.js.coffee
@@ -48,6 +48,9 @@
   reset: ->
     @get('columns').forEach( (c) =>
       @set(c, null)
+      if @get('nullableColumns').contains(c)
+        blankPropertyName = c + 'Blank'
+        @set(blankPropertyName, false)
     )

   export: ->


code style

code style

--- workers/social/lib/social/models/payment/index.coffee
+++ workers/social/lib/social/models/payment/index.coffee
@@ -55,7 +55,12 @@
     delegate.fetchUser (err, user)->
       return callback err  if err?
       {username, firstName, lastName} = delegate.profile
-      callback null, {email: user.email, username, firstName, lastName}
+      callback null, {
+        email: user.email
+        username
+        firstName
+        lastName
+      }

   @getBalance_ = (account, callback)->
     recurly.getTransactions account, (err, adjs)->


bugfix: fix group ownership info

bugfix: fix group ownership info

--- workers/social/lib/social/models/recurly/group.coffee
+++ workers/social/lib/social/models/recurly/group.coffee
@@ -1,22 +1,33 @@
-recurly  = require 'koding-payment'
-JRecurly = require './index'
+recurly     = require 'koding-payment'
+JRecurly    = require './index'
+{ extend }  = require 'underscore'

 module.exports = class JRecurlyGroup extends JRecurly

   @setAccount = (client, group, data, callback)->
-    console.log { client, group, data }
-    JSession = require '../session'
-    JSession.one {clientId: client.sessionToken}, (err, session) =>
-    JRecurlyPlan.fetchGroupAccount group, (err, groupAccount) =>
+
+    group.fetchOwner (err, owner) ->
       return callback err  if err
-      {email, username, firstName, lastName} = groupAccount
-
-      accountCode = "group_#{group.getId()}"
-      extend data, {accountCode, email, username, firstName, lastName}
+      owner.fetchUser (err, ownerUser) ->
+        return callback err  if err

-      recurly.setAccount accountCode, data, (err, res)->
-        return callback err  if err
-        recurly.setBilling accountCode, data, callback
+        {email, username} = ownerUser
+
+        {firstName, lastName} = owner
+
+        extend data, {
+          accountCode
+          email
+          username
+          firstName
+          lastName
+        }
+
+        accountCode = "group_#{group.getId()}"
+
+        recurly.setAccount accountCode, data, (err, res)->
+          return callback err  if err
+          recurly.setBilling accountCode, data, callback

   @getAccount = (group, callback) ->
     recurly.getAccount "group_#{group.getId()}", callback


Add quantities

Add quantities

--- workers/social/lib/social/models/recurly/index.coffee
+++ workers/social/lib/social/models/recurly/index.coffee
@@ -137,12 +137,14 @@

   subscribe: secure (client, data, callback)->
     {delegate} = client.connection
+    data.quantity ?= 1
     JRecurlyToken.checkToken client, {planCode: @code, pin: data.pin}, (status)=>
       unless status
         callback yes, {}
       else
         payment.addUserSubscription "user_#{delegate._id}",
-          plan : @code
+          plan     : @code
+          quantity : data.quantity
         , (err, result)->
           return callback err  if err
           sub = new JRecurlySubscription


Fix sssd

Fix sssd

--- actions/sssd.coffee
+++ actions/sssd.coffee
@@ -55,13 +55,13 @@
       # Note: we dont detect a change when executing authconfig
       # "--enablecachecreds --enablecache" require "nscd"
       cmd = """
-      authconfig \
-        --enableshadow --nostart  \
-        --enablesssd --enablesssdauth --enablelocauthorize \
-        --enableldap --enableldaptls --enableldapauth \
-        --ldapserver=#{ldap_uri} --ldapbasedn=dc=adaltas,dc=com\
-        --enablekrb5 \
-        --kickstart --enablemkhomedir \
+      authconfig \\
+        --enableshadow --nostart  \\
+        --enablesssd --enablesssdauth --enablelocauthorize \\
+        --enableldap --enableldaptls --enableldapauth \\
+        --ldapserver=#{ldap_uri} --ldapbasedn=dc=adaltas,dc=com\\
+        --enablekrb5 \\
+        --kickstart --enablemkhomedir \\
         --updateall
       """
       ctx.log "Run #{cmd}"


Fixup e0b4126, regression in event mixin's #off.

--- src/js/jquery.extension.hlf.core.coffee
+++ src/js/jquery.extension.hlf.core.coffee
@@ -418,7 +418,7 @@
           arguments[0] = if _.isString(obj) then @evt(obj) else @evtMap(obj)
           @$el.on.apply @$el, arguments
           return
-        off: (name) ->
+        off: (obj) ->
           arguments[0] = if _.isString(obj) then @evt(obj) else @evtMap(obj)
           @$el.off.apply @$el, arguments
           return


bitcoinclassic homepage heading

bitcoinclassic homepage heading

Former-commit-id: 1eebc79e7d839930d91ed4b8aef98cadd49c0359 [formerly 46afcf8979427965517dfe3fd852ffac1e257337] [formerly 3d262523b79b8267f5e1f00df89b05281b2f950c [formerly c46d694a6952454b617425d461e75ca3bf74f849]] [formerly a2a33637e919c1dc064c9e76f3cfe0e937d5d7aa [formerly c50a4d44f0d835e796a186bd8dca084fd0c67eb8] [formerly 2714e32c27d180310aad72800253b94d647a35d1 [formerly 2714e32c27d180310aad72800253b94d647a35d1 [formerly d57fa4bd8c06cf28eb3e74b598676f3dfa24a5b5]]]]
Former-commit-id: 5ad9f88d22d03075873bcbb9f8cef1078a56cf9f [formerly 65b7077c684069bb2589391fc9f481f908400b13] [formerly 494b48c7fb44a9d24b2a52641d5bb6883e9bbd4e [formerly de2706024de962d64826df9459907c63266080a4]]
Former-commit-id: 2d6ac9814b28d4404cd9412cbad44635e1f0111c [formerly 840a2dd1f6a11a0a86696b6ae0b0db77acf434b6]
Former-commit-id: 5f5cdd83bd8c627f9d1eac4a52c5105b43aa81c1
Former-commit-id: e37563b89180f02d29b0577850d82233890b63c4

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -1856,11 +1856,80 @@
     }
   ]

+# https://www.reddit.com/r/Bitcoin_Classic/comments/40u3ws/considerit_voting_guide/
+
 customizations.bitcoinclassic = _.extend {},
   user_filters: customizations.bitcoin.user_filters
   auth: customizations.bitcoin.auth
   show_proposer_icon: true
   collapse_descriptions_at: 300
+
+  HomepageHeader: ReactiveComponent
+    displayName: 'HomepageHeader'
+
+    render: ->
+      homepage = fetch('location').url == '/'
+
+      DIV
+        style:
+          backgroundColor: 'white'
+          height: 63
+          borderBottom: '1px solid #ddd'
+          boxShadow: '0 1px 2px rgba(0,0,0,.1)'
+
+        onMouseEnter: => @local.hover=true;  save(@local)
+        onMouseLeave: => @local.hover=false; save(@local)
+
+        STYLE null,
+          '''.profile_anchor.login {font-size: 26px; padding-top: 16px;}
+             p {margin-bottom: 1em}'''
+
+        # The top bar with the logo
+        DIV
+          style:
+            cursor: if not homepage then 'pointer'
+          onClick: if not homepage then => loadPage('/')
+
+          DIV
+            style:
+              marginLeft: 50
+            SPAN
+              style:
+                display: 'inline-block'
+                visibility: if homepage then 'hidden'
+                color: '#eee'
+                marginLeft: -10
+                fontSize: 43
+                fontWeight: 400
+                paddingLeft: 25 # Make the clickable target bigger
+                paddingRight: 25 # Make the clickable target bigger
+              '<'
+
+            # Logo
+            A
+              href: 'https://bitcoinclassic.com'
+
+              IMG
+                style:
+                  height: 29
+                  width: 206
+
+                src: asset('bitcoin/bitcoinclassic.png')
+
+            A
+              href: 'https://www.reddit.com/r/Bitcoin_Classic/comments/40u3ws/considerit_voting_guide/'
+              target: '_blank'
+              style:
+                textDecoration: 'underline'
+                color: '#8d8d8d'
+                marginLeft: 15
+                fontSize: 14
+
+              "Guide to participation"
+
+
+customizations.bitcoinclassic.NonHomepageHeader = customizations.bitcoinclassic.HomepageHeader
+

 customizations['bitcoinfoundation'] =


Static MainPage : instead of sending parameters standalone, send an object as a parameter

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -123,7 +123,7 @@
     @off "publicFeedFetched_#{eventSuffix}"
     # log "------------------ bindingsCleared", dateFormat(@lastFrom, "mmmm dS HH:mm:ss"), @_e

-  featuredActivityCommentRequested:(activityId, commentId, callback)=>
+  featuredActivityCommentRequested:({activityId, commentId}, callback)=>
     return if commentId > 2
     timeoutValue = KD.utils.getRandomNumber 100000
     KD.utils.wait timeoutValue, =>
@@ -137,11 +137,13 @@
       # if fetching success, add them to activity feed
       @once eventName, (activities)=>
         @listActivities activities, callback, true
-        @emit "FeaturedActivityCommentRequested", activityId, commentId+1, callback
+        @emit "FeaturedActivityCommentRequested", {activityId:activityId, commentId:commentId+1}, callback
       @fetchFeatureds(activityId, commentId)

-  featuredActivityRequested:(activityId=0, callback)=>
+  featuredActivityRequested:({activityId}, callback)=>
+    unless activityId? then activityId = 0
     return if activityId > 7
+
     @isLoading = true
     timeoutValue = KD.utils.getRandomNumber 10000

@@ -151,14 +153,14 @@
     KD.utils.wait timeoutValue, =>
       @once "#{eventName}_failed", ()=>
         @off "#{eventName}_succeeded"
-        @emit "FeaturedActivityRequested", activityId+1, callback
+        @emit "FeaturedActivityRequested", {activityId:activityId+1}, callback

       @once "#{eventName}_succeeded", (activities)=>
         @listActivities activities, (sanitizedCache)=>
           callback sanitizedCache
-          @emit "FeaturedActivityCommentRequested", activityId, 1, callback
-
-        @emit "FeaturedActivityRequested", activityId+1, callback
+          @emit "FeaturedActivityCommentRequested", {activityId:activityId, commentId:1}, callback
+
+        @emit "FeaturedActivityRequested", {activityId:activityId+1}, callback
       #fetch featured activity
       @fetchFeatureds(activityId)

@@ -175,7 +177,7 @@
     @once "#{eventName}_succeeded", (activities)=>
       activities.overview.reverse()  if activities.overview
       @listActivities activities, callback
-      @emit "FeaturedActivityRequested", 1, callback
+      @emit "FeaturedActivityRequested", {activityId:1}, callback
     @fetchFeatureds()

   populateActivity:(options = {}, callback=noop)->


NavigationActivityLink: Fixed new activity items counter for activity link on navigation panel.

--- client/landing/app/MainApp/navigation/navigationactivitylink.coffee
+++ client/landing/app/MainApp/navigation/navigationactivitylink.coffee
@@ -14,7 +14,6 @@
       partial   : ""
       click     : =>
         @setActivityLinkToDefaultState()
-        appManager.tell "Activity", "unhideNewItems"

     @count.hide()

@@ -22,19 +21,21 @@
       tagName   : "span"
       cssClass  : "main-nav-icon #{__utils.slugify @getData().title}"

-    @utils.wait 1000, =>
-      KD.getSingleton("activityController").on "ActivitiesArrived", (activities) =>
+    mainController = KD.getSingleton "mainController"
+
+    mainController.ready =>
+      activityController = KD.getSingleton "activityController"
+      activityController.on "ActivitiesArrived", =>
         return if KD.getSingleton("router").currentPath is "/Activity"

-        appManager.tell "Activity", "getNewItemsCount", (itemCount) =>
-          @updateNewItemsCount itemCount
+        newItemsCount = activityController.getNewItemsCount()
+        @updateNewItemsCount newItemsCount  if newItemsCount > 0

-    mainController = KD.getSingleton "mainController"
+        activityController.on "NewItemsCounterCleared", @bound "setActivityLinkToDefaultState"
+
     mainController.on "NavigationLinkTitleClick", (options) =>
-      @setActivityLinkToDefaultState() if options.appPath is "Activity"
-
-    mainController.on "ShouldResetNavigationTitleLink", =>
-      @setActivityLinkToDefaultState()
+      if options.appPath is "Activity"
+        KD.getSingleton("activityController").clearNewItemsCount()

   updateNewItemsCount: (itemCount) ->
     return if itemCount is 0


Accept multiple redirect URIs

Accept multiple redirect URIs

--- oauth.coffee
+++ oauth.coffee
@@ -64,6 +64,12 @@

 			if client.redirectUri isnt req.query.redirect_uri
 				return res.redirect '/oauth/error/invalid_redirect_uri'
+			if Array.isArray(client.redirectUri)
+				if client.redirectUri.indexOf(req.query.redirect_uri) < 0
+					return res.redirect '/oauth/error/invalid_redirect_uri'
+			else
+				if client.redirectUri isnt req.query.redirect_uri
+					return res.redirect '/oauth/error/invalid_redirect_uri'

 			next()


panelutil: pull out key bit of reorgLodData() as reorgByChr()

--- src/panelutil.coffee
+++ src/panelutil.coffee
@@ -30,17 +30,26 @@
     (data[i][variable] for i of data)

 # reorganize lod/pos by chromosome
-# lodvarname==null    -> case for multiple LOD columns (lodheatmap)
-# lodvarname provided -> case for one LOD column (lodchart)
+#    uniquechr = vector of unique chromosome IDs
+#    chr = vector of chromosome IDs
+reorgByChr = (uniquechr, chr, vector) ->
+    if(chr.length != vector.length)
+        displayError("reorgByChr: chr.length (#{chr.length}) != vector.length (#{vector.length})")
+
+    result = {}
+    for c,i in uniquechr
+        result[c] = (vector[i] for i of vector when chr[i]==c)
+
+    result
+
+# reorganize lod and position by chromosome
+# also, add markerinfo object
 reorgLodData = (data) ->
-    data.posByChr = {}
-    data.lodByChr = {}
-
-    for chr,i in data.chrname
-        data.posByChr[chr] = (data.pos[i] for i of data.pos when data.chr[i]==chr)
-        data.lodByChr[chr] = (data.lod[i] for i of data.pos when data.chr[i]==chr)
-
-    data.markerinfo = ({name: data.marker[i], chr:data.chr[i], pos:data.pos[i], lod:data.lod[i]} for i of data.marker when data.marker[i] != "")
+    data.posByChr = reorgByChr(data.chrname, data.chr, data.pos)
+    data.lodByChr = reorgByChr(data.chrname, data.chr, data.lod)
+
+    if data.marker?
+        data.markerinfo = ({name: data.marker[i], chr:data.chr[i], pos:data.pos[i], lod:data.lod[i]} for i of data.marker when data.marker[i] != "")

     data


auto skill links

auto skill links

--- code/autolinker.coffee
+++ code/autolinker.coffee
@@ -25,6 +25,7 @@
 usa = "#{site}#{baseurl}assets/usa.svg"
 cthulhu = "#{site}#{baseurl}assets/cthulhu.svg"
 hastur = "#{site}#{baseurl}assets/hastur.svg"
+
 image_style =
     """
     display: inline-block;
@@ -32,6 +33,7 @@
     width: 1em;
     margin: auto;
     """
+

 ### Helper Functions ###
 String::startsWith ?= (s) -> @slice(0,s.length)==s
@@ -51,6 +53,74 @@

 ### Linkable Patterns ###
 links = [
+    base: "characters/skills/"
+    styles: [toStrong,(s) => applyColor(s,"#111111")]
+    patterns: [
+        link: ""
+        regex: ///
+                 Athletics
+               | Accounting
+               | Conceal
+               | Anthropology
+               | Disguise
+               | Archaeology
+               | Driving
+               | Architecture
+               | Electrical Repair
+               | Art History
+               | Explosives
+               | Biology
+               | Filch
+               | Cthulhu Mythos
+               | Firearms
+               | Cryptography
+               | First Aid
+               | Geology
+               | Fleeing
+               | History
+               | Hypnosis
+               | Languages
+               | Mechanical Repair
+               | Law
+               | Piloting
+               | Library Use
+               | Preparedness
+               | Medicine
+               | Psychoanalysis
+               | Occult
+               | Riding
+               | Physics
+               | Sanity
+               | Theology
+               | Stability
+               | Scuffling
+               | Sense Trouble
+               | Shadowing
+               | Stealth
+               | Weapons
+               | Art
+               | Astronomy
+               | Chemistry
+               | Craft
+               | Evidence Collection
+               | Forensics
+               | Locksmith
+               | Outdoorsman
+               | Pharmacy
+               | Assess Honesty
+               | Bargain
+               | Bureaucracy
+               | Cop Talk
+               | Credit Rating
+               | Flattery
+               | Interrogation
+               | Intimidation
+               | Oral History
+               | Photography
+               ///g
+    ],
+,
+
     base: "items/"
     styles: [toItalic]
     patterns: [
@@ -159,7 +229,6 @@
     ],
 ,
     base: "setting/"
-    styles: [(s) => s] #.toUpperCase()
     patterns: [
         link: "grotto/"
         styles: [toCthulhu]
@@ -199,7 +268,8 @@
             for pattern in group.patterns
                 link = pattern.link
                 div.innerHTML = div.innerHTML.replace pattern.regex, (match) ->
-                    match = style(match) for style in group.styles
+                    if (group.styles)
+                        match = style(match) for style in group.styles
                     if (pattern.styles)
                         match = style(match) for style in pattern.styles
                     match = """#{match}"""


Add streaming ability for document fetch

--- coffee/document.coffee
+++ coffee/document.coffee
@@ -1,6 +1,7 @@
 "use strict"

 ObjectID = require('mongodb').ObjectID
+JSONStream = require 'JSONStream'

 exports.param = (req, res, next, id) ->
   return res.json 400, error: 'Ugyldig ObjectID' if not /^[a-f0-9]{24}$/.test id
@@ -34,12 +35,15 @@
   res.set 'Last-Modified', new Date(req.doc.endret).toUTCString()
   res.status(200)

+  return res.end() if req.method is 'HEAD'
+  res.set 'Content-Type', 'application/json; charset=utf-8'
+
   fields = if req.doc.tilbyder is req.usr then {} else {privat: false}
-
-  return res.end() if req.method is 'HEAD'
-  req.cache.getCol(req.col).findOne {_id: req.doc._id}, fields, (err, doc) ->
-    return res.json doc if not err
-    return next(err)
+  req.cache.getCol(req.col)
+    .find({_id: req.doc._id}, fields, {limit: 1})
+    .stream()
+    .pipe(JSONStream.stringify('','',''))
+    .pipe(res)

 exports.put = (req, res, next) ->
   res.json 501, message: 'HTTP method not implmented'


main: export acorn :)

main: export acorn :)

--- coffee/src/main.coffee
+++ coffee/src/main.coffee
@@ -4,3 +4,5 @@
 goog.require 'acorn.config'
 goog.require 'acorn.util'
 goog.require 'acorn'
+
+(exports ? this).acorn = acorn


Update child app tests to match recent app.getAll changes

--- tests/integration/models/application.spec.coffee
+++ tests/integration/models/application.spec.coffee
@@ -63,7 +63,8 @@
 						deviceType: 'generic'
 						parent: parentApplication.id
 				.then ->
-					resin.models.application.getAll()
+					# application.getAll() doesn't return dependent apps
+					resin.pine.get(resource: 'application')
 				.then ([ parentApplication, childApplication ]) ->
 					m.chai.expect(childApplication.depends_on__application.__id).to.equal(parentApplication.id)


Adding Vars for faster writing.

Adding Vars for faster writing.

--- snippets/bulma_elements.cson
+++ snippets/bulma_elements.cson
@@ -39,8 +39,8 @@
   'Bulma element - Icon':
     'prefix': 'bulma:icon'
     'body': """
-
-
+
+

     """


Update organized-mode.cson

Update organized-mode.cson

--- snippets/organized-mode.cson
+++ snippets/organized-mode.cson
@@ -13,3 +13,7 @@
   'Shell Source Code Block':
     'prefix': '#+BEGIN'
     'body': '#+BEGIN_SRC sh :results verbatim\n$0\n#+END_SRC'
+  'Headline':
+    'prefix': 'h'
+    'body': '# $1\n\tDEADLINE: <DATE>'
+    'description': 'Add a Headline'


Social: Follow notification request handler is updated for using activity request

--- workers/social/lib/social/models/socialapi/notification.coffee
+++ workers/social/lib/social/models/socialapi/notification.coffee
@@ -65,9 +65,9 @@
     {connection:{delegate}} = client
     return callback new KodingError "Access denied"  if delegate.type isnt 'registered'

-    delegate.createSocialApiId (err, followerId) ->
+    delegate.createSocialApiId (err, actorId) ->
       return callback err  if err
-      followee.createSocialApiId (err, followeeId) ->
+      followee.createSocialApiId (err, targetId) ->
         return callback err  if err
         {createFollowNotification} = require './requests'
         createFollowNotification {actorId, targetId}, (err, response) ->


More Aid Work

More Aid Work

General loading and compiling is setup.

--- app/views.coffee
+++ app/views.coffee
@@ -27,9 +27,9 @@
       notify 'Manifest could not load, halting app.'
       return

-    # The index Helper is a FSHelper that allows us to fetch the file contents.
-    #fshelper_path = "[#{@options.vmName}]#{@options.manifest.path}/index.js"
-    #@indexjsHelper = FSHelper.createFileFromPath fshelper_path
+    @options.targetApp = {}
+    # Soon we'll offer targetted VMs, but for now default it.
+    @options.targetApp.vmName = @options.vmName

@@ -42,7 +42,7 @@
     KD.singletons.vmController.run
       vmName    : @options.vmName
       withArgs  : "ls ~/Applications"
-      (err, res) ->
+      (err, res) =>
         if err? then notify err.message; return
         kdAppNames = res.split('\n')[..-1]
         kdAppNameOpt = []
@@ -50,11 +50,15 @@
           if appname is 'appaid.kdapp' then continue
           kdAppNameOpt.push {title: appname, value: appname}
         appSelectBox.setSelectOptions kdAppNameOpt
+        # Don't forget to add our targetApp Default
+        @options.targetApp.appName = kdAppNames[0]

     appLoadBtn = new KDButtonView
       title     : 'Load App'
       callback  : =>
-        notify 'Loading..'
+        @loadApp (err) ->
+          if err?
+            notify "Error during Load: #{err.message}"

     appSplit = new KDSplitView
@@ -83,7 +87,6 @@

     @previewView = new KDView()

-
     @addSubView new KDSplitView
       type      : 'horizontal'
       resizable : false
@@ -100,26 +103,54 @@

   # ### Compile App
   #
-  compileApp: (callback) ->
-    new KDNotificationView
-      title: 'Compiling...'
+  compileApp: (callback=->) ->
+    {
+      appName
+      vmName
+    } = @options.targetApp
+    notify "Compiling '#{appName}'..."

     KD.singletons.vmController.run
-      vmName    : @options.vmName
-      withArgs  : "kdc #{Settings.APP_DIR}"
+      vmName    : vmName
+      withArgs  : "kdc ~/Applications/#{appName}"
       (err, res) ->
         # Currently ignoring the response of kdc.
         callback err

+  # ### Load App
+  #
+  loadApp: (callback=->) ->
+    console.log "Wtf?"
+    console.log @options
+    {
+      appName
+      vmName
+    } = @options.targetApp
+    notify "Loading '#{appName}'..."
+
+    @appIndexHelper = FSHelper.createFileFromPath(
+      "[#{vmName}]~/Applications/#{appName}"
+    )
+    @appIndexHelper.exists (err, exists) =>
+      if exists
+        @previewApp callback
+      else
+        @compileApp (err) =>
+          if err? then return callback err
+          @previewApp callback
+

   # ### Preview App
   #
-  previewApp: ->
-    new KDNotificationView
-      title: 'Loading preview...'
+  previewApp: (callback=->) ->
+    {
+      appName
+      vmName
+    } = @options.targetApp
+    notify "Previewing '#{appName}'..."

-    @indexjsHelper.fetchContents (err, res) =>
-      if err? then new KDNotificationView title: err.message
+    @appIndexHelper.fetchContents (err, res) =>
+      if err? then return callback err
       console.log 'Fetched! '+ res?.length

       # By destroying the subviews, we ensure (or try to) that the newly


Add a password reset request auth function (untested)

--- app/api/auth.coffee
+++ app/api/auth.coffee
@@ -199,6 +199,14 @@
       else
         throw new Error 'No signed-in user to change the password for'

+  requestPasswordReset: ({email}) ->
+    @_getAuthToken().then (token) =>
+      data =
+        authenticity_token: token
+        user: {email}
+
+      client.post '/../users/password', data, JSON_HEADERS
+
   disableAccount: ->
     console?.log 'Disabling account'
     @checkCurrent().then (user) =>