Color change

Color change

--- site/app.coffee
+++ site/app.coffee
@@ -145,7 +145,11 @@
     mappedData[loc] = {}
   for row in epidata
     wk = row.epiweek%100
-    mappedData[row.location][wk] = row.value
+    yr = (Math.round(row.epiweek / 100))
+    if yr == 2015 and wk >= 40
+      mappedData[row.location][wk] = row.value
+    if yr == 2016 and wk <= 39
+      mappedData[row.location][wk] = row.value
   for region in HHS_REGIONS
     weeks = nonInfluenzaWeekData[region]
     for state in REGION2STATE[region]
@@ -444,22 +448,19 @@
       @dataTimeline.html("Nowcasting epi-week " + epiweek2%100 + " " + datestr)
       callback = (epidata) =>
         NonInfluenzaData = calculateNonInfluenzaData(epidata)
-        callback = (epidata) =>
-          @colors = {}
-          @mapData = {}
-          colorData = calculateColor(NonInfluenzaData, epidata, epiweek2)
-          for row in epidata
-            if row.epiweek == epiweek2
-              ili = row.value
-              v = colorData[row.location]
-              c = ('0' + Math.round(0x3f + v * 0xc0).toString(16)).slice(-2)
-              @colors[row.location] = '#' + c + '4040'
-              @mapData[row.location] = row
-          @renderMap()
-        handler = getEpidataHander(callback)
-        Epidata_nowcast_multi(handler, LOCATIONS, epiweek1, epiweek2)
+        @colors = {}
+        @mapData = {}
+        colorData = calculateColor(NonInfluenzaData, epidata, epiweek2)
+        for row in epidata
+          if row.epiweek == epiweek2
+            ili = row.value
+            v = colorData[row.location]
+            c = ('0' + Math.round(0x3f + v * 0xc0).toString(16)).slice(-2)
+            @colors[row.location] = '#' + c + '4040'
+            @mapData[row.location] = row
+        @renderMap()
       handler = getEpidataHander(callback)
-      Epidata_nowcast_multi(handler, LOCATIONS, 201540, 201620)
+      Epidata_nowcast_multi(handler, LOCATIONS, 201540, 201710)
     handler = getEpidataHander(callback)
     Epidata_nowcast_single(handler, 'nat')


we want to make POST requests here…

we want to make POST requests here…

--- app/assets/javascripts/application.js.erb.coffee
+++ app/assets/javascripts/application.js.erb.coffee
@@ -21,6 +21,7 @@
                         true
   $.ajax
     url: '/ajax/ask' # TODO: find a way to use rake routes instead of hardcoding them here
+    type: 'POST'
     data:
       rcpt: $("input[name=qb-to]").val()
       question: $("textarea[name=qb-question]").val()


[polling-service]: add possibility to enable or disable polling service on a element

--- app/assets/javascripts/polling_service.js.coffee
+++ app/assets/javascripts/polling_service.js.coffee
@@ -5,11 +5,18 @@

   updateElement= (element) ->
     $element = $(element)
-    return if $element.data('polling_is_updating') == true
-    $element.data( 'polling_is_updating', true );
+
+    # Ignore update if update already in the update queue
+    return if $element.data('queuedForPolling') == true
+
+    # Ignore update if element disabled
+    return if $element.data('pollingIsDisabled') == true
+
+    $element.data( 'queuedForPolling', true );
     url = $element.data('updatePath')
     return unless url
     dataType = if url.search(/^[^\?]+\.js/) >= 0 then 'script' else 'html'
+
     $.ajax
       url: url,
       dataType: dataType,
@@ -32,8 +39,8 @@
           if dataType == 'html'
             $element.replaceWith(data)
       error: () ->
-        $element.data( 'polling_is_updating', false );
       complete: () ->
+        $element.data( 'queuedForPolling', false );
         $('body').trigger( 'polling:update_complete' );

@@ -52,7 +59,6 @@

       # modulo operation: rest of current timestamp divided by element's interval should be zero
       shouldUpdate = (timestamp % Math.round(updateInterval/ interval))==0
-
       updateElement($element) if shouldUpdate

@@ -79,3 +85,13 @@
   # For example, update('inquiries') will update data-update-path='inquiriy/inquiries/list'.
   @update= (name) ->
     $("*[data-update-path*='#{name}']").each () -> updateElement(this)
+
+  # Pop element from the update queue.
+  # Element selector -> css selector
+  @disableElement= (elementSelector) ->
+    $(elementSelector).data('pollingIsDisabled', true)
+
+  # Push element to the update queue.
+  # Element selector -> css selector
+  @enableElement= (elementSelector) ->
+    $(elementSelector).data('pollingIsDisabled', false)


can add lines

can add lines

--- app/assets/javascripts/views/mapping_view.coffee
+++ app/assets/javascripts/views/mapping_view.coffee
@@ -41,6 +41,13 @@
     @getUserLocation((latlng)=>
       @farm.addPoint(latlng)
     )
+    @renderMapPolygon()
+
+  renderMapPolygon: ->
+    @map.removeLayer(@polyline) if @polyline?
+    @polyline = L.polyline(@farm.points)
+    @polyline.addTo(@map)
+

   getUserLocation: (callback) ->
     foundListener = null


Fix  coffeescript style violations

Fix  coffeescript style violations

--- app/js/jail_iframe/classes/control_iframe.coffee
+++ app/js/jail_iframe/classes/control_iframe.coffee
@@ -3,12 +3,12 @@
   constructor: ->
     @el = document.createElement('iframe');
     #need to append to outer document before we can access frame document.
-    FactlinkJailRoot.$factlinkCoreContainer.append(@el);
+    FactlinkJailRoot.$factlinkCoreContainer.append(@el)
     @doc = @el.contentWindow.document;
-    @doc.open();
+    @doc.open()
     #need doctype to avoid quirks mode
-    @doc.write('<!DOCTYPE html><title></title>');
-    @doc.close();
+    @doc.write('<!DOCTYPE html><title></title>')
+    @doc.close()

   destroy: ->
     return unless @el


Smaller close 'X' icon in GrowlNotifications component

--- app/scripts/components/GrowlNotifications.coffee
+++ app/scripts/components/GrowlNotifications.coffee
@@ -24,7 +24,7 @@
         template: Ember.Handlebars.compile [
             ''
             ''
-                ''
+                ''
             ''
             ''
                 '{{view.content.title}}'


comment out tests for now

comment out tests for now

--- apps/artwork/components/bid/test/template.coffee
+++ apps/artwork/components/bid/test/template.coffee
@@ -46,7 +46,7 @@

       @$ = cheerio.load(@html)

-    it 'display artwork current bid, bid counts, reserve status', ->
+    xit 'display artwork current bid, bid counts, reserve status', ->
       @$('.artwork-auction-bid-module__bid-status-amount').text().should.equal '$7,000'
       @$('.artwork-auction-bid-module__bid-status-count').text().should.containEql '3 Bids, Reserve not met'

@@ -242,5 +242,5 @@
       @$('.artwork-auction-bid-module__bid-status-title').text().should.equal 'Starting Bid:'

-    it 'do not display number of bids', ->
+    xit 'do not display number of bids', ->
       @$('.artwork-auction-bid-module__bid-status-count').text().should.equal 'Reserve not met'


activity: rename settings menu factory methods

--- client/Social/Activity/views/settingsview.coffee
+++ client/Social/Activity/views/settingsview.coffee
@@ -23,7 +23,7 @@

   addMenuItem: (title, callback) -> @menu[title] = {callback}

-  getGenericMenu: ->
+  addFollowActionMenu: ->

     {socialapi} = KD.singletons
     post = @getData()
@@ -46,7 +46,7 @@
     return @menu

-  getOwnerMenu: ->
+  addOwnerMenu: ->

     @addMenuItem 'Edit Post', @lazyBound 'emit', 'ActivityEditIsClicked'
     @addMenuItem 'Delete Post', @bound 'confirmDeletePost'
@@ -54,7 +54,7 @@
     return @menu

-  getAdminMenu: ->
+  addAdminMenu: ->

     post = @getData()

@@ -83,12 +83,12 @@

   settingsMenu: ->

-    @getGenericMenu()
+    @addFollowActionMenu()

     if KD.isMyPost @getData()
-    then @getOwnerMenu()
+    then @addOwnerMenu()
     else if KD.checkFlag('super-admin') or KD.hasAccess('delete posts')
-    then @getAdminMenu()
+    then @addAdminMenu()

     return @menu


minor linkview fix

minor linkview fix

--- client/app/CommonViews/linkviews/linkview.coffee
+++ client/app/CommonViews/linkviews/linkview.coffee
@@ -33,8 +33,10 @@
       @emit "OriginLoadComplete", data

     if origin.constructorName
-      KD.remote.cacheable origin.constructorName, origin.id, (err, origin)=>
-        callback origin
+      KD.remote.cacheable origin.constructorName, origin.id, (err, originModel)=>
+        unless originModel
+        then warn "couldn't get the model via cacheable", origin.constructorName, origin.id
+        else callback originModel
     else
       callback origin


ensure template's deps are installed

ensure template's deps are installed

--- lib/api/init.coffee
+++ lib/api/init.coffee
@@ -19,6 +19,7 @@

   execute: (opts) ->
     configure_options.call(@, opts).with(@)
+      .then(install_template_dependencies)
       .then(get_user_init_file)
       .then(run_user_before_function)
       .then(remove_overrides_from_prompt)
@@ -77,6 +78,10 @@
   parse_version = (name) ->
     match = name.match(/@+([^@]*)$/)
     if match then match[1] else false
+
+  install_template_dependencies = ->
+    pkg = path.join(@sprout_path, 'package.json')
+    if fs.existsSync(pkg) then nodefn.call(exec, "npm install", cwd: @sprout_path)

   get_user_init_file = ->
     init_file = path.join(@sprout_path, 'init.coffee')


Make change backwards compatible

Make change backwards compatible

--- lib/autosave.coffee
+++ lib/autosave.coffee
@@ -19,7 +19,7 @@
       if event.target is window
         @autosaveAllPaneItems()
       else if editorElement = event.target.closest('atom-text-editor:not(mini)')
-        unless event.target.matches('atom-text-editor:not(mini)') or editorElement.contains(event.relatedTarget)
+        unless editorElement.contains(event.relatedTarget) or (editorElement.lightDOM and editorElement is event.target)
           @autosavePaneItem(editorElement.getModel())

     window.addEventListener('blur', handleBlur, true)


Regression for cwd fixed

Regression for cwd fixed

--- lib/debugger.coffee
+++ lib/debugger.coffee
@@ -35,16 +35,19 @@
           .getActiveTextEditor()
           .getPath()

+        dbgFile = file or appPath
+        cwd = path.dirname(dbgFile)
+
         args = []
         args = args.concat (nodeArgs.split(' ')) if nodeArgs
         args.push "--debug-brk=#{port}"
-        args.push file or appPath
+        args.push dbgFile
         args = args.concat (appArgs.split(' ')) if appArgs

         logger.error 'spawn', {args:args, env:env}
         @process = childprocess.spawn nodePath, args, {
           detached: true
-          cwd: path.dirname(args[1])
+          cwd: cwd
           env: env if env
         }


:bug: fix to follow restorePoint is deferred

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -71,7 +71,7 @@
     @initialize()
     @setTarget(@new(@target)) if _.isString(@target)

-  restorePoint: (selection) ->
+  restorePoint: (selection, fn) ->
     which = if @wasNeedStay then 'head' else 'start'
     if swrap(selection).getProperties().head?
       # Deffer restoring cursor point.
@@ -79,6 +79,7 @@
       # when intersecting selection is destroyed, cursor is moved automatically.
       @onDidFinishOperation ->
         swrap(selection).setBufferPositionTo(which, fromProperty: true)
+        fn?(selection)
     else
       selection.destroy()

@@ -613,9 +614,10 @@

   mutateSelection: (selection) ->
     selection[@indentFunction]()
-    @restorePoint(selection)
     unless @needStay()
-      selection.cursor.moveToFirstCharacterOfLine()
+      fn = (selection) ->
+        selection.cursor.moveToFirstCharacterOfLine()
+    @restorePoint(selection, fn)

 class Outdent extends Indent
   @extend()


Use _.escape to escape tooltip titles

Use _.escape to escape tooltip titles

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -1,4 +1,5 @@
 {$, View} = require 'atom'
+_ = require 'underscore-plus'
 path = require 'path'

 module.exports =
@@ -25,7 +26,7 @@

     if itemPath = @item.getPath?()
       @setTooltip
-        title: itemPath
+        title: _.escape(itemPath)
         delay:
           show: 2000
           hide: 100


allow reset socket and handle error and close events

--- lib/terminal.coffee
+++ lib/terminal.coffee
@@ -31,9 +31,13 @@
         resolve()

       @socket.on 'message', (message) =>
-        console.log('message coming out of socket', message)
         @emit 'message', utf8.decode(atob(message))
-        console.log('message over localStorage', message)
+
+      @socket.on 'close', =>
+        @emit 'close'
+
+      @socket.on 'error', (e) =>
+        @emit 'error', e

   url: ->
     protocol = if @port == 443 then 'wss' else 'ws'
@@ -41,10 +45,7 @@

   reset: ->
     logger.info('term:reset')
-    @socket.close().then =>
-      @connect()
-    .catch (err) =>
-      @emit 'error', err
+    @socket.reset()

   send: (msg) ->
     @socket.send(msg)


:new: Support message-specific name Reference: https://github.com/noseglid/atom-build/pull/251/files#r47442663

--- lib/validate.coffee
+++ lib/validate.coffee
@@ -51,6 +51,6 @@
         result.filePath = null
       result.range = Range.fromObject result.range if result.range?
       result.key = JSON.stringify(result)
-      result.linter = linter.name
+      result.linter = result.name or linter.name or null
       Validate.messages(result.trace, linter) if result.trace and result.trace.length
     return


Add test for nested mock() calls.

Add test for nested mock() calls.

--- test/TinyMockJS.test.coffee
+++ test/TinyMockJS.test.coffee
@@ -101,6 +101,14 @@
       o.my_method(1)                  # otherwise we'll get a 'my_method not called' error
       o.my_method(2)
     o.my_method.should.equal(original_method)
+
+  it "can be nested (cannot see the need, but just to verify that it will work)", ->
+    mock (m1) ->
+      mock (m2) ->
+        m1.expects("my_method1")
+        m2.expects("my_method2")
+        m1.my_method1()
+        m2.my_method2()

 describe "expects(method_name)", ->


Add Page#field to locate input fields by name, id, or label.

--- application/chrome/content/wesabe/dom/Page.coffee
+++ application/chrome/content/wesabe/dom/Page.coffee
@@ -1,4 +1,3 @@
-{Pathway} = require 'xpath'
 Colorizer = require 'util/Colorizer'
 inspect   = require 'util/inspect'
 type      = require 'lang/type'
@@ -8,6 +7,7 @@
 snapshot  = require 'canvas/snapshot'
 english   = require 'util/words'
 privacy   = require 'util/privacy'
+{Pathway, bind} = require 'xpath'
 {tryCatch, tryThrow} = require 'util/try'

 # Internal: Event name to internal type mapping.
@@ -334,6 +334,28 @@
       @fireEvent element, 'focusout'
       @fireEvent element, 'blur'

+  # Public: Finds an input or select matching the id, name, or label.
+  #
+  # idNameOrLabel - A field id, name, or label.
+  # scope - An Element to restrict the xpath search to, ignored if xpathOrNode
+  #         is a Node.
+  #
+  # Examples
+  #
+  #   page.fill page.field('username'), answers.username
+  #
+  # Returns either a tainted Element or null if no matching Element is found.
+  field: (idNameOrLabel, scope) ->
+    if field = @find bind('//*[name()="input" or name()="select"][@id=":label" or @name=":label"]', label: idNameOrLabel)
+      return field
+
+    if label = @find bind('//label[contains(string(.), ":label")]', label: idNameOrLabel)
+      if fieldId = label.getAttribute('for')
+        return @byId fieldId
+
+    return null
+
+
   # Public: Clicks the given element by firing mousedown, click,
   # and mouseup events.
   #


spec for multi-drag

spec for multi-drag

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -3611,6 +3611,7 @@
         [dragStartEvent, dragEnterEvent, dropEvent] =
             eventHelpers.buildInternalDragEvents(deltaFile, alphaDir.querySelector('.header'))
         treeView.onDragStart(dragStartEvent)
+        expect(deltaFile).toHaveClass('selected')
         treeView.onDragEnter(dragEnterEvent)
         expect(alphaDir).toHaveClass('selected')

@@ -3674,6 +3675,30 @@
           editors = atom.workspace.getTextEditors()
           expect(editors[0].getPath()).toBe deltaFilePath.replace('gamma', 'alpha')
           expect(editors[1].getPath()).toBe deltaFilePath2
+
+    describe "when dropping multiple FileViews onto a DirectoryView's header", ->
+      it "should move the files to the hovered directory", ->
+        # Dragging delta.txt onto alphaDir
+        alphaDir = findDirectoryContainingText(treeView.roots[0], 'alpha')
+        alphaDir.expand()
+
+        gammaDir = findDirectoryContainingText(treeView.roots[0], 'gamma')
+        gammaDir.expand()
+        gammaFiles = [].slice.call(gammaDir.entries.children, 1, 3)
+
+        [dragStartEvent, dragEnterEvent, dropEvent] =
+            eventHelpers.buildInternalDragEvents([gammaFiles], alphaDir.querySelector('.header'), alphaDir)
+
+        runs ->
+          treeView.onDragStart(dragStartEvent)
+          treeView.onDrop(dropEvent)
+          expect(alphaDir.children.length).toBe 2
+
+        waitsFor "directory view contents to refresh", ->
+          findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length > 2
+
+        runs ->
+          expect(findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length).toBe 4

     describe "when dropping a DirectoryView onto a DirectoryView's header", ->
       beforeEach ->


Revert, moved back to App.ControllerContent to render of new wizard screen.

--- app/assets/javascripts/app/controllers/getting_started.js.coffee
+++ app/assets/javascripts/app/controllers/getting_started.js.coffee
@@ -1,4 +1,4 @@
-class Index extends App.Controller
+class Index extends App.ControllerContent
   className: 'getstarted fit'

   constructor: ->
@@ -61,7 +61,7 @@
 App.Config.set( 'getting_started', Index, 'Routes' )

-class AutoWizard extends App.Controller
+class AutoWizard extends App.ControllerContent
   className: 'getstarted fit'

   constructor: ->
@@ -130,7 +130,7 @@
 App.Config.set( 'getting_started/auto_wizard/:token', AutoWizard, 'Routes' )

-class Admin extends App.Controller
+class Admin extends App.ControllerContent
   className: 'getstarted fit'
   events:
     'submit form': 'submit'
@@ -510,7 +510,7 @@
 App.Config.set( 'getting_started/email_notification', EmailNotification, 'Routes' )

-class Channel extends App.Controller
+class Channel extends App.ControllerContent
   className: 'getstarted fit'

   constructor: ->
@@ -563,7 +563,7 @@

 App.Config.set( 'getting_started/channel', Channel, 'Routes' )

-class ChannelEmailPreConfigured extends App.Controller
+class ChannelEmailPreConfigured extends App.ControllerContent
   className: 'getstarted fit'

   constructor: ->
@@ -869,7 +869,7 @@

 App.Config.set( 'getting_started/channel/email', ChannelEmail, 'Routes' )

-class Agent extends App.Controller
+class Agent extends App.ControllerContent
   className: 'getstarted fit'
   events:
     'submit form': 'submit'
@@ -969,7 +969,7 @@

 App.Config.set( 'getting_started/agents', Agent, 'Routes' )

-class Channel extends App.Controller
+class Channel extends App.ControllerContent
   className: 'getstarted fit'

   constructor: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -26,6 +26,6 @@
     y: 20

   equals engine.first('type=testObj').length, 1
-  equals engine.fin
+  equals engine.find('')

 module()


Add test for finish event of WritableFSStream

--- specs/WritableFSStream.spec.coffee
+++ specs/WritableFSStream.spec.coffee
@@ -85,3 +85,14 @@
         stream.write createFile('/project/src/readme', '/project/src', 'readme')

         fs.readFile('/project/dest/readme').should.equal 'readme'
+
+    describe 'events', ->
+      it 'should fire finish event', (done) ->
+        fs = createFS fsData()
+
+        stream = fs.createWriteStream()
+
+        stream.on 'finish', ->
+          done()
+
+        stream.end()


restore 'Last Value' text in graph footer

--- app/assets/javascripts/states/monitoring/helpers/graph.js.coffee
+++ app/assets/javascripts/states/monitoring/helpers/graph.js.coffee
@@ -67,7 +67,7 @@
     last_date   = vals[vals.length-1][0]
     last_val    = vals[vals.length-1][1]
     footer      = $(div).find(".footer")
-    footer_text = metric.format_value(last_val, last_date) # referenced later in unhighlightCallback
+    footer_text = "Last Value: " + metric.format_value(last_val, last_date) # referenced later in unhighlightCallback
     footer.text(footer_text)

     # dygraph options
@@ -117,7 +117,7 @@
     opts =
       highlightCallback: (e, x, pts, row, seriesName) =>
         text = metric.format_value(pts[0].yval, x)
-        footer.text(text)
+        footer.text("Highlighted Value: " + text)
         @show_tooltip(g, el, pts[0].canvasx, e.pageY, text)

       unhighlightCallback: (e) =>


tests for fsSelect

tests for fsSelect

--- test/fs/fsSelectSpec.coffee
+++ test/fs/fsSelectSpec.coffee
@@ -44,3 +44,13 @@

     element.find('.fs-select-clear-btn').click()
     expect($scope.value).toBe(null)
+
+  it 'should set scope value as it were in items array', ->
+    $scope.items = [
+      { id: 1, label: "first" },
+      { id: 2, label: "second" }
+    ]
+
+    element = compile('<div fs-select ng-model="value" items="items">{{ item.label }}</div>')
+    $scope.$apply(-> $scope.value = $scope.items.first)
+    expect($scope.value).toBe $scope.items.first


QUALITY: Alter clearMenus header function

--- src/app/common/services/header-service.coffee
+++ src/app/common/services/header-service.coffee
@@ -4,7 +4,7 @@
 #
 .factory("headerService", ($rootScope) ->
   # Internal object that stores the menus
-  $rootScope.headerMenuData = [ ]
+  $rootScope.headerMenuData = []
   # Internal object that stores the visbility of the header
   $rootScope.showHeader = true

@@ -20,7 +20,7 @@
   # Clears all menus in the header
   #
   headerService.clearMenus = ->
-    $rootScope.headerMenuData.length = 0
+    $rootScope.headerMenuData = []

   #
   # Set new menus where:


landing: make sure encoded team name is shown

--- client/landing/site.landing/coffee/team/tabs/teamlogintab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamlogintab.coffee
@@ -3,6 +3,7 @@
 JView           = require './../../core/jview'
 MainHeaderView  = require './../../core/mainheaderview'
 LoginInlineForm = require './../../login/loginform'
+Encoder         = require 'htmlencode'

 track = (action) ->

@@ -68,11 +69,15 @@

   pistachio: ->

+    # this is to make sure that already created teams with problematic names
+    # are not causing any problems as well. ~Umut
+    title = Encoder.htmlEncode Encoder.htmlDecode kd.config.group.title
+
     """
     {{> @header }}

       {{> @logo}}
-
Sign in to #{kd.config.group.title}

+
Sign in to #{title}

       {{> @form}}


Navigate to Fact url when clicking on evidence

--- core/app/backbone/views/fact_relations/fact_relation_view.coffee
+++ core/app/backbone/views/fact_relations/fact_relation_view.coffee
@@ -108,5 +108,13 @@
     creator: @model.creator().toJSON()

   onRender: ->
-    @factBaseView.show new FactBaseView(model: @model)
+    @factBaseView.show @_factBaseView()
     @factRelationActivityView.show new FactRelationActivityView(model: @model)
+
+  _factBaseView: ->
+    fbv = new FactBaseView(model: @model)
+
+    @bindTo fbv, 'click:body', =>
+      Backbone.history.navigate @model.getFact().friendlyUrl(), true
+
+    fbv


sagews printing: change how the link is made to avoid caching.

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -1612,8 +1612,9 @@
                             if err
                                 cb(err)
                             else
-                                window.open(mesg.url,'_blank')
-                                dialog.find(".salvus-file-printing-link").attr('href', mesg.url).text(pdf).show()
+                                url = mesg.url + "?nocache=#{Math.random()}"
+                                window.open(url,'_blank')
+                                dialog.find(".salvus-file-printing-link").attr('href', url).text(pdf).show()
                                 cb()
             ], (err) =>
                 dialog.find(".btn-submit").icon_spin(false)


Make terminal try harder to focus.

Make terminal try harder to focus.

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -1262,6 +1262,7 @@
             top = @editor.editor_top_position() + @element.find(".salvus-console-topbar").height()
             e.height($(window).height() - top)
             @console.focus()
+            setTimeout( (() => @console.focus()), 150) # failing to focus is infuriating.

 class Worksheet extends FileEditor
     constructor: (@editor, @filename, content, opts) ->


missing mappers

missing mappers

--- bokehjs/src/coffee/common/base.coffee
+++ bokehjs/src/coffee/common/base.coffee
@@ -13,8 +13,6 @@
 console.log('Bokeh: setting prefix to', Config.prefix)

 locations =
-  OpenURL:                  'action/open_url'
-
   Plot:                     require './plot'
   GMapPlot:                 require './gmap_plot'
   GeoJSPlot:                require './geojs_plot'
@@ -28,7 +26,11 @@
   Selector:                 require './selector'
   ToolEvents:               require './tool_events'

+  OpenURL:                  require '../action/open_url'
+
+  CategoricalMapper:        require '../mapper/categorical_mapper'
   LinearColorMapper:        require '../mapper/linear_color_mapper'
+  LinearMapper:             require '../mapper/linear_mapper'

   DataRange1d:              require '../range/data_range1d'
   FactorRange:              require '../range/factor_range'


Don't preload images that pass /exists test

--- scripts/home-files.coffee
+++ scripts/home-files.coffee
@@ -157,6 +157,7 @@
             deleteLink.classList.remove('hidden')

     preload: ->
+        return if not @blob?
         for file in @blob.files
             if file.type.startsWith('image/') and file.type != 'image/gif'
                 _ = document.createElement('img')


Some debugging. Initial stream code.

Some debugging. Initial stream code.

--- src/pushbullet.coffee
+++ src/pushbullet.coffee
@@ -19,10 +19,17 @@
   apiKey = process.env.HUBOT_PUSHBULLET_API_KEY
 ##check an env var to see if we want to open a websocket stream (HUBOT_PUSHBULLET_STREAM?)

+
 PushBullet = require 'pushbullet'
 pushbullet = new PushBullet apiKey

 module.exports = (robot) ->
+  if process.env.HUBOT_PUSHBULLET_STREAM
+    pbStream = pushbullet.stream()
+    pbStream.on 'push', (push) ->
+      console.log "got a push: #{push.title}"
+    pbStream.connect()
+
   robot.respond /(?:pb|pushbullet) last/, (msg) ->
     options =
       limit: 1,


[term] literal() coerces toString

[term] literal() coerces toString

--- term.coffee
+++ term.coffee
@@ -191,7 +191,7 @@
             uri = $rdf.ns[uri] + name
         new $rdf.Symbol uri
     literal: (val, lang, dt) ->
-        new $rdf.Literal val.toString(), lang, dt
+        new $rdf.Literal "#{val}", lang, dt
     bnode: (id) ->
         new $rdf.BlankNode id
     formula: ->


adapt test to latest data

adapt test to latest data

--- test.coffee
+++ test.coffee
@@ -103,9 +103,9 @@
 			t.strictEqual data[0].name, 'Verkehrsverbund Brandenburg-Berlin'
 			t.done()

-		'lines': (t) -> s.lines(true, 243).then (data) ->
+		'lines': (t) -> s.lines(true, 245).then (data) ->
 			t.strictEqual data.length,      1
-			t.strictEqual data[0].id,       243
+			t.strictEqual data[0].id,       245
 			t.strictEqual data[0].name,     '100'
 			t.strictEqual data[0].type,     'bus'
 			t.done()
@@ -116,8 +116,8 @@
 			t.strictEqual data[0].name,     'U Spichernstr. (Berlin)'
 			t.done()

-		'trips': (t) -> s.trips(true, 80075).then (data) ->
+		'trips': (t) -> s.trips(true, 77336).then (data) ->
 			t.strictEqual data.length,      1
-			t.strictEqual data[0].id,       80075
-			t.strictEqual data[0].lineId,   544 # U9
+			t.strictEqual data[0].id,       77336
+			t.strictEqual data[0].lineId,   548 # U9
 			t.done()


Pass group information onwards

Pass group information onwards

--- src/components/CreateObject.coffee
+++ src/components/CreateObject.coffee
@@ -7,8 +7,12 @@
     @outPorts =
       out: new noflo.Port()

+    @inPorts.start.on 'begingroup', (group) =>
+      @outPorts.out.beginGroup group
     @inPorts.start.on "data", =>
       @outPorts.out.send {}
       @outPorts.out.disconnect()
+    @inPorts.start.on 'endgroup', =>
+      @outPorts.out.endGroup()

 exports.getComponent = -> new CreateObject


Fixed web_server example to be compatible with Node v0.1.30

--- examples/web_server.coffee
+++ examples/web_server.coffee
@@ -4,9 +4,9 @@

 server: http.createServer (req, res) ->
   res.sendHeader 200, {'Content-Type': 'text/plain'}
-  res.sendBody 'Hello, World!'
-  res.finish()
+  res.write 'Hello, World!'
+  res.close()

 server.listen 3000

-puts "Server running at http://localhost:3000/"+puts "Server running at http://localhost:3000/"


only call failed handler function, if defined

--- lib/assets/javascripts/firehose/web_socket.js.coffee
+++ lib/assets/javascripts/firehose/web_socket.js.coffee
@@ -71,8 +71,10 @@
     if @_needToNotifyOfDisconnect
       @_needToNotifyOfDisconnect = false
       @config.disconnected()
-    if @_succeeded then @connect @_retryDelay
-    else @config.failed @
+    if @_succeeded
+      @connect @_retryDelay
+    else if @config.failed
+      @config.failed @

   _cleanUp: =>
     @_clearKeepalive()


Fixed bug in server that was causing it to terminate before all child processes had finished their tests.

--- src/main/coffeescript/scxml/test/multi-process/server.coffee
+++ src/main/coffeescript/scxml/test/multi-process/server.coffee
@@ -79,13 +79,14 @@
 			if currentTest
 				#there are still tests left, so start a test

-				console.log "starting test {#{currentTest.interpreter}}#{currentTest.id})"
+				console.log "starting test {#{currentTest.interpreter}}#{currentTest.id}) on process #{p.pid}"

 				#put the current test in the testmap
 				#the important stateful variable is the list of expected configurations
 				testMap[currentTest.id] =
 					test : currentTest
 					sourceProcess : p
+					finished : false

 				results.testCount++

@@ -100,12 +101,21 @@

 			else
 				#there are no more tests left, so tell the client he's done
-				console.log "No more tests. Wrapping up."
-
-				finish()
+				console.log "No more tests. Ending client process #{p.pid}."
+
+				p.removeAllListeners 'exit'
+				p.stdin.end()
+				clientProcesses.splice(clientProcesses.indexOf(p),1)
+
+				#all client processes have finished, then we can end
+				if not clientProcesses.length then finish()

 		finish = ->
 			console.log "All clients finished. Wrapping up."
+
+			console.log "The following tests did not receive results:"
+			for own k,v of testMap when not v.finished
+				console.log k

 			summary = (results) -> "{#{result.interpreter}}#{result.id}" for result in results

@@ -119,9 +129,6 @@

 			endTime = new Date()

-			#terminate all client processes
-			p.stdin.end() for p in clientProcesses
-
 			console.log "Running time: #{(endTime - startTime)/1000} seconds"

 			if log then log.end()
@@ -129,6 +136,10 @@
 			process.exit results.testCount == results.testsPassed

 		processMessage = (jsonResults,p) ->
+
+			#console.log "Received results back for #{jsonResults.testId} from process #{p.pid}"
+
+			testMap[jsonResults.testId].finished = true

 			if jsonResults.results.pass
 				results.testsPassed.push testMap[jsonResults.testId].test
@@ -160,6 +171,10 @@
 						log.write l
 					else
 						console.log l
+
+		onPrematureExit = (p) ->
+			console.error "Process #{p.pid} ended unexpectedly"
+			if stopOnFail then process.exit 1

 		CLIENT_MODULE = "scxml/test/multi-process/client"

@@ -183,6 +198,7 @@

 		#send initial tests to clients
 		for p in clientProcesses
+			p.on "exit",onPrematureExit
 			hookUpEventHandling p
 			sendTest p


Remove default value of Accounts_AvatarStorePath

--- packages/rocketchat-lib/settings/server/startup.coffee
+++ packages/rocketchat-lib/settings/server/startup.coffee
@@ -9,7 +9,7 @@
 RocketChat.settings.add 'Accounts_AllowedDomainsList', '', { type: 'string', group: 'Accounts', public: true, section: 'Registration' }

 RocketChat.settings.add 'Accounts_AvatarStoreType', 'GridFS', { type: 'string', group: 'Accounts', section: 'Avatar' }
-RocketChat.settings.add 'Accounts_AvatarStorePath', '/var/www/rocket.chat/uploads/avatar/', { type: 'string', group: 'Accounts', section: 'Avatar' }
+RocketChat.settings.add 'Accounts_AvatarStorePath', '', { type: 'string', group: 'Accounts', section: 'Avatar' }
 RocketChat.settings.add 'Accounts_AvatarResize', false, { type: 'boolean', group: 'Accounts', section: 'Avatar' }
 RocketChat.settings.add 'Accounts_AvatarSize', 200, { type: 'int', group: 'Accounts', section: 'Avatar' }


utils: Added formatBytesToHumanReadable method.

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

   # deprecated ends


main: cmd+option click to debug is back

main: cmd+option click to debug is back

--- client/Core/mainviewcontroller.coffee
+++ client/Core/mainviewcontroller.coffee
@@ -46,11 +46,11 @@

     windowController.on 'ScrollHappened', @bound 'handleScroll'

-    # if KD.config?.environment isnt 'production'
-    #   window.addEventListener 'click', (event) =>
-    #     if event.metaKey and event.altKey
-    #       logViewByElement event.target
-    #   , yes
+    if KD.config?.environment isnt 'production'
+      window.addEventListener 'click', (event) =>
+        if event.metaKey and event.altKey
+          logViewByElement event.target
+      , yes

   loadView:(mainView)->


AppManager: minor cleanup

AppManager: minor cleanup

--- client/Main/ApplicationManager.coffee
+++ client/Main/ApplicationManager.coffee
@@ -29,8 +29,8 @@
     # @on 'AnInstanceIsShown', @bound "setMissingRoute"

     # set unload listener
-    windowController = @getSingleton 'windowController'
-    windowController.addUnloadListener 'window', =>
+    wc = @getSingleton 'windowController'
+    wc.addUnloadListener 'window', =>
       for own app of @appControllers when app in ['Ace', 'Terminal']
         safeToUnload = no
         break
@@ -71,8 +71,6 @@
       appParams            = options.params or {}
       defaultCallback      = createOrShow.bind this, appOptions, appParams, callback
       kodingAppsController = KD.getSingleton("kodingAppsController")
-
-      # -------

       # If app has a preCondition then first check condition in it
       # if it returns true then continue, otherwise call failure
@@ -129,8 +127,7 @@
       else do defaultCallback

   openFileWithApplication: (appName, file) ->
-    @open appName, =>
-      @utils.defer => @getFrontApp().openFile file
+    @open appName, => @utils.defer => @getFrontApp().openFile file

   fetchManifests:(appName, callback)->

@@ -185,8 +182,6 @@
   tell:(name, command, rest...)->

     return warn "ApplicationManager::tell called without an app name!"  unless name
-
-    # log "::: Telling #{command} to #{name}"

     app = @get name
     cb  = (appInstance)-> appInstance?[command]? rest...


Comment soft delete unittest.

Comment soft delete unittest.

--- test/mongoose/nmodel.coffee
+++ test/mongoose/nmodel.coffee
@@ -28,28 +28,28 @@
       v = await BaseNModel.find()
       v.should.have.length 5

-    it 'returns undeleted records', ->
-      records.ins1.deleted = true
-      await records.ins1.save()
-      v = await BaseNModel.find()
-      v.should.have.length 4
+    # it 'returns undeleted records', ->
+      # records.ins1.deleted = true
+      # await records.ins1.save()
+      # v = await BaseNModel.find()
+      # v.should.have.length 4

-    it 'returns all records with deleted', ->
-      records.ins1.deleted = true
-      await records.ins1.save()
-      v = await BaseNModel.find({}, null, { withDeleted: true })
-      v.should.have.length 5
+    # it 'returns all records with deleted', ->
+      # records.ins1.deleted = true
+      # await records.ins1.save()
+      # v = await BaseNModel.find({}, null, { withDeleted: true })
+      # v.should.have.length 5

-    it 'use customized delete', ->
-      await records.ins1.delete()
-      v = await BaseNModel.find()
-      v.should.have.length 4
-      v = await BaseNModel.find({}, null, { withDeleted: true })
-      v.should.have.length 5
+    # it 'use customized delete', ->
+      # await records.ins1.delete()
+      # v = await BaseNModel.find()
+      # v.should.have.length 4
+      # v = await BaseNModel.find({}, null, { withDeleted: true })
+      # v.should.have.length 5

-    it 'blocks remove to be called', ->
-      try
-        await records.ins1.remove()
-        true.should.not.be.ok()
-      catch e
-        e.message.should.equal 'remove is not supported, use delete instead'
+    # it 'blocks remove to be called', ->
+      # try
+        # await records.ins1.remove()
+        # true.should.not.be.ok()
+      # catch e
+        # e.message.should.equal 'remove is not supported, use delete instead'


add Submit Changes button, if requested

add Submit Changes button, if requested

--- client/plugins/changes/changes.coffee
+++ client/plugins/changes/changes.coffee
@@ -4,7 +4,7 @@
       <a class="internal" href="#" title="origin" data-page-name="#{slug}">
         #{page.title}
       </a>
-      <button>✕</button>
+      <button class="delete">✕</button>
     </li>
   """

@@ -20,13 +20,17 @@
     for i in [0...localStorage.length]
       slug = localStorage.key(i)
       page = JSON.parse(localStorage.getItem(slug))
-      ul.prepend listItemHtml(slug,page)
+      ul.append listItemHtml(slug,page)
+    ul.append """<button class="submit">Submit Changes</button>""" if item.submit?

   bind = ($div, item) ->
-    $div.on 'click', 'button', ->
+    $div.on 'click', '.delete', ->
       slug = $(this).siblings('a.internal').data('pageName')
       localStorage.removeItem(slug)
       emit( $div.empty(), item )
+
+    $div.on 'click', '.submit', ->
+      alert "submit"

     $div.dblclick ->
       bundle = {}


Fix OS-specific paths

Fix OS-specific paths

--- src/steps/path.coffee
+++ src/steps/path.coffee
@@ -18,9 +18,9 @@
         when 'ia32'
           "C:\\Program Files\\Steam"
     when 'darwin'
-      "~/Library/Application Support/Steam/SteamApps"
+      "~/Library/Application Support/Steam"
     when 'linux'
-      "~/.local/share/Steam/steamapps"
+      "~/.local/share/Steam"
     else
       throw Error "Unsupported OS"


CSS Scrollable impl now doesn't use abstract wheel implementation

--- impl/css/level2/scrollable.coffee
+++ impl/css/level2/scrollable.coffee
@@ -20,6 +20,7 @@
 		return abstractScrollable
 	else
 		impl._scrollableUsePointer = false
+		impl._scrollableUseWheel = false

 	DATA =
 		contentItem: null


Fix a typo

Fix a typo

--- coffee/angular-daterangepicker.coffee
+++ coffee/angular-daterangepicker.coffee
@@ -34,7 +34,7 @@
         _picker.setEndDate(m)

     #Watchers enable resetting of start and end dates
-    $scope.$watch 'model.startDate', (newValue) –>
+    $scope.$watch 'model.startDate', (newValue) ->
       _setStartDate(newValue)

     $scope.$watch 'model.endDate', (newValue) ->


Put the element in the dom when it isn’t in the dom yet so can’t yet be measured

--- framer/SVGBaseLayer.coffee
+++ framer/SVGBaseLayer.coffee
@@ -1,6 +1,32 @@
 {LayerStyle} = require "./LayerStyle"
 {Layer, layerProperty} = require "./Layer"
 {Color} = require "./Color"
+
+
+_svgMeasureElement = null
+
+
+getSVGMeasureElement = (constraints={}) ->
+	if not _svgMeasureElement?
+		_svgMeasureElement = document.createElementNS("http://www.w3.org/2000/svg", "svg")
+		_svgMeasureElement.id = "_svgMeasure"
+		_svgMeasureElement.style.position = "fixed"
+		_svgMeasureElement.style.visibility = "hidden"
+		_svgMeasureElement.style.top = "-10000px"
+		_svgMeasureElement.style.left = "-10000px"
+
+		if not window.document.body
+			document.write(_svgMeasureElement.outerHTML)
+			_svgMeasureElement = document.getElementById("_svgMeasure")
+		else
+			window.document.body.appendChild(_svgMeasureElement)
+
+
+	while _svgMeasureElement.hasChildNodes()
+		_svgMeasureElement.removeChild(_svgMeasureElement.lastChild)
+
+	return _svgMeasureElement
+

 originTransform = (value, layer, name) ->
 	sizeProp = undefined
@@ -134,25 +160,26 @@
 		set: (value) ->
 			console.warn "The gradient property is currently not supported on shapes"

-	elementInsertedIntoDocument: ->
-		super
-		@calculateSize()
-		@recalculateOrigin()
+	calculateSize: ->
+		element = @_element
+		measuredElement = null
+		if not Framer?.CurrentContext.elementInDOM
+			parent = @_element.parentElement
+			reference = @_element.nextSibling
+			svgMeasure = getSVGMeasureElement()
+			svgMeasure.appendChild(@_element)
+			measuredElement = svgMeasure.firstChild
+			element = measuredElement

-	calculateSize: ->
-		if Framer?.CurrentContext.elementInDOM
-			rect = @_element.getBoundingClientRect()
-			@_width = rect.width / @_parent.canvasScaleX()
-			@_height = rect.height / @_parent.canvasScaleY()
-		else
-			# No use in calculating, so set width and height to 0
-			@_width = 0
-			@_height = 0
+		rect = element.getBoundingClientRect()
+		@_width = rect.width / @_parent.canvasScaleX()
+		@_height = rect.height / @_parent.canvasScaleY()

-	recalculateOrigin: ->
-		@_properties.originX = originTransform(@originX, @, "originX")
-		@_properties.originY = originTransform(@originY, @, "originY")
-		@_element.style.webkitTransformOrigin = LayerStyle.webkitTransformOrigin(@)
+		if measuredElement?
+			if reference?
+				parent.insertBefore(measuredElement, reference)
+			else
+				parent.appendChild(measuredElement)

 	resetViewbox: =>
 		@_svg.setAttribute("viewBox", "0,0,#{@width},#{@height}")


Loader: defaults changed

Loader: defaults changed

--- components/loader/KDLoaderView.coffee
+++ components/loader/KDLoaderView.coffee
@@ -10,8 +10,8 @@
       bind          : o.bind                    or "mouseenter mouseleave"
       showLoader    : o.showLoader              or no
       size          :
-        width       : o.size.width              or 12
-        height      : o.size.height             or 12
+        width       : o.size.width              or 24
+        height      : o.size.height             or 24
       loaderOptions :
         color       : o.loaderOptions.color     or "#000000"   # hex color
         shape       : o.loaderOptions.shape     or "rect"      # "spiral", "oval", "square", "rect", "roundRect"


Cubemap: Multiply sh coefficients (they're reordered in shader_lib_extractor).

--- engine/cubemap.coffee
+++ engine/cubemap.coffee
@@ -143,12 +143,18 @@
         return

     # Generate spherical harmonics for diffuse shading
-    # @todo TODO: order them to be in the same format as Blender PBR shaders expect
     generate_spherical_harmonics: (size=@size)->
         faces = (new Uint8Array(size*size*4) for [0...6])
         @read_faces(faces, size)
         [posx, posy, posz, negx, negy, negz] = faces
         @coefficients = sh [posx, negx, posy, negy, posz, negz], size, 4
+        # See spherical_harmonics_L2() in original shader and
+        # in shader_lib_extractor.py
+        # result is very similar to Blender but not quite the same
+        m=[0.282095,0.488603,0.488603,0.488603,
+           1.092548,1.092548,0.315392,1.092548,0.546274]
+        for c,i in @coefficients[1...]
+            vec3.scale c, c, 1/m[i]
         return @

     destroy: ->


#1053 Remove unused function

#1053 Remove unused function

--- app/assets/javascripts/oxalis/model/binary/cube.coffee
+++ app/assets/javascripts/oxalis/model/binary/cube.coffee
@@ -126,29 +126,6 @@
     return @arbitraryCube

-  setArbitraryBucketByZoomedAddress : ([bucket_x, bucket_y, bucket_z, zoomStep], bucketData) ->
-
-    cube = @arbitraryCube
-
-    width = 1 << zoomStep
-
-    for dx in [0...width] by 1
-      for dy in [0...width] by 1
-        for dz in [0...width] by 1
-
-          subBucket = [
-            (bucket_x << zoomStep) + dx
-            (bucket_y << zoomStep) + dy
-            (bucket_z << zoomStep) + dz
-            0
-          ]
-
-          bucketIndex = @getBucketIndexByZoomedAddress(subBucket)
-          bucket = cube[bucketIndex]
-
-          cube[bucketIndex] = bucketData if not bucket? or bucket.zoomStep > zoomStep
-
-
   getVoxelIndexByVoxelOffset : ([x, y, z]) ->

     return x + y * (1 << @BUCKET_SIZE_P) + z * (1 << @BUCKET_SIZE_P * 2)


Check if label names are defined before trying to access their properties

--- app/assets/javascripts/pgdatagraph/datagraph.js.coffee
+++ app/assets/javascripts/pgdatagraph/datagraph.js.coffee
@@ -98,7 +98,7 @@
       palette = @options.palette[name] or @options.palette.default
       stroke = if renderer is "area" then no else palette.stroke
       renderer = @options.series[name]?.renderer or @options.series.renderer
-      seriesName = @options.series[name].name or name
+      seriesName = @options.series[name]?.name or name
       series.push {
         data: seriesData
         name: seriesName


Added autosave every 60 seconds

Added autosave every 60 seconds

--- app/assets/javascripts/stores/questionnaire_store.cjsx
+++ app/assets/javascripts/stores/questionnaire_store.cjsx
@@ -10,9 +10,18 @@
   questionnaireMode = null
   currentPage   = 0
   questionnaire = {}
+  autoSaveInterval = null
+

   constructor: ->
     @on(PAGE_CHANGE_EVENT, @saveOrUpdateReport)
+    @startAutoSave()
+
+  startAutoSave: ->
+    autoSaveInterval = setInterval(@saveOrUpdateReport, 60000)
+
+  stopAutoSave: ->
+    clearInterval(autoSaveInterval) if autoSaveInterval

   currentPage: ->
     currentPage
@@ -124,6 +133,7 @@
     })

   submitReport: =>
+    @stopAutoSave()
     alert("Report marked as submitted")

 module.exports = new QuestionnaireStore()


do not call switchTab twice

do not call switchTab twice

--- app/assets/javascripts/uploadcare/widget/dialog.coffee
+++ app/assets/javascripts/uploadcare/widget/dialog.coffee
@@ -374,7 +374,7 @@
       @tabs[name] = new TabCls(tabPanel, tabButton, @publicPromise(), @settings, name)

     switchTab: (tab) =>
-      if not tab
+      if not tab or @currentTab == tab
         return
       @currentTab = tab


:memo: Clear up specs more!

:memo: Clear up specs more!

--- spec/keymap-manager-spec.coffee
+++ spec/keymap-manager-spec.coffee
@@ -366,22 +366,22 @@
         advanceClock(keymapManager.getPartialMatchTimeout())
         expect(events).toEqual ['y-up-ctrl-keyup']

-      it "dispatches a command when the keybinding does not specify all key up events", ->
-        keymapManager.handleKeyboardEvent(buildKeydownEvent('x', ctrl: true, target: elementA))
-        keymapManager.handleKeyboardEvent(buildKeyupEvent('x', ctrl: true, target: elementA))
-        keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
-        advanceClock(keymapManager.getPartialMatchTimeout())
-        expect(events).toEqual ['x-ctrl-keyup']
-
       it "dispatches a command when modifier is lifted before the character", ->
         keymapManager.handleKeyboardEvent(buildKeydownEvent('y', ctrl: true, target: elementA))
         keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
         advanceClock(keymapManager.getPartialMatchTimeout())
         expect(events).toEqual ['y-ctrl-keyup']

-      it "does not dispatch the keyup command when a non-matching keydown event is made between matching keystrokes", ->
+      it "dispatches keyup command when extra user-generated keyup events are not specified in the binding", ->
+        keymapManager.handleKeyboardEvent(buildKeydownEvent('x', ctrl: true, target: elementA))
+        keymapManager.handleKeyboardEvent(buildKeyupEvent('x', ctrl: true, target: elementA)) # not specified in binding
+        keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
+        advanceClock(keymapManager.getPartialMatchTimeout())
+        expect(events).toEqual ['x-ctrl-keyup']
+
+      it "does _not_ dispatch keyup command when extra user-generated keydown events are not specified in the binding", ->
         keymapManager.handleKeyboardEvent(buildKeydownEvent('y', ctrl: true, target: elementA))
-        keymapManager.handleKeyboardEvent(buildKeydownEvent('z', ctrl: true, target: elementA))
+        keymapManager.handleKeyboardEvent(buildKeydownEvent('z', ctrl: true, target: elementA)) # not specified in binding
         keymapManager.handleKeyboardEvent(buildKeyupEvent('ctrl', target: elementA))
         advanceClock(keymapManager.getPartialMatchTimeout())
         expect(events).toEqual ['y-keydown']


™i

™i

--- spec/notice/connect_spec.coffee
+++ spec/notice/connect_spec.coffee
@@ -32,6 +32,8 @@

         it 'does not callsback ERRORS after accepted handshake', (done) ->

+            HANDSHAKE = undefined
+
             spy = io.connect
             io.connect = (uri) ->
                 io.connect = spy
@@ -48,14 +50,22 @@

                         if event == 'handshake'

+                            HANDSHAKE = args
                             MOCKSOCKET.handshakeReply()

                 return MOCKSOCKET

-            Connect (error) ->
+            Connect secret: ' ™i ', (error) ->

                 should.not.exist error
+
+                done 'and sends secret in handshake', (stacks...) ->
+
+                    HANDSHAKE[0].should.equal ' ™i '
+
+                    test stacks[0]
+
                 test done


Update upload progress

Update upload progress

--- server/publication.coffee
+++ server/publication.coffee
@@ -122,8 +122,10 @@

       Publications.update
         _id: publication._id
+        'importing.uploadProgress':
+          $lt: file.end / file.size
       ,
-        $max:
+        $set:
           'importing.uploadProgress': file.end / file.size

       if file.end == file.size


Style.

Style.

--- server/publication.coffee
+++ server/publication.coffee
@@ -209,10 +209,9 @@
   handlePublications = null

   removePublications = (ids) =>
-    for id of ids
-      if currentLibrary[id]
-        delete currentLibrary[id]
-        @removed 'Publications', id
+    for id of ids when currentLibrary[id]
+      delete currentLibrary[id]
+      @removed 'Publications', id

   publishPublications = (newLibrary) =>
     newLibrary ||= []
@@ -302,4 +301,4 @@
   ,
     fields: _.extend Publication.PUBLIC_FIELDS().fields,
       cached: 1
-      processed: 1+      processed: 1


New line cinema

New line cinema

--- scripts/music.coffee
+++ scripts/music.coffee
@@ -136,4 +136,4 @@
           usersChecked += 1

   for user in usersWithLastFm
-    checkTheUser(user)+    checkTheUser(user)


Tweak login usage message

Tweak login usage message

--- src/login.coffee
+++ src/login.coffee
@@ -18,7 +18,7 @@
     options.usage """
       Usage: apm login

-      Create and save an API token to the keychain. This token will
+      Enter your Atom.io API token and save it to the keychain. This token will
       be used to identify you when publishing packages to atom.io.
     """
     options.alias('h', 'help').describe('help', 'Print this usage message')


add example of deserialize failure (Collection)

--- spec/util.coffee
+++ spec/util.coffee
@@ -1,6 +1,6 @@

 { Util } = require './others'
-{ Entity, ValueObject } = Facade = require('./base-domain')
+{ Entity, ValueObject, BaseDict, BaseList } = Facade = require('./base-domain')
 facade = require('./create-facade').create()

 describe 'Util', ->
@@ -304,3 +304,43 @@
             diary2 = facade.createModel('diary', { p1: 123, p2: 'str' })
             serialized = Util.serialize(d1: diary1, d2: diary2)
             assert.deepEqual(Util.deserialize(serialized, facade), d1: diary1, d2: diary2)
+
+
+        it 'restores model in list', ->
+
+            class Diary extends Entity
+            facade.addClass('diary', Diary)
+
+            class DiaryList extends BaseList
+                @itemModelName: 'diary'
+
+            facade.addClass('diary-list', DiaryList)
+
+            diary1 = facade.createModel('diary', { id: 'abc', p1: 123, p2: 'str' })
+            diary2 = facade.createModel('diary', { id: 'def', p1: 123, p2: 'str' })
+
+            diaries = facade.createModel('diary-list', [diary1, diary2])
+
+            serialized = Util.serialize(diaries)
+            assert.deepEqual(Util.deserialize(serialized, facade), diaries)
+
+
+        it 'restores model in dict', ->
+
+            class Diary extends Entity
+            facade.addClass('diary', Diary)
+
+            class DiaryDict extends BaseDict
+                @itemModelName: 'diary'
+
+            facade.addClass('diary-dict', DiaryDict)
+
+            diary1 = facade.createModel('diary', { id: 'abc', p1: 123, p2: 'str' })
+            diary2 = facade.createModel('diary', { id: 'def', p1: 123, p2: 'str' })
+
+            diaries = facade.createModel('diary-dict', [diary1, diary2])
+
+            serialized = Util.serialize(diaries)
+            assert.deepEqual(Util.deserialize(serialized, facade), diaries)
+
+


amqp: Implement removeBinding()

amqp: Implement removeBinding()

--- src/amqp.coffee
+++ src/amqp.coffee
@@ -197,7 +197,6 @@
           return callback err

   addBinding: (binding, callback) ->
-    # TODO: support roundrobin type
     debug 'Broker.addBinding', binding
     if binding.type == 'pubsub'
       @channel.bindQueue binding.tgt, binding.src, '', {}, callback
@@ -234,9 +233,18 @@
     else
       return callback new Error 'Unsupported binding type: '+binding.type

-  removeBinding: (binding, callback) -> # FIXME: implement
-    return callback null
+  removeBinding: (binding, callback) ->
+    debug 'Broker.removeBinding', binding
+    if binding.type == 'pubsub'
+      @channel.unbindQueue binding.tgt, binding.src, '', {}, callback
+    else if binding.type == 'roundrobin'
+      return callback new Error "removeBinding() not supported for type='roundrobin'" # TODO:
+    else
+      return callback new Error "Unsupported binding type: #{binding.type}"
+
+
   listBindings: (from, callback) -> # FIXME: implement
+    # NOTE: probably need to use the RabbitMQ HTTP API for this
     return callback null, []

   # Data subscriptions


Set context on the wrapper

Set context on the wrapper

--- src/core/kb.coffee
+++ src/core/kb.coffee
@@ -148,7 +148,10 @@

     el = document.createElement('div')
     observable = ko.renderTemplate(template, view_model, options, el, 'replaceChildren');
-    el = el.childNodes[0] if el.childNodes.length is 1 # do not return the template wrapper if possible
+    if el.childNodes.length is 1 # do not return the template wrapper if possible
+      el = el.childNodes[0]
+    else if el.childNodes.length
+      ko.storedBindingContextForNode(el, ko.contextFor(el.childNodes[0])) # ensure the context is passed up to wrapper
     kb.releaseOnNodeRemove(view_model, el)
     observable.dispose() # we will handle memory management with ko.removeNode (otherwise creates memory leak on default bound dispose function)


Add awesome-awkward and awkward-awesome option

--- src/memes.coffee
+++ src/memes.coffee
@@ -25,7 +25,9 @@
   "alwaysonbeat": "Always On Beat",
   "ants": "Do You Want Ants? (archer)",
   "awesome": "Socially Awesome Penguin",
+  "awesome-awkward": "Socially Awesome Awkward Penguin",
   "awkward": "Socially Awkward Penguin",
+  "awkward-awesome": "Socially Awkward Awesome Penguin",
   "bad": "You Should Feel Bad",
   "badchoice": "Milk Was a Bad Choice (anchorman)",
   "bd": "Butthurt Dweller",


Add encounter and treatment tests.

Add encounter and treatment tests.

--- test/unit/routerSpec.coffee
+++ test/unit/routerSpec.coffee
@@ -1,5 +1,5 @@
-define ['lodash', 'ngmocks', 'expect', 'router'],
-  (_, mocks, expect, router) ->
+define ['lodash', 'ngmocks', 'expect', 'router', 'moment'],
+  (_, mocks, expect, router, moment) ->
     describe 'Unit Testing Router', ->
       # The mock Router service module.
       Router = null
@@ -29,11 +29,17 @@
           ]
           encounters: [
             encounter_type: 'Biopsy'
+            date: moment('July 12, 2013').valueOf()
             outcomes: [
               _cls: 'BreastPathology'
               tnm:
                 size: 'T3'
             ]
+          ]
+          treatments: [
+            treatment_type: 'neodjuvant'
+            begin_date: moment('June 4, 2013').valueOf()
+            end_date: moment('July 16, 2013').valueOf()
           ]
         session_detail:
           scan:
@@ -77,11 +83,28 @@
       describe 'Subject Detail', ->
         # Validates the resolved subject.
         validate = (subject) ->
+          # There should be encounters.
+          expect(subject.encounters, "Subject is missing encounters").to.exist
+          expect(subject.encounters.length).to.equal(1)
+          enc = subject.encounters[0]
+          expect(enc.encounter_type, "Encounter type is incorrect")
+            .to.equal(mock.subject_detail.encounters[0].encounter_type)
+          # There should be treatments.
+          expect(subject.treatments, "Subject is missing treatments").to.exist
+          expect(subject.treatments.length).to.equal(1)
+          trt = subject.treatments[0]
+          expect(trt.treatment_type, "Treatment type is incorrect")
+            .to.equal(mock.subject_detail.treatments[0].treatment_type)
+          expect(trt.begin_date.valueOf(), "Treatment begin date is incorrect")
+            .to.equal(mock.subject_detail.treatments[0].begin_date)
+          # There should be sessions.
           expect(subject.sessions, "Subject is missing sessions").to.exist
           expect(subject.sessions.length).to.equal(1)
           expect(subject.isMultiSession).to.be.false
           sess = subject.sessions[0]
+          # There should be a parent subject.
           expect(sess.subject, "Session is missing a subject").to.exist
+          # Validate the modeling.
           expect(sess.modeling, "Session is missing modeling").to.exist
           # TODO - replace the assignment below for multiple
           # modeling results per session.


Form label fixes

Form label fixes

Avoided modifying forms if units are not provided.

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -211,7 +211,6 @@

       schemaInputs = Form.getSchemaInputs(@)
       popupInputs = []
-      hasRequiredField = false
       _.each schemaInputs, (input, key) ->
         $input = $(input.node)
         if Forms.isCheckbox($input)
@@ -223,24 +222,18 @@
         if desc?
           popupInputs.push($input.data('desc', desc))
         # Add units into labels
-        $label = Forms.getInputLabel($input)
-        units = field.units
-        $labelContent = $('
' + $label.html() + '
')
-        $label.empty()
-        $label.append($labelContent)
         if units?
+          $label = Forms.getInputLabel($input)
+          units = field.units
+          $labelContent = $('
' + $label.html() + '
')
+          $label.empty()
+          $label.append($labelContent)
           formattedUnits = Strings.format.scripts(units)
           $units = $('
' + formattedUnits + '
')
           $label.append($units)
         required = field.optional != true
-        if required
-          Forms.addRequiredLabel($label)
-          hasRequiredField = true

       Form.setUpFields(@)
-
-      if hasRequiredField
-        @$('.ui.form.segment').append($('

Required field
'))

       addPopups = ->
         return unless supportsPopups()
@@ -645,10 +638,6 @@
   # STATICS
   ##################################################################################################

-  addRequiredLabel: ($label) ->
-    $requiredContent = $('

')
-    $label.append($requiredContent)
-
   getRequiredLabels: ($em) -> $('.required', $em)

   getInputLabel: ($input) ->


Refactor Dynamic

Refactor Dynamic

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -1,10 +1,11 @@
 {Serenade} = require './serenade'
+{Collection} = require './collection'
 {format, get, set, preventDefault} = require './helpers'

 class Node
   @element: (ast, model, controller) ->
     element = Serenade.document.createElement(ast.name)
-    node = new Node(ast, element, model, controller)
+    node = new Node(ast, element)

     element.setAttribute('id', ast.id) if ast.id
     element.setAttribute('class', ast.classes.join(' ')) if ast.classes?.length
@@ -27,7 +28,8 @@

   @view: (ast, model, parent) ->
     controller = Serenade.controllerFor(ast.arguments[0]) or parent
-    new Node(ast, Serenade.render(ast.arguments[0], model, controller, parent), model, controller)
+    element = Serenade.render(ast.arguments[0], model, controller, parent)
+    new Node(ast, element)

   @helper: (ast, model, controller) ->
     render = (model=model, controller=controller) ->
@@ -39,7 +41,7 @@
     helperFunction = Serenade.Helpers[ast.command] or throw SyntaxError "no helper #{ast.command} defined"
     context = { render, model, controller }
     element = helperFunction.apply(context, ast.arguments)
-    new Node(ast, element, model, controller)
+    new Node(ast, element)

   @text: (ast, model, controller) ->
     getValue = ->
@@ -48,9 +50,9 @@
       value or ""
     textNode = Serenade.document.createTextNode(getValue())
     model.bind?("change:#{ast.value}", -> textNode.nodeValue = getValue()) if ast.bound
-    new Node(ast, textNode, model, controller)
-
-  constructor: (@ast, @element, @model, @controller) ->
+    new Node(ast, textNode)
+
+  constructor: (@ast, @element) ->

   append: (inside) ->
     inside.appendChild(@element)
@@ -65,94 +67,96 @@
     @element

 class Dynamic
-  class Item
-    constructor: (@children, @model, @controller) ->
-      @nodes = (Nodes.compile(child, @model, @controller) for child in @children)
-    insertAfter: (element) ->
-      last = element
-      for node in @nodes
-        node.insertAfter(last)
-        last = node.lastElement()
-    lastElement: -> @nodes[@nodes.length-1].lastElement()
-    remove: -> node.remove() for node in @nodes
-
   @collection: (ast, model, controller) ->
+    compileItem = (item) -> Nodes.compile(child, item, controller) for child in ast.children
+
+    dynamic = new Dynamic(ast)
     collection = get(model, ast.arguments[0])
-    new Dynamic(ast, collection, model, controller)
+    if typeof(collection.bind) is "function"
+      collection.bind('set', => dynamic.replace(compileItem(item) for item in collection))
+      collection.bind('update', => dynamic.replace(compileItem(item) for item in collection))
+      collection.bind('add', (item) => dynamic.appendNodeSet(compileItem(item)))
+      collection.bind('insert', (index, item) => dynamic.insertNodeSet(index, compileItem(item)))
+      collection.bind('delete', (index) => dynamic.deleteNodeSet(index))
+    dynamic.replace(compileItem(item) for item in collection)
+    dynamic

   @in: (ast, model, controller) ->
-    collection = new Serenade.Collection([])
-    update = ->
-      subModel = get(model, ast.arguments[0])
-      if subModel
-        collection.update([subModel])
-      else
-        collection.update([])
-    update()
-    model.bind? "change:#{ast.arguments[0]}", update
-    new Dynamic(ast, collection, model, controller)
-
-  @if: (ast, model, controller) ->
-    collection = new Serenade.Collection([])
+    dynamic = new Dynamic(ast)
     update = ->
       value = get(model, ast.arguments[0])
       if value
-        collection.update([model])
+        nodes = (Nodes.compile(child, value, controller) for child in ast.children)
+        dynamic.replace([nodes])
       else
-        collection.update([])
+        dynamic.clear()
     update()
     model.bind? "change:#{ast.arguments[0]}", update
-    new Dynamic(ast, collection, model, controller)
-
-  constructor: (@ast, @collection, @model, @controller) ->
+    dynamic
+
+  @if: (ast, model, controller) ->
+    dynamic = new Dynamic(ast)
+    update = ->
+      value = get(model, ast.arguments[0])
+      if value
+        nodes = (Nodes.compile(child, model, controller) for child in ast.children)
+        dynamic.replace([nodes])
+      else
+        dynamic.clear()
+    update()
+    model.bind? "change:#{ast.arguments[0]}", update
+    dynamic
+
+  constructor: (@ast) ->
     @anchor = Serenade.document.createTextNode('')
-    if @collection.bind
-      @collection.bind('update', => @rebuild())
-      @collection.bind('set', => @rebuild())
-      @collection.bind('add', (item) => @appendItem(item))
-      @collection.bind('insert', (index, item) => @insertItem(index, item))
-      @collection.bind('delete', (index) => @delete(index))
+    @nodeSets = new Collection([])
+
+  eachNode: (fun) ->
+    for set in @nodeSets
+      fun(node) for node in set

   rebuild: ->
-    item.remove() for item in @items
-    @build()
-
-  build: ->
-    @items = []
-    new Serenade.Collection(@collection).forEach (item) => @appendItem(item)
-
-  appendItem: (item) ->
-    node = new Item(@ast.children, item, @controller)
-    node.insertAfter(@lastElement())
-    @items.push(node)
-
-  insertItem: (index, item) ->
-    node = new Item(@ast.children, item, @controller)
-    node.insertAfter(@items[index-1]?.lastElement() or @anchor)
-    @items.splice(0, node)
-
-  delete: (index) ->
-    @items[index].remove()
-    @items.splice(index, 1)
-
-  lastItem: ->
-    @items[@items.length - 1]
-
-  lastElement: ->
-    item = @lastItem()
-    if item then item.lastElement() else @anchor
+    if @anchor.parentNode
+      last = @anchor
+      @eachNode (node) ->
+        node.insertAfter(last)
+        last = node.lastElement()
+
+  replace: (sets) ->
+    @clear()
+    @nodeSets.update(new Collection(set) for set in sets)
+    @rebuild()
+
+  appendNodeSet: (nodes) ->
+    @insertNodeSet(@nodeSets.length, nodes)
+
+  deleteNodeSet: (index) ->
+    node.remove() for node in @nodeSets[index]
+    @nodeSets.deleteAt(index)
+
+  insertNodeSet: (index, nodes) ->
+    last = @nodeSets[index-1].last()
+    for node in nodes
+      node.insertAfter(last.lastElement())
+      last = node
+    @nodeSets.insertAt(index, new Collection(nodes))
+
+  clear: -> @eachNode (node) -> node.remove()

   remove: ->
+    @clear()
     @anchor.parentNode.removeChild(@anchor)
-    item.remove() for item in @items

   append: (inside) ->
     inside.appendChild(@anchor)
-    @build()
+    @rebuild()

   insertAfter: (after) ->
     after.parentNode.insertBefore(@anchor, after.nextSibling)
-    @build()
+    @rebuild()
+
+  lastElement: ->
+    @nodeSets.last()?.last()?.lastElement() or @anchor

 class Style
   constructor: (@ast, @node, @model, @controller) ->
@@ -237,14 +241,14 @@
   compile: (ast, model, controller) ->
     switch ast.type
       when 'element' then Node.element(ast, model, controller)
-      when 'text' then new Node.text(ast, model, controller)
+      when 'text' then Node.text(ast, model, controller)
       when 'instruction'
         switch ast.command
-          when "view" then new Node.view(ast, model, controller)
-          when "collection" then new Dynamic.collection(ast, model, controller)
-          when "if" then new Dynamic.if(ast, model, controller)
-          when "in" then new Dynamic.in(ast, model, controller)
-          else new Node.helper(ast, model, controller)
+          when "view" then Node.view(ast, model, controller)
+          when "collection" then Dynamic.collection(ast, model, controller)
+          when "if" then Dynamic.if(ast, model, controller)
+          when "in" then Dynamic.in(ast, model, controller)
+          else Node.helper(ast, model, controller)
       else throw SyntaxError "unknown type '#{ast.type}'"

 exports.Nodes = Nodes


Update route declaration in karma test

Update route declaration in karma test

--- spec/javascripts/unit/darkswarm/services/products_spec.js.coffee
+++ spec/javascripts/unit/darkswarm/services/products_spec.js.coffee
@@ -13,7 +13,7 @@
   properties = null
   taxons = null
   GmapsGeo = {}
-  endpoint = "/api/order_cycles/1/products?distributor=1"
+  endpoint = "/api/order_cycles/1/products.json?distributor=1"

   beforeEach ->
     product =


fix bug

fix bug

--- src/order.coffee
+++ src/order.coffee
@@ -26,7 +26,7 @@
 			{@name, @userId, @date, @food, @note, @money} = options

 		toString: () ->
-			"#{@name} - #{@food} #{@note} $#{@money}}"
+			"#{@name} - #{@food} #{@note} $#{@money}"

 	robot.brain.data['orders'] = []


testing pre-commit hook that runs test suite

--- lib/personal-app.coffee
+++ lib/personal-app.coffee
@@ -500,7 +500,7 @@
     ###
     PersonalConnectOptions[key] = val for key,val of options
     #connect_opts.set(key,val) for key,val of options
-
+
 PersonalConnectOptions
     update: true
     sandbox: false


fall back to previous way to handle font declarations (logging font requests possible but too time-consuming)

--- src/tex-writer.coffee
+++ src/tex-writer.coffee
@@ -189,12 +189,15 @@
         executed; still, redefining a *font* doesn't seem to bother XeLaTeX much and indeed, only
         the needed fonts are loaded. Also, we could capture the output of the font commands and
         compile a list of all used fonts. ###
-        write "\\newcommand{\\#{texname}}{%"
-        write "\\renewcommand{\\#{texname}}{\\typeout{\\trmGreen{using #{texname}}}}%"
+        ### TAINT Mystery: redefinition doesn't work, processing time skyrockets ###
+        write "\\newfontface{\\#{texname}}{#{filename}}[#{font_settings_txt}]%"
+        # write "\\newcommand{\\#{texname}}{%"
+        # write "\\renewcommand{\\#{texname}}{\\#{texname}XXX}%"
+        # write "\\renewcommand{\\#{texname}}{\\typeout{\\trmGreen{using #{texname}}}}%"
         # write "\\typeout{\\trmWhite{defining #{texname}}}%"
-        write "\\newfontface{\\#{texname}XXX}{#{filename}}[#{font_settings_txt}]%"
-        write "\\#{texname}XXX%"
-        write "}"
+        # # write "\\newfontface{\\#{texname}XXX}{#{filename}}[#{font_settings_txt}]%"
+        # # write "\\#{texname}XXX%"
+        # write "}"
       else
         write "\\newfontface\\#{texname}[#{font_settings_txt}]{#{filename}}"
         # write "\\newcommand\\#{texname}{%"


make backticks, `<tt>` and `<code-span>` synonyms


Fix moving uri tabs between processes. Removed some of the duplicated data being passed between processes. Changed data passed to use URI instead of simply file path.

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -170,10 +170,9 @@
     event.originalEvent.dataTransfer.setData 'from-routing-id', @getRoutingId()

     item = @pane.getItems()[element.index()]
-    if item.getPath?
-      if process.platform isnt 'linux' # See #69
-        event.originalEvent.dataTransfer.setData 'text/uri-list', "file://#{item.getPath()}"
-      event.originalEvent.dataTransfer.setData 'text/plain', item.getPath() ? ''
+    console.log(item.getUri?())
+    if item.getUri?
+      event.originalEvent.dataTransfer.setData 'text/urilist', item.getUri?() ? ''

       if item.isModified?() and item.getText?
         event.originalEvent.dataTransfer.setData 'has-unsaved-changes', 'true'
@@ -245,8 +244,9 @@
       {item} = fromPane.find(".tab-bar .sortable:eq(#{fromIndex})").view() ? {}
       @moveItemBetweenPanes(fromPane, fromIndex, toPane, toIndex, item) if item?
     else
-      droppedPath = dataTransfer.getData('text/plain')
-      atom.workspace.open(droppedPath).then (item) =>
+      droppedUri = dataTransfer.getData('text/urilist')
+      console.log(droppedUri)
+      atom.workspace.open(droppedUri).then (item) =>
         # Move the item from the pane it was opened on to the target pane
         # where it was dropped onto
         activePane = atom.workspaceView.getActivePaneView()


Don't stop event propagation from onDrop

Issue #88 : https://github.com/atom/tabs/issues/88
Allow the drop event to propagate from the tab-bar, so that other packages can listen for it on the pane, workspace, etc.

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -246,7 +246,6 @@

   onDrop: (event) =>
     event.preventDefault()
-    event.stopPropagation()
     {dataTransfer} = event.originalEvent

     return unless dataTransfer.getData('atom-event') is 'true'


Added padding to trees

Added padding to trees

--- src/treeLoader.coffee
+++ src/treeLoader.coffee
@@ -15,6 +15,7 @@
 	nodeAlign: 'TOP',
 	levelSeparation: 50,
 	siblingSeparation: 50,
+	padding: 50,
 	node: {
 		collapsable: true
 	}


Fixes #5

Fixes #5

--- src/inflate.coffee
+++ src/inflate.coffee
@@ -96,7 +96,7 @@
           xml.writeMetaXml 'ApexClass', fp, version: '27.0', _.ary done, 1

         (done) ->
-          fp = path.join 'templates', 'CoverageInflation.cls.tmpl'
+          fp = path.join __dirname, '..', 'templates', 'CoverageInflation.cls.tmpl'
           fs.readFile fp, 'utf-8', done

         (res, done) ->


Add systemId to query before sending to DB


fix: stringify when the message is a value, not a promise


Pends specs

Pends specs


Fix windows unit tests

Fix windows unit tests

--- spec/robot-framework-keywords-autocomplete-spec.coffee
+++ spec/robot-framework-keywords-autocomplete-spec.coffee
@@ -10,6 +10,7 @@
 CFG_KEY = 'autocomplete-robot-framework'

 TIMEOUT=5000 #ms
+SEP = pathUtils.sep

 # Credits - https://raw.githubusercontent.com/atom/autocomplete-atom-api/master/spec/provider-spec.coffee
 getCompletions = (editor, provider)->
@@ -905,7 +906,7 @@
           suggestions = suggestions.filter (suggestion) -> suggestion.type=='keyword'
           expect(suggestions.length).toEqual(1)
           expect(suggestions[0]?.displayText).toEqual('pr1k')
-          expect(suggestions[0]?.resourceKey.endsWith('path-resolver/pr1.robot')).toBeTruthy()
+          expect(suggestions[0]?.resourceKey.endsWith("path-resolver#{SEP}pr1.robot")).toBeTruthy()
     it 'resolves resource by resource name in different directory', ->
       waitsForPromise -> atom.workspace.open('path-resolver/path-resolver.robot')
       runs ->
@@ -917,7 +918,7 @@
           suggestions = suggestions.filter (suggestion) -> suggestion.type=='keyword'
           expect(suggestions.length).toEqual(1)
           expect(suggestions[0]?.displayText).toEqual('pr6ka')
-          expect(suggestions[0]?.resourceKey.endsWith('path-resolver/a/pr6.robot')).toBeTruthy()
+          expect(suggestions[0]?.resourceKey.endsWith("path-resolver#{SEP}a#{SEP}pr6.robot")).toBeTruthy()
     it 'resolves resource by relative resource path in the same directory', ->
       waitsForPromise -> atom.workspace.open('path-resolver/path-resolver.robot')
       runs ->
@@ -929,7 +930,7 @@
           suggestions = suggestions.filter (suggestion) -> suggestion.type=='keyword'
           expect(suggestions.length).toEqual(1)
           expect(suggestions[0]?.displayText).toEqual('pr2k')
-          expect(suggestions[0]?.resourceKey.endsWith('path-resolver/pr2.robot')).toBeTruthy()
+          expect(suggestions[0]?.resourceKey.endsWith("path-resolver#{SEP}pr2.robot")).toBeTruthy()
     it 'resolves resource by relative resource path in different directory', ->
       waitsForPromise -> atom.workspace.open('path-resolver/path-resolver.robot')
       runs ->
@@ -941,7 +942,7 @@
           suggestions = suggestions.filter (suggestion) -> suggestion.type=='keyword'
           expect(suggestions.length).toEqual(1)
           expect(suggestions[0]?.displayText).toEqual('pr3ka')
-          expect(suggestions[0]?.resourceKey.endsWith('path-resolver/a/pr3.robot')).toBeTruthy()
+          expect(suggestions[0]?.resourceKey.endsWith("path-resolver#{SEP}a#{SEP}pr3.robot")).toBeTruthy()
       runs ->
         editor = atom.workspace.getActiveTextEditor()
         editor.setCursorBufferPosition([Infinity, Infinity])
@@ -951,7 +952,7 @@
           suggestions = suggestions.filter (suggestion) -> suggestion.type=='keyword'
           expect(suggestions.length).toEqual(1)
           expect(suggestions[0]?.displayText).toEqual('pr4ka')
-          expect(suggestions[0]?.resourceKey.endsWith('path-resolver/a/pr4.robot')).toBeTruthy()
+          expect(suggestions[0]?.resourceKey.endsWith("path-resolver#{SEP}a#{SEP}pr4.robot")).toBeTruthy()
     it 'suggests keywords from all resources with same name when import path is computed', ->
       waitsForPromise -> atom.workspace.open('path-resolver/path-resolver.robot')
       runs ->
@@ -963,9 +964,9 @@
           suggestions = suggestions.filter (suggestion) -> suggestion.type=='keyword'
           expect(suggestions.length).toEqual(2)
           expect(suggestions[0]?.displayText).toEqual('pr5k')
-          expect(suggestions[0]?.resourceKey.endsWith('path-resolver/pr5.robot')).toBeTruthy()
+          expect(suggestions[0]?.resourceKey.endsWith("path-resolver#{SEP}pr5.robot")).toBeTruthy()
           expect(suggestions[1]?.displayText).toEqual('pr5ka')
-          expect(suggestions[1]?.resourceKey.endsWith('path-resolver/a/pr5.robot')).toBeTruthy()
+          expect(suggestions[1]?.resourceKey.endsWith("path-resolver#{SEP}a#{SEP}pr5.robot")).toBeTruthy()
     it 'suggests keywords from all resources with same name when import path is wrong', ->
       waitsForPromise -> atom.workspace.open('path-resolver/path-resolver.robot')
       runs ->
@@ -977,9 +978,9 @@
           suggestions = suggestions.filter (suggestion) -> suggestion.type=='keyword'
           expect(suggestions.length).toEqual(2)
           expect(suggestions[0]?.displayText).toEqual('pr7k')
-          expect(suggestions[0]?.resourceKey.endsWith('path-resolver/pr7.robot')).toBeTruthy()
+          expect(suggestions[0]?.resourceKey.endsWith("path-resolver#{SEP}pr7.robot")).toBeTruthy()
           expect(suggestions[1]?.displayText).toEqual('pr7ka')
-          expect(suggestions[1]?.resourceKey.endsWith('path-resolver/a/pr7.robot')).toBeTruthy()
+          expect(suggestions[1]?.resourceKey.endsWith("path-resolver#{SEP}a#{SEP}pr7.robot")).toBeTruthy()
     it 'resolves external resources', ->
       waitsForPromise -> atom.workspace.open('path-resolver/path-resolver.robot')
       runs ->
@@ -991,4 +992,4 @@
           suggestions = suggestions.filter (suggestion) -> suggestion.type=='keyword'
           expect(suggestions.length).toEqual(1)
           expect(suggestions[0]?.displayText).toEqual('prext')
-          expect(suggestions[0]?.resourceKey.endsWith('path-resolver/external-resource.robot')).toBeTruthy()+          expect(suggestions[0]?.resourceKey.endsWith("path-resolver#{SEP}external-resource.robot")).toBeTruthy()


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -20,6 +20,7 @@
         #$(@node).fadeOut().fadeIn()
         @container = $(@node).parent()
         #@instanceDataId = $(@node).attr('data-id')
+        @instanceDataId = @instanceDataId ? $(@node).attr('data-id')
         console.log('onData -> @instanceDataId: ',@instanceDataId)

         #


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -14,7 +14,7 @@

 enemy.include Metered

-someOtherObject
+someOtherObject = GameObject

 player.meter 'health'
 # => Sets up a health meter that will be drawn during the player overlay event


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -145,7 +145,9 @@
       showName: false
       text: null
       width: 100
-
+
+    name
+
     if not I[options.name]
       I[options.name] = 100


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -1,7 +1,7 @@
 ###*
 The Metered module provides a simple drop-in
 meter ui to track arbitrary numeric attributes.
-If
+If name

 player = Core


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -8,7 +8,9 @@
   maxHealth: 100

 enemy = GameObject
-  health: 20
+  health: 500
+
+

 player.position()


fixing slot lookup

fixing slot lookup

--- src/methods.coffee
+++ src/methods.coffee
@@ -219,6 +219,12 @@
   # this one must point to a valid rt.jar file
   'sun.boot.class.path': '/Developer/Applications/Utilities/Application Loader.app/Contents/MacOS/itms/java/lib/rt.jar'
 }
+
+get_field_from_offset = (rs, cls, offset) ->
+  until cls.fields[offset]?
+    throw "field #{offset} doesn't exist in class #{cls.this_class}" unless cls.super_class?
+    cls = rs.class_lookup(cls.super_class)
+  cls.fields[offset]

 native_methods =
   java:
@@ -455,8 +461,13 @@
         o 'staticFieldBase(Ljava/lang/reflect/Field;)Ljava/lang/Object;', (rs,_this,field) ->
             rs.set_obj rs.get_obj(field.fields.clazz).fields.$type.toClassString()
         o 'getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object;', (rs,_this,obj,offset) ->
-            f = rs.class_lookup(obj.type).fields[offset.toInt()]
-            f.static_value
+            f = get_field_from_offset rs, rs.class_lookup(obj.type), offset.toInt()
+            return f.static_value if f.access_flags.static
+            obj.fields[f.name] ? 0
+        o 'getObject(Ljava/lang/Object;J)Ljava/lang/Object;', (rs,_this,obj,offset) ->
+            f = get_field_from_offset rs, rs.class_lookup(obj.type), offset.toInt()
+            return f.static_value if f.access_flags.static
+            obj.fields[f.name] ? 0
       ]
     reflect:
       NativeMethodAccessorImpl: [


More unmocking.

More unmocking.

--- src/natives.coffee
+++ src/natives.coffee
@@ -55,12 +55,6 @@
       Currency: [
         o 'getInstance(Ljava/lang/String;)Ljava/util/Currency;', (rs) -> null # because it uses lots of reflection and we don't need it
       ]
-      EnumSet: [
-        o 'getUniverse(L!/lang/Class;)[L!/lang/Enum;', (rs) ->
-            rs.push rs.curr_frame().locals[0]
-            rs.method_lookup({class: 'java/lang/Class', sig: 'getEnumConstants()[Ljava/lang/Object;'}).run(rs)
-            rs.pop()
-      ]
     nio:
       charset:
         Charset$3: [
@@ -68,24 +62,6 @@
         ]
       Bits: [
         o 'byteOrder()L!/!/ByteOrder;', (rs) -> rs.static_get {class:'java/nio/ByteOrder',name:'LITTLE_ENDIAN'}
-      ]
-    io:
-      PrintStream: [
-        o 'write(L!/lang/String;)V', (rs, _this, jvm_str) ->
-            str = rs.jvm2js_str(jvm_str)
-            sysout = rs.static_get class:'java/lang/System', name:'out'
-            syserr = rs.static_get class:'java/lang/System', name:'err'
-            if _this.ref is sysout
-              rs.print str
-            else if _this.ref is syserr
-              rs.print str
-            else
-              throw "You tried to write to a PrintStream that wasn't System.out or System.err! For shame!"
-            if node?
-              # For the browser implementation -- the DOM doesn't get repainted
-              # unless we give the event loop a chance to spin.
-              rs.curr_frame().resume = -> # NOP
-              throw new util.YieldException (cb) -> setTimeout(cb, 0)
       ]
   sun:
     misc:
@@ -355,6 +331,11 @@
               fs.writeSync(_this.fields.$file, new Buffer(bytes.array), offset, len)
               return
             rs.print rs.jvm_carr2js_str(bytes.ref, offset, len)
+            if node?
+              # For the browser implementation -- the DOM doesn't get repainted
+              # unless we give the event loop a chance to spin.
+              rs.curr_frame().resume = -> # NOP
+              throw new util.YieldException (cb) -> setTimeout(cb, 0)
         o 'close0()V', (rs, _this) ->
             return unless _this.fields.$file?
             fs.closeSync(_this.fields.$file)


Debug failing Travis spec

Debug failing Travis spec

--- spec/install-spec.coffee
+++ spec/install-spec.coffee
@@ -531,12 +531,12 @@
         expect(json[1].metadata.name).toBe 'test-module2'

     describe "with a space in node-gyp's path", ->
-      nodeModules = fs.realpathSync(path.join(__dirname, '..', 'node_modules'))
+      npmNodeModules = fs.realpathSync(path.join(__dirname, '..', 'node_modules', 'npm', 'node_modules'))

       beforeEach ->
-        fs.cp path.join(nodeModules, 'npm', 'node_modules', 'node-gyp'), path.join(nodeModules, 'with a space')
-        process.env.npm_config_node_gyp = path.join(nodeModules, 'with a space', 'bin', 'node-gyp.js')
-        process.env.ATOM_NODE_GYP_PATH = path.join(nodeModules, 'with a space', 'bin', 'node-gyp.js')
+        fs.cp path.join(npmNodeModules, 'node-gyp'), path.join(npmNodeModules, 'with a space')
+        process.env.npm_config_node_gyp = path.join(npmNodeModules, 'with a space', 'bin', 'node-gyp.js')
+        process.env.ATOM_NODE_GYP_PATH = path.join(npmNodeModules, 'with a space', 'bin', 'node-gyp.js')

       afterEach ->
         delete process.env.npm_config_node_gyp
@@ -565,7 +565,6 @@
     if process.platform isnt 'win32'
       # Only applicable on Linux and macOS
       describe "configurable Python binaries", ->
-        nodeModules = fs.realpathSync(path.join(__dirname, '..', 'node_modules'))
         [originalPython, originalNpmConfigPython] = []

         beforeEach ->


Thumbgallery now uses indexswitching

Thumbgallery now uses indexswitching

--- src/presets.coffee
+++ src/presets.coffee
@@ -143,12 +143,13 @@
       special:
         columns: 3
         rows: 3
-        groupSize: 10
+        groupSize: 9
         itemPadding: 5
       # Standard rondell options
       visibleItems: 9
       wrapIndices: false
       currentLayer: 1
+      switchIndices: true
       center:
         top: 215
         left: 250
@@ -174,34 +175,37 @@
           width: 292
           right: 0
           bottom: 0
+
       funcDiff: (d, r, i) ->
         Math.abs d
+
       funcOpacity: (l, r, i) ->
+        # Find current layers index for group comparison
+        currentLayerIndex = if r.currentLayer > r._focusedItem.currentSlot \
+            then r.currentLayer - 1 else r.currentLayer
+        # Modify items indices right of the selected slot to remove empty slot
+        i-- if i > r._focusedItem.currentSlot
         # Show items in the same group as the focused one
         if Math.floor((i - 1) / r.special.groupSize) is \
-          Math.floor((i - 1 - l) / r.special.groupSize) then 0.8 else 0
+          Math.floor((currentLayerIndex - 1) / r.special.groupSize) then 0.8 else 0
+
       funcTop: (l, r, i) ->
-        # Shift items right of the focused item and a special fix for the last item in the first group
-        if l > 0 or i is r.special.groupSize
-          i--
-          l--
-        # Hack to fix animation of the focused element in a group
-        i = 1 if i % r.special.groupSize is 0
+        # Modify items indices right of the selected slot to remove empty slot
+        i-- if i > r._focusedItem.currentSlot
         # Compute items vertical offset
         r.special.itemPadding + Math.floor(((i - 1) % r.special.groupSize) / r.special.rows) \
           * (r.itemProperties.size.height + r.special.itemPadding)

       funcLeft: (l, r, i) ->
-        # Shift elements right of the focused item and a special fix for the last item in the first group
-        if l > 0 or i is r.special.groupSize
-          i--
-          l--
-        # Hack to fix animation of the focused element in a group
-        i = 1 if i % r.special.groupSize is 0
+        # Find current layers index for group comparison
+        currentLayerIndex = if r.currentLayer > r._focusedItem.currentSlot \
+            then r.currentLayer - 1 else r.currentLayer
+        # Modify items indices right of the selected slot to remove empty slot
+        i-- if i > r._focusedItem.currentSlot
         # Get items column
         column = ((i - 1) % r.special.groupSize) % r.special.columns
         # Get the group difference
-        groupOffset = Math.floor((i - 1) / r.special.groupSize) - Math.floor((i - 1 - l) / r.special.groupSize)
+        groupOffset = Math.floor((i - 1) / r.special.groupSize) - Math.floor((currentLayerIndex - 1) / r.special.groupSize)
         # Compute final column positioning
         500 + r.special.itemPadding + (column + r.special.columns * groupOffset) \
           * (r.itemProperties.size.width + r.special.itemPadding)


Use Login.getTokenOrLogin helper in Publish

--- src/publish.coffee
+++ src/publish.coffee
@@ -3,7 +3,6 @@
 optimist = require 'optimist'
 Git = require 'git-utils'

-auth = require './auth'
 fs = require './fs'
 config = require './config'
 Command = require './command'
@@ -41,13 +40,6 @@
     options.alias('t', 'tag').string('tag').describe('tag', 'Specify a tag to publish')

   showHelp: (argv) -> @parseOptions(argv).showHelp()
-
-  getToken: (callback) ->
-    auth.getToken (error, token) ->
-      if error?
-        new Login().run({callback, commandArgs: []})
-      else
-        callback(null, token)

   # Create a new version and tag use the `npm version` command.
   #
@@ -112,10 +104,8 @@
   # callback    - The callback function invoke with an error as the first
   #               argument and true/false as the second argument.
   packageExists: (packageName, callback) ->
-    @getToken (error, token) ->
-      if error?
-        callback(error)
-        return
+    Login.getTokenOrLogin (error, token) ->
+      return callback(error) if error?

       requestSettings =
         url: "#{config.getAtomPackagesUrl()}/#{packageName}"


changes for browserify compatibility

changes for browserify compatibility

--- src/request.coffee
+++ src/request.coffee
@@ -36,6 +36,8 @@
       path: path
       method: @method
       headers: @headers
+      scheme: protocol.replace ':', ''
+      withCredentials: false

     if @body? && Buffer?
       @headers["Content-Length"] = Buffer.byteLength(@body)
@@ -131,10 +133,7 @@
   process: (callback) ->
     # TODO: take encoding into account
     # TODO: check content-length against actual length
-    if Buffer?
-      @buffer = Buffer.concat @chunks, @length
-    else
-      @buffer = @chunks.join("")
+    @buffer = @chunks.join("")

     @process_encoding =>
       @process_type callback


log_level fix, for consistency

log_level fix, for consistency

--- src/runtime.coffee
+++ src/runtime.coffee
@@ -86,7 +86,7 @@
       l = ((if x?.ref? then x.ref else x) for x in cf.locals)
       debug "showing current state: method '#{cf.method?.name}', stack: [#{s}], locals: [#{l}]"
     else
-      error "current frame is undefined. meta_stack looks like:", @meta_stack()
+      debug "current frame is undefined. meta_stack: #{@meta_stack()}"

   wait: (monitor, yieldee) ->
     # add current thread to wait queue


ensure to setup storage only once

ensure to setup storage only once

--- src/storage.coffee
+++ src/storage.coffee
@@ -7,10 +7,11 @@

 class Storage
   @initialize: (path, cb) ->
-    DBI.setup 'storage',
-      type: 'sqlite'
-      options:
-        filePath: path
+    if not DBI.hasSetup 'storage'
+      DBI.setup 'storage',
+        type: 'sqlite'
+        options:
+          filePath: path
     DBI.connect 'storage', (err, conn) ->
       if err
         cb err


takes initials

takes initials

--- src/ui/util.coffee
+++ src/ui/util.coffee
@@ -35,8 +35,9 @@
         name = nameof e
         firstname = e?.first_name
         return  firstname.charAt(0) + name.replace(firstname, "").charAt(1)
-    else if e?.display_name
-        name_splitted = e.display_name.split(' ')
+    else if e?.display_name || e?.fallback_name
+        name_to_split = e?.display_name ? e?.fallback_name
+        name_splitted = name_to_split.split(' ')
         firstname = name_splitted[0].charAt(0)
         if name_splitted.length == 1
             return firstname.charAt(0)


Remove promises from persist utils functions

--- src/angular-parse.coffee
+++ src/angular-parse.coffee
@@ -14,18 +14,18 @@
           result[key] = store.getItem key
         else
           result[key] = undefined
-      $q.when(result)
+      result

     set: (obj) ->
       for own key, val of obj
         store.setItem key, val
-      $q.when true
+      true

     remove: (keys) ->
       keys = [keys] unless angular.isArray keys
       for key in keys
         localStorage.removeItem key
-      $q.when true
+      true

 module.factory 'ParseUtils', ($http, $window) ->
   BaseUrl: "https://api.parse.com/1"
@@ -78,18 +78,17 @@
       user

     resumeSession: ->
-      persist.get(['PARSE_SESSION_TOKEN', 'PARSE_USER_INFO']).then (r) ->
-        userAttrs = r.PARSE_USER_INFO
-        sessionToken = r.PARSE_SESSION_TOKEN
-        if userAttrs and sessionToken
-          try
-            user = new ParseUser(JSON.parse(userAttrs))
-            auth.currentUser = user
-            auth.sessionToken = sessionToken
-            user.refresh()
-
-          catch e
-            false
+      results = persist.get(['PARSE_SESSION_TOKEN', 'PARSE_USER_INFO'])
+      userAttrs = results.PARSE_USER_INFO
+      sessionToken = results.PARSE_SESSION_TOKEN
+      if userAttrs and sessionToken
+        try
+          user = new ParseUser(JSON.parse(userAttrs))
+          auth.currentUser = user
+          auth.sessionToken = sessionToken
+          user.refresh()
+        catch e
+          false

     register: (username, password) ->
       new ParseUser(


Honor autoscroll option when setting a selection's buffer range

If we pass an explicit autoscroll option when selection, we don't want
to autoscroll the cursor. If the option is true, we'll autoscroll to
the middle of the selection. If the option is false, we don't want any
autoscrolling, including to the cursor. The cursor should only autoscroll
when the option is undefined.

--- src/app/selection.coffee
+++ src/app/selection.coffee
@@ -59,6 +59,7 @@
     options.reverse ?= @isReversed()
     @editSession.destroyFoldsIntersectingBufferRange(bufferRange) unless options.preserveFolds
     @modifySelection =>
+      @cursor.needsAutoscroll = false if options.autoscroll?
       @editSession.setMarkerBufferRange(@marker, bufferRange, options)

   getBufferRowRange: ->


LineMap.linesByDelta and translatePosition use common traverseByDelta

--- src/atom/line-map.coffee
+++ src/atom/line-map.coffee
@@ -89,21 +89,16 @@

   linesByDelta: (deltaType, startRow, endRow) ->
     lines = []
-    delta = new Point
-
-    for lineFragment in @lineFragments
-      break if delta.row > endRow
-      if delta.row >= startRow
-        if pendingFragment
-          pendingFragment = pendingFragment.concat(lineFragment)
-        else
-          pendingFragment = _.clone(lineFragment)
-        if pendingFragment[deltaType].row > 0
-          pendingFragment.bufferDelta = new Point(1, 0)
-          lines.push pendingFragment
-          pendingFragment = null
-      delta = delta.add(lineFragment[deltaType])
-
+    pendingFragment = null
+    @traverseByDelta deltaType, new Point(startRow, 0), new Point(endRow, Infinity), (lineFragment) ->
+      if pendingFragment
+        pendingFragment = pendingFragment.concat(lineFragment)
+      else
+        pendingFragment = _.clone(lineFragment)
+      if pendingFragment[deltaType].row > 0
+        pendingFragment.bufferDelta = new Point(1, 0)
+        lines.push pendingFragment
+        pendingFragment = null
     lines

   translatePosition: (sourceDeltaType, targetDeltaType, sourcePosition, options={}) ->
@@ -112,9 +107,6 @@
     wrapAtSoftNewlines = options.wrapAtSoftNewlines ? false
     skipAtomicTokens = options.skipAtomicTokens ? false

-    sourceDelta = new Point
-    targetDelta = new Point
-
     if sourcePosition.column < 0
       sourcePosition.column = 0

@@ -122,21 +114,20 @@
       sourcePosition.row = 0
       sourcePosition.column = 0

-    for lineFragment in @lineFragments
-      nextSourceDelta = sourceDelta.add(lineFragment[sourceDeltaType])
-      break if nextSourceDelta.isGreaterThan(sourcePosition)
-      sourceDelta = nextSourceDelta
-      targetDelta = targetDelta.add(lineFragment[targetDeltaType])
+    traversalResult = @traverseByDelta(sourceDeltaType, sourcePosition)
+    lastLineFragment = traversalResult.lastLineFragment
+    sourceDelta = traversalResult[sourceDeltaType]
+    targetDelta = traversalResult[targetDeltaType]

-    if lineFragment.isAtomic
+    if lastLineFragment.isAtomic
       if skipAtomicTokens and sourcePosition.column > sourceDelta.column
-        return new Point(targetDelta.row, targetDelta.column + lineFragment.text.length)
+        return new Point(targetDelta.row, targetDelta.column + lastLineFragment.text.length)
       else
         return targetDelta

-    maxSourceColumn = sourceDelta.column + lineFragment.text.length
-    maxTargetColumn = targetDelta.column + lineFragment.text.length
-    if lineFragment.isSoftWrapped() and sourcePosition.column >= maxSourceColumn
+    maxSourceColumn = sourceDelta.column + lastLineFragment.text.length
+    maxTargetColumn = targetDelta.column + lastLineFragment.text.length
+    if lastLineFragment.isSoftWrapped() and sourcePosition.column >= maxSourceColumn
       if wrapAtSoftNewlines
         return new Point(targetDelta.row + 1, 0)
       else
@@ -148,6 +139,20 @@
     targetColumn = targetDelta.column + (sourcePosition.column - sourceDelta.column)
     new Point(targetDelta.row, Math.min(maxTargetColumn, targetColumn))

+  traverseByDelta: (deltaType, startPosition, endPosition=startPosition, iterator=null) ->
+    traversalDelta = new Point
+    screenDelta = new Point
+    bufferDelta = new Point
+
+    for lineFragment in @lineFragments
+      iterator(lineFragment) if traversalDelta.isGreaterThanOrEqual(startPosition) and iterator?
+      traversalDelta = traversalDelta.add(lineFragment[deltaType])
+      break if traversalDelta.isGreaterThan(endPosition)
+      screenDelta = screenDelta.add(lineFragment.screenDelta)
+      bufferDelta = bufferDelta.add(lineFragment.bufferDelta)
+
+    { screenDelta, bufferDelta, lastLineFragment: lineFragment }
+
   logLines: (start=0, end=@screenLineCount() - 1)->
     for row in [start..end]
       line = @lineForScreenRow(row).text


updated qlog.

updated qlog.

--- log.coffee
+++ log.coffee
@@ -65,6 +65,6 @@
 			results.push 'ELSE_TYPE: '+arg+' ['+typeof arg+']'
 	return results.join(', ')

-global.log = module.exports = ->
+module.exports = ->
 	# Output Object Introspection
-	console.log ' log: '+format.apply this, arguments
+	return ' log: '+format.apply this, arguments


Tidy translate

Tidy translate

--- src/coffee/flight.coffee
+++ src/coffee/flight.coffee
@@ -138,13 +138,13 @@

     if @back
       setTimeout =>
-        @translate(currentPage, "Y", screenHeight)
+        @translate currentPage, "Y", screenHeight
       , 0
     else
       targetPage.style.zIndex = "1000"
-      @translate(targetPage, "Y", screenHeight,"0ms")
-      setTimeout =>
-        @translate(targetPage, "Y", "0%")
+      @translate targetPage, "Y", screenHeight,"0ms"
+      setTimeout =>
+        @translate targetPage, "Y", "0%"
       , 0

     @back = false