DRY php-cs-fixer options

DRY php-cs-fixer options

--- src/beautifiers/php-cs-fixer.coffee
+++ src/beautifiers/php-cs-fixer.coffee
@@ -18,6 +18,12 @@
     @debug('php-cs-fixer', options)

     configFile = if context? and context.filePath? then @findFile(path.dirname(context.filePath), '.php_cs')
+    phpCsFixerOptions = [
+      "fix"
+      "--level=#{options.level}" if options.level
+      "--fixers=#{options.fixers}" if options.fixers
+      "--config-file=#{configFile}" if configFile
+    ]
     runOptions = {
       ignoreReturnCode: true
       help: {
@@ -40,26 +46,15 @@
       # Check if PHP-CS-Fixer path was found
       if phpCSFixerPath?
         # Found PHP-CS-Fixer path
+        tempFile = @tempFile("temp", text)
+
         if @isWindows
-          @run("php", [
-            phpCSFixerPath
-            "fix"
-            "--level=#{options.level}" if options.level
-            "--fixers=#{options.fixers}" if options.fixers
-            "--config-file=#{configFile}" if configFile
-            tempFile = @tempFile("temp", text)
-            ], runOptions)
+          @run("php", [phpCSFixerPath, phpCsFixerOptions, tempFile], runOptions)
             .then(=>
               @readFile(tempFile)
             )
         else
-          @run(phpCSFixerPath, [
-            "fix"
-            "--level=#{options.level}" if options.level
-            "--fixers=#{options.fixers}" if options.fixers
-            "--config-file=#{configFile}" if configFile
-            tempFile = @tempFile("temp", text)
-            ], runOptions)
+          @run(phpCSFixerPath, [phpCsFixerOptions, tempFile], runOptions)
             .then(=>
               @readFile(tempFile)
             )


$ -> _

$ -> _

--- src/client/embeds/deployment.coffee
+++ src/client/embeds/deployment.coffee
@@ -28,12 +28,12 @@
     # "index"-> href for embed

     @settings   = dpl.settings
-    # "$selector" : ".ship", //CSS3 Selector on which to embed the ship(s)
-    # "$multi": true, //Wether to embed on the first matching element or all
-    # "$placement" : "before"|"after"|"append"|"prepend"|"replace", //Position relative to selector
-    # "$sandbox" : true //Wether to sandbox the platform : true
-    # "$width" : "100%", //Dimensions to give the containing element. Passed as-is as style tag
-    # "$height" : "50px", //Dimensions to give the containing element. Passed as-is as style tag
+    # "_selector" : ".ship", //CSS3 Selector on which to embed the ship(s)
+    # "_multi": true, //Wether to embed on the first matching element or all
+    # "_placement" : "before"|"after"|"append"|"prepend"|"replace", //Position relative to selector
+    # "_sandbox" : true //Wether to sandbox the platform : true
+    # "_width" : "100%", //Dimensions to give the containing element. Passed as-is as style tag
+    # "_height" : "50px", //Dimensions to give the containing element. Passed as-is as style tag

     @targets    = @getTargets()
     @_imports   = []
@@ -41,12 +41,12 @@
     @_callbacks = []

   getTargets : (opts={})->
-    selector = @settings.$selector
+    selector = @settings._selector
     targets = []
     return [] if !selector || selector.length == 0
     return @targets if @targets and !opts.refresh

-    if @settings.$multi
+    if @settings._multi
       targets = document.querySelectorAll selector
     else
       target = document.querySelector selector
@@ -79,7 +79,7 @@
   embed : (opts={}, embedCompleteCallback)->
     @targets = @getTargets({refresh:opts.refresh}) if opts.refresh
     @_callbacks.push(embedCompleteCallback) if _.isFunction(embedCompleteCallback)
-    if @settings.$sandbox
+    if @settings._sandbox
       @forEachTarget (target, args...)=>
         iframe = @embedIframe(target)
         @insert(target, iframe)
@@ -150,10 +150,10 @@
       el = @_elements.shift()

   insert: (target, el)->
-    setDimension(el, 'width', @settings.$width || '100%')
-    setDimension(el, 'height', @settings.$height)
+    setDimension(el, 'width', @settings._width || '100%')
+    setDimension(el, 'height', @settings._height)
     @_elements.push el
-    switch @settings.$placement
+    switch @settings._placement
       when 'before' then target.parentNode.insertBefore(el, target)
       when 'after'  then target.parentNode.insertBefore(el, target.nextSibling)
       when 'top'    then target.insertBefore(el, target.firstChild)


Removed base Model requirement for Document

This var is unused since DocumentModel inherits from FileModel


Help dialog; consistent ordering of commands.

It reads weird on the helpd dialog if we don't put the full-page and
half-page commands in a similar order.

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -122,8 +122,8 @@
       "scrollToBottom",
       "scrollPageDown",
       "scrollPageUp",
+      "scrollFullPageDown",
       "scrollFullPageUp",
-      "scrollFullPageDown",
       "scrollLeft",
       "scrollRight",
       "scrollToLeft",


Fixed up the test for the new unsubscribe behavior.

--- test/lib/messageIOClient-spec.coffee
+++ test/lib/messageIOClient-spec.coffee
@@ -82,9 +82,40 @@
         expect(@socketIOClient.emit).to.have.been.calledWith 'subscribe', 'pear_bc'

   describe 'unsubscribe', ->
-    beforeEach ->
-      @sut.start()
-      @sut.unsubscribe 'apple'
+    describe 'received only', ->
+      beforeEach ->
+        @sut.start()
+        @sut.unsubscribe 'banana', ['received']

-    it 'should call emit on socketIOClient', ->
-      expect(@socketIOClient.emit).to.have.been.calledWith 'unsubscribe', 'apple'
+      it 'should call emit on socketIOClient', ->
+        expect(@socketIOClient.emit).to.have.been.calledWith 'unsubscribe', 'banana'
+
+    describe 'sent only', ->
+      beforeEach ->
+        @sut.start()
+        @sut.unsubscribe 'watermelon', ['sent']
+
+      it 'should call emit on socketIOClient', ->
+        expect(@socketIOClient.emit).to.have.been.calledWith 'unsubscribe', 'watermelon_send'
+
+    describe 'broadcast only', ->
+      beforeEach ->
+        @sut.start()
+        @sut.unsubscribe 'coffee', ['broadcast']
+
+      it 'should call emit on socketIOClient', ->
+        expect(@socketIOClient.emit).to.have.been.calledWith 'unsubscribe', 'coffee_bc'
+
+    describe 'all kinds', ->
+      beforeEach ->
+        @sut.start()
+        @sut.unsubscribe 'apple', ['broadcast', 'sent', 'received']
+
+      it 'should call emit on socketIOClient', ->
+        expect(@socketIOClient.emit).to.have.been.calledWith 'unsubscribe', 'apple'
+
+      it 'should call emit on socketIOClient', ->
+        expect(@socketIOClient.emit).to.have.been.calledWith 'unsubscribe', 'apple_send'
+
+      it 'should call emit on socketIOClient', ->
+        expect(@socketIOClient.emit).to.have.been.calledWith 'unsubscribe', 'apple_bc'


EDSC-449: Prevent invalid or incomplete states from being serialized to the URL

--- app/assets/javascripts/models/ui/state_manager.coffee
+++ app/assets/javascripts/models/ui/state_manager.coffee
@@ -211,7 +211,24 @@
       else
         @_timeouts[key] = setTimeout((-> observable(false)), config.defaultAnimationDurationMs)

+    _isValid: (path, serialized) ->
+      # Perform validations to ensure that invalid or incomplete states (often caused by script errors)
+      # don't get saved in the URL, destroying the user's project bookmark
+
+      if path.indexOf('/granule-details') != -1 && !serialized.g
+        # If the user is on the granule details page, but there is no selected selected granule
+        false
+      if path.indexOf('/granules') != -1 && (!serialized.p || serialized.p.indexOf('!') == 0)
+        # If the user is viewing granules, but there is no selected dataset
+        false
+      else
+        true
+
     _persistStateInUrl: ->
-      @historyChanged = true if urlUtil.saveState(@path(), @serialize(), !@historyChanged, @page.workspaceNameField.peek())
+      path = @path()
+      serialized = @serialize()
+      if @_isValid(path, serialized)
+        changed = urlUtil.saveState(path, serialized, !@historyChanged, @page.workspaceNameField.peek())
+        @historyChanged = true if changed

   exports = StateManager


Speed up logic for triggering and executing preview refreshes

--- servers/www/assets/js/services/panes/previewer.coffee
+++ servers/www/assets/js/services/panes/previewer.coffee
@@ -35,6 +35,7 @@
             </div>
           </div>
         </div>
+        <iframe class="plnk-runner" frameborder="0" width="100%" height="100%" scrolling="auto"></iframe>
       </div>
     """
     refreshPreview: ->
@@ -45,26 +46,23 @@
         json.files[buffer.filename] =
           content: buffer.content

+      self.$preview.fadeOut("")
+
       request = $http.post(url.run, json)

       request.then (response) ->
-        $old = self.$preview
-        self.$preview = $("<iframe>", src: response.data.run_url, class: "plnk-runner", frameborder: 0, width: "100%", height: "100%", scrolling: "auto").appendTo(self.el)
-        self.$preview.ready ->
-          if $old then $old.fadeOut -> $old.remove()
-          self.$preview.fadeIn()
-
+        self.$preview[0].contentWindow.location.replace(response.data.run_url)
+        self.$preview.fadeIn(10).ready ->
           self.badge = null
-    , 1000

     link: ($scope, el, attrs) ->
       self = @
       @el = el
-      @$preview = null
+      @$preview = $(".plnk-runner", el)
       @enabled = false
       @awaiting = false

-      refresh = angular.bind(@, debounce(@refreshPreview, 1000))
+      refresh = angular.bind(@, debounce(@refreshPreview, 750))

       $scope.scratch = scratch
       $scope.refreshPreview = refresh


About page doesn't depend on about.md, so don't read it

--- server/controllers/static.coffee
+++ server/controllers/static.coffee
@@ -5,10 +5,7 @@
 fs = require( "fs" )

 exports.about = (req, res) ->
-  aboutMarkdown = fs.readFileSync("#{process.cwd()}/lib/about.md", 'UTF8')
-  aboutHTML = markdown.toHTML(aboutMarkdown)
   res.render "about",
-    aboutHTML: aboutHTML
     title: "Welcome to the NRT"

 exports.architecture = (req, res) ->


locally server sockjs client code

locally server sockjs client code

--- server/express/lib/server.coffee
+++ server/express/lib/server.coffee
@@ -38,7 +38,7 @@
 # Set export objects for node and coffee to a function that generates a sfw server.
 module.exports = exports = (argv) ->
   # Echo sockjs server
-  sockjs_opts = {sockjs_url: "http://cdn.sockjs.org/sockjs-0.3.min.js"}
+  sockjs_opts = {sockjs_url: "/js/sockjs-0.3.min.js"}

   sockjs_echo = sockjs.createServer(sockjs_opts)
   sockjs_echo.on('connection', (conn) ->
@@ -49,7 +49,7 @@
         conn.write(message)
     )
   )
-  logWatchSocket = sockjs.createServer({sockjs_url: "http://cdn.sockjs.org/sockjs-0.3.min.js"})
+  logWatchSocket = sockjs.createServer({sockjs_url: "/js/sockjs-0.3.min.js"})
   logWatchSocket.on('connection', (conn) ->
     logWatchSocket.on('fetch', (page) ->
       reference =


watch streams clean up

watch streams clean up

--- server/express/lib/server.coffee
+++ server/express/lib/server.coffee
@@ -107,7 +107,7 @@
         listeners: logWatchSocket.listeners('fetch').length
       ws.send JSON.stringify(reference), (e) ->
         if e
-          log 'unable to send ws message: ', e
+          log 'unable to send ws message: ', e, reference
           logWatchSocket.removeListener 'fetch', fetchListener
           ws.close()
     )


use html() instead of text()

use html() instead of text()

--- app/assets/javascripts/pages/eu_legislation.js.coffee
+++ app/assets/javascripts/pages/eu_legislation.js.coffee
@@ -3,8 +3,8 @@
     historicRows = $(e.target).parent().prev('table').find('tr')
     if historicRows.hasClass('hidden')
       historicRows.removeClass('hidden')
-      $(e.target).text('HIDE HISTORY')
+      $(e.target).html('HIDE HISTORY')
     else
       historicRows.addClass('hidden')
-      $(e.target).text('SHOW HISTORY')
+      $(e.target).html('SHOW HISTORY')
   )


forgoten "debugger"

forgoten "debugger"

--- app/assets/javascripts/uploadcare/files/url.js.coffee
+++ app/assets/javascripts/uploadcare/files/url.js.coffee
@@ -81,7 +81,6 @@
         success: (data) =>
           return if @__shutdown
           @__state('progress', data)
-          debugger
           [@fileName, @fileId] = [data.original_filename, data.uuid]
           @__uploadDf.resolve(this)


Add test for animating to null

Add test for animating to null

--- test/tests/LayerAnimationTest.coffee
+++ test/tests/LayerAnimationTest.coffee
@@ -1,3 +1,5 @@
+assert = require "assert"
+
 AnimationTime = 0.05
 AnimationProperties = ["x", "y", "midY", "rotation"]

@@ -144,6 +146,17 @@
 			layer.on "end", ->
 				layer.backgroundColor.toName().should.eql color
 				done()
+
+		it "should not animate non-animatable properties that are set to null", ->
+			layerA = new Layer
+			layerB = new Layer
+				parent: layerA
+			layerB.animateTo
+				parent: null
+				options:
+					instant: true
+			assert.equal(layerB.parent, null)
+

 	describe "Basic", ->


Re-disable this since it breaks acvitiity create

--- client/app/CommonViews/LikeView.coffee
+++ client/app/CommonViews/LikeView.coffee
@@ -5,7 +5,7 @@
     options.tagName            or= 'span'
     options.cssClass           or= 'like-view'
     options.tooltipPosition    or= 'se'
-    options.checkIfLikedBefore  ?= yes
+    options.checkIfLikedBefore  ?= no

     super options, data


removed title attribute from LikeView counter

--- client/app/CommonViews/LikeView.coffee
+++ client/app/CommonViews/LikeView.coffee
@@ -19,7 +19,6 @@
       mouseenter  : => @fetchLikeInfo()
       attributes  :
         href      : "#"
-        title     : "Click to view..."
       click       : (event)=>
         # event.preventDefault()
         if data.meta.likes > 0


Client: Very minor typo.

Client: Very minor typo.

--- client/app/js/concerns/commands.coffee
+++ client/app/js/concerns/commands.coffee
@@ -120,7 +120,7 @@
   if messages.length > 0
     plural = if messages.length == 1 then "is" else "are"
     messages.unshift("There #{plural} #{counter} Pokemon with a PBV of
-      #{pbv} or less.")
+      #{pbv} or less")
   messages

 findTotalPBV = (pokemon) ->


spec: graceful-fs should not touch global fs object

--- spec/asar-spec.coffee
+++ spec/asar-spec.coffee
@@ -414,7 +414,11 @@
       assert stats.isFile()

   describe 'graceful-fs module', ->
+    gfs = require 'graceful-fs'
+
     it 'recognize asar archvies', ->
-      gfs = require 'graceful-fs'
       p = path.join fixtures, 'asar', 'a.asar', 'link1'
       assert.equal gfs.readFileSync(p).toString(), 'file1\n'
+
+    it 'does not touch global fs object', ->
+      assert.notEqual fs.readdir, gfs.readdir


lodash has startsWith :D

lodash has startsWith :D

--- resources/assets/lib/store-centili.coffee
+++ resources/assets/lib/store-centili.coffee
@@ -34,7 +34,7 @@
       hasFrame = false
       hasContent = false
       for node in mutation.addedNodes
-        node.id?.substr(0, 'fancybox-'.length) == 'fancybox-' && fancyboxes.push(node)
+        _.startsWith(node.id, 'fancybox-') && fancyboxes.push(node)
         hasFrame |= node.id == 'fancybox-frame'
         hasContent |= node.id == 'fancybox-content'


Providers: add Softlayer configs and attribute mapping

--- client/app/lib/providers/config.coffee
+++ client/app/lib/providers/config.coffee
@@ -285,12 +285,36 @@
         label              : 'Deploy Timeout'
         placeholder        : 'deploy timeout value in seconds'

+  softlayer                :
+    name                   : 'Softlayer'
+    link                   : 'http://www.softlayer.com'
+    title                  : 'Softlayer'
+    color                  : '#B52025'
+    supported              : yes
+    enabled                : 'beta'
+    defaultTemplate        : require './templates/softlayer'
+    instanceTypes          : require './instance-types/softlayer'
+    description            : 'Softlayer Virtual Guest'
+    attributeMapping       :
+      image                : 'image'
+      instance_type        : 'size'
+      region               : 'region'
+    credentialFields       :
+      username             :
+        label              : 'User ID'
+        placeholder        : 'user id including prefix (like SL or IBM)'
+      api_key              :
+        label              : 'API Key'
+        placeholder        : 'softlayer api key'
+        attributes         :
+          autocomplete     : if isProd then 'off' else 'on'
+
   rackspace                :
     name                   : 'Rackspace'
     link                   : 'http://www.rackspace.com'
     title                  : 'Rackspace'
     color                  : '#d8deea'
-    supported              : no
+    supported              : yes
     enabled                : no
     description            : 'Rackspace machines'
     credentialFields       :
@@ -302,26 +326,6 @@
       apiKey               :
         label              : 'API Key'
         placeholder        : 'rackspace api key'
-        attributes         :
-          autocomplete     : if isProd then 'off' else 'on'
-
-  softlayer                :
-    name                   : 'Softlayer'
-    link                   : 'http://www.softlayer.com'
-    title                  : 'Softlayer'
-    color                  : '#B52025'
-    supported              : yes
-    enabled                : 'beta'
-    defaultTemplate        : require './templates/softlayer'
-    instanceTypes          : require './instance-types/softlayer'
-    description            : 'Softlayer Virtual Guest'
-    credentialFields       :
-      username             :
-        label              : 'Username'
-        placeholder        : 'username for softlayer'
-      api_key              :
-        label              : 'API Key'
-        placeholder        : 'softlayer api key'
         attributes         :
           autocomplete     : if isProd then 'off' else 'on'


Use space-ben to create editor div

Use space-ben to create editor div

--- spec/atom-spec.coffee
+++ spec/atom-spec.coffee
@@ -11,7 +11,7 @@

   describe "base stylesheet loading", ->
     beforeEach ->
-      rootView.append $("

")
+      rootView.append $$ -> @div class: 'editor'
       rootView.attachToDom()
       atom.themes.load()
       atom.watchThemes()


Spec change: like JSON.stringify, functions in objects are silently filtered

--- spec/cson-spec.coffee
+++ spec/cson-spec.coffee
@@ -18,8 +18,8 @@
         expect(-> CSON.stringify(-> 'function')).toThrow()

     describe "when the object contains a function", ->
-      it "throws an exception", ->
-        expect(-> CSON.stringify(a:  -> 'function')).toThrow()
+      it "it gets filtered away, when not providing a visitor function", ->
+        expect(CSON.stringify(a:  -> 'function')).toBe '{}'

     describe "when formatting an undefined key", ->
       it "does not include the key in the formatted CSON", ->


Rename require from atom-api to atom

Rename require from atom-api to atom

--- spec/link-spec.coffee
+++ spec/link-spec.coffee
@@ -1,4 +1,4 @@
-{RootView} = require 'atom-api'
+{RootView} = require 'atom'
 shell = require 'shell'

 describe "link package", ->


Fetching config values only once.

Fetching config values only once.


IE11 / strict-mode tweaks

IE11 / strict-mode tweaks

--- source/Art/Engine/Animation/PersistantAnimator.coffee
+++ source/Art/Engine/Animation/PersistantAnimator.coffee
@@ -381,7 +381,7 @@
       fromVoid:   # added animation
       toVoid:     # removed animation
   ###
-  constructor: (prop, options)->
+  constructor: (prop, options = {})->
     super
     @_prop = prop
     @_options = options


:white_check_mark: Mock repo for specs

:white_check_mark: Mock repo for specs

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -891,23 +891,53 @@
           expect($(tabBar.tabForItem(imageView)).find('.title')).toHaveClass 'temp'

   describe "integration with version control systems", ->
-    [repository, tab] = []
+    [repository, tab, tab1] = []

     beforeEach ->
       atom.config.set "tabs.enableVcsColoring", true
-      tab = tabBar.tabAtIndex 1
-      repository = atom.project.getRepo()
-      spyOn(repository, 'isPathIgnored')
-      spyOn(repository, 'getCachedPathStatus')
-      repository.getCachedPathStatus.andReturn 128
+
+      tab = tabBar.tabForItem editor1
+      spyOn(tab, 'setupVcsStatus').andCallThrough()
+      spyOn(tab, 'updateVcsStatus').andCallThrough()
+
+      tab1 = tabBar.tabForItem item1
+      tab1.path = '/some/path/outside/the/repository'
+      spyOn(tab1, 'updateVcsStatus').andCallThrough()
+
+      # Mock the repository
+      repository = jasmine.createSpyObj 'repo', ['isPathIgnored', 'getCachedPathStatus', 'isStatusNew', 'isStatusModified']
+      repository.isStatusNew.andCallFake (status) -> status is 'new'
+      repository.isStatusModified.andCallFake (status) -> status is 'modified'
+
+      repository.onDidChangeStatus = (callback) ->
+        @changeStatusCallbacks ?= []
+        @changeStatusCallbacks.push(callback)
+        dispose: => _.remove(@changeStatusCallbacks, callback)
+      repository.emitDidChangeStatus = (event) ->
+        callback(event) for callback in @changeStatusCallbacks ? []
+
+      repository.onDidChangeStatuses = (callback) ->
+        @changeStatusesCallbacks ?= []
+        @changeStatusesCallbacks.push(callback)
+        dispose: => _.remove(@changeStatusesCallbacks, callback)
+      repository.emitDidChangeStatuses = (event) ->
+        callback(event) for callback in @changeStatusesCallbacks ? []
+
+      # Mock atom.project to simulate we are working with a repository
+      spyOn(atom.project, 'getPaths').andReturn [tab.path]
+      spyOn(atom.project, 'getRepositories').andReturn [repository]
+
+      tab.setupVcsStatus()
+      tab1.setupVcsStatus()

     describe "when working inside a VCS repository", ->
       it "adds custom style for new items", ->
+        repository.getCachedPathStatus.andReturn 'new'
         tab.updateVcsStatus(repository)
         expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-added"

       it "adds custom style for modified items", ->
-        repository.getCachedPathStatus.andReturn 256
+        repository.getCachedPathStatus.andReturn 'modified'
         tab.updateVcsStatus(repository)
         expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-modified"

@@ -917,57 +947,51 @@
         expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-ignored"

       it "does not add any styles for items not in the repository", ->
-        tab = tabBar.tabForItem(item1)
-        tab.path = '/some/path'
-        tab.setupVcsStatus()
-
         expect(tabBar.find('.tab:eq(0) .title')).not.toHaveClass "status-added"
         expect(tabBar.find('.tab:eq(0) .title')).not.toHaveClass "status-modified"
         expect(tabBar.find('.tab:eq(0) .title')).not.toHaveClass "status-ignored"

     describe "when changes in item statuses are notified", ->
       it "updates status for items in the repository", ->
-        spyOn(tab, 'updateVcsStatus').andCallThrough()
-        expect(tab.updateVcsStatus.calls.length).toEqual 0
-        repository.emitter.emit 'did-change-statuses'
+        tab.updateVcsStatus.reset()
+        repository.emitDidChangeStatuses()
         expect(tab.updateVcsStatus.calls.length).toEqual 1

       it "updates the status of an item if it has changed", ->
+        repository.getCachedPathStatus.reset()
         expect(tabBar.find('.tab:eq(1) .title')).not.toHaveClass "status-modified"
-        repository.emitter.emit 'did-change-status', {path: tab.path, pathStatus: 256}
+        repository.emitDidChangeStatus {path: tab.path, pathStatus: "modified"}
         expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-modified"
         expect(repository.getCachedPathStatus.calls.length).toBe 0

       it "does not update status for items not in the repository", ->
-        tab = tabBar.tabForItem(item1)
-        tab.path = '/some/path'
-        tab.setupVcsStatus()
-
-        spyOn(tab, 'updateVcsStatus').andCallThrough()
-        repository.emitter.emit 'did-change-statuses'
-        expect(tab.updateVcsStatus.calls.length).toEqual 0
+        tab1.updateVcsStatus.reset()
+        repository.emitDidChangeStatuses()
+        expect(tab1.updateVcsStatus.calls.length).toEqual 0

     describe "when an item is saved", ->
       it "does not update VCS subscription if the item's path remains the same", ->
-        spyOn(tab, 'setupVcsStatus').andCallThrough()
-        tab.item.buffer.emitter.emit( 'did-save', {path: tab.path})
+        tab.setupVcsStatus.reset()
+        tab.item.buffer.emitter.emit 'did-save', {path: tab.path}
         expect(tab.setupVcsStatus.calls.length).toBe 0

       it "updates VCS subscription if the item's path has changed", ->
-        spyOn(tab, 'setupVcsStatus').andCallThrough()
-        tab.item.buffer.emitter.emit( 'did-save', {path: '/some/other/path'})
+        tab.setupVcsStatus.reset()
+        tab.item.buffer.emitter.emit 'did-save', {path: '/some/other/path'}
         expect(tab.setupVcsStatus.calls.length).toBe 1

     describe "when enableVcsColoring changes in package settings", ->
       it "removes status from the tab if enableVcsColoring is set to false", ->
-        tab.setupVcsStatus()
+        repository.emitDidChangeStatus {path: tab.path, pathStatus: 'new'}
+
         expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-added"
         atom.config.set "tabs.enableVcsColoring", false
         expect(tabBar.find('.tab:eq(1) .title')).not.toHaveClass "status-added"

       it "adds status to the tab if enableVcsColoring is set to true", ->
         atom.config.set "tabs.enableVcsColoring", false
-        tab.setupVcsStatus()
-        expect(tabBar.find('.tab:eq(1) .title')).not.toHaveClass "status-added"
+        repository.emitDidChangeStatus {path: tab.path, pathStatus: 'modified'}
+
+        expect(tabBar.find('.tab:eq(1) .title')).not.toHaveClass "status-modified"
         atom.config.set "tabs.enableVcsColoring", true
-        expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-added"
+        expect(tabBar.find('.tab:eq(1) .title')).toHaveClass "status-modified"


Reformat To Fix :shirt: Warning

Reformat To Fix :shirt: Warning

--- spec/test-spec.coffee
+++ spec/test-spec.coffee
@@ -16,7 +16,15 @@
     specPath = path.join(currentDir, 'spec')

   it "calls atom to test", ->
-    atomSpawn = spyOn(child_process, 'spawn').andReturn({stdout: {on: -> {}}, stderr: {on: -> {}}, on: -> {}})
+    atomSpawn = spyOn(child_process, 'spawn').andReturn({
+      stdout: {
+        on: ->
+      },
+      stderr: {
+        on: ->
+      },
+      on: ->
+    })
     apm.run(['test'])

     waitsFor 'waiting for test to complete', ->
@@ -41,7 +49,14 @@
     returnWithCode = (type, code) ->
       callback = jasmine.createSpy('callback')
       atomReturnFn = (e, fn) -> fn(code) if e is type
-      spyOn(child_process, 'spawn').andReturn({stdout: {on: -> {}}, stderr: {on: -> {}}, on: atomReturnFn})
+      spyOn(child_process, 'spawn').andReturn({
+        stdout: {
+          on: ->
+        },
+        stderr: {
+          on: ->
+        },
+        on: atomReturnFn})
       apm.run(['test'], callback)

     describe 'successfully', ->


Finder: AppController: enable uploader

Finder: AppController: enable uploader

--- client/finder/AppController.coffee
+++ client/finder/AppController.coffee
@@ -21,7 +21,7 @@
     finderView  = @controller.getView()

     finderView.addSubView @getAppTitleView()  if options.addAppTitle
-    # finderView.addSubView @getUploader()
+    finderView.addSubView @getUploader()

     return @controller


Bundle themes are no longer copied

Bundle themes are no longer copied

--- spec/app/config-spec.coffee
+++ spec/app/config-spec.coffee
@@ -181,20 +181,6 @@
           expect(fsUtils.exists(path.join(config.configDirPath, 'snippets'))).toBeTruthy()
           expect(fsUtils.exists(path.join(config.configDirPath, 'themes'))).toBeTruthy()
           expect(fsUtils.isFileSync(path.join(config.configDirPath, 'config.cson'))).toBeTruthy()
-
-      it "copies the bundles themes to ~/.atom", ->
-        initializationDone = false
-        jasmine.unspy(window, "setTimeout")
-        config.initializeConfigDirectory ->
-          initializationDone = true
-
-        waitsFor -> initializationDone
-
-        runs ->
-          expect(fsUtils.isFileSync(path.join(config.configDirPath, 'themes/atom-dark-ui/package.cson'))).toBeTruthy()
-          expect(fsUtils.isFileSync(path.join(config.configDirPath, 'themes/atom-light-ui/package.cson'))).toBeTruthy()
-          expect(fsUtils.isFileSync(path.join(config.configDirPath, 'themes/atom-dark-syntax.less'))).toBeTruthy()
-          expect(fsUtils.isFileSync(path.join(config.configDirPath, 'themes/atom-light-syntax.less'))).toBeTruthy()

   describe ".loadUserConfig()", ->
     beforeEach ->


List must be emptied in the result callback to avoid duplicates

--- client/suggestion_completer.coffee
+++ client/suggestion_completer.coffee
@@ -8,13 +8,15 @@
     @suggestionField = @options.suggestionField || "suggest"
     @suggestionName = @options.suggestionName || "placesuggest"
     @indexName = @options.indexName || "places"
+    @term = null
     @inputEl.on('input', (e) =>
       q = $("#q").val()
-      @listEl.empty()
-      if q.length >= 2
+      if q.length >= 2 and q != @term
+        @term = q
         @getAutoCompleteResults(q).then (result) =>
           @emit('result', result)
           completions = result[@suggestionName][0].options
+          @listEl.empty()
           $.map(completions, (completion) =>
             li = $("<li data-payload='" +
                 JSON.stringify(completion.payload) +
@@ -54,7 +56,7 @@
           @emit('arrowup',
             $("##{@listEl.attr('id')} li.selected").first()
           )
-        when 13, 9 # Enter
+        when 13, 9 # Enter, tab
           @submit(e)
     )


added 'json' view helper

added 'json' view helper

--- shared/handlebars_helpers.coffee
+++ shared/handlebars_helpers.coffee
@@ -32,3 +32,6 @@
     template = templateFinder.getTemplate(templateName)
     html = template(this)
     new Handlebars.SafeString(html)
+
+  json: (object) ->
+    new Handlebars.SafeString JSON.stringify(object) || 'null'


Run tests

Run tests

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -288,7 +288,7 @@
   ciTasks.push('set-version', 'check-licenses', 'lint', 'generate-asar')
   ciTasks.push('mkdeb') if process.platform is 'linux'
   ciTasks.push('mktar') if process.platform is 'linux'
-  # ciTasks.push('test') if process.platform is 'darwin'
+  ciTasks.push('test') if process.platform is 'darwin'
   ciTasks.push('codesign:app') if process.platform is 'darwin' and not process.env.CI
   if process.platform is 'win32'
     ciTasks.push('codesign:exe') if process.env.JANKY_SIGNTOOL


Remove unneeded empty array fallback

Remove unneeded empty array fallback

--- src/app/atom-package.coffee
+++ src/app/atom-package.coffee
@@ -68,7 +68,7 @@
     if @metadata.keymaps
       @metadata.keymaps.map (name) -> fsUtils.resolve(keymapsDirPath, name, ['json', 'cson', ''])
     else
-      fsUtils.list(keymapsDirPath, ['cson', 'json']) ? []
+      fsUtils.list(keymapsDirPath, ['cson', 'json'])

   loadStylesheets: ->
     @stylesheets = @getStylesheetPaths().map (path) -> [path, loadStylesheet(path)]
@@ -78,18 +78,18 @@
     if @metadata.stylesheets
       @metadata.stylesheets.map (name) -> fsUtils.resolve(stylesheetDirPath, name, ['css', 'less', ''])
     else
-      fsUtils.list(stylesheetDirPath, ['css', 'less']) ? []
+      fsUtils.list(stylesheetDirPath, ['css', 'less'])

   loadGrammars: ->
     @grammars = []
     grammarsDirPath = fsUtils.join(@path, 'grammars')
-    for grammarPath in fsUtils.list(grammarsDirPath, ['.cson', '.json']) ? []
+    for grammarPath in fsUtils.list(grammarsDirPath, ['.cson', '.json'])
       @grammars.push(TextMateGrammar.loadSync(grammarPath))

   loadScopedProperties: ->
     @scopedProperties = []
     scopedPropertiessDirPath = fsUtils.join(@path, 'scoped-properties')
-    for scopedPropertiesPath in fsUtils.list(scopedPropertiessDirPath, ['.cson', '.json']) ? []
+    for scopedPropertiesPath in fsUtils.list(scopedPropertiessDirPath, ['.cson', '.json'])
       for selector, properties of fsUtils.readObject(scopedPropertiesPath)
         @scopedProperties.push([scopedPropertiesPath, selector, properties])


:lipstick:

:lipstick:

--- src/app/autocomplete.coffee
+++ src/app/autocomplete.coffee
@@ -86,15 +86,13 @@
     [prefix, suffix] = ["", ""]

     @currentBuffer.scanInRange @wordRegex, lineRange, (match, range, {stop}) ->
+      stop() if range.start.isGreaterThan(selectionRange.end)
+
       if range.intersectsWith(selectionRange)
         prefixOffset = selectionRange.start.column - range.start.column
         suffixOffset = selectionRange.end.column - range.end.column

-        if range.start.isLessThan(selectionRange.start)
-          prefix = match[0][0...prefixOffset]
-
-        if range.end.isGreaterThan(selectionRange.end)
-          suffix = match[0][suffixOffset..]
-          stop()
+        prefix = match[0][0...prefixOffset] if range.start.isLessThan(selectionRange.start)
+        suffix = match[0][suffixOffset..] if range.end.isGreaterThan(selectionRange.end)

     {prefix, suffix}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -145,6 +145,14 @@

 ###*
 Returns true if the element is present in the array.
+
+

+   ["a", "b", "c"].include("c")
+=> true
+
+   [40, "a"].include(700)
+=> false
+

 @name include
 @methodOf Array#


koding kontrol: nest the query and who params

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -42,6 +42,11 @@
     then Promise.resolve region
     else Promise.reject new Error "TODO: implement vm region fetching"

+  getWhoParams: (kiteName, correlationName) ->
+    if kiteName in ['oskite', 'terminal']
+      return vmName: correlationName
+    { correlationName }
+
   getKite: (options = {}) ->
     { name, correlationName, region } = options

@@ -56,7 +61,9 @@

     @fetchRegion(vmName, region).then (region) =>

-      @fetchKite { name, region }
+      @fetchKite
+        query : { name, region }
+        who   : @getWhoParams name, correlationName

     .then kite.bound 'setTransport'


activity test: all components tests are not required anymore

--- client/activity/lib/test/index.coffee
+++ client/activity/lib/test/index.coffee
@@ -1,2 +1 @@
 require '../flux/test'
-require '../components/test'


fix tester

fix tester

--- src/tester.coffee
+++ src/tester.coffee
@@ -6,8 +6,7 @@

 getTemplate = (formula_src) ->
   formula = parser.parse formula_src
-  tree = evaluator.build formula
-  lets = tree.unbound()
+  lets = evaluator.unbound formula
   lets.push 'expected'
   lets.push 'message'
   lets.join ','


Fixed Paths

Fixed Paths

--- gulp/development/index.coffee
+++ gulp/development/index.coffee
@@ -1,14 +1,14 @@
 gulp      = require 'gulp'
 sequence  = require 'gulp-run-sequence'

-require('./gulp/development/clean') gulp
-require('./gulp/development/coffee') gulp
-require('./gulp/development/jade') gulp
-require('./gulp/development/scss') gulp
-require('./gulp/development/vendor') gulp
-require('./gulp/development/watch') gulp
-require('./gulp/development/webserver') gulp
-require('./gulp/development/specs') gulp
+require('./clean') gulp
+require('./coffee') gulp
+require('./jade') gulp
+require('./scss') gulp
+require('./vendor') gulp
+require('./watch') gulp
+require('./webserver') gulp
+require('./specs') gulp

 module.exports = (gulp) ->


More CS handling of @

More CS handling of @

--- coffee/bootstrap-text-dropdown.coffee
+++ coffee/bootstrap-text-dropdown.coffee
@@ -18,8 +18,8 @@
     _init: () ->
       self = @
       @.$element.on {
-        'click.textdropdown': $.proxy(this.showWidget, this)
-        'blur.textdropdown': $.proxy(this.blurElement, this)
+        'click.textdropdown': $.proxy(@showWidget, @)
+        'blur.textdropdown': $.proxy(@blurElement, @)
       }

       @.$widget = $(@getTemplate()).on('click', $.proxy(@widgetClick, @))
@@ -84,7 +84,7 @@
       @.$widget.removeClass('textdropdown-orient-top textdropdown-orient-bottom textdropdown-orient-right textdropdown-orient-left')

       if @orientation.x != 'auto'
-        @picker.addClass('textdropdown-orient-' + this.orientation.x)
+        @picker.addClass('textdropdown-orient-' + @orientation.x)
         if @orientation.x == 'right'
           left -= widgetWidth - width
       else
@@ -110,7 +110,7 @@
       if yorient == 'top'
         top += height
       else
-        top -= widgetHeight + parseInt(this.$widget.css('padding-top'), 10)
+        top -= widgetHeight + parseInt(@.$widget.css('padding-top'), 10)

       @.$widget.css {
         top : top
@@ -138,7 +138,7 @@
       return if @.$element.is(':disabled')

-      @.$widget.appendTo(this.container)
+      @.$widget.appendTo(@container)
       self = @;
       $(document).on 'mousedown.textdropdown, touchend.textdropdown', (e) ->
         self.hideWidget() if !(self.$element.parent().find(e.target).length || self.$widget.is(e.target) || self.$widget.find(e.target).length)
@@ -159,7 +159,7 @@

     update: (ignoreWidget) ->
       @updateElement()
-      this.updateWidget() if !ignoreWidget
+      @updateWidget() if !ignoreWidget

     updateElement: () ->
       @.$element.val(@getText()).change()
@@ -203,7 +203,7 @@
       options = typeof option == 'object' && option

       if !data
-        data = new Textdropdown(this, $.extend({}, $.fn.textdropdown.defaults, options, $(this).data()))
+        data = new Textdropdown(@, $.extend({}, $.fn.textdropdown.defaults, options, $(@).data()))
         $this.data('textdropdown', data)

       if typeof option == 'string'


fix: adapt poincare-drawing code from Pentagrid app

--- coffee/hyperbolic_tessellation.coffee
+++ coffee/hyperbolic_tessellation.coffee
@@ -1,5 +1,7 @@
 {CenteredVonDyck} = require "./triangle_group_representation.coffee"
 M = require "./matrix3.coffee"
+
+len2 = (x,y) -> x*x + y*y

 exports.Tessellation = class Tessellation
   constructor: (n,m) ->
@@ -45,36 +47,66 @@
         context.moveTo xx0, yy0

       #@_drawCircularArc context, xx0, yy0, xx1, yy1, xc, yc
-      context.lineTo xx1, yy1
+      #context.lineTo xx1, yy1
+      @drawPoincareCircleTo context, xx0, yy0, xx1, yy1
+
       #else
       #  context.lineTo xx, yy
     context.closePath()

-  _drawCircularArc1: (context, x1, y1, x4, y4, xc, yc)->
-    a1 = Math.atan2(y1-yc, x1-xc)
-    a2 = Math.atan2(y4-yc, x4-xc)
-    r = Math.sqrt((y1-yc)**2 + (x1-xc)**2)
-    context.arc xc, yc, r, a1, a2
+  drawPoincareCircleTo: (context, x0, y0, x1, y1) ->
+    #Calculate radius of the circular arc.
+    sq_l0 = len2( x0, y0 )
+    sq_l1 = len2( x1, y1 )

-  _drawCircularArc: (context, x1, y1, x4, y4, xc, yc)->
-    #http://itc.ktu.lt/itc354/Riskus354.pdf
-    ax = x1 - xc
-    ay = y1 - yc
-    bx = x4 - xc
-    by_ = y4 - yc
-    q1 = ax**2 + ay ** 2
-    q2 = q1 + ax*bx + ay*by_
-    k2 = 4.0/3.0*(Math.sqrt(2*q1*q2)-q2) / (ax*by_ - ay*bx)
-
-    x2 = xc+x1 - k2*y1
-    y2 = yc+y1 + k2*x1
-
-    x3 = xc+x4 - k2*y4
-    y3 = yc + y4 + k2*x4
-
-    context.bezierCurveTo( x2, y2, x3, y3, x4, y4 )
+    k0 = (1+1/sq_l0) * 0.5
+    k1 = (1+1/sq_l1) * 0.5

-
+    delta2 = len2( x0*k0 - x1*k1, y0*k0 - y1*k1 )
+
+    if delta2 < 1e-4 # 0.01^2 lenght of a path too small, create straight line instead to make it faster.
+      context.lineTo( x1, y1 )
+      return
+
+    cross = (x0*y1 - x1*y0)
+    r2 = ( sq_l0 * sq_l1 * delta2 ) / (cross*cross) - 1
+
+    r = - Math.sqrt( r2 )
+    if cross < 0
+      r = -r
+
+    if Math.abs(r) < 100
+      @drawBezierApproxArcTo( context, x0, y0, x1, y1, r, r<0 )
+    else
+      context.lineTo x1, y1
+
+  drawBezierApproxArcTo: (context, x0, y0, x1, y1, r, reverse) ->
+      d2 = (x0-x1)**2 +(y0-y1)**2
+      d  = Math.sqrt( d2 )
+
+      ct = Math.sqrt( 4*r*r - d2)*0.5
+      if reverse
+        ct = -ct
+
+      #Main formulas for calculating bezier points. Math was used to get them.
+      kx = 4.0/3.0*(r-ct)/d
+      ky = 8.0/3.0*r*(ct - r)/d2 + 1.0/6.0
+
+      #Get the bezier control point positions
+      #vx is a perpendicular vector, vy is parallel
+      vy_x = x1-x0
+      vy_y = y1-y0
+      vx_x = vy_y
+      vx_y = -vy_x # #rotated by Pi/2
+
+      p11x = (x0+x1)*0.5 + vx_x * kx + vy_x * ky
+      p11y = (y0+y1)*0.5 + vx_y * kx + vy_y * ky
+
+      p12x = (x0+x1)*0.5 + vx_x * kx - vy_x * ky
+      p12y = (y0+y1)*0.5 + vx_y * kx - vy_y * ky
+
+      context.bezierCurveTo p11x, p11y, p12x, p12y, x1, y1
+
   makeCellShape: (cellTransformMatrix, context) ->
     #each cell is n-gon.


got trello working

got trello working


Remove a debugger

Remove a debugger

--- admin/static/coffee/namespaces/index.coffee
+++ admin/static/coffee/namespaces/index.coffee
@@ -238,7 +238,6 @@
                     error: @on_error

         on_success: (response) =>
-            debugger
             super

             # the result of this operation are some


made sliders obey the value attribute

made sliders obey the value attribute

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -32,6 +32,9 @@
 #   elem - the input element to turn into a slider
 setupSlider = (elem) ->
   slider = $(elem)
+  oldVal = parseInt(slider.val())
+  if not oldVal
+    oldVal = 0

   valElem = $(slider.attr("value-holder"))
   updateSlider = (ev) ->
@@ -52,10 +55,10 @@
       min: 0,
       max: 100,
       tooltip: "hide",
-      value: 0
+      value: oldVal
     })
   else
-    slider.slider({value: 0})
+    slider.slider({value: oldVal})

 # set .tl-slider class to be sliders
 setupSliders = () ->


fix add file button

fix add file button

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -248,7 +248,7 @@

     # If there is a specific add_example use that
     add_example_attr = $(this).attr("data-add-example")
-    if ((typeof(attr) != typeof(undefined)) && add_example_attr != false)
+    if ((typeof(add_example_attr) != typeof(undefined)) && add_example_attr != false)
       add_example = question.find(".js-add-example[data-add-example=#{add_example_attr}]").html()

     if question.find(".list-add").size() > 0


modal_template，用于自行设置modal模版

--- app/assets/javascripts/bucketerize.js.coffee
+++ app/assets/javascripts/bucketerize.js.coffee
@@ -98,8 +98,10 @@
     "#{name}更新时间"

   append_modal: ->
-    console.log 'append_modal'
-    jQuery('body').append """
+    jQuery('body').append @modal_template()
+
+  modal_template: ->
+    """


d3 merit order chart: legend tweak

d3 merit order chart: legend tweak

--- app/assets/javascripts/d3/merit_order.coffee
+++ app/assets/javascripts/d3/merit_order.coffee
@@ -141,7 +141,7 @@
         attr("x", (d, i) -> 115 * (Math.floor(i / 4)) + 10).
         attr("y", (d, i) -> 18 * (i % 4)).
         attr("height", 30).
-        attr("width", 90)
+        attr("width", 110)

       legends.append("svg:rect").
         attr("width", 10).


fix #125 (hash-anchors within pagination)

--- app/assets/javascripts/main/paginator.coffee
+++ app/assets/javascripts/main/paginator.coffee
@@ -21,6 +21,16 @@

     searchboxElement = @pageSelectionDiv.find(".pagination-searchbox")
     @addSearchboxListener(searchboxElement)
+    @addHashListener(searchboxElement)
+
+
+  addHashListener : (searchboxElement) ->
+
+    if hash = window.location.hash.slice(1)
+      searchboxElement.val(hash).trigger("keyup")
+
+    window.onhashchange = ->
+      searchboxElement.val(window.location.hash.slice(1)).trigger("keyup")

   extractTemplate : ->
@@ -42,7 +52,9 @@
       dataType : "json"
       type : "get"

-    $.ajax(ajaxOptions).then(
+    @dataRetrievalPromise = $.ajax(ajaxOptions)
+
+    @dataRetrievalPromise.then(

       (responseData) =>

@@ -95,27 +107,29 @@
     currentQuery = null

     searchboxElement.keyup (event) =>
-      newQuery = $(event.currentTarget).val().toLowerCase()
+      @dataRetrievalPromise.done( =>
+        newQuery = $(event.currentTarget).val().toLowerCase()

-      if newQuery == currentQuery
-        return
+        if newQuery == currentQuery
+          return

-      currentQuery = currentQuery
-      lastQuery = currentQuery
-      currentQuery = newQuery
+        currentQuery = currentQuery
+        lastQuery = currentQuery
+        currentQuery = newQuery

-      if currentQuery.length > 0
-        @elementsToShow = []
+        if currentQuery.length > 0
+          @elementsToShow = []

-        i = 0
-        for task in @allElements
-          if @JSONcontains task, currentQuery
-            @elementsToShow.push task
-      else
-        @elementsToShow = @allElements
-
-      @updatePageCount()
-      @displayJSON(@getElementsForPage(0))
+          i = 0
+          for task in @allElements
+            if @JSONcontains task, currentQuery
+              @elementsToShow.push task
+        else
+          @elementsToShow = @allElements
+
+        @updatePageCount()
+        @displayJSON(@getElementsForPage(0))
+      )

   displayJSON : (jsonArray) ->


Better browser compat for repeating scrollTo

--- app/assets/javascripts/site_search.js.coffee
+++ app/assets/javascripts/site_search.js.coffee
@@ -64,12 +64,17 @@
           _gaq.push(['_trackEvent', 'SearchClickCategory', GAAction,  GALabel])

     # Override user agents and scroll to search box on narrow devices
-    if $(document).width() <= 4568
+    if $(document).width() <= 568
       newY = 75
       $("body").css("min-height", $(document).height() + newY)
       $searchField.focus ->
         $searchField.css("font-size", "16px") # hack to prevent iOS from zooming
-        window.scrollTo(0, newY - 6)
+        times = 0
+        i = setInterval ->
+          window.scrollTo(0, newY - 6)
+          if times++ > 10
+            clearInterval(i)
+        , 10

     else # set focus on field if not a narrow device and if no search field is empty
       $searchField.focus() unless $searchField.val().length


Disallow a 2nd binding of a bindable.

Disallow a 2nd binding of a bindable.

--- app/assets/utensils/bindable/bindable.coffee
+++ app/assets/utensils/bindable/bindable.coffee
@@ -9,6 +9,7 @@

   bindAll: ->
     @bind(el) for el in @bindables
+    @

   getRefs: ->
@@ -30,7 +31,7 @@
     el = $(el)
     key = el.data(dataKey)
     if _class = @constructor.getClass(key)
-      el.data( @instanceKey, new _class(el) )
+      el.data( @instanceKey, new _class(el) ) unless el.data(@instanceKey)
     else
       console?.error "Bindable for key: #{key} not found in Bindable.registry for instance", el

@@ -44,3 +45,4 @@
     @registry[key] = { class: klass }
     return null

+


Avoid runnning more than one phantomjs

Avoid runnning more than one phantomjs

--- src/TestRunner.coffee
+++ src/TestRunner.coffee
@@ -26,6 +26,7 @@
     packages:null,
     meteor_ready_text: "=> Meteor server running on:",
     meteor_error_text: "=> Your application has errors. Waiting for file change."
+    meteor_crashing_text: "=> Your application is crashing. Waiting for file change."
     })
     log.setLevel(@rc.log_level)

@@ -47,12 +48,11 @@
     @meteor = new Meteor(@rc)
     @meteor.on "ready", =>
       log.info "Meteor is ready"
-      @runPhantom()
+      @runPhantom() if not @phantomjs

     @meteor.on "error", =>
       log.error "Meteor has errors, exiting"
-      @meteor.childProcess.child.kill()
-      process.exit TestRunner.ERR_CODE.METEOR_ERROR
+      @killAllChilds TestRunner.ERR_CODE.METEOR_ERROR

     @meteor.run()


Add empty Metadata object

Add empty Metadata object

--- framer/Framer.coffee
+++ framer/Framer.coffee
@@ -51,6 +51,8 @@
 Framer.Version = require "../build/Version"
 Framer.Loop = new Framer.AnimationLoop()

+Framer.Metadata = {}
+
 window.Framer = Framer if window

 # Set the defaults


Reducing use of msg.send

Reducing use of msg.send

--- src/twitch.coffee
+++ src/twitch.coffee
@@ -66,9 +66,11 @@

     robot.respond /ttv featured/i, (msg) ->
         GetTwitchResult msg, '/streams/featured', null, (object) ->
+            response = ""
             for feature in object.featured
                 channel = feature.stream.channel
-                msg.send "#{feature.stream.game}: #{channel.display_name} (#{channel.status}) - #{channel.url} [Viewers: #{feature.stream.viewers}]"
+                response += "#{feature.stream.game}: #{channel.display_name} (#{channel.status}) - #{channel.url} [Viewers: #{feature.stream.viewers}]\n"
+            msg.send response

     robot.respond /ttv game (.+)/i, (msg) ->
         category = msg.match[1]
@@ -77,9 +79,11 @@
                 msg.reply "No live streams were found in \"#{category}\". Try a different category or try again later."
                 return

+            response = ""
             for stream in object.streams
                 channel = stream.channel
-                msg.send "#{channel.display_name} (\"#{channel.status}\"): #{channel.url} [Viewers: #{stream.viewers}]"
+                response += "#{channel.display_name} (\"#{channel.status}\"): #{channel.url} [Viewers: #{stream.viewers}]\n"
+            msg.send response

             if object._total > TWITCH_MAX_RESULTS
                 msg.reply "There are #{object._total - TWITCH_MAX_RESULTS} other \"#{category}\" live streams."
@@ -91,9 +95,11 @@
                 msg.reply "No live streams were found using search query: \"#{query}\". Try a different query or try again later."
                 return

+            response = ""
             for stream in object.streams
                 channel = stream.channel
-                msg.send "#{channel.display_name} (\"#{channel.status}\"): #{channel.url} [Viewers: #{stream.viewers}]"
+                response += "#{channel.display_name} (\"#{channel.status}\"): #{channel.url} [Viewers: #{stream.viewers}]\n"
+            msg.send response

             if object._total > TWITCH_MAX_RESULTS
                 msg.reply "There are #{object._total - TWITCH_MAX_RESULTS} other live streams matching your search query: \"#{query}\"."
@@ -109,17 +115,20 @@
                 return

             channel = object.stream.channel
-            msg.send "#{channel.display_name} is streaming #{channel.game} @ #{channel.url}"
-            msg.send "Stream status: \"#{channel.status}\""
-            msg.send "Viewers: #{object.stream.viewers}"
+            response = "#{channel.display_name} is streaming #{channel.game} @ #{channel.url}"
+            response += "Stream status: \"#{channel.status}\""
+            response += "Viewers: #{object.stream.viewers}"
+            msg.send response

     robot.respond /ttv top/i, (msg) ->
         createGameURL = (game) ->
             "https://www.twitch.tv/directory/game/#{encodeURIComponent(game)}"

         GetTwitchResult msg, "/games/top", null, (object) ->
+            response = ""
             for gameObj, i in object.top
-                msg.send "#{i + 1}. #{gameObj.game.name} | Viewers: #{gameObj.viewers} | Channels: #{gameObj.channels} | #{createGameURL(gameObj.game.name)}"
+                response += "#{i + 1}. #{gameObj.game.name} | Viewers: #{gameObj.viewers} | Channels: #{gameObj.channels} | #{createGameURL(gameObj.game.name)}\n"
+            msg.send response

 GetTwitchResult = (msg, api, params = {}, handler) ->
     params.client_id = TWITCH_API_KEY


EnvironmentsListItem: fix emit logic here

--- client/app/lib/environment/environmentlistitem.coffee
+++ client/app/lib/environment/environmentlistitem.coffee
@@ -43,9 +43,9 @@
       @reinitButton = new kd.ButtonView
         cssClass    : 'solid compact red'
         title       : 'RE-INIT STACK'
-        callback    : ->
-          ComputeController_UI.askFor 'reinitStack', {}, ->
-            delegate.emit 'StackReinitRequested', stack
+        callback    : =>
+          ComputeController_UI.askFor 'reinitStack', {}, =>
+            @getDelegate().emit 'StackReinitRequested', @getData()

     if isKoding()
       @addVMButton = new kd.ButtonView


Fixed adminUser login

Fixed adminUser login

--- server/informatics/InformaticsUser.coffee
+++ server/informatics/InformaticsUser.coffee
@@ -44,9 +44,9 @@

     @_findAdmin: () ->
         admin = await RegisteredUser.findAdmin()
-        adminUser = new InformaticsUser(admin.informaticsUsername, admin.informaticsPassword)
-        await adminUser.doLogin()
-        return adminUser
+        _adminUser = new InformaticsUser(admin.informaticsUsername, admin.informaticsPassword)
+        await _adminUser.doLogin()
+        return _adminUser

     @findAdmin: () ->
         if not adminUser or (new Date() - adminUser.loginTime > 1000 * 60 * 60)


add implementation of https://forums.zotero.org/discussion/3673/2/original-date-of-publication/ for #237

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -230,8 +230,6 @@
               fields.push({ name: 'eprint', value: m[2] })
             else
               fields.push({ name: 'googlebooks', value: m[2] })
-          when ':original-date'
-            fields.push({ name: 'origdate', value: data[2] })

           else extra.push(line)
       @item.extra = extra.join("\n")
@@ -263,6 +261,14 @@
           for name, value of json
             fields.push({name: name, value: value})

+      # fetch fields as per https://forums.zotero.org/discussion/3673/2/original-date-of-publication/
+      @item.extra = @item.extra.replace(/{:([^:]+:\s*([^}]+)}/g, (m, field, value) ->
+        switch field.toLowerCase()
+          when 'original-date'
+            fields.push({ name: 'origdate', value: value })
+        return ''
+      )
+
       for field in fields
         if field.name == 'referencetype'
           @itemtype = field.value


see what happens if I just match word boundaries

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -370,8 +370,8 @@
   return ((part.replace(/([\\{}:;])/g, "\\$1") for part in [att.title, att.path, att.mimetype]).join(':') for att in attachments).join(';')

 Reference::preserveCaps = {
-  inner: new XRegExp("\\b[\\p{Letter}\\p{Number}]+\\p{Uppercase_Letter}[\\p{Letter}\\p{Number}]*", 'g')
-  all: new XRegExp("\\b[\\p{Letter}\\p{Number}]*\\p{Uppercase_Letter}[\\p{Letter}\\p{Number}]*", 'g')
+  inner: new XRegExp("\\b.+\\p{Uppercase_Letter}.*\\b", 'g')
+  all: new XRegExp("\\b.*\\p{Uppercase_Letter}.*\\b", 'g')
 }
 Reference::initialCapOnly = new XRegExp("^\\p{Uppercase_Letter}\\p{Lowercase_Letter}+$")


--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -370,8 +370,8 @@
   return ((part.replace(/([\\{}:;])/g, "\\$1") for part in [att.title, att.path, att.mimetype]).join(':') for att in attachments).join(';')

 Reference::preserveCaps = {
-  inner: new XRegExp("\\b.+\\p{Uppercase_Letter}.*\\b", 'g')
-  all: new XRegExp("\\b.*\\p{Uppercase_Letter}.*\\b", 'g')
+  inner: new XRegExp("\\b\\B+\\p{Uppercase_Letter}\\B*\\b", 'g')
+  all: new XRegExp("\\b\\B*\\p{Uppercase_Letter}\\B*\\b", 'g')
 }
 Reference::initialCapOnly = new XRegExp("^\\p{Uppercase_Letter}\\p{Lowercase_Letter}+$")


Added geo data.

Added geo data.

--- app/assets/javascripts/app/models/user.js.coffee
+++ app/assets/javascripts/app/models/user.js.coffee
@@ -11,6 +11,9 @@
     { name: 'phone',            display: 'Phone',         tag: 'input',    type: 'phone',    limit: 100, null: true,  class: 'xlarge', signup: false, quick: true, info: true },
     { name: 'mobile',           display: 'Mobile',        tag: 'input',    type: 'phone',    limit: 100, null: true,  class: 'xlarge', signup: false, quick: true, info: true },
     { name: 'fax',              display: 'Fax',           tag: 'input',    type: 'phone',    limit: 100, null: true,  class: 'xlarge', signup: false, quick: true, info: true },
+    { name: 'street',           display: 'Street',        tag: 'input',    type: 'text',    limit: 100, null: true,  class: 'xlarge', signup: false, quick: true, info: true },
+    { name: 'zip',              display: 'Zip',           tag: 'input',    type: 'text',    limit: 100, null: true,  class: 'xlarge', signup: false, quick: true, info: true },
+    { name: 'city',             display: 'City',          tag: 'input',    type: 'text',    limit: 100, null: true,  class: 'xlarge', signup: false, quick: true, info: true },
     { name: 'password',         display: 'Password',      tag: 'input',    type: 'password', limit: 50,  null: true, class:  'xlarge', signup: true,  quick: false, },
     { name: 'organization_id',  display: 'Organization',  tag: 'select',   multiple: false, nulloption: true, null: true, relation: 'Organization', class: 'xlarge' },
     { name: 'note',             display: 'Note',          tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, class: 'xlarge', quick: true, info: true },


Port to Process API

Port to Process API

--- components/ContextToRuntime.coffee
+++ components/ContextToRuntime.coffee
@@ -1,21 +1,18 @@
 noflo = require 'noflo'
 _ = require 'underscore'

-sendContext = (context, out) ->
+sendContext = (context, callback) ->
   if context.project
     sendProject context.project, context.runtime
-    out.send context
-    out.disconnect()
+    do callback
     return

   if context.graphs?.length
     sendGraph null, graph, context.runtime for graph in context.graphs
-    out.send context
-    out.disconnect()
+    do callback
     return

-  out.send context
-  out.disconnect()
+  do callback

 sendProject = (project, runtime) ->
   namespace = project.namespace or project.id
@@ -95,36 +92,62 @@
         port: priv.port
         graph: graphId

-currentContext = null
-
 exports.getComponent = ->
   c = new noflo.Component
   c.inPorts.add 'context',
     datatype: 'object'
-    process: (event, payload) ->
-      return unless event is 'data'
-      return unless payload.runtime
-      if currentContext?.runtime and currentContext.graphs[0] isnt payload.graphs[0] and sender
-        currentContext.runtime.removeListener 'capabilities', sender
-        if currentContext.runtime is payload.runtime
-          # Same runtime, different graph. Reconnect to clear caches
-          currentContext.runtime.reconnect()
-        else
-          # Different runtime, different graph. Disconnect old runtime connection
-          currentContext.runtime.disconnect()
-
-      send = _.debounce sendContext, 300, true
-
-      # Prepare to send data
-      currentContext = payload
-      send payload, c.outPorts.context if payload.runtime.isConnected()
-      sender = ->
-        return unless currentContext is payload
-        send payload, c.outPorts.context
-
-      payload.runtime.on 'capabilities', sender
-
   c.outPorts.add 'context',
     datatype: 'object'

-  c
+  c.current = null
+
+  unsubscribe = ->
+    return unless c.current
+    c.current.runtime.removeListener 'capabilities', c.current.sender
+    c.current.ctx.deactivate()
+    c.current = null
+
+  c.tearDown = (callback) ->
+    do unsubscribe
+    do callback
+
+  c.process (input, output, context) ->
+    return unless input.hasData 'context'
+    payload = input.getData 'context'
+    unless payload.runtime
+      # No runtime associated with the context, unsubscribe previous
+      unsubscribe()
+      output.done()
+      return
+    if c.current?.runtime and c.current?.payload?.graphs[0] isnt payload.graphs[0]
+      # We have an existing runtime connection, with different main graph
+      if c.current.runtime is payload.runtime
+        # Same runtime, different graph. Reconnect to clear caches
+        c.current.runtime.removeListener 'capabilities', c.current.sender
+        c.current.runtime.reconnect()
+      else
+        # Different runtime, different graph. Disconnect old runtime connection
+        c.current.runtime.disconnect()
+        do unsubscribe
+
+    # Prepare to send data
+    send = _.debounce sendContext, 300, true
+    c.current =
+      runtime: payload.runtime
+      payload: payload
+      ctx: context
+      sender: ->
+        return unless c.current.payload is payload
+        send payload, ->
+          output.send
+            context: payload
+
+    if payload.runtime.isConnected()
+      # We're already connected, send context to runtime right away
+      send payload, ->
+        output.send
+          context: payload
+
+    # If runtime reconnects, we need to re-send the context in case
+    # runtime doesn't have persistent state
+    payload.runtime.on 'capabilities', c.current.sender


Move render methods together

Move render methods together

--- client/lanes/components/select-field/SelectField.cjsx
+++ client/lanes/components/select-field/SelectField.cjsx
@@ -60,13 +60,6 @@
             else
                 {id: null, label: ''}

-    renderDisplayValue: ->
-        value = if @props.multi
-            _.toSentence _.pluck(@getCurrentSelection(), @props.labelField)
-        else
-            @getCurrentSelection()?.label
-        {value}
-
     _getChoices: ->
         selection = @getCurrentSelection()
         if @state.isOpen
@@ -82,6 +75,13 @@
         @collection.fetch(
             _.extend(limit: @props.displayLimit, Lanes.u.invokeOrReturn(@props.syncOptions))
         ) if isOpen and @props.fetchWhenOpen
+
+    renderDisplayValue: ->
+        value = if @props.multi
+            _.toSentence _.pluck(@getCurrentSelection(), @props.labelField)
+        else
+            @getCurrentSelection()?.label
+        {value}

     renderEdit: (label) ->
         Component = if @props.multi


Use the registry endpoint from config, not environment

--- src/application.coffee
+++ src/application.coffee
@@ -203,7 +203,7 @@
 				return {
 					appId: '' + app.id
 					commit: app.commit
-					imageId: "#{process.env.REGISTRY_ENDPOINT}/#{path.basename(app.git_repository, '.git')}/#{app.commit}"
+					imageId: "#{config.registryEndpoint}/#{path.basename(app.git_repository, '.git')}/#{app.commit}"
 					env: JSON.stringify(env) # The env has to be stored as a JSON string for knex
 				}


do things the js way not the ruby way

do things the js way not the ruby way

--- app/assets/javascripts/me-add-to-playlist.coffee
+++ app/assets/javascripts/me-add-to-playlist.coffee
@@ -36,12 +36,12 @@
               end_time: $('#playlist_item_end').val()
           success: (response) ->
             $('#add_to_playlist_alert')[0].className = 'alert alert-success'
-            $('#add_to_playlist_result_message')[0].value = response[:message]
+            $('#add_to_playlist_result_message')[0].value = response['message']
             $('#add_to_playlist_alert').show(300)
             $('#add_to_playlist').hide(500)
           error: (response) ->
             $('#add_to_playlist_alert')[0].className = 'alert alert-danger'
-            $('#add_to_playlist_result_message')[0].value = response[:message]
+            $('#add_to_playlist_result_message')[0].value = response['message']
             $('#add_to_playlist_alert').show(300)

       $('.add_playlist_item_cancel').click (event) ->


add footer links

add footer links

--- app/assets/javascripts/partials/yale-footer.cjsx
+++ app/assets/javascripts/partials/yale-footer.cjsx
@@ -16,7 +16,7 @@

-                A collaboration of the Yale Digital Humanities Laboratory & Robert B. Haas Family Arts Library
+                A collaboration of the Yale Digital Humanities Laboratory & Robert B. Haas Family Arts Library


added checkpointing and logging to pack script

--- pack.coffee
+++ pack.coffee
@@ -1,19 +1,27 @@
-mongojs = require "mongojs"
-async = require "async"
-db = mongojs.connect("localhost/sharelatex", ["docHistory"])
+Settings = require "settings-sharelatex"
+fs = require("fs")
+mongojs = require("mongojs")
+ObjectId = mongojs.ObjectId
+db = mongojs(Settings.mongo.url, ['docHistory'])
+async = require("async")
 BSON=db.bson.BSON
 util = require 'util'
 _ = require 'underscore'
+
+lineReader = require "line-reader"
+
 MAX_SIZE = 1024*1024
 MAX_COUNT = 1024
 MIN_COUNT = 100
 KEEP_OPS = 100

 insertPack = (packObj, callback) ->
+	if shutdownRequested
+		return callback('shutdown')
 	bulk = db.docHistory.initializeOrderedBulkOp();
 	expect_nInserted = 1
 	expect_nRemoved = packObj.pack.length
-	console.log 'inserting', expect_nInserted, 'pack, will remove', expect_nRemoved, 'ops'
+	util.log "insert #{expect_nInserted} pack, remove #{expect_nRemoved} ops"
 	bulk.insert packObj
 	packObj.pack.forEach (op) ->
 		bulk.find({_id:op._id}).removeOne()
@@ -24,7 +32,7 @@
 		callback(err, result)

 packDocHistory = (doc_id, callback) ->
-	console.log 'packing doc_id', doc_id
+	util.log "starting pack operation for #{doc_id}"
 	db.docHistory.find({doc_id:mongojs.ObjectId(doc_id),pack:{$exists:false}}).sort {v:1}, (err, docs) ->
 		packs = []
 		top = null
@@ -50,21 +58,56 @@
 				packs.push top
 			else
 				# keep the op
-				console.log 'keeping large op unchanged'
+				util.log 'keeping large op unchanged (#{sz} bytes)'

 		# only store packs with a sufficient number of ops, discard others
 		packs = packs.filter (packObj) ->
 			packObj.pack.length > MIN_COUNT

-		console.log 'docs', origDocs, 'packs', packs.length
-		async.each packs, insertPack, (err, result) ->
+		util.log "docs #{origDocs} packs #{packs.length}"
+		if packs.length
+			async.each packs, insertPack, (err, result) ->
+				if err?
+					console.log doc_id, err
+				else
+					util.log "done writing packs"
+				callback err, result
+		else
+			util.log "no packs to write"
+			callback null, null
+
+readFile = (file, callback) ->
+	ids = []
+	lineReader.eachLine file, (line) ->
+		result = line.match(/[0-9a-f]{24}/)
+		if result?
+			ids.push result[0]
+	.then () ->
+		callback(null, ids)
+
+todoFile = process.argv[2]
+doneFile = process.argv[3]
+fs.appendFileSync doneFile, '# starting pack run at ' + new Date() + '\n'
+
+shutdownRequested = false
+process.on  'SIGINT', () ->
+	util.log "Gracefully shutting down from SIGINT"
+	shutdownRequested = true
+
+readFile todoFile, (err, todo) ->
+	readFile doneFile, (err, done) ->
+		pending = _.difference todo, done
+		async.eachSeries pending,	(doc_id, callback) ->
+			packDocHistory doc_id, (err, result) ->
+				if err?
+					return callback(err)
+				else
+					fs.appendFileSync doneFile, doc_id + '\n'
+				if shutdownRequested
+					return callback('shutdown')
+				callback(err, result)
+		, (err, results) ->
 			if err?
-				console.log 'err', err
-			console.log 'done writing packs'
-			callback err, result
-
-async.each process.argv.slice(2),	(doc_id, callback) ->
-		packDocHistory(doc_id, callback)
-	, (err, results) ->
-		console.log 'closing db'
-		db.close()
+				console.log 'error:', err
+			util.log 'closing db'
+			db.close()


use docHistoryStats to drive the packing script

--- pack.coffee
+++ pack.coffee
@@ -10,6 +10,7 @@
 lineReader = require "line-reader"
 cli = require "cli"
 options = cli.parse {
+	'update': ['u', 'find documents to pack from database']
 	'dry-run':   ['n', 'do not write to database'],
 	'fast':  [false, 'no delays on writes']
 }
@@ -21,10 +22,16 @@
 	util.log "starting pack operation for #{doc_id}"
 	getDocHistory doc_id, (err, docs) ->
 		return callback(err) if err?
-		origDocs = docs.length
+		origDocs = 0
+		origPacks = 0
+		for d in docs
+			if d.pack? then	origPacks++	else origDocs++
 		PackManager.convertDocsToPacks docs, (err, packs) ->
 			return callback(err) if err?
-			util.log "docs #{origDocs} packs #{packs.length}"
+			total = 0
+			for p in packs
+				total = total + p.pack.length
+			util.log "docs #{origDocs} packs #{origPacks} => packs #{packs.length} of #{total} ops"
 			if packs.length
 				if options['dry-run']
 					util.log 'dry-run, skipping write packs'
@@ -76,34 +83,52 @@
 	.then () ->
 		callback(null, ids)

-todoFile = cli.args[1]
-doneFile = cli.args[2]
-util.log "reading from #{todoFile}"
-util.log "logging progress to #{doneFile}"
-fs.appendFileSync doneFile, '# starting pack run at ' + new Date() + '\n'
-
 shutdownRequested = false
 process.on  'SIGINT', () ->
 	util.log "Gracefully shutting down from SIGINT"
 	shutdownRequested = true

-readFile todoFile, (err, todo) ->
-	readFile doneFile, (err, done) ->
-		pending = _.difference todo, done
-		async.eachSeries pending,	(doc_id, callback) ->
-			packDocHistory doc_id, (err, result) ->
-				if err?
-					console.log "ERROR:", err, result
-					return callback(err)
-				else if not options['dry-run']
-					fs.appendFileSync doneFile, doc_id + '\n'
-				if shutdownRequested
-					return callback('shutdown')
-				setTimeout () ->
-					callback(err, result)
-				, DOCUMENT_PACK_DELAY
-		, (err, results) ->
+processUpdates = (pending) ->
+	async.eachSeries pending,	(doc_id, callback) ->
+		packDocHistory doc_id, (err, result) ->
 			if err?
-				console.log 'error:', err
-			util.log 'closing db'
+				console.log "ERROR:", err, result
+				return callback(err)
+			else if not options['dry-run'] && doneFile?
+				fs.appendFileSync doneFile, doc_id + '\n'
+			if shutdownRequested
+				return callback('shutdown')
+			setTimeout () ->
+				callback(err, result)
+			, DOCUMENT_PACK_DELAY
+	, (err, results) ->
+		if err?
+			console.log 'error:', err
+		util.log 'closing db'
+		db.close()
+
+if options['update']
+	util.log 'checking for updates'
+	db.docHistoryStats.find({
+		update_count: {$gt : PackManager.MIN_COUNT}
+	}).sort({
+		update_count:-1
+	}).limit 1000, (error, results) ->
+		if err?
+			utils.log 'error', error
 			db.close()
+			return
+		util.log "found #{results.length} documents to pack"
+		pending = _.pluck results, 'doc_id'
+		processUpdates pending
+else
+	todoFile = cli.args[1]
+	doneFile = cli.args[2]
+	util.log "reading from #{todoFile}"
+	util.log "logging progress to #{doneFile}"
+	fs.appendFileSync doneFile, '# starting pack run at ' + new Date() + '\n'
+
+	readFile todoFile, (err, todo) ->
+		readFile doneFile, (err, done) ->
+			pending = _.difference todo, done
+			processUpdates pending


Hide comment sharing for kennisland as it's too buggy

--- app/backbone/views/comments/react_comment.coffee
+++ app/backbone/views/comments/react_comment.coffee
@@ -54,9 +54,12 @@
               _i ['icon-edit comment-edit-icon']
         _a ["spec-sub-comments-link", href:"javascript:", onClick: @_toggleSubcomments],
           "(#{sub_comment_count}) Reply"
-        @_separator()
-        ReactSlidingShareButton
-          model: @model()
+
+        unless window.is_kennisland
+          _span [],
+            @_separator()
+            ReactSlidingShareButton
+              model: @model()
       if @_show_subcomments()
         ReactSubComments
           model: @model().sub_comments()


draggable nodes

draggable nodes

--- app/assets/javascripts/svg_nodes/map.coffee
+++ app/assets/javascripts/svg_nodes/map.coffee
@@ -8,6 +8,11 @@
     @w = params.w
     @h = params.h
     @svg = Snap(parent) || Snap(@w, @h)
+    @bbox = {
+      x: @svg.node.getBoundingClientRect().x
+      y: @svg.node.getBoundingClientRect().y
+    }
+

     @nodes = []
     @edges = []
@@ -17,7 +22,8 @@
         fill: 'white',
         stroke: 'black',
         strokeWidth: 1.5,
-        fillOpacity: 1
+        fillOpacity: 1,
+        draggable: true
       },
       edges: {
         stroke: 'black',
@@ -35,6 +41,7 @@
       node.label
     )
     @nodes.push(n)
+    n

   addEdge: (edge)->
     src = this.fetchNode(edge.from)
@@ -46,25 +53,32 @@
       des
     )
     @edges.push(e)
+    e

   fetchNode: (id)->
     @nodes.find (node)->
       node.id == id

+  clear: =>
+    @nodes.splice(0,@nodes.length)
+    @edges.splice(0,@edges.length)
+    @svg.clear()
+
 class SvgNodes.Responsible
   constructor: ->
-    @svg.click ->
-      for fn in @_clickHandlers
-        fn()
-    @svg.drag ->
-      for fn in @_dragHandlers
-        fn()
+  onClick: (fn, bubbling = true)=>
+    @_clickHandlers.push(fn)
+    self = this
+    @svg.click (e)->
+      fn() for fn in self._clickHandlers
+      e.stopPropagation() unless bubbling

-  onClick: (fn)->
-    @_clickHandlers.push(fn)
-
-  onDrag:  (fn)->
+  onDrag:  (fn, bubbling = true)=>
     @_dragHandlers.push(fn)
+    self = this
+    @svg.drag (e)->
+      fn() for fn in self._dragHandlers
+      e.stopPropagation() unless bubbling

 class SvgNodes.Node extends SvgNodes.Responsible
   constructor: (@map, @id, @x, @y, @r, @text, @style)->
@@ -73,8 +87,39 @@
     @svg = @map.svg.circle(@x, @y, @r)
     @label = new SvgNodes.Label(@map, @x + @r, @y + @r, @text)
     @svg.attr(@map.options.nodes)
-    this.onClick ->
-      alert "Fancy click"
+    this.setDraggable(@map) if @map.options.nodes.draggable
+
+  setDraggable: (map, draggable = true) =>
+    return unless draggable
+    node = this
+    this.svg.drag (dx, dy, x, y, e)=>
+      x = x - map.bbox.x
+      y = y - map.bbox.y
+      this.move(x,y)
+
+  move: (x, y)=>
+    @x = x
+    @y = y
+    this.attr
+      cx: @x
+      cy: @y
+    this._moveEdges()
+    this._moveLabel()
+
+  _moveEdges: =>
+    @map.edges.forEach (e)=>
+      if e.src.id == @id
+        e.svg.attr
+          x1: @x
+          y1: @y
+      if e.dest.id == @id
+        e.svg.attr
+          x2: @x
+          y2: @y
+  _moveLabel: =>
+    @label.svg.attr
+      x: @x + @r
+      y: @y + @r

   attr: (hash)->
     @svg.attr(hash)


[add] ベタ打ちしたURLを開いてページタイトルを取得する機能を追加

[add] ベタ打ちしたURLを開いてページタイトルを取得する機能を追加

とりあえずcasperjsを使って動くプログラムを書いた

--- src/w-cars.coffee
+++ src/w-cars.coffee
@@ -1 +1,16 @@
-console.log 'Hello, w-cars!!'
+'use strict'
+casper = require('casper').create {
+  verbose: true
+  logLevel: 'warning' # debug,info,warning,error(default)
+  viewportSize:
+    width: 2560
+    height: 1600
+}
+
+casper.start()
+url = 'http://loilo.tv/jp/product/ipad_edu_note'
+
+casper.thenOpen url, ->
+  @echo @getTitle()
+
+casper.run()


Minor code cleanup.

Minor code cleanup.

--- src/class_file.coffee
+++ src/class_file.coffee
@@ -51,9 +51,7 @@
   parse_descriptor: (raw_descriptor) ->
     raw_descriptor = raw_descriptor.split ''
     throw "Invalid descriptor #{raw_descriptor}" if raw_descriptor.shift() != '('
-    @param_types = # apparently making this a one-liner makes this undefined. CS bug?
-      while field = @parse_field_type raw_descriptor
-        field
+    @param_types = (field while @parse_field_type raw_descriptor)
     throw "Invalid descriptor #{raw_descriptor}" if raw_descriptor.shift() != ')'
     @num_args = @param_types.length
     @num_args++ unless @access_flags.static # nonstatic methods get 'this'


Fixing <a> click inside description

Fixing <a> click inside description

--- app/coffee/modules/common/components.coffee
+++ app/coffee/modules/common/components.coffee
@@ -520,8 +520,10 @@
             $el.off()

         $el.on "click", ".view-description", (event) ->
-            target = angular.element(event.currentTarget)
-            if target.not('a')
+            # We want to dettect the a inside the div so we use the target and
+            # not the currentTarget
+            target = angular.element(event.target)
+            if not target.is('a')
                 $el.find('div.edit-description').show()
                 $el.find('div.view-description').hide()
                 $el.find('textarea').focus()


Moar logging

Moar logging

--- app/scripts/Civilization/Game/Manager.coffee
+++ app/scripts/Civilization/Game/Manager.coffee
@@ -63,14 +63,17 @@
         LOGGER.log("#{score.owner.name} scored #{score.score}")
         ordered.push(score)

-
       ordered.sort (a, b) ->
         return -1 if a.score < b.score
         return 1 if a.score > b.score
         return 0
       .reverse()

-      @info.setText(Civilization.Language.GAME_FINISHED.format(ordered[0].owner.name, ordered[0].score))
+      LOGGER.log("Winner is #{ordered[0].owner.name} with #{ordered[0].score} points")
+      @info.setText(Civilization.Language.GAME_FINISHED.format(
+        ordered[0].owner.name,
+        ordered[0].score
+      ))
       @updateState()

   start: ->


remove SSL proxying for fetching config

remove SSL proxying for fetching config

--- src/coffee/app.coffee
+++ src/coffee/app.coffee
@@ -85,10 +85,6 @@

   fetchPodcastDataFromUrl: (url) ->
     request = new XMLHttpRequest()
-    sslPage = document.location.protocol == 'https:'
-    urlIsHttp = url.substr(0, 5) == 'http:'
-    if sslPage && urlIsHttp
-      url = "https://cdn.podigee.com/ssl-proxy/#{url}"
     request.open('GET', url, true)

     request.onload = () =>


use ngAnimate, and push tools up to a root level concern

--- src/coffee/app.coffee
+++ src/coffee/app.coffee
@@ -1,8 +1,9 @@
-deps = ['angular', './routes', 'angular-route', 'angular-ui', 'angular-ui-select2',
+deps = ['angular', './routes', 'angular-route', 'angular-ui', 'angular-animate', 'angular-ui-select2',
         './filters', './services', './directives', './controllers']

 modules = [
   'ngRoute',
+  'ngAnimate',
   'steps.controllers',
   'steps.services',
   'steps.filters',
@@ -39,5 +40,13 @@

   router Steps

+  Steps.run Array '$rootScope', '$http', (scope, http) ->
+    scope.startingPoints = []
+
+    http.get("/tools", {params: {capabilities: "initial"}})
+        .then ({data}) ->
+          console.log data
+          scope.startingPoints = data
+
   return Steps


Move Engine-y Bits to Their Own Module

Move Engine-y Bits to Their Own Module

--- src/coffee/qbn.coffee
+++ src/coffee/qbn.coffee
@@ -15,20 +15,21 @@
     instance[newName] = value
   return instance

-qbnApp.controller 'QBN', ($scope) ->
-  $scope.qualities = [
-    instantiate new Quality 'Punchiness', '', 7
-    instantiate new Quality 'Thinkyness', '', 2
-  ]
-  $scope.storylets = [
-    new Storylet 'The Thinker\'s Apprentice', 'You do some thinking.'
-    new Storylet 'Change Location', 'Yeah, but where to go?'
-    new Storylet 'A Mysterious Occurrence', 'So mysterious, you don\'t even know about it.'
-  ]
-  $scope.eventText = 'Event'
-  $scope.choices = [
-    new Choice 'Choice A', 'Requires Punchiness 7'
-    new Choice 'Choice B', 'Requires Thinkyness 5'
-  ]
-  $scope.currentStorylet = null;
-  return
+angular.module 'qbn.engine', ['qbn.quality', 'qbn.storylet']
+  .controller 'QBN', ($scope) ->
+    $scope.qualities = [
+      instantiate new Quality 'Punchiness', '', 7
+      instantiate new Quality 'Thinkyness', '', 2
+    ]
+    $scope.storylets = [
+      new Storylet 'The Thinker\'s Apprentice', 'You do some thinking.'
+      new Storylet 'Change Location', 'Yeah, but where to go?'
+      new Storylet 'A Mysterious Occurrence', 'So mysterious, you don\'t even know about it.'
+    ]
+    $scope.eventText = 'Event'
+    $scope.choices = [
+      new Choice 'Choice A', 'Requires Punchiness 7'
+      new Choice 'Choice B', 'Requires Thinkyness 5'
+    ]
+    $scope.currentStorylet = null;
+    return


Only print errors to stderr

Only print errors to stderr

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -13,7 +13,7 @@

 logger = new Logger
   streams: [
-    { level: 'warn', stream: process.stderr }
+    { level: 'error', stream: process.stderr }
     { level: argv.logLevel, type: 'rotating-file', period: '1d', count: 90, path: "#{argv.logDir}/sphere-stock-sync-#{argv.projectKey}.log" }
   ]

@@ -35,4 +35,4 @@
     logger.info msg
     process.exit 0
   logger.error msg
-  process.exit 1+  process.exit 1


Added sync to insert on collection

Added sync to insert on collection

--- src/collection.coffee
+++ src/collection.coffee
@@ -38,11 +38,12 @@

   insert: (definition, data, autosave=true) ->
     return false if @readonly
-    document = @at()
-    document.definition(@definition(definition))
-    .then => document.data(data)
-    .then -> document.save() if autosave
-    .then => @_documents[document.hdkey.index - @offset] = document
+    @sync().then =>
+      document = @at()
+      document.definition(@definition(definition))
+      .then => document.data(data)
+      .then -> document.save() if autosave
+      .then => @_documents[document.hdkey.index - @offset] = document

   at: (index=@_documents.length+@offset) ->
     document = @_documents[index-@offset]
@@ -57,6 +58,7 @@
       pluck = refresh
       refresh = false
     @sync(refresh).then =>
+      console.log 'all', @_documents.length
       if pluck then defer.map(@_documents, (d) -> d[pluck]?())
       else @_documents

@@ -68,6 +70,6 @@
       (document) => document.fetch().yield(false).else(true)
       (document) => @_documents[document.hdkey.index - @offset] = document
       @at(start)
-    )
+    ).then => @

 exports = module.exports = Collection


got rid of shit

got rid of shit

--- src/atom/window.coffee
+++ src/atom/window.coffee
@@ -4,8 +4,6 @@
 # This a weirdo file. We don't create a Window class, we just add stuff to
 # the DOM window.
 windowAdditions =
-  appRoot: OSX.NSBundle.mainBundle.resourcePath
-
   path: null

   startup: ->
@@ -26,15 +24,11 @@

   open: (path) ->
     path = atom.native.openPanel() unless path
-    if atom.document.open path
-      atom.trigger 'window:open', path
-    else
-      atom.app.open path
+    (atom.document.open path) or atom.app.open path

   close: (path) ->
     @shutdown()
     $atomController.close
-    atom.trigger 'window:close', path

   handleKeyEvent: ->
     atom.keybinder.handleEvent arguments...
@@ -42,9 +36,6 @@
   triggerEvent: ->
     atom.trigger arguments...

-  canOpen: (path) ->
-    false
-
 for key, value of windowAdditions
   console.warn "DOMWindow already has a key named `#{key}`" if window[key]
   window[key] = value


Bugfix: encoded /

Bugfix: encoded /

--- app/scripts/directives/wintersarticle.coffee
+++ app/scripts/directives/wintersarticle.coffee
@@ -22,7 +22,7 @@

-
+


use TutorLink

use TutorLink

--- tutor/src/components/course-listing/course.cjsx
+++ tutor/src/components/course-listing/course.cjsx
@@ -3,7 +3,7 @@
 BS = require 'react-bootstrap'
 _ = require 'lodash'

-{Redirect, Link} = require 'react-router'
+TutorLink = require '../link'

 Router = require '../../helpers/router'

@@ -68,11 +68,11 @@
       {controls}

-  CourseName: ({coursePath}) ->
+  CourseName: ->
     {course, courseSubject} = @props
     courseNameSegments = getCourseNameSegments(course, courseSubject)

-    <Link to={coursePath}>
+    <TutorLink to='dashboard' params={courseId: course.id}>
       {_.map(courseNameSegments, (courseNameSegment, index) ->
         <span
           key="course-name-segment-#{index}"
@@ -81,11 +81,10 @@
           {"#{courseNameSegment} "}

       )}
-    </Link>
+    </TutorLink>

   render: ->
     {course, courseDataProps, controls, courseIsTeacher, className} = @props
-    coursePath = Router.makePathname('dashboard', {courseId: course.id})

     itemClasses = classnames('course-listing-item', className)

@@ -98,16 +97,16 @@
       >

-          <@CourseName coursePath={coursePath}/>
+          <@CourseName />

-          <Link to={coursePath}>
+          <TutorLink to='dashboard' params={courseId: course.id}>
             <CourseBranding isConceptCoach={course.is_concept_coach or false} />

{course.term} {course.year}

-          </Link>
+          </TutorLink>
           {<@Controls /> if controls?}

@@ -119,14 +118,13 @@
     _.omit(Course.propTypes, 'controls')
   render: ->
     {course} = @props
-    query =
-      courseId: course.id
+    link =
+      <TutorLink
+        to='createNewCourse'
+        query={courseId: course.id}
+        className='btn btn-default btn-sm'
+      >Teach Again</TutorLink>
+    <Course {...@props} controls={link} />

-    controls = <BS.Button
-      bsSize='sm'
-      href={Router.makePathname('createNewCourse', {}, {query})}
-    >Teach Again</BS.Button>
-
-    <Course {...@props} controls={controls} />
-
+      # onClick={href={Router.makePathname('createNewCourse', {}, {query})}
 module.exports = {Course, CourseTeacher, CoursePropType}


Uncommented some headers from nl-NL.coffee

--- app/locale/nl-NL.coffee
+++ app/locale/nl-NL.coffee
@@ -83,7 +83,7 @@
 #    campaign_old_multiplayer: "(Deprecated) Old Multiplayer Arenas"
 #    campaign_old_multiplayer_description: "Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas."

-#  share_progress_modal:
+  share_progress_modal:
     blurb: "Je gaat snel vooruit! Vertel aan je ouders hoeveel je geleerd hebt met CodeCombat."
     email_invalid: "Emailaddress klopt niet."
     form_blurb: "Vul het emailadres van je ouders hieronder in en we zullen het ze laten zien!"
@@ -256,7 +256,7 @@
     victory_new_item: "Nieuw Item"
 #    victory_viking_code_school: "Holy smokes, that was a hard level you just beat! If you aren't already a software developer, you should be. You just got fast-tracked for acceptance with Viking Code School, where you can take your skills to the next level and become a professional web developer in 14 weeks."
     victory_become_a_viking: "Word een Viking"
-#   victory_bloc: "Great work! Your skills are improving, and someone's taking notice. If you've considered becoming a software developer, this may be your lucky day. Bloc is an online bootcamp that pairs you 1-on-1 with an expert mentor who will help train you into a professional developer! By beating A Mayhem of Munchkins, you're now eligible for a $500 price reduction with the code: CCRULES"
+#    victory_bloc: "Great work! Your skills are improving, and someone's taking notice. If you've considered becoming a software developer, this may be your lucky day. Bloc is an online bootcamp that pairs you 1-on-1 with an expert mentor who will help train you into a professional developer! By beating A Mayhem of Munchkins, you're now eligible for a $500 price reduction with the code: CCRULES"
     victory_bloc_cta: "Ontmoet je mentor – leer over Bloc"
     guide_title: "Handleiding"
     tome_minion_spells: "Jouw Minions' Spreuken" # Only in old-style levels.
@@ -360,7 +360,7 @@
     multiplayer_caption: "Speel met vrienden!"
     auth_caption: "Bewaar je voortgang."

-#  leaderboard:
+  leaderboard:
     leaderboard: "Klassement"
     view_other_solutions: "Bekijk Klassement"
     scores: "Scores"
@@ -432,9 +432,9 @@
     parents: "Voor ouders"
     parents_title: "Uw kind leert programmeren." # {change}
     parents_blurb1: "Met CodeCombat leert uw kind door echte code te schrijven. Ze beginnen met simpele instructies en naarmate ze verder komen, komen er moeilijkere onderwerpen aan bod."
-#    parents_blurb1a: "Programmeren is een essentiële vaardigheid die uw kind als volwassene ongetwijfeld gaat gebruiken. In 2020 is de basis van programmeren nodig bij 77% van de banen en zijn softwareontwikkelaars dringend nodig in de wereld. Wist u dat informatica de hoogstbetaalde universitaire graad is?"
+    parents_blurb1a: "Programmeren is een essentiële vaardigheid die uw kind als volwassene ongetwijfeld gaat gebruiken. In 2020 is de basis van programmeren nodig bij 77% van de banen en zijn softwareontwikkelaars dringend nodig in de wereld. Wist u dat informatica de hoogstbetaalde universitaire graad is?"
 #    parents_blurb2: "For $9.99 USD/mo, your child will get new challenges every week and personal email support from professional programmers."
-#    parents_blurb3: "Geen risico's: 100% geld terug garantie, 1-klik uitschrijving."
+    parents_blurb3: "Geen risico's: 100% geld terug garantie, 1-klik uitschrijving."
     payment_methods: "Betaalmethoden"
     payment_methods_title: "Geaccepteerde betaalmethoden"
     payment_methods_blurb1: "Momenteel wordt allen credit cards en Alipay geaccepteerd."
@@ -525,7 +525,7 @@
 #    level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
     restricted_to_certain_heroes: "Only certain heroes can play this level."

-#  skill_docs:
+  skill_docs:
 #    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
 #    read_only: "read-only"
     action: "Actie"
@@ -545,7 +545,7 @@
 #    returns: "Returns"
 #    granted_by: "Granted by"

-#  save_load:
+  save_load:
     granularity_saved_games: "Opgeslagen"
     granularity_change_history: "Geschiedenis"

@@ -605,7 +605,7 @@
     retrostyle_title: "Illustraties"
 #    retrostyle_blurb: "RetroStyle Games"

-#  teachers:
+  teachers:
     title: "CodeCombat: Info voor leerkrachten"
     intro_1: "CodeCombat is een online spel dat onderwijst in programmeren. Leerlingen schrijven code in echte programmeertalen."
     intro_2: "Geen ervaring nodig!"
@@ -657,7 +657,7 @@
     sys_requirements_1: "Een moderne webbrowser. Een nieuwe versie van Chrome, Firefox, of Safari. Internet Explorer 9 of later."
     sys_requirements_2: "CodeCombat ondersteund de IPad nog niet."

-#  teachers_survey:
+  teachers_survey:
 #    title: "Teacher Survey"
 #    must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
 #    retrieving: "Retrieving information..."
@@ -758,7 +758,7 @@
     sample_profile: "Bekijk een voorbeeld kandidaat-profiel"
     view_profile: "Bekijk je eigen kandidaat-profiel"

-#  keyboard_shortcuts:
+  keyboard_shortcuts:
 #    keyboard_shortcuts: "Keyboard Shortcuts"
     space: "Spatsie"
     enter: "Enter"
@@ -1341,7 +1341,7 @@
 #    while_loops: "Loops"
 #    recursion: "Recursion"

-#  delta:
+  delta:
     added: "Toegevoegd"
     modified: "Veranderd"
     not_modified: "Niet veranderd"


Update nl-NL.coffee

Update nl-NL.coffee

--- app/locale/nl-NL.coffee
+++ app/locale/nl-NL.coffee
@@ -1,26 +1,26 @@
 module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription: "Dutch (Netherlands)", translation:
   new_home:
     slogan: "Het meest uitdagende spel om mee te leren programmeren." # {change}
-#    classroom_edition: "Classroom Edition:"
-#    learn_to_code: "Learn to code:"
-#    play_now: "Play Now"
-#    im_a_teacher: "I'm a Teacher"
-#    im_a_student: "I'm a Student"
-#    learn_more: "Learn more"
+    classroom_edition: "School editie:"
+    learn_to_code: "Leer programmeren:"
+    play_now: "Speel nu"
+    im_a_teacher: "Ik ben docent"
+    im_a_student: "Ik ben student"
+    learn_more: "Leer meer"
 #    classroom_in_a_box: "A classroom in-a-box for teaching computer science."
 #    codecombat_is: "CodeCombat is a platform for students to learn computer science while playing through a real game."
 #    our_courses: "Our courses have been specifically playtested to excel in the classroom, even for teachers with little to no prior programming experience."
 #    watch_how: "Watch how CodeCombat is transforming the way people learn computer science."
 #    top_screenshots_hint: "Students write code and see their changes update in real-time"
 #    designed_with: "Designed with teachers in mind"
-#    real_code: "Real, typed code"
-#    from_the_first_level: "from the first level"
+    real_code: "Echte, zelf geschreven code"
+    from_the_first_level: "vanaf het eerste level"
 #    getting_students: "Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure."
 #    educator_resources: "Educator resources"
 #    course_guides: "and course guides"
 #    teaching_computer_science: "Teaching computer science does not require a costly degree, because we provide tools to support educators of all backgrounds."
-#    accessible_to: "Accessible to"
-#    everyone: "everyone"
+    accessible_to: "Toegankelijk voor"
+    everyone: "iedereen"
 #    democratizing: "Democratizing the process of learning coding is at the core of our philosophy. Everyone should be able to learn to code."
 #    forgot_learning: "I think they actually forgot that they were learning something."
 #    wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school."
@@ -31,11 +31,11 @@
 #    excel: "Games excel at rewarding"
 #    struggle: "productive struggle"
 #    kind_of_struggle: "the kind of struggle that results in learning that’s engaging and"
-#    motivating: "motivating"
+    motivating: "motiverend"
 #    not_tedious: "not tedious."
 #    gaming_is_good: "Studies suggest gaming is good for children’s brains. (it’s true!)"
 #    game_based: "When game-based learning systems are"
-#    compared: "compared"
+    compared: "vergeleken"
 #    conventional: "against conventional assessment methods, the difference is clear: games are better at helping students retain knowledge, concentrate and"
 #    perform_at_higher_level: "perform at a higher level of achievement"
 #    feedback: "Games also provide real-time feedback that allows students to adjust their solution path and understand concepts more holistically, instead of being limited to just “correct” or “incorrect” answers."


Remove unused attr

Remove unused attr

--- lib/changes/file-summary-view.coffee
+++ lib/changes/file-summary-view.coffee
@@ -25,7 +25,6 @@
     @dirNode      = @querySelector(".dir")
     @filenameNode = @querySelector(".filename")
     @buttonNode   = @querySelector(".btn")
-    @index        = undefined

   attachedCallback: ->
     @handleEvents()


:lipstick:

:lipstick:

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -55,7 +55,7 @@
       @checkForUpdates()

     if test
-      @runSpecs(true, @resourcePath)
+      @runSpecs({exitWhenDone: true, @resourcePath})
     else if pathsToOpen.length > 0
       @openPaths({pathsToOpen, pidToKillWhenClosed, newWindow})
     else
@@ -102,7 +102,7 @@
           label: 'Run Specs'
           accelerator: 'Command+MacCtrl+Alt+S'
           click: =>
-            @runSpecs(false, global.devResourcePath)
+            @runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath)
         }
         { type: 'separator' }
         { label: 'Quit', accelerator: 'Command+Q', click: -> app.quit() }
@@ -252,7 +252,7 @@
     @configWindow.browserWindow.on 'destroyed', =>
       @configWindow = null

-  runSpecs: (exitWhenDone, resourcePath) ->
+  runSpecs: ({exitWhenDone, resourcePath}) ->
     if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
       resourcePath = @resourcePath


Don't return a promise for job in RepoActions

--- assets/scripts/app/views/repo-actions.coffee
+++ assets/scripts/app/views/repo-actions.coffee
@@ -100,7 +100,9 @@

   _job: (->
     if id = @get('jobIdForLog')
-      Travis.__container__.lookup('store:main').find('job', id)
+      store =Travis.__container__.lookup('store:main')
+      store.find('job', id)
+      store.recordForId('job', id)
   ).property('jobIdForLog')

   jobIdForLog: (->


draw gorillas

draw gorillas


Fixed bug reading log files for a specific date

--- routes/logRoutes.coffee
+++ routes/logRoutes.coffee
@@ -42,7 +42,8 @@
   if _isValidLogDate logDate
     Logger.info "logRoutes.viewSpecific #{logDate}"
     logDate = logDate.replace(/-/g, '_')
-    logFile = './logs/' + logDate + '.txt'
+    logFile = "#{res.app.settings.dataOptions.logPath}/#{logDate}.txt"
+
     fs.readFile logFile, (err, text) ->
       if err
         Logger.error "logRoutes.viewSpecific: Error reading log file #{logFile}\r\n#{err}"


Fixed bug in getAttr, also defined undefined return values and added checks for existence of elements, when events are called.

--- js/core/elem/elem.coffee
+++ js/core/elem/elem.coffee
@@ -451,7 +451,7 @@
     for i in [ 1.._loopMaxL ]
       _key = _currTodo.shift()
       _val = _attrCache[_key]
-      _elem.setAttribute( _key, _val )
+      _elem[_key] = _val
     null

   ###
@@ -459,27 +459,28 @@
   ###
   _getAttrDirect: (_id, _key)->
     _elem = @_elements[_id]
-    _elem.getAttribute( _key )
+    _elem[_key]

   ###
   Gets a named element attribute from the cache or selectively direct
   ###
   getAttr: (_id, _key, _noCache)->
-    if _noCache
+    return null if not @_attrCache[_id]?
+    _val = @_attrCache[_id][_key]
+    if _noCache or not _val?
       _val = @_getAttrDirect( _id, _key )
-    else
-      _val = @_attrCache[_id][_key]
-    @_attrCache[_id][_key] = _val
+      @_attrCache[_id][_key] = _val
     _val

   ###
   Sets a named element attribute into the cache and buffer or selectively direct
   ###
   setAttr: (_id, _key, _value, _noCache)->
+    return null unless @_elements[_id]? # item is deleted
     _attrTodo = @_attrTodo[_id]
     _attrCache = @_attrCache[_id]
-    @_elements[_id].setAttribute( _key, _value ) if _noCache
-    _reCache = _value != @getAttr( _id, _key )
+    @_elements[_id][_key] = _value if _noCache
+    _reCache = ( _value != @getAttr( _id, _key ) )
     if _reCache or _noCache
       _attrCache[_key] = _value
       unless _noCache
@@ -488,25 +489,27 @@
           @_elemTodo.push( _id )
           @_elemTodoH[ _id ] = true
           @_checkNeedFlush()
-    null
+    true

   ###
   Deletes a named element attribute
   ###
   delAttr: (_id, _key)->
-    _attrTodo = @_attrtodo[_id]
+    return null unless @_elements[_id]? # item is deleted
+    _attrTodo = @_attrTodo[_id]
     _attrCache = @_attrCache[_id]
     delete _attrCache[_key]
     @_elements[_id].removeAttribute( _key )
     _todoIndex = _attrTodo.indexOf( _key )
     _attrTodo.splice( _todoIndex, 1 ) unless _todoIndex == -1
     @_checkNeedFlush()
-    null
+    true

   ###
   Checks if the element has a named CSS className
   ###
   hasClassName: (_id, _className)->
+    return null unless @_elements[_id]? # item is deleted
     _classNames = @_elements[_id].className.split(' ')
     return _classNames.indexOf( _className ) != -1

@@ -514,6 +517,7 @@
   Adds a named CSS className to the element
   ###
   addClassName: (_id, _className)->
+    return null unless @_elements[_id]? # item is deleted
     unless @hasClassName( _id, _className )
       _elem = @_elements[_id]
       if _elem.className.trim() == ''
@@ -527,13 +531,18 @@
   ###
   Removes a named CSS className of the element
   ###
-  removeClassName: (_id, _className)->
+  delClassName: (_id, _className)->
+    return null unless @_elements[_id]? # item is deleted
     if @hasClassName( _id, _className )
       _elem = @_elements[_id]
       _classNames = _elem.className.split(' ')
       _classNames.splice( _classNames.indexOf( _className ), 1 )
       _elem.className = _classNames.join(' ')
     null
+
+  removeClassName: (_id, _className)->
+    console.log( 'ELEM.removeClassName is deprecated. Use ELEM.delClassName instead.' )
+    return @delClassName( _id, _className )

   ###
   Checks if buffers need to be flushed
@@ -583,6 +592,7 @@
   Sets and buffers the named style attribute value or selectively direct
   ###
   setStyle: (_id, _key, _value, _noCache)->
+    return null unless @_elements[_id]? # item is deleted
     if _id == undefined
       console.log('ERROR; undefined id in ELEM#setStyle(',_id, _key, _value, _noCache,')')
     _noCache = true if BROWSER_TYPE.ie9
@@ -658,6 +668,7 @@
   Gets the named element style attribute value.
   ###
   getStyle: (_id, _key, _noCache)->
+    return null unless @_styleCache[_id]?
     _cached = @_styleCache[_id]
     _key = @_deCamelize(_key)
     if _cached[_key] == undefined or _noCache