Add workarounds for stopping executions on Darwin, Linux and Win32 systems (see #2)

--- lib/execution-manager.coffee
+++ lib/execution-manager.coffee
@@ -1,5 +1,6 @@
-exec              = require('child_process').exec
+child_process     = require('child_process')
 path              = require('path')
+_                 = require('underscore-plus')

 notificationUtils = require('./notification-utils')

@@ -45,7 +46,7 @@
     ].join(' ')

     # spawn the child process
-    @process = exec cmd,
+    @process = child_process.exec cmd,
       cwd: path.dirname(runPath)
       env: process.env

@@ -70,17 +71,69 @@

   stopExecution: ->
     if @isExecuting()
-      # FIXME on OS X and Linux this only kills the sh root process but not the
-      # child processes (primarily the run process); therefore the close event
-      # will not be emitted until the run process is killed manually
-      # @process.kill()
-      message =
-        'Unfortunately, this functionality is not yet supported.\nAt the moment
-        execution can only be stopped by killing the process manually.\n \nThis
-        will be fixed in a future release.'
-      notificationUtils.addWarning message,
-        head: 'Sorry! This is not yet supported!'
-        important: true
-      # -----------------------------------------------------------------------
+      switch process.platform
+        when 'darwin' then @killProcessOnDarwinAndLinux(@process.pid)
+        when 'linux'  then @killProcessOnDarwinAndLinux(@process.pid)
+        when 'win32'  then @killProcessOnWin32(@process.pid)
+        # else
+        #   # FIXME on OS X and Linux this only kills the sh root process but not the
+        #   # child processes (primarily the run process); therefore the close event
+        #   # will not be emitted until the run process is killed manually
+        #   # @process.kill()
+        #   message =
+        #     'Unfortunately, this functionality is not yet supported.\nAt the moment
+        #     execution can only be stopped by killing the process manually.\n \nThis
+        #     will be fixed in a future release.'
+        #   notificationUtils.addWarning message,
+        #     head: 'Sorry! This is not yet supported!'
+        #     important: true
+        #   # -----------------------------------------------------------------------
+
+  killProcessOnDarwinAndLinux: (parentPid) ->
+    # get child process IDs
+    try
+      out = child_process.execSync("pgrep -P #{parentPid}",{env: process.env})
+      childPids = _.filter _.map(out.toString().split('\n'),parseInt), (pid) ->
+        not isNaN(pid)
+    catch error
+      # execSync returns an error if the process has no childs, so we ignore
+      # this error here and continue with an empty childPids array
+      childPids = []
+
+    # recursively kill child processes
+    for childPid in childPids
+      @killProcessOnDarwinAndLinux(childPid)
+
+    # kill parent process
+    process.kill(parentPid)
+
+  # NOTE stolen from Atom's BufferedNodeProcess API (Oops!)
+  killProcessOnWin32: (parentPid) ->
+    try
+      wmicProcess = child_process.spawn 'wmic', [
+        'process'
+        'where'
+        "(ParentProcessId=#{parentPid})"
+        'get'
+        'processid'
+      ]
+      out = ''
+      wmicProcess.stdout.on 'data', (data) ->
+        out += data
+      wmicProcess.stdout.on 'close', ->
+        # get child process pids
+        childPids = out.split(/\s+/)
+                      .filter (pid) -> /^\d+$/.test(pid)
+                      .map (pid) -> parseInt(pid)
+                      .filter (pid) -> pid isnt parentPid and 0 < pid < Infinity
+        # recursively kill child processes
+        for childPid in childPids
+          @killProcessOnWin32(childPid)
+        # kill parent process
+        process.kill(parentPid)
+    catch error
+      # TODO show proper error notification here
+      console.log("Spawn error")
+      # ----------------------------------------

 # ------------------------------------------------------------------------------


use relative path

use relative path

--- lib/insert-image-view.coffee
+++ lib/insert-image-view.coffee
@@ -113,9 +113,9 @@
     return file if utils.isUrl(file)
     localDir = atom.project.getPath()
     if file.startsWith(localDir)
-      return file.replace(localDir, "")
+      return file.replace(localDir, ".")
     else
-      template = atom.config.get("markdown-writer.siteImageUrl") || "/"
+      template = atom.config.get("markdown-writer.siteImageUrl") || "./"
       return utils.dirTemplate(template) + path.basename(file)

   generateImageTag: (data) ->


Parse module errors in linter

Parse module errors in linter

--- lib/linter-purescript.coffee
+++ lib/linter-purescript.coffee
@@ -34,9 +34,16 @@
       atom.notifications.addInfo "linter: compiling PureScript"
       helpers.exec(command, args, options)
         .then (result) ->
+          matches = []
+
+          moduleRegex = '^[^\n]*(?<type>Error|Warning) in module [^\n]+:(?<message>.*?)^[^\n]*See'
+          XRegExp.forEach result, XRegExp(moduleRegex, "sm"), (match) ->
+            matches.push(mkResult(match))
+
+
           regex = '^[^\n]*(?<type>Error|Warning) at (?<file>[^\n]*) line (?<lineStart>[0-9]+), column (?<colStart>[0-9]+) - line (?<lineEnd>[0-9]+), column (?<colEnd>[0-9]+):(?<message>.*?)^[^\n]*See'

-          matches = []
+
           XRegExp.forEach result, XRegExp(regex, "sm"), (match) ->
             matches.push(mkResult(match))


Subscribe to channel join and remove connection

--- lib/nsync/initializer.coffee
+++ lib/nsync/initializer.coffee
@@ -8,30 +8,6 @@
 executeCustomCommand = require './custom-commands'
 {name} = require '../../package.json'
 {CompositeDisposable} = require 'event-kit'
-
-require('dotenv').config
-  path: _path.join(__dirname, '..', '..', '.env')
-  silent: true
-
-require('dotenv').config
-  path: _path.join(atom.getConfigDirPath(), '.env')
-  silent: true
-
-WS_SERVER_URL = (->
-  config = _.defaults
-    host: process.env['IDE_WS_HOST']
-    port: process.env['IDE_WS_PORT']
-    path: process.env['IDE_WS_FS_PATH']
-  ,
-    host: 'ile.learn.co',
-    port: 443,
-    path: 'environment'
-
-  {host, port, path} = config
-  protocol = if port is 443 then 'wss' else 'ws'
-
-  "#{protocol}://#{host}:#{port}/#{path}"
-)()

 convertEOL = (text) ->
   text.replace(/\r\n|\n|\r/g, '\n')
@@ -197,16 +173,11 @@

   disposables.forEach (disposable) -> composite.add(disposable)

-  connect = ->
-    atomHelper.getToken().then (token) ->
-      nsync.configure
-        expansionState: activationState.directoryExpansionStates
-        localRoot: _path.join(atom.configDirPath, '.learn-ide')
-        connection:
-          url: "#{WS_SERVER_URL}?token=#{token}&version=#{atomHelper.learnIdeVersion()}"
-          socketKey: 'environment'
-
-  atomHelper.waitForTerminalConnection().then(connect).catch(connect)
+  atom.emitter.on 'learn-ide:did-join-channel', (channel) ->
+    nsync.configure
+      expansionState: activationState.directoryExpansionStates
+      localRoot: _path.join(atom.configDirPath, '.learn-ide')
+      channel: channel

   return composite


Fixes #33

Fixes #33

--- lib/open-last-project.coffee
+++ lib/open-last-project.coffee
@@ -11,13 +11,14 @@
         return unless File
         Files.push File
       CurrentFile = ActiveEditor && ActiveEditor.getPath() || null;
-      localStorage.setItem('open-last-project',JSON.stringify({Path: atom.project.getPaths()[0], Files: Files, CurrentFile: CurrentFile}))
+      localStorage.setItem('open-last-project',JSON.stringify({Paths: atom.project.getPaths(), Files: Files, CurrentFile: CurrentFile}))
   LoadProject:->
     LastProject = localStorage.getItem('open-last-project')
     return unless LastProject
     LastProject = JSON.parse(LastProject)
-    return unless LastProject.Path
-    atom.project.setPaths [LastProject.Path]
+    return unless LastProject.Paths.length
+
+    atom.project.setPaths LastProject.Paths
     Promises = LastProject.Files.map (file)->
       return new Promise (resolve)->
         FS.exists file, (Status)->


Make editable

Make editable

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -38,6 +38,7 @@
     initialize: () ->
       super()
       @listenTo(@model, 'change:downloads change:buyLink changePage', @render)
+      @listenTo(@model, 'change:edit', @toggleEdit)

     onRender: () ->
       @regions.button.append new BookPopoverView
@@ -49,3 +50,6 @@

       $summary.find('h5').toggleClass('active')
       @$el.find('.abstract').toggle()
+
+    toggleEdit: () ->
+      @$el.children('.media-header').children('h2').attr('contenteditable', @model.get('edit'))


Don't allow headers of unpublished content to be edited

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -56,6 +56,12 @@
       @regions.button.append new BookPopoverView
         model: @model
         owner: @$el.find('.info .btn')
+
+    isEditable: () ->
+      if @model.isBook()
+        return @model.get('currentPage')?.isEditable()
+
+      return @model.isEditable()

     toggleSummary: (e) ->
       $summary = @$el.find('.summary')


取消var 标识

取消var 标识

--- lib/pushservices/apns.coffee
+++ lib/pushservices/apns.coffee
@@ -51,8 +51,7 @@
                 difference = note_bytesize - 265
             alert_bytesize =  oldAlert_bytesize - difference - 3
             alertNew = ''
-            for(var i = 0; i < b.length;i++){
-                alert(b.charAt(i));
+            for(i = 0; i < b.length;i++){
                 if alertNew.replace(/[^\x00-\xff]/gi, "--").length + b.charAt(i).replace(/[^\x00-\xff]/gi, "--").length <= alert_bytesize
                     alertNew = alertNew + b.charAt(i)
                 else


Delete only files which are generated at least 60 seconds ago.

--- lib/tempfile-repo-lib.coffee
+++ lib/tempfile-repo-lib.coffee
@@ -12,9 +12,8 @@
     console.log 'LocaleTempFileRepository.gc'
     now = new Date()
     validUntil = now - 60
-    for id, item of @items #when item.created < validUntil
+    for id, item of @items when item.created.getTime() < validUntil
       filePath = @items[id].filePath
-      console.info item.created.getTime(), validUntil
       console.log "LocaleTempFileRepository.gc >> Deleting #{filePath}..."
       @remove id
     return


FIX detect import statements with curly braces

--- lib/typescript-import.coffee
+++ lib/typescript-import.coffee
@@ -62,7 +62,7 @@
         atom.notifications.addWarning('Import already defined.');
       else
         currentPosition = editor.getCursorBufferPosition()
-        importMatches = currentText.match(/import\s*\w*\s*from.*\n/g)
+        importMatches = currentText.match(/import\s*(\{\s*)?\w*\s*(\}\s*)?from.*\n/g)
         referencesMatches= currentText.match(/\/\/\/\s*<reference\s*path.*\/>\n/g)
         useStrictMatche = currentText.match(/.*[\'\"]use strict[\'\"].*/)
         if importMatches


Remove not needed keyword

Remove not needed keyword

--- library/nucleus/tools.coffee
+++ library/nucleus/tools.coffee
@@ -39,7 +39,7 @@
 # that are in use by the current kernel and the servers. It may
 # be very useful for automatic generation of correct URLs and all
 # other content that requires the specific reference to the host.
-module.exports.urlWithHost = (ssl = no, parts, params, segment) ->
+module.exports.urlWithHost = (ssl, parts, params, segment) ->
     params = query.stringify params if params?
     parts = parts.join "/" if _.isArray parts
     server = nconf.get "server:port"


force usage of jQuery 2 from jquery-rails

--- app/assets/javascripts/active_admin/base.js.coffee
+++ app/assets/javascripts/active_admin/base.js.coffee
@@ -1,4 +1,4 @@
-#= require jquery
+#= require jquery2
 #= require ./jquery_ui
 #= require jquery_ujs
 #= require_self


require search for admin layout

require search for admin layout

[ci skip]

--- app/assets/javascripts/application-admin.js.coffee
+++ app/assets/javascripts/application-admin.js.coffee
@@ -1,3 +1,4 @@
 #= require jquery
 #= require jquery_ujs
 #= require bootstrap-sprockets
+#= require search


also show loading on link click

also show loading on link click

--- app/assets/javascripts/loading_animation.js.coffee
+++ app/assets/javascripts/loading_animation.js.coffee
@@ -1,6 +1,8 @@
 $ ->
   Loading.elem ||= $('.loading-animation')
   $('form').on 'submit', (e) ->
+    Loading.show()
+  $('body').on 'click touchstart', 'a', (e) ->
     Loading.show()
   window.App.loading = Loading


update from medium editor after closing image controls

--- app/assets/javascripts/storytime/wysiwyg.js.coffee
+++ app/assets/javascripts/storytime/wysiwyg.js.coffee
@@ -96,6 +96,15 @@
   closeImageControls: ->
     $(".medium-editor img").removeClass("medium-active-image")
     $(".medium-image-controls").remove()
+    @updateFromMediumEditor()
+
+  updateFromMediumEditor: ->
+    $('.medium-editor').each ->
+      input = $($(this).data('input'))
+      codemirror = input.siblings('.CodeMirror')[0].CodeMirror
+      html = $(this).html()
+      input.val(html)
+      codemirror.setValue(html)

   setupMedium: ->
     # Medium-editor keeps adding toolbars when this method gets triggered


DevTools:codeEditor:loadFile make sure the corresponding kite exists

--- client/DevTools/views/codeeditor.coffee
+++ client/DevTools/views/codeeditor.coffee
@@ -19,6 +19,10 @@
   loadFile:(path, callback = noop)->

     file = FSHelper.createFileFromPath path
+
+    kite = KD.getSingleton('vmController').getKiteByVmName file.vmName
+    return  callback {message: "VM not found"}  unless kite
+
     file.fetchContents (err, content)=>
       return callback err  if err


TerminalSession: existantial check is added for sessions

--- client/Terminal/sessionstackview.coffee
+++ client/Terminal/sessionstackview.coffee
@@ -25,7 +25,7 @@
       @destroySubViews()
       @show()
       @loader.hide()
-      sessions.forEach @bound 'addSession'
+      sessions?.forEach @bound 'addSession'
     .catch (err) =>
       @hide()
       @loader.hide()


Clear search input and Reactive filters when tabs clicked

--- client/controllers/events/events.coffee
+++ client/controllers/events/events.coffee
@@ -46,7 +46,8 @@
     $(event.currentTarget).blur()

   'click .tab a': (event, instance) ->
-    ReactiveTable.clearFilters(['smartEventFilter'])
+    instance.$('.search').val('')
+    ReactiveTable.clearFilters(['eventFilter', 'creatorFilter'])
     event.currentTarget.blur()

   'click .create-event': (event, instance) ->


Added template helpers for capitalized translation

--- core/app/backbone/views/generic/tourstepbox.coffee
+++ core/app/backbone/views/generic/tourstepbox.coffee
@@ -16,6 +16,10 @@

 class window.AddChannelsTourStep2 extends TourStepBox
   template: 'tour/add_channel_step2'
+  templateHelpers: ->
+    topics: Factlink.Global.t.topics.capitalize()
+    channels: Factlink.Global.t.channels.capitalize()
+

 class window.AddChannelsTourStep3 extends TourStepBox
   template: 'tour/add_channel_step3'


visible not visual

visible not visual

--- client/skr/screens/invoice/Invoice.cjsx
+++ client/skr/screens/invoice/Invoice.cjsx
@@ -9,7 +9,7 @@
         invoice: ->
             @loadOrCreateModel({
                 syncOptions: @syncOptions, klass: Skr.Models.Invoice,
-                prop: 'invoice', attribute: 'visual_id'
+                prop: 'invoice', attribute: 'visible_id'
             })

     getInitialState: ->


Give the abort CSS class higher priority.

(Maybe.  If this doesn't work, there are other ways to make that
happen.)

--- dashboard/assets/javascripts/controllers.js.coffee
+++ dashboard/assets/javascripts/controllers.js.coffee
@@ -18,8 +18,8 @@
   classNames: (->
     classes = []

+    classes.pushObject('completed') if @get('completed')
     classes.pushObject('aborted') if @get('aborted')
-    classes.pushObject('completed') if @get('completed')

     classes
   ).property('aborted', 'completed')


Test SMS for iPhone.

Test SMS for iPhone.

--- client/templates/includes/footer.coffee
+++ client/templates/includes/footer.coffee
@@ -8,7 +8,7 @@
   selected_users: -> selected_users()
   project_or_member_page: ->
     Router.current().route.getName() in ["projectPage", "memberPage"]
-
+
   phone_url: ->
     users = selected_users()
     if users.length == 0
@@ -26,7 +26,7 @@
       "#"
     else if users.length == 1
       phone_number = users[0].telephone_nr
-      "sms:#{phone_number}"
+      "sms:#{phone_number};body=Hello there"
     # "whatsapp://send?abid=#{phone_number}"

 Template.footer.events


fix animation skipping on shifts page

fix animation skipping on shifts page

--- client/views/pages/shifts/shifts.coffee
+++ client/views/pages/shifts/shifts.coffee
@@ -74,7 +74,7 @@

 			WeekSubs.subscribe 'week', projectId, week

-		$('.animated').removeClass('animated').addClass('skipped')
+		Delay -> $('.animated').removeClass('animated').addClass('skipped')

 Template.shifts.onDestroyed ->


config: use tunnelproxy manager's credential for tunnelserver

--- config/workers.coffee
+++ config/workers.coffee
@@ -476,7 +476,7 @@
     tunnelserver        :
       group             : "proxy"
       supervisord       :
-        command         : "#{GOBIN}/tunnelserver -accesskey #{credentials.awsKeys.worker_tunnelproxymanager_route53.accessKeyId} -secretkey #{credentials.awsKeys.worker_tunnelproxymanager_route53.secretAccessKey} -port #{KONFIG.tunnelserver.port} -basevirtualhost #{KONFIG.tunnelserver.basevirtualhost} -hostedzone #{KONFIG.tunnelserver.hostedzone} -region #{options.region} -environment #{options.environment}"
+        command         : "#{GOBIN}/tunnelserver -accesskey #{credentials.awsKeys.worker_tunnelproxymanager.accessKeyId} -secretkey #{credentials.awsKeys.worker_tunnelproxymanager.secretAccessKey} -port #{KONFIG.tunnelserver.port} -basevirtualhost #{KONFIG.tunnelserver.basevirtualhost} -hostedzone #{KONFIG.tunnelserver.hostedzone} -region #{options.region} -environment #{options.environment}"
       ports             :
         incoming        : "#{KONFIG.tunnelserver.port}"
       healthCheckURL    : "http://tunnelserver/healthCheck"


[Gutter] Remove test-only CSS

[Gutter] Remove test-only CSS

--- src/custom-gutter-component.coffee
+++ src/custom-gutter-component.coffee
@@ -2,11 +2,6 @@

 # This class represents a gutter other than the 'line-numbers' gutter.
 # The contents of this gutter may be specified by Decorations.
-
-# TODO (jssln) Remove these (testing-only).
-TEMP_MIN_WIDTH = 25
-TEMP_DECOR_WIDTH = '50px'
-TEMP_DECOR_BACKGROUND = 'white'

 module.exports =
 class CustomGutterComponent
@@ -18,8 +13,6 @@

     @domNode = atom.views.getView(@gutter)
     @decorationsNode = @domNode.firstChild
-
-    @domNode.style['width'] = '' + TEMP_MIN_WIDTH + 'px'

   getDomNode: ->
     @domNode
@@ -73,8 +66,6 @@
     newNode.style.top = decorationInfo.top + 'px'
     newNode.style.height = decorationInfo.height + 'px'
     newNode.style.position = 'absolute'
-    newNode.style['background-color'] = TEMP_DECOR_BACKGROUND
-    newNode.style.width = TEMP_DECOR_WIDTH
     if decorationInfo.class
       newNode.classList.add(decorationInfo.class)
     @setDecorationItem(decorationInfo.item, decorationInfo.height, decorationId, newNode)


ContentEditable: minor cleanup

ContentEditable: minor cleanup

--- src/components/inputs/contenteditableview.coffee
+++ src/components/inputs/contenteditableview.coffee
@@ -2,6 +2,7 @@
 KDNotificationView = require './../notifications/notificationview.coffee'

 module.exports = class KDContentEditableView extends KDView
+
   constructor: (options = {}, data) ->
     options.cssClass         = KD.utils.curry "kdcontenteditableview", options.cssClass
     options.bind             = KD.utils.curry "click input keydown paste drop", options.bind
@@ -91,8 +92,8 @@
     {tabNavigation, multiline, validate} = @getOptions()

     switch event.which
-      when 9  then @utils.stopDOMEvent event  if tabNavigation
-      when 13 then @utils.stopDOMEvent event
+      when 9  then KD.utils.stopDOMEvent event  if tabNavigation
+      when 13 then KD.utils.stopDOMEvent event

     switch event.which
       when 9 # Tab key


After creating a Factlink you should still be able to open a modal

--- core/app/assets/javascripts/tour.coffee
+++ core/app/assets/javascripts/tour.coffee
@@ -41,7 +41,8 @@
         { name: 'deselect_text',   from:  'text_selected',                       to: 'started' }
         { name: 'deselect_text',   from:  'factlink_created_and_text_selected',  to: 'factlink_created' }
         { name: 'open_modal',      from:  'text_selected',                       to: 'modal_opened' }
-        { name: 'open_modal',      from:  'factlink_created_and_text_selected',  to: 'factlink_created_and_modal_opened' }
+        { name: 'open_modal',      from:  ['factlink_created',
+                                           'factlink_created_and_text_selected'],to: 'factlink_created_and_modal_opened' }
         { name: 'close_modal',     from:  'modal_opened',                        to: 'started' }
         { name: 'close_modal',     from:  'factlink_created_and_modal_opened',   to: 'factlink_created' }
         { name: 'create_factlink', from: ['modal_opened',


Recheck macAutocomplete url expression on each callback fire

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -42,7 +42,7 @@
       delay         = +attrs.macAutocompleteDelay      or 800
       clearOnSelect = attrs.macAutocompleteClearOnSelect?

-      autocompleteUrl     = $parse(attrs.macAutocompleteUrl) $scope
+      autocompleteUrl     = $parse attrs.macAutocompleteUrl
       onSelect            = $parse attrs.macAutocompleteOnSelect
       onSuccess           = $parse attrs.macAutocompleteOnSuccess
       onError             = $parse attrs.macAutocompleteOnError
@@ -84,10 +84,12 @@
       # @param {Response function} resp Response callback function for jQuery UI
       #
       sourceFn = (req, resp) ->
-        if autocompleteUrl
+        url = autocompleteUrl $scope
+
+        if url
           options =
             method: "GET"
-            url:    autocompleteUrl
+            url:    url
             params: {}
           options.params[queryKey] = req.term


fix(macAutocomplete): Fixed not querying data correctly

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -164,7 +164,7 @@
               queryData value
             , delay

-          else if isMenuAppended
+          else
             queryData value

         else


logout before trying to login as a different type

--- controllers/login.coffee
+++ controllers/login.coffee
@@ -49,9 +49,10 @@
   else if req.team
     res.redirect "/teams/#{req.team}"
   else
-    res.redirect '/teams/new'
+    res.redirect '/'

 # order matters
 app.get '/login/:service?', [setReturnTo], (req, res, next) ->
+  req.logout()
   req.session.returnTo or= req.returnTo
   res.redirect "/auth/#{req.param('service') or 'github'}"


Fix error when event.touches[0] is undefined

--- src/angular-slider.coffee
+++ src/angular-slider.coffee
@@ -174,7 +174,7 @@
                         ngDocument.unbind events.move
                         ngDocument.unbind events.end
                     onMove = (event) ->
-                        eventX = event.clientX || event.touches[0] ? event.touches[0].clientX : 0
+                        eventX = event.clientX || (event.touches ? event.touches[0].clientX : 0)
                         newOffset = eventX - element[0].getBoundingClientRect().left - pointerHalfWidth
                         newOffset = Math.max(Math.min(newOffset, maxOffset), minOffset)
                         newPercent = percentOffset newOffset


test: Test #683

test: Test #683

--- test/integration/cli/reporters-cli-test.coffee
+++ test/integration/cli/reporters-cli-test.coffee
@@ -274,12 +274,12 @@
       assert.isOk fs.existsSync "#{process.cwd()}/__test_file_output1__.xml"
       assert.isOk fs.existsSync "#{process.cwd()}/__test_file_output2__.xml"

-  describe 'When -o/--output is used to specify output file but directory is not existent', ->
-    dreddCommandInfo = undefined
-    args = [
-      './test/fixtures/single-get.apib'
-      "http://127.0.0.1:#{DEFAULT_SERVER_PORT}"
-      '--reporter=junit'
+  describe 'when -o/--output is used to specify output file but directory is not existent', ->
+    dreddCommandInfo = undefined
+    args = [
+      './test/fixtures/single-get.apib'
+      "http://127.0.0.1:#{DEFAULT_SERVER_PORT}"
+      '--reporter=xunit'
       '--output=./__test_directory/__test_file_output__.xml'
     ]

@@ -298,3 +298,35 @@

     it 'should create given file', ->
       assert.isOk fs.existsSync "#{process.cwd()}/__test_directory/__test_file_output__.xml"
+
+  describe('when the \'apiary\' reporter fails', ->
+    apiaryApiUrl = undefined
+    dreddCommandInfo = undefined
+    args = [
+      './test/fixtures/single-get.apib'
+      "http://127.0.0.1:#{DEFAULT_SERVER_PORT}"
+      '--reporter=apiary'
+    ]
+
+    beforeEach((done) ->
+      apiaryApiUrl = process.env.APIARY_API_URL
+
+      nonExistentPort = DEFAULT_SERVER_PORT + 42
+      process.env.APIARY_API_URL = "http://127.0.0.1:#{nonExistentPort}"
+
+      runDreddCommand(args, (err, info) ->
+        dreddCommandInfo = info
+        done(err)
+      )
+    )
+    afterEach( ->
+      process.env.APIARY_API_URL = apiaryApiUrl
+    )
+
+    it('ends successfully', ->
+      assert.equal(dreddCommandInfo.exitStatus, 0)
+    )
+    it('prints error about Apiary API connection issues', ->
+      assert.include(dreddCommandInfo.stderr, 'Apiary reporter could not connect to Apiary API')
+    )
+  )


fix typo

fix typo

--- core/router/admin.coffee
+++ core/router/admin.coffee
@@ -43,7 +43,7 @@

     finish: (callback) ->
       mTicket.find
-        status: 'open'
+        status: 'finish'
       ,
         sort:
           updated_at: -1


Idle watch

Idle watch

--- app/js/controllers/root.coffee
+++ app/js/controllers/root.coffee
@@ -11,9 +11,9 @@
     , ->
         $scope.currentPath = $location.path()
         if $location.path() == "/unlockwallet" || $location.path() == "/createwallet"
+            stopIdleWatch()
             $scope.bodyclass = "splash"
             $scope.unlockwallet = true
-            stoptIdleWatch()
         else
             # TODO update bodyclass by watching unlockwallet
             startIdleWatch()


Set a more reasonable interval count

Set a more reasonable interval count

--- test/spec/sync/iteration/interval.tests.coffee
+++ test/spec/sync/iteration/interval.tests.coffee
@@ -11,7 +11,7 @@
   DATABASE_URL = options.database_url or ''
   BASE_SCHEMA = options.schema or {}
   SYNC = options.sync
-  BASE_COUNT = 50
+  BASE_COUNT = 100

   DATE_START = new Date('2013-06-09T08:00:00.000Z')
   DATE_STEP_MS = 1000


When a message selected enable messageActionButtons

--- client/app/Applications/Inbox.kdapplication/AppView.coffee
+++ client/app/Applications/Inbox.kdapplication/AppView.coffee
@@ -1,14 +1,14 @@
 class PageInbox extends KDView
-
+
   KD.registerPage "Inbox", PageInbox

   createCommons:->
     @addSubView header = new HeaderViewSection type : "big", title : "Inbox"

-    # Common left pane
+    # Common left pane
     @commonInnerNavigation = new InboxInnerNavigation

-    @commonInnerNavigation.registerListener
+    @commonInnerNavigation.registerListener
       KDEventTypes : "CommonInnerNavigationListItemReceivedClick"
       listener     : @
       callback     : (pubInst, data)=>
@@ -21,7 +21,7 @@
           @sort data.type

     @inboxTabs = new KDTabView
-      cssClass  : "inbox-tabview"
+      cssClass  : "inbox-tabview"
     @inboxTabs.hideHandleContainer()

     inboxSplitView = @inboxSplitView = new ContentPageSplitBelowHeader
@@ -29,20 +29,19 @@
       sizes     : [138,null]
       cssClass  : "inbox-main-split"
       resizable : no
-
+
     @addSubView inboxSplitView

     inboxSplitView._windowDidResize()
-

   createTabs:->
     @_tab_messages      = @createMessagesTab()
     @_tab_notifications = @createNotificationsTab()
     @inboxTabs.showPane @_tab_messages
-
+
   createMessagesTab:->
     @inboxTabs.addPane tab = new KDTabPaneView cssClass : "messages-tab"
-
+
     @inboxMessagesContainer = new KDTabView cssClass : "message-thread-tabs"
     @inboxMessagesContainer.hideHandleContainer()

@@ -56,21 +55,22 @@
     inboxMessageListController = new InboxMessageListController
       view          : inboxMessagesList

-    tab.addSubView @newMessageBar = new InboxNewMessageBar
+    tab.addSubView @newMessageBar = new InboxNewMessageBar
       cssClass  : "new-message-bar clearfix"
       delegate  : @inboxMessagesContainer

     @newMessageBar.on "RefreshButtonClicked", =>
       inboxMessageListController.loadMessages =>
         @newMessageBar.refreshButton.hideLoader()
-
+
+    @newMessageBar.disableMessageActionButtons()
+
     inboxMessageListController.loadMessages()
-

     messagesSplit = new SplitViewWithOlderSiblings
       sizes     : ["100%",null]
       views     : [inboxMessagesList,@inboxMessagesContainer]
-      cssClass  : "messages-split"
+      cssClass  : "messages-split"
       resizable : yes
       minimums  : [150, null]

@@ -86,16 +86,17 @@
         messagesSplit._windowDidResize()
         unless messagesSplit.didResizeBefore
           messagesSplit.resizePanel "100%",0
-
+
     messagesSplit.listenTo
       KDEventTypes : "MessageIsSelected"
       listenedToInstance : @
-      callback :->
-        @resizePanel "33%",0 unless messagesSplit.didResizeBefore
+      callback :=>
+        messagesSplit.resizePanel "33%",0 unless messagesSplit.didResizeBefore
         messagesSplit.didResizeBefore = yes
-
+        @newMessageBar.enableMessageActionButtons()
+
     inboxMessageInputWrapper.addSubView @messageInputElement = new KDHitEnterInputView
-      type         : "textarea"
+      type         : "textarea"
       name         : "sendMessage"
       cssClass     : "sendMessageInput"
       placeholder  : "Just type and press enter.."
@@ -104,11 +105,11 @@
         @messageInputElement.setValue ''
         @propagateEvent KDEventType: 'ReplyShouldBeSent', {message: @messageInputElement.getData(), reply}

-    @listenTo
+    @listenTo
       KDEventTypes       : "viewAppended"
       listenedToInstance : @messageInputElement
       callback           : messagesSplit._windowDidResize
-
+
     return tab

   createNotificationsTab:->
@@ -118,17 +119,17 @@
       view            : inboxNotificationsList = new InboxMessagesList
         cssClass      : "inbox-list notifications"
         subItemClass  : NotificationListItem
-
+
     tab.addSubView inboxNotificationsController.getView()
     inboxNotificationsController.fetchNotificationTeasers (items)=>
       inboxNotificationsController.instantiateListItems items
-
+
     return tab

   createFriendsTab:->
-
+
   createChatTab:->
-
+
   showTab:(type)->
     mainView = @
     unless mainView["_tab_#{type}"]?
@@ -141,13 +142,13 @@
   constructor:(options, data)->
     options.cssClass = "clearfix member-suggestion-item"
     super
-
+
     {userInput} = @getDelegate()
-
+
     @avatar = new AutoCompleteAvatarView {},data
     @profileLink = new AutoCompleteProfileTextView {userInput, shouldShowNick: yes},data
     # @nickLink = new AutoCompleteNickView {userInput},data
-
+
   pistachio:->
     """
       {{> @avatar}}
@@ -158,8 +159,8 @@
     super()
     @setTemplate @pistachio()
     @template.update()
-
-
+
+
   partial:()-> ''

 class MemberAutoCompletedItemView extends KDAutoCompletedItem
@@ -179,13 +180,13 @@
     super()
     @setTemplate @pistachio()
     @template.update()
-
+
   partial:()-> ''
-
-

-

-
-
-
+
+
+
+
+
+


Better confirmation modal for VMs.

Better confirmation modal for VMs.

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -217,8 +217,8 @@
           forms                     :
             "Create VM"             :
               callback              : (formData)=>
-                paymentController.makePaymentModal formData.type, @paymentPlans[formData.host], ->
-                  modal.destroy()
+                modal.destroy()
+                @confirmPayment formData.type, @paymentPlans[formData.host]
               buttons               :
                 user                :
                   title             : "Create a Personal VM"
@@ -286,31 +286,6 @@
                 type                :
                   name              : "type"
                   type              : "hidden"
-                "userVM"            :
-                  itemClass         : KDCustomHTMLView
-                  partial           : "

-                                        You already have a subscription for personal VM. You
-                                        won't be charged.
-
"
-                "userCredits"       :
-                  itemClass         : KDCustomHTMLView
-                  partial           : "

-                                        You have $0 credited to your personal account. You won't
-                                        be charged until you consume that amount.
-
"
-                "groupVM"           :
-                  itemClass         : KDCustomHTMLView
-                  partial           : "

-                                        You already have a subscription for group VM. You
-                                        won't be charged.
-
"
-                "groupCredits"      :
-                  itemClass         : KDCustomHTMLView
-                  partial           : "

-                                        You have $0 credited to your group account. You won't
-                                        be charged until you consume that amount.
-
"
-

       if canCreateSharedVM
         modal.modalTabs.forms["Create VM"].buttons.group.show()
@@ -319,8 +294,6 @@
       modal.once 'KDModalViewDestroyed', => @dialogIsOpen = no

       form = modal.modalTabs.forms["Create VM"]
-
-      @showPaymentNotices form

       hideLoaders = ->
         {group, user, expensed} = form.buttons
@@ -331,46 +304,84 @@
       vmController.on "PaymentModalDestroyed", hideLoaders
       form.on "FormValidationFailed", hideLoaders

-  showPaymentNotices: (form)->
-    group = KD.getSingleton("groupsController").getCurrentGroup()
+  confirmPayment: (type, plan, callback=->)->
+    paymentController = KD.getSingleton('paymentController')
+    group             = KD.getSingleton("groupsController").getCurrentGroup()
+
+    getBalance = (type, cb)->
+      if type is 'user'
+        KD.remote.api.JRecurlyPlan.getUserBalance cb
+      else
+        KD.remote.api.JRecurlyPlan.getGroupBalance group, cb
+
+    buildModal = (content, cb)->
+      modal           = new KDModalView
+        title         : "Confirm VM Creation"
+        content       : "
#{content}
"
+        overlay       : yes
+        buttons       :
+          No          :
+            title     : "Cancel"
+            cssClass  : "modal-clean-gray"
+            callback  : =>
+              modal.destroy()
+              cb()
+          Yes         :
+            title     : "OK, create the VM"
+            cssClass  : "modal-clean-green"
+            callback  : =>
+              modal.destroy()
+              cb()
+              paymentController.makePaymentModal type, plan, ->
+                console.log "I'm done."

-    # Show user account balance, if necessary
-    {userCredits, userVM} = form.fields
-    userCredits.setClass "hidden"
-    userVM.setClass "hidden"
-
     group.canCreateVM
-      type    : "user"
-      planCode: "group_vm_1xs_1"
+      type     : type
+      planCode : plan.code
     , (err, status)->
       if not err and status
-        userVM.unsetClass "hidden"
+        content = """

+                       You already subscribed for an additional
+                       #{type} VM.
+

+

+                       You won't be charged. Do you want to
+                       continue?.
+

+                  """
+        buildModal content, callback
       else
-        KD.remote.api.JRecurlyPlan.getUserBalance (err, balance)->
-          if err or balance <= 0
-            userCredits.setClass "hidden"
+        getBalance type, (err, balance)->
+          if not err and balance > 0
+            charge = (plan.feeMonthly - balance) / 100
+            balance = balance / 100
+
+            content = """

+                           You have $#{balance.toFixed(2)} credited to your
+                           #{type} account.
+

+                      """
+
+            if charge > 0
+              content += """

+                              You will be charged for $#{charge.toFixed(2)}.
+                              Do you want to continue?
+

+                         """
+            else
+              content += """

+                              You won't be charged. Do you
+                              want to continue?
+

+                         """
           else
-            userCredits.$('p span.price').text (balance / 100).toFixed(2)
-            userCredits.unsetClass "hidden"
-
-    # Show group account balance, if necessary
-    {groupCredits, groupVM} = form.fields
-    groupCredits.setClass "hidden"
-    groupVM.setClass "hidden"
-
-    group.canCreateVM
-      type    : "group"
-      planCode: "group_vm_1xs_1"
-    , (err, status)->
-      if not err and status
-        groupVM.unsetClass "hidden"
-      else
-        KD.remote.api.JRecurlyPlan.getGroupBalance group, (err, balance)->
-          if err or balance <= 0
-            groupCredits.setClass "hidden"
-          else
-            groupCredits.$('p span.price').text (balance / 100).toFixed(2)
-            groupCredits.unsetClass "hidden"
+            charge = plan.feeMonthly / 100
+            content = """

+                           You will be charged for $#{charge.toFixed(2)}. Do
+                           you want to continue?
+

+                      """
+          buildModal content, callback

   askForApprove:(command, callback)->


include needed Icon component

include needed Icon component

--- client/lanes/components/shared/NetworkActivityOverlay.cjsx
+++ client/lanes/components/shared/NetworkActivityOverlay.cjsx
@@ -1,3 +1,5 @@
+##= require 'lanes/components/shared/Icon'
+
 class Lanes.Components.NetworkActivityOverlay extends Lanes.React.Component

     propTypes:


Prevent bubbling/tunneling when any callback returns false

--- source/core/event.coffee
+++ source/core/event.coffee
@@ -108,6 +108,6 @@

   _state: (instance, event, callback, args, type="bubble") ->
     # Dispatch event to instance
-    instance[callback]?.apply instance, args
-    # Recursive event
-    instance[type].apply instance, [event].concat(args)
+    unless instance[callback]?.apply(instance, args) is false
+      # Recursive event
+      instance[type].apply instance, [event].concat(args)


Use statusColor scale for regional status charts

--- app/scripts/dcplotsctrl.coffee
+++ app/scripts/dcplotsctrl.coffee
@@ -273,7 +273,7 @@
         pieChart quantityChart, quantities, quantities.group(), all
         pieChart qualityChart, qualities, qualities.group(), all
         pieChart extractionChart, extractionTypes, extractionTypes.group(), all
-        pieChart statusChart, statuses, statusGroup, all
+        pieChart statusChart, statuses, statusGroup, all, statusColor
         pieChart paymentChart, paymentTypes, paymentGroup, all
         pieChart installerChart, installers, installersGroup, all
         pieChart funderChart, funders, fundersGroup, all
@@ -357,6 +357,7 @@
           .stack(group, "Not Functional", (d) -> d.value["not functional"])
           .x(d3.scale.ordinal())
           .xUnits(dc.units.ordinal)
+          .colors(statusColor)
           .elasticY(true)
           .elasticX(true)
           .gap(gap || 10)
@@ -376,7 +377,7 @@
           .on("preRedraw", (chart) ->
             chart.rescale())

-      pieChart = (chart, dim, group,all) ->
+      pieChart = (chart, dim, group,all, colorScale) ->
         chart
           .width(null)
           .height(null)
@@ -392,6 +393,8 @@
           .title((d) ->
             d.key + " (" + Math.floor(d.value / all.value() * 100) + "%)")

+        if colorScale then chart.colors(colorScale)
+
       yearChart = (chart, dim, group, xlabel) ->
         chart
           .width(null)
@@ -404,6 +407,7 @@
           .valueAccessor((p) -> p.value.functional)
           .stack(group, "Needs Repair", (d) -> d.value["needs repair"])
           .stack(group, "Not Functional", (d) -> d.value["not functional"])
+          .colors(statusColor)
           .elasticY(true)
           .elasticX(true)
           .gap(1)


fix test

fix test

--- app/selectors/libs.spec.coffee
+++ app/selectors/libs.spec.coffee
@@ -1,7 +1,7 @@
 # A test suite for lib selectors.

 import {should} from 'chai'
-import libs from './libs'
+import {filteredByQuery} from './libs'

 should()

@@ -18,22 +18,22 @@
 describe 'app', -> describe 'libs', ->
   describe '#filteredByQuery', ->
     it 'should filter to a single result', (done) ->
-      (libs.filteredByQuery withQuery 'a').should.deep.equal [
+      (filteredByQuery withQuery 'a').should.deep.equal [
         {name: 'a'}
       ]
       done()
     it 'should be case insensitive', (done) ->
-      (libs.filteredByQuery withQuery 'b').should.deep.equal [
+      (filteredByQuery withQuery 'b').should.deep.equal [
         {name: 'b'}
         {name: 'B'}
       ]
-      (libs.filteredByQuery withQuery 'B').should.deep.equal [
+      (filteredByQuery withQuery 'B').should.deep.equal [
         {name: 'b'}
         {name: 'B'}
       ]
       done()
     it 'should do prefix matching', (done) ->
-      (libs.filteredByQuery withQuery 'c').should.deep.equal [
+      (filteredByQuery withQuery 'c').should.deep.equal [
         {name: 'c'}
         {name: 'cool'}
       ]


use wrapper div to fetch the preview size

--- lib/generators/templates/carrierwave_cropper.js.coffee
+++ lib/generators/templates/carrierwave_cropper.js.coffee
@@ -20,8 +20,8 @@
     $('#<%= file_name %>_<%= attachment_name %>_crop_h').val(coords.h)

   updatePreview: (coords) =>
-    zoom_factor_x = $('#<%= file_name %>_<%= attachment_name %>_previewbox').width()  / coords.w
-    zoom_factor_y = $('#<%= file_name %>_<%= attachment_name %>_previewbox').height() / coords.h
+    zoom_factor_x = $('#<%= file_name %>_<%= attachment_name %>_previewbox_wrapper').width()  / coords.w
+    zoom_factor_y = $('#<%= file_name %>_<%= attachment_name %>_previewbox_wrapper').height() / coords.h
     $('#<%= file_name %>_<%= attachment_name %>_previewbox').css
       width:  Math.round(zoom_factor_x * $('#<%= file_name %>_<%= attachment_name %>_cropbox').width())  + 'px'
       height: Math.round(zoom_factor_y * $('#<%= file_name %>_<%= attachment_name %>_cropbox').height()) + 'px'


Render only top and bottom levels of nested repost

--- assets/javascripts/views/post/repost.js.coffee
+++ assets/javascripts/views/post/repost.js.coffee
@@ -6,19 +6,21 @@
   constructor: ->
     super

-    parent_post = @parentPost()
+    @fetchPost()

+  parentPost: =>
+    TentStatus.Models.Post.instances.all[@parent_view.post_cid]
+
+  fetchPost: (parent_post = @parentPost()) =>
     TentStatus.Models.Post.find { id: parent_post.get('content.id'), entity: parent_post.get('content.entity') }, {
       success: (post) =>
+        return @fetchPost(post) if post.isRepost()
         @post_cid = post.cid
         @render(@context(post))

       error: (res, xhr) =>
         console.log 'Repost:fetch:failed', res
     }
-
-  parentPost: =>
-    TentStatus.Models.Post.instances.all[@parent_view.post_cid]

   post: =>
     TentStatus.Models.Post.instances.all[@post_cid]


add zero

add zero

--- atmospheric/002-accumulated-heat/viewer.coffee
+++ atmospheric/002-accumulated-heat/viewer.coffee
@@ -82,8 +82,8 @@
         [num, len, heats] = heatdata
         for col in [0...num]
             cur = num * col
-            for idx in [1...num]
-                value = sc / 10 / num * idx
+            for idx in [0...num]
+                value = sc / 5 / num * idx
                 points = find(heats, cur, cur + num - 1, value)
                 for row in points
                     [x, y] = positioning(lng(col), lat(row))


menu closes on browser resize

menu closes on browser resize

--- app/assets/javascripts/static_pages.js.coffee
+++ app/assets/javascripts/static_pages.js.coffee
@@ -30,7 +30,16 @@
 moveEvent = (if (touch) then "touchmove" else "mousemove")
 endEvent = (if (touch) then "touchend" else "mouseup")

+changeMenuWidth = (e) ->
+	$(".menu").removeClass("menu-open-mobile");
+	$('.siteContainer').removeClass("site-open-mobile");
+	$(".menu").removeClass("menu-open-browser");
+	$('.siteContainer').removeClass("site-open-browser");
+
 jQuery ->
+
+	#Closes the menu when resizing in the browser
+	$(window).bind("resize", changeMenuWidth)

 	# Menu Open/Close Button
 	$('.btn-menu').click ->


Insert Show more results as a jQuery menu item last in list

--- app/assets/javascripts/users_search.js.coffee
+++ app/assets/javascripts/users_search.js.coffee
@@ -2,6 +2,7 @@
   # Autocomplete on employee search
   $queryEmployee = $("#query-employee")
   if $queryEmployee.length
+    items = 0
     $queryEmployee.autocomplete
       source: (request, response) ->
         $.ajax
@@ -12,17 +13,22 @@
           timeout: 5000
           success: (data) ->
             if data.length
+              items = data.length
               response $.map data, (item) ->
                 item
       minLength: 2
       select: (event, ui) ->
-        if ui.item and "path" of ui.item
+        if ui.item.path is "full-search"
+          $queryEmployee.closest("form").submit()
+        else
           document.location = ui.item.path
-
     .data("ui-autocomplete")._renderItem = (ul, item) ->
+      # Create item for full search if it is the last item
+      $more = ""
+      if items is ul.find("li").length + 1
+        $more = $("
Visa fler resultat
")
+          .data("ui-autocomplete-item", { path: "full-search"})
       ul.addClass('search_users')
-      if $queryEmployee.hasClass("full-search")
-        ul.addClass('full-search')
       $("
")
         .data("ui-autocomplete-item", item)
         .append("
@@ -30,8 +36,7 @@
             #{item.company_short}

             #{item.department}

         ")
-        .appendTo(ul)
-
+        .appendTo(ul).append($more)

   # Search results, load more
   $("section.index.users").on "click", ".load-more input", (event) ->


Fixed spliting pane in certain pane arrangement breaks Atom

Fixes #1

--- lib/coffee-compile.coffee
+++ lib/coffee-compile.coffee
@@ -1,4 +1,4 @@
-url = require 'url'
+url         = require 'url'
 querystring = require 'querystring'

 CoffeeCompileView = require './coffee-compile-view'
@@ -17,7 +17,9 @@
       new CoffeeCompileView(pathname)

   display: ->
-    editor = atom.workspace.getActiveEditor()
+    editor     = atom.workspace.getActiveEditor()
+    activePane = atom.workspace.getActivePane()
+
     return unless editor?

     unless editor.getGrammar().scopeName is "source.coffee"
@@ -25,7 +27,7 @@
       return

     range = editor.getSelectedBufferRange()
-    code =
+    code  =
       if range.isEmpty()
         editor.getText()
       else
@@ -33,7 +35,12 @@

     uri = "coffeecompile://#{editor.getPath()}"

-    atom.workspace.open(uri, split: 'right', changeFocus: true, searchAllPanes: true).done (coffeeCompileView) ->
+    # If a pane with the uri
+    pane = atom.workspace.paneContainer.paneForUri uri
+    # If not, always split right
+    pane ?= activePane.splitRight()
+
+    atom.workspace.openUriInPane(uri, pane, {}).done (coffeeCompileView) ->
       if coffeeCompileView instanceof CoffeeCompileView
         coffeeCompileView.setCode(code)
         coffeeCompileView.renderCompiled()


Use default empty message when items exist

--- lib/diff-list-view.coffee
+++ lib/diff-list-view.coffee
@@ -6,8 +6,11 @@
     super
     @addClass('diff-list-view overlay from-top')

-  getEmptyMessage: ->
-    'No diffs'
+  getEmptyMessage: (itemCount) ->
+    if itemCount is 0
+      'No diffs in file'
+    else
+      super

   getFilterKey: ->
     'lineText'


Explicitly focus editor when file finder is closed.

Trying to catch focusout events on anything other than the editor was really error-prone and ultimately more abstraction than we need right now. This fixes a bug where we would lose the ability to input text after clicking on the editor element.

--- src/atom/root-view.coffee
+++ src/atom/root-view.coffee
@@ -28,11 +28,6 @@
     @on 'toggle-file-finder', => @toggleFileFinder()
     @on 'show-console', -> window.showConsole()

-    @on 'focusout', (e) =>
-      # if anything but the main editor's hidden input loses focus, restore focus to the main editor
-      unless @editor.containsElement($(e.target))
-        @editor.focus()
-
   createProject: (url) ->
     if url
       @project = new Project(fs.directory(url))
@@ -47,6 +42,7 @@
     if @fileFinder and @fileFinder.parent()[0]
       @fileFinder.remove()
       @fileFinder = null
+      @editor.focus()
     else
       @project.getFilePaths().done (paths) =>
         relativePaths = (path.replace(@project.url, "") for path in paths)


clean ugly line of code

clean ugly line of code

--- lib/geekywalletlib.coffee
+++ lib/geekywalletlib.coffee
@@ -61,8 +61,9 @@
         line.computed.given[payer.name] = payer.amount
         line.computed.spent[payer.name] ?= 0
       # compute balance
-      line.computed.balance[person] = line.computed.given[person] - line.computed.spent[person] for own person, val of line.computed.spent
-
+      for own person, val of line.computed.spent
+        do (person) =>
+          line.computed.balance[person] = line.computed.given[person] - line.computed.spent[person]
       # return line object
       line


Remove grafana.coffee

Remove grafana.coffee

Proposing the deprecation of this in favor of the [hubot-grafana](https://www.npmjs.com/package/hubot-grafana) package, which offers cross-adapter support, dashboard browsing and can be augmented with Amazon S3 credentials.

Fixes #2206

--- vendor/grafana.coffee
+++ vendor/grafana.coffee
@@ -1,60 +0,0 @@
-# Description:
-#   A way to interact with the Google Images API.
-#
-# Commands:
-#   hubot image me <query> - The Original. Queries Google Images for <query> and returns a random top result.
-#   hubot animate me <query> - The same thing as `image me`, except adds a few parameters to try to return an animated GIF instead.
-#   hubot mustache me <url> - Adds a mustache to the specified URL.
-#   hubot mustache me <query> - Searches Google Images for the specified query and mustaches it.
-
-module.exports = (robot) ->
-  robot.hear /grafana (.*)/i, (msg) ->
-    sendUrl msg.match[1]
-
-  robot.router.get '/hubot/test', (req, res) ->
-    sendUrl()
-    res.send 'OK '
-
-imageMe = (msg, cb) ->
-  cb 'http://localhost:3000/render/dashboard/solo/grafana-play-home?from=now-1h&to=now&panelId=4&fullscreen'
-
-sendUrl = (params) ->
-  https = require 'https'
-  querystring = require 'querystring'
-  opts = params.split(' ')
-  dashboard = opts[0]
-  panelId = opts[1]
-  from = opts[2]
-
-  imageUrl = "http://localhost:3000/render/dashboard/solo/#{dashboard}/?panelId=#{panelId}"
-  link = "http://localhost:3000/dashboard/db/#{dashboard}/?panelId=#{panelId}&fullscreen"
-  if from
-    imageUrl += "&from=#{from}"
-    link += "&from=#{from}"
-
-  console.log 'imageUrl: ' + imageUrl
-
-  hipchat = {}
-  hipchat.format = 'json'
-  hipchat.auth_token = process.env.HUBOT_HIPCHAT_TOKEN
-  console.log 'token: ' + hipchat.auth_token
-
-  hipchat.room_id = '877465'
-  hipchat.message = ""
-  hipchat.from = "hubot"
-  hipchat.message_format = "html"
-
-  params = querystring.stringify(hipchat)
-
-  path = "/v1/rooms/message/?#{params}"
-
-  data = ''
-
-  https.get {host: 'api.hipchat.com', path: path}, (res) ->
-      res.on 'data', (chunk) ->
-          data += chunk.toString()
-      res.on 'end', () ->
-          json = JSON.parse(data)
-          console.log "Hipchat response ", data
-
-


be more careful about fixing the language mapping if it's broken

--- lib/kernel-manager.coffee
+++ lib/kernel-manager.coffee
@@ -66,10 +66,12 @@
     getLanguageMappings: ->
         try
             languageMappings = JSON.parse atom.config.get('Hydrogen.languageMappings')
+            return languageMappings
         catch error
             console.error error
             languageMappings = {}
-
+            return languageMappings
+
     getKernelInfoForLanguage: (language) ->
         kernels = @getAvailableKernels()
         console.log "Available kernels:", kernels


Wait for packages loaded event before starting specs

--- spec/app/package-config-panel-spec.coffee
+++ spec/app/package-config-panel-spec.coffee
@@ -30,7 +30,12 @@
     observeSubscription = config.observe('core.disabledPackages', configObserver)
     config.set('core.disabledPackages', ['p1', 'p3'])
     configObserver.reset()
+    jasmine.unspy(window, "setTimeout")
     panel = new PackageConfigPanel
+
+    installedCallback = jasmine.createSpy("installed packages callback")
+    panel.installed.on("installed-packages-loaded", installedCallback)
+    waitsFor -> installedCallback.callCount > 0

   describe 'Installed tab', ->
     it "lists all installed packages with a link to enable or disable the package", ->


fixed the ebook specs so they used the new packageDocument constructor

--- spec/javascripts/models/ebook_spec.coffee
+++ spec/javascripts/models/ebook_spec.coffee
@@ -6,13 +6,14 @@

 		beforeEach ->
 			stubFileSystem()
+

 		describe "without passing a file path", ->

 			it "throws an exeption", ->
 				createWithNoPath = -> new Readium.Models.Ebook()
-				expect(createWithNoPath).toThrow("Cannot sync the model to the fs without a path")
+				expect(createWithNoPath).toThrow("This class cannot be synced without a file path")

 		describe "with valid params", ->
@@ -26,7 +27,7 @@
 				expect(ebook.packageDocument).toBeDefined()

 			it 'passes a reference to itself to the package document', ->
-				packDoc = new Readium.Models.PackageDocument({book: {}}, {"file_path": "some/path"})
+				packDoc = new Readium.Models.PackageDocument({book: {}, "file_path": "some/path"})
 				spyOn(Readium.Models, "PackageDocument").andReturn(packDoc)
 				ebook = new Readium.Models.Ebook({"package_doc_path": "some/file/path"})
 				args = Readium.Models.PackageDocument.mostRecentCall.args
@@ -34,7 +35,7 @@

 			it "calls fetch on the package document", ->
-				packDoc = new Readium.Models.PackageDocument({book: {}}, {"file_path": "some/path"})
+				packDoc = new Readium.Models.PackageDocument({book: {}, "file_path": "some/path"})
 				spyOn(Readium.Models, "PackageDocument").andReturn(packDoc)
 				spyOn(packDoc, "fetch")
 				ebook = new Readium.Models.Ebook({"package_doc_path": "some/file/path"})


Add Debug for TravisCi

Add Debug for TravisCi

--- spec/linter-mypy_project_name-spec.coffee
+++ spec/linter-mypy_project_name-spec.coffee
@@ -34,19 +34,19 @@

       # Create a project root (aka isolated temp folder)
       @ProjectRoot = path.join(os.tmpdir(), "linter-mypy" + Math.floor(Math.random() * 9007199254740991))
+      console.log("Project root: " + @ProjectRoot)

       #Simulate a pseudo project #1 containing one file
       @firstProjectName = 'first_python_project'
       @firstProjectPath = path.join(@ProjectRoot, @firstProjectName)
       @firstFilePath = path.join(@firstProjectPath, 'src', 'first_file.py')
+      console.log("FirstFilePath: " + @firstFilePath)

       #Simulate a pseudo project #2 containing one file
       @secondProjectName = 'second_python_project'
       @secondProjectPath = path.join(@ProjectRoot, @secondProjectName)
       @secondFilePath = path.join(@secondProjectPath, 'src', 'second_file.py')
-
-      # Open the two projects
-      atom.project.setPaths([@firstProjectPath, @secondProjectPath])
+      console.log("SecondFilePath: " + @secondFilePath)

       # Note: The method atom.project.relativizePath(filepath) which is internally use by the tested method requires the folders to exist on HD)
       fs.mkdirSync(@ProjectRoot)
@@ -58,6 +58,9 @@
       # Note: The files do not need to be created on HD.
       #fs.closeSync(fs.openSync(@secondFilePath, 'w'))
       #fs.closeSync(fs.openSync(@firstFilePath, 'w'))
+
+      # Open the two projects
+      atom.project.setPaths([@firstProjectPath, @secondProjectPath])

     afterEach ->
       # Remove the file/folders created on the hard drive.
@@ -76,6 +79,7 @@
     it "should return the first project's name when given the variable", ->
       firstProjectName = @firstProjectName
       loadedFile = @firstFilePath
+      console.log("loadedFile: " + loadedFile)
       result = LinterMyPystyle.resolvePath("$PROJECT_NAME", loadedFile)
       # Return this project's name (i.e. first_python_project)
       expect(result).toBe(firstProjectName)
@@ -83,6 +87,7 @@
     it "should return the second project's name when given the variable", ->
       secondProjectName = @secondProjectName
       loadedFile = @secondFilePath
+      console.log("loadedFile: " + loadedFile)
       result = LinterMyPystyle.resolvePath("$PROJECT_NAME", loadedFile)
       # Return this project's name (i.e. second_python_project)
       expect(result).toBe(secondProjectName)
@@ -91,6 +96,7 @@
       loadedFile = @firstFilePath
       targetPath = '/home/user/.virtualenvs/$PROJECT_NAME/bin/python'
       expectedPath = '/home/user/.virtualenvs/first_python_project/bin/python'
+      console.log("loadedFile: " + loadedFile)
       result = LinterMyPystyle.resolvePath(targetPath, loadedFile)
       expect(result).toBe(expectedPath)

@@ -98,6 +104,7 @@
       loadedFile = @secondFilePath
       targetPath = '/home/user/.virtualenvs/$PROJECT_NAME/bin/python'
       expectedPath = '/home/user/.virtualenvs/second_python_project/bin/python'
+      console.log("loadedFile: " + loadedFile)
       result = LinterMyPystyle.resolvePath(targetPath, loadedFile)
       expect(result).toBe(expectedPath)


cleaned up some. commented some. now returns focus to the input after user clicks on a suggestion.

--- src/bootstrap-tags.coffee
+++ src/bootstrap-tags.coffee
@@ -1,6 +1,11 @@
 # Bootstrap Tags
 # Max Lahey
 # Monday, November 12, 2012
+
+# todos
+# 1) make suggestions list scrollable
+# 2) popovers for tag data
+# 3) ajax for tags, suggestions, and restrictions

 jQuery ->
   $.tags = (element, options) ->
@@ -9,6 +14,9 @@

     @suggestions = options.suggestions
     @restrictTo = (if options.restrictTo? then options.restrictTo.concat @suggestions else false)
+
+    @tagsArray = []
+    @tagData = ""

     @$element = $ element

@@ -21,18 +29,18 @@
     @removeLastTag = =>
       el = $('.tag', @$element).last()
       el.remove()
-      @removeTag @tags[@tags.length-1]
+      @removeTag @tagsArray[@tagsArray.length-1]

     @removeTag = (tag) =>
-      @tags.splice(@tags.indexOf(tag), 1)
-      $('.tag-data', @$element).html @tags.join(',')
-      @renderTags @tags
+      @tagsArray.splice(@tagsArray.indexOf(tag), 1)
+      @tagData = @tagsArray.join(',')
+      @renderTags @tagsArray

     @addTag = (tag) =>
-      if (@restrictTo == false or @restrictTo.indexOf(tag) != -1) and $('.tag-data', @$element).html().indexOf(tag) < 0
-        @tags.push tag
-        $('.tag-data', @$element).html @tags.join(',')
-        @renderTags @tags
+      if (@restrictTo == false or @restrictTo.indexOf(tag) != -1) and @tagData.indexOf(tag) < 0
+        @tagsArray.push tag
+        @tagData = @tagsArray.join(',')
+        @renderTags @tagsArray

     # toggles remove button color for a tag when moused over or out
     @toggleCloseColor = (e) ->
@@ -41,7 +49,6 @@
       tagAnchor.css color:color

     # Key handlers
-
     @keyDownHandler = (e) =>
       k = (if e.keyCode? then e.keyCode else e.which)
       switch k
@@ -51,7 +58,7 @@
             tag = @suggestionList[@suggestedIndex]
           @addTag tag
           e.target.value = ''
-          @renderTags @tags
+          @renderTags @tagsArray
         when 46, 8 # delete
           if e.target.value == ''
             @removeLastTag()
@@ -66,14 +73,15 @@
           @selectSuggested @suggestedIndex
         else

+    # makeSuggestions creates auto suggestions that match the value in the input
+    # if overrideLengthCheck is set to true, then if the input value is empty (''), return all possible suggestions
     @makeSuggestions = (e, overrideLengthCheck) =>
       val = (if e.target.value? then e.target.value else e.target.textContent)
       @suggestedIndex = -1
       @$suggestionList.html ''
       @suggestionList = []
       $.each @suggestions, (i, suggestion) =>
-        current = $('.tag-data', @$element).html()
-        if current.indexOf(suggestion) < 0 and suggestion.substring(0, val.length) == val and (val.length > 0 or overrideLengthCheck)
+        if @tagData.indexOf(suggestion) < 0 and suggestion.substring(0, val.length) == val and (val.length > 0 or overrideLengthCheck)
           @$suggestionList.append '
'+suggestion+'
'
           @suggestionList.push suggestion
       $('.tags-suggestion', @$element).mouseover @selectSuggestedMouseOver
@@ -86,6 +94,7 @@
       @addTag tag
       @input.val ''
       @makeSuggestions e, false
+      @input.focus() # return focus to input so user can continue typing

     @keyUpHandler = (e) =>
       k = (if e.keyCode? then e.keyCode else e.which)
@@ -120,29 +129,29 @@
       @$element.css height : pBottom

     @renderTags = (tags) =>
-      el = $('.tags',@$element)
-      el.html('')
+      tagList = $('.tags',@$element)
+      tagList.html('')
       $.each tags, (i, tag) =>
         tag = $(@formatTag i, tag)
-        # tag.on "click", @alert
-        $('a', tag).on "click", @removeTagClicked
-        $('a', tag).on "mouseover", @toggleCloseColor
-        $('a', tag).on "mouseout", @toggleCloseColor
-        el.append tag
+        $('a', tag).click @removeTagClicked
+        $('a', tag).mouseover @toggleCloseColor
+        $('a', tag).mouseout @toggleCloseColor
+        tagList.append tag
       @adjustInputPosition()

     @formatTag = (i, tag) ->
-      markup = "
"+tag+" X
"
+      "
"+tag+" X
"

     @init = ->
-      @tags = $('.tag-data', @$element).html().split ','
+      @tagData = $('.tag-data', @$element).html()
+      @tagsArray = @tagData.split ','
       @input = $ "<input class='tags-input'>"
+      @input.keydown @keyDownHandler
+      @input.keyup @keyUpHandler
       @$element.append @input
       @$suggestionList = $ '

'
       @$element.append @$suggestionList
-      @renderTags @tags
-      @input.keydown @keyDownHandler
-      @input.keyup @keyUpHandler
+      @renderTags @tagsArray

     @init()


added new createSymlinks to handle paths that start with ~

--- app/templates/Gruntfile.coffee
+++ app/templates/Gruntfile.coffee
@@ -340,8 +340,15 @@

 	grunt.registerMultiTask 'createSymlinks', 'Creates a symlink', ->
 		for own index, config of this.data
-			src = if config.src[0] isnt '/' then process.cwd() + '/' + config.src else config.src
-			dest = if config.dest[0] isnt '/' then process.cwd() + '/' + config.dest else config.dest
+
+			src = config.src
+			dest = config.dest
+
+			src = process.env.HOME + src.substr(1) if src[0] is '~'
+			dest = process.env.HOME + dest.substr(1) if dest[0] is '~'
+
+			src = process.cwd() + '/' + src if src[0] isnt '/'
+			dest = process.cwd() + '/' + dest if dest[0] isnt '/'

 			grunt.log.writeln 'ERROR: source dir does not exist!' if not fs.existsSync(src) # Without a source, all is lost.


clarify comments about grunt

clarify comments about grunt

--- app/templates/_Gulpfile.coffee
+++ app/templates/_Gulpfile.coffee
@@ -260,14 +260,15 @@
 #*------------------------------------*\
 #   $DEV UPDATE
 #*------------------------------------*/
-# No exsisting gulp task runner
+# No exsisting gulp task implemented

 #*------------------------------------*\
 #   $TASKS
 #*------------------------------------*/
 gulp.task 'default', ['watch']
 gulp.task "build", ["uglify", "minify"]
+
+# No exsisting gulp task implemented
 # grunt's devUpdate:check alternative
 # grunt's devUpdate:ask alternative
 # grunt's devUpdate:up alternative
-


code cleanup

code cleanup

--- client/landing/app/Applications/Dashboard.kdapplication/views/billingmethodview.coffee
+++ client/landing/app/Applications/Dashboard.kdapplication/views/billingmethodview.coffee
@@ -21,7 +21,7 @@
   getCardInfoPartial: (billingInfo) ->
     if billingInfo
       { cardFirstName, cardLastName, cardNumber, cardMonth, cardYear
-      cardType, address1, address2, city, state, zip } = billingInfo
+        cardType, address1, address2, city, state, zip } = billingInfo
       """

#{cardFirstName} #{cardLastName}

#{cardNumber} - #{cardMonth}/#{cardYear} (#{cardType})


default-apps: Added createExtensionToAppMap and fetchUserDefaultAppConfig methods to KodingAppCcontroller.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -27,6 +27,9 @@
     @kiteController = @getSingleton "kiteController"
     @manifests      = KodingAppsController.manifests
     @getPublishedApps()
+
+    @createExtensionToAppMap()
+    @fetchUserDefaultAppConfig()

   getAppPath:(manifest, escaped=no)->

@@ -598,6 +601,29 @@
             @appManager.open manifest.name
             modal.destroy()
             @skipUpdate = no
+
+  createExtensionToAppMap: ->
+    @extensionToApp = map = {}
+    @fetchApps (err, res) =>
+      # TODO: Find a better way to add Ace extensions to this map. (fatihacet)
+      res.Ace =
+        name     : "Ace"
+        fileTypes: [
+          "php", "pl", "py", "jsp", "asp", "aspx", "htm", "html", "phtml","shtml",
+          "sh", "cgi", "htaccess","fcgi","wsgi","mvc","xml","sql","rhtml", "diff",
+          "js","json", "coffee", "css","styl","sass", "scss", "less", "txt"
+        ]
+      for key, app of res
+        fileTypes = app.fileTypes
+        if fileTypes
+          for type in fileTypes
+            map[type] = [] unless map[type]
+            map[type].push app.name
+
+  fetchUserDefaultAppConfig: ->
+    appConfigStorage = new AppStorage "DefaultAppConfig", "1.0"
+    appConfigStorage.fetchStorage (storage) ->
+      log storage

   defaultManifest = (type, name)->
     {profile} = KD.whoami()


ApplicationManager: fixed switching between same appinstances from maintabhandles

--- client/app/MainApp/maintabs/maintabview.coffee
+++ client/app/MainApp/maintabs/maintabview.coffee
@@ -57,7 +57,7 @@
     # this is a temporary fix for third party apps
     # until router handles everything correctly
     if options.route is '/Develop'
-      appManager.show KD.getAppOptions options.name
+      appManager.showInstance appInstance

   showHandleContainer:()->
     @tabHandleContainer.$().css top : -25


Changes based on reviews in merge request #767

--- client/landing/app/MainApp/LocalStorage.coffee
+++ client/landing/app/MainApp/LocalStorage.coffee
@@ -1,14 +1,14 @@
 class LocalStorage extends AppStorage

-  fetchStorage:(callback=noop)->
-    callback window["localStorage"][@getSignature()]
+  storage = window.localStorage
+
+  fetchStorage:->
     KD.utils.defer => @emit "ready"

   getValue: (key)->
     data = @_storageData[key]
     return data  if data
-
-    data = window["localStorage"][@getSignature key]
+    data = storage[@getSignature key]
     if data
       try
         data = JSON.parse data
@@ -37,12 +37,12 @@
     callback? @getValue key

   setValue: (key, value, callback)->
-    @_storageData[key] = value
-    window["localStorage"][@getSignature key] = JSON.stringify value
+    @_storageData[key] = value or ''
+    storage[@getSignature key] = (JSON.stringify value) or ''
     KD.utils.defer => callback? null

   unsetKey: (key, callback)->
-    delete window["localStorage"][@getSignature key]
+    delete storage[@getSignature key]
     delete @_storageData[key]

   getSignature:(key)->


RNG gods are not on my side

RNG gods are not on my side

--- modules/seal.coffee
+++ modules/seal.coffee
@@ -16,7 +16,7 @@
         .replace 'jpeg', 'jpg'
         .replace 'animatedgif', 'gif'
         name = image.Title + '.' + ext
-        if chance.integer { min: 0, max: 100 } > 2
+        if chance.integer { min: 0, max: 100 } > 10
           # Send the pic
           msg.channel.uploadFile request(image.MediaUrl), name
         else


Tweaked markdown regex for [text](URI) to ignore close parethesis characters inside the URI


Client: Activity: Deleted unused component reference

--- client/activity/lib/components/chatlistitem/simplechatlistitem.coffee
+++ client/activity/lib/components/chatlistitem/simplechatlistitem.coffee
@@ -1,6 +1,5 @@
 kd                   = require 'kd'
 React                = require 'kd-react'
-TimeAgo              = require 'app/components/common/timeago'
 MessageBody          = require 'activity/components/common/messagebody'
 ButtonWithMenu       = require 'app/components/buttonwithmenu'
 ChatListItem         = require 'activity/components/chatlistitem'


fixed positioning of the activity header: issue was: height was not updated correctly

--- client/landing/app/Applications/Activity.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppView.coffee
@@ -43,8 +43,7 @@
     @_windowDidResize()

   setFixed:->
-    {groupEntryPoint:entryPoint} = KD.config
-    pre = if @getScrollTop() > headerHeight and not entryPoint then "set" else "unset"
+    pre = if @getScrollTop() > headerHeight = @header.getHeight() then "set" else "unset"
     @["#{pre}Class"] "fixed"

   navigateHome:(itemData)->


Doh. Initially hide the progress bar.

Doh. Initially hide the progress bar.

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -174,7 +174,7 @@
     rootView.openSingletonSync(ResultsPaneView.URI, options)

   clearMessages: ->
-    # @replacmentInfoBlock.hide()
+    @replacmentInfoBlock.hide()
     @errorMessages.hide().empty()
     @infoMessages.hide().empty()


Remove console.log .

Remove console.log .

--- lib/login7-payload.coffee
+++ lib/login7-payload.coffee
@@ -84,7 +84,6 @@
     length = buildBuffer('U32', lengthLength + fixed.length + variable.length)

     @data = new Buffer(length.concat(fixed, variable))
-    console.log(@data.length, @data)

   createFixedData: ->
     @tdsVersion = DEFAULT_TDS_VERSION


Fix box selection after layout changes

Fix box selection after layout changes

--- bokehjs/src/coffee/tool/box_select_tool.coffee
+++ bokehjs/src/coffee/tool/box_select_tool.coffee
@@ -84,7 +84,7 @@
       if @select_every_mousemove
         @_select_data()

-      @plot_view._render_levels(@plot_view.ctx, ['overlay'], true)
+      @plot_view._render_levels(@plot_view.canvas_view.ctx, ['overlay'])
       return null

     _dragend : () ->


adding logging, saving vote to brain

adding logging, saving vote to brain

--- src/poll.coffee
+++ src/poll.coffee
@@ -53,9 +53,10 @@
 			msg.send error
 			@robot.logger.info error

-	robot.respond /vote\s+(\d)/i, (msg) ->
+	robot.respond /vote\s+(\d)/i, (msg) ->
 		vote = msg.match[1]
 		user = msg.user
+		@robot.logger.info "Adding a vote for #{vote} for #{user}"
 		#check if the user already voted
 		for option in @robot.brain.data.poll.options
 			if option.users != undefined
@@ -65,9 +66,12 @@
 		else
 			@robot.brain.data.poll.options[vote].users = []
 			@robot.brain.data.poll.options[vote].users.push(user)
+		@robot.brain.save()
+		@robot.logger.info "Brain saved"
 		msg.reply "Thanks for your vote"

 	robot.respond /view results/i, (msg) ->
+		@robot.logger.info "View results"
 		topic = @robot.brain.data.poll.topic
 		options = @robot.brain.data.poll.options
 		optionString = ""
@@ -76,7 +80,8 @@
 			text = option.text
 			numVotes = if option.users == undefined then 0 else option.users.length
 			users = option.users
-			optionString += "#{index}: #{text} Total: #{numVotes} Users:(#{users.join(", ")})"
+			@robot.logger.info "option: #{option} :: text: #{text} :: count: #{numVotes}"
+			optionString += "\t#{index}: #{text} Total: #{numVotes} Users:(#{users.join(", ")})\n"
 		msg.send """
 		#{topic}
 		Current Poll Results:


apidocs bugfix

apidocs bugfix

--- apidocs/Gruntfile.coffee
+++ apidocs/Gruntfile.coffee
@@ -24,7 +24,7 @@
         description: ''
         version: '1.0.0'
       schemes: ['http']
-      basePath: '/'
+      basePath: '/event/api'
       produces: ['application/json']
       consumes: ['application/json']
       paths: {}


revert previous commit, defer to 0.3

revert previous commit, defer to 0.3

--- subtree/bokehjs/static/coffee/common/plot.coffee
+++ subtree/bokehjs/static/coffee/common/plot.coffee
@@ -33,7 +33,6 @@
 #requestAnimationFrame, in addition the throttled function will be run
 #no more frequently than request animation frame allow
 #
-window.plot_count = 0
 throttleAnimation = (func, wait) ->
   [context , args, timeout, result] = [null,null,null,null]
   previous = 0
@@ -89,13 +88,11 @@
       @request_render()

   request_render : () ->
-    #console.log("request render", @plotnum)
     if not @is_paused
       @throttled_render()
     return

   request_render_canvas : (full_render) ->
-    #console.log("request_render_canvas", @plotnum)
     if not @is_paused
       @throttled_render_canvas(full_render)
     return
@@ -106,9 +103,6 @@
     #@throttled_render = _.throttle(@render, 15)
     @throttled_render = throttleAnimation(@render, 15)
     @throttled_render_canvas = throttleAnimation(@render_canvas, 15)
-    @plotnum = window.plot_count
-    window.plot_count+=1
-    window['plot'+@plotnum]=@
     @title_props = new text_properties(@, {}, 'title_')

     @view_state = new ViewState({
@@ -224,34 +218,10 @@
     return [x, y]

   update_range : (range_info) ->
-    contexts = _.pluck(@x_range._events.change, "ctx")
-    contexts = contexts.concat(_.pluck(@y_range._events.change, "ctx"))
-    listener_fs = _.pluck(@x_range._events.change, "callback")
-    listener_fs = listener_fs.concat(_.pluck(@y_range._events.change, "callback"))
-
-    uniq_listener_fs =_.uniq(listener_fs)
-    uniq_contexts = _.uniq(contexts)
-
-    if not uniq_listener_fs.length == 0
-      #throw an error, this is only going to work if all the contexts
-      #are the same object type
-      console.log('throwing an error')
-      1/0
     @pause()
-    for ctx in uniq_contexts
-      ctx.pause()
-    @x_range.set(range_info.xr, {silent:true})
-    @y_range.set(range_info.yr, {silent:true})
-
-    f = uniq_listener_fs[0]
-    for ctx in uniq_contexts
-      ctx.is_paused = false
-      try
-        ctx.xmapper.properties.mapper_state.callbacks.propchange()
-        ctx.ymapper.properties.mapper_state.callbacks.propchange()
-      catch err
-        "somehow some listneres don't have x or ymappers'"
-      ctx.request_render()
+    @x_range.set(range_info.xr)
+    @y_range.set(range_info.yr)
+    @unpause()

   build_tools: () ->
     return build_views(@tools, @mget_obj('tools'), @view_options())
@@ -346,9 +316,6 @@

   render: (force) ->
     super()
-    window.render_count +=1
-    #console.log("render #{@plotnum}", window.render_count)
-
     #newtime = new Date()
     # if @last_render
     #   console.log(newtime - @last_render)
@@ -404,7 +371,6 @@
       @old_mapper_state.y = yms
       have_new_mapper_state = true

-
     @ctx.save()

     @ctx.beginPath()
@@ -432,6 +398,7 @@
       sy = th
       @title_props.set(@ctx, {})
       @ctx.fillText(title, sx, sy)
+

 class PNGView extends ContinuumView


:bug: fix #120

:bug: fix #120

--- lib/selection-wrapper.coffee
+++ lib/selection-wrapper.coffee
@@ -116,7 +116,7 @@
     else
       [tail, head]
     [start.row, end.row] = @selection.getBufferRowRange()
-    @setBufferRange([start, end])
+    @setBufferRange([start, end], {preserveFolds: true})
     if @selection.isReversed()
       @reverse()
       @selection.selectRight()


Modal was not passed in parameters to GoalController

--- results/front/app/coffee/controllers/main.coffee
+++ results/front/app/coffee/controllers/main.coffee
@@ -224,7 +224,7 @@
 #module.controller("ContainerController", ["$scope", "$rootScope", "$routeParams", "resource", ContainerController])
 module.controller("TooltipController", ["$scope", "$document", TooltipController])
 module.controller("LoginController", ["$scope","$rootScope", "$location", "$routeParams", "resource", "$gmAuth", LoginController])
-module.controller("GoalController", ["$scope","$rootScope","$location", "$routeParams", "$model", "resource", GoalController])
+module.controller("GoalController", ["$scope","$rootScope","$location", "$routeParams", "$model", "$modal", "resource", GoalController])
 module.controller("GoalListController", ["$scope","$rootScope", "$location", "$model", "resource", GoalListController])
 module.controller("WinListController", ["$scope","$rootScope", "$location", "$model", "resource", WinListController])
 module.controller("PublicRegisterController", ["$scope", "$rootScope", "$location", "resource", "$gmAuth", PublicRegisterController])


Fixed handling of 'formats'; allowing '.' and '\'.

--- lib/plugins/router.coffee
+++ lib/plugins/router.coffee
@@ -22,20 +22,22 @@
                 obj[k].push v
         obj

+# produce regular expression from string
 normalize = (command, keys, sensitive) ->
     command = command
     .concat('/?')
     .replace(/\/\(/g, '(?:/')
-    .replace(/(\/)?(\.)?:(\w+)(\?)?/g, (_, slash, format, key, optional) ->
+    # regexp factors:
+    #    0. match a literal ':'
+    #    1. 'key': match 1 or more word characters followed by :
+    #    2. 'format': match anything inside (), should be a regexp factor
+    #       ie ([0-9]+)
+    #    3. 'optional': match an optional literal '?'
+    .replace(/:(\w+)(\(.*\))?(\?)?/g, (_, key, format, optional) ->
         keys.push key
-        slash ?= ''
-        ''.concat(
-            (if optional then '' else slash),
-            '(?:',
-            (if optional then slash else ''),
-            (format or '') + '([^/.]+))',
-            (optional or '')
-        )
+        format = format or '([^ ]+)' # provide default format
+        optional = optional or ''
+        return format + optional
     )
     .replace(/([\/.])/g, '\\$1')
     .replace(/\*/g, '(.+)')


sync: rm request.rsm.max clamp

sync: rm request.rsm.max clamp

--- src/sync.coffee
+++ src/sync.coffee
@@ -44,7 +44,6 @@
     constructor: ->
         super
         @request.rsm = new RSM.RSM()
-        @request.rsm.max = 3

     run: (t, cb) ->
         # for detecting RSM loops


fix(productImport): fetch all products for searched skus

--- lib/product-import.coffee
+++ lib/product-import.coffee
@@ -153,6 +153,7 @@
           @client.productProjections
           .where(predicate)
           .staged(true)
+          .all()
           .fetch()
           .then ({ body: { results } }) ->
             resolve(results)


added -> queued.

added -> queued.

--- avalonstar/assets/javascripts/notifier.coffee
+++ avalonstar/assets/javascripts/notifier.coffee
@@ -29,7 +29,7 @@
   ($ ".js-square-#{payload.action}").removeClass('visible')
   ($ '.js-square-flipper').removeClass('toggle')

-subscribed = (payload, resub, added) ->
+subscribed = (payload, resub, queued) ->
   if not running
     running = true

@@ -52,7 +52,7 @@
         console.log "There are #{poolSubscribing} subs left in the pool."
     ), 6900
   else
-    if not added
+    if not queued
       poolSubscribing++
       console.log "There are #{poolSubscribing} subs left in the pool."
     setTimeout (->
@@ -77,7 +77,7 @@
         console.log "There are #{poolDonating} donators left in the pool."
     ), 6900
   else
-    if not added
+    if not queued
       poolDonating++
       console.log "There are #{poolDonating} donators left in the pool."
     setTimeout (->


KDContentEditableView: trim content value

--- components/inputs/KDContentEditableView.coffee
+++ components/inputs/KDContentEditableView.coffee
@@ -47,7 +47,7 @@
       when "text" then value = element.textContent
       when "html" then value = element.innerHTML

-    if value is placeholder then "" else value
+    if value is placeholder then "" else value.trim()

   setContent: (content) ->
     {type, textExpansion} = @getOptions()


fixed weird style issue with user roles, there were css rules for .fatih and as the option value was Fatih, it took that as css class. added option to KDInputRadioGroup which accepts a cssClassPrefix with you can give a prefix to the value based css classes

--- client/landing/app/Applications/Groups.kdapplication/Views/groupsmemberroleseditview.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupsmemberroleseditview.coffee
@@ -33,9 +33,10 @@

     isAdmin = 'admin' in @roles.usersRole
     @checkboxGroup = new KDInputCheckboxGroup
-      name         : 'user-role'
-      defaultValue : @roles.usersRole
-      checkboxes   : @roles.allRoles.map (role)=>
+      name           : 'user-role'
+      cssClassPrefix : 'role-'
+      defaultValue   : @roles.usersRole
+      checkboxes     : @roles.allRoles.map (role)=>
         if role is 'admin'
           callback = =>
             isAdmin = 'admin' in @checkboxGroup.getValue()


log: include broker url

log: include broker url

--- client/Main/troubleshoot/brokerrecovery.coffee
+++ client/Main/troubleshoot/brokerrecovery.coffee
@@ -23,7 +23,8 @@
         @unsuccessfulAttempt++
         @emit "brokerNotResponding"

-        KD.utils.warnAndLog 'broker not responding', {@unsuccessfulAttempt}
+        brokerURL = @broker.sockURL.replace("/subscribe")
+        KD.utils.warnAndLog 'broker not responding', {@unsuccessfulAttempt, brokerURL}

       @broker.ping =>
         @unsuccessfulAttempt = 0


Disable default shortcuts

Disable default shortcuts

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -641,21 +641,21 @@
       description : 'Koding Terminal'
       author      : 'Koding'
       path        : 'WebTerm'
-    CodeMirror    :
-      name        : 'CodeMirror'
-      type        : 'comingsoon'
-      icon        : 'icn-codemirror.png'
-      description : 'Code Editor'
-      author      : 'Marijn Haverbeke'
-    yMacs         :
-      name        : 'yMacs'
-      type        : 'comingsoon'
-      icon        : 'icn-ymacs.png'
-      description : 'Code Editor'
-      author      : 'Mihai Bazon'
-    Pixlr         :
-      name        : 'Pixlr'
-      type        : 'comingsoon'
-      icon        : 'icn-pixlr.png'
-      description : 'Image Editor'
-      author      : 'Autodesk'
+    # CodeMirror    :
+    #   name        : 'CodeMirror'
+    #   type        : 'comingsoon'
+    #   icon        : 'icn-codemirror.png'
+    #   description : 'Code Editor'
+    #   author      : 'Marijn Haverbeke'
+    # yMacs         :
+    #   name        : 'yMacs'
+    #   type        : 'comingsoon'
+    #   icon        : 'icn-ymacs.png'
+    #   description : 'Code Editor'
+    #   author      : 'Mihai Bazon'
+    # Pixlr         :
+    #   name        : 'Pixlr'
+    #   type        : 'comingsoon'
+    #   icon        : 'icn-pixlr.png'
+    #   description : 'Image Editor'
+    #   author      : 'Autodesk'


Create appSkeleton for non-blank apps

Create appSkeleton for non-blank apps

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -293,6 +293,7 @@
               appScript = "var appView = KD.instances[\"#{appView.getId()}\"];\n\n"+appScript
               eval appScript
           catch e
+            # if not manifest.ignoreWarnings? # GG FIXME
             warn "App caused some problems:", e
           callback?()
           return appView
@@ -628,20 +629,13 @@
                           """
           , cb

-        # Uncomment followings when we have reachable files for skel of Apps
-        #
-        # stack.push (cb)=>
-        #   @kiteController.run
-        #     withArgs  :
-        #       command : "cp -f /opt/Apps/.default/README #{escapeFilePath fsFolder.path}"
-        #   , cb
-
-        # if not isBlank
-        #   stack.push (cb)=>
-        #     @kiteController.run
-        #       withArgs  :
-        #         command : "cp -rf /opt/Apps/.default/resources #{escapeFilePath fsFolder.path}"
-        #     , cb
+        # Copy default app files (app Skeleton)
+        if type isnt 'blank'
+          stack.push (cb)=>
+            @kiteController.run
+              method        : "copyAppSkeleton"
+              withArgs      : {appPath}
+              , cb

         async.parallel stack, (error, result) =>
           if err then warn err


Do not add all icons that we not provided in resources

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -26,15 +26,9 @@
             # post    : ""
       options       :
         type        : "tab"
-
-    if type is "sample"
-      raw.icns      =
-        "64"        : "./resources/icon.64.png"
+      icns          :
         "128"       : "./resources/icon.128.png"
-        "160"       : "./resources/icon.160.png"
-        "256"       : "./resources/icon.256.png"
-        "512"       : "./resources/icon.512.png"
-      raw.source.stylesheets = [ "./resources/style.css" ]
+      stylesheets   : [ "./resources/style.css" ]

     json = JSON.stringify raw, null, 2


Create a new app completed which uses os kite now.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -467,58 +467,49 @@
       newAppModal = null
       callback? yes

+  _createChangeLog:(name)->
+    today = new Date().format('yyyy-mm-dd')
+    {profile} = KD.whoami()
+    fullName = Encoder.htmlDecode "#{profile.firstName} #{profile.lastName}"
+
+    """
+     #{today} #{fullName} <@#{profile.nickname}>
+
+        * #{name} (index.coffee): Application created.
+    """
+
   prepareApplication:({isBlank, name}, callback)->

-    type        = if isBlank then "blank" else "sample"
-    name        = if name is "" then null else name
-    name        = name.replace(/[^a-zA-Z0-9\/\-.]/g, '') if name
-    manifestStr = defaultManifest type, name
-    manifest    = JSON.parse manifestStr
-    appPath     = @getAppPath manifest
+    type         = if isBlank then "blank" else "sample"
+    name         = if name is "" then null else name
+    name         = name.replace(/[^a-zA-Z0-9\/\-.]/g, '')  if name
+    manifestStr  = defaultManifest type, name
+    changeLogStr = @_createChangeLog name
+    manifest     = JSON.parse manifestStr
+    appPath      = @getAppPath manifest
     # log manifestStr

-    FSItem.create appPath, "folder", (err, fsFolder)=>
+    stack = []
+
+    manifestFile  = FSHelper.createFileFromPath "#{appPath}/manifest.json"
+    changeLogFile = FSHelper.createFileFromPath "#{appPath}/ChangeLog"
+
+    # Copy default app files (app Skeleton)
+    stack.push (cb)=>
+      @kiteController.run
+        method    : "app.skeleton"
+        withArgs  :
+          type    : if isBlank then "blank" else "sample"
+          appPath : appPath
+        , cb
+
+    stack.push (cb)=> manifestFile.save  manifestStr,  cb
+    stack.push (cb)=> changeLogFile.save changeLogStr, cb
+
+    async.parallel stack, (err, result) =>
       if err then warn err
-      else
-        stack = []
-        today = new Date().format('yyyy-mm-dd')
-        {profile} = KD.whoami()
-        fullName = Encoder.htmlDecode "#{profile.firstName} #{profile.lastName}"
-
-        stack.push (cb)=>
-          @kiteController.run
-            method      : "uploadFile"
-            withArgs    :
-              path      : escapeFilePath "#{fsFolder.path}/.manifest"
-              contents  : manifestStr
-          , cb
-
-        stack.push (cb)=>
-          @kiteController.run
-            method      : "uploadFile"
-            withArgs    :
-              path      : escapeFilePath "#{fsFolder.path}/ChangeLog"
-              contents  : """
-                              #{today} #{fullName} <@#{profile.nickname}>
-
-                                  * #{name} (index.coffee): Application created.
-                          """
-          , cb
-
-        # Copy default app files (app Skeleton)
-        stack.push (cb)=>
-          @kiteController.run
-            kiteName  : "applications"
-            method    : "copyAppSkeleton"
-            withArgs  :
-              type    : if isBlank then "blank" else "sample"
-              appPath : appPath
-            , cb
-
-        async.parallel stack, (error, result) =>
-          if err then warn err
-          @emit "aNewAppCreated" if not err
-          callback? err, result
+      @emit "aNewAppCreated" if not err
+      callback? err, result

   # #
   # FORK / CLONE APP


Removed unnecessary resize event handler.

--- client/app/MainApp/maintabs/maintabview.coffee
+++ client/app/MainApp/maintabs/maintabview.coffee
@@ -22,7 +22,6 @@
     @getSingleton("mainView").on "mainViewTransitionEnd", (e) =>
       if e.target is @getSingleton("contentPanel").domElement[0]
         @tabHandleContainer.setWidth @getWidth()
-        @resizeTabHandles()

   # temp fix sinan 27 Nov 12
   # not calling @removePane but @_removePane


video/model: make video work with lazy publishing

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -92,7 +92,14 @@
     @setState { connected: yes }
     @emit 'SessionConnected', session

-    @enableVideo { error: (err) => console.error err }  if helper.isVideoActive @channel
+    if helper.isVideoActive @channel
+      @setActive()
+
+      if @isMySession()
+        @enableVideo {},
+          success: =>
+            @setVideoState yes
+            @setAudioState yes

   ###*
@@ -129,7 +136,15 @@
         success : @bound 'handleStopSuccess'
         error   : (err) -> console.error err

-    session.on 'signal:start', => @enableVideo { error: (err) => console.error err }
+    session.on 'signal:start', =>
+
+      @setActive()
+
+      if @isMySession()
+        @enableVideo {},
+          success: =>
+            @setVideoState yes
+            @setAudioState yes

     # this event only comes to the user who has been muted, so need to make a
     # filtering here.
@@ -143,11 +158,13 @@
    * @emits VideoCollaborationModel~ParticipantConnected
   ###
   onConnectionCreated: (event) ->
+
+
+    { connection } = event
+
     # We don't want to deal with own user's events, we basically want to
     # abstract out logged-in user out of the equation here.
-    return  if @isMyConnection event.connection
-
-    { connection } = event
+    return  if @isMyConnection connection

     nick = helper.getNicknameFromConnection connection

@@ -166,12 +183,7 @@

     return  if @isMyConnection connection

-    @setParticipantLeft connection.id
-    @decrementConnectionCount()
-
-    nick = helper.getNicknameFromConnection connection
-
-    @emit 'ParticipantDisconnected', helper.defaultSubscriber nick
+    @setParticipantDisconnected connection.id

   ###*
@@ -219,17 +231,17 @@
    * This method handles initial automatic publishing with setting some default
    * options for host, and participant to be different.
    *
+   * @param {object} options
    * @param {object} callbacks
   ###
-  enableVideo: (callbacks) ->
-
-    @setActive()
+  enableVideo: (options, callbacks) ->

     success = (publisher) =>
       @handlePublishSuccess publisher
       callbacks.success? publisher

-    options = { publishAudio: @isMySession(), publishVideo: @isMySession() }
+    defaults = { publishAudio: @isMySession(), publishVideo: @isMySession() }
+    options  = _.assign {}, defaults, options
     @startPublishing options,
       success : success
       error   : callbacks.error
@@ -314,6 +326,7 @@
     publisher.on 'TalkingDidStart', =>
       return  unless @state.audio
       @emit 'ParticipantStartedTalking', getNick()
+      @changeActiveParticipant getNick()  unless @state.selectedParticipant

     publisher.on 'TalkingDidStop', =>
       return  unless @state.active
@@ -424,6 +437,10 @@
     if @state.selectedParticipant is subscriber.nick
       @setSelectedParticipant null

+    @decrementConnectionCount()
+
+    @unregisterSubscriber connectionId
+
     @emit 'ParticipantDisconnected', subscriber

@@ -473,9 +490,10 @@
   ###
   setAudioState: (state) ->

-    @publisher.videoData.publishAudio state
-    @setState { audio: state }
-    @emit 'AudioPublishStateChanged', state
+    @ensurePublishing {}, =>
+      @publisher.videoData.publishAudio state
+      @setState { audio: state }
+      @emit 'AudioPublishStateChanged', state

   ###*
@@ -485,9 +503,19 @@
   ###
   setVideoState: (state) ->

-    @publisher.videoData.publishVideo state
-    @setState { video: state }
-    @emit 'VideoPublishStateChanged', state
+    @ensurePublishing {}, =>
+      @publisher.videoData.publishVideo state
+      @setState { video: state }
+      @emit 'VideoPublishStateChanged', state
+
+
+  ensurePublishing: (options, callback) ->
+
+    @setActive()
+
+    if @state.publishing
+    then callback @publisher
+    else @enableVideo options, { success: callback }

   ###*
@@ -526,14 +554,17 @@
    * @param {function(error: object)} callbacks.error
   ###
   startPublishing: (options, callbacks) ->
+
     return  if @state.publishing

     defaults =
       publishAudio: no
       publishVideo: no

+    options = _.assign {}, defaults, options
+
     # first create publisher with defaults.
-    helper.createPublisher @view.getContainer(), defaults, (err, publisher) =>
+    helper.createPublisher @view.getContainer(), options, (err, publisher) =>
       return callbacks.error err  if err

       publisher.on
@@ -545,10 +576,9 @@
       publisher.on 'streamDestroyed', => @unregisterPublisher()

       @session.publish publisher, (err) =>
-        return callbacks.error err  if err
-        callbacks.success publisher
-        @setAudioState options.publishAudio
-        @setVideoState options.publishVideo
+        if err
+        then callbacks.error err
+        else callbacks.success publisher

   ###*
@@ -602,14 +632,15 @@
   ###
   setSelectedParticipant: (nick) ->

-    unless isOnline = @isParticipantOnline nick
-      @emit 'SelectedParticipantChanged', nick, no
+    unless @isParticipantOnline nick
+      @setState { selectedParticipant: null }
+      @emit 'SelectedParticipantChanged', nick
       return

     nick = null  if @state.selectedParticipant is nick
-
     @setState { selectedParticipant: nick }
-    @emit 'SelectedParticipantChanged', nick, yes
+
+    @emit 'SelectedParticipantChanged', nick

   ###*
@@ -650,6 +681,12 @@
   getParticipant: (nick) -> @getParticipants()[nick]

+  getActiveParticipant: -> @state.activeParticipant
+
+
+  getSelectedParticipant: -> @state.selectedParticipant
+
+
   ###*
    * Merges instance state with given state.
    *
@@ -685,7 +722,10 @@
   hasParticipantWithAudio: (nickname, callback) ->
     return callback no  unless @state.active

-    if participant = @getParticipant nickname
+    participant             = @getParticipant nickname
+    { isDefaultSubscriber } = helper
+
+    if participant and not isDefaultSubscriber participant
     then callback participant.videoData.stream.hasAudio
     else callback no


:lipstick:

:lipstick:

--- spec/api/protocol.coffee
+++ spec/api/protocol.coffee
@@ -45,7 +45,8 @@
       $.ajax
         url: 'atom-file-job://' + __filename
         success: (data) ->
-          console.log data
+          content = require('fs').readFileSync __filename
+          assert.equal data, String(content)
           done()
         error: (xhr, errorType, error) ->
           assert false, 'Got error: ' + errorType + ' ' + error


Correct typo in spec description

Correct typo in spec description

--- spec/apm-cli-spec.coffee
+++ spec/apm-cli-spec.coffee
@@ -20,7 +20,7 @@
       expect(console.error).toHaveBeenCalled()
       expect(console.error.argsForCall[0][0].length).toBeGreaterThan 0

-  describe 'when the version flag is specified', ->
+  describe 'when the help flag is specified', ->
     it 'prints a usage message', ->
       apm.run(['-h'])
       expect(console.log).not.toHaveBeenCalled()


keep old range on focus loss

keep old range on focus loss

--- src/core/selection.coffee
+++ src/core/selection.coffee
@@ -8,8 +8,10 @@
 class Selection
   constructor: (@doc, @iframe, @emitter) ->
     @document = @doc.root.ownerDocument
-    @range = this.getRange()
+    @focus = false
+    @range = new Range(0, 0)
     @nullDelay = false
+    this.update('silent')

   checkFocus: ->
     return false unless @document.activeElement == @doc.root
@@ -17,16 +19,20 @@
       return document.activeElement == @iframe
     return true

-  getRange: ->
-    return null unless this.checkFocus()
-    nativeRange = this._getNativeRange()
-    return null unless nativeRange?
-    start = this._positionToIndex(nativeRange.startContainer, nativeRange.startOffset)
-    if nativeRange.startContainer == nativeRange.endContainer and nativeRange.startOffset == nativeRange.endOffset
-      end = start
+  getRange: (ignoreFocus = false) ->
+    if this.checkFocus()
+      nativeRange = this._getNativeRange()
+      return null unless nativeRange?
+      start = this._positionToIndex(nativeRange.startContainer, nativeRange.startOffset)
+      if nativeRange.startContainer == nativeRange.endContainer and nativeRange.startOffset == nativeRange.endOffset
+        end = start
+      else
+        end = this._positionToIndex(nativeRange.endContainer, nativeRange.endOffset)
+      return new Range(Math.min(start, end), Math.max(start, end)) # Handle backwards ranges
+    else if ignoreFocus
+      return @range
     else
-      end = this._positionToIndex(nativeRange.endContainer, nativeRange.endOffset)
-    return new Range(Math.min(start, end), Math.max(start, end))
+      return null

   preserve: (fn) ->
     nativeRange = this._getNativeRange()
@@ -60,16 +66,19 @@
       this.setRange(range, 'silent')

   update: (source) ->
-    range = this.getRange()
-    emit = source != 'silent' and !Range.compare(range, @range)
+    focus = this.checkFocus()
+    range = this.getRange(true)
+    emit = source != 'silent' and (!Range.compare(range, @range) or focus != @focus)
+    toEmit = if focus then range else null
     # If range changes to null, require two update cycles to update and emit
-    if range == null and source =='user' and !@nullDelay
+    if toEmit == null and source == 'user' and !@nullDelay
       @nullDelay = true
     else
       @nullDelay = false
       @range = range
+      @focus = focus
       # Set range before emitting to prevent infinite loop if listeners call quill.getSelection()
-      @emitter.emit(@emitter.constructor.events.SELECTION_CHANGE, range, source) if emit
+      @emitter.emit(@emitter.constructor.events.SELECTION_CHANGE, toEmit, source) if emit

   _decodePosition: (node, offset) ->
     if dom(node).isElement()