remove global notebook object

remove global notebook object

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -11,52 +11,48 @@

   ignore = new Object

-  default_options = {}
   define_parameters = true

-  $document = null
   $file_picker = null

   notebook_content_type = 'application/x-lead-notebook'

   graphite.load_docs()
-
-  input_number = 1

   init_codemirror = ->
     CodeMirror.keyMap.lead = ed.key_map
     $.extend CodeMirror.commands, ed.commands

-  notebook = []
-
-  nb =
-    get_input_cell_by_number: (notebook, number) ->
-      for cell in notebook
-        return cell if cell.input_number == number
+  create_notebook = ->
+    notebook = []
+    notebook.input_number = 1
+    notebook.default_options = {}
+    notebook.$document = $ '
'
+    notebook

   export_notebook = (current_cell) ->
     lead_js_version: 0
-    cells: notebook.filter((cell) -> cell != current_cell).map (cell) ->
+    cells: current_cell.notebook.filter((cell) -> cell != current_cell).map (cell) ->
       type: 'input'
       value: cell.editor.getValue()

-  import_notebook = (notebook, options) ->
-    for cell in notebook.cells
+  import_notebook = (notebook, imported, options) ->
+    for cell in imported.cells
       if cell.type is 'input'
         if options.run
           run_in_available_context cell.value
         else
-          add_context cell.value
-
-  clear_notebook = ->
-    $document.empty()
-    notebook = []
+          add_context notebook, cell.value
+
+  clear_notebook = (notebook) ->
+    notebook.$document.empty()
+    notebook.length = 0

   input_cell_at_offset = (cell, offset) ->
-    index = notebook.indexOf cell
-    notebook[index + offset]
-
-  get_available_input_cell = ->
+    index = cell.notebook.indexOf cell
+    cell.notebook[index + offset]
+
+  get_available_input_cell = (notebook) ->
     last = notebook[notebook.length - 1]
     if last?.is_clean()
       return last
@@ -64,17 +60,17 @@
       return null

   remove_cell = (cell) ->
-    index = notebook.indexOf cell
+    index = cell.notebook.indexOf cell
     cell.$el.remove()
-    notebook.splice index, 1
-
-  add_context = (code='') ->
-    cell = get_available_input_cell()
+    cell.notebook.splice index, 1
+
+  add_context = (notebook, code='') ->
+    cell = get_available_input_cell notebook
     if cell?
       cell.editor.setValue code
     else
-      cell = create_input_cell code
-      $document.append cell.$el
+      cell = create_input_cell notebook, code
+      notebook.$document.append cell.$el
       cell.rendered()
       notebook.push cell

@@ -83,24 +79,20 @@
     editor.setCursor(line: editor.lineCount() - 1)
     cell

-  run_in_available_context = (code) ->
-    add_context(code).run()
-    add_context()
+  run_in_available_context = (notebook, code) ->
+    add_context(notebook, code).run()
+    add_context notebook

   # Add an input cell above the last input cell
   run_in_info_context = (current_cell, code) ->
-    cell = create_input_cell code
-    if current_cell?
-      current_cell.$el.before cell.$el
-      index = notebook.indexOf cell
-    else
-      $document.append cell.$el
-      index = -1
+    cell = create_input_cell current_cell.notebook, code
+    current_cell.$el.before cell.$el
+    index = notebook.indexOf cell
     cell.rendered()
-    notebook.splice index, 0, cell
+    current_cell.notebook.splice index, 0, cell
     cell.run code

-  create_input_cell = (code) ->
+  create_input_cell = (notebook, code) ->
     $el = $ '
'
     $code = $ '
'
     $el.append $code
@@ -114,6 +106,7 @@
       gutters: ['error']

     context =
+      notebook: notebook
       used: false
       editor: editor
       rendered: -> editor.refresh()
@@ -124,7 +117,7 @@
         context.used = true
         context.output_cell?.$el.remove()
         context.output_cell = run context, editor.getValue()
-        context.input_number = input_number++
+        context.input_number = notebook.input_number++
         context.$el.attr 'data-cell-number', context.input_number

     editor.lead_cell = context
@@ -162,6 +155,10 @@

     context

+  get_input_cell_by_number = (notebook, number) ->
+    for cell in notebook
+      return cell if cell.input_number == number
+
   bind_cli = (run_context) ->
     bind_op = (op) ->
       bound = (args...) ->
@@ -214,10 +211,12 @@
         $target.append $item
         $item

+    notebook = input_cell.notebook
     run_context =
+      notebook: notebook
       ops: ops
       current_options: {}
-      default_options: default_options
+      default_options: notebook.default_options
       output: output $el
       success: ->
         scroll_to_result $top
@@ -225,9 +224,9 @@
       failure: ->
         scroll_to_result $top
         ignore
-      set_code: add_context
-      run: run_in_available_context
-      clear_output: -> clear_notebook()
+      set_code: (code) -> add_context notebook, code
+      run: (code) -> run_in_available_context notebook, code
+      clear_output: -> clear_notebook notebook
       previously_run: -> input_cell_at_offset(input_cell, -1).editor.getValue()
       hide_input: -> remove_cell input_cell
       value: (value) -> _lead_cli_value: value
@@ -243,7 +242,7 @@
         link.click()
         @output link
       get_input_value: (number) ->
-        cell = nb.get_input_cell_by_number notebook, number
+        cell = get_input_cell_by_number notebook, number
         cell?.editor.getValue()

       async: (fn) ->
@@ -331,9 +330,9 @@
       [_..., extension] = file.filename.split '.'
       if extension is 'coffee'
         if options.run
-          run_in_available_context file.content
+          run_in_available_context run_context.notebook, file.content
         else
-          add_context file.content
+          add_context run_context.notebook, file.content
       else
         try
           imported = JSON.parse file.content
@@ -344,7 +343,7 @@
         unless version?
           run_context.cli.error "File #{file.filename} isn't a lead.js notebook"
           return
-        import_notebook imported, options
+        import_notebook run_context.notebook, imported, options

   load_file = (run_context, file) ->
     if file.type.indexOf('image') < 0
@@ -357,12 +356,16 @@

       reader.readAsText file

-  nb =
+  exports =
     init_editor: ->
       init_codemirror()
       $document = $ '#document'
       $file_picker = $ '#file'

+
+      notebook = create_notebook()
+      $document.append notebook.$document
+
       $file_picker.on 'change', (e) ->
         for file in e.target.files
           load_file opening_run_context, file
@@ -373,24 +376,24 @@

       rc = localStorage.lead_rc
       if rc?
-        run_in_available_context rc
+        run_in_available_context notebook, rc

       uri = URI location.href
       fragment = uri.fragment()
       if fragment.length > 0 and fragment[0] == '/'
         id = fragment[1..]
-        run_in_available_context "gist #{JSON.stringify id}, run: true; quiet"
+        run_in_available_context notebook, "gist #{JSON.stringify id}, run: true; quiet"
       else
         program = if location.search isnt ''
           atob decodeURIComponent location.search[1..]
         else
           'intro'

-        run_in_available_context program
+        run_in_available_context notebook, program

     run: (cell) ->
       cell.run()
-      add_context()
+      add_context cell.notebook

     handle_file: handle_file

@@ -411,4 +414,4 @@
       else
         false

-  nb
+  exports


fix if header has no num value

fix if header has no num value

--- src/opensong.coffee
+++ src/opensong.coffee
@@ -116,7 +116,7 @@
     else
       char

-  abbArr.pop() unless abbArr[1]? # remove num if empty
+  abbArr.pop() if abbArr[1] is "" # remove num if empty
   abbArr.join " "

 ###


implemented prestige parsing for activities, status, eta and dest

--- src/prestige.coffee
+++ src/prestige.coffee
@@ -1,4 +1,4 @@
-{find} = require 'underscore'
+{reduce} = require 'underscore'
 moment = require 'moment-timezone'
 {titleCase} = require 'change-case'
 {ShipperClient} = require './shipper'
@@ -15,44 +15,54 @@
     return cb(error: 'missing events') unless response['TrackingEventHistory']?
     cb null, response

-  presentAddress: (address) ->
+  ADDR_ATTRS = ['City', 'State', 'Zip']
+
+  presentAddress: (prefix, event) ->
+    return unless event?
+    address = reduce ADDR_ATTRS, ((d, v) ->
+      d[v]=event["#{prefix}#{v}"]
+      return d
+    ), {}
     city = address['City']
     stateCode = address['State']
-    postalCode = address['PostalCode']
-    countryCode = address['Country']
-    @presentLocation {city, stateCode, countryCode, postalCode}
+    postalCode = address['Zip']
+    @presentLocation {city, stateCode, postalCode}

   STATUS_MAP =
-    'Released': ShipperClient.STATUS_TYPES.DELIVERED
-    'Delivered': ShipperClient.STATUS_TYPES.DELIVERED
-    'OutForDelivery': ShipperClient.STATUS_TYPES.OUT_FOR_DELIVERY
-    'Arrived': ShipperClient.STATUS_TYPES.EN_ROUTE
-    'Received': ShipperClient.STATUS_TYPES.EN_ROUTE
-    'OrderReceived': ShipperClient.STATUS_TYPES.SHIPPING
-    'OrderCreated': ShipperClient.STATUS_TYPES.SHIPPING
+    301: ShipperClient.STATUS_TYPES.DELIVERED
+    302: ShipperClient.STATUS_TYPES.OUT_FOR_DELIVERY
+    101: ShipperClient.STATUS_TYPES.SHIPPING

   presentStatus: (eventType) ->
-    STATUS_MAP[eventType] if eventType?
+    codeStr = eventType.match('EVENT_(.*)$')?[1]
+    return unless codeStr?.length
+    eventCode = parseInt codeStr
+    return if isNaN eventCode
+    status = STATUS_MAP[eventCode]
+    return status if status?
+    return ShipperClient.STATUS_TYPES.EN_ROUTE if (eventCode < 300 and eventCode > 101)

   getActivitiesAndStatus: (shipment) ->
     activities = []
     status = null
     rawActivities = shipment?['TrackingEventHistory']
     for rawActivity in rawActivities or []
-      location = @presentAddress rawActivity
-      dateTime = rawActivity?['DateTime']
-      timestamp = moment("#{dateTime}Z").toDate() if dateTime?
-      details = rawActivity?['EventShortText']
+      location = @presentAddress 'EL', rawActivity
+      dateTime = "#{rawActivity?['serverDate']} #{rawActivity?['serverTime']}"
+      timestamp = moment("#{dateTime} +00:00").toDate()
+      details = rawActivity?['EventCodeDesc']
       if details? and location? and timestamp?
         activity = {timestamp, location, details}
         activities.push activity
       if !status
-        status = @presentStatus rawActivity?['EventType']
+        status = @presentStatus rawActivity?['EventCode']
     {activities, status}

   getEta: (shipment) ->
-    return unless shipment?['EstimatedDeliveryDate']?
-    moment("#{shipment['EstimatedDeliveryDate']}T00:00:00Z").toDate()
+    eta = shipment?['TrackingEventHistory']?[0]?['EstimatedDeliveryDate']
+    return unless eta?.length
+    eta = "#{eta} 00:00 +00:00"
+    moment(eta, 'MM/DD/YYYY HH:mm ZZ').toDate()

   getService: (shipment) ->

@@ -65,13 +75,11 @@
     weight

   getDestination: (shipment) ->
-    destination = shipment?['Destination']
-    return unless destination?
-    @presentAddress destination
+    @presentAddress 'PD', shipment?['TrackingEventHistory']?[0]

   requestOptions: ({trackingNumber}) ->
     method: 'GET'
-    uri: "http://www.lasership.com/track/#{trackingNumber}/json"
+    uri: "http://www.prestigedelivery.com/TrackingHandler.ashx?trackingNumbers=#{trackingNumber}"

 module.exports = {PrestigeClient}


Set all tools at once in Plot.add_tools()

--- bokehjs/src/coffee/models/plots/plot.coffee
+++ bokehjs/src/coffee/models/plots/plot.coffee
@@ -750,7 +750,8 @@
   add_tools: (tools...) ->
     for tool in tools
       tool.plot = this
-      @set("tools", @get("tools").concat([tool]))
+
+    @set("tools", @get("tools").concat(tools))

   @mixins ['line:outline_', 'text:title_', 'fill:background_', 'fill:border_']


Fix test for IE8; typeof detachEvent is 'object' which breaks spies

--- tests/batman/view/simple_rendering_test.coffee
+++ tests/batman/view/simple_rendering_test.coffee
@@ -621,8 +621,13 @@
       ok listeners.click instanceof Batman.Set
       ok !listeners.click.isEmpty()

-      removeFn = if Batman.DOM.hasAddEventListener then 'removeEventListener' else 'detachEvent'
-      spy = spyOn n, removeFn
+      if Batman.DOM.hasAddEventListener
+        spy = spyOn n, 'removeEventListener'
+      else
+        # Spoof detachEvent because typeof detachEvent is 'object' in IE8, and
+        # spies break because detachEvent.call blows up
+        n.detachEvent = ->
+        spy = spyOn n, 'detachEvent'

       Batman.DOM.removeNode n


[LF/WF] [82773168] Delete unused spec

[LF/WF] [82773168] Delete unused spec

--- test/spec/example_spec.coffee
+++ test/spec/example_spec.coffee
@@ -1,12 +0,0 @@
-define [
-  'jquery'
-  'browserdetect/example'
-], (
-  $
-  Example
-) ->
-
-  describe "example", ->
-    it "should set the param when given one", () ->
-      example = new Example(1)
-      expect(example.param).toBe(1)


Refactor components that are waiting to be listed in one place to prevent race conditions

--- src/steroids.coffee
+++ src/steroids.coffee
@@ -66,11 +66,14 @@
 # Valid values are subclasses of Bridge
 window.steroids.nativeBridge = Bridge.getBestNativeBridge()

+# All compnents that perform async operations are here
 window.steroids.waitingForComponents.push("App")
+window.steroids.waitingForComponents.push("Events.focuslisteners")
+window.steroids.waitingForComponents.push("Events.initialVisibility")
+
+
 window.steroids.app = new App

-window.steroids.waitingForComponents.push("Events.focuslisteners")
-window.steroids.waitingForComponents.push("Events.initialVisibility")
 Events.extend()

 window.steroids.layers = new LayerCollection


Shorten echo

Shorten echo

--- src/sys/echo.coffee
+++ src/sys/echo.coffee
@@ -1,2 +1,2 @@
-module.exports = ->
-  wsh.Echo [].slice.apply(arguments).join ' '
+module.exports = (args...)->
+  wsh.Echo args.join ' '


no need to slice args from arguments

no need to slice args from arguments

--- src/template.coffee
+++ src/template.coffee
@@ -152,12 +152,12 @@
         # add self closing tag behavior
         # some of the html tags dont need a closing tag
         end_tag = Tag::end
-        @xml.Tag::end = (args...) ->
+        @xml.Tag::end = ->
             if opts.self_closing is on or opts.self_closing.match @name
-                end_tag.call this, args...
+                end_tag.call this, arguments...
             else
                 @text "", force:yes if @isempty
-                end_tag.call this, args...
+                end_tag.call this, arguments...

         ##
         # pipe events through and add hooks to be able to alter event behavior
@@ -165,11 +165,11 @@
         # override method "on#{event}" to access the hook
         EVENTS.forEach (event) =>
             # build hook
-            @["on#{event}"] = (args...) =>
-                @emit event, args...
+            @["on#{event}"] = =>
+                @emit event, arguments...
             # listen
-            @xml.on event, (args...) =>
-                @["on#{event}"](args...)
+            @xml.on event, =>
+                @["on#{event}"](arguments...)

         ##
         # start the templating process after user listened for events


Refactor update tests

Refactor update tests

--- test/unit/test_updates.coffee
+++ test/unit/test_updates.coffee
@@ -260,92 +260,162 @@
     $('#qunit-fixture').append(@container)
     @container.taskList()

+    @setSourcePosition = (item, pos) =>
+      item.attr('data-sourcepos', pos)
+
+    @onChanged = (assert) =>
+      utils =
+      test: (fn) =>
+        done = assert.async()
+        @field.on 'tasklist:changed', (event) =>
+          fn event
+          done()
+      eventHas: (name, value) =>
+        utils.test (event) =>
+          assert.equal event.detail[name], value
+      fieldIs: (value) =>
+        utils.test () =>
+          assert.equal @field.val(), value
+
   afterEach: ->
     $(document).off 'tasklist:changed'

-assertTaskComplete =(assert, field, changes, taskItem, taskCheckbox, sourcepos) ->
-  done = assert.async()
-  assert.expect 3
-
-  taskItem.attr('data-sourcepos', sourcepos) if sourcepos
-
-  field.on 'tasklist:changed', (event) =>
-    assert.ok event.detail.checked
-    assert.equal event.detail.index, taskItem.expectedIndex
-    assert.equal field.val(), changes
-    done()
-
-  taskCheckbox.click()
-
-assertTaskIncomplete =(assert, field, changes, taskItem, taskCheckbox, sourcepos) ->
-  done = assert.async()
-  assert.expect 3
-
-  taskItem.attr('data-sourcepos', sourcepos) if sourcepos
-
-  field.on 'tasklist:changed', (event) =>
-    assert.ok !event.detail.checked
-    assert.equal event.detail.index, taskItem.expectedIndex
-    assert.equal field.val(), changes
-    done()
-
-  taskCheckbox.click()
-
 QUnit.test "updates the source, marking the incomplete item as complete", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toIncomplete, @incompleteItem, @incompleteCheckbox, null)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @incompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toIncomplete)
+
+  @incompleteCheckbox.click()

 QUnit.test "updates the source, marking the incomplete item as complete (sourcepos)", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toIncomplete, @incompleteItem, @incompleteCheckbox, @incompleteItemSourcePos)
+  @setSourcePosition(@incompleteItem, @incompleteItemSourcePos)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @incompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toIncomplete)
+
+  @incompleteCheckbox.click()

 QUnit.test "updates the source, marking the complete item as incomplete", (assert) ->
-  assertTaskIncomplete(assert, @field, @changes.toComplete, @completeItem, @completeCheckbox, null)
+  @onChanged(assert).eventHas('checked', false)
+  @onChanged(assert).eventHas('index', @completeItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toComplete)
+
+  @completeCheckbox.click()

 QUnit.test "updates the source, marking the complete item as incomplete (sourcepos)", (assert) ->
-  assertTaskIncomplete(assert, @field, @changes.toComplete, @completeItem, @completeCheckbox, @completeItemSourcePos)
+  @setSourcePosition(@completeItem, @completeItemSourcePos)
+  @onChanged(assert).eventHas('checked', false)
+  @onChanged(assert).eventHas('index', @completeItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toComplete)
+
+  @completeCheckbox.click()

 # See: https://github.com/github/task-lists/pull/14
 QUnit.test "updates the source for items with non-breaking spaces", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toIncompleteNBSP, @incompleteNBSPItem, @incompleteNBSPCheckbox, null)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @incompleteNBSPItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toIncompleteNBSP)
+
+  @incompleteNBSPCheckbox.click()

 # See: https://github.com/github/task-lists/pull/14
 QUnit.test "updates the source for items with non-breaking spaces (sourcepos)", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toIncompleteNBSP, @incompleteNBSPItem, @incompleteNBSPCheckbox, @incompleteNBSPItemSourcePos)
+  @setSourcePosition(@incompleteNBSPItem, @incompleteNBSPItemSourcePos)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @incompleteNBSPItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toIncompleteNBSP)
+
+  @incompleteNBSPCheckbox.click()

 QUnit.test "updates the source of a quoted item, marking the incomplete item as complete", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toQuotedIncomplete, @quotedIncompleteItem, @quotedIncompleteCheckbox, null)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @quotedIncompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toQuotedIncomplete)
+
+  @quotedIncompleteCheckbox.click()

 QUnit.test "updates the source of a quoted item, marking the incomplete item as complete (sourcepos)", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toQuotedIncomplete, @quotedIncompleteItem, @quotedIncompleteCheckbox, @quotedIncompleteItemSourcePos)
+  @setSourcePosition(@quotedIncompleteItem, @quotedIncompleteItemSourcePos)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @quotedIncompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toQuotedIncomplete)
+
+  @quotedIncompleteCheckbox.click()

 QUnit.test "updates the source of a quoted item, marking the complete item as incomplete", (assert) ->
-  assertTaskIncomplete(assert, @field, @changes.toQuotedComplete, @quotedCompleteItem, @quotedCompleteCheckbox, null)
+  @onChanged(assert).eventHas('checked', false)
+  @onChanged(assert).eventHas('index', @quotedCompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toQuotedComplete)
+
+  @quotedCompleteCheckbox.click()

 QUnit.test "updates the source of a quoted item, marking the complete item as incomplete (sourcepos)", (assert) ->
-  assertTaskIncomplete(assert, @field, @changes.toQuotedComplete, @quotedCompleteItem, @quotedCompleteCheckbox, @quotedCompleteItemSourcePos)
+  @setSourcePosition(@quotedCompleteItem, @quotedCompleteItemSourcePos)
+  @onChanged(assert).eventHas('checked', false)
+  @onChanged(assert).eventHas('index', @quotedCompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toQuotedComplete)
+
+  @quotedCompleteCheckbox.click()

 QUnit.test "updates the source of a quoted quoted item, marking the incomplete item as complete", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toInnerIncomplete, @innerIncompleteItem, @innerIncompleteCheckbox, null)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @innerIncompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toInnerIncomplete)
+
+  @innerIncompleteCheckbox.click()

 QUnit.test "updates the source of a quoted quoted item, marking the incomplete item as complete (sourcepos)", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toInnerIncomplete, @innerIncompleteItem, @innerIncompleteCheckbox, @innerIncompleteItemSourcePos)
+  @setSourcePosition(@innerIncompleteItem, @innerIncompleteItemSourcePos)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @innerIncompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toInnerIncomplete)
+
+  @innerIncompleteCheckbox.click()

 QUnit.test "updates the source of a quoted quoted item, marking the complete item as incomplete", (assert) ->
-  assertTaskIncomplete(assert, @field, @changes.toInnerComplete, @innerCompleteItem, @innerCompleteCheckbox, null)
+  @onChanged(assert).eventHas('checked', false)
+  @onChanged(assert).eventHas('index', @innerCompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toInnerComplete)
+
+  @innerCompleteCheckbox.click()

 QUnit.test "updates the source of a quoted quoted item, marking the complete item as incomplete (sourcepos)", (assert) ->
-  assertTaskIncomplete(assert, @field, @changes.toInnerComplete, @innerCompleteItem, @innerCompleteCheckbox, @innerCompleteItemSourcePos)
+  @setSourcePosition(@innerCompleteItem, @innerCompleteItemSourcePos)
+  @onChanged(assert).eventHas('checked', false)
+  @onChanged(assert).eventHas('index', @innerCompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toInnerComplete)
+
+  @innerCompleteCheckbox.click()

 QUnit.test "updates the source of an ordered list item, marking the incomplete item as complete", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toOrderedIncomplete, @orderedIncompleteItem, @orderedIncompleteCheckbox, null)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @orderedIncompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toOrderedIncomplete)
+
+  @orderedIncompleteCheckbox.click()

 QUnit.test "updates the source of an ordered list item, marking the incomplete item as complete (sourcepos)", (assert) ->
-  assertTaskComplete(assert, @field, @changes.toOrderedIncomplete, @orderedIncompleteItem, @orderedIncompleteCheckbox, @orderedIncompleteItemSourcePos)
+  @setSourcePosition(@orderedIncompleteItem, @orderedIncompleteItemSourcePos)
+  @onChanged(assert).eventHas('checked', true)
+  @onChanged(assert).eventHas('index', @orderedIncompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toOrderedIncomplete)
+
+  @orderedIncompleteCheckbox.click()

 QUnit.test "updates the source of an ordered list item, marking the complete item as incomplete", (assert) ->
-  assertTaskIncomplete(assert, @field, @changes.toOrderedComplete, @orderedCompleteItem, @orderedCompleteCheckbox, null)
+  @onChanged(assert).eventHas('checked', false)
+  @onChanged(assert).eventHas('index', @orderedCompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toOrderedComplete)
+
+  @orderedCompleteCheckbox.click()

 QUnit.test "updates the source of an ordered list item, marking the complete item as incomplete (sourcepos)", (assert) ->
-  assertTaskIncomplete(assert, @field, @changes.toOrderedComplete, @orderedCompleteItem, @orderedCompleteCheckbox, @orderedCompleteItemSourcePos)
+  @setSourcePosition(@orderedCompleteItem, @orderedCompleteItemSourcePos)
+  @onChanged(assert).eventHas('checked', false)
+  @onChanged(assert).eventHas('index', @orderedCompleteItem.expectedIndex)
+  @onChanged(assert).fieldIs(@changes.toOrderedComplete)
+
+  @orderedCompleteCheckbox.click()

 QUnit.test "update ignores items that look like Task List items but lack list prefix", (assert) ->
   assertItemsLackListPrefix(assert, null, null)


fix itemView

fix itemView

--- Resources/windows/favorit.coffee
+++ Resources/windows/favorit.coffee
@@ -1,4 +1,4 @@
-currentWindow = Ti.UI.currentWindow
+window = Ti.UI.currentWindow
 rows = [
     {title:'Row 1', hasChild:true},
     {title:'Row 2', hasDetail:true},
@@ -6,13 +6,109 @@
     {title:'Row 4'}
 ]

-itemView = Ti.UI.createTableView
-    data: rows
+window.barColor = '#385292'
+
+searchBar = Ti.UI.createSearchBar
+    barColor:'#285292'
+    showCancel:false
+
+rowData = []
+# header
+headerRow = Ti.UI.createTableViewRow
+    backgroundColor:'#576996'
+    selectedBackgroundColor:'#385292'
+    height:40
+
+clickLabel = Ti.UI.createLabel
+    text:'Click different parts of the row'
+    color:'#fff'
+    textAlign:'center'
+    font:{fontSize:14}
+    width:'auto'
+    height:'auto'
+
+headerRow.className = 'header'
+headerRow.add clickLabel
+rowData.push headerRow
+
+layout = (c) ->
+    row = Ti.UI.createTableViewRow
+        selectedBackgroundColor:'#fff'
+        height:100
+        className:'datarow'
+
+    # add image
+    photo = Ti.UI.createView
+        backgroundImage:'../KS_nav_views.png'
+        top:5
+        left:10
+        width:50
+        height:50
+    photo.rowNum = c
+    photo.addEventListener 'click', (e) ->
+        Ti.API.info 'clikc it!'
+    row.add photo

-itemView.addEventListener 'click', (e) ->
-    index    = e.index
-    section  = e.section
-    row      = e.row
-    rowdata  = e.rowData
+    # add label
+    user = Ti.UI.createLabel
+        color:'#576996'
+        font:{fontSize:16, fontWeight:'bold', fontFamily:'Arial'}
+        left:70
+        top:2
+        height:30
+        width:200
+        text:'Fred Smith ' + c
+    user.rowNum = c
+    user.addEventListener 'click', (e) ->
+        Ti.API.info 'click it!'
+    row.add user

-currentWindow.add itemView+    # add label2
+    comment = Ti.UI.createLabel
+        color:'#222'
+        font:{fontSize:16, fontWeight:'normal', fontFamily:'Arial'}
+        left:70
+        top:21
+        height:50
+        width:200
+        text:'Got some fresh fruit, conducted some business, took a nap'
+    row.add comment
+
+    # add View
+    calendar = Ti.UI.createView
+        backgroundImage:'../KS_nav_views.png'
+        bottom:2
+        left:70
+        width:32
+        height:32
+    calendar.rowNum = c
+    calendar.addEventListener 'click', (e) ->
+        Ti.API.info 'click it!'
+    row.add calendar
+
+    # add Button
+    button = Ti.UI.createView
+        backgroundImage:'../KS_nav_views.png'
+        top:35
+        right:5
+        width:36
+        height:34
+    button.rowNum = c
+    button.addEventListener 'click', (e) ->
+        Ti.API.info 'click it!'
+    row.add button
+
+    # add Label2
+    date = Ti.UI.createLabel
+        text:'posted on 3/11'
+    row.add date
+
+    rowData.push row
+
+layout c for c in [1..50]
+
+tableView = Titanium.UI.createTableView
+    data:rowData
+    search: searchBar
+
+window.add tableView


Use BsClasses instead of style for buttons

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -13,6 +13,7 @@
 {dialog} = remote.require 'electron'
 Divider = require './divider'
 async = Promise.coroutine
+classnames = require 'classnames'

 PluginSettingWrap = React.createClass
   shouldComponentUpdate: (nextProps, nextState)->
@@ -20,6 +21,8 @@
   render: ->
     React.createElement @props.plugin.settingsClass

+# A collapsible panel that automatically hides itself after collapsed
+# to avoid the two borders that still remains when height=0
 CollapsiblePanel = React.createClass
   getInitialState: ->
     expanded: @props.expanded
@@ -89,7 +92,7 @@
       <Row>
         <Col xs={7}>{plugin.description}</Col>
         <Col xs={5} style={padding: 0}>
-

+

             <ButtonGroup bsSize='small' style={width: '100%'}>
               {
                 if plugin.settingsClass?
@@ -98,14 +101,15 @@
                        {__ 'Settings'}
                      </Tooltip>
                      }>
-                     <Button ref="setting-btn"
+                     <Button ref='setting-btn'
                              bsStyle='primary' bsSize='xs'
-                             style={width: 'calc(100% / 3)'}
                              onClick={@toggleSettingPop}
-                             className="plugin-control-button">
+                             className='plugin-control-button col-xs-4'>
                        <FontAwesome name='gear' />
                      </Button>
                    </OverlayTrigger>
+                else
+

               }
               <OverlayTrigger placement='top' overlay={
                 <Tooltip>
@@ -125,8 +129,7 @@
                 <Button bsStyle='info'
                   disabled={PluginManager.getStatusOfPlugin(plugin) == PluginManager.NEEDUPDATE}
                   onClick={@props.handleEnable}
-                  style={width: "#{if plugin.settingsClass? then 'calc(100% / 3)' else '50%'}"}
-                  className="plugin-control-button">
+                  className='plugin-control-button col-xs-4'>
                   <FontAwesome name={
                     switch PluginManager.getStatusOfPlugin plugin
                       when PluginManager.VALID
@@ -155,8 +158,7 @@
                 <Button bsStyle='danger'
                   onClick={@props.handleRemove}
                   disabled={not plugin.isInstalled}
-                  style={width: "#{if plugin.settingsClass? then 'calc(100% / 3)' else '50%'}"}
-                  className="plugin-control-button">
+                  className="plugin-control-button col-xs-4">
                   <FontAwesome name={if plugin.isInstalled then 'trash' else 'trash-o'} />
                 </Button>
               </OverlayTrigger>


Don't notify when local version higher than remote version

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -200,15 +200,21 @@
       @setState {installStatus}
   solveUpdate: (updateData, isfirst) ->
     latest = @state.latest
+    updateCount = 0
     for updateObject, index in updateData
-      latest[updateObject[1]] = updateObject[4]
-    isUpdateAvailable = updateData.length > 0
-    if isfirst && updateData.length > 0
+      if semver.lt(latest[updateObject[1]], updateObject[4])
+        latest[updateObject[1]] = updateObject[4]
+        updateCount++
+    isUpdateAvailable = updateCount > 0
+    if isfirst && updateCount > 0
       title = __ 'Plugin update'
       outdatedPlugins = []
       for plugin, index in plugins
         if semver.lt(plugin.version, latest[plugin.packageName])
-          displayItems = plugin.displayName.props.children || plugin.displayName
+          if plugin.displayName.props?.children?
+            displayItems = plugin.displayName.props.children
+          else
+            displayItems = plugin.displayName
           for child in displayItems
             if typeof child is "string"
               outdatedPlugins.push child


Fix divv view

Fix divv view

--- client/components/ReviewResult.coffee
+++ client/components/ReviewResult.coffee
@@ -249,12 +249,10 @@
                             diffText = diffText.join("\n")
                             files = parseDiff(diffText, {nearbySequences: "zip"})

-                            markEdits = markWordEdits({threshold: 30, markLongDistanceDiff: true});
-

                                 <SubmitHeader submit={@state.currentDiff[0]} admin={admin}/>

-                                    {files.map(({hunks}, i) => <Diff key={i} hunks={hunks} viewType="split" markEdits={markEdits}/>)}
+                                    {files.map(({hunks}, i) => <Diff key={i} hunks={hunks} viewType="split"/>)}

                     }


Support \begin/\end{quote}

Support \begin/\end{quote}

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -233,6 +233,8 @@
       """#{arg}"""
   .replace /\\begin\s*{(problem|theorem|conjecture|lemma|corollary|fact|observation|proposition|claim)}(\s*\[([^\]]*)\])?/g, (m, env, x, opt) -> "

#{s.capitalize env}#{if opt then " (#{opt})" else ''}: "
   .replace /\\end\s*{(problem|theorem|conjecture|lemma|corollary|fact|observation|proposition|claim)}/g, '
'
+  .replace /\\begin\s*{(quote)}/g, '

'
+  .replace /\\end\s*{(quote)}/g, '
'
   .replace /\\begin\s*{(proof|pf)}(\s*\[([^\]]*)\])?/g, (m, env, x, opt) -> "Proof#{if opt then " (#{opt})" else ''}: "
   .replace /\\end\s*{(proof|pf)}/g, ' ∎

'
   .replace /\\(dots|ldots|textellipsis)\b\s*/g, '…'


update error mode yes, that's much less elegant than before

--- lib/gotodef.coffee
+++ lib/gotodef.coffee
@@ -14,23 +14,23 @@
 #     .line:      Line of definition.
 #     .dispfile:  Humanized file path, displayed.
 #
-# On rejection, the `GotoView` will only show the error message provided.
+# If the Promise errors, it should contain an object with the field error, which
+# will subsequently be displayed in a modal panel.

 module.exports =
 goto: (promise) ->
   @view ?= new GotoView()

   promise
-    .then (items) ->
+    .then (items) =>
+      if items.error?
+        @view.setError items.error
+        @view.show()
       if items.length == 1
         GotoView.openItem items[0]
       else if items.length > 1
         @view.setItems items
         @view.show()
-
-    .catch (error) ->
-      @view.setError error
-      @view.show()

 class GotoView extends SelectListView
   initialize: ->


Use fat arrow

Use fat arrow

--- lib/metrics.coffee
+++ lib/metrics.coffee
@@ -3,7 +3,7 @@

 module.exports =
   activate: ({sessionLength}) ->
-    @getUserId (userId) ->
+    @getUserId (userId) =>
       Reporter.setUserId(userId)
       @begin(sessionLength)


Improve Output pane change, ignore can't execute an empty query

--- lib/pg-hoff.coffee
+++ lib/pg-hoff.coffee
@@ -431,7 +431,7 @@
     connect: ->
         paneItem = atom.workspace.getActivePaneItem()
         pane = atom.workspace.getActivePane()
-
+
         if @listServersViewPanel.isVisible()
             @listServersViewPanel.hide()
             return
@@ -586,6 +586,7 @@
                 gotErrors = false
                 gotNotices = false
                 newBatch = true
+                rowcount = null
                 NumberOfQueries = response.queryids.length
                 return unless response.queryids.length >= 1
                 queryCount = response.queryids.length
@@ -606,9 +607,9 @@
                 boom = () =>
                     return getResult()
                         .then (result) =>
-                            gotResults = result.has_result
-                            gotNotices = result.has_notices
-                            gotErrors = result.has_error
+                            gotResults = true if result.has_result
+                            gotNotices = true if result.has_notices
+
                             if queryCount == 1 and result.has_queryplan
                                 gotQueryplan = true
                             else
@@ -622,6 +623,8 @@
                             fetchPartialResult = () =>
                                 return PgHoffServerRequest.Get(url + currentpage + '/' + (currentpage + pagesize), true)
                                 .then (result) =>
+                                    gotErrors = true if result.error? and not result.error.match /can\'t execute an empty query/
+
                                     if result.errormessage?
                                         throw("#{result.errormessage}")
                                     if result.error?


Filter out empty lines

Filter out empty lines


lint fixes

lint fixes

--- tests/src/styles/style.coffee
+++ tests/src/styles/style.coffee
@@ -28,8 +28,8 @@
     doc.render()

 describe 'src/styles', ->
-    describe "n-style", ->
-        it "can be a Renderer.Item instance", ->
+    describe 'n-style', ->
+        it 'can be a Renderer.Item instance', ->
             # item = Renderer.Rectangle.New()
             # node = new Element.Tag
             # node.attrs['n-style'] = item
@@ -42,14 +42,14 @@

         it "accepts 'renderer:' prefix", ->
             doc = render
-                html: """"""
+                html: ''

             assert.is doc.styles.length, 1
             assert.instanceOf (style = doc.styles[0]).item, Renderer.Rectangle

         it "accepts 'styles:file' syntax", ->
             doc = render
-                html: """"""
+                html: ''
                 styles:
                     file:
                         _main:
@@ -61,7 +61,7 @@

         it "accepts 'styles:file:style' syntax", ->
             doc = render
-                html: """"""
+                html: ''
                 styles:
                     file:
                         style:
@@ -73,11 +73,11 @@

         it "accepts 'styles:file:style:subid' syntax", ->
             doc = render
-                html: """
+                html: '''

-                """
+                '''
                 styles:
                     file:
                         style:
@@ -95,41 +95,41 @@

         it "accepts 'styles:view:style:subid' syntax", ->
             doc = render
-                html: """"""
+                html: ''
                 windowStyle:
                     objects:
                         subid: Renderer.Text.New()

             assert.is doc.styles.length, 1
-            assert.instanceOf (item = (style = doc.styles[0]).item), Renderer.Text
-
-        it "created style is accessible in node", ->
-            doc = render
-                html: """"""
+            assert.instanceOf doc.styles[0].item, Renderer.Text
+
+        it 'created style is accessible in node', ->
+            doc = render
+                html: ''

             assert.is (node = doc.styles[0].node).name, 'b'
             assert.instanceOf node.style, Renderer.Item

-        it "node is accessible in created style document", ->
-            doc = render
-                html: """"""
+        it 'node is accessible in created style document', ->
+            doc = render
+                html: ''

             bNode = doc.node.query 'b'
             assert.is bNode.style.document.node, bNode

-        it "must be specify to create a style", ->
-            doc = render
-                html: """"""
+        it 'must be specify to create a style', ->
+            doc = render
+                html: ''

             node = doc.node.query 'b'

             assert.is doc.styles.length, 0
             assert.isNotDefined node.style

-    describe "Renderer.Text::text", ->
-        it "is always equal of stringified node children", ->
-            doc = render
-                html: """abc"""
+    describe 'Renderer.Text::text', ->
+        it 'is always equal of stringified node children', ->
+            doc = render
+                html: 'abc'

             node = doc.node.query 'b'
             item = doc.styles[0].item
@@ -139,9 +139,11 @@
             node.children[0].text = '123'
             assert.is item.text, node.stringifyChildren()

-        it "is always equal of stringified visible node children", ->
-            doc = render
-                html: """abc"""
+        it 'is always equal of stringified visible node children', ->
+            doc = render
+                html: '''
+                    abc
+                '''

             bNode = doc.node.query 'b'
             iNode = doc.node.query 'i'
@@ -152,10 +154,10 @@
             iNode.attrs.set 'visible', true
             assert.is item.text, bNode.stringifyChildren()

-    describe "Renderer.Item::$.text", ->
-        it "is always equal stringified node children", ->
-            doc = render
-                html: """abc"""
+    describe 'Renderer.Item::$.text', ->
+        it 'is always equal stringified node children', ->
+            doc = render
+                html: 'abc'
                 styles:
                     file:
                         _main:
@@ -172,9 +174,9 @@
             node.children[0].text = '123'
             assert.is item.$.text, node.stringifyChildren()

-        it "is more important than ::text property", ->
-            doc = render
-                html: """abc"""
+        it 'is more important than ::text property', ->
+            doc = render
+                html: 'abc'
                 styles:
                     file:
                         _main:
@@ -194,19 +196,19 @@
             assert.is item.text, ''
             assert.is item.$.text, node.stringifyChildren()

-    describe "Element.Text", ->
-        it "is rendered as Renderer.Text", ->
-            doc = render
-                html: """abc"""
+    describe 'Element.Text', ->
+        it 'is rendered as Renderer.Text', ->
+            doc = render
+                html: 'abc'

             assert.is doc.styles.length, 1
             assert.instanceOf node = (style = doc.styles[0]).node, Element.Text
             assert.is node.text, 'abc'
             assert.instanceOf item = style.item, Renderer.Text

-        it "text is properly synchronized", ->
-            doc = render
-                html: """abc"""
+        it 'text is properly synchronized', ->
+            doc = render
+                html: 'abc'

             node = doc.node.query '#text'
             item = doc.styles[0].item
@@ -217,9 +219,9 @@

             assert.is item.text, node.text

-        it "is not created if has a text parent", ->
-            doc = render
-                html: """ab"""
+        it 'is not created if has a text parent', ->
+            doc = render
+                html: 'ab'
                 styles:
                     file:
                         _main:
@@ -234,7 +236,7 @@
             assert.instanceOf item, Renderer.Text
             assert.isNotDefined textNode.style

-    describe "a[href]", ->
+    describe 'a[href]', ->
         it 'sets style linkUri', ->
             doc = render
                 html: ''


Support completion of form

Support completion of form

--- lib/psc-ide.coffee
+++ lib/psc-ide.coffee
@@ -145,10 +145,12 @@
   getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) =>
     new Promise (resolve) =>
       prefix = prefix.trim()
+      originalPrefix = prefix
+      prefix = "" if prefix is "." # shift right

       modulePrefix = getModulePrefix(editor, bufferPosition.translate([0, -prefix.length]))

-      if prefix.length > 0
+      if prefix.length > 0 || originalPrefix is "."
         @getCompletion(prefix,modulePrefix)
           .then (completions) =>
             resolve completions.map (c) =>


runtime: Remove non-live-programming mode

That is so yesterday...

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -18,8 +18,6 @@
   flowhub = require("flowhub-registry")
 catch e
   #
-
-liveProgramming = process.env.MICROFLO_LIVE?

 commandstream = require("./commandstream")
 generate = require("./generate")
@@ -144,19 +142,13 @@
             type: "microflo"
             version: "0.4"
             capabilities: caps
-        if liveProgramming
-          runtime.device.open () ->
-            connection.send
-              protocol: "runtime"
-              command: "runtime"
-              payload: r
+
+        runtime.device.open () ->
+          connection.send
+            protocol: "runtime"
+            command: "runtime"
+            payload: r
           sendExportedPorts connection, runtime
-        else
-          connection.send
-              protocol: "runtime"
-              command: "runtime"
-              payload: r
-          sendExportedPorts connection, runtime
     else if command is 'packet'
         sendPacket runtime, payload.port, payload.event, payload.payload
     else
@@ -193,27 +185,18 @@
         graph.nodeMap = {}
         graph.componentMap = {}
         graph.currentNodeId = 1
-        # FIXME: should be on the graph!
         runtime.exportedEdges = []
         runtime.edgesForInspection = []
-        if liveProgramming
-          sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
-        else
-          sendAck connection, { protocol: 'graph', command: command, payload: payload }
+
+        sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
     else if command is "addnode"
         graph.processes[payload.id] = payload
         graph.nodeMap[payload.id] = { id: graph.currentNodeId++ }
         graph.componentMap[payload.id] = payload.component
         # TODO: wait for nodeId from runtime, update nodeMap then
-        if liveProgramming
-          sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
-        else
-          sendAck connection, { protocol: 'graph', command: command, payload: payload }
+        sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
     else if command is "removenode"
-        if liveProgramming
-          sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
-        else
-          sendAck connection, { protocol: 'graph', command: command, payload: payload }
+        sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
         delete graph.processes[payload.id]
         delete graph.nodeMap[payload.id]
         delete graph.componentMap[payload.id]
@@ -226,23 +209,15 @@
         sendAck connection, { protocol: 'graph', command: command, payload: payload }
     else if command is "addedge"
         graph.connections.push protocol.wsConnectionFormatToFbp(payload)
-        if liveProgramming
-          sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
-        else
-          sendAck connection, { protocol: 'graph', command: command, payload: payload }
+        sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
     else if command is "removeedge"
         graph.connections = connectionsWithoutEdge(graph.connections, protocol.wsConnectionFormatToFbp(payload))
-        if liveProgramming
-          sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
-        else
-          sendAck connection, { protocol: 'graph', command: command, payload: payload }
+        sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
     else if command is "addinitial"
         graph.connections.push protocol.wsConnectionFormatToFbp(payload)
-        if liveProgramming
-          sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
-        else
-          sendAck connection, { protocol: 'graph', command: command, payload: payload }
+        sendMessage runtime, { protocol: 'graph', command: command, payload: payload }
     else if command is "removeinitial"
+        # TODO: send to runtime side, wait for response
         graph.connections = connectionsWithoutEdge(graph.connections, protocol.wsConnectionFormatToFbp(payload))
         sendAck connection, { protocol: 'graph', command: command, payload: payload }

@@ -269,10 +244,11 @@
             src:
                 process: payload.node
                 port: payload.port
-        if liveProgramming
-            edges = runtime.exportedEdges.concat runtime.edgesForInspection
-            handleNetworkEdges runtime, connection, edges, (err) ->
-              console.log 'handle network edges error', err if err
+
+        # update subscriptions on device side
+        edges = runtime.exportedEdges.concat runtime.edgesForInspection
+        handleNetworkEdges runtime, connection, edges, (err) ->
+          console.log 'handle network edges error', err if err

         sendAck connection, { protocol: 'graph', command: command, payload: payload }
     else if command is "removeoutport"
@@ -420,23 +396,11 @@

 handleNetworkCommand = (command, payload, connection, runtime, transport, debugLevel) ->
     if command is "start"
-        if liveProgramming
-          m = { protocol: 'network', command: command, payload: payload }
-          sendMessage runtime, m
-        else
-          resetAndUploadGraph runtime, connection, debugLevel
+        m = { protocol: 'network', command: command, payload: payload }
+        sendMessage runtime, m
     else if command is "stop"
-        if liveProgramming
-          m = { protocol: 'network', command: command, payload: payload }
-          sendMessage runtime, m
-        else
-          m =
-              protocol: "network"
-              command: "stopped"
-              payload:
-                  running: false
-                  started: false
-          sendAck connection, m
+        m = { protocol: 'network', command: command, payload: payload }
+        sendMessage runtime, m
     else if command is "edges"
         # TODO: merge with those of exported outports
         runtime.edgesForInspection = payload.edges


katkokävely

katkokävely

--- web/js/views/map_view.coffee
+++ web/js/views/map_view.coffee
@@ -24,6 +24,18 @@
     36:   '#193695' # Kirkkonummi internal bus lines
     39:   '#193695' # Kerava internal bus lines

+  legStyles =
+    walk:
+      strokeOpacity: 0
+      icons: [{
+        icon:
+          path: 'M 0,-0.2 0,0.2'
+          strokeOpacity: 1
+          strokeColor: legColors['walk']
+        offset: '0',
+        repeat: '7px'
+      }]
+
   class MapView extends Backbone.View

     el: $('#map')
@@ -63,11 +75,13 @@
       @clearRoute()
       @route = for leg in route[0].legs
         latLngs = (new google.maps.LatLng point.y, point.x for point in leg.shape)
-        new google.maps.Polyline
-          map: @map
-          path: latLngs
-          strokeColor: legColors[leg.type] or '#0000ee'
-          strokeWeight: 4
+
+        new google.maps.Polyline _.extend({
+            map: @map
+            path: latLngs
+            strokeWeight: 4
+            strokeColor: legColors[leg.type]
+          }, legStyles[leg.type])

       @panToRouteBounds()


Dont listen to arrow key presses when you are in an input field

--- components/merry_go_round/view.coffee
+++ components/merry_go_round/view.coffee
@@ -17,6 +17,7 @@
     $(document).on 'keydown.mgr', @keypress

   keypress: (e) =>
+    return if $(e.target).is(':input')
     switch e.keyCode
       when 37
         @prev e


Uncommented an header

Uncommented an header

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -859,7 +859,7 @@
     track_concepts8: "para se juntar"
     private_require_sub: "Clãs particulares requerem uma assinatura para criar ou juntar-se."

-#  courses:
+  courses:
     course: "Curso"
     courses: "cursos"
     not_enrolled: "Você não está matriculado nesse curso."


removed console.log line

removed console.log line

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -646,9 +646,7 @@
         subject : "Your password has changed"
         content : "Your password has changed, please contact support@koding.com if you didn't change your password"
       }
-      email.save (err)=>
-        if err then 'couldnt send email' else 'sent notification for password change'
-
+      email.save ()->

   @changeEmail = secure (client,options,callback)->


Account: renamed invalidatePin as handleError

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -366,26 +366,28 @@
     {connection: {delegate}} = client
     {password, email} = options

-    invalidatePin = (err, user) ->
+    # handles error and decide to invalidate pin or not
+    # depending on email and user variables
+    handleError = (err, user) ->
       if email and user
-        # when email is set, we need to invalidate verification token
+        # when email and user is set, we need to invalidate verification token
         params =
           action    : "update-email"
           username  : user.username
           email     : email
         JVerificationToken = require '../verificationtoken'
         JVerificationToken.invalidatePin params, (err) ->
-          return console.error 'Pin invalidation error occurred'  if err
+          return console.error 'Pin invalidation error occurred', err  if err
       callback err, no

     # fetch user for invalidating created token
-    @fetchUser client, (err, user) =>
-      return invalidatePin err if err
-      unless password and password isnt ""
-        return invalidatePin createKodingError("Password cannot be empty!"), user
+    @fetchUser client, (err, user) ->
+      return handleError err  if err
+      if not password or password is ""
+        return handleError createKodingError("Password cannot be empty!"), user
       confirmed = user.getAt('password') is hashPassword password, user.getAt('salt')
       return callback null, yes  if confirmed
-      return invalidatePin null, user
+      return handleError null, user

   logAndReturnLoginError = (username, error, callback)->


pass user variable in convert callback

pass user variable in convert callback

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1671,7 +1671,7 @@

       ->
         # don't block register
-        callback error, { account, newToken }
+        callback error, { account, newToken, user }
         queue.next()

       ->


Bind keyboard events to both document and input elements

--- orgviz/static/timeline.coffee
+++ orgviz/static/timeline.coffee
@@ -106,7 +106,8 @@

   # `Timeline._Band' uses a text input box to capture keyboard events.
   # Let's mix keyboard shortcut to this element.
-  keyboardInput = $(tl.getBand(0)._keyboardInput)
+  keyboardInput = $([$(document),  $("div.timeline-band-input > input")])
+    .map -> this.toArray()

   reload = (cb) -> loadEventData tl, eventSource, data_source, cb
   reload -> setupKeybinds keyboardInput, tl


shortcuts: adjust modal to new design

shortcuts: adjust modal to new design

--- client/app/lib/shortcuts/views/modal.coffee
+++ client/app/lib/shortcuts/views/modal.coffee
@@ -1,62 +1,57 @@
 kd            = require 'kd'
 _             = require 'lodash'
 ShortcutsPane = require './pane'
-defaults      = require '../config'

-module.exports = class ShortcutsModal extends kd.ModalViewWithForms
+module.exports =

-  constructor: (options={}, keyconfig) ->
+class ShortcutsModal extends kd.ModalViewWithForms

-    options =
-      title                   : 'Shortcuts'
-      cssClass                : 'shortcuts-modal'
-      overlay                 : yes
-      width                   : 640
-      height                  : 'auto'
-      buttons                 :
-        restore               :
-          title               : 'Restore Defaults'
-          style               : 'solid light-gray medium'
-          loader              : color : '#444444'
-          callback            : -> modal.destroy()
-        save                  :
-          title               : 'Save'
-          cssClass            : 'solid green medium'
-          callback            : =>
-            # poc code - SY
-            console.log "this is fake!!!"
-            new kd.NotificationView title : 'Shortcuts saved!'
-            @modalTabs.getActivePane().form.inputs.view.listController.getListItems().forEach (item) ->
-              item.unsetClass 'updated'
-      tabs                    :
-        hideHandleCloseIcons  : yes
-        enableMoveTabHandle   : no
-        cssClass              : 'shortcuts-tab'
-        forms                 : @prepareTabData keyconfig
+  constructor: (options, @config) ->

-    super options
+    super _.extend
+
+      title    : 'Shortcuts'
+      content  : """
+        <div class=instructions>
+          To change a shortcut, click the key combination, then type the new keys.
+        </div>
+      """
+      cssClass : 'shortcuts-modal'
+      overlay  : yes
+      width    : 600
+      height   : 'auto'
+
+      buttons:
+        restore:
+          title    : 'Restore Defaults'
+          style    : 'solid light-gray medium'
+          loader   : color: '#444444'
+          #callback : -> modal.destroy()
+
+      tabs:
+        hideHandleCloseIcons : yes
+        enableMoveTabHandle  : no
+        forms                : @presentForms()
+
+    , options
+
+
+  presentForms: ->
+
+    @config.reduce (acc, collection) ->
+      acc[collection.title] =
+        fields:
+          view:
+            itemClass: ShortcutsPane
+            collection: collection
+      return acc
+    , {}

   viewAppended: ->

     @modalTabs.on 'PaneDidShow', => kd.utils.defer @bound '_windowDidResize'
-
     @_windowDidResize()
-
-
-  prepareTabData: (keyconfig) ->
-
-    forms = {}
-    keyconfig.forEach (collection) ->
-      displayData = defaults[collection.name]
-      forms[displayData.title] =
-        fields                :
-          view                :
-            itemClass         : ShortcutsPane
-            description       : displayData.description
-            collection        : collection
-
-    return forms

   _windowDidResize: ->


specify size of image to render

specify size of image to render

--- client/skr/screens/locations/Locations.cjsx
+++ client/skr/screens/locations/Locations.cjsx
@@ -22,7 +22,7 @@
                 <LC.Input sm=6 name='name' model={@location} />
             </BS.Row>
             <BS.Row>
-                <LC.ImageSaver label='Logo' sm=3 model={@location} name='logo' />
+                <LC.ImageSaver label='Logo' sm=3 model={@location} name='logo', size='thumb' />
             </BS.Row>
             <BS.Row>
                 <SC.Address lg=6 model={@location.address}  />


Tweak nav animations

Tweak nav animations

--- client/source/views/layouts/loggedin.coffee
+++ client/source/views/layouts/loggedin.coffee
@@ -27,11 +27,11 @@
     @$navLinks = @$('.nav-primary a')

     @navTimeline ?= new TimelineLite
-    @navTimeline.staggerFrom @$('.nav-primary li'), .2,
+    @navTimeline.staggerFrom @$('.nav-primary li'), .15,
       y: '30px'
       opacity: 0
       ease: Back.easeOut
-    , .05
+    , .02

     @navTimeline.play()


removed auth config manager -- auth/sso will not be rikki-tikki's bailywick

--- test/auth_config_manager.coffee
+++ test/auth_config_manager.coffee
@@ -1,15 +0,0 @@
-fs                = require 'fs'
-path              = require 'path'
-(chai             = require 'chai').should()
-RikkiTikkiAPI     = require '../src'
-RikkiTikkiAPI.AUTH_CONFIG_PATH = ".#{path.sep}test#{path.sep}scripts#{path.sep}configs#{path.sep}auth"
-RikkiTikkiAPI.getOptions = =>
-  new RikkiTikkiAPI.APIOptions().toJSON()
-module.exports.RikkiTikkiAPI = RikkiTikkiAPI
-AuthConfigManager = require '../src/classes/auth/AuthConfigManager'
-
-describe 'AuthConfigManager Test Suites', ->
-  it 'should load auth configs', (done)=>
-    AuthConfigManager.getInstance().load (e,s)=>
-      throw e if e?
-      done()


S52265: Alter apps to use ArtifactTypeChooser plugin. Bumped appsdk to 718-e043b45.

--- kanban/KanbanAppSpec.coffee
+++ kanban/KanbanAppSpec.coffee
@@ -357,7 +357,8 @@

     assertPolicyCmpConfig: (settingsKey, policy) ->
       column = @app.down('rallycardboard').getColumns()[0]
-      prefConfigSettings = column.policyCmpConfig.prefConfig.settings
+      plugin = _.find(column.plugins, {ptype: 'rallycolumnpolicy'});
+      prefConfigSettings = plugin.policyCmpConfig.prefConfig.settings
       expect(Ext.Object.getKeys(prefConfigSettings)[0]).toBe settingsKey
       expect(prefConfigSettings[settingsKey]).toBe policy
-      expect(column.policyCmpConfig.policies).toBe policy+      expect(plugin.policyCmpConfig.policies).toBe policy


Remove obsolete property tests

Remove obsolete property tests

--- tests/batman/object_test.coffee
+++ tests/batman/object_test.coffee
@@ -71,39 +71,6 @@
   ok !subSpy.called

-QUnit.module "Batman.Object properties"
-  setup: ->
-    @get = get = createSpy().whichReturns("")
-    @set = set = createSpy()
-    @klass = class Test extends Batman.Object
-      foo: @property
-        get: get
-        set: set
-    @obj = new Test
-
-test "it should allow creation of properties", ->
-  ok @obj.foo.isProperty
-
-test "it should allow getting and setting via the object", ->
-  @obj.set("foo", "bar")
-  deepEqual @set.lastCallArguments, ["foo", "bar", @obj]
-
-test "it should allow getting and setting via the property", ->
-  @obj.foo("qux")
-  deepEqual @set.lastCallArguments, ["foo", "qux", @obj]
-
-test "it should allow observation via the object", ->
-  a = createSpy()
-  @obj.observe("foo", a)
-
-  b = createSpy()
-  @obj2 = new @klass
-  @obj2.observe("foo", b)
-
-  @obj.set("foo", "baz")
-  ok a.called
-  ok !b.called
-
 test "it should allow observation via the class", ->
   a = createSpy()
   class Custom extends Batman.Object
@@ -117,52 +84,4 @@
   @obj2.set("foo", "qux")
   equal a.callCount, 2

-test "it should allow custom getters and setters", ->
-  class Custom extends Batman.Object
-    foo: @property
-      get: (key, context) ->
-        "special"
-      set: (key, value, context) ->
-        context.somethingElse = value
-
-  @obj = new Custom
-  @obj.get("foo")
-  equal @obj.get("foo"), "special"
-
-  @obj.set("foo", "something")
-  equal @obj.somethingElse, "something"
-
-test "one object should not affect the other", ->
-  @obj2 = new @klass
-
-  @obj.set("foo", "bar")
-  equal @set.lastCallArguments[2], @obj
-
-  @obj2.set("foo", "baz")
-  equal @set.lastCallArguments[2], @obj2
-
-test "property setters should fire observers if the return a changed value", 2, ->
-  class Custom extends Batman.Object
-    foo: @property
-      get: (key, context) -> context.test
-      set: (key, value, context) ->
-        context.test = value * 2
-
-    bar: @property
-      get: (key, context) ->
-        "silly"
-      set: (key, value, context) ->
-        "silly"
-
-  @obj = new Custom
-  @obj.set 'foo', 1
-  @obj.observe 'foo', (value, oldValue) ->
-    equals value, 4
-    equals oldValue, 2
-  @obj.set 'foo', 2
-
-  @obj.observe 'bar', (value, oldValue) ->
-    ok false, "Observer isn't supposed to be called because set doesn't return a different value!"
-  @obj.set 'bar', 'weird'
-
 QUnit.module "Batman (the function)"


Add test to ensure that aliases exist

Add test to ensure that aliases exist

--- tapestry-core/src/test/coffeescript/org/apache/tapestry5/integration/app1/pages/test-pubsub.coffee
+++ tapestry-core/src/test/coffeescript/org/apache/tapestry5/integration/app1/pages/test-pubsub.coffee
@@ -1,6 +1,11 @@
 module "PubSub"

 require ["core/pubsub"], (pubsub) ->
+
+  test "export aliases", ->
+
+    ok pubsub.on is pubsub.respondTo, "on and respondTo"
+    ok pubsub.off is pubsub.stopResponding, "off and stopResponding"

   test "simple on/fire", ->

@@ -10,4 +15,5 @@
     pubsub.on "stim", (memo) -> memoValue = memo
     pubsub.fire "stim", expectedMemo

-    ok memoValue is expectedMemo, "responder function was invoked"+    ok memoValue is expectedMemo, "responder function was invoked"
+


rename price sorting options

rename price sorting options

--- public/views/sort_view.coffee
+++ public/views/sort_view.coffee
@@ -12,8 +12,8 @@
     Sort by:
     <select name="sort">
       <option value="">Name</option>
-      <option value="price,asc">Price 0..9</option>
-      <option value="price,desc">Price 9..0</option>
+      <option value="price,asc">Price: low to high</option>
+      <option value="price,desc">Price: high to low</option>
     </select>
   """


[minor] wording

[minor] wording

--- test/src/backbone.websql.deferred-test.coffee
+++ test/src/backbone.websql.deferred-test.coffee
@@ -23,8 +23,8 @@
     db.transaction (tx) =>
       for table in ['users', 'posts']
         tx.executeSql "DROP TABLE IF EXISTS #{table}"
-    , (err)  -> console.error "FAIL: drop tables"
-    , (resp) -> console.info  "SUCESS: drop tables"
+    , (err)  -> console.error "ERROR: drop tables"
+    , (resp) -> console.info  "SUCCESS: drop tables"
     done()

   context 'when window.openDatabase doesnt exist', ->


Remove debug statement from gulp/tasks/scripts.coffee

--- bokehjs/gulp/tasks/scripts.coffee
+++ bokehjs/gulp/tasks/scripts.coffee
@@ -60,8 +60,6 @@
       if not fs.existsSync(depPath)
         depPath = "#{depPath}.js"
       depModMap[depPath] = dep
-
-  debugger

   modPath = row.id


Small adjustments

Small adjustments

--- car_crashes/car_crashes.coffee
+++ car_crashes/car_crashes.coffee
@@ -3,11 +3,11 @@
     n_states = data.state.length

     # sizes of things
-    htop = 500
+    htop = 530
     hbot = 500
     height = htop + hbot

-    margin_top = {left:5, top:40, right:25, bottom:40, inner:0}
+    margin_top = {left:5, top:20, right:25, bottom:40, inner:0}
     fullpanelwidth_top = 180
     panelwidth_top = fullpanelwidth_top - margin_top.left - margin_top.right
     panelheight_top = htop - margin_top.top - margin_top.bottom
@@ -30,17 +30,21 @@
             .attr("height", height)
             .attr("width", width)

+    # info top panels, with dot plots
     top_panel_var = ["total", "not_distracted", "speeding", "alcohol", "ins_premium", "ins_losses"]
     xlim = [[0,25],[0,25],[0,25],[0,25],[0,1500],[0,200]]
     nxticks = [6,6,6,6, 4, 5]
     xlab = ["Crashes per billion miles", "Crashes per billion miles", "Crashes per billion miles", "Crashes per billion miles", "Dollars", "Dollars"]
     title = ["Total", "Not distracted", "Speeding", "Alcohol", "Ave. Ins. premium", "Ave. Ins. Losses"]
+
+    # now make the dot plots
     dotplots = []
     for i of top_panel_var

         this_dotplot = scatterplot().width(panelwidth_top)
                                     .height(panelheight_top)
                                     .margin(margin_top)
+                                    .titlepos(10)
                                     .xNA({handle:false})
                                     .yNA({handle:false})
                                     .xlim(xlim[i])
@@ -55,13 +59,15 @@
         dotplots.push(this_dotplot)

         this_g = svg.append("g")
+                    .attr("class", "dotplot")
                     .attr("id", "dotplot#{i}")
                     .attr("transform", "translate(#{statenamewidth+i*fullpanelwidth_top},0)")

         this_g.datum({data:data, indID:data.abbrev})
               .call(this_dotplot)

-        d3.selectAll("g.y.axis line").attr("stroke", "#bbb")
+    # make the horizontal grid lines gray
+    d3.selectAll("g.dotplot g.y.axis line").attr("stroke", "#bbb")

     # add state names
     yscale = dotplots[0].yscale()


Remove markers correctly

Remove markers correctly

--- services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee
@@ -103,23 +103,21 @@
 			expected_markers = []
 			for change in @changesTracker.changes
 				op = change.op
-				{background_marker_id} = @changeIdToMarkerIdMap[change.id]
+				{background_marker_id, callout_marker_id} = @changeIdToMarkerIdMap[change.id]
 				start = @_shareJsOffsetToAcePosition(op.p)
 				if op.i?
 					end = @_shareJsOffsetToAcePosition(op.p + op.i.length)
 				else if op.d?
 					end = start
-				expected_markers.push {
-					marker_id: background_marker_id, start, end
-				}
+				expected_markers.push { marker_id: background_marker_id, start, end }
+				expected_markers.push { marker_id: callout_marker_id, start, end: start }

 			for comment in @changesTracker.comments
-				{background_marker_id} = @changeIdToMarkerIdMap[comment.id]
+				{background_marker_id, callout_marker_id} = @changeIdToMarkerIdMap[comment.id]
 				start = @_shareJsOffsetToAcePosition(comment.offset)
 				end = @_shareJsOffsetToAcePosition(comment.offset + comment.length)
-				expected_markers.push {
-					marker_id: background_marker_id, start, end
-				}
+				expected_markers.push { marker_id: background_marker_id, start, end }
+				expected_markers.push { marker_id: callout_marker_id, start, end: start }

 			for {marker_id, start, end} in expected_markers
 				marker = markers[marker_id]
@@ -213,15 +211,17 @@
 			@updateReviewEntriesScope()

 		_onInsertRemoved: (change) ->
-			marker_id = @changeIdToMarkerIdMap[change.id]
-			session = @editor.getSession()
-			session.removeMarker marker_id
+			{background_marker_id, callout_marker_id} = @changeIdToMarkerIdMap[change.id]
+			session = @editor.getSession()
+			session.removeMarker background_marker_id
+			session.removeMarker callout_marker_id
 			@updateReviewEntriesScope()

 		_onDeleteRemoved: (change) ->
-			marker_id = @changeIdToMarkerIdMap[change.id]
-			session = @editor.getSession()
-			session.removeMarker marker_id
+			{background_marker_id, callout_marker_id} = @changeIdToMarkerIdMap[change.id]
+			session = @editor.getSession()
+			session.removeMarker background_marker_id
+			session.removeMarker callout_marker_id
 			@updateReviewEntriesScope()

 		_onCommentAdded: (comment) ->


Fix draggable/pinachable weird offsets

Fix draggable/pinachable weird offsets

--- framer/LayerPinchable.coffee
+++ framer/LayerPinchable.coffee
@@ -41,28 +41,40 @@
 		@layer.on(Gestures.PinchStart, @_pinchStart)
 		@layer.on(Gestures.Pinch, @_pinch)
 		@layer.on(Gestures.PinchEnd, @_pinchEnd)
+		@layer.on(Gestures.TapStart, @_tapStart)

 	_reset: ->
 		@_scaleStart = null
 		@_rotationStart = null
 		@_rotationOffset = null

+	_tapStart: (event) ->
+		#@_centerOrigin(event) if @centerOrigin
+
+	_centerOrigin: (event) =>
+
+		topInSuperBefore = Utils.convertPoint({}, @layer, @layer.superLayer)
+		pinchLocation = Utils.convertPointFromContext(event.touchCenter, @layer, true, true)
+		@layer.originX = pinchLocation.x / @layer.width
+		@layer.originY = pinchLocation.y / @layer.height
+
+		topInSuperAfter = Utils.convertPoint({}, @layer, @layer.superLayer)
+		xDiff = topInSuperAfter.x - topInSuperBefore.x
+		yDiff = topInSuperAfter.y - topInSuperBefore.y
+		@layer.x -= xDiff
+		@layer.y -= yDiff
+
+		# This is not a great fix, we should add this in the draggable. Basically
+		# we need to account in the draggable for a change of origin relative to
+		# the dragging start offset.
+
+		if @layer._draggable
+			@layer.draggable._layerStartPoint.x -= xDiff
+			@layer.draggable._layerStartPoint.y -= yDiff
+
 	_pinchStart: (event) =>
-
 		@_reset()
-
-		if @centerOrigin
-
-			topInSuperBefore = Utils.convertPoint({}, @layer, @layer.superLayer)
-			pinchLocation = Utils.convertPointFromContext(event.touchCenter, @layer, true, true)
-			@layer.originX = pinchLocation.x / @layer.width
-			@layer.originY = pinchLocation.y / @layer.height
-
-			topInSuperAfter = Utils.convertPoint({}, @layer, @layer.superLayer)
-			xDiff = topInSuperAfter.x - topInSuperBefore.x
-			yDiff = topInSuperAfter.y - topInSuperBefore.y
-			@layer.x -= xDiff
-			@layer.y -= yDiff
+		@_centerOrigin(event) if @centerOrigin

 	_pinch: (event) =>


Generate arbitrary bodies for debugging purposes

--- app/js/main.coffee
+++ app/js/main.coffee
@@ -81,7 +81,34 @@

     @world.addBody a for a in @asteroids

-    window.a = @asteroids[0]
-    window.b = @asteroids[1]
+  generateBodies: ->
+    @asteroids = []
+
+    @asteroids.push new Asteroid 1,
+      position: [1, 9.6]
+      velocity: [0, 0]
+      density: 5
+
+    @asteroids.push new Asteroid 1,
+      position: [1, 1]
+      velocity: [0, -1]
+      density: 5
+
+    @asteroids.push new Asteroid 1,
+      position: [4.5, 0.1]
+      velocity: [1, 0]
+      density: 5
+
+    @asteroids.push new Asteroid 1,
+      position: [5.4, 9.9]
+      velocity: [-1, 0]
+      density: 5
+
+    [window.a, window.b, window.c, window.d] = @asteroids
+
+    # @world.addBody a for a in @asteroids
+    @world.addBody a
+    @world.addBody b
+

 window.go = -> window.simulation = new Simulation


Fix seed generation with dat.GUI initialization

--- app/js/main.coffee
+++ app/js/main.coffee
@@ -8,16 +8,14 @@
       scale: 50
       # paused: true

+    @setNewSeed() unless @seed
     @initializeGUI()

-    if @seed
-      @reset()
-    else
-      @randomize()
+    @reset()

   # Public: set a new random seed and reset the simulation
   randomize: ->
-    @seed = Math.floor(Math.random() * 10000000)
+    @setNewSeed()
     @reset()

   # Public: reset the simulation, start over
@@ -32,6 +30,9 @@

     @generateBodies()
     # @generateAsteroids()
+
+  setNewSeed: ->
+    @seed = Math.floor(Math.random() * 10000000)

   # Internal: set up a GUI controller for the simulation
   initializeGUI: ->


upload check disabled

upload check disabled

--- lib/directory-upload.coffee
+++ lib/directory-upload.coffee
@@ -55,17 +55,17 @@
   upload: (filename) ->
     filePath = "#{@uploadDir}/#{filename}"
     if fs.existsSync filePath
-      if isTextOrBinary.isTextSync filePath
-        content = fs.readFileSync "#{@uploadDir}/#{filename}",'utf8'
-        file = @splitFileName filename
+      # if isTextOrBinary.isTextSync filePath
+      content = fs.readFileSync "#{@uploadDir}/#{filename}",'utf8'
+      file = @splitFileName filename

-        # eventbus.fire "Notifications","info", "Die Datei #{filename} wird hochgeladen."
-        window.eventbus.fire 'MainApp','uploadFile', {} =
-          appName: file.type
-          url: 'none'
-          data: content
-          filename: file.name
-          type: file.type
+      # eventbus.fire "Notifications","info", "Die Datei #{filename} wird hochgeladen."
+      window.eventbus.fire 'MainApp','uploadFile', {} =
+        appName: file.type
+        url: 'none'
+        data: content
+        filename: file.name
+        type: file.type
       else
         eventbus.fire "Notifications","info","Die Datei '#{filename}' ist keine Text-/Office-Datei."
         eventbus.fire "Notifications","info","Bild Datein und andere Binary Dateien können über 'Daisy->Dateien->Neue Datei anlegen' hochgeladen werden."


FSItem: create: static method for Machine.fs.create and updates on instance methods

--- client/Finder/fs/fsitem.coffee
+++ client/Finder/fs/fsitem.coffee
@@ -1,16 +1,36 @@
+
+# Base class for file instances
+# Requires to work with Klient Kite
+# and a provided Machine instance
+#
 class FSItem extends KDObject

-  ###
-  CLASS CONTEXT
-  ###
-
+  # Commonly used helpers
   { escapeFilePath, handleStdErr } = FSHelper

-  @create:({ path, type, vmName, treeController }, callback)->
-
-    kite = FSItem.getKite {vmName}
-
-    kite.vmOn().then ->
+  # Assign every option to instance
+  constructor:(options, data)->
+    @[key] = value for own key, value of options
+    super
+
+  # Only static method of FSItem
+  # Requires a valid machine object and a path to create
+  # file on the server, type folder is also supported
+  #
+  # If requested path already exists, it generates new paths
+  # eg, if /tmp/foo then /tmp/foo_1 will be created, and so on.
+  @create = ({ path, type, machine }, callback = noop)->
+
+    unless path or machine
+      warn message = "pass a path and machine to create a file"
+      return callback { message }
+
+    type ?= "file"
+    kite  = machine.getBaseKite()
+
+    kite.vmOn()
+
+    .then ->

       plainPath = FSHelper.plainPath path


Send shortName as custom variable to Google Analytics.

--- client/code/router/main.coffee
+++ client/code/router/main.coffee
@@ -50,6 +50,8 @@
   trackPageView: (e) ->
     path = Backbone.history.getFragment()
     _gaq.push ['_trackPageview', "/#{path}"]
+    if 'real' of window.user
+      _gaq.push ['_setCustomVar', 1, 'shortName', window.user.real.shortName, 1]

   trackOptimizely: (e) ->
     window.optimizely = window.optimizely or []


slightly refactored whitelabelling function

--- client/code/view/titles.coffee
+++ client/code/view/titles.coffee
@@ -6,9 +6,7 @@

   render: ->
     tpl = """#{@options.text}"""
-    if window.user.effective?.logoUrl?
-      tpl = """ #{tpl}"""
-    @$el.html tpl
+    @$el.html whitelabel(tpl)
     @

   setDocumentTitle: (model) =>
@@ -78,9 +76,7 @@
       /
       Settings
     """
-    if window.user.effective?.logoUrl?
-      tpl = """ #{tpl}"""
-    @$el.html tpl
+    @$el.html whitelabel(tpl)
     @

@@ -98,9 +94,7 @@
       #{@model.get 'displayName'}
       <input type="text" id="txtName" style="display: none"/>
     """
-    if window.user.effective?.logoUrl?
-      tpl = """ #{tpl}"""
-    @$el.html tpl
+    @$el.html whitelabel(tpl)
     @

 class Cu.View.ViewTitle extends Cu.View.EditableTitle
@@ -122,7 +116,10 @@
       #{@model.get 'displayName'}
       <input type="text" id="txtName" style="display: none"/>
     """
-    if window.user.effective?.logoUrl?
-      tpl = """ #{tpl}"""
-    @$el.html tpl
+    @$el.html whitelabel(tpl)
     @
+
+whitelabel = (tpl) ->
+   if window.user.effective?.logoUrl?
+     tpl = """ #{tpl}"""
+   tpl


.

Former-commit-id: 15f30de93586f00c5a21057f455e25edc08e80c5 [formerly 33262504fc18665089d00ee71249e0e8eb375217] [formerly 146f856b61f1a20dd39e829af25611d97b9ced3c [formerly d396797cfd5f553b4943c1da3da6eeaf30b0e77f]] [formerly 0b5384fc64f66350cf93b46075ff8c6e31643d01 [formerly dfe2cbe308fc240b6e52173003cb61f1f411219c] [formerly d1759a7e6d8f18cace3d6e842cae8b4011da8ee5 [formerly d1759a7e6d8f18cace3d6e842cae8b4011da8ee5 [formerly ac9aa35ebf496779506806373729743192e56605]]]]
Former-commit-id: bb23e48b26567d70dfca4b0396f728e54d6c481f [formerly f8464a9804e79322a735056c09846cbe7ddc0b3b] [formerly 631ebb90d7423625dc9f87c02c26e2ffae923136 [formerly 01beac81943219ffbe9ea6da8778aee4ade38572]]
Former-commit-id: 5620d44afc43c564a48b867345275b98486e937e [formerly 420aeaa4fe337e7e1115115366dd905bf522de40]
Former-commit-id: 56bfba6296e75a5f4210697afadb29e48d863f54
Former-commit-id: 8fed477319c503f39d26f9f77c3048a834e79860

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -720,7 +720,7 @@

           DIV
             style:
-              width: CONTENT_WIDTH()
+              width: HOMEPAGE_WIDTH()
               margin: 'auto'


Handle line continuations in suggestion filtering

--- lib/fortran-provider.coffee
+++ lib/fortran-provider.coffee
@@ -272,14 +272,15 @@
     suggestions = []
     if prefix
       prefixLower = prefix.toLowerCase()
-      lineContext = @getLineContext(editor, bufferPosition)
+      fullLine = @getFullLine(editor, bufferPosition)
+      lineContext = @getLineContext(fullLine)
       if lineContext == 2
         return completions
       if lineContext == 1
-        suggestions = @getUseSuggestion(editor, bufferPosition, prefixLower)
+        suggestions = @getUseSuggestion(fullLine, prefixLower)
         return @buildCompletionList(suggestions, lineContext)
       lineScopes = @getLineScopes(editor, bufferPosition)
-      cursorScope = @getClassScope(editor, bufferPosition, lineScopes)
+      cursorScope = @getClassScope(fullLine, lineScopes)
       if cursorScope?
         suggestions = @addChildren(cursorScope, suggestions, prefixLower, [])
         return @buildCompletionList(suggestions)
@@ -301,8 +302,9 @@
       line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
       unless line.endsWith('%')
         return completions
+      fullLine = @getFullLine(editor, bufferPosition)
       lineScopes = @getLineScopes(editor, bufferPosition)
-      cursorScope = @getClassScope(editor, bufferPosition, lineScopes)
+      cursorScope = @getClassScope(fullLine, lineScopes)
       if cursorScope?
         suggestions = @addChildren(cursorScope, suggestions, prefixLower, [])
         return @buildCompletionList(suggestions)
@@ -321,7 +323,8 @@
     wordLower = word.toLowerCase()
     lineScopes = @getLineScopes(editor, bufferPosition)
     # Look up class tree
-    cursorScope = @getClassScope(editor, bufferPosition, lineScopes)
+    fullLine = @getFullLine(editor, bufferPosition)
+    cursorScope = @getClassScope(fullLine, lineScopes)
     if cursorScope?
       @resolveIherited(cursorScope)
       containingScope = @findInScope(cursorScope, wordLower)
@@ -350,11 +353,10 @@
       return @modFiles[fileRef]+":"+lineRef.toString()
     return null

-  getUseSuggestion: (editor, bufferPosition, prefixLower) ->
+  getUseSuggestion: (line, prefixLower) ->
     useRegex = /^[ \t]*use[ \t]+/i
     wordRegex = /[a-z0-9_]+/gi
     suggestions = []
-    line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
     if line.match(useRegex)?
       unless prefixLower.match(wordRegex)?
         prefixLower = ""
@@ -373,11 +375,33 @@
         suggestions = @addPublicChildren(modName, suggestions, prefixLower, [])
     return suggestions # Unknown enable everything!!!!

-  getLineContext: (editor, bufferPosition) ->
+  getFullLine: (editor, bufferPosition) ->
+    F77Regex = /[a-z0-9_]*\.F$/i
+    fixedCommRegex = /^     [\S]/i
+    freeCommRegex = /&[ \t]*$/i
+    line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
+    #
+    fixedForm = false
+    if editor.getPath().match(F77Regex)
+      fixedForm = true
+    pRow = bufferPosition.row - 1
+    while pRow >= 0
+      pLine = editor.lineTextForBufferRow(pRow)
+      pLine = pLine.split('!')[0]
+      if fixedForm
+        unless line.match(fixedCommRegex)
+          break
+      else
+        unless pLine.match(freeCommRegex)
+          break
+      line = pLine.split('&')[0] + line
+      pRow = pRow - 1
+    return line
+
+  getLineContext: (line) ->
     useRegex = /^[ \t]*USE[ \t]/i
     subDefRegex = /^[ \t]*(PURE|ELEMENTAL|RECURSIVE)*[ \t]*(MODULE|PROGRAM|SUBROUTINE|FUNCTION)[ \t]/i
     typeDefRegex = /^[ \t]*(CLASS|TYPE)[ \t]*(IS)?[ \t]*\(/i
-    line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
     if line.match(useRegex)?
       return 1
     if line.match(useRegex)?
@@ -439,11 +463,10 @@
     i2 = typeDef.indexOf(')')
     return typeDef.substring(i1+1,i2)

-  getClassScope: (editor, bufferPosition, currScopes) ->
+  getClassScope: (line, currScopes) ->
     typeDerefCheck = /%/i
     objBreakReg = /[\/\-(.,+*<>=$:]/ig
     parenRepReg = /\((.+)\)/ig
-    line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
     #
     unless line.match(typeDerefCheck)?
       return null


Add link/unlink to usage message

Add link/unlink to usage message

--- src/apm-cli.coffee
+++ src/apm-cli.coffee
@@ -15,7 +15,7 @@
     Usage: apm <command>

     where <command> is one of:
-        available, help, install, list, publish, uninstall
+        available, help, install, link, list, publish, uninstall, unlink
   """
   options.alias('v', 'version').describe('v', 'Print the apm version')
   options.alias('h', 'help').describe('h', 'Print this usage message')


[r] not gonna use html5mode as I'm planning to host with github pages

see http://stackoverflow.com/a/17242276

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -8,11 +8,7 @@
   '20sq-proto.finale'
 ])

-.config ($locationProvider, $urlRouterProvider) ->
-  $locationProvider.html5Mode
-    enabled: true
-    requireBase: false
-  $locationProvider.hashPrefix '#'
+.config ($urlRouterProvider) ->
   $urlRouterProvider.otherwise '/'

 .constant 'appConst',


Add more static file types to be copied to public folder

--- lib/utils/post-build.coffee
+++ lib/utils/post-build.coffee
@@ -39,6 +39,6 @@
       )

     # Copy static assets to public folder.
-    glob directory + '/pages/**/?(*.jpg|*.png|*.pdf)', null, (err, files) ->
+    glob directory + '/pages/**/?(*.jpg|*.png|*.pdf|*.gif|*.ico)', null, (err, files) ->
       async.map files, copy, (err, results) ->
         cb(err, results)


ki-router: improved logging and fixed targetHostSame check

--- lib/web/js/ki-router.coffee
+++ lib/web/js/ki-router.coffee
@@ -55,8 +55,8 @@
   postExecutionListeners: []
   debug: false
   log: =>
-    if @debug
-      console.log.apply(this, arguments)
+    if @debug && console && console.log
+      console.log(arguments)
   add: (urlPattern, fn, metadata) =>
     @routes.push({route: new SinatraRouteParser(urlPattern), fn: fn, urlPattern: urlPattern, metadata: metadata})
   exec: (path) =>
@@ -101,11 +101,17 @@
       @addListener document, "click", (event) =>
         target = event.target
         if target
+          @log("Checking if click event should be rendered")
           aTag = @findATag(target)
-          if aTag && @leftMouseButton(event) && !@metakeyPressed(event) && @targetAttributeIsCurrentWindow(aTag) && @targetHostSame(aTag)
+          if @blog("- A tag", aTag) &&
+          @blog("- Left mouse button click", @leftMouseButton(event)) &&
+          @blog("- Not meta keys pressed", !@metakeyPressed(event)) &&
+          @blog("- Target attribute is current window", @targetAttributeIsCurrentWindow(aTag)) &&
+          @blog("- Link host same as current window", @targetHostSame(aTag))
             href = aTag.attributes.href.nodeValue
-            @log("Processing click", href)
+            @log("Click event passed all checks, rendering ", href)
             if !@pushStateSupport && @hashchangeSupport && @hashBaseUrl && @hashBaseUrl != window.location.pathname
+              @log("Using hashbang change to trigger rendering")
               event.preventDefault();
               window.location.href = @hashBaseUrl + "#!" + href
               return
@@ -114,6 +120,10 @@
               event.preventDefault();
               @updateUrl(href)

+  blog: (str, v) =>
+    @log(str + ", result: " + v)
+    v
+
   leftMouseButton: (event) =>
     event.which? && event.which == 1 || event.button == 0

@@ -122,7 +132,7 @@
       if target.tagName == "A"
         return target
       target = target.parentElement
-    null
+    false

   metakeyPressed: (event) =>
     (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)
@@ -141,11 +151,11 @@

   targetHostSame: (aTag) =>
     l = window.location
-    # Firefox 26 sets window.location.username to undefined by default
-    locationUserName = l.username
-    if !locationUserName
-      locationUserName = ""
-    aTag.host == l.host && aTag.protocol == l.protocol && aTag.username == locationUserName && aTag.password == aTag.password
+    # Firefox 26 sets aTag.username to "", other browsers use undefined
+    targetUserName = aTag.username
+    if targetUserName == ""
+      targetUserName = undefined
+    aTag.host == l.host && aTag.protocol == l.protocol && targetUserName == l.username && aTag.password == aTag.password

   attachLocationChangeListener: =>
     if @pushStateSupport
@@ -229,7 +239,7 @@
       ret = {}
       for match in matches.slice(1)
         if paramVerify && !paramVerify(match)
-          return null # parameter did not pass verifier -> abort
+          return false # parameter did not pass verifier -> abort
         key = @keys[i]
         i+=1
         #        console.log("Found item", match, key)


make aliases instead of repeating; rename something seems 'private'

--- minimongoid.coffee
+++ minimongoid.coffee
@@ -4,7 +4,7 @@

   constructor: (attributes = {}) ->
     if attributes._id
-      @attributes = @demongoize(attributes)
+      @attributes = @_demongoize(attributes)
       @id = attributes._id
     else
       @attributes = attributes
@@ -16,7 +16,7 @@
   save: ->
     return false unless @isValid()

-    attributes = @mongoize(@attributes)
+    attributes = @_mongoize(@attributes)
     attributes['_type'] = @constructor._type if @constructor._type?

     if @isPersisted()
@@ -34,19 +34,14 @@
       @constructor._collection.remove @id
       @id = null

-  mongoize: (attributes) ->
+  _mongoize: (attributes) ->
     taken = {}
     for name, value of attributes
       continue if name.match(/^_/)
       taken[name] = value
     taken

-  demongoize: (attributes) ->
-    taken = {}
-    for name, value of attributes
-      continue if name.match(/^_/)
-      taken[name] = value
-    taken
+  _demongoize: (attributes) -> @_mongoize(attributes)

   @_collection: undefined
   @_type: undefined
@@ -61,7 +56,7 @@
     @_collection.find(selector, options)

   @all: (selector = {}, options = {}) ->
-    @_collection.find(selector, options)
+    @where(selector, options)

   @toArray: (selector = {}, options = {}) ->
     for attributes in @where(selector, options).fetch()


Log saves via ModelBase

Log saves via ModelBase

--- server/code/model/base.coffee
+++ server/code/model/base.coffee
@@ -39,6 +39,7 @@
     else
       for k of @dbInstance
         @dbInstance[k] = @[k] if @hasOwnProperty k
+    console.log "base.coffee: saving", @constructor.name, @dbInstance
     @dbInstance.save callback

   @findAll: (callback) ->


app-settings-menu: Made menu closing optional on menu item clicked.

--- client/landing/app/MainApp/maincontroller/mainviews/appsettingsmenubutton.coffee
+++ client/landing/app/MainApp/maincontroller/mainviews/appsettingsmenubutton.coffee
@@ -10,12 +10,13 @@

       {mainTabView} = KD.getSingleton "mainView"
       menu.forEach (item, index) =>
+        item.closeMenuWhenClicked ?= yes

         item.callback = (contextmenu) =>
           view = mainTabView.activePane?.mainView
           item.eventName or= item.title
-          @contextMenu.destroy()
           view?.emit "menu.#{item.eventName}", item.eventName, item, contextmenu, @offset
+          @contextMenu.destroy() if item.closeMenuWhenClicked

       @createMenu event, menu


Add some Elixir spec snippets

Add some Elixir spec snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -39,6 +39,25 @@
     @Test(expected=${1:Exception}.class)
     public void ${2:throwsOnSomething}() {$0\n}
     """
+'.source.elixir':
+  Context:
+    prefix: 'con'
+    body: 'context "$1" do$0\nend'
+  Describe:
+    prefix: 'desc'
+    body: 'describe "$1" do$0\nend'
+  It:
+    prefix: 'it'
+    body: 'it "$1" do$0\nend'
+  'It One-Line':
+    prefix: 'itd'
+    body: 'it "$1", do: $0'
+  Let:
+    prefix: 'let'
+    body: 'let :$1 do$0\nend'
+  'Let One-Line':
+    prefix: 'letd'
+    body: 'let :$1, do: $0'
 '.source.ruby':
   Before:
     prefix: 'bef'


Add snippets for JS event methods

Add snippets for JS event methods

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -248,6 +248,18 @@
     'prefix': 'addeventlistener'
     'body': 'addEventListener($1, $2);$3'

+  'preventDefault':
+    'prefix': 'preventdefault'
+    'body': 'preventDefault();'
+
+  'stopPropagation':
+    'prefix': 'stoppropagation'
+    'body': 'stopPropagation();'
+
+  'stopImmediatePropagation':
+    'prefix': 'stopimmediatepropagation'
+    'body': 'stopImmediatePropagation();'
+
   'requestAnimationFrame':
     'prefix': 'raf'
     'body': 'requestAnimationFrame($1);$2'


Lost ticket lists if second tab with other user was opened.

--- app/assets/javascripts/app/controllers/_dashboard/ticket_search.js.coffee
+++ app/assets/javascripts/app/controllers/_dashboard/ticket_search.js.coffee
@@ -7,7 +7,7 @@
     @item_from = 1
     @navupdate '#'

-    @key = @name + @Session.get('id')
+    @key = @name + Math.floor( Math.random() * 999999 ).toString()

     # render
     @fetch()


change to use JSON response

change to use JSON response

--- src/bonusly.coffee
+++ src/bonusly.coffee
@@ -34,12 +34,15 @@
       .get() (err, res, body) ->
         switch res.statusCode
           when 200
-            bonus_data = JSON.parse body
-            bonuses = bonus_data.result
+            data = JSON.parse body
+            bonuses = data.result
             bonuses_text = ("From #{bonus.giver.short_name} to #{bonus.receiver.short_name}: #{bonus.amount_with_currency} #{bonus.reason}" for bonus in bonuses).join('\n')
             msg.send bonuses_text
+          when 400
+            data = JSON.parse body
+            msg.send data.message
           else
-            msg.reply "Request (#{service}#{path}) failed (#{res.statusCode})."
+            msg.send "Request (#{service}#{path}) failed (#{res.statusCode})."

   robot.respond /(bonusly)? (give) ?(.*)?/i, (msg) ->
@@ -61,6 +64,10 @@
       .post(post) (err, res, body) ->
         switch res.statusCode
           when 200
-            msg.send body
+            data = JSON.parse body
+            msg.send data.result
+          when 400
+            data = JSON.parse body
+            msg.send data.message
           else
-            msg.reply "Failed to give: (#{res.statusCode}). Tried to post (#{post}) to (#{service}#{path})"
+            msg.send "Failed to give: (#{res.statusCode}). Tried to post (#{post}) to (#{service}#{path})"


Explicitly require analyticsHooks

Explicitly require analyticsHooks

--- components/cta_bar/view.coffee
+++ components/cta_bar/view.coffee
@@ -3,6 +3,7 @@
 Form = require '../mixins/form.coffee'
 AuthModalView = require '../auth_modal/view.coffee'
 Cookies = require '../cookies/index.coffee'
+analyticsHooks = require '../../lib/analytics_hooks.coffee'
 CurrentUser = require '../../models/current_user.coffee'
 template = -> require('./template.jade') arguments...

@@ -50,6 +51,7 @@
     this

   transitionIn: (cb) ->
+    debugger
     @__transition__ 'in', cb
     analyticsHooks.trigger 'artist_page:cta:shown'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -15,4 +15,6 @@

   obj.meter 'health'

+  equal
+
 module()


pass runtime through to the compiler

pass runtime through to the compiler

--- src/command.coffee
+++ src/command.coffee
@@ -346,6 +346,7 @@
     bare: opts.bare
     header: opts.compile
     sourceMap: opts.map
+    runtime : opts.runtime
   }
   if filename
     if base


We shouldn't ever end up in that error state

Unless the compiler royally fucks up, which it shouldn't.

--- src/compile.coffee
+++ src/compile.coffee
@@ -182,12 +182,6 @@
     model.bind? "change:#{ast.arguments[0]}", update
     dynamic

-
-compile = (ast, model, controller) ->
-  action = Compile[ast.type]
-  if action
-    action(ast, model, controller)
-  else
-    throw SyntaxError "unknown type '#{ast.type}'"
+compile = (ast, model, controller) -> Compile[ast.type](ast, model, controller)

 exports.compile = compile


Also fixed second unit test >.<

Also fixed second unit test >.<

--- test/timeout.coffee
+++ test/timeout.coffee
@@ -21,7 +21,7 @@
                 return new NodeXHR.XMLHttpRequest()

             xhr.call(
-                url:      "http://labs.marviq.net/xdm/example.json"
+                url:      "http://www.qwerios.nl/projects/xdm/example.json"
                 method:   "GET"
                 type:     "json"
             )


Updated client size. TODO: get this thing programmatically!

--- tests/assets.coffee
+++ tests/assets.coffee
@@ -123,7 +123,7 @@
     c = t.client(zapp.app)
     c.get '/zappa/zappa.js', (err, res) ->
       t.equal 'content-type', res.headers['content-type'], 'application/javascript'
-      t.equal 'length', res.headers['content-length'], '6865'
+      t.equal 'length', res.headers['content-length'], '7037'

   'zappa (automatic)': (t) ->
     t.expect 'content-type', 'length'
@@ -135,4 +135,4 @@
     c = t.client(zapp.app)
     c.get '/zappa/zappa.js', (err, res) ->
       t.equal 'content-type', res.headers['content-type'], 'application/javascript'
-      t.equal 'length', res.headers['content-length'], '6865'+      t.equal 'length', res.headers['content-length'], '7037'


moar finer grained meta stuff

moar finer grained meta stuff

--- api/apps/verticals/model.coffee
+++ api/apps/verticals/model.coffee
@@ -21,14 +21,17 @@
 schema = (->
   id: @objectId()
   title: @string().allow('', null)
+  meta_title: @string().allow('', null)
   description: @string().allow('', null)
   slug: @string().allow('', null)
   partner_logo_url: @string().allow('', null)
   partner_website_url: @string().allow('', null)
   thumbnail_url: @string().allow('', null)
+  featured_articles_header: @string().allow('', null)
   featured_article_ids: @array().items(@objectId()).allow(null)
   start_at: @date().allow(null)
   end_at: @date().allow(null)
+  title: @string().allow('', null)
 ).call Joi

 querySchema = (->


heatmap: error about data.x (or .y) - no need if have data.xcat (or .ycat)

--- src/heatmap.coffee
+++ src/heatmap.coffee
@@ -30,8 +30,8 @@
                                  #   optionally, include xcat and ycat in place of x and y
                                  #       then: categorial scales on x and y axis

-        d3panels.displayError("heatmap: data.x is missing") unless data.x?
-        d3panels.displayError("heatmap: data.y is missing") unless data.y?
+        d3panels.displayError("heatmap: data.x is missing") unless data.x? or data.xcat?
+        d3panels.displayError("heatmap: data.y is missing") unless data.y? or data.ycat?
         d3panels.displayError("heatmap: data.z is missing") unless data.z?

         # xcat and ycat included?


Made conditional more explicit to resolve a bug where the latter operation was being included in the former's is()

--- src/hotkeys.coffee
+++ src/hotkeys.coffee
@@ -179,7 +179,9 @@

             for namespace, view of @views
                 bindings = view.bindings
-                unless $(view.context?.el).is ":hidden" or Object.keys(bindings).length == 0
+                isHidden = $(view.context?.el).is ":hidden"
+                hasNoKeys = Object.keys(bindings).length is 0
+                unless isHidden or hasNoKeys
                     h1 = $ "<h1/>", text: view.label
                     ul = $ "<ul/>"
                     for binding in bindings