update date converter to allow for time and date stamps

--- lib/assets/javascripts/dateconverter.coffee
+++ lib/assets/javascripts/dateconverter.coffee
@@ -2,20 +2,22 @@
   constructor: (object)->
     @object = object
   evaluate: ->
-    return @object unless typeof @object == 'object' || typeof @object == 'array'
-    if typeof @object == 'array'
-      for obj in @object
-        new DateParser(obj).evaluate
-    if typeof @object == 'object'
-      for key,value of @object
-        if typeof value == 'string'
-          @object[key] = new Date(value) if !!value.match(/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/)
-    return @object
+    for i of @object
+      if @object[i] != null and typeof @object[i] == 'object'
+        new DateParser(@object[i]).evaluate()
+      else if @object[i] != null and typeof @object[i] == 'string'
+        if !!@object[i].match(/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/)
+          time = new Date(@object[i])
+          time.setTime( time.getTime() + time.getTimezoneOffset()*60*1000 ) #offset timezone
+          @object[i] = time
+        else if !!@object[i].match(/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T[0-9]{2}\:[0-9]{2}\:[0-9]{2}\.[0-9]{3}[A-Z]$/)
+          @object[i] = new Date(@object[i])
+    return
 angular.factories
   .factory 'DateConverterInterceptor', ($q, $rootScope) ->
     request: (config)          -> config
     requestError: (rejection)  -> rejection
     response: (response)       ->
-      dp = new DateParser(response.data).evaluate()
+      new DateParser(response.data).evaluate()
       response
     responseError: (rejection) -> rejection


Fixed value_interpretation bug

Fixed value_interpretation bug

--- tutor/src/components/performance-forecast/progress-bar.cjsx
+++ tutor/src/components/performance-forecast/progress-bar.cjsx
@@ -28,7 +28,7 @@

     bar = if PerformanceForecast.Helpers.canDisplayForecast(section.clue)
       percent = Math.round(Number(section.clue.most_likely) * 100)
-      value_interpretation = percent >= 80 ? 'high' : (percent >= 30 ? 'medium' : 'low')
+      value_interpretation = if percent >= 80 then 'high' else (if percent >= 30 then 'medium' else 'low')
       # always show at least 5% of bar, otherwise it just looks empty
       <BS.ProgressBar className={value_interpretation} now={Math.max(percent, 5)} />
     else


remove unused unmet member

remove unused unmet member

--- lib/dispatcher.coffee
+++ lib/dispatcher.coffee
@@ -26,7 +26,6 @@
     @_count = {}
     @_state = {}
     @_cycles = {}
-    @_unmet = 0

   # Subscribe to the `requests` and `responses` channels.
   listen: ->
@@ -111,7 +110,6 @@
   # zero, it is rescheduled.
   _progress: (keys) ->
     for key in keys
-      @_unmet--
       unless --@_count[key]
         @_reschedule key

@@ -199,7 +197,6 @@
       when 'done' then return
       when undefined then @_reqs.push key
     (@deps[key] ?= []).push source
-    @_unmet++
     @_count[source]++

   # Take the unmet dependencies from the latest request and push


print json message on error

print json message on error

--- lib/linter-provider.coffee
+++ lib/linter-provider.coffee
@@ -100,7 +100,11 @@
         res.on 'data', (chunk) ->
           received = received + chunk
         res.on 'end', ->
-          jsonObject = JSON.parse received
+          try
+            jsonObject = JSON.parse received
+          catch error
+            atom.notifications.addError("Invalid output received from LanguageTool server", {detail: received})
+
           matches = jsonObject["matches"]
           for match in matches
             offset = match['offset']


test for set-password, currently fails

test for set-password, currently fails

--- test/integration/login.coffee
+++ test/integration/login.coffee
@@ -85,6 +85,39 @@

      context 'when I try to login with my email address as my username', ->

+describe 'Password', ->
+  context 'when I use the password reset link', ->
+    newUser = String(Math.random()).replace('0.', 'pass-')
+    newPass = newUser
+    before (done) ->
+      login 'teststaff', process.env.CU_TEST_STAFF_PASSWORD, (err, res, body) =>
+        form =
+          displayName: newUser
+          email: "pass@example.com"
+        request.post
+          uri: "#{BASE_URL}/api/#{newUser}"
+          form: form
+        , (err, resp, body) =>
+          obj = JSON.parse body
+          @browser = new Browser()
+          @browser.visit "#{BASE_URL}/set-password/#{obj.token}", =>
+            @browser.wait done
+
+    it 'shows a page with a password field', ->
+      should.exist @browser.query('#password')
+
+    context 'when I fill in my new password', ->
+      before (done) ->
+        @browser.fill '#password', newPass
+        console.log "FILLED"
+        @browser.pressButton '#content .btn-primary', =>
+          console.log "PRESSED"
+          @browser.wait done
+
+      it 'sets my password', ->
+        should.exist @browser.query('.alert-success')
+
+
 describe 'Switch', ->

   nonstaff_user = 'ickletest'


A much better decoupled design for the api

--- library/gearing/api.coffee
+++ library/gearing/api.coffee
@@ -93,6 +93,13 @@
         return respons.end(descriptor) if handles("json")
         response.end(supported.join(", ")); this

+# An abstract base class with all of the HTTP methods, defined in
+# the HTTP specification and covered by the base implementation
+# stubbed with default implementations. By default, the methods
+# will throw the 405, method not allowed HTTP error status code.
+# The methods have implementations, but marked as unsupported.
+module.exports.Stub = class Stub extends Api
+
     # Delete the contents of the resources at the establushed path. It
     # generally should destroy the contents of the resource for good.
     # Be sure to provide enough protection for your API for destructive


Added Regex Match

Added Regex Match

For cleaner output testing, i added a regex must pass string. All
of the stubs will likely contain these soon.

--- test/stubs/nodeps/main.coffee
+++ test/stubs/nodeps/main.coffee
@@ -1,9 +1,11 @@
 #
 # # Test Stub
 #
+### required to pass ###
+

 do ->
   new KDNotificationView
-    title: 'Stub'
+    title: 'stub'


starting hub when doing dev in cocalc-docker would kill the project you are starting the hub in

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -503,7 +503,9 @@
         (cb) ->
             init_compute_server(cb)
         (cb) ->
-            if not program.dev
+            if not program.dev or process.env.USER.length == 32
+                # not in dev mode or in cocalc-docker, better not
+                # kill everything, or we kill ourself!
                 cb(); return
             # Whenever we start the dev server, we just assume
             # all projects are stopped, since assuming they are


Fix missing clone

Fix missing clone

--- src/style_mixin.coffee
+++ src/style_mixin.coffee
@@ -1,10 +1,10 @@
-
+require('js-object-clone')

 class StyleMixin

 	@mixin: (proto)->
 		cloneStyle = (self)->
-			mixin = self._.styleMixin
+			mixin = self._.styleMixin = Object.clone(self._.styleMixin)
 			mixin.style = mixin.sm.cloneResource(mixin.style)

 		cloneFont = (self)->
@@ -17,6 +17,7 @@
 			if mixin.cloned
 				return mixin.style.font[key] = val
 			cloneFont(self)
+			mixin = self._.styleMixin
 			mixin.style.font[key] = val
 			mixin.cloned = true
 			mixin.onUpdate?()


Use native promises instead of Q

Use native promises instead of Q

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -4,7 +4,6 @@
 {File} = require 'pathwatcher'
 SpanSkipList = require 'span-skip-list'
 diff = require 'atom-diff'
-Q = require 'q'
 _ = require 'underscore-plus'
 fs = require 'fs-plus'
 path = require 'path'
@@ -1262,7 +1261,12 @@
   # * `callback`   (optional) {Function} to call after the cached contents have
   #                been updated.
   updateCachedDiskContents: (flushCache=false, callback) ->
-    Q(@file?.read(flushCache) ? "").then (contents) =>
+    if @file?
+      promise = @file.read(flushCache)
+    else
+      promise = Promise.resolve("")
+
+    promise.then (contents) =>
       @cachedDiskContents = contents
       callback?()


load with included dir

load with included dir

--- lib/executable.coffee
+++ lib/executable.coffee
@@ -6,8 +6,8 @@
 class Executable extends EventEmitter

     # instance wired the agda-mode executable
-    agdaProcessWired: false
-    agdaProcess: null
+    processWired: false
+    process: null

     constructor: (@core) ->

@@ -38,9 +38,9 @@
             .then (path) => path
             .fail        => @queryExecutablePathUntilSuccess()

-    getAgdaProcess: -> Q.Promise (resolve, reject, notify) =>
-        if @agdaProcessWired
-            resolve @agdaProcess
+    getProcess: -> Q.Promise (resolve, reject, notify) =>
+        if @processWired
+            resolve @process
         else
             @getExecutablePath().then (path) =>
                 process = spawn path, ['--interaction']
@@ -52,19 +52,35 @@
                 # see if it is really agda
                 process.stdout.once 'data', (data) =>
                   if /^Agda2/.test data
-                    @agdaProcessWired = true
-                    @agdaProcess = process
+                    @processWired = true
+                    @process = process
                     resolve process

     ################
     #   COMMANDS   #
     ################

-    load: -> @getAgdaProcess().then (process) =>
-        command = "IOTCM \"#{@core.filePath}\"
-            NonInteractive
-            Indirect
-            ( Cmd_load \"#{@core.filePath}\" [])\n"
-        process.stdin.write command
+    load: -> @getProcess().then (process) =>
+
+        includeDir = atom.config.get 'agda-mode.agdaLibraryPath'
+
+        if includeDir
+            command = "IOTCM
+                \"#{@core.filePath}\"
+                NonInteractive
+                Indirect
+                ( Cmd_load
+                    \"#{@core.filePath}\"
+                    [\".\", \"#{includeDir}\"])\n"
+            process.stdin.write command
+        else
+            command = "IOTCM
+                \"#{@core.filePath}\"
+                NonInteractive
+                Indirect
+                ( Cmd_load
+                    \"#{@core.filePath}\"
+                    [])\n"
+            process.stdin.write command

 module.exports = Executable


setupPusher delay so that it doesn't get stuck on splash screen

--- app/controllers/user_edit.coffee
+++ app/controllers/user_edit.coffee
@@ -62,10 +62,10 @@

   change: =>
     @item = MyUser.first()
-    @setupPusher()
+    setTimeout(@setupPusher, 3000)
     @render()

-  setupPusher: ->
+  setupPusher: =>
     return if @pusher
     @startDate = new Date()
     Spine.bind 'appResumed', ->


Refresh tree view after commit

Refresh tree view after commit

--- lib/git-commit.coffee
+++ lib/git-commit.coffee
@@ -73,6 +73,7 @@
       # reset editor for commitFile
       currentEditor = null
       atom.workspaceView.trigger 'core:save'
+      atom.project.getRepo()?.refreshStatus()
     stderr: (data) ->
       new StatusView(type: 'alert', message: data.toString())
       atom.beep()


Fix item['element_id'] instead of item['elementid'] in embed errors

--- bokehjs/src/coffee/server/embed.coffee
+++ bokehjs/src/coffee/server/embed.coffee
@@ -115,13 +115,14 @@
     docs[docid]= Document.from_json(docs_json[docid])

   for item in render_items
-    elem = $('#' + item['elementid']);
+    element_id = item['elementid']
+    elem = $('#' + element_id);
     if elem.length == 0
-      throw new Error("Error rendering Bokeh model: could not find tag with id: #{item['element_id']}")
+      throw new Error("Error rendering Bokeh model: could not find tag with id: #{element_id}")
     if elem.length > 1
-      throw new Error("Error rendering Bokeh model: found too many tags with id: #{item['element_id']}")
+      throw new Error("Error rendering Bokeh model: found too many tags with id: #{element_id}")
     if not document.body.contains(elem[0])
-      throw new Error("Error rendering Bokeh model: element with id '#{item['elementid']}' must be under <body>")
+      throw new Error("Error rendering Bokeh model: element with id '#{element_id}' must be under <body>")

     if elem.prop("tagName") == "SCRIPT"
       fill_render_item_from_script_tag(elem, item)
@@ -136,14 +137,14 @@
       else if item.sessionid?
         promise = add_model_from_session(elem, item.modelid, item.sessionid)
       else
-        throw new Error("Error rendering Bokeh model #{item['modelid']} to element #{item['elementid']}: no document ID or session ID specified")
+        throw new Error("Error rendering Bokeh model #{item['modelid']} to element #{element_id}: no document ID or session ID specified")
     else
       if item.docid?
          add_document_static(elem, docs[item.docid])
       else if item.sessionid?
          promise = add_document_from_session(elem, item.sessionid)
       else
-        throw new Error("Error rendering Bokeh document to element #{item['elementid']}: no document ID or session ID specified")
+        throw new Error("Error rendering Bokeh document to element #{element_id}: no document ID or session ID specified")

     if promise != null
       promise.then(


client: add missing decodeToken in view, fixes bug that prevents opening activities directly

--- client/Social/Activity/ContentDisplays/StatusUpdate.coffee
+++ client/Social/Activity/ContentDisplays/StatusUpdate.coffee
@@ -185,6 +185,9 @@
         new TokenView options, data

     return  str
+
+  decodeToken: (str) ->
+    return  match[1].split /:/g  if match = str.match /^\|(.+)\|$/

   pistachio:->
     """


activity: don't add channel name to end of body when creating

--- client/activity/lib/components/publicchatpane/index.coffee
+++ client/activity/lib/components/publicchatpane/index.coffee
@@ -29,10 +29,6 @@
   onSubmit: ({ value }) ->

     return  unless body = value
-    name = @channel 'name'
-
-    unless body.match ///\##{name}///
-      body += " ##{name} "

     ActivityFlux.actions.message.createMessage @channel('id'), body


FinderController: after showDotFiles expand root folder first

--- client/finder/filetree/controllers/findercontroller.coffee
+++ client/finder/filetree/controllers/findercontroller.coffee
@@ -217,7 +217,7 @@
     for own path, node of @treeController.nodes when node.getData().type is 'machine'
       return if node.getData().machine.uid is uid
         @treeController.collapseFolder node, =>
-          @reloadPreviousState uid
+          @treeController.expandFolder node, => @reloadPreviousState uid
         , yes


Fix for adding the user multiple times to the disabled array

--- src/adapters/generic.coffee
+++ src/adapters/generic.coffee
@@ -15,7 +15,7 @@
   disableNotificationsFor: (user) ->
     @robot.logger.info "Disabling JIRA notifications for #{user.name}"
     @disabledUsers.push user.id
-    @robot.brain.set GenericAdapter.JIRA_NOTIFICATIONS_DISABLED, @disabledUsers
+    @robot.brain.set GenericAdapter.JIRA_NOTIFICATIONS_DISABLED, _(@disabledUsers).unique()
     @robot.brain.save()

   enableNotificationsFor: (user) ->


Gruntのdefaultをdebugに変更

Gruntのdefaultをdebugに変更

--- client/Gruntfile.coffee
+++ client/Gruntfile.coffee
@@ -40,8 +40,12 @@
     "coffee:compile"
     "requirejs:nooptimize"
     ]
+
+  grunt.registerTask "default", [
+    "debug"
+    ]

-  grunt.registerTask "default", [
+  grunt.registerTask "release", [
     "coffeelint"
     "coffee:compile"
     "requirejs:optimize"


Change compile behavior to include reset on failure

Extended if statement following csound.CompileOrc in plugin module’s
“run” method to implement csound.Reset action if necessary prior to
returning from method.

--- lib/ide-csound.coffee
+++ lib/ide-csound.coffee
@@ -69,7 +69,10 @@
         result = csound.CompileArgs @Csound, ['csound', editor.getPath()]
       else
         result = csound.CompileOrc @Csound, editor.getText()
-    return if result isnt csound.CSOUND_SUCCESS
+    if result isnt csound.CSOUND_SUCCESS
+      csound.Reset @Csound
+      return
+

     result = csound.Start @Csound
     if result is csound.CSOUND_SUCCESS


Fixed indentation issue

Fixed indentation issue

--- src/api/transactions.coffee
+++ src/api/transactions.coffee
@@ -14,16 +14,16 @@
 # function to construct projection object
 getProjectionObject = (filterRepresentation) ->
 	switch filterRepresentation
-	  when "simpledetails"
-	    # view minimum required data for transaction details view
-	    return { "request.body": 0, "response.body": 0, "routes.request.body": 0, "routes.response.body": 0, "orchestrations.request.body": 0, "orchestrations.response.body": 0 };
-	  when "full"
-	    # view all transaction data
-	    return {};
-	  else
-	  	# no filterRepresentation supplied - simple view
-	    # view minimum required data for transactions
-	    return { "request.body": 0, "request.headers": 0, "response.body": 0, "response.headers": 0, orchestrations: 0, routes: 0 };
+		when "simpledetails"
+			# view minimum required data for transaction details view
+			return { "request.body": 0, "response.body": 0, "routes.request.body": 0, "routes.response.body": 0, "orchestrations.request.body": 0, "orchestrations.response.body": 0 };
+		when "full"
+			# view all transaction data
+			return {};
+		else
+			# no filterRepresentation supplied - simple view
+			# view minimum required data for transactions
+			return { "request.body": 0, "request.headers": 0, "response.body": 0, "response.headers": 0, orchestrations: 0, routes: 0 };


update config description

update config description

--- lib/isotope-ui.coffee
+++ lib/isotope-ui.coffee
@@ -44,7 +44,8 @@
       type: 'boolean'
       default: false
     backgroundImageUrl:
-      description: 'Experimental: the url for an image from te internets.'
+      description: 'Experimental: the url for an image from te internets
+        (e.g. hubblesite.org or unsplash.com).'
       type: 'string'
       default: 'atom://isotope-ui/resources/images/hubble.jpg'
     backgroundGrain:


improved list splitting

improved list splitting

--- modules/gui/tools/widgets.coffee
+++ modules/gui/tools/widgets.coffee
@@ -12,18 +12,18 @@
                 tools.csv.values ele, val

             return ele
-
+
         values: (ele, values)->
             ele._val ?= ele.val ## only if not already defined

             if values is undefined  ## read
-                ele._val().replace('/ /g','').split(',')
+                ele._val().trim().split /[\s]?,[\s]?/

             else ## write
                 if typeof values is 'string'
                     values = [values]

-                ele._val values.join ','
+                ele._val values.join ', '

     ##
     ##


Add extension point for opening urls

Add extension point for opening urls

If packages specify a urlMain in their package.json then that file
will be used as the bootstrap script new windows when a URL is opened
to that package.

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -51,7 +51,7 @@
   version: null

   constructor: (options) ->
-    {@resourcePath, @version} = options
+    {@resourcePath, @version, @devMode} = options
     global.atomApplication = this

     @pidsToOpenWindows = {}
@@ -147,7 +147,7 @@

     app.on 'open-url', (event, urlToOpen) =>
       event.preventDefault()
-      @openUrl(urlToOpen)
+      @openUrl({urlToOpen, @devMode})

     autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdateCallback) =>
       event.preventDefault()
@@ -253,8 +253,6 @@

   # Private: Handles an atom:// url.
   #
-  # Currently only supports atom://session/<session-id> urls.
-  #
   # * options
   #    + urlToOpen:
   #      The atom:// url to open.
@@ -262,14 +260,27 @@
   #      Boolean to control the opened window's dev mode.
   openUrl: ({urlToOpen, devMode}) ->
     parsedUrl = url.parse(urlToOpen)
-    if parsedUrl.host is 'session'
-      sessionId = parsedUrl.path.split('/')[1]
-      console.log "Joining session #{sessionId}"
-      if sessionId
-        bootstrapScript = 'collaboration/lib/bootstrap'
-        new AtomWindow({bootstrapScript, @resourcePath, sessionId, devMode})
+    packageName = parsedUrl.host
+    unless @packages?
+      PackageManager = require './package-manager'
+      fsUtils = require './fs-utils'
+      @packages = new PackageManager({
+        devMode: devMode
+        configDirPath: fsUtils.absolute('~/.atom')
+        resourcePath: @resourcePath
+      })
+
+    pack = _.find @packages.getAvailablePackageMetadata(), ({name}) -> name is packageName
+    if pack?
+      console.log(pack)
+      if pack.urlMain
+        packagePath = @packages.resolvePackagePath(packageName)
+        bootstrapScript = path.resolve(packagePath, pack.urlMain)
+        new AtomWindow({bootstrapScript, @resourcePath, devMode})
+      else
+        console.log "Package '#{pack.name}' does not have a url main: #{urlToOpen}"
     else
-      console.log "Opening unknown url #{urlToOpen}"
+      console.log "Opening unknown url: #{urlToOpen}"

   # Private: Opens up a new {AtomWindow} to run specs within.
   #


Split on whitespace when parsing pids

Split on whitespace when parsing pids

--- src/buffered-process.coffee
+++ src/buffered-process.coffee
@@ -134,7 +134,7 @@
     output = ''
     wmicProcess.stdout.on 'data', (data) -> output += data
     wmicProcess.stdout.on 'close', =>
-      pidsToKill = output.split('\n')
+      pidsToKill = output.split(/\s+/)
                     .filter (pid) -> /^\d+$/.test(pid)
                     .map (pid) -> parseInt(pid)
                     .filter (pid) -> not pid is parentPid


Remove old comments now wrong to prevent misunderstanding of code.

--- lib/log-filter.coffee
+++ lib/log-filter.coffee
@@ -79,8 +79,6 @@
   foldLineRange: (start, end) ->
     return unless start? and end?

-    # Atom folds the line under the selected row
-    # and the first line doesn't really fold
     actualStartLine = start
     actualStartColumn = 0
     foldPositionConfig = atom.config.get('language-log.foldPosition')


mocks/jobs: changed default timeout to 0

Now that events are guaranteed to be emitted in sequence, there's no need to
default to an arbitrary 500ms delay (which slows down tests).

--- lib/mocks/jobs.coffee
+++ lib/mocks/jobs.coffee
@@ -30,24 +30,24 @@
     @

 class CompleteJob extends MockJob
-  constructor: (timeout = 500) ->
+  constructor: (timeout = 0) ->
     super
     @delayEmit 'complete', timeout

 class DataJob extends CompleteJob
-  constructor: (data, timeout = 500) ->
-    # In case timeout is 0, add 1 so 'complete' event fires last
+  constructor: (data, timeout = 0) ->
+    # Add 1 so 'complete' event fires last
     super timeout + 1
     @intervalEmit 'data', timeout, data

 class FailJob extends MockJob
-  constructor: (timeout = 500) ->
+  constructor: (timeout = 0) ->
     super
     @delayEmit 'fail', timeout

 class ErrorJob extends FailJob
-  constructor: (messages, timeout = 500) ->
-    # In case timeout is 0, add 1 so 'fail' event fires last
+  constructor: (messages, timeout = 0) ->
+    # Add 1 so 'fail' event fires last
     super timeout + 1
     @intervalEmit 'warning', timeout, messages


Use attr for method

Use attr for method

--- lib/rails/csrf.coffee
+++ lib/rails/csrf.coffee
@@ -34,7 +34,7 @@
   return if form.is 'form[data-remote]'

   # Skip for GET requests
-  return if form.prop('method').toUpperCase() is 'GET'
+  return if form.attr('method').toUpperCase() is 'GET'

   # Get param token from meta elements on the page.
   #


:bug: Fix a react invariant violation raised on drag & click

--- lib/table-view.coffee
+++ lib/table-view.coffee
@@ -85,6 +85,9 @@
         e.stopPropagation()
         e.preventDefault()
         @endDrag(e)
+      'click': (e) =>
+        e.stopPropagation()
+        e.preventDefault()

     @configUndefinedDisplay = atom.config.get('table-edit.undefinedDisplay')
     @configPageMovesAmount = atom.config.get('table-edit.pageMovesAmount')


Uses some Coffeescript idioms

Uses some Coffeescript idioms

--- src/client/templates.coffee
+++ src/client/templates.coffee
@@ -1,8 +1,9 @@
 define ['underscore', 'lib/hullbase', 'handlebars'], (_, Hull, Handlebars) ->

-  appStrategies = ['hullGlobal', 'meteor', 'sprockets', 'hullDefault']
-  domStrategies = ['inner', 'global']
-  serverStrategies = ['require']
+  strategies =
+    app: ['hullGlobal', 'meteor', 'sprockets', 'hullDefault']
+    dom: ['inner', 'global']
+    server: ['require']

   #Compiles the template depending on its definition
   setupTemplate = (tplSrc, tplName) ->
@@ -50,24 +51,21 @@
           dfd.reject err
         dfd

+  _execute = (type, args...)->
+    for stratName in strategies[type]
+      strategyResult = strategyHandlers[type][stratName](args...)
+      return strategyResult if strategyResult
+
   applyDomStrategies = (tplName, el)->
     selector = "script[data-hull-template='#{tplName}']"
-    for stratName in domStrategies
-      handler = strategyHandlers.dom[stratName]
-      tpl = handler selector, tplName, el
-      return setupTemplate(tpl, tplName) if tpl
+    tpl = _execute('dom', selector, tplName, el)
+    setupTemplate(tpl, tplName) if tpl

   applyAppStrategies = (tplName)->
-    for stratName in appStrategies
-      handler = strategyHandlers.app[stratName]
-      tpl = handler tplName
-      return tpl if tpl
+    _execute('app', tplName)

   applyServerStrategies = (tplName, path, format)->
-    for stratName in serverStrategies
-      handler = strategyHandlers.server[stratName]
-      tpl = handler tplName, path, format
-      return tpl if tpl
+    _execute('server', tplName, path, format)

   lookupTemplate = (componentName, el, ref, format, name)->
@@ -88,17 +86,18 @@
     define: define
     templateEngine: Handlebars
     domFind: undefined
+    deferred: undefined
     initialize: (app) ->
       module.domFind = app.core.dom.find
       module.deferred = app.core.data.deferred
       app.core.template.load = (names=[], ref, el, format="hbs") ->
         names = [names] if _.isString(names)
         componentName = ref.replace('__component__$', '').split('@')[0]
-        dfd   = app.core.data.deferred()
+        dfd = app.core.data.deferred()
         tpls = _.map names, _.bind(lookupTemplate, undefined, componentName, el, ref, format)
-        app.core.data.when.apply(undefined, tpls).then ->
-          dfd.resolve _.object names, [].slice.apply(arguments)
+        app.core.data.when(tpls...).then ->
+          dfd.resolve _.object(names, [].slice.apply(arguments))
         , (err)->
-          dfd.reject(err)
+          dfd.reject err
         dfd.promise()
   module


fix clear console keybinding on macs

fix clear console keybinding on macs

--- keymaps/julia-client.cson
+++ keymaps/julia-client.cson
@@ -37,7 +37,7 @@
   'cmd-j cmd-s': 'julia-client:start-julia'
   'cmd-j cmd-o': 'julia-client:open-console'
   'cmd-j cmd-p': 'julia-client:open-plot-pane'
-  'cmd-j cmd-c': 'julia-client:clear-terminal'
+  'cmd-j cmd-c': 'julia-client:clear-console'
   'cmd-j cmd-k': 'julia-client:kill-julia'
   'cmd-j cmd-,': 'julia-client:settings'


[background] fixed bug where extension crashed if manifest with key "" was added

--- src/coffee/data/data.coffee
+++ src/coffee/data/data.coffee
@@ -453,7 +453,13 @@
                 callback (if Object.size(err) > 0 then err else null), (if sources.length > 0 then (new CombinedPlayerSource(sources)).setTopLevel() else null)
                 return

-            key = resolveKey keys[i], '', err
+            tmpErr = []
+            key = resolveKey keys[i], '', tmpErr
+
+            if key is false
+                err[keys[i]] = tmpErr
+                step i + 1
+                return

             loadManifest key, (err2, manifest) ->
                 if err2?


Add checking for IP address format

Add checking for IP address format

--- src/collector-darwin.coffee
+++ src/collector-darwin.coffee
@@ -11,6 +11,8 @@
     defaultInterface = (stdout.match(/interface:\s*(.+)\s*\n/) || [])[1]
     if not defaultGateway? || not defaultInterface?
       return callback(new Error('defaultGateway or defaultInterface not found'))
+    if not net.isIP(defaultGateway)
+      return callback(new Error("defaultGateway not found: #{defaultGateway}"))
     data = {
       defaultGateway: defaultGateway
       defaultInterface: defaultInterface


[#26] Fix wrong checking

[#26] Fix wrong checking

--- src/pattern/default/custom.coffee
+++ src/pattern/default/custom.coffee
@@ -11,7 +11,7 @@
   housing = element.housing
   pins = element.pins
   numbers = Object.keys pins
-  unless (housing['holeDiameter' + suffix]?) or (housing['padWidth' + suffix]? and housing['padHeight' + suffix]?)
+  unless (housing['padDiameter' + suffix]?) or (housing['padWidth' + suffix]? and housing['padHeight' + suffix]?)
     return false
   hasPads = false
   if housing['holeDiameter' + suffix]?


Client: Activity: Moved dropbox wrapper methods to dropboxwrappermixin

--- client/activity/lib/components/dropbox/dropboxwrappermixin.coffee
+++ client/activity/lib/components/dropbox/dropboxwrappermixin.coffee
@@ -32,3 +32,32 @@

     scrollToTarget containerElement, itemElement

+
+  moveToPrevPosition: (keyInfo) ->
+
+    if keyInfo.isLeftArrow
+      @close()
+      return no
+
+    @moveToPrevAction()  unless @hasSingleItem()
+    return yes
+
+
+  moveToNextPosition: (keyInfo) ->
+
+    if keyInfo.isRightArrow
+      @close()
+      return no
+
+    @moveToNextAction()  unless @hasSingleItem()
+    return yes
+
+
+  onItemSelected: (index) -> @onItemSelectedAction index
+
+
+  close: -> @closeAction no
+
+
+  getItemKey: (item) -> item.get 'id'
+


fix a typo

fix a typo

--- client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
@@ -74,7 +74,7 @@

     @switchLink = new CustomLinkView
       title       : title
-      href        : "/#{slug}"
+      href        : "/#{slug}/Activity"
       target      : slug
       icon        :
         cssClass  : 'new-page'
@@ -87,11 +87,11 @@
     JView::viewAppended.call this

     {group:{slug}, roles} = @getData()
-
+
     dashboardHref = "/#{slug}/Dashboard"

     if 'admin' in roles
-      @addSubView new KDCustomHTMLView
+      @addSubView new CustomLinkView
         title     : 'Admin dashboard'
         href      : dashboardHref
         click     : (event)->
@@ -103,7 +103,8 @@
     """
     {{> @avatar}}

-      {{> @switchLink}}#{roles.join ', '}
+      {{> @switchLink}}
+      #{roles.join ', '}

     """


adding stragler file that was missed in last commit

--- Src/coffeescript/cql-execution/test/elm/library/data.coffee
+++ Src/coffeescript/cql-execution/test/elm/library/data.coffee
@@ -163,16 +163,6 @@

 define InDemographic:
 AgeInYearsAt(start of MeasurementPeriod) >= 2 and AgeInYearsAt(start of MeasurementPeriod) < 18
-<<<<<<< HEAD
-<<<<<<< HEAD
-=======
-
-define X : InDemographic
-
-define function Z() : true
->>>>>>> ugly first attempt at using included libraries
-=======
->>>>>>> Refactoring the execution Context class to support CQL Population contexts and provide additional support for included libraries in CQL documents when evaluating CQL Patient context statements.
 ###

 module.exports['CommonLib'] = {
@@ -320,15 +310,6 @@
 context Patient

 define ID: common.InDemographic
-<<<<<<< HEAD
-<<<<<<< HEAD
-=======
-
-
-define Test2 : if common.InDemographic then true else false
->>>>>>> ugly first attempt at using included libraries
-=======
->>>>>>> Refactoring the execution Context class to support CQL Population contexts and provide additional support for included libraries in CQL documents when evaluating CQL Patient context statements.
 ###

 module.exports['Using CommonLib'] = {
@@ -422,7 +403,7 @@
                "libraryName" : "common",
                "type" : "ExpressionRef"
             }
- } ]
+         } ]
       }
    }
 }


adding polyfill for IE in base.coffee

adding polyfill for IE in base.coffee

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -3,6 +3,7 @@
   _ = require('underscore')
   Backbone = require('backbone')
   settings = require('settings')
+  socialMedia = require('cs!helpers/socialmedia.coffee')

   dispose = (obj) ->
     delete obj.parent
@@ -96,6 +97,7 @@

     addOpenGraphMetaTags: () ->
       summary = @summary?() or @summary
+      location.origin = socialMedia.locationOrigin()

       if summary isnt undefined
         url = window.location.href


show remote link

show remote link

--- src/server/src/Jekyll-Data.coffee
+++ src/server/src/Jekyll-Data.coffee
@@ -81,7 +81,7 @@
       times        = data.metadata['when-time']    || 'no-time'
       locations    = data.metadata['location' ]    || 'no-location'
       layout       = data.metadata['room-layout' ] || 'unknown'
-      remote       = data.metadata['remote' ]      || 'not link yet'
+      remote       = data.metadata['remote' ]      || ''
       tracks       = data.metadata.track           || 'no-track'
       locked       = data.metadata.locked          || false
       status       = data.metadata.status          || ''


Playground: main.coffee emptied

Playground: main.coffee emptied

--- playground/main.coffee
+++ playground/main.coffee
@@ -1,54 +0,0 @@
-paragraphs   = ["Frankfurter ground round jowl, tongue flank tenderloin pastrami.  Ball tip jowl ground round, leberkas drumstick pork chop doner.  Short loin venison pork belly beef beef ribs shank.  Beef ribs ham bacon pork loin salami, biltong strip steak pastrami ham hock boudin pork belly.","Kevin flank tenderloin porchetta pork biltong salami, capicola beef ribs shankle turducken doner cow corned beef.  Short ribs biltong tenderloin turducken pork doner hamburger kielbasa venison ground round drumstick chuck jerky.  Swine rump ham landjaeger.  Venison ribeye biltong meatloaf shank doner tongue chicken ham filet mignon ball tip hamburger tenderloin ground round.","Tri-tip spare ribs ham hamburger landjaeger.  Ground round flank jowl, rump chuck prosciutto frankfurter tongue short ribs turkey jerky pork chop bacon venison pork belly.  Tail bresaola brisket shoulder leberkas.  Kielbasa cow turkey chuck, rump jowl meatball.  Porchetta bresaola shankle pork loin shoulder beef ribs.  Biltong tenderloin landjaeger t-bone bacon hamburger.","Beef ribs shank biltong drumstick rump salami venison pork loin prosciutto ribeye filet mignon frankfurter meatloaf.  Chuck turducken ground round shoulder tail fatback.  Turkey hamburger frankfurter shank fatback pork pork belly short loin porchetta.  Shankle pork loin shoulder ham hock.  Pastrami pig ham chicken flank bresaola.","Beef ribs biltong filet mignon bacon rump shankle landjaeger pork chop chuck ball tip kevin cow.  Drumstick corned beef salami, biltong beef shoulder spare ribs flank sirloin beef ribs bacon.  Rump jowl tail, short loin doner kielbasa pig beef ribs drumstick pastrami jerky strip steak.  Ham fatback andouille shankle drumstick pork loin.","Beef ribs brisket pork belly, ball tip venison short loin pork chop sausage bacon kielbasa jerky.  Short ribs sirloin pork chop pork loin tenderloin brisket tri-tip bresaola.  Ham fatback drumstick venison strip steak sausage prosciutto leberkas biltong meatball pork loin doner.  Flank chuck brisket pork loin turkey.","Leberkas ribeye beef ribs, filet mignon brisket short loin pork tenderloin rump sirloin.  Sirloin bacon beef, ball tip doner pork belly pork loin strip steak venison chicken.  Spare ribs tri-tip andouille capicola pork belly sausage landjaeger, bacon kevin chuck shoulder hamburger leberkas biltong ham.  Beef ribs filet mignon shoulder meatloaf.  Tri-tip pig ribeye venison short loin meatball.","Meatball brisket porchetta, shankle jowl beef sausage chuck landjaeger ground round cow ham hock t-bone shank.  Pork belly cow kielbasa, t-bone bacon shoulder rump.  Shankle beef ribs turducken, ham filet mignon corned beef bacon beef meatloaf swine t-bone ground round.  Boudin leberkas shoulder ribeye biltong chuck.  Cow hamburger turducken, salami pastrami porchetta pork chop kevin jowl shoulder swine chuck bresaola.  Chicken ribeye turkey pork pancetta leberkas.","Tongue jerky meatball, kielbasa short loin pork shank turkey pork belly boudin porchetta biltong.  Turducken beef ribs chicken swine turkey chuck.  Tongue ham hock chuck turkey, pig fatback tail frankfurter.  Jowl kielbasa shankle landjaeger doner capicola, filet mignon beef pork short loin.  Flank pork chop tongue pork belly venison, bacon tri-tip frankfurter filet mignon fatback turkey t-bone biltong boudin.  Corned beef pancetta ribeye jerky, tenderloin doner strip steak swine.","Venison turducken pig porchetta boudin.  Flank biltong beef ribs prosciutto.  Pig pork rump drumstick, corned beef bresaola ham hock shankle doner tenderloin flank leberkas.  Ribeye prosciutto chicken turkey bacon.  Shoulder pork chop drumstick t-bone short loin meatloaf prosciutto.  Pancetta salami t-bone, cow swine turducken corned beef ground round shankle pork filet mignon frankfurter tenderloin.  Flank ham hock strip steak, corned beef salami short loin hamburger spare ribs pig boudin meatball tri-tip sausage."]
-container    = new KDView
-addParagraph = -> scrollView.setPartial "<p>#{paragraphs[KD.utils.getRandomNumber(10)-1]}</p>"
-
-container.addSubView scrollView = new KDScrollView
-  ownScrollBars : yes
-
-scrollView.setStyle
-  margin     : '100px auto'
-  width      : 300
-  height     : 200
-  background : '#1aa5fd'
-  color      : 'white'
-  padding    : 30
-
-
-container.addSubView new KDButtonView
-  title     : "Add a paragraph"
-  callback  : addParagraph
-
-
-container.addSubView new KDButtonView
-  title     : "Remove a paragraph"
-  callback  : ->
-    $paragraphs = scrollView.$('p')
-    $paragraphs[$paragraphs.length - 1]?.remove()
-
-addParagraph() for i in [0...100]
-
-container.appendToDomBody()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-


CADS-131- correcting how new line breaks are counted in countdown

--- c3/app/assets/javascripts/textAreaCounter.coffee
+++ c3/app/assets/javascripts/textAreaCounter.coffee
@@ -2,7 +2,7 @@
     elem = $(selector)
     helper = elem.parent().find(".countdown")
     text = helper.html()
-    num = maxChars - elem.val().length
+    num = maxChars - elem.val().replace(/\r(?!\n)|\n(?!\r)/g, "\r\n").length
     if ((text != undefined) && (text.length > 0))
       helper.html(text.replace /-?([0-9]+)/,num)


code cleanup: remove a commented out line

--- client/app/MainApp/kodingrouter.coffee
+++ client/app/MainApp/kodingrouter.coffee
@@ -165,7 +165,6 @@

     requireLogout =(fn)->
       mainController.accountReady ->
-        # console.log 'sfsfsfsfsfsf', KD.whoami(), KD.isLoggedIn()
         unless KD.isLoggedIn() then __utils.defer fn
         else clear()


Changed label selector

Changed label selector

--- client/test/lib/environments/vm.coffee
+++ client/test/lib/environments/vm.coffee
@@ -79,7 +79,7 @@

     terminateSelector       = '.kdmodal.AppModal .advanced .advanced.terminate'
     proceedSelector         = '.kdmodal.with-buttons .kdbutton.red'
-    terminatedLabelSelector = '.terminated'
+    terminatedLabelSelector = '.kdmodal.env-modal .state-label.terminated'

     helpers.beginTest(browser)
     helpers.waitForVMRunning(browser)


commentation

commentation

--- coffeescript/service/dispatcher.coffee
+++ coffeescript/service/dispatcher.coffee
@@ -110,7 +110,11 @@
       results
     else
       matches = @compile_matches(results)
+      # first match, obviously. compile_matches is a good place
+      # to sort the matches based on whatever criteria we decide
+      # to use.
       match = matches[0]
+      # Move the payload properties into the main object. HACKY.
       payload = match.payload
       delete match.payload
       for key, value of payload
@@ -119,7 +123,11 @@

   # this code was stolen and adapted from Djinn, which
   # is why it looks so hideous.  Not that Djinn is hideous,
-  # just that you're seeing it out of context.
+  # just that you're seeing it out of context. It's a messy,
+  # arguably perverted, variant of the famous Thompson algorithm for
+  # testing NFA acceptance.
+  # See http://swtch.com/~rsc/regexp/regexp1.html, specifically the
+  # section with heading "Implementation: Simulating the NFA"
   match_request_sequence: (sequence) ->
     stage = @matchers
     current = [new MatchTracker(null, stage)]
@@ -149,6 +157,10 @@
       else
         current = next

+  # Given a list of MatchTrackers which represent the leaf nodes
+  # of successful matches, traverse each parent-ward, collecting
+  # any data left behind by each matcher (e.g. the parameters
+  # captured by path-matching).
   compile_matches: (list, val) ->
     matches = []
     for tracker in list
@@ -162,6 +174,7 @@
       matches.push(out)
     matches

+# Trivial helper class.
 class MatchTracker
   constructor: (@parent, @stage, @type, @val) ->


only for collections it seems

only for collections it seems

--- collections/artwork_collections.coffee
+++ collections/artwork_collections.coffee
@@ -16,7 +16,7 @@
       "#{API_URL}/api/v1/collection/#{@get 'id'}?user_id=#{@userId()}"

   artworkParams: ->
-    qs.stringify user_id: @userId(), total_count: true, sort: '-position', size: 50, bustCache: Math.random()
+    qs.stringify user_id: @userId(), total_count: true, sort: '-position', size: 50

   initialize: ->
     @initArtworks()


Port FindFeatureFreeAreas to Process API

--- components/FindFeatureFreeAreas.coffee
+++ components/FindFeatureFreeAreas.coffee
@@ -144,54 +144,56 @@
   regions.sort sortByArea
   return regions

-class FindFeatureFreeAreas extends noflo.Component
-  description: 'Extract feature corners of image (method: YAPE)'
-  icon: 'file-image-o'
-  constructor: ->
-    @width = 0
-    @height = 0
-    @segments = 4
+exports.getComponent = ->
+  c = new noflo.Component
+  c.description = 'Extract feature corners of image (method: YAPE)'
+  c.icon = 'file-image-o'
+  c.inPorts.add 'corners',
+    datatype: 'array'
+  c.inPorts.add 'width',
+    datatype: 'int'
+    control: true
+    default: 0
+  c.inPorts.add 'height',
+    datatype: 'int'
+    control: true
+    default: 0
+  c.inPorts.add 'segments',
+    datatype: 'int'
+    control: true
+    default: 4
+  c.outPorts.add 'areas',
+    datatype: 'array'
+  c.outPorts.add 'corners',
+    datatype: 'array'
+  c.forwardBrackets =
+    corners: ['areas', 'corners']
+  c.process (input, output) ->
+    return unless input.hasData 'corners'
+    return if input.attached('width').length and not input.hasData 'width'
+    return if input.attached('height').length and not input.hasData 'height'
+    return if input.attached('segments').length and not input.hasData 'segments'
+    width = 0
+    if input.hasData 'width'
+      width = parseInt input.getData 'width'
+    height = 0
+    if input.hasData 'height'
+      height = parseInt input.getData 'height'
+    segments = 4
+    if input.hasData 'segments'
+      segments = parseInt input.getData 'segments'

-    @inPorts =
-      corners: new noflo.Port 'array'
-      width: new noflo.Port 'int'
-      height: new noflo.Port 'int'
-      segments: new noflo.Port 'int'
-    @outPorts =
-      areas: new noflo.Port 'array'
-      corners: new noflo.Port 'array'
+    corners = input.getData 'corners'
+    b = { w: width, h: height }
+    s = segments
+    regions = findRegions corners, b, s
+    output.send
+      areas: regions
+      corners: corners
+    output.done()
+    return

-    @inPorts.width.on 'data', (data) =>
-      #console.log 'width', data
-      @width = data
-    @inPorts.height.on 'data', (data) =>
-      #console.log 'height', data
-      @height = data
-    @inPorts.segments.on 'data', (data) =>
-      #console.log 'segments', data
-      @segments = data
-
-    @inPorts.corners.on 'begingroup', (group) =>
-      @outPorts.areas.beginGroup group
-      @outPorts.corners.beginGroup group
-    @inPorts.corners.on 'endgroup', (group) =>
-      @outPorts.areas.endGroup group
-      @outPorts.corners.endGroup group
-    @inPorts.corners.on 'disconnect', () =>
-      @outPorts.areas.disconnect()
-      @outPorts.corners.disconnect()
-    @inPorts.corners.on 'data', (corners) =>
-      b = { w: @width, h: @height }
-      s = @segments
-      #console.log b, s
-      regions = findRegions corners, b, s
-      @outPorts.areas.send regions
-      @outPorts.corners.send corners
-
-exports.getComponent = -> new FindFeatureFreeAreas
 exports.calculateStartingPoints = calculateStartingPoints
 exports.spatialSortedIndices = spatialSortedIndices
 exports.findIndexForPoint = findIndexForPoint
 exports.growRectangle = growRectangle
-
-


Add tracking to logged out cta

Add tracking to logged out cta

--- components/logged_out_cta/index.coffee
+++ components/logged_out_cta/index.coffee
@@ -1,4 +1,5 @@
 Cookies = require 'cookies-js'
+analytics = require '../../lib/analytics.coffee'

 module.exports = ->
   $el = $('.lo-cta')
@@ -8,6 +9,6 @@

     $el.on 'click', '.lo-cta__close', (e) ->
       $el.removeClass 'lo-cta--visible'
-
+      analytics.track.click 'Clicked on Learn More'
       # Cookie expires in 1 day
       Cookies.set 'lo-cta', true, { expires: 86400000 }


rails confirmation dialog to bootbox

rails confirmation dialog to bootbox

--- app/assets/javascripts/ab_admin/core/confirmation.js.coffee
+++ app/assets/javascripts/ab_admin/core/confirmation.js.coffee
@@ -1,45 +1,6 @@
-# Override Rails handling of confirmation
-
 $.rails.allowAction = (element) ->
-  # The message is something like "Are you sure?"
-  message = element.data('confirm')
-  # If there's no message, there's no data-confirm attribute,
-  # which means there's nothing to confirm
+  message = element.data("confirm")
   return true unless message
-  # Clone the clicked element (probably a delete link) so we can use it in the dialog box.
-  $link = element.clone()
-    # We don't necessarily want the same styling as the original link/button.
-    .removeAttr('class')
-    # We don't want to pop up another confirmation (recursion)
-    .removeAttr('data-confirm')
-    # We want a button
-    .addClass('btn').addClass('btn-danger')
-    # We want it to sound confirmy
-    .html(I18n.t('admin.js.confirm'))
-
-  # Create the modal box with the message
-  modal_html = """
-

-

-                   ×
-
#{message}

-

-

-

-

-

-                   #{I18n.t('admin.js.cancel')}
-

-

-               """
-  $modal_html = $(modal_html)
-  # Add the new button to the modal box
-  $modal_html.find('.modal-footer').append($link)
-  # Pop it up
-  $modal_html.modal()
-  $link.click (e) ->
-    e.preventDefault()
-    $(this).closest('.modal').modal('hide')
-    element.trigger('ajax:complete').trigger('confirm:success')
-  # Prevent the original link from working
-  return false+  bootbox.confirm message, (answer) ->
+    element.data('confirm', null).click() if answer
+  false


allows passing the rank scope to the autocomplete end-point from the select2 taxon concept selectors

--- app/assets/javascripts/admin/nomenclature_changes.js.coffee
+++ app/assets/javascripts/admin/nomenclature_changes.js.coffee
@@ -14,11 +14,17 @@
       url: '/admin/taxon_concepts/autocomplete'
       dataType: 'json'
       data: (query, page) ->
+        form = $(@).closest('form')
+        taxonomySelector = form && form.find('.taxonomy-selector')
+        rankSelector = form && form.find('.rank-selector')
         search_params:
           scientific_name: query
-          name_status: this.data('name-status-filter')
+          name_status: @data('name-status-filter')
           taxonomy:
-            id: this.data('taxonomy-id')
+            id: taxonomySelector && taxonomySelector.val() || @data('taxonomy-id')
+          rank:
+            id: rankSelector && rankSelector.val() || @data('rank-id')
+            scope: $(@).attr('data-rank-scope')
         per_page: 25
         page: 1
       results: (data, page) => # parse the results into the format expected by Select2.


Moved object lookup to generic model.

Moved object lookup to generic model.

--- app/assets/javascripts/app/controllers/navigation.js.coffee
+++ app/assets/javascripts/app/controllers/navigation.js.coffee
@@ -316,13 +316,28 @@
       if prio is 8000
         divider   = true
         navheader = 'Recent Viewed'
-      ticket = App.Ticket.find( item.o_id )
+
+      item.link  = ''
+      item.title = '???'
+
+      # convert backend name space to local name space
+      item.object = item.object.replace("::", '')
+
+      # lookup real data
+      if App[item.object]
+        object           = App[item.object].find( item.o_id )
+        item.link        = object.uiUrl()
+        item.title       = object.displayName()
+        item.object_name = object.objectDisplayName()
+
+      item.created_by = App.User.find( item.created_by_id )
+
       prio++
-      NavBarRight['RecendViewed::' + ticket.id + '-' + prio ] = {
+      NavBarRight['RecendViewed::' + item.o_id + item.object + '-' + prio ] = {
         prio:      prio
         parent:    '#current_user'
-        name:      item.recent_view_object + ' (' + ticket.title + ')'
-        target:    ticket.uiUrl()
+        name:      item.object + ' (' + item.title + ')'
+        target:    item.link
         divider:   divider
         navheader: navheader
       }


remove jquery

remove jquery

--- app/assets/javascripts/components/image-subject-viewer.cjsx
+++ app/assets/javascripts/components/image-subject-viewer.cjsx
@@ -3,7 +3,6 @@
 React                         = require 'react'
 {Router, Routes, Route, Link} = require 'react-router'
 example_subjects              = require '../lib/example_subject.json'
-$                             = require '../lib/jquery-2.1.0.min.js'
 SVGImage                      = require './svg-image'
 Draggable                     = require '../lib/draggable'
 LoadingIndicator              = require './loading-indicator'
@@ -56,14 +55,14 @@

   componentWillMount: ->
     @updateDimensions()
-
+
   componentWillUnmount: ->
     window.removeEventListener "resize", this.updateDimensions

   updateDimensions: ->
     console.log 'updating dimensions'
     @setState
-      windowInnerWidth: window.innerWidth
+      windowInnerWidth: window.innerWidth
       windowInnerHeight: window.innerHeight

   fetchSubjects: ->
@@ -88,14 +87,14 @@
       ).bind(this)
     return

-  loadImage: (url) ->
+  loadImage: (url) ->
     # console.log 'Loading image...' # DEBUG CODE
     @setState loading: true, =>
       img = new Image()
       img.src = url
       img.onload = =>
         if @isMounted()
-          @setState
+          @setState
             url: url
             imageWidth: img.width
             imageHeight: img.height
@@ -115,7 +114,7 @@
     console.log JSON.stringify @state.classification # DEBUG CODE
     @state.classification.send()
     @setState
-      workflow: "text-region"
+      workflow: "text-region"
       marks: [] # clear marks for next subject

     # prepare new classification
@@ -165,7 +164,7 @@
       currentMark.yUpper = currentMark.y - dist
       currentMark.yLower = currentMark.y + dist

-      @setState
+      @setState
         selectedMark: currentMark, =>
           console.log 'STATE: ', @state.selectedMark

@@ -184,7 +183,7 @@

   getEventOffset: (e) ->
     rect = @refs.sizeRect.getDOMNode().getBoundingClientRect()
-
+
     # console.log 'RECT: ', rect
     {horizontal, vertical} = @getScale()
     x: ((e.pageX - pageXOffset - rect.left) / horizontal) + @state.viewX
@@ -197,7 +196,7 @@
     console.log 'handleToolMouseDown()'

   selectMark: (mark) ->
-    return if mark is @state.selectedMark
+    return if mark is @state.selectedMark
     @setState selectedMark: mark

   onClickDelete: (key) ->
@@ -209,8 +208,8 @@

   beginTextEntry: ->
     console.log 'beginTextEntry()'
-    return unless @state.marks.length > 0
-    @setState
+    return unless @state.marks.length > 0
+    @setState
       workflow: "text-entry"
       selectedMark: @state.marks[0], =>
         console.log 'SELECTED MARK: ', @state.selectedMark
@@ -219,7 +218,7 @@

   nextTextEntry: ->
     console.log "nextTextEntry()"
-
+
     key = @state.selectedMark.key
     if key+1 > @state.marks.length-1
       console.log "That's all the marks for now!"
@@ -240,7 +239,7 @@

           <LoadingIndicator/>
-

+

{@state.subjects[0].location}

           <ActionButton loading={@state.loading} />
@@ -255,42 +254,42 @@
         action_button =  <ActionButton label={"FINISH"} onActionSubmit={@nextSubject} />

       else if @state.marks.length > 0
-
+
         if @state.workflow is "text-entry"
           action_button = <ActionButton label={"NEXT"} onActionSubmit={@nextTextEntry} />
         else
           action_button =  <ActionButton label={"FINISHED MARKING"} onActionSubmit={@beginTextEntry} />
-
+

-
-          <svg
-            className = "subject-viewer-svg"
-            width = {@state.imageWidth}
-            height = {@state.imageHeight}
-            viewBox = {viewBox}
+
+          <svg
+            className = "subject-viewer-svg"
+            width = {@state.imageWidth}
+            height = {@state.imageHeight}
+            viewBox = {viewBox}
             data-tool = {@props.selectedDrawingTool?.type} >

-            <rect
-              ref = "sizeRect"
-              width = {@state.imageWidth}
+            <rect
+              ref = "sizeRect"
+              width = {@state.imageWidth}
               height = {@state.imageHeight} />
-
-            <Draggable
-              onStart = {@handleInitStart}
-              onDrag  = {@handleInitDrag}
+
+            <Draggable
+              onStart = {@handleInitStart}
+              onDrag  = {@handleInitDrag}
               onEnd   = {@handleInitRelease} >
-              <SVGImage
-                src = {@state.subjects[0].location}
-                width = {@state.imageWidth}
+              <SVGImage
+                src = {@state.subjects[0].location}
+                width = {@state.imageWidth}
                 height = {@state.imageHeight} />

             </Draggable>

             { @state.marks.map ((mark, i) ->

-              <TextRegionTool
-                key = {mark.key}
+              <TextRegionTool
+                key = {mark.key}
                 mark = {mark}
                 disabled = {false}
                 imageWidth = {@state.imageWidth}
@@ -311,10 +310,10 @@

           { if @state.workflow is "text-entry"
             console.log ">>>>>>>>> TOP: ", @getScale().vertical * @state.selectedMark.y - window.innerHeight/2 + 8
-            <TextEntryTool
-              top={ @getScale().vertical * @state.selectedMark.yLower + 20 }
-              left={@state.windowInnerWidth/2 - 200}
-            />
+            <TextEntryTool
+              top={ @getScale().vertical * @state.selectedMark.yLower + 20 }
+              left={@state.windowInnerWidth/2 - 200}
+            />
           }


Fix focus/blur issues with markable regions.

--- app/assets/javascripts/mercury/regions/markupable.js.coffee
+++ app/assets/javascripts/mercury/regions/markupable.js.coffee
@@ -25,9 +25,12 @@
     @element.hide()
     @resize()

+    @previewElement = @element
+    @element = @textarea
+

   focus: ->
-    @textarea.focus()
+    @element.focus()

   bindEvents: ->
@@ -44,17 +47,23 @@
       return unless Mercury.region == @
       @execCommand(options.action, options) if options.action

-    @textarea.bind 'dragenter', (event) =>
+    Mercury.bind 'unfocus:regions', (event) =>
+      return if @previewing
+      if Mercury.region == @
+        @element.blur()
+        Mercury.trigger('region:blurred', {region: @})
+
+    @element.bind 'dragenter', (event) =>
       return if @previewing
       event.preventDefault()
       event.originalEvent.dataTransfer.dropEffect = 'copy'

-    @textarea.bind 'dragover', (event) =>
+    @element.bind 'dragover', (event) =>
       return if @previewing
       event.preventDefault()
       event.originalEvent.dataTransfer.dropEffect = 'copy'

-    @textarea.bind 'drop', (event) =>
+    @element.bind 'drop', (event) =>
       return if @previewing

       # handle dropping snippets
@@ -69,18 +78,12 @@
         @focus()
         Mercury.uploader(event.originalEvent.dataTransfer.files[0])

-    @textarea.focus =>
+    @element.focus =>
       return if @previewing
       Mercury.region = @
-      @textarea.addClass('focus')
       Mercury.trigger('region:focused', {region: @})

-    @textarea.blur =>
-      return if @previewing
-      @textarea.removeClass('focus')
-      Mercury.trigger('region:blurred', {region: @})
-
-    @textarea.keydown (event) =>
+    @element.keydown (event) =>
       return if @previewing
       Mercury.changes = true
       @resize()
@@ -88,7 +91,7 @@

         when 13 # enter or return
           selection = @selection()
-          text = @textarea.val()
+          text = @element.val()
           start = text.lastIndexOf('\n', selection.start)
           end = text.indexOf('\n', selection.end)
           end = text.length if end < start
@@ -98,9 +101,7 @@
             event.preventDefault()
           if /\d/.test(text[start + 1])
             lineText = text.substring(start, end)
-            console.debug(lineText)
             if /(\d+)\./.test(lineText)
-              console.debug(2)
               number = parseInt(RegExp.$1)
               selection.replace("\n#{number += 1}. ", false, true)
               event.preventDefault()
@@ -128,34 +129,39 @@

       @pushHistory(event.keyCode)

-    @textarea.keyup =>
+    @element.keyup =>
       return if @previewing
       Mercury.trigger('region:update', {region: @})

-    @element.click (event) =>
+    @element.mouseup =>
+      return if @previewing
+      @focus()
+      Mercury.trigger('region:focused', {region: @})
+
+    @previewElement.click (event) =>
       $(event.target).closest('a').attr('target', '_top') if @previewing

   content: (value = null, filterSnippets = true) ->
     if value != null
       if $.type(value) == 'string'
-        @textarea.val(value)
+        @element.val(value)
       else
-        @textarea.val(value.html)
+        @element.val(value.html)
         @selection().select(value.selection.start, value.selection.end)
     else
-      return @textarea.val()
+      return @element.val()

   togglePreview: ->
     if @previewing
+      @previewElement.hide()
+      @element.show()
+    else
+      value = @converter.makeHtml(@element.val())
+      @previewElement.html(value)
+      @previewElement.show()
       @element.hide()
-      @textarea.show()
-    else
-      value = @converter.makeHtml(@textarea.val())
-      @element.html(value)
-      @element.show()
-      @textarea.hide()
     super

@@ -194,7 +200,7 @@

   selection: ->
-    return new Mercury.Regions.Markupable.Selection(@textarea)
+    return new Mercury.Regions.Markupable.Selection(@element)

   resize: ->


Filename in CDN url because it's nice

Filename in CDN url because it's nice

--- app/assets/javascripts/uploadcare/widget/template.js.coffee
+++ app/assets/javascripts/uploadcare/widget/template.js.coffee
@@ -86,7 +86,7 @@
           size = infos[0].fileSize

           if infos[0].isStored
-            href = "#{@settings.cdnBase}/#{infos[0].fileId}/"
+            href = "#{@settings.cdnBase}/#{infos[0].fileId}/#{infos[0].fileName}"
             caption = "#{caption}"

         size = Math.ceil(size / 1024).toString()


update member deletion warnings

update member deletion warnings

--- app/directives/group-page-funders/group-page-funders.coffee
+++ app/directives/group-page-funders/group-page-funders.coffee
@@ -21,7 +21,7 @@
               "All of their funds will be deleted from currently funding buckets",
               "All of their funds will be removed from the group",
               "All of their ideas will be deleted from the group",
-              "All of their comments will be deleted from the group"
+              "All of their funding buckets will be deleted from the group and money will be refunded"
             ]
             $scope.cancel = ->
               $mdDialog.cancel()


修正c-repeat可能错误的相应不输入迭代范围的新增记录的BUG

修正c-repeat可能错误的相应不输入迭代范围的新增记录的BUG

--- src/core/dom-feature.coffee
+++ src/core/dom-feature.coffee
@@ -167,6 +167,8 @@
 			return

 		scope.onItemInsert = (arg)=>
+			return unless scope.items is arg.entityList
+
 			headDom = domBinding.dom
 			tailDom = cola.util.userData(headDom, cola.constants.REPEAT_TAIL_KEY)
 			templateDom = cola.util.userData(headDom, cola.constants.REPEAT_TEMPLATE_KEY)


JK, passing vars.self with callbacks now

--- src/core/methods/set.coffee
+++ src/core/methods/set.coffee
@@ -125,6 +125,6 @@
     # If there is a callback function not associated with a URL, run it.
     if key is "value" and object.callback and not object.url
       callback = if typeof object.callback is "function" then object.callback else object.callback.value
-      callback value, vars if callback
+      callback value, vars.self if callback

   return


Emit 'cursor:moved' event to update cursor position in status bar

Emitting the event *before* update, rather than after. This is because
we read from the DOM after update to measure new characters, which
forces layout, so emitting the event after measuring forces another
layout when the position is updated.

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -74,6 +74,9 @@

   componentWillUnmount: ->
     @getDOMNode().removeEventListener 'mousewheel', @onMouseWheel
+
+  componentWillUpdate: ->
+    @props.parentView.trigger 'cursor:moved' if @cursorsMoved

   componentDidUpdate: ->
     @pendingChanges.length = 0


Correctly close scope spans when rendering line HTML

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -322,6 +322,7 @@
     if scopeTree.children?
       html = ""
       html += @buildScopeTreeHTML(child) for child in scopeTree.children
+      html += ""
       html
     else
       "#{scopeTree.getValueAsHtml({})}"


Tidy up exports in vimium_frontend

Tidy up exports in vimium_frontend

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -283,17 +283,17 @@
   document.activeElement.blur() if document.activeElement.tagName.toLowerCase() == "iframe"
   flashFrame() if request.highlight

-extend root,
-  lastFocusedInput: do ->
-    # Track the most recently focused input element.
-    recentlyFocusedElement = null
-    window.addEventListener "focus",
-      forTrusted (event) ->
-        DomUtils = window.DomUtils ? root.DomUtils # Workaround FF bug 1408996.
-        if DomUtils.isEditable event.target
-          recentlyFocusedElement = event.target
-    , true
-    -> recentlyFocusedElement
+# Used by focusInput command.
+root.lastFocusedInput = do ->
+  # Track the most recently focused input element.
+  recentlyFocusedElement = null
+  window.addEventListener "focus",
+    forTrusted (event) ->
+      DomUtils = window.DomUtils ? root.DomUtils # Workaround FF bug 1408996.
+      if DomUtils.isEditable event.target
+        recentlyFocusedElement = event.target
+  , true
+  -> recentlyFocusedElement

 # Checks if Vimium should be enabled or not in this frame.  As a side effect, it also informs the background
 # page whether this frame has the focus, allowing the background page to track the active frame's URL and set
@@ -338,7 +338,7 @@
 root.Frame = Frame
 root.windowIsFocused = windowIsFocused
 root.bgLog = bgLog
-# These are exported for find mode and link-hints mode.
+# These are exported for normal mode and link-hints mode.
 extend root, {focusThisFrame}
 # These are exported only for the tests.
 extend root, {installModes}


Use absolute path as URI

Use absolute path as URI

--- lib/archive-editor.coffee
+++ lib/archive-editor.coffee
@@ -27,9 +27,6 @@
   getPath: ->
     @file.getPath()

-  getRelativePath: ->
-    atom.project.relativize(@getPath()) if @getPath()?
-
   destroy: ->
     @file?.off()

@@ -41,7 +38,7 @@
     else
       'untitled'

-  getUri: -> @getRelativePath()
+  getUri: -> @getPath()

   isEqual: (other) ->
     other instanceof ArchiveEditor and @getUri() is other.getUri()


don't need this any more

don't need this any more

--- lib/connection/tcp.coffee
+++ lib/connection/tcp.coffee
@@ -1,5 +1,4 @@
 net = require 'net'
-{Emitter} = require 'atom'

 client = require './client'


more debug output

more debug output

--- app/assets/javascripts/webrtc.coffee
+++ app/assets/javascripts/webrtc.coffee
@@ -210,9 +210,10 @@
       console.log("Already connected to peer " + peer_id)
       return

-    peer_connection = new RTCPeerConnection(@pc_config, {"optional": [{"DtlsSrtpKeyAgreement": true}]} )
+    peer_connection = new RTCPeerConnection(@pc_config, {"optional": []} )
     @peers[peer_id] = peer_connection
-
+    peer_connection.oniceconnectionstatechange = (ev) ->
+      console.log('oniceconnectionstatechange', ev)
     peer_connection.onicecandidate = (event) =>
       if (event.candidate)
         @send('relay', {
@@ -245,7 +246,7 @@
       console.log('Adding local stream', @localStream)
       peer_connection.addStream(@localStream)
     else
-      console.log('[WARN] - localStream could not be added to peer_connection, localStream',@localStream)
+      console.log('[ERROR] - localStream could not be added to peer_connection, localStream',@localStream)

     # Only one side of the peer connection should create the
     # offer, the signaling server picks one to be the offerer.
@@ -274,8 +275,9 @@
             }
           )
           console.log("Offer setLocalDescription succeeded")
-        , ->
-          Alert("Offer setLocalDescription failed!")
+        , (er) ->
+          console.log('setLocalDescription failed', er)
+          alert("Offer setLocalDescription failed!")
         )
       , (error) ->
         console.log("Error sending offer: "+error)


Add a loading state

Add a loading state

--- app/pages/lab/field-guide/index.cjsx
+++ app/pages/lab/field-guide/index.cjsx
@@ -19,6 +19,7 @@
     actions: actions

   getInitialState: ->
+    loading: false
     guide: null
     icons: {}
     editing: null
@@ -31,10 +32,13 @@
       @loadGuide nextProps.project

   loadGuide: (project) ->
+    @setState loading: true
     apiClient.type('field_guides').get project_id: project.id
       .then ([guide]) =>
         @listenTo guide
-        @setState {guide}
+        @setState
+          loading: false
+          guide: guide
         if guide?
           @fetchIcons guide

@@ -92,20 +96,18 @@

       {if @state.guide?
         @renderEditor()
+      else if @state.loading
+
Loading field guide...

       else
-        @renderCreator()}
-

-
-  renderCreator: ->
-

-

-        This project doesn’t have a field guide yet.{' '}
-        <button type="button" onClick={@createGuide}>Create one!</button>
-

+

+          This project doesn’t have a field guide yet.{' '}
+          <button type="button" onClick={@createGuide}>Create one!</button>
+
}

   renderEditor: ->
     window.editingGuide = @state.guide
+

         <ArticleList


Bind broadcast socket to same address/interface as web server

--- lib/devices/Device.coffee
+++ lib/devices/Device.coffee
@@ -51,7 +51,7 @@
         @uuid = "uuid:#{res.uuid}"
         @address = res.address
         @httpPort = res.port
-        @broadcastSocket.bind @ssdp.port,'0.0.0.0', =>
+        @broadcastSocket.bind @ssdp.port,@address, =>
           @broadcastSocket.addMembership @ssdp.address
           @broadcastSocket.setMulticastTTL @ssdp.ttl
           @addServices()


hub/compute: derive data center value dc from name if it is not given

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -193,7 +193,7 @@
             @_add_server_single(opts)
             return

-        if not opts.host
+        if not opts.dc
             i = opts.host.indexOf('-')
             if i != -1
                 opts.dc = opts.host.slice(0,i)


Update dom.js.coffee

Update dom.js.coffee

--- lib/js/core/dom.js.coffee
+++ lib/js/core/dom.js.coffee
@@ -1,4 +1,4 @@
-diff_match_patch = require "diff_match_patch"
+diff_match_patch = require "google-diff-match-patch-js"

 module.exports = class DomPredictionHelper
   recursiveNodes: (e) ->


KernelManager: Set kernel working directory

Fixes #398

--- lib/kernel-manager.coffee
+++ lib/kernel-manager.coffee
@@ -108,17 +108,23 @@

         console.log 'KernelManager: startKernelFor:', language

-        launchSpec(kernelSpec).then ({config, connectionFile, spawn}) =>
-            kernel = new ZMQKernel(
-                kernelSpec, grammar,
-                config, connectionFile,
-                spawn
-            )
-            @setRunningKernelFor grammar, kernel
-
-            @_executeStartupCode kernel
-
-            onStarted?(kernel)
+        projectPath = path.dirname(
+            atom.workspace.getActiveTextEditor().getPath()
+        )
+        spawnOptions =
+            cwd: projectPath
+        launchSpec(kernelSpec, spawnOptions).
+            then ({config, connectionFile, spawn}) =>
+                kernel = new ZMQKernel(
+                    kernelSpec, grammar,
+                    config, connectionFile,
+                    spawn
+                )
+                @setRunningKernelFor grammar, kernel
+
+                @_executeStartupCode kernel
+
+                onStarted?(kernel)

     _executeStartupCode: (kernel) ->


Remove commit SHA-1 from version before comparing

Closes #55

--- lib/user-utilities.coffee
+++ lib/user-utilities.coffee
@@ -115,7 +115,7 @@

   checkAtomUpToDate: ->
     @getLatestAtomData().then (latestAtomData) =>
-      installedVersion = atom.getVersion()
+      installedVersion = atom.getVersion()?.replace(/-.*$/, '')
       latestVersion = latestAtomData.name
       upToDate = installedVersion? and semver.gte(installedVersion, latestVersion)
       { upToDate, latestVersion, installedVersion }


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -7,7 +7,7 @@

   self.bind "update", ->
     if I.fadeOut
-      I.alpha = startingAlpha - (I.age / I.duration) *
+      I.alpha = startingAlpha - (I.age / I.duration) * startingAlpha

     if I.duration != -1 && I.age >= I.duration
       I.active = false


AdministrationView: add same information to admin screen too

--- client/Dashboard/views/administrationview.coffee
+++ client/Dashboard/views/administrationview.coffee
@@ -241,7 +241,7 @@
         account = accounts[0]
         inputs.Flags.setValue account.globalFlags?.join(',')
         userRequestLineEdit.hide()
-        @showConnectedFields()
+        @showConnectedFields account
       else
         userRequestLineEdit.show()
         @hideConnectedFields()
@@ -256,10 +256,21 @@
     fields.Block.hide()
     inputs.Flags.setValue ''

-  showConnectedFields:->
+    @metaInfo?.destroy()
+
+  showConnectedFields: (account)->
+
     {fields, inputs, buttons} = @forms["User Details"]
     fields.Impersonate.show()
     fields.Flags.show()
     fields.Block.show()
     buttons.Update.show()

+    @metaInfo?.destroy()
+
+    account.fetchMetaInformation (err, info)=>
+      return if KD.showError err
+      info = KD.utils.objectToString info, separator: "  "
+      @addSubView @metaInfo = new KDView
+        cssClass : 'has-markdown'
+        partial  : KD.utils.applyMarkdown "```json \n#{info}\n```"


bug fix

bug fix

--- client/Social/Activity/views/settingsview.coffee
+++ client/Social/Activity/views/settingsview.coffee
@@ -121,11 +121,11 @@
       size         : "tiny"
       callback     : (value)->
         newTags = []
-        {JTag} = KD.remote.api
-        JTag.fetchSystemTags {title:value},limit:1, (err, systemTags)->
-          tag = systemTags.first
-          stringToAdd = "|#:JTag:#{tag.getId()}|"
-          post.body += " #{stringToAdd}"
+        {JTag}  = KD.remote.api
+        JTag.fetchSystemTags {title:value}, {limit:1}, (err, systemTags)->
+          tag         = systemTags.first
+          stringToAdd = " |#:JTag:#{tag.getId()}|"
+          post.body  += stringToAdd

           newTags.push id:tag.getId() for tag in post.tags
           options  =
@@ -136,6 +136,5 @@
           post.modify options, (err)->
             log err if err

-
     modal.addSubView systemTags


PMs: Only notify leave/join if PM is open.

--- client/app/js/views/private_messages_view.coffee
+++ client/app/js/views/private_messages_view.coffee
@@ -102,11 +102,16 @@

   notifyJoin: (user) =>
     message = @collection.get(user.id)
+    return  unless @isOpen(message)
     message?.add(user.id, "#{user.id} is now online!", type: "alert")

   notifyLeave: (user) =>
     message = @collection.get(user.id)
+    return  unless @isOpen(message)
     message?.add(user.id, "#{user.id} is now offline.", type: "alert")
+
+  isOpen: (message) =>
+    $findPopup(message.id).length > 0

   # Returns true if the chat is scrolled to the bottom of the screen.
   # This also returns true if the messages are hidden.


Extend smart event IR query to include locations

--- client/controllers/smartEvents/smartEvent.coffee
+++ client/controllers/smartEvents/smartEvent.coffee
@@ -17,10 +17,14 @@
     event = SmartEvents.findOne(eventId)
     if event
       eventDateRange = event.dateRange
+      locations = event.locations
       query = disease: event.disease
       if eventDateRange
         query['dateRange.start'] = $lte: eventDateRange.end
         query['dateRange.end'] = $gte: eventDateRange.start
+      if locations
+        locationNames = _.flatten(_.pluck(locations, 'alternateNames'))
+        query['locations.name'] = $in: locationNames
       @subscribe 'smartEventIncidents', query,
         onReady: =>
           @loading.set(false)


ChatParticipantView: Handle isMe case.

ChatParticipantView: Handle isMe case.

--- client/ide/views/chat/chatparticipantview.coffee
+++ client/ide/views/chat/chatparticipantview.coffee
@@ -8,6 +8,7 @@

     participantData = @getData()
     participantName = participantData.nickname
+    @setClass 'me'  if isMe

     @avatar    = new AvatarView
       origin   : participantData.nickname
@@ -15,21 +16,25 @@

     @name = new KDCustomHTMLView
       cssClass : 'name'
-      partial  : participantName
+      partial  : nickname

-    @watchButton = new KDButtonView
-      iconOnly : 'yes'
-      cssClass : 'watch-button'
-      callback : =>
-        @watchButton.toggleClass 'watching'
-        log 'watch/unwatch user'
+    if isMe
+      @watchButton = new KDCustomHTMLView cssClass: 'hidden'
+      @settings    = new KDCustomHTMLView cssClass: 'hidden'
+    else
+      @watchButton = new KDButtonView
+        iconOnly : 'yes'
+        cssClass : 'watch-button'
+        callback : =>
+          @watchButton.toggleClass 'watching'
+          log 'watch/unwatch user'

-    @settings       = new KDSelectBox
-      defaultValue  : 'edit'
-      selectOptions : [
-        { title : 'CAN READ', value : 'read'}
-        { title : 'CAN EDIT', value : 'edit'}
-      ]
+      @settings       = new KDSelectBox
+        defaultValue  : 'edit'
+        selectOptions : [
+          { title : 'CAN READ', value : 'read'}
+          { title : 'CAN EDIT', value : 'edit'}
+        ]

   pistachio: ->


Strip null and undefined params

Strip null and undefined params

--- client/shared/record_store/restful_client.coffee
+++ client/shared/record_store/restful_client.coffee
@@ -41,17 +41,17 @@
       @get(path or '', params)

     post: (path, params) ->
-      @request @buildUrl(path), 'POST', _.defaults({}, @defaultParams, params)
+      @request @buildUrl(path), 'POST', paramsFor(params)

     patch: (path, params) ->
-      @request @buildUrl(path), 'PATCH', _.defaults({}, @defaultParams, params)
+      @request @buildUrl(path), 'PATCH', paramsFor(params)

     delete: (path, params) ->
-      @request @buildUrl(path), 'DELETE', _.defaults({}, @defaultParams, params)
+      @request @buildUrl(path), 'DELETE', paramsFor(params)

     # NB: get requests place their params into the query string, rather than the request body
     get: (path, params) ->
-      @request @buildUrl(path, _.defaults({}, @defaultParams, params)), 'GET'
+      @request @buildUrl(path, paramsFor(params)), 'GET'

     request: (path, method, body = {}) ->
       opts =
@@ -110,3 +110,6 @@

     abort: ->
       @currentUpload.abort() if @currentUpload
+
+paramsFor = (params) ->
+  _.defaults({}, @defaultParams, _.pick(params, _.identity))


Images fix

Images fix

--- vendor/assets/javascripts/lightbox.js.coffee
+++ vendor/assets/javascripts/lightbox.js.coffee
@@ -45,8 +45,8 @@

 class LightboxOptions
   constructor: ->
-    @fileLoadingImage = 'images/loading.gif'
-    @fileCloseImage = 'images/close.png'
+    @fileLoadingImage = '/assets/lightbox/loading.gif'
+    @fileCloseImage = '/assets/lightbox/close.png'
     @resizeDuration = 700
     @fadeDuration = 500
     @labelImage = "Image" # Change to localize to non-english language


Client: Teams: Added sendInvitations method to send invitations and close modals and prepare notify title by invitations length

--- client/admin/lib/views/invitations/invitesomeoneview.coffee
+++ client/admin/lib/views/invitations/invitesomeoneview.coffee
@@ -228,6 +228,11 @@

     return newInvitations

+
+  sendInvitations: (invites, pendingInvites) ->
+
+    return  unless invites.length
+
     remote.api.JInvitation.create invitations: invites, (err) =>
       if err
         return new KDNotificationView
@@ -238,12 +243,19 @@

       @createInitialInputs()

+      title = "Invitation is sent to <strong>#{invites[0].email}</strong>"
+
+      if invites.length > 1 or pendingInvites?.length
+        title = 'All invitations are sent.'
+
       new KDNotificationView
-        title    : 'Invitations are sent to new members.'
+        title    : title
         duration : 5000

       @confirmModal?.destroy()
       @confirmModal = null
+      @resendInvitationConfirmModal?.destroy()
+      @resendInvitationConfirmModal = null
       @emit 'NewInvitationsAdded'


:art:

:art:

--- src/install.coffee
+++ src/install.coffee
@@ -475,14 +475,14 @@

     @getResourcePath (resourcePath) ->
       try
-        CoffeeCache = require(path.join(resourcePath, 'src', 'coffee-cache'))
+        CompileCache = require(path.join(resourcePath, 'src', 'coffee-cache'))

         onDirectory = (directoryPath) ->
           path.basename(directoryPath) isnt 'node_modules'

         onFile = (filePath) ->
           try
-            CoffeeCache.addPathToCache(filePath)
+            CompileCache.addPathToCache(filePath)

         fs.traverseTreeSync(packageDirectory, onFile, onDirectory)
       callback(null)


im not happy about this, but .update messes with bindings

--- src/js/view.coffee
+++ src/js/view.coffee
@@ -18,18 +18,19 @@
     @_position = store.get('position') || {x:0, y:0}
     @_open     = store.get('open')
     @_events   =
+      'save-session'              : @controller.saveSession
+      'cancel-session'            : @hideSaveSession
+      'toggle-save'               : @toggleSaveSession
+    @_actions =
       'peteshow-hide'             : @hide
       'fill-out-forms'            : @controller.fillOutForms
       'fill-out-forms-and-submit' : @controller.fillOutFormsAndSubmit
-      'save-session'              : @controller.saveSession
-      'cancel-session'            : @hideSaveSession
-      'toggle-save'               : @toggleSaveSession

   render: ->
     $('body').append(indexTemplate)

     @_positionWindow(store.get('position'))
-    @_createEvents(@_events)
+    @_createEvents()

     @update()
     @open(@_open)
@@ -45,13 +46,20 @@

     $(@$sessions).html(template)

-  _createEvents: (events) ->
-    # menu actions
-    for key, value of events
+  _createEvents: () =>
+    # events
+    for key, value of @_events
       $('body').on 'click', "[data-action='#{key}']", (e) =>
         e.preventDefault()
         e.stopPropagation()
-        events["#{e.currentTarget.dataset.action}"]()
+        @_events["#{e.currentTarget.dataset.action}"]()
+
+    # actions
+    for key, value of @_actions
+      $("[data-action='#{key}']").on 'click', (e) =>
+        e.preventDefault()
+        e.stopPropagation()
+        @_actions["#{e.currentTarget.dataset.action}"]()

     # switching sessions
     $('body').on 'change', "#{@$tools} input:radio", (e) =>


Was erroring *sometimes*; not sure why...

--- components/artwork_filter/models/selected.coffee
+++ components/artwork_filter/models/selected.coffee
@@ -18,7 +18,7 @@
       return 'For Sale'
     else
       label = if filterHash[type] then filterHash[type][string] else ''
-      name = if label['name'] then label['name'] else deslugify(string)
+      name = if label?['name'] then label['name'] else deslugify(string)

   isActive: (value) ->
     (_.find _.values(@attributes), (val) -> String(val) is String(value))?


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -131,7 +131,8 @@
     borderWidth - Width of the meter's border
     color - Color of the meter's inner rectangle
     nameColor - Color of the property name displayed above the meter
-    font - Size and style of the meter's font
+    font - Size and style of the meter's font
+    height -
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


better error message when calling an abstract method

--- src/methods.coffee
+++ src/methods.coffee
@@ -197,6 +197,8 @@
           util.java_throw runtime_state, 'java/lang/Error', "native method NYI: #{sig}"
         finally
           runtime_state.meta_stack.pop()
+    else if @access_flags.abstract
+      throw "called abstract method: #{sig}"
     else
       @run_bytecode runtime_state, padding
     cf = runtime_state.curr_frame()


fix: konnector ameli

fix: konnector ameli

--- server/konnectors/ameli.coffee
+++ server/konnectors/ameli.coffee
@@ -72,10 +72,11 @@

             # Second request to authenticate
             request loginOptions, (err, res, body) ->
+                $ = cheerio.load body
                 if err
                     log.error err
                     next 'bad credentials'
-                else if body.indexOf('Connexion à mon compte') > -1
+                else if $("#id_lien_deco").length != 1
                     log.error 'Authentication error'
                     next 'bad credentials'
                 else


Fix lead scoring algorithm

Fix lead scoring algorithm

--- server/middleware/users.coffee
+++ server/middleware/users.coffee
@@ -134,19 +134,13 @@
     trialRequest = yield TrialRequest.findOne(applicant: mongoose.Types.ObjectId(req.user.id))
     if trialRequest
       nces_district_students = trialRequest.get('properties').nces_district_students
-      numStudentsTotal = trialRequest.get('properties').numStudentsTotal
-      # nces_district_students takes priority over self-reported number
-      if nces_district_students > 5000
+      numStudents = trialRequest.get('properties').numStudents
+      if numStudents in ['101-200', '201-500', '501-1000', '1000+']
         return res.status(200).send({ priority: 'high' })
-      else if nces_district_students > 1000
+      else if numStudents in ['11-50', '51-100']
         return res.status(200).send({ priority: 'medium' })
-      else if nces_district_students > 0
-        return res.status(200).send({ priority: 'low' })
-      else if numStudentsTotal in ['5,000-10,000', '10,000+']
-        return res.status(200).send({ priority: 'high' })
-      else if numStudentsTotal in ['1,000-5,000']
-        return res.status(200).send({ priority: 'medium' })
-      else if numStudentsTotal in ['1-500', '500-1,000']
+      else if numStudents in ['1-10']
+        # this is the only outcome specifically used; determines if we try to sell them starter licenses
         return res.status(200).send({ priority: 'low' })
     return res.status(200).send({ priority: undefined })


Grammers

Grammers

--- server/routes/api/users.coffee
+++ server/routes/api/users.coffee
@@ -64,7 +64,7 @@
         from: 'Buckets <noreply@buckets.io>'
         subject: 'Buckets Password Reset'
         text: """
-          You are receiving this because you (or someone else) have requested the reset of the password for your account.\n
+          You are receiving this because you (or someone else) has requested the reset of the password for your account.\n
           Please click on the following link, or paste this into your browser to complete the process:\n
           http://#{req.headers.host}/#{config.buckets.adminSegment}/reset/#{token}\n\n
           If you did not request this, please ignore this email and your password will remain unchanged.\n


fixed code style on servers.lib.server.amqp.coffee

--- servers/lib/server/amqp.coffee
+++ servers/lib/server/amqp.coffee
@@ -1,6 +1,6 @@
-amqp   = require "amqp"
-{argv} = require 'optimist'
-KONFIG = require('koding-config-manager').load("main.#{argv.c}")
-{mq }  = KONFIG
+amqp     = require 'amqp'
+{ argv } = require 'optimist'
+KONFIG   = require('koding-config-manager').load("main.#{argv.c}")
+{ mq }   = KONFIG

-module.exports = amqp.createConnection mq, reconnect: no
+module.exports = amqp.createConnection mq, { reconnect: no }


Fixed returning deck not get updated

Fixed returning deck not get updated

--- views/components/main/parts/mission-panel.cjsx
+++ views/components/main/parts/mission-panel.cjsx
@@ -74,7 +74,7 @@
           mi = new MissionInfo deck.api_name
           switch deck.api_mission[0]
             when 0 then mi.setInPort()
-            when 1, 2
+            when 1, 2, 3  # 1: In mission  2: Just returned  3: Returning
               mi.setInMission deck.api_mission[1], deck.api_mission[2]
           mi
         if !_.isEqual missions, @state.missions


operations: ManageNodeSubscriptions refactor for efficiency

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -773,21 +773,26 @@
     requiredAffiliation: 'owner'

     privilegedTransaction: (t, cb) ->
-        async.series @req.subscriptions.map(({user, subscription}) =>
-            (cb2) =>
-                async.waterfall [(cb3) =>
-                    t.setSubscription @req.node, user, null, subscription, cb2
-                , (cb3) =>
-                    t.getAffiliation @req.node, user, cb3
-                , (affiliation, cb3) =>
+        defaultAffiliation = null
+        async.waterfall [(cb2) =>
+            t.getConfig @req.node, (error, config) =>
+                defaultAffiliation = config.defaultAffiliation or 'member'
+                cb2(error)
+        , (cb2) =>
+            async.forEach @req.subscriptions
+            , ({user, subscription}, cb3) =>
+                async.waterfall [(cb4) =>
+                    t.setSubscription @req.node, user, null, subscription, cb4
+                , (cb4) =>
+                    t.getAffiliation @req.node, user, cb4
+                , (affiliation, cb4) =>
                     if affiliation is 'none'
-                        t.getConfig @req.node, (error, config) =>
-                            defaultAffiliation = config.defaultAffiliation or 'member'
-                            t.setAffiliation @req.node, user, defaultAffiliation, cb3
+                        t.setAffiliation @req.node, user, defaultAffiliation, cb4
                     else
-                        cb3()
-                ], cb2
-        ), cb
+                        cb4()
+                ], cb3
+            , cb2
+        ], cb

     notification: ->
         @req.subscriptions.map ({user, subscription}) =>


point sql() at dataset when used in a view

--- shared/code/scraperwiki.coffee
+++ shared/code/scraperwiki.coffee
@@ -35,9 +35,10 @@

 scraperwiki.sql = (sql, success, error) ->
   settings = scraperwiki.readSettings()
-  token = settings.source.token
+  # Points to the dataset box, when used from either the dataset or a view.
+  boxSettings = settings.target ? settings.source
   options =
-    url: "#{window.location.protocol}//#{window.location.host}/#{scraperwiki.boxName}/#{token}/sqlite"
+    url: "#{boxSettings.url}/sql/"
     type: "GET"
     dataType: "json"
     data:


parse string Date from JSON into real Date object

--- server/code/model/dataset.coffee
+++ server/code/model/dataset.coffee
@@ -66,6 +66,11 @@
   validate: ->
     return 'no tool' unless @tool? and @tool.length > 0
     return 'no display name' unless @displayName? and @displayName.length > 0
+
+  save: (callback) ->
+    if @toBeDeleted?
+      @toBeDeleted = new Date(@toBeDeleted)
+    super callback

   updateStatus: (status, callback) ->
     @status =


Don't destroyPersisted on initial page load

--- resources/assets/lib/react-turbolinks.coffee
+++ resources/assets/lib/react-turbolinks.coffee
@@ -21,6 +21,7 @@

 export class ReactTurbolinks
   constructor: (@components = {}) ->
+    @navigated = false
     @targets = []
     $(document).on 'turbolinks:load', @destroyPersisted
     $(document).on 'turbolinks:load', @boot
@@ -37,6 +38,8 @@

   destroy: =>
+      @navigated = true
+
       for own _name, component of @components
         for target in component.targets
           continue if target.dataset.reactTurbolinksLoaded != '1'
@@ -45,6 +48,8 @@

   destroyPersisted: =>
+    return unless @navigated
+
     ReactDOM.unmountComponentAtNode(target) while target = @targets.pop()


removing middleware next

removing middleware next

--- lib/CompiledFile.coffee
+++ lib/CompiledFile.coffee
@@ -46,8 +46,6 @@
       if req.method is 'GET' and req.originalUrl is @route + ".map" and @debug
         @responder(@compiledSourceMap, req, res)

-      next()
-

   onBuildError: (msg) ->


Reformat Bootstrap.

Reformat Bootstrap.

--- snippets/bootstrap3_forms.cson
+++ snippets/bootstrap3_forms.cson
@@ -1,140 +1,61 @@
 '.text.html.php.blade, .text.html, .text.html.php':
-
   "Form Group":
     prefix:"bootstrap3:formgroup"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 form-group + label'
-    body:"""
-

-        ${2:Form Title}
-        $3
-

-      $6
-    """
-
+    body:'
\n\t${2:Form Title}\n\t$3\n
\n$6'
   "Button Group":
     prefix:"bootstrap3:buttonGroup"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Button Group'
-    body:"""
-

-        <button type="button" class="btn btn-default">Left</button>
-        <button type="button" class="btn btn-default">Middle</button>
-        <button type="button" class="btn btn-default">Right</button>
-

-    """
-
+    body:'
\n\t<button type="button" class="btn btn-default">Left</button>\n\t<button type="button" class="btn btn-default">Middle</button>\n\t<button type="button" class="btn btn-default">Right</button>\n
'
   "Form tag":
     prefix:"bootstrap3:form"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Form declaration'
-    body:"""
-      <form${1: class="${2:form-inline }${3:form-horizontal }"}>
-        $4
-      </form>
-    """
-
+    body:'<form${1: class="${2:form-inline }${3:form-horizontal }"}>\n\t$4\n</form>'
   "Text input":
     prefix:"bootstrap3:input"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Input'
-    body:"""
-      <input ${1:type="$2" }${3:class="${4:form-control }${5:input-${6:lg}}$7" }${8:placeholder="$9" }${10:disabled}>
-      $11
-    """
-
+    body:'<input ${1:type="$2" }${3:class="${4:form-control }${5:input-${6:lg}}$7" }${8:placeholder="$9" }${10:disabled}>\n$11'
   "Textarea input":
     prefix:"bootstrap3:textarea"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Text area'
-    body:"""
-      <textarea ${1:class="form-control$2" }${3:placeholder="$4" }${5:disabled}>$6</textarea>
-      $7
-    """
-
+    body:'<textarea ${1:class="form-control$2" }${3:placeholder="$4" }${5:disabled}>$6</textarea>\n$7'
   "Checkbox input":
     prefix:"bootstrap3:checkbox"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Checkbox'
-    body:"""
-

-
-          <input type="checkbox" ${3:name="$4" }${5:value="$5" }${6:checked }${7:disabled}>
-          $8
-
-

-      $9
-    """
-
+    body:'
\n\t\n\t\t<input type="checkbox" ${3:name="$4" }${5:value="$5" }${6:checked }${7:disabled}>\n\t\t$8\n\t\t\n
\n$9'
   "Checkbox input Inline":
     prefix:"bootstrap3:checkboxinline"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Checkbox inline'
-    body:"""
-

-
-          <input type="checkbox" ${2:name="$3" }${4:value="$5" }${6:checked }${7:disabled}>
-          $8
-
-

-      $8
-    """
-
+    body:'
\n\t\n\t\t<input type="checkbox" ${2:name="$3" }${4:value="$5" }${6:checked }${7:disabled}>\n\t\t$8\n\t\n
\n$8'
   "Radio input":
     prefix:"bootstrap3:radio"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Radio button'
-    body:"""
-

-
-          <input type="radio" ${3:name="$4" }${5:value="$5" }${6:checked }${7:disabled}>
-          $8
-
-

-      $9
-    """
-
+    body:'
\n\t\n\t\t<input type="radio" ${3:name="$4" }${5:value="$5" }${6:checked }${7:disabled}>\n\t\t$8\n\t\n
\n$9'
   "Inline Radio input":
     prefix:"bootstrap3:radioinline"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Radio button inline'
-    body:"""
-

-
-        <input type="radio" ${2:name="$3" }${4:value="$5" }${6:checked }${7:disabled}>
-          $7
-
-

-      $8
-    """
-
+    body:'
\n\t\n\t<input type="radio" ${2:name="$3" }${4:value="$5" }${6:checked }${7:disabled}>\n\t\t$7\n\t\n
\n$8'
   "Select input":
     prefix:"bootstrap3:select"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Select input'
-    body:"""
-      <select${1: class="form-control$2"}${3: disabled}>
-        $4
-      </select>
-      $5
-    """
-
+    body:'<select${1: class="form-control$2"}${3: disabled}>\n\t$4\n</select>\n$5'
   "Fieldset":
     prefix:"bootstrap3:fieldset"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Fieldset'
-    body:"""
-      <fieldset${1: class="$2"}>
-        $3
-      </fieldset>
-      $4
-    """
-
+    body:'<fieldset${1: class="$2"}>\n\t$3\n</fieldset>\n$4'
   "Button":
     prefix:"bootstrap3:button"
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Bootstrap 3 Button'
-    body:"""
-      <button type="${1:submit}"${2: class="${3:btn }${4:btn-${5:lg} }${6:btn-${7:default} }$8"}>$9</button>
-      $10
-    """
+    body:'<button type="${1:submit}"${2: class="${3:btn }${4:btn-${5:lg} }${6:btn-${7:default} }$8"}>$9</button>\n$10'


unused feedback code

unused feedback code

--- apps/auction_lots/client/view.coffee
+++ apps/auction_lots/client/view.coffee
@@ -11,7 +11,6 @@
   events:
     'click .auction-lot-image-zoom': 'zoomImage'
     'click .auction-lot-sale-signup': 'signUp'
-    'click .auction-lots-feedback-link': 'feedback'
     'click .auction-lot': 'onRowClick'
     'click a[data-client]': 'intercept'

@@ -60,10 +59,6 @@
     e.preventDefault()
     mediator.trigger 'open:auth', mode: 'register', copy: 'Sign up to see full auction records — for free'

-  feedback: (e) ->
-    e.preventDefault()
-    mediator.trigger 'open:feedback'
-
   onRowClick: (e) ->
     return if @user
     e.stopPropagation()


Minor Enhancement to AutoComplete

Minor Enhancement to AutoComplete

--- lib/autocomplete.coffee
+++ lib/autocomplete.coffee
@@ -34,7 +34,7 @@
   @IsInstance:(Result,Editor)->
     Buffer = Editor.getBuffer().lineForRow(Editor.getCursorBufferPosition().row).split('->');
     return false if Buffer.length is 1
-    Buffer = Buffer.splice(-1)[0].split(';')[0].split(',')[0].split(')')[0].split('(')[0]
+    Buffer = Buffer.splice(-1)[0].split(';')[0].split(',')[0].split(')')[0].split('(')[0].trim()
     return false if Buffer.length is 0
     for word in @InstBlackList
       return false if Buffer.indexOf(word) isnt -1


Add test reopening closed document in a connection

--- test/client.coffee
+++ test/client.coffee
@@ -498,6 +498,20 @@
           doc2.close ->
             test.done()

+  'Opening a closed document reopens the document': (test) ->
+    @c.open @name, (error, doc1) =>
+      test.ifError error
+      test.ok doc1
+      test.equal doc1.state, 'open'
+      doc1.close =>
+        @c.open @name, 'text', (error, doc2) ->
+          test.ifError error
+          test.ok doc2
+          test.equal doc2.state, 'open'
+          test.equal doc1, doc2
+          doc2.close ->
+            test.done()
+
    # ** This is missing tests for submitOp receiving an error through its callback

 # This isn't working yet. I might have to rethink it.


allow attaching multiple view

allow attaching multiple view

--- lib/view/manager.coffee
+++ lib/view/manager.coffee
@@ -4,10 +4,11 @@
 PanelView = require './panel'
 PathQueryView = require './path-query'
 InputBoxView = require './input-box'
+InputMethodView = require './input-method'

 class ViewManager extends EventEmitter

-  attachedView: null
+  attachedView: []

   constructor: ->

@@ -15,29 +16,39 @@
     @panel = new PanelView
     @pathQuery = new PathQueryView
     @inputBox = new InputBoxView
+    @inputMethod = new InputMethodView

   attachInputBox:  (callback) -> @attach 'inputBox', callback
   attachPathQuery: (callback) -> @attach 'pathQuery', callback
   attachPanel:                -> @attach 'panel'

+  # we shall not detach other views then invoking input method
+  # so we won't apply it to @attach (since it will detach all the other views first)
+  attachInputMethod: (callback) ->
+    @inputMethod.attach()
+    @attachedView.push @inputMethod
+
   #
   #   attach & detach
   #

-  attach: (view, callback) ->
+  attach: (viewName, callback) ->
     @detach()
-    @attachedView = @[view]
+
+    view = @[viewName]
+
+    @attachedView.push view

     if callback
-      @attachedView.attach callback
+      view.attach() callback
     else
-      @attachedView.attach()
+      view.attach()

   detach: ->
-    if @attachedView isnt null
-      @attachedView.detach()
-      # put the focus back to the current view
-      atom.workspaceView.focus()
+    @attachedView.forEach (view) ->
+      view.detach()
+    # put the focus back to the current view
+    atom.workspaceView.focus()

 module.exports = ViewManager