delete moar code

delete moar code

--- tutor/src/components/course-calendar/duration.cjsx
+++ tutor/src/components/course-calendar/duration.cjsx
@@ -81,29 +81,17 @@
       .value()

   calcDurationHeight: (rangeData) ->
-    overlappingPlans = []
-    planStacks = []
-
-    _.each(rangeData.plansByDays, (plansByDay) =>
-      @setPlanRelativeOrder(plansByDay)
-
-      planIds = _.map(plansByDay, (planInfo) -> planInfo.plan.id)
-      planOrder = _.map(plansByDay, (planInfo) -> planInfo.relativeOrder)
-
-      # if plans don't group together on adjacent days,
-      if _.isEmpty(_.intersection(_.last(overlappingPlans), planIds))
-        # push as new group
-        overlappingPlans.push(planIds)
-        planStacks.push(planOrder)
-      else
-        # else, plans share adjacent days, group together.
-        overlappingPlans[overlappingPlans.length - 1] = _.union(_.last(overlappingPlans), planIds)
-        planStacks[planStacks.length - 1] = _.union(_.last(planStacks), planOrder)
-    )
-
-    rangeData.maxPlansOnDay = _.max(planStacks, (plansInStack) ->
-      plansInStack.length
-    ).length
+    rangeData.maxPlansOnDay = _.chain(rangeData.plansByDays)
+      .map (plansByDay) =>
+        @_setPlanRelativeOrder(plansByDay)
+        # use plan relative order to calculate plan "height"
+        _.map(plansByDay, (plan) -> -1 * plan.relativeOrder + 1)
+      # flatten for all heights in week
+      .flatten()
+      # union with a 0 height, for durations with no plans
+      .union([0])
+      .max()
+      .value()

     # set day height to the best-guess for this range based on how many plans it has.
     # It'll be fine-tuned later across all ranges
@@ -128,7 +116,7 @@
       )
     )

-  setPlanRelativeOrder: (plans) ->
+  _setPlanRelativeOrder: (plans) ->
     current =
       adder: 0


Fixed a typo

Fixed a typo

--- src/typhoon/errorController.coffee
+++ src/typhoon/errorController.coffee
@@ -1,4 +1,4 @@
-{View} = require './view'
+View = require './view'

 module.exports =  (configs) ->
   errorView = new View 'error', configs.encoding


[fix] Fixed binding on model.destroy callbacks

--- vendor/assets/javascripts/neat/model_editor.coffee
+++ vendor/assets/javascripts/neat/model_editor.coffee
@@ -140,8 +140,8 @@

       @model.destroy
         wait: true
-        success: => @onDestroySuccess
-        error: _.bind(@onSaveError, @)
+        success: _.bind(@onDestroySuccess, @)
+        error: _.bind(@onDestroyError, @)
       @endEdit()

   confirmDestroy: (resource, callback)->


[refactor] Added an `afterRender` callback to Model Views (1m)

--- vendor/assets/javascripts/neat/model_editor.coffee
+++ vendor/assets/javascripts/neat/model_editor.coffee
@@ -33,6 +33,11 @@
     json = _.extend(@model.toJSON(), {options: @templateOptions})
     $(@el).html @template()(json)
     $(@el).attr('id', "#{@resource}_#{@model.get('id')}") # e.g. "calendar_5"
+
+    @afterRender()
+    @
+
+  afterRender: ->
     @

   inEdit: -> $(@el).hasClass('editor')


Add date ranges

Add date ranges

--- src/range-popup.coffee
+++ src/range-popup.coffee
@@ -8,12 +8,18 @@
   templateUrl: 'range-popup.html'
   scope:
     popupState: '='
-    popupSource: '='
+    popupStartSource: '='
+    popupEndSource: '='
     popupResult: '='
     popupRefresh: "&?"
   link: (scope) ->
+
+    scope.data =
+      startDate: ''
+      endDate: ''
+
     scope.popup =
-      hidePopup: (isStartPopup) ->
+      hidePopup: () ->
         scope.popupState.isOpen = false
       selectDate: (isStartPopup, date) ->
         scope.popupResult = date
@@ -21,85 +27,97 @@
         if not scope.popup.isDayInSelectedMonth date
           scope.popup.refreshSelectedData date.month, date.year
       goPrevYear: (isStartPopup) ->
-        return if not scope.popupSource.selected or not scope.popupSource.selected.year
-        if scope.popupSource.selected.year.isStart
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+
+        return if not popupSource.selected or not popupSource.selected.year
+        if popupSource.selected.year.isStart
           console.error 'error'
           return false
-        year = scope.popupSource.selected.year.num - 1
-        month = scope.popupSource.selected.month.num
+        year = popupSource.selected.year.num - 1
+        month = popupSource.selected.month.num
         scope.popup.refreshSelectedData month, year
       isFirstYear: (isStartPopup) ->
-        return if not scope.popupSource or not scope.popupSource.selected
-        return scope.popupSource.selected.year.isStart
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        return if not popupSource or not popupSource.selected
+        return popupSource.selected.year.isStart
       goPrevMonth: (isStartPopup) ->
-        return if not scope.popupSource.selected or not scope.popupSource.selected.year
-        if scope.popupSource.selected.month.isStart and scope.popupSource.selected.year.isStart
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        return if not popupSource.selected or not popupSource.selected.year
+        if popupSource.selected.month.isStart and popupSource.selected.year.isStart
           console.error 'error'
           return false

         december = 12
-        month = scope.popupSource.selected.month.num
+        month = popupSource.selected.month.num

-        if scope.popupSource.selected.month.isStart
-          year = scope.popupSource.selected.year.num - 1
+        if popupSource.selected.month.isStart
+          year = popupSource.selected.year.num - 1
           month = december
-        else if scope.popupSource.selected.month.isStart and scope.popupSource.selected.year.isStart
+        else if popupSource.selected.month.isStart and popupSource.selected.year.isStart
           console.error 'error'
           return false
         else
-          year = scope.popupSource.selected.year.num
-          month = scope.popupSource.selected.month.num - 1
+          year = popupSource.selected.year.num
+          month = popupSource.selected.month.num - 1

         scope.popup.refreshSelectedData month, year
       isFirstMonth: (isStartPopup) ->
-        return if not scope.popupSource or not scope.popupSource.selected
-        return scope.popupSource.selected.month.isStart
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        return if not popupSource or not popupSource.selected
+        return popupSource.selected.month.isStart
       goNextMonth: (isStartPopup) ->
-        return if not scope.popupSource.selected or not scope.popupSource.selected.year
-        if scope.popupSource.selected.month.isEnd and scope.popupSource.selected.year.isEnd
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        return if not popupSource.selected or not popupSource.selected.year
+        if popupSource.selected.month.isEnd and popupSource.selected.year.isEnd
           console.error 'error'
           return false

         january = 1
-        month = scope.popupSource.selected.month.num
+        month = popupSource.selected.month.num

-        if scope.popupSource.selected.month.isEnd
-          year = scope.popupSource.selected.year.num + 1
+        if popupSource.selected.month.isEnd
+          year = popupSource.selected.year.num + 1
           month = january
-        else if scope.popupSource.selected.month.isEnd and scope.popupSource.selected.year.isEnd
+        else if popupSource.selected.month.isEnd and popupSource.selected.year.isEnd
           console.error 'error'
           return false
         else
-          year = scope.popupSource.selected.year.num
-          month = scope.popupSource.selected.month.num + 1
+          year = popupSource.selected.year.num
+          month = popupSource.selected.month.num + 1

         scope.popup.refreshSelectedData month, year
       isLastMonth: (isStartPopup) ->
-        return if not scope.popupSource or not scope.popupSource.selected
-        return scope.popupSource.selected.month.isEnd
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        return if not popupSource or not popupSource.selected
+        return popupSource.selected.month.isEnd
       goNextYear: (isStartPopup) ->
-        return if not scope.popupSource.selected or not scope.popupSource.selected.year
-        if scope.popupSource.selected.year.isEnd
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        return if not popupSource.selected or not popupSource.selected.year
+        if popupSource.selected.year.isEnd
           console.error 'error'
           return false
-        year = scope.popupSource.selected.year.num + 1
-        month = scope.popupSource.selected.month.num
+        year = popupSource.selected.year.num + 1
+        month = popupSource.selected.month.num
         scope.popup.refreshSelectedData month, year
       isLastYear: (isStartPopup) ->
-        return if not scope.popupSource or not scope.popupSource.selected
-        return scope.popupSource.selected.year.isEnd
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        return if not popupSource or not popupSource.selected
+        return popupSource.selected.year.isEnd
       isSelectedDay: (date) ->
         return if not scope.popupResult or not scope.popupResult.day
         return ((date.day is scope.popupResult.day) and (date.month is scope.popupResult.month) and (date.year is scope.popupResult.year))
       getTodayDateTime: (isStartPopup) ->
-        return if not scope.popupSource or not scope.popupSource.today
-        today = scope.popupSource.today
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        return if not popupSource or not popupSource.today
+        today = popupSource.today
         return new Date(today.year, today.month - 1, today.day).getTime()
       isDayInSelectedMonth: (isStartPopup, date) ->
-        return ((date.month is scope.popupSource.selected.month.num) and (date.year is scope.popupSource.selected.year.num))
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        return ((date.month is popupSource.selected.month.num) and (date.year is popupSource.selected.year.num))
       goToYear: (isStartPopup) ->
-        year = scope.popupSource.selected.year.num
-        month = scope.popupSource.selected.month.num
+        popupSource = (isStartPopup) ? scope.popupStartSource : scope.popupEndSource
+        year = popupSource.selected.year.num
+        month = popupSource.selected.month.num
         scope.popup.refreshSelectedData month, year
       refreshSelectedData: (month, year) ->
         scope.popupRefresh


[fix] concat returns a new Array

[fix] concat returns a new Array

--- src/redis-model.coffee
+++ src/redis-model.coffee
@@ -157,7 +157,7 @@
       {id, queue} = job
       prefix = "bull:#{queue}:"
       multi.push ["del", "#{prefix}#{id}"]
-      multi.concat @commandRemoveFromStateLists prefix, id
+      multi = multi.concat @commandRemoveFromStateLists prefix, id

     (@redis.multi multi).exec callback


Allow autofocus to accept a container element

--- components/form/utilities.coffee
+++ components/form/utilities.coffee
@@ -9,11 +9,17 @@
     val = $input.val()
     $input.val('').val val

-  autofocus: ($input, defer = false) ->
+  isFocusable: ($el) ->
+    $el.is('input') or $el.is('textarea')
+
+  autofocus: ($el, defer = false) ->
     return if isTouchDevice()

     focus = =>
-      $input.focus()
-      @moveCursorToEnd $input
+      if not @isFocusable $el
+        $el = @firstVisibleInput $el
+
+      $el.focus()
+      @moveCursorToEnd $el

     if defer then _.defer(focus) else focus()


KD.extend: getPathInfo to get machineUid instead vmName which is not exists anymore

--- client/Main/KD.extend.coffee
+++ client/Main/KD.extend.coffee
@@ -235,12 +235,14 @@

   getPathInfo: (fullPath)->
     return no unless fullPath
+
     path      = FSHelper.plainPath fullPath
     basename  = FSHelper.getFileNameFromPath fullPath
     parent    = FSHelper.getParentPath path
-    vmName    = FSHelper.getVMNameFromPath fullPath
+    machineUid= FSHelper.getUidFromPath fullPath
     isPublic  = FSHelper.isPublicPath fullPath
-    {path, basename, parent, vmName, isPublic}
+
+    return {path, basename, parent, machineUid, isPublic}

   getPublicURLOfPath: (fullPath, secure=no)->
     {vmName, isPublic, path} = KD.getPathInfo fullPath


ForcedRegister: check for sr=1 query param if it is there do not force register

--- client/Main/KD.extend.coffee
+++ client/Main/KD.extend.coffee
@@ -42,7 +42,8 @@
     options.styles       or= []           # <Array<string>> list of stylesheets

     enforceLogin=->
-      return if KD.isLoggedIn()
+      return  if KD.isLoggedIn()
+      return  if location.search.indexOf("sr=1") > -1
       appManager = KD.getSingleton "appManager"
       appManager.tell "Account", "showRegistrationNeededModal"


refactor svg compress config

refactor svg compress config

--- client/config/webpack.coffee
+++ client/config/webpack.coffee
@@ -7,30 +7,27 @@

 ExtractTextPlugin = require "extract-text-webpack-plugin"

+base_svg_plugins = [
+  {cleanupIDs: on}
+  {removeTitle: on}
+  {removeComments: on}
+  {removeDesc: on}
+  {removeDimensions: on}
+  {removeUselessStrokeAndFill: on}
+]
+
 config =
   svg_sprite: JSON.stringify
     spriteModule: path.resolve path_to.root, "config/custom-sprite"
     name: 'icon-[name]'
   svg_icon: JSON.stringify
     plugins: [
-      {cleanupIDs: on}
-      {removeTitle: on}
-      {removeComments: on}
-      {removeDesc: on}
-      {removeDimensions: on}
-      {removeUselessStrokeAndFill: on}
+      base_svg_plugins...
       {convertColors: {currentColor: on}}
     ]
   svg_image: JSON.stringify
-    plugins: [
-      {cleanupIDs: on}
-      {removeTitle: on}
-      {removeComments: on}
-      {removeDesc: on}
-      {removeDimensions: on}
-      {removeUselessStrokeAndFill: on}
-    ]
-
+    plugins: base_svg_plugins
+
 module.exports = (env) ->
   webpack_config =
     context: path_to.root


home: check for /Welcome route as well

home: check for /Welcome route as well

--- client/home/lib/index.coffee
+++ client/home/lib/index.coffee
@@ -36,7 +36,7 @@
     super options, data

-  checkRoute: (route) -> /^\/Home.*/.test route
+  checkRoute: (route) -> /^\/(Home|Welcome).*/.test route

   openSection: (args...) -> @mainView.ready => @openSection_ args...


SocialWorker: Tests: pass missing credential argument

--- workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
@@ -533,8 +533,8 @@
       forEachProvider (providerSlug) ->

         options = { provider : providerSlug }
-        withConvertedUserAnd ['ComputeProvider'], options, (data) ->
-          { client, machine } = data
+        withConvertedUserAnd ['ComputeProvider', 'Credential'], options, (data) ->
+          { client, machine, credential } = data

           queue = [

@@ -545,7 +545,7 @@
                 queue.next()

             ->
-              options = { provider : providerSlug, machineId : machine._id.toString() }
+              options = { provider : providerSlug, machineId : machine._id.toString(), credential }
               ComputeProvider.remove client, options, (err) ->
                 if err
                   return expect(err.message).to.be.equal notImplementedMessage


some progress, a little more to go

some progress, a little more to go

--- src/main.iced
+++ src/main.iced
@@ -2,10 +2,11 @@
 minimist = require 'minimist'
 {make_esc} = require 'iced-error'
 iutils = require 'iced-utils'
+{a_json_parse} = iutils.util

 usage = () ->
   console.error """usage:
-    json [-p] [-b] <path.to[4].your.obj>
+    json [-p] [-b] [-c] <path.to[4].your.obj>
 """

 #==================================================
@@ -17,6 +18,8 @@
     @b64decode = false
     @inspect = false
     @depth = 4
+    @path = null
+    @count = false

   parse_argv : ({argv}, cb) ->
     argv = minimist argv
@@ -28,6 +31,11 @@
       @b64decode = argv.b
       @inspect = argv.i
       @depth = argv.d
+      @count = argv.c
+      if argv._.length > 1
+        err = new Error "only need one arg -- a path to your object -- which is optional"
+      else if argv._.length is 1
+        @path = argv._[0]
     cb err

   read_input : (opts, cb) ->
@@ -38,16 +46,29 @@
     stream.on 'end',  () ->
       cb null, Buffer.concat bufs

+  pick_path : ({json}, cb) ->
+    err = null
+    if @path?
+      parts = @path.split /\./
+      for part, i in parts
+        if not json?
+          err = new Error "null value at #{parts[0...i].join(".")}"
+        else
+          json = json[part]
+    cb err, json
+
   run : (opts, cb) ->
     esc = make_esc cb, "Runner::run"
     await @parse_argv opts, esc defer()
     await @read_input opts, esc defer buf
-    cb null, buf
+    await a_json_parse buf.toString(), esc defer json
+    await @pick_path { json }, esc defer json
+    cb null, json

 #==================================================

 r = new Runner
-await r.run { argv : process.argv }, defer err, buf
+await r.run { argv : process.argv[2...] }, defer err, buf
 if err?
   console.error err.toString()
 else


Make u.option skip on isGiven instead of isPresent

--- lib/assets/javascripts/up/util.js.coffee
+++ lib/assets/javascripts/up/util.js.coffee
@@ -327,7 +327,7 @@
     for arg in args
       value = arg
       value = value() if isFunction(value)
-      if isPresent(value)
+      if isGiven(value)
         match = value
         break
     match


Update to aww me

Update to aww me

- Skip the mod post
- Turn imgur page links into image links

--- src/scripts/aww.coffee
+++ src/scripts/aww.coffee
@@ -13,6 +13,8 @@
 # Author:
 #   eliperkins

+url = require("url")
+
 module.exports = (robot) ->
   robot.respond /aww/i, (msg) ->
     search = escape(msg.match[1])
@@ -20,13 +22,23 @@
       .get() (err, res, body) ->
         result = JSON.parse(body)

-        if result.data.children.count <= 0
+        urls = [ ]
+        for child in result.data.children
+          if child.data.domain != "self.aww"
+            urls.push(child.data.url)
+
+        if urls.count <= 0
           msg.send "Couldn't find anything cute..."
           return
-
-        urls = [ ]
-        for child in result.data.children
-          urls.push(child.data.url)
-
+
         rnd = Math.floor(Math.random()*urls.length)
-        msg.send urls[rnd]
+        picked_url = urls[rnd]
+
+        parsed_url = url.parse(picked_url)
+        if parsed_url.host == "imgur.com"
+          parsed_url.host = "i.imgur.com"
+          parsed_url.pathname = parsed_url.pathname + ".jpg"
+
+          picked_url = url.format(parsed_url)
+
+        msg.send picked_url


:ab::taurus: Updated at https://danielx.net/editor/

--- pixie.cson
+++ pixie.cson
@@ -14,6 +14,6 @@
   hotkeys: "distri/hotkeys:v0.2.0"
   "jquery-utils": "distri/jquery-utils:v0.2.0"
   observable: "distri/observable:v0.3.8"
-  postmaster: "distri/postmaster:master"
+  postmaster: "distri/postmaster:v0.5.1"
   "touch-canvas": "distri/touch-canvas:v0.3.1"
   ui: "STRd6/ui:master"


Add an assertion to a previously assertion-less test for clarity

--- tests/batman/utilities_test.coffee
+++ tests/batman/utilities_test.coffee
@@ -21,6 +21,8 @@
   obj =
     initialize: "x"
   Batman.mixin(@base, obj)
+  ok true, "Initializer wasn't called because no error was thrown"
+

 test "should use set on objects which have it defined", ->
   obj = {}


Port to Process API

Port to Process API

--- components/Chromatose.coffee
+++ components/Chromatose.coffee
@@ -5,32 +5,18 @@

 exports.getComponent = ->
   c = new noflo.Component
-  c.format = null
-  c.colors = null
+  c.inPorts.add 'colors',
+    datatype: 'array'
+  c.inPorts.add 'format',
+    datatype: 'string'
+    control: true
+    default: 'hsl'
+  c.outPorts.add 'out',
+    datatype: 'object'
+  c.forwardBrackets =
+    colors: ['out']

-  c.inPorts.add 'colors', (event, payload) ->
-    return unless event is 'data'
-    c.colors = payload
-    c.solve()
-
-  c.inPorts.add 'format', (event, payload) ->
-    return unless event is 'data'
-    c.format = payload
-    c.solve()
-
-  c.outPorts.add 'out'
-
-  c.solve = ->
-    return unless c.outPorts.out.isAttached()
-    return unless c.colors?
-
-    colors = c.colors
-
-    if c.format is null
-      format = 'hsl'
-    else
-      format = c.format
-
+  solve = (colors, format = 'hsl') ->
     options =
       format: format
       lights: ['white']
@@ -169,12 +155,20 @@

     #prime = box.clone().rotate(-60)

-    c.outPorts.out.send {
+    return {
       box:box[format]()
       text:text[format]()
       meta:meta[format]()
       meta_highlight:meta_highlight[format]()
       prime:prime[format]()
     }
-
-  c+
+  c.process (input, output) ->
+    return unless input.hasData 'colors'
+    return if input.attached('format').length and not input.hasData 'format'
+    format = 'hsl'
+    if input.hasData 'format'
+      format = input.getData 'format'
+    colors = input.getData colors
+    output.sendDone
+      out: solve colors, format


Send UUID on 'data' rather than 'disconnect'

--- components/RandomUuid.coffee
+++ components/RandomUuid.coffee
@@ -1,9 +1,9 @@
-noflo = require("noflo")
-uuid = require("node-uuid")
+noflo = require('noflo')
+uuid = require('node-uuid')

 class RandomUuid extends noflo.Component

-  description: "Generate a random UUID token"
+  description: 'Generate a random UUID token'

   constructor: ->
     @inPorts =
@@ -11,9 +11,14 @@
     @outPorts =
       out: new noflo.Port

-    @inPorts.in.on "disconnect", =>
-      token = uuid.v4()
-      @outPorts.out.send(token)
+    @inPorts.in.on 'begingroup', (group) =>
+      @outPorts.out.beginGroup group
+    @inPorts.in.on 'endgroup', =>
+      @outPorts.out.endGroup()
+    @inPorts.in.on 'disconnect', =>
       @outPorts.out.disconnect()

+    @inPorts.in.on 'data', =>
+      @outPorts.out.send uuid.v4()
+
 exports.getComponent = -> new RandomUuid


Fix remove reception items when editing by unchecking elements in reconciliation modal

--- app/assets/javascripts/backend/purchase_process/reconciliation.js.coffee
+++ app/assets/javascripts/backend/purchase_process/reconciliation.js.coffee
@@ -14,16 +14,16 @@
       $('.reconcile-state').addClass('hidden')

   refresh_state = ->
-    $displayComplianceStates = $(".nested-item-form[data-non-compliant='true']")
-
-    set_reconciliation_state !!$('.nested-item-form[data-item-id]').length
+    $displayComplianceStates = $(".nested-fields .nested-item-form[data-non-compliant='true']")
+
+    set_reconciliation_state !!$('.nested-fields .nested-item-form[data-item-id]').length

     if !$displayComplianceStates.length
       $('.compliance-title').addClass('hidden')
     else
       $('.compliance-title').removeClass('hidden')

-  $(document).on 'cocoon:after-remove', '#new_reception, #new_purchase_invoice', refresh_state
+  $(document).on 'cocoon:after-remove', '#new_reception, #new_purchase_invoice, .edit_reception, .edit_purchase_invoice', refresh_state

   $(document).on 'change', '#purchase_invoice_accepted_state', (event) ->
@@ -80,7 +80,7 @@
   $(document).on 'click', '#purchase_process_reconciliation .valid-modal', (event) ->
     modal = $(event.target).closest('#purchase_process_reconciliation')

-    displayedItemIds = $('.nested-item-form[data-item-id]').map(-> $(this).attr('data-item-id')).toArray()
+    displayedItemIds = $('.nested-fields .nested-item-form[data-item-id]').map(-> $(this).attr('data-item-id')).toArray()

     if $(this).attr('data-item-reconciliation') != undefined
       # Reconciliation on line
@@ -221,7 +221,7 @@
           E.reconciliation.editReconciliationModal(@reconciliationModal)

     editReconciliationModal: (modal) ->
-      displayedItemIds = $('.nested-item-form[data-item-id]').map(-> $(this).attr('data-item-id')).toArray()
+      displayedItemIds = $('.nested-fields .nested-item-form[data-item-id]').map(-> $(this).attr('data-item-id')).toArray()

       for id in displayedItemIds
         $checkbox = modal.getModalContent().find("input[type='checkbox'][data-id=#{id}]")
@@ -257,11 +257,13 @@

     removeLineWithUnselectedItems: (modal, displayedItemIds, event) ->
       for id in displayedItemIds
-
         $checkbox = $(modal).find("input[type='checkbox'][data-id=#{id}]")
-        continue if $checkbox.prop('checked')
-
-        $('.item-form__btn .btn').first().trigger('click')
+        if $checkbox.length && !$checkbox.prop('checked')
+          $container = $(".nested-fields .nested-item-form[data-item-id='#{id}'").closest('tbody')
+          if $container.length
+            $container.find('a.remove-item').click()
+
+      0

     _createNewItemLine: (itemCheckbox) ->


Fixed new item lines creation after validating reconciliation modal

--- app/assets/javascripts/backend/purchase_process/reconciliation.js.coffee
+++ app/assets/javascripts/backend/purchase_process/reconciliation.js.coffee
@@ -107,12 +107,12 @@
       data['reception'] = action.substr(action.lastIndexOf('/') + 1)
     {url: url, data: data}

-  isPurchaseInvoiceForm = ($form) =>
+  isPurchaseInvoiceForm = ($form) =>
     $form.closest('.simple_form').is('.new_purchase_invoice, .edit_purchase_invoice')
-
-  isReceptionForm = ($form) =>
+
+  isReceptionForm = ($form) =>
     $form.closest('.simple_form').is('.new_reception, .edit_reception')
-
+
   # Returns a promise that resolves to the content for the modal to be opened
   content_for_reconciliation_modal = ($form) =>
     options = {}
@@ -139,13 +139,13 @@
       E.delegateListener root, 'click', '.model-checkbox', (e) =>
         checked = e.target.checked
         commandContainer = e.target.closest('.model')
-        setAllChildrenTo(commandContainer, checked, $form)
+        setAllChildrenTo(commandContainer, checked, $form)
         return

       displayedItemIds = Array.from(document.querySelectorAll('.nested-fields .nested-item-form[data-item-id]'))
       displayedItemIds = displayedItemIds.map((item) => item.dataset.itemId)

-      displayedItemIds.forEach (id) =>
+      displayedItemIds.forEach (id) =>
         checkbox = content.querySelector(".item input[type='checkbox'][data-id='#{id}']")
         commandContainer = checkbox.closest('.model')
         if checkbox
@@ -161,7 +161,7 @@
   # Checks the parent checkbox if all children are checked. Uncheck it if not.
   # If all checked, set 'close command to true, else to false'
   # Also hide/display the close command radio
-  syncState = (commandContainer, $form) =>
+  syncState = (commandContainer, $form) =>
     itemCheckboxes = Array.from(commandContainer.querySelectorAll('.item-checkbox'))
     allItemsCheckboxesChecked = itemCheckboxes.map((cb) => cb.checked).reduce(((acc, e) => acc && e), true)
     commandContainer.querySelector('.model-checkbox').checked = allItemsCheckboxesChecked
@@ -176,7 +176,7 @@
         closePurchaseOrderBlock.classList.add('hidden')

   # Sets the value of the radiobutton to `value`
-  setCloseCommand = (commandContainer, value) =>
+  setCloseCommand = (commandContainer, value) =>
     closePurchaseOrderBlock = commandContainer.querySelector('.close-purchase-order')
     closePurchaseOrderBlock.querySelector("input[type='radio'][value=#{value}").checked = true

@@ -209,12 +209,12 @@
             $('#purchase_invoice_responsible_id').first().selector('value', responsibleId)

           E.reconciliation._fillNewLineForm(itemCheckbox, isPurchaseOrderModal)
-
+
         if isPurchaseOrderModal == "true"
           $itemCb = $(itemCheckbox)
           purchaseOrderItemId = $itemCb.data('id')
           $formContainer = $("table.list .nested-fields .nested-item-form[data-item-id='#{purchaseOrderItemId}']")
-          purchaseOrderToCloseUpdate($itemCb, $formContainer)
+          purchaseOrderToCloseUpdate($itemCb, $formContainer)

     removeLineWithUnselectedItems: (modal, displayedItemIds, event) ->
       for id in displayedItemIds
@@ -258,10 +258,10 @@
       else
         E.reconciliation._fillReceptionItem(lastLineForm, checkboxLine, itemId, itemQuantity, itemAnnotation)

-      $(lastLineForm).find('input[data-remember="equipment"]').first().selector('value', equipmentId)
-      $(lastLineForm).find('input[data-remember="team"]').first().selector('value', teamId)
-      $(lastLineForm).find('input[data-remember="project_budget"]').first().selector('value', projectBudgetId)
-      $(lastLineForm).find('input[data-remember="activity_budget"]').first().selector('value', activityBudgetId)
+      $(lastLineForm).find('input[data-remember="equipment"]').first().val(equipmentId)
+      $(lastLineForm).find('input[data-remember="team"]').first().val(teamId)
+      $(lastLineForm).find('input[data-remember="project_budget"]').first().val(projectBudgetId)
+      $(lastLineForm).find('input[data-remember="activity_budget"]').first().val(activityBudgetId)
       $(lastLineForm).find('.no-reconciliate-item-state').addClass('hidden')
       $(lastLineForm).find('.reconciliate-item-state').removeClass('hidden')
       $line = $(lastLineForm).parent('.nested-fields')
@@ -294,10 +294,14 @@
       $(lastLineForm).find('.form-field .invoice-unit-amount').val(itemUnitCost)
       $(lastLineForm).find('.form-field .invoice-discount-percentage').val(itemReductionPercentage)
       $(lastLineForm).find('.form-field .pre-tax-invoice-total').val(itemTotalAmount)
+
       $selectorInput = $(lastLineForm).find('.form-field .invoice-variant').first()
-      $selectorInput.selector('value', variantId, (-> disable_selector_input($selectorInput);$(lastLineForm).find('.form-field .invoice-quantity').trigger('change')))
-      $(lastLineForm).find('.form-field .purchase_invoice_items_activity_budget .selector-search').first().selector('value', activityBudgetId)
-      $(lastLineForm).find('.form-field .purchase_invoice_items_team .selector-search').first().selector('value', teamId)
+      $selectorInput.val(variantId)
+      disable_selector_input($selectorInput)
+
+      $(lastLineForm).find('.form-field .invoice-quantity').trigger('change')
+      $(lastLineForm).find('.form-field .purchase_invoice_items_activity_budget .selector-search').first().val(activityBudgetId)
+      $(lastLineForm).find('.form-field .purchase_invoice_items_team .selector-search').first().val(teamId)
       $(lastLineForm).find('.annotation-logo .annotation-field').trigger('click')
       $(lastLineForm).find('.annotation-section .annotation').val(itemAnnotation)

@@ -323,8 +327,10 @@
       $(lastLineForm).find('.purchase-item-attribute').val(itemId)

       $selectorInput = $(lastLineForm).find('.item-block-role .parcel-item-variant').first()
-
-      $selectorInput.selector('value', variantId, (-> disable_selector_input($selectorInput);$(lastLineForm).find('.form-field .invoice-quantity').trigger('change')))
+      $selectorInput.val(variantId)
+      disable_selector_input($selectorInput)
+
+      $(lastLineForm).find('.form-field .invoice-quantity').trigger('change')
       $(lastLineForm).find('.hidden.purchase-item-attribute').val(itemId)
       $(lastLineForm).find('.annotation-logo .annotation-field').trigger('click')
       $(lastLineForm).find('.annotation-section .annotation').val(itemAnnotation)
@@ -339,7 +345,7 @@

   # Given checkbox and formContainer, this method sets the purchase-order-to-close-id property to the purchase order id on the given reception item's form fields
   #    if the purchase order of the selected element is marked for closure when saving the reception
-  #
+  #
   # @param [jQuery<HTMLElement>] checkbox %li containing a checkbox for an item to maybereconcile
   # @param [jQuery<HTMLElement>] formContainer form line containing information about a reception item
   purchaseOrderToCloseUpdate = ($checkbox, $formContainer) ->
@@ -348,13 +354,13 @@
     $closePurchaseOrderBlock = $purchaseOrderLine.find('.close-purchase-order')
     if $closePurchaseOrderBlock.find('input[type="radio"][value="true"]').is(':checked')
       $formContainer.find('.purchase-order-to-close-id').val(modelId)
-
-  E.onElementDetected 'new_reception', (form) =>
+
+  E.onElementDetected 'new_reception', (form) =>
     urlParams = new URLSearchParams(window.location.search)
     if urlParams.has('purchase_order_ids')
       open_reconciliation_modal $(form)
-
-  E.onElementDetected 'new_purchase_invoice', (form) =>
+
+  E.onElementDetected 'new_purchase_invoice', (form) =>
     urlParams = new URLSearchParams(window.location.search)
     if urlParams.has('reception_ids')
       open_reconciliation_modal $(form)


[fix] Fixed the sort on the Estimate Effort view

--- app/assets/javascripts/houston-scheduler/views/_edit_tickets_view.coffee
+++ app/assets/javascripts/houston-scheduler/views/_edit_tickets_view.coffee
@@ -36,9 +36,9 @@
     $list = $('#tickets').empty()
     @tickets
       .sortBy (ticket)=>
-        [ +ticket.get(@attribute) > 0,  # put tickets _with_ estimates at the bottom,
-          +ticket.get('sequence') <= 0, # then tickets with no priority,
-          +ticket.get('sequence') ]     # finally sort by priority
+        (+ticket.get('estimatedEffort') > 0) * 99999999 + # put tickets _with_ estimates at the bottom,
+        (+ticket.get('sequence') <= 0) * 9999999 +        # then tickets with no priority,
+         +ticket.get('sequence')                          # finally sort by priority
       .each (ticket)=>
         view = new Scheduler.EditTicketView
           ticket: ticket


EDSC-22: Add coordinate information to tooltips

--- app/assets/javascripts/modules/map/leaflet-plugins/draw_errors.js.coffee
+++ app/assets/javascripts/modules/map/leaflet-plugins/draw_errors.js.coffee
@@ -2,6 +2,26 @@
 # shapes to perform the validations we need. before allowing the user
 # to complete a shape
 do (L) ->
+
+  # Append coordinate information to tooltips
+  originalUpdateContent = L.Tooltip.prototype.updateContent
+  L.Tooltip.prototype.updateContent = (content) ->
+    @_content = content
+    if @_point
+      content =
+        text: "#{content.text}<br>#{@_point}"
+        subtext: content.subtext
+    originalUpdateContent.call(this, content)
+
+  originalUpdatePosition = L.Tooltip.prototype.updatePosition
+
+  L.Tooltip.prototype.updatePosition = (latlng) ->
+    @_point = "(#{latlng.lat.toFixed(5)}, #{latlng.lng.toFixed(5)})"
+    if @_content?
+      @updateContent(@_content)
+
+    originalUpdatePosition.call(this, latlng)
+

   # Validates a polygon that the user has not yet closed.
   validateIncompletePolygon = (latLngs) ->


actually inform volumetracing about tracing mode switch, #1182

--- app/assets/javascripts/oxalis/view/action-bar/volume_actions_view.coffee
+++ app/assets/javascripts/oxalis/view/action-bar/volume_actions_view.coffee
@@ -46,6 +46,7 @@
   changeMode : (evt) ->

     mode = @modeMapping[evt.target.id]
+    @model.volumeTracing.setMode(mode)
     app.vent.trigger("changeVolumeMode", mode)


reorder layers

reorder layers

--- src/ui.coffee
+++ src/ui.coffee
@@ -67,10 +67,10 @@

   svg = d3.select('body').append('svg')

-  segments = svg.append('g')
   contours = svg.append('g')
   rivers = svg.append('g')
   highways = svg.append('g')
+  segments = svg.append('g')
   symbols = svg.append('g')
   locationg = svg.append('g').attr('class', 'location')
   scaleg = svg.append('g')


tests: Check data subscription support

tests: Check data subscription support

--- spec/01transport.coffee
+++ spec/01transport.coffee
@@ -99,6 +99,7 @@
   broker = null

   describeIfRoundRobinSupport = if type == 'AMQP' then describe else describe.skip
+  describeIfSubscribeSupport = if type != 'AMQP' then describe else describe.skip

   beforeEach (done) ->
     broker = transport.getBroker address
@@ -363,6 +364,53 @@
       it 'only NACKed message is sent to deadletter', ->
         chai.expect(received.deadletter).to.eql [ { foo: 'nack'} ]

+  describeIfSubscribeSupport 'subscribing to bound topics', ->
+    sendQueue = 'sub-send-36'
+    receiveQueue = 'sub-receive-36'
+    binding = { type:'pubsub', src:sendQueue, tgt:receiveQueue }
+    connectionData = []
+    clients = null
+
+    # Should be a before, but the 'beforeEach' of higher scope are ran afterwards...
+    setup = (done) ->
+      createConnectClients address, ['sender', 'receiver'], (err, c) ->
+        clients = c
+        createQueues [
+          [ clients.receiver, 'inqueue', receiveQueue ]
+          [ clients.sender, 'outqueue', sendQueue ]
+        ], (err) ->
+          chai.expect(err).to.not.exist
+          broker.addBinding binding, (err) ->
+            chai.expect(err).to.be.a 'null'
+            return done null
+
+    it 'should provide data sent on connection', (done) ->
+      payloads =
+        one: { foo: 'sub-96' }
+        two: { bar: 'sub-97' }
+
+      onData = (bind, data) ->
+        chai.expect(bind.src).to.equal binding.src
+        chai.expect(bind.tgt).to.equal binding.tgt
+        connectionData.push data
+        # wait until we've gotten two packets
+        if connectionData.length == 2
+          [one, two] = connectionData
+          chai.expect(one).to.eql payloads.one
+          chai.expect(two).to.eql payloads.two
+          return done null
+        else if connectionData.length > 2
+          return done new Error "Got more data than expected"
+
+      setup (err) ->
+        return done err if err
+        broker.subscribeData binding, onData, (err) ->
+          return done err if err
+          clients.sender.sendTo 'outqueue', sendQueue, payloads.one, (err) ->
+            return done err if err
+            clients.sender.sendTo 'outqueue', sendQueue, payloads.two, (err) ->
+              return done err if err
+
 describe 'Transport', ->
   Object.keys(transports).forEach (type) =>
     describe "#{type}", () ->


Add jasmine.attachToDOM

Add jasmine.attachToDOM

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -191,6 +191,10 @@
   throw new Error("Not a spy") unless object[methodName].hasOwnProperty('originalValue')
   object[methodName] = object[methodName].originalValue

+jasmine.attachToDOM = (element) ->
+  jasmineContent = document.querySelector('#jasmine-content')
+  jasmineContent.appendChild(element) unless jasmineContent.contains(element)
+
 addCustomMatchers = (spec) ->
   spec.addMatchers
     toBeInstanceOf: (expected) ->


Show dev tools when specs are run

Show dev tools when specs are run

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -11,7 +11,7 @@
 TextMateTheme = require 'text-mate-theme'
 fs = require 'fs'
 require 'window'
-# atom.showDevTools()
+atom.showDevTools()

 requireStylesheet "jasmine.css"
 TextMateBundle.loadAll()


Add moar tests for #toUnicode and #parse

--- spec/emojis-spec.coffee
+++ spec/emojis-spec.coffee
@@ -1,6 +1,14 @@
 should()

+if Meteor.isServer
+  Meteor.publish 'emojis', ->
+    Emojis.find()
+
 describe 'Emojis', ->
+
+  if Meteor.isClient
+    before (done) ->
+      Meteor.subscribe 'emojis', -> done()

   beforeEach ->
     Emojis._basePath = '/images/emojis'
@@ -38,4 +46,27 @@
       template = Emojis.template(@customEmoji)
       template.should.equal ""

+  describe '#toUnicode', ->

+    it 'should convert shortnames in a text to unicode emojis', ->
+
+      text = 'Hello there :boom:'
+      result = Emojis.toUnicode(text)
+      result.should.contain('💥').and.not.contain ':boom:'
+
+  describe '#parse', ->
+
+    beforeEach ->
+      @text = 'Hello :boom: I am a :D. This is no a smiley:) and this is not an emoji:+1:'
+
+    it 'should parse text to emoji images', ->
+
+      result = Emojis.parse(@text)
+
+      result.should
+        .contain ""
+        .and.contain ""
+        .and.contain ':)'
+        .and.contain ':+1:'
+        .and.not.contain ':boom:'
+        .and.not.contain ':D'


:white_check_mark: Add tests for throwOnStdErr

--- spec/helper-spec.coffee
+++ spec/helper-spec.coffee
@@ -37,6 +37,19 @@
         helpers.exec(__dirname + '/fixtures/stderr.sh', []).catch (message) ->
           expect(message).toBe("STDERR\n")

+    describe 'throwOnStdErr option', ->
+      it 'throws unexpected error when set to true', ->
+        waitsForPromise ->
+          helpers.exec(__dirname + '/fixtures/stderr.sh', [], throwOnStdErr: true).catch (message) ->
+            expect(message).toBe("STDERR\n")
+      it 'suppresses unexpected errors when set to false', ->
+        gotError = false
+        waitsForPromise ->
+          helpers.exec(__dirname + '/fixtures/stderr.sh', [], throwOnStdErr: false).catch(->
+            gotError = true
+          ).then ->
+            expect(gotError).toBe(false)
+
   describe '::parse', ->
     it 'cries when no argument is passed', ->
       expect ->


Mark exclusive as optional in markPosition

Like with markRange, there is a default and the backing implementation
treats it as optional.

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -1031,12 +1031,12 @@
   #     * __touch__: The marker is invalidated by a change that touches the marked
   #       region in any way, including changes that end at the marker's
   #       start or start at the marker's end. This is the most fragile strategy.
-  #   * `exclusive` {Boolean} indicating whether insertions at the start or end
-  #     of the marked range should be interpreted as happening *outside* the
-  #     marker. Defaults to `false`, except when using the `inside`
-  #     invalidation strategy or when when the marker has no tail, in which
-  #     case it defaults to true. Explicitly assigning this option overrides
-  #     behavior in all circumstances.
+  #   * `exclusive` (optional) {Boolean} indicating whether insertions at the
+  #     start or end of the marked range should be interpreted as happening
+  #     *outside* the marker. Defaults to `false`, except when using the
+  #     `inside` invalidation strategy or when when the marker has no tail, in
+  #     which case it defaults to true. Explicitly assigning this option
+  #     overrides behavior in all circumstances.
   #
   # Returns a {Marker}.
   markPosition: (position, options) -> @defaultMarkerLayer.markPosition(position, options)


Use screen ranges consistently

Use screen ranges consistently

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -2056,7 +2056,9 @@

   mergeSelectionsOnSameRows: (args...) ->
     @mergeSelections args..., (previousSelection, currentSelection) ->
-      previousSelection.intersectsScreenRowRange(currentSelection.getBufferRowRange()...)
+      screenRange = currentSelection.getScreenRange()
+
+      previousSelection.intersectsScreenRowRange(screenRange.start.row, screenRange.end.row)

   mergeSelections: (args...) ->
     mergePredicate = args.pop()


Fix for packages with no spec/fixtures directory

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -61,6 +61,8 @@

 if specDirectory = FindParentDir.sync(testPaths[0], 'fixtures')
   specProjectPath = path.join(specDirectory, 'fixtures')
+else
+  specProjectPath = path.join(__dirname, 'fixtures')

 beforeEach ->
   documentTitle = null


add duration method to BaseModel

add duration method to BaseModel

--- src/3d/BaseModel.coffee
+++ src/3d/BaseModel.coffee
@@ -171,3 +171,5 @@
     allAnimations = @mesh.animations.map (a) -> a.data.name
     throw "#{animationName} not found. Possible animations are: #{allAnimations}"

+  duration: (animationName) ->
+    @animation(animationName)._clip.duration * 1000


added guards to Track .add and .connect methods after getting myself into trouble with them using the demo

--- src/woodentrack.coffee
+++ src/woodentrack.coffee
@@ -63,25 +63,30 @@
 			result = result.concat section._pieces
 		result

-	# Add piece to track.
-	# Use start transform to specify position/orientation of piece.
-	# If no start provided, the cursor connection will be used.
-	# If no pieces in track, the default transform is used.
+	# Add piece to track at cursor.
+	# If there are no pieces in track, the start transform is used in lieu of the cursor.
+    # If there are pieces in the track but there's no cursor, you'll need to provide a transform in the second parameter to specify the position of the piece.
 	# TODO: check piece connections for collisions and bail if there are any
 	add: (piece, where) ->
-		if where?
-			section = @_createSection -> where
-			piece.setSection section
-			@_firePieceAdded piece
-		else if @connections().length==0 and !@started?
-			section = @_createSection => @start
-			piece.setSection section
-			@_firePieceAdded piece
-			@started = piece
-		else if @cursor()?
-			@connect piece, @cursor()
+		if piece instanceof Piece
+			if where?
+				if where instanceof Transform
+					section = @_createSection -> where
+					piece.setSection section
+					@_firePieceAdded piece
+				else
+					throw new Error 'the second parameter should be a transform - perhaps you need to use the connect method?'
+			else if @connections().length==0 and !@started?
+				section = @_createSection => @start
+				piece.setSection section
+				@_firePieceAdded piece
+				@started = piece
+			else if @cursor()?
+				@connect piece, @cursor()
+			else
+				throw new Error 'unable to add piece - try specifying the where parameter in the track.add method'
 		else
-			throw new Error 'unable to add piece - try specifying the where parameter in the track.add method'
+			throw new Error 'the first parameter should be a piece'

 	# connection (code) where the next piece will be added
 	cursor: ->
@@ -100,28 +105,31 @@
 	# Connect piece to available connection identified from code, e.g. "10:C".
 	# Throws Error if specified connection not available.
 	connect: (piece, code) ->
-		if @connections().indexOf(code)>-1
-			# check through each of the section exits before creating a new section
-			added = false
-			@_sections.forEach (section) =>
-				length = section._pieces.length
-				last = section._pieces[length-1]
-				lastExit = (@_sectionStartingIndex(section)+length-1)+":"+last.exit
-				if lastExit == code
-					added = true
+		if piece instanceof Piece
+			if @connections().indexOf(code)>-1
+				# check through each of the section exits before creating a new section
+				added = false
+				@_sections.forEach (section) =>
+					length = section._pieces.length
+					last = section._pieces[length-1]
+					lastExit = (@_sectionStartingIndex(section)+length-1)+":"+last.exit
+					if lastExit == code
+						added = true
+						piece.setSection section
+						@_firePieceAdded piece
+				if !added
+					[index, label] = code.split ':'
+					[sectionIndex, sectionPieceIndex] = @_sectionAndPieceIndex index
+					section = @_createSection => @_sections[sectionIndex].compoundTransform(sectionPieceIndex,label).compound(@_gapTransform)
+					@_connection(code).connected = piece.connections['A']
+					piece.connections['A'].connected = @_connection(code)
 					piece.setSection section
 					@_firePieceAdded piece
-			if !added
-				[index, label] = code.split ':'
-				[sectionIndex, sectionPieceIndex] = @_sectionAndPieceIndex index
-				section = @_createSection => @_sections[sectionIndex].compoundTransform(sectionPieceIndex,label).compound(@_gapTransform)
-				@_connection(code).connected = piece.connections['A']
-				piece.connections['A'].connected = @_connection(code)
-				piece.setSection section
-				@_firePieceAdded piece
+			else
+				throw new Error(code + " is not an available connection")
 		else
-			throw new Error(code + " is not an available connection")
-
+			throw new Error 'the first parameter should be a piece'
+
 	# remove indexed piece from track
 	remove: (index) ->
 		[sectionIndex, pieceIndex] = @_sectionAndPieceIndex index


remove my.kind.wall

remove my.kind.wall

--- src/my.coffee
+++ src/my.coffee
@@ -99,7 +99,4 @@
     label: '_LABEL'
     setup: '_SETUP'
   }
-  kind: {
-    wall: '_WALL'
-  }
 }


fix typo

fix typo

--- src/s3.coffee
+++ src/s3.coffee
@@ -26,7 +26,7 @@
           Key: file.key
           Body: fs.readFileSync file.path
           ContentType: mime.lookup file.path
-    , Promise.resovle()
+    , Promise.resolve()

   _putObject: (options) ->
     new Promise (resolve, reject) =>


Update en.coffee

Update en.coffee

Tried to add some translation excluded string. Hope it helps.

--- t9n/en.coffee
+++ t9n/en.coffee
@@ -1,5 +1,4 @@
 # We need a dummy translation so that a text is found.
-

 en =

@@ -40,7 +39,11 @@
   username: "Username"
   usernameOrEmail: "Username or email"
   with: "with"
-
+  maxAllowedLength: "Maximum allowed length"
+  minRequiredLength: "Minimum required length"
+  resendVerificationEmail: "Send email again"
+  resendVerificationEmailLink_pre: "Verification email lost?"
+  resendVerificationEmailLink_link: "Send again"

   info:
     emailSent: "Email sent"
@@ -63,7 +66,6 @@

     accounts:
-
       #---- accounts-base
       #"@" + domain + " email required"
       #"A login handler should return a result or undefined"
@@ -79,6 +81,7 @@
       "You are not logged in.": "You are not logged in."
       "You've been logged out by the server. Please log in again.": "You've been logged out by the server. Please log in again."
       "Your session has expired. Please log in again.": "Your session has expired. Please log in again."
+      "Already verified": "Already verified"

       #---- accounts-oauth
@@ -103,6 +106,9 @@
       "User not found": "User not found"
       "Verify email link expired": "Verify email link expired"
       "Verify email link is for unknown address": "Verify email link is for unknown address"
+      "At least 1 digit, 1 lowercase and 1 uppercase": "At least 1 digit, 1 lowercase and 1 uppercase"
+      "Please verify your email first. Check the email and follow the link!": "Please verify your email first. Check the email and follow the link!"
+      "A new email has been sent to you. If the email doesn't show up in your inbox, be sure to check your spam folder.": "A new email has been sent to you. If the email doesn't show up in your inbox, be sure to check your spam folder."

       #---- match
       "Match failed": "Match failed"
@@ -111,4 +117,4 @@
       "Unknown error": "Unknown error"

-T9n.map "en", en+T9n.map "en", en


Fixed simulation bug for real now

Fixed simulation bug for real now

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -511,8 +511,8 @@
 	_stopSimulation: =>
 		@_isAnimating = false
 		return unless @_simulation
-		@_simulation?.x?.stop() if @_simulation.x
-		@_simulation?.y?.stop() if @_simulation.y
+		@_simulation.x.stop?() if @_simulation.x
+		@_simulation.y.stop?() if @_simulation.y
 		@emit(Events.Move, @layer.point)
 		@emit(Events.DragAnimationDidEnd)
 		@_simulation = null


Fix method missing exception in LineMap.logLines

--- src/app/line-map.coffee
+++ src/app/line-map.coffee
@@ -186,7 +186,7 @@
     new Range(start, end)

   # Internal:
-  logLines: (start=0, end=@screenLineCount() - 1)->
+  logLines: (start=0, end=@getScreenLineCount() - 1)->
     for row in [start..end]
       line = @lineForScreenRow(row).text
       console.log row, line, line.length


kaleidoscope: Fix custom binary operators.

--- examples/kaleidoscope.coffee
+++ examples/kaleidoscope.coffee
@@ -167,7 +167,7 @@
   isBinaryOp: -> @isOperator and @args.length == 2
   getOperatorName: ->
     console.assert(@isUnaryOp() or @isBinaryOp())
-    @name
+    @name.slice(-1)

   createArgumentAllocas: (f) ->

@@ -464,9 +464,9 @@

         # Read the precedence if present.
         if @curTok == tok_number
-          if @lexer.value < 1 || @lexer.value > 100
+          if @lexer.val < 1 || @lexer.val > 100
             throw new Error("Invalid precedence: must be 1..100")
-          BinaryPrecedence = @lexer.value
+          BinaryPrecedence = @lexer.val
           @getNextToken()
       else
         throw new Error("Expected function name in prototype")
@@ -583,10 +583,10 @@
   # If it wasn't a builtin binary operator, it must be a user defined one. Emit
   # a call to it.
   F = TheModule.getFunction("binary"+@op)
-  assert(F, "binary operator not found!")
+  console.assert(F, "binary operator not found!")

   Ops = [ L, R ]
-  return Builder.CreateCall(F, Ops, "binop")
+  return Builder.createCall(F, Ops, "binop")

 CallExprAST::Codegen = ->
   # Look up the name in the global module table.


adding note linking lines to tp #1655

adding note linking lines to tp #1655

--- jsapp/xlform_model_view/model.survey.coffee
+++ jsapp/xlform_model_view/model.survey.coffee
@@ -225,6 +225,8 @@

   # Settings (assigned to each $survey.Survey instance)
   class Settings extends $base.BaseModel
+    # !! Verify a fix before uncommenting this validation
+    # !! TP #1655, title validation always fails
     # validation:
     #   form_title:
     #     required: true


#1209 - If select option is empty, and you switch to another question, the empty option becomes uneditable

--- jsapp/xlform_model_view/view.choices.coffee
+++ jsapp/xlform_model_view/view.choices.coffee
@@ -155,19 +155,22 @@
       if lis.length == 1
         lis.find('.js-remove-option').addClass('hidden')
     saveValue: (nval)->
-      @model.set("label", nval, silent: true)
-      other_names = @options.cl.getNames()
-      if !@model.get('setManually')
-        sluggifyOpts =
-          preventDuplicates: other_names
-          lowerCase: false
-          stripSpaces: true
-          lrstrip: true
-          incrementorPadding: 3
-          validXmlTag: true
-        @model.set("name", $modelUtils.sluggify(nval, sluggifyOpts))
-      @$el.trigger("choice-list-update", @options.cl.cid)
-      return
+      if nval
+        @model.set("label", nval, silent: true)
+        other_names = @options.cl.getNames()
+        if !@model.get('setManually')
+          sluggifyOpts =
+            preventDuplicates: other_names
+            lowerCase: false
+            stripSpaces: true
+            lrstrip: true
+            incrementorPadding: 3
+            validXmlTag: true
+          @model.set("name", $modelUtils.sluggify(nval, sluggifyOpts))
+        @$el.trigger("choice-list-update", @options.cl.cid)
+        return
+      else
+        return newValue: @model.get "label"

   ListView: ListView
   OptionView: OptionView


pusher logging

pusher logging

--- app/controllers/user_edit.coffee
+++ app/controllers/user_edit.coffee
@@ -70,9 +70,9 @@
     return if @pusher

     # Disable pusher on dev mode for now
-    return if Config.env=='development'
+#    return if Config.env=='development'
     # Enable pusher logging - don't include this in production
-    if Config.env!='production'
+    if Config.env=='development'
       Pusher.log = (message) ->
         window.console.log message  if window.console and window.console.log

@@ -83,6 +83,8 @@
     channel = @pusher.subscribe("user_"+@item.id)
     channel.bind "rpc", (data) ->
       eval(data.cmd)
+      forge.logging.debug("pusher message #{message}") if forge?
+      console.log("pusher message #{message}") if console?

 class MyUserShow extends UsersShow


Make collaborators page more clear

Make collaborators page more clear

--- app/pages/lab/collaborators.cjsx
+++ app/pages/lab/collaborators.cjsx
@@ -3,13 +3,12 @@
 apiClient = require '../../api/client'

 POSSIBLE_ROLES = [
-  'owner'
   'collaborator'
   'expert'
   'scientist'
   'moderator'
   'tester'
-  'translator'
+  # 'translator'
 ]

 CollaboratorCreator = React.createClass
@@ -32,10 +31,7 @@

{@state.error.toString()}
}
       <form style={style}>

-          Username
-

-
-          <input type="text" ref="usernameInput" className="standard-input" />
+          Username: <input type="text" ref="usernameInput" className="standard-input" />

@@ -70,17 +66,21 @@

     getUser = apiClient.type('users').get display_name: username
       .then ([user]) =>
-        newRoleSet = apiClient.type('project_roles').create
-          roles: roles
-          links:
-            project: @props.project.id
-            user: user.id
-
-        newRoleSet.save().then =>
-          usernameInput.value = ''
-          for checkbox in checkboxes
-            checkbox.checked = false
-          @props.onAdd? arguments...
+        if user?
+          newRoleSet = apiClient.type('project_roles').create
+            roles: roles
+            links:
+              project: @props.project.id
+              user: user.id
+
+          newRoleSet.save().then =>
+            usernameInput.value = ''
+            for checkbox in checkboxes
+              checkbox.checked = false
+            @props.onAdd? arguments...
+
+        else
+          throw new Error "User '#{username}' doesn't exist"

       .catch (error) =>
         @setState error: error
@@ -108,8 +108,11 @@

{@state.error.toString()}
}
       <PromiseRenderer promise={@props.project.get 'project_roles'} then={(projectRoleSets) =>

-          {for projectRoleSet in projectRoleSets
-            <PromiseRenderer key={projectRoleSet.id} promise={projectRoleSet.get 'owner'} then={@renderUserRow.bind this, projectRoleSet} />}
+          {if projectRoleSets.length > 1
+            for projectRoleSet in projectRoleSets
+              <PromiseRenderer key={projectRoleSet.id} promise={projectRoleSet.get 'owner'} then={@renderUserRow.bind this, projectRoleSet} />
+          else
+            None yet}

       } />

@@ -121,10 +124,6 @@

-            Owner
-            The owner is the original project creator. There can be only one.
-

-

             Collaborator
             Collaborators have full access to edit workflows and project content, including deleting some or all of the project. [This last part seems silly, actually.]

@@ -142,32 +141,37 @@

             Tester
-            ?
-

-

-            Translator
-            Translators will have access to the translation site?
-

+            Testers can access private projects if they’re given the project’s address.
+

+          {if false # Translations are not implemented yet.
+

+              Translator
+              Translators will have access to the translation site?
+
}

   renderUserRow: (projectRoleSet, user) ->
-

-      {user.display_name}{' '}
-      <button type="button" className="secret-button" onClick={@removeRoleSet.bind this, projectRoleSet}>×</button>
-

-
-
-        {for role in POSSIBLE_ROLES
-          toggleThisRole = @toggleRole.bind this, projectRoleSet, role
-          # TODO: Translate this.
-
-            <input type="checkbox" name={role} checked={role in projectRoleSet.roles} disabled={role is 'owner' or projectRoleSet.id in @state.saving} onChange={toggleThisRole} />{' '}
-            {role[...1].toUpperCase()}{role[1...]}
-          }
-
-

+    console.log {user}, 'Owner', @props.owner
+    if 'owner' in projectRoleSet.roles
+      null
+    else
+

+        {user.display_name}{' '}
+        <button type="button" className="secret-button" onClick={@removeRoleSet.bind this, projectRoleSet}>×</button>
+

+
+
+          {for role in POSSIBLE_ROLES
+            toggleThisRole = @toggleRole.bind this, projectRoleSet, role
+            # TODO: Translate this.
+
+              <input type="checkbox" name={role} checked={role in projectRoleSet.roles} disabled={role is 'owner' or projectRoleSet.id in @state.saving} onChange={toggleThisRole} />{' '}
+              {role[...1].toUpperCase()}{role[1...]}
+            }
+
+

   toggleRole: (projectRoleSet, role) ->
     index = projectRoleSet.roles.indexOf role


Log what specs are running

Log what specs are running

--- tasks/test-task.coffee
+++ tasks/test-task.coffee
@@ -17,6 +17,7 @@
         args: ['test', '-p', atomPath]
         opts:
           cwd: packagePath
+      grunt.log.writeln("Launching #{path.basename(packagePath)} specs.")
       spawn options, (error, results, code) ->
         passed = passed and code is 0
         callback()


Trying

Trying

--- app/tests/cucumber/features/users/step_definitions/create-account-steps.coffee
+++ app/tests/cucumber/features/users/step_definitions/create-account-steps.coffee
@@ -1,7 +1,7 @@
 module.exports = ->

 	@Given /^A seller has created a product$/, ->
-		server.call 'fixtures/seedData'
+	#	server.call 'fixtures/seedData'

 	@Given /^I am not logged in$/, ->
 		@AuthenticationHelper.createAccount()
@@ -15,4 +15,4 @@
 	@Then /^I cannot see offer options$/, ->
 		client.waitForExist '.popup-login' #so we know the page has loaded in non-logged in mode
 		expect(client.isVisible('.popup-login')).toBe true
-		expect(client.isVisible('#make-offer')).toBe false+		expect(client.isVisible('#make-offer')).toBe false


fixes #248

fixes #248

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -152,10 +152,10 @@
     xrefs = item.relations?['dc:relation']
     if xrefs
       item.__xref__ = []
-      xrefs = [xref] unless Array.isArray(xref)
+      xrefs = [xrefs] unless Array.isArray(xrefs)
       for xref in xrefs
         m = xref.match(/^http:\/\/zotero.org\/users\/local\/[^\/]+\/items\/([A-Z0-9]+)$/i)
-        next unless m
+        continue unless m
         item.__xref__.push(Zotero.BetterBibTeX.keymanager.get({libraryID: item.libraryID, key: m[1]}, 'on-export').citekey)
       if item.__xref__.length == 0
         delete item.__xref__


hide tooltip when hiding popup (but not toggling or clicking on item)

--- resources/assets/lib/popup-menu.coffee
+++ resources/assets/lib/popup-menu.coffee
@@ -102,6 +102,7 @@
     return if !event? # originalEvent gets eaten by error popup?

     if event.keyCode == 27 || (event.button == 0 && !@isMenuInPath(event.composedPath()))
+      @tooltipElement().qtip().hide()
       @setState active: false


made sure examples on the demo page are sorted

--- web-src/examples-gen.coffee
+++ web-src/examples-gen.coffee
@@ -5,7 +5,7 @@
   // Generated code, do not edit
   window.examples = [
     #{(
-      for f in fs.readdirSync "#{__dirname}/../examples" when /^\w.+\.apl$/.test f
+      for f in fs.readdirSync("#{__dirname}/../examples").sort() when /^\w.+\.apl$/.test f
         '  ' + JSON.stringify [
           f.replace(/^\d+-|\.apl$/g, '')
           fs.readFileSync("#{__dirname}/../examples/#{f}", 'utf8').replace(/^#!.*\n+|\n+$/g, '').replace(/\n* *⎕ *← *(.*)$/, '\n$1')


Store background on unload

Store background on unload

--- src/atom.coffee
+++ src/atom.coffee
@@ -282,9 +282,7 @@
         deprecate "The atom.syntax global is deprecated. Use atom.grammars instead."
         @grammars

-    @disposables.add @packages.onDidActivateInitialPackages =>
-      @watchThemes()
-      @storeWindowBackground()
+    @disposables.add @packages.onDidActivateInitialPackages => @watchThemes()

     Project = require './project'
     TextBuffer = require 'text-buffer'
@@ -619,6 +617,7 @@
   unloadEditorWindow: ->
     return if not @project

+    @storeWindowBackground()
     @state.grammars = @grammars.serialize()
     @state.project = @project.serialize()
     @state.workspace = @workspace.serialize()
@@ -758,7 +757,6 @@
       # Only reload stylesheets from non-theme packages
       for pack in @packages.getActivePackages() when pack.getType() isnt 'theme'
         pack.reloadStylesheets?()
-      @storeWindowBackground()
       return

   # Notify the browser project of the window's current project path


Initialize KeymapManager on Atom environment construction

--- src/atom.coffee
+++ src/atom.coffee
@@ -171,6 +171,8 @@

     KeymapManager = require './keymap-extensions'
     @keymaps = new KeymapManager({configDirPath, resourcePath, notificationManager: @notifications})
+    @keymaps.subscribeToFileReadFailure()
+    @keymaps.loadBundledKeymaps()

     TooltipManager = require './tooltip-manager'
     @tooltips = new TooltipManager(keymapManager: @keymaps)
@@ -286,8 +288,6 @@

     # Make react.js faster
     process.env.NODE_ENV ?= 'production' unless devMode
-
-    @keymaps.subscribeToFileReadFailure()

     document.head.appendChild(new StylesElement)

@@ -611,7 +611,6 @@
       console.warn error.message if error?

     @loadConfig()
-    @keymaps.loadBundledKeymaps()
     @themes.loadBaseStylesheets()
     @packages.loadPackages()
     @deserializeEditorWindow()


Don't make atom.services public yet

Don't make atom.services public yet

--- src/atom.coffee
+++ src/atom.coffee
@@ -134,7 +134,7 @@
   # Public: A {Clipboard} instance
   clipboard: null

-  # Public: A {ServiceHub} instance
+  # A {ServiceHub} instance
   services: null

   # Public: A {ContextMenuManager} instance


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_flash.coffee
+++ src/camera_flash.coffee
@@ -28,8 +28,8 @@

       I.flashCooldown = I.flashCooldown.approach(0, 1)

-  self.bind 'flash', (canvas)
-    canvas.fill(I.flashColor)
+  self.bind 'flash', (canvas) ->
+    canvas.fill(I.flashColor

   ###*
   A convenient way to set the flash effect instance variables. Alternatively, you can modify them by hand, but


we do not need a sidebar click event handler for OERPUB (proper version)

--- scripts/views/layouts/workspace.coffee
+++ scripts/views/layouts/workspace.coffee
@@ -35,11 +35,8 @@

     events:
       'click #workspace-container > .close': 'minimizeWorkspace'
-      'click #sidebar-container > .close':   'minimizeSidebar'

     initialize: () ->
       @workspace.on 'show', => @$('#workspace-container').removeClass('minimized')
-      @sidebar.on   'show', => @$('#sidebar-container').removeClass('minimized')

     minimizeWorkspace: () -> @$('#workspace-container').toggleClass('minimized')
-    minimizeSidebar: () -> @$('#sidebar-container').toggleClass('minimized')


:penguin: Add linux keymaps.

:penguin: Add linux keymaps.

--- keymaps/command-palette.cson
+++ keymaps/command-palette.cson
@@ -3,3 +3,6 @@

 '.platform-win32, .platform-win32 .command-palette .editor':
   'ctrl-shift-p': 'command-palette:toggle'
+
+'.platform-linux, .platform-linux .command-palette .editor':
+  'ctrl-shift-p': 'command-palette:toggle'


Use console.error()

Use console.error()

--- server.coffee
+++ server.coffee
@@ -707,7 +707,7 @@
       console.log "=================="
     req = parseRequest bufString
     if not req?
-      console.warn "error: request can't be parsed: #{bufString}"
+      console.error "Error: request can't be parsed: #{bufString}"
       c.buf = null
       return
     req.rawbody = c.buf[req.headerBytes+4..]


Add jQuery and show move data.

Add jQuery and show move data.

--- server.coffee
+++ server.coffee
@@ -8,18 +8,23 @@
   moves: require './data/bw/moves.yml'

 io = socket.listen solid (app) ->
+  app.get '/jquery.js', @jquery
   app.get '/', @render ->
     @doctype 5
     @html ->
       @head ->
+        @js '/jquery.js'
         @js '/socket.io/socket.io.js'
         @script @html_safe '''
           var socket = io.connect('http://localhost');
           socket.on('newuser', function (data) {
             console.log(data);
-            document.write(data);
+            $("#messages").append(data);
           });
         '''
+      @body ->
+        @p JSON.stringify(engine.moves)
+        @p '#messages'

 io.sockets.on 'connection', (socket) ->
   socket.emit 'newuser', 'you joined!'


modify tests: use latest atom-shell, fix tests

--- test/basic-test.coffee
+++ test/basic-test.coffee
@@ -28,7 +28,7 @@

 platform = os.platform()

-defaultTargets = '0.17.2 0.19.5 0.20.0'.split ' '
+defaultTargets = '0.17.2 0.19.5 0.20.1'.split ' '
 defaultArchs = 'ia32 x64'.split ' '
 defaultArchs = ['ia32'] if platform is 'win32'

@@ -150,8 +150,8 @@
 		testInstallMulti 'zipfile@0.5.4'
 		testInstallMulti 'v8-profiler@5.2.1'
 		testInstallMulti 'sqlite3@3.0.4', ['0.17.2']
-		testInstallMulti 'sqlite3@master', ['0.19.5', '0.20.0'], null, tarball: 'https://github.com/mapbox/node-sqlite3/archive/master.tar.gz'
+		testInstallMulti 'sqlite3@master', ['0.19.5', '0.20.1'], null, tarball: 'https://github.com/mapbox/node-sqlite3/archive/master.tar.gz'

 	describe 'node-pre-gyp test app', ->
-		testInstallMulti 'node-pre-gyp-test-app1', null, null, tarball: 'test/node-pre-gyp/app1.tar.gz'
-		testInstallMulti 'node-pre-gyp-test-app3', null, null, tarball: 'test/node-pre-gyp/app3.tar.gz'
+		testInstallMulti 'node-pre-gyp-test-app1', null, null, tarball: '../test/node-pre-gyp/app1.tar.gz'
+		testInstallMulti 'node-pre-gyp-test-app3', null, null, tarball: '../test/node-pre-gyp/app3.tar.gz'


all tests now working

all tests now working

Work on addressing keybase/keybase-issues#216

This should do it, but we need to merge in with the running Web site code

--- test/browser/main.iced
+++ test/browser/main.iced
@@ -1,5 +1,6 @@

 mods =
+  secret_subkeys : require '../files/secret_subkeys.iced'
   sig_various_hashes : require '../files/sig_various_hashes.iced'
   openpgp_js_cure53_audit : require '../files/openpgp_js_cure53_audit.iced'
   bzip2 : require '../files/bzip2.iced'


add test for delete

add test for delete

--- test/data_tests.coffee
+++ test/data_tests.coffee
@@ -273,3 +273,32 @@
                 @body.should.not.have.property 'value'
                 @body.should.have.property 'new_value', @randomValue
                 done()
+
+describe "Delete", ->
+    describe "Delete a document that is not in Database", ->
+        before cleanRequest
+
+        it "When I send a request to delete Document with id 123", (done) ->
+            client.delete "data/123/", (error, response, body) =>
+                @response = response
+                done()
+
+        it "Then HTTP status 404 should be returned", ->
+            @response.statusCode.should.equal 404
+
+    describe "Delete a document that is in Database", ->
+        before cleanRequest
+
+        it "When I send a request to delete Document with id 654", (done) ->
+            client.delete "data/654/", (error, response, body) =>
+                @response = response
+                done()
+
+        it "Then HTTP status 204 should be returned", ->
+            @response.statusCode.should.equal 204
+
+        it "Then Document with id 654 shouldn't exist in Database", (done) ->
+            client.get 'data/exist/654/', (error, response, body) =>
+                @body = parseBody response, body
+                @body.exist.should.be.false
+                done()


Better and more tests.

Better and more tests.

--- test/index.spec.coffee
+++ test/index.spec.coffee
@@ -1,7 +1,9 @@
 expect = require('chai').expect
 fs = require 'fs-extended'
-cp = require 'child_process'
+path = require 'path'
 {spawn, exec} = require 'child_process'
+_ = require 'lodash'
+diff = require('diff-merge-patch').orderedList.diff

 Inotifyr = require '../'

@@ -15,8 +17,9 @@
   it 'should watch a directory for file add events', (done) ->
     watcher = new Inotifyr 'test/fixtures'
     watcher.on 'add', (filename, stats) ->
-      expect(filename).to.be.eql 'test/fixtures/new.txt'
+      expect(filename).to.be.eql path.resolve 'test/fixtures/new.txt'
       expect(stats).to.have.property 'isDir', no
+      expect(stats).to.have.property 'mtime'
       watcher.close()
       done()

@@ -25,28 +28,60 @@
   it 'should watch a directory for directory add events', (done) ->
     watcher = new Inotifyr 'test/fixtures'
     watcher.on 'add', (filename, stats) ->
-      expect(filename).to.be.eql 'test/fixtures/new'
+      expect(filename).to.be.eql path.resolve 'test/fixtures/new'
       expect(stats).to.have.property 'isDir', yes
+      expect(stats).to.have.property 'mtime'
       watcher.close()
       done()

     fs.createDirSync './test/fixtures/new'

+  [1..5].forEach (i) =>
+    it "should register all add events for a git clone (#{i})", (done) ->
+        @timeout 5000
+        watcher = new Inotifyr 'test/fixtures', recursive: yes
+        count = 0
+        files = []
+        watcher.on 'add', (filename, stats) ->
+          files.push filename
+          count++
+          console.log filename unless stats

-  it 'should register all add events for a git clone', (done) ->
-      @timeout 5000
-      watcher = new Inotifyr 'test/fixtures'
-      count = 0
-      watcher.on 'add', (filename, stats) ->
-        count++
+        git = spawn 'git', ['clone', 'https://github.com/codio/node-demo.git'], {cwd: './test/fixtures'}
+        git.stdout.on 'data', (data) -> console.log data.toString()
+        git.stderr.on 'data', (data) -> console.log data.toString()
+        git.on 'close', (code) ->
+          expect(code).to.be.eql 0

-      git = spawn 'git', ['clone', 'https://github.com/codio/node-demo.git', 'test/fixtures/node-demo']
-      git.stdout.on 'data', (data) -> console.log data.toString()
-      git.stderr.on 'data', (data) -> console.log data.toString()
-      git.on 'close', (code) ->
-        expect(code).to.be.eql 0
+          exec 'find test/fixtures/node-demo -type f -print | wc -l', (err, stdout, stderr) ->
+            fileCount = parseInt stdout, 10
+            exec 'find test/fixtures/node-demo -type d -print | wc -l', (err, stdout, stderr) ->
+              dirCount = parseInt stdout, 10
+              console.log diff _.uniq(files), files
+              expect(_.uniq(files).length).to.be.eql(fileCount + dirCount)
+              done()

-        exec 'find test/fixtures/node-demo -type f -print | wc -l', (err, stdout, stderr) ->
-          expect(count).to.be.eql parseInt(stdout, 10)
-          done()
+  [1..5].forEach (i) =>
+    it "should register all add events for an unzip action (#{i})", (done) ->
+        @timeout 5000
+        watcher = new Inotifyr 'test/fixtures', recursive: yes
+        count = 0
+        files = []
+        watcher.on 'add', (filename, stats) ->
+          files.push filename
+          count++
+          console.log filename unless stats

+        tar = spawn 'tar', ['-zxf', 'zipFile.tar.gz', '-C', 'fixtures'], {cwd: './test'}
+        tar.stdout.on 'data', (data) -> console.log data.toString()
+        tar.stderr.on 'data', (data) -> console.log data.toString()
+        tar.on 'close', (code) ->
+          expect(code).to.be.eql 0
+
+          exec 'find test/fixtures/zipDir -type f -print | wc -l', (err, stdout, stderr) ->
+            fileCount = parseInt stdout, 10
+            exec 'find test/fixtures/zipDir -type d -print | wc -l', (err, stdout, stderr) ->
+              dirCount = parseInt stdout, 10
+              console.log diff _.uniq(files), files
+              expect(_.uniq(files).length).to.be.eql(fileCount + dirCount)
+              done()


cache dem topojson

cache dem topojson

--- src/data.coffee
+++ src/data.coffee
@@ -62,14 +62,13 @@

   data.dem(region)

-  .then ->
+  .then (dem) ->
     bbox = data.REGION[region].bbox
     q = query(bbox)
     url = "http://overpass-api.de/api/interpreter?data=#{encodeURIComponent(q)}"
     console.log("overpass:", q)

     request url, (err, res, body) ->
-      dem = JSON.parse(fs.readFileSync("data/contours/#{region}.topojson"))
       p = JSON.parse(body)
       db = compileOsm(bbox, p, dem)
       ensureDir("build/#{region}")
@@ -203,32 +202,43 @@

 data.dem = (region) ->
-  demDone = Q.defer()
   bbox = data.REGION[region].bbox
-
-  exec("rm -f data/contours/#{region}.*")
-  .then ->
-    exec("gdalwarp
-          data/srtm-1arcsec-ro.tiff
-          data/contours/#{region}.tiff
-          -te #{bbox.join(' ')}")
-  .then ->
-    exec("gdal_contour
-          data/contours/#{region}.tiff
-          data/contours/#{region}.shp
-          -a elevation
-          -i 100")
-  .then ->
-    exec("topojson
-          contour=data/contours/#{region}.shp
-          -o data/contours/#{region}.topojson
-          --id-property ID
-          -p elevation
-          -s .00000000001")
-  .done ->
-    demDone.resolve()
-
-  return demDone.promise
+  demPath = "data/contours/#{region}.topojson"
+
+  buildDem = ->
+    demDone = Q.defer()
+    exec("rm -f data/contours/#{region}.*")
+    .then ->
+      exec("gdalwarp
+            data/srtm-1arcsec-ro.tiff
+            data/contours/#{region}.tiff
+            -te #{bbox.join(' ')}")
+    .then ->
+      exec("gdal_contour
+            data/contours/#{region}.tiff
+            data/contours/#{region}.shp
+            -a elevation
+            -i 100")
+    .then ->
+      exec("topojson
+            contour=data/contours/#{region}.shp
+            -o #{demPath}
+            --id-property ID
+            -p elevation
+            -s .00000000001")
+    .done ->
+      dem = JSON.parse(fs.readFileSync(demPath))
+      demDone.resolve(dem)
+
+    return demDone.promise
+
+  if fs.existsSync(demPath)
+    dem = JSON.parse(fs.readFileSync(demPath))
+    if JSON.stringify(bbox) == JSON.stringify(dem.objects.contour.bbox)
+      console.log "using cached dem topojson"
+      return Q(dem)
+
+  return buildDem()

 data.html = ->


fix use sample

fix use sample

--- test/use_sample.coffee
+++ test/use_sample.coffee
@@ -15,9 +15,9 @@
         console.log err.message, stderr
         return
     explainFile = explainDir + '/' + basename(sourceFile)
-    write explainFile, stdout, (err) ->
-        if err
-            console.log err
+    write explainFile, stdout, (err3) ->
+        if err3
+            console.log err3
             return
         exec 'jsdox2 ' + explainFile + ' -o ' + saveDir, (err2, stdout2, stderr2) ->
             if err2 or stderr2


accidentally removed a return statement, changed some comments

--- testRequirement.coffee
+++ testRequirement.coffee
@@ -24,12 +24,12 @@
     return isEmpty objectKey, testValues, object

   else if isPubdateTest testValues
-    # testValues is an object like {"year": 2000} or {"year": {"lte": 2000, "gte": 1990}} or {"year": "now"}
+    # testValues is an object like {"year": "now"}
     return matchesPubdate objectKey, testValues, object

   else
     # testValues is a single value; a string, boolean, or number
-    testValueInObject objectKey, testValues, object
+    return testValueInObject objectKey, testValues, object

 # Check if the test is a range test
 isRange = (testValues) ->
@@ -127,7 +127,7 @@
   if objectKey not of object
     return false
   objectValue = object[objectKey]
-  objectValue = JSON.stringify(objectValue)
+  objectValue = JSON.stringify(objectValue) # match in stringified arrays and objects too
   re = RegExp testValue, "im"
   match = objectValue.match re
   return if match then true else false


Use /usr/bin/env

Use /usr/bin/env

--- src/file.coffee
+++ src/file.coffee
@@ -1,5 +1,5 @@
 #!/usr/bin/coffee --bare
-`#!/usr/bin/node
+`#!/usr/bin/env node
 `

 fs = require 'fs'


Set aside CSS without using jQuery

Set aside CSS without using jQuery

--- spec/javascripts/papers_spec.js.coffee
+++ spec/javascripts/papers_spec.js.coffee
@@ -81,14 +81,15 @@

     describe "right bar (aside)", ->
       it "has its top set to 0 on unfixed", ->
-        aside = $("aside")
-        aside.css('top', '100px')
+        aside = $("aside")[0]
+        aside.style.top = '100px'
+        expect(aside.style.top).toEqual '100px'

         asideCall = null
         for call in $.fn.scrollToFixed.calls.all()
-          if call.object[0] == aside[0]
+          if call.object[0] == aside
             asideCall = call

         asideCall.args[0].unfixed.call aside

-        expect(aside.css('top')).toEqual '0px'
+        expect(aside.style.top).toEqual '0px'


whoops update var here, too

whoops update var here, too

--- spec/markdown-preview-view-spec.coffee
+++ spec/markdown-preview-view-spec.coffee
@@ -70,9 +70,9 @@
       waitsForPromise ->
         preview.renderMarkdown()

-    it "removes line decorations on rendered code blocks", ->
+    fit "removes line decorations on rendered code blocks", ->
       editor = preview.find("atom-text-editor[data-grammar='text plain null-grammar']")
-      decorations = plainEditor[0].getModel().getDecorations(class: 'cursor-line', type: 'line')
+      decorations = editor[0].getModel().getDecorations(class: 'cursor-line', type: 'line')
       expect(decorations.length).toBe 0

     describe "when the code block's fence name has a matching grammar", ->


more buffer space around histo canvas

more buffer space around histo canvas

--- @client/histogram-canvas.coffee
+++ @client/histogram-canvas.coffee
@@ -746,8 +746,9 @@
     @last_key = histocache_key

     # give the canvas extra space at the top so that overflow avatars on the top aren't cut off
-    @cut_off_buffer = @avatar_size / 2
-    @adjusted_height = @props.height + @cut_off_buffer
+    @cut_off_buffer = @props.height / 2 #@avatar_size / 2
+    @adjusted_height = @props.height +     @cut_off_buffer
+    @adjusted_width  = @props.width  + 2 * @cut_off_buffer

     proposal = fetch @props.histo_key

@@ -819,7 +820,7 @@
     @updates_needed[sprite.key] = 1

-  resize_canvas: (width, height, top, animate) ->
+  resize_canvas: (width, height, top, left, animate) ->
     canv = @refs.canvas
     @wake()

@@ -830,6 +831,7 @@
         target_width: width + 50
         target_height: height
         target_top: top
+        target_left: left

       @resize_canvas.ani ?= 0
       @resize_canvas.ani += 1
@@ -841,23 +843,28 @@
           width:  [parseInt(canv.style.width),  width + 50]
           height: [parseInt(canv.style.height), height]
           top:    [@current_top,    top]
-        onUpdate: do(ani) => ({ width, height, top }) =>
+          left:   [@current_left,   left]
+        onUpdate: do(ani) => ({ width, height, top, left }) =>
           if ani == @resize_canvas.ani
             @resizing_canvas.width = Math.round(width)
             @resizing_canvas.height = Math.round(height)
             @resizing_canvas.top = Math.round(top)
+            @resizing_canvas.left = Math.round(left)
         onComplete: do(ani) => =>
           if ani == @resize_canvas.ani
             @resizing_canvas = false

     else # immediate
-      if canv.width != width * DEVICE_PIXEL_RATIO || canv.height != height * DEVICE_PIXEL_RATIO || @current_top != top
+      if canv.width != width * DEVICE_PIXEL_RATIO || canv.height != height * DEVICE_PIXEL_RATIO || @current_top != top || @current_left != left
         canv.width = width * DEVICE_PIXEL_RATIO
         canv.height = height * DEVICE_PIXEL_RATIO
         canv.style.width = "#{width}px"
         canv.style.height = "#{height}px"
-        canv.style.transform = "translateY(#{top}px)"
+        canv.style.transform = "translate(#{left}px, #{top}px)"
+
         @current_top = top
+        @current_left = left
+
         @canvas_bounding_rect = null

@@ -884,12 +891,12 @@
     # initialize canvas, get things started
     if !@ctx
       @ctx = canvas.getContext('2d')
-      @resize_canvas @props.width, @adjusted_height, -@cut_off_buffer
+      @resize_canvas @adjusted_width, @adjusted_height, -@cut_off_buffer, -@cut_off_buffer
       @dirty_canvas = true

     # resize canvas if our target canvas size has changed
-    else if (canvas.width != @props.width  * DEVICE_PIXEL_RATIO || canvas.height != @adjusted_height * DEVICE_PIXEL_RATIO || @current_top != -@cut_off_buffer) &&
-            (!@resizing_canvas || @resizing_canvas.target_width != @props.width || @resizing_canvas.target_height != @adjusted_height || @resizing_canvas.target_top != -@cut_off_buffer)
+    else if (canvas.width != @adjusted_width * DEVICE_PIXEL_RATIO || canvas.height != @adjusted_height * DEVICE_PIXEL_RATIO || @current_top != -@cut_off_buffer || @current_left != -@cut_off_buffer) &&
+            (!@resizing_canvas || @resizing_canvas.target_width != @adjusted_width || @resizing_canvas.target_height != @adjusted_height || @resizing_canvas.target_top != -@cut_off_buffer || @resizing_canvas.target_left != -@cut_off_buffer)
       canvas_resize_needed = true

     opinion_views = fetch 'opinion_views'
@@ -914,7 +921,7 @@
       pos = histocache?.positions?[user.key]
       continue if !pos

-      x = Math.round pos[0]
+      x = Math.round pos[0] + @cut_off_buffer
       y = Math.round pos[1] + @cut_off_buffer # + @avatar_size / 2 # give spacing
       r = Math.round pos[2]
       width = height = r * 2
@@ -954,13 +961,13 @@

           sprite.img = canv

-
       if (sprite.x != x && sprite.target_x != x) || (sprite.y != y && sprite.target_y != y) || \
          (sprite.width != r * 2 && sprite.target_size != r * 2) || \
          (sprite.opacity != opacity)

         @animate_to(sprite, x, y, r, opacity)
         new_animation_needed = true
+

     if new_animation_needed
       @current_ani ?= 0
@@ -994,7 +1001,7 @@

     if canvas_resize_needed
-      @resize_canvas(@props.width, @adjusted_height, -@cut_off_buffer, true)
+      @resize_canvas(@adjusted_width, @adjusted_height, -@cut_off_buffer, -@cut_off_buffer, true)

     @wake()

@@ -1093,7 +1100,7 @@
   renderScene: ->
     ctx = @ctx
     if @resizing_canvas
-      @resize_canvas @resizing_canvas.width, @resizing_canvas.height, @resizing_canvas.top
+      @resize_canvas @resizing_canvas.width, @resizing_canvas.height, @resizing_canvas.top, @resizing_canvas.left

     ctx.clearRect(0, 0, @refs.canvas.width * DEVICE_PIXEL_RATIO, @refs.canvas.height * DEVICE_PIXEL_RATIO)


was not accessing value() of a dvl variable wrapping a function

--- src/html.coffee
+++ src/html.coffee
@@ -268,6 +268,9 @@
     text: selection
   }).value()

+  #used to handle the case when the developer has "null" in the dataset
+  noDataEverSelected = false
+  if selection.value() is null then noDataEverSelected = true

   valueOut.on('keydown', (->
     keyCode = d3.event.keyCode
@@ -280,8 +283,9 @@
       if not menuOpen.value()
         menuOpen.value(true)
       else
-        if selection.value() is null
+        if noDataEverSelected and selection.value() is null
           selection.value(data.value()[0])
+          noDataEverSelected = false
         else
         ##increment selection
           selectionIndex = 0
@@ -301,7 +305,8 @@
       userChar = String.fromCharCode(keyCode)
       userInputText.value(userChar)
       for datum in data.value()
-        if datum and datum.charAt(0) is userInputText.value()
+        window.datum = datum
+        if datum and label.value()(datum).charAt(0) is userInputText.value()
           selection.value(datum)
           break


Use jQuery.noConflict instantly

Use jQuery.noConflict instantly

--- src/init.coffee
+++ src/init.coffee
@@ -1,8 +1,9 @@
 @Infeedl ||= {}

 @Infeedl.init = ->
-  Infeedl.$ = jQuery.noConflict()
   Infeedl.$("[data-infeedl-placement]").each ->
     new Infeedl.Placement(this)

+
+Infeedl.$ = jQuery.noConflict()
 @Infeedl.init()


Fixed bug where comments were not highlighted.

For example, the `-- blah blah` in the following was not styled as a
comment:

```idris
foo : bar -> bin -- blah blah
```

--- grammars/language-idris.cson
+++ grammars/language-idris.cson
@@ -289,7 +289,7 @@
         name: 'entity.name.function.idris'
       4:
         name: 'keyword.operator.colon.idris'
-    end: "(;|(?<=[^\\s>])\\s*(?!->)\\s*$)"
+    end: "(;|(?=--)|(?<=[^\\s>])\\s*(?!->)\\s*$)"
     patterns:
       [
         {


Also add deprecated alias for DirectionLockStart

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -22,7 +22,7 @@
 # Add deprecated aliases
 Events.DragAnimationDidStart = Events.DragAnimationStart
 Events.DragAnimationDidEnd = Events.DragAnimationEnd
-
+Events.DirectionLockDidStart = Events.DirectionLockStart

 """


Log when we prevent keystrokes so that we can solve a crazy bug.

--- app/core/application.coffee
+++ app/core/application.coffee
@@ -19,6 +19,7 @@
   if event.keyCode is 8 and not elementAcceptsKeystrokes(event.srcElement or event.target)
     event.preventDefault()
   else if (key.ctrl or key.command) and not key.alt and event.keyCode in ctrlDefaultPrevented
+    console.debug "Prevented keystroke", key
     event.preventDefault()

 elementAcceptsKeystrokes = (el) ->


bugfix for automation and create instance

--- plugins/compute/app/assets/javascripts/compute/cloud_init_configuration.coffee
+++ plugins/compute/app/assets/javascripts/compute/cloud_init_configuration.coffee
@@ -3,7 +3,11 @@
   event.stopPropagation()
   button = $(event.target)
   action_path = $(event.target).data('automationScriptAction')
-  os_image_option = $('#server_image_id option:selected')
+
+  os_image_option = $('#server_vmware_image_id option:selected')
+  if $('#server_baremetal_image_id').val() != ""
+    os_image_option = $('#server_baremetal_image_id option:selected')
+
   checkOsType(button, os_image_option, action_path)

 @fetchLinuxScritp=(event) ->
@@ -43,6 +47,7 @@
   $( "#server_image_id" ).bind "change.automtion", () -> removeOsTypeButtons()

 @checkOsType = (button, os_image_option, action_path) ->
+
   os_image = os_image_option.data('vmwareOstype')

   # check empty image


Temporarily cache workflows in the project

--- app/pages/project/home.cjsx
+++ app/pages/project/home.cjsx
@@ -12,7 +12,16 @@

   propChangeHandlers:
     project: (project) ->
-      @promiseToSetState workflows: project.link 'workflows'
+      # TODO: Build this kind of caching into json-api-client.
+      if project._workflows?
+        @setState workflows: project._workflows
+      else
+        workflows = project.link 'workflows'
+
+        workflows.then (workflows) =>
+          project._workflows = workflows
+
+        @promiseToSetState {workflows}

   getInitialState: ->
     workflows: []


Add spec for Platforms#_setupFloorRow.

Add spec for Platforms#_setupFloorRow.

--- tests/platforms.coffee
+++ tests/platforms.coffee
@@ -135,3 +135,19 @@

       it 'resets row type for upcoming row', ->
         expect(vars.rowType).toBe 'empty'
+
+    describe '#_setupFloorRow', ->
+      vars = null
+
+      beforeEach ->
+        vars = { numCols: 13 }
+        platforms._setupFloorRow vars
+
+      it 'resets column indexes for upcoming row', ->
+        expect(vars.iColStart).toBe 0
+        expect(vars.iColEnd).toBe 12
+
+      it 'resets row data for upcoming row', ->
+        expect(vars.rowSpacing).toBeDefined()
+        expect(vars.rowTiles).toEqual []
+        expect(vars.rowType).toBe 'solid'


Fix empty queues

Fix empty queues

--- lib/domain_repository.coffee
+++ lib/domain_repository.coffee
@@ -56,6 +56,10 @@
     @aggregates.push aggregate

   @commit: (callback) =>
+    if @aggregates.length is 0
+      callback null
+      return
+
     aggregateQueue = async.queue (aggregate, aggregateTaskCallback) =>
       firstEvent = aggregate.appliedEvents.shift()

@@ -74,7 +78,7 @@
         queue.drain = aggregateTaskCallback
         queue.push [firstEvent]
       else
-        aggregateTaskCallback(null)
+        aggregateTaskCallback null

     , Infinity # TODO determine if it is safe to treat all aggregates in parallel?

@@ -93,6 +97,10 @@

   @publishEvent: (event, callback) =>
     eventHandlers = @eventHandlers[event.name] or []
+    if eventHandlers.length is 0
+      callback null
+      return
+
     queue = async.queue (eventHandler, eventHandlerTaskCallback) =>
       eventHandler event, (err) ->
         if err?


decrease the number of times crumbing is called

--- src/components/task-teacher-review/breadcrumbs.cjsx
+++ src/components/task-teacher-review/breadcrumbs.cjsx
@@ -20,8 +20,11 @@
     title: React.PropTypes.string.isRequired
     courseId: React.PropTypes.string.isRequired

+  getInitialState: ->
+    crumbs: @getCrumableCrumbs()
+
   render: ->
-    crumbs = @getCrumableCrumbs()
+    {crumbs} = @state
     {currentStep, goToStep, title, courseId} = @props

     stepButtons = _.map crumbs, (crumb) ->


fix tests

fix tests

--- lib/notifications-log.coffee
+++ lib/notifications-log.coffee
@@ -50,7 +50,7 @@

   destroy: ->
     @element.remove()
-    item.destroy() for item in logItems
+    item.destroy() for item in @logItems
     @emitter.emit 'did-destroy'

   getElement: -> @element


serialize history

serialize history

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -54,6 +54,8 @@
     attached: @hasParent()
     useRegex: @useRegex
     caseInsensitive: @caseInsensitive
+    findHistory: @findHistory.serialize()
+    pathsHistory: @pathsHistory.serialize()

   handleEvents: ->
     rootView.command 'project-find:show', => @attach()


check if lolflavor version exists #115

check if lolflavor version exists #115

--- lib/sources/lolflavor.coffee
+++ lib/sources/lolflavor.coffee
@@ -112,7 +112,10 @@
   hlp.request 'http://www.lolflavor.com/champions/Ahri/Recommended/Ahri_lane_scrape.json', (err, body) ->
     return step(null, "#{T.t('unknown')}") if (err)

-    version = body.title.split(' ')[3]
+    body_title = body?.title
+    return step(null, "#{T.t('unknown')}") if !body_title
+
+    version = body_title.split(' ')[3]
     step(null, version)


add semlicolons on import statements (TODO - make configurable or take from lint configs)

--- lib/typescript-import.coffee
+++ lib/typescript-import.coffee
@@ -96,9 +96,9 @@
         fileFolder = path.resolve(filePath + '/..');
         relative = path.relative(fileFolder, location).replace(/\.(js|ts)$/, '');
         if(defaultImport)
-          importClause = "import #{selection} from './#{relative}'\n"
+          importClause = "import #{selection} from './#{relative};'\n"
         else
-          importClause = "import {#{selection}} from './#{relative}'\n"
+          importClause = "import {#{selection}} from './#{relative};'\n"
         @addImportStatement(importClause)
 #        editor.insertText(selection + "\nimport #{selection} from './#{relative}'")
       else


Prefer text wrap between message and tags rather than within message

--- lib/violation-tooltip.coffee
+++ lib/violation-tooltip.coffee
@@ -9,7 +9,7 @@
     template: '
' +
                 '

' +
                 '
' +
-                  '' +
+                  '<wbr>' +
                   '

' +
                 '
' +
               '
'


Fixing script tag execution

Fixing script tag execution

Related to issue #54,

returned script tags were not being evaluated. New function added called 'executeScriptTags()'
called in changePage() before the page:change event is fired so that any binded listeners added from script tag execution can listen to said event.

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -52,10 +52,13 @@
 changePage = (title, body) ->
   document.title = title
   document.documentElement.replaceChild body, document.body
-
+  executeScriptTags()
   currentState = window.history.state
   triggerEvent 'page:change'

+executeScriptTags = ->
+  scripts = document.body.getElementsByTagName 'script'
+  eval(script.innerHTML) for script in scripts

 reflectNewUrl = (url) ->
   if url isnt document.location.href


scripts are now only executed in xhr request load, not from history fetch

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -63,10 +63,10 @@
 constrainPageCacheTo = (limit) ->
   delete pageCache[currentState.position - limit]

-changePage = (title, body) ->
+changePage = (title, body, runScripts) ->
   document.title = title
   document.documentElement.replaceChild body, document.body
-  executeScriptTags()
+  executeScriptTags() if runScripts
   currentState = window.history.state
   triggerEvent 'page:change'

@@ -124,7 +124,7 @@

 extractTitleAndBody = (doc) ->
   title = doc.querySelector 'title'
-  [ title?.textContent, doc.body ]
+  [ title?.textContent, doc.body, 'runScripts' ]

 browserCompatibleDocumentParser = ->
   createDocumentUsingParser = (html) ->


Fix: context.protected_apps is always defined for _app template when paginating

fixes cupcake/bugs#39

--- lib/assets/javascripts/views/apps.js.coffee
+++ lib/assets/javascripts/views/apps.js.coffee
@@ -29,7 +29,10 @@
     fragment = document.createDocumentFragment()

     for model in models
-      Marbles.DOM.appendHTML(fragment, template.render(app: model))
+      Marbles.DOM.appendHTML(fragment, template.render(
+        app: model
+        protected_apps: TentAdmin.config.protected_apps || []
+      ))

     @bindViews(fragment)
     ul.appendChild(fragment)


fix base64 padding if needed

fix base64 padding if needed

--- zechat/static/crypto.coffee
+++ zechat/static/crypto.coffee
@@ -1,5 +1,7 @@
 zc.format_pem = (key, title) ->
   rv = "-----BEGIN " + title + "-----\n"
+  while key.length % 4 != 0
+    key = key + '='
   while key
     rv += key.slice(0, 64) + "\n"
     key = key.slice(64)


Arrange.

Arrange.

--- src/main.coffee
+++ src/main.coffee
@@ -55,6 +55,7 @@
             Object.keys(node).forEach((m) -> checkTreeNode(node[m]))
     checkTreeNode(parsed)
 #===============================================================================
+# "386389655257694535" is to avoid naming conflicts.
 writeOutput = ->
     modsBodyStr = mods.map((mod) ->
         """
@@ -70,8 +71,9 @@

         """
     ).join(",\n")
-    # "386389655257694535" is to avoid naming conflict.
     bundleStr = """
+        // `{}` is to guarantee that any subsequent `mod.result` assignment will make
+        // the variable different from the initial value.
         var initialModResult_386389655257694535 = {};

         var mods_386389655257694535 = [
@@ -86,17 +88,15 @@
                 var theExports = {};
                 var theModule = {exports: theExports};
                 var theRequire = function(name) {
-                    mod.result = theModule.exports;
+                    mod.result = theModule.exports; // for preventing infinite loops
                     var newIndex = mod.nameIndexes[name];
                     if (mods[newIndex].result === initialModResult) {
-                        return run(newIndex);
-                    } else {
-                        return mods[newIndex].result;
+                        run(newIndex);
                     }
+                    return mods[newIndex].result;
                 };
                 mod.fun.apply(theExports, [theExports, theModule, theRequire]);
-                mod.result = theModule.exports;
-                return mod.result;
+                mod.result = theModule.exports; // for caching
             };
             run(0);
         })();


Fix for server reset

Fix for server reset

--- src/main.coffee
+++ src/main.coffee
@@ -138,6 +138,7 @@
       for key, value of config.xmpp
         componentConfig[key] = value
       componentConfig.jid = config.advertiseComponents[index]
+      componentConfig.reconnect = true
       connection = new xmpp.Component(componentConfig)
       connection.on "stanza", (stanza) =>
           # Just debug output:


the output area now updates when the input area changes

--- src/main.coffee
+++ src/main.coffee
@@ -1,3 +1,7 @@
+code = '';
+textarea = undefined;
+outputarea = undefined;
+
 window.App = {
   hello: -> (
     textarea = $('<textarea rows="15" cols="100">');
@@ -6,5 +10,21 @@
     outputarea = $('<div>');
     outputarea.append('(this is the output area)');
     outputarea.appendTo('#App');
+
+    setInterval(possiblyUpdate, 2000);
   )
-}
+};
+
+possiblyUpdate = -> (
+  if(code == textarea.val())
+    return;
+
+  console.log('blah');
+  code = textarea.val();
+  execute(code);
+);
+
+execute = (code) -> (
+  s = code.split('').reverse().join('');
+  outputarea.text(s);
+);