Hacky way to enable desired syntax

Hacky way to enable desired syntax

--- src/netrunner.coffee
+++ src/netrunner.coffee
@@ -6,10 +6,12 @@
 #
 # Commands:
 #  hubot ancur {query} - responds with card info from ANCUR and shows a url
-#  hubot nrdb {card_attribute} {query} - responds with card info from netrunner db
-#  hubot nrdb {card_attribute} {query} -l - responds with list of first 10 matches
-#  hubot nrdb {card_attribute} {query} -n - responds with only card image, no text
-#  hubot nrdbq {query | attribute:query [ [OR|AND] attribute2:query2] }
+#  hubot nrdb {card_name} - responds with card info from netrunner db
+#  hubot nrdb {card_name} -l - responds with list of first 10 matches
+#  hubot nrdb {card_name} -n - responds with only card image, no text
+#  hubot nrdbq {card_attribute} {query} - responds with card info from netrunner db
+#  hubot nrdbq {card_attribute} {query} -l - responds with list of first 10 matches
+#  hubot nrdbq {card_attribute} {query} -n - responds with only card image, no text
 #
 # Author:
 #  omardelarosa
@@ -138,6 +140,52 @@
   else
     noText = false
   opts = { noText: noText, listTen: listTen }
+  key = 'title'
+  query = matchData.slice(2).join(' ')
+  url = 'http://netrunnerdb.com/api/cards/'
+  msg.http(url)
+    .get() (err, res, body) ->
+      if err
+        # console.log err
+        msg.send "Error fetching card data."
+        return
+      else
+        try
+          cardList = JSON.parse(body)
+          lowerCaseQuery = query.toLowerCase()
+          results = _.filter cardList, (card) ->
+            if card[key] and card[key].toLowerCase().search(lowerCaseQuery) != -1
+              return true
+            else
+              return false
+          if results.length > 0
+            if opts.listTen
+              results.slice(0, 10).forEach (card) ->
+                formatNRDBResponse msg, card, opts
+            else
+              formatNRDBResponse msg, results[0], opts
+          else
+            msg.send 'No results matched your query "' + key + ': ' + query + '"'
+        catch e
+          console.log e
+          console.log e.stack
+          msg.send "Error parsing response from NetRunner DB"
+
+nrdbq = (msg) ->
+  matchData = msg.match[0].split(' ')
+  indexOfListFlag = matchData.indexOf('-l')
+  indexOfNoTextFlag = matchData.indexOf('-n')
+  if indexOfListFlag != -1
+    listTen = true
+    matchData.splice(indexOfListFlag, 1)
+  else
+    listTen = false
+  if indexOfNoTextFlag != -1
+    noText = true
+    matchData.splice(indexOfNoTextFlag, 1)
+  else
+    noText = false
+  opts = { noText: noText, listTen: listTen }
   key = matchData[2]
   query = matchData.slice(3).join(' ')
   url = 'http://netrunnerdb.com/api/cards/'
@@ -168,21 +216,6 @@
           console.log e
           console.log e.stack
           msg.send "Error parsing response from NetRunner DB"
-
-nrdbq = (msg) ->
-  q = msg.match[1]
-  url = "http://netrunner.delarosa.io/search/"
-  concatUrl = "#{url}#{q}"
-  msg.http(concatUrl)
-    .get() (err, res, body) ->
-      if err
-        console.log err
-      content = JSON.parse body
-      if content.length == 0
-        msg.send "No matches found for #{q}"
-      else
-        content.forEach (c) ->
-          formatNRDBResponse(msg, c, {})

 fetchCard = (msg) ->
@@ -216,5 +249,5 @@
   robot.respond /nrdb (.*)\b/i, (msg) ->
     nrdb(msg)

-  robot.respond /nrdbq (.*)/i, (msg) ->
+  robot.respond /nrdbq (.*)\b/i, (msg) ->
     nrdbq(msg)


Fixed excessive ) in macro snippets

Fixed excessive ) in macro snippets

--- snippets/language-sqf.cson
+++ snippets/language-sqf.cson
@@ -268,7 +268,7 @@
   'GVAR':
     'prefix': 'GVAR'
     'body': """
-      GVAR(${1:[variable]})
+      GVAR(${1:variable})
     """
     'description': 'Expands to: prefix_component_variable'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -276,7 +276,7 @@
   'QGVAR':
     'prefix': 'QGVAR'
     'body': """
-      QGVAR(${1:[variable]})
+      QGVAR(${1:variable})
     """
     'description': 'Expands to: "prefix_component_variable"'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -284,7 +284,7 @@
   'EGVAR':
     'prefix': 'EGVAR'
     'body': """
-      EGVAR(${1:[component]}),${2:[variable]})
+      EGVAR(${1:component},${2:variable})
     """
     'description': 'Expands to: prefix_component_variable'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -292,7 +292,7 @@
   'QEGVAR':
     'prefix': 'QEGVAR'
     'body': """
-      QEGVAR(${1:[component]}),${2:[variable]})
+      QEGVAR(${1:component},${2:variable})
     """
     'description': 'Expands to: "prefix_component_variable"'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -300,7 +300,7 @@
   'FUNC':
     'prefix': 'FUNC'
     'body': """
-      FUNC(${1:[functionName]})
+      FUNC(${1:functionName})
     """
     'description': 'Expands to: prefix_component_fnc_functionName'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -308,7 +308,7 @@
   'EFUNC':
     'prefix': 'EFUNC'
     'body': """
-      EFUNC(${1:[component]}),${2:[functionName]})
+      EFUNC(${1:component},${2:functionName})
     """
     'description': 'Expands to: prefix_component_fnc_functionName'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -316,7 +316,7 @@
   'DFUNC':
     'prefix': 'DFUNC'
     'body': """
-      DFUNC(${1:[functionName]})
+      DFUNC(${1:functionName})
     """
     'description': 'Expands to: prefix_component_fnc_functionName'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -324,7 +324,7 @@
   'DEFUNC':
     'prefix': 'DEFUNC'
     'body': """
-      DEFUNC(${1:[component]}),${2:[functionName]})
+      DEFUNC(${1:component},${2:functionName})
     """
     'description': 'Expands to: prefix_component_fnc_functionName'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -332,7 +332,7 @@
   'QFUNC':
     'prefix': 'QFUNC'
     'body': """
-      QFUNC(${1:[functionName]})
+      QFUNC(${1:functionName})
     """
     'description': 'Expands to: "prefix_component_fnc_functionName"'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -340,7 +340,7 @@
   'QEFUNC':
     'prefix': 'QEFUNC'
     'body': """
-      QEFUNC(${1:[component]}),${2:[functionName]})
+      QEFUNC(${1:component},${2:functionName})
     """
     'description': 'Expands to: "prefix_component_fnc_functionName"'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -348,7 +348,7 @@
   'QUOTE':
     'prefix': 'QUOTE'
     'body': """
-      QUOTE(${1:[content]})
+      QUOTE(${1:content})
     """
     'description': 'Expands to: "content"'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -356,7 +356,7 @@
   'LSTRING':
     'prefix': 'LSTRING'
     'body': """
-      LSTRING(${1:[string]})
+      LSTRING(${1:string})
     """
     'description': 'Expands to: "STR_prefix_component_fnc_string"'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -364,7 +364,7 @@
   'ELSTRING':
     'prefix': 'ELSTRING'
     'body': """
-      ELSTRING(${1:[component]}),${2:[string]})
+      ELSTRING(${1:component},${2:string})
     """
     'description': 'Expands to: "STR_prefix_component_fnc_string"'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -372,7 +372,7 @@
   'CSTRING':
     'prefix': 'CSTRING'
     'body': """
-      CSTRING(${1:[string]})
+      CSTRING(${1:string})
     """
     'description': 'Expands to: "$STR_prefix_component_fnc_string"'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'
@@ -380,7 +380,7 @@
   'ECSTRING':
     'prefix': 'ECSTRING'
     'body': """
-      ELSTRING(${1:[component]}),${2:[string]})
+      ELSTRING(${1:component},${2:string})
     """
     'description': 'Expands to: "$STR_prefix_component_fnc_string"'
     'descriptionMoreURL': 'http://ace3mod.com/wiki/development/coding-guidelines.html#4-macro-usage'


re-wired wideScreen logic

re-wired wideScreen logic

--- client/app/Applications/Activity.kdapplication/widgets/codesharewidget.coffee
+++ client/app/Applications/Activity.kdapplication/widgets/codesharewidget.coffee
@@ -97,9 +97,9 @@
         wideScreenHeight = viewport / 2

         if @isWideScreen
+          @unsetWideScreen wideScreenHeight
+        else
           @setWideScreen wideScreenHeight
-        else
-          @unsetWideScreen wideScreenHeight

     @labelCSSContent = new KDLabelView
       title : "CSS Options"
@@ -539,7 +539,7 @@

     @codeShareContainer.showPane @codeShareResultPane

-  setWideScreen:(wideScreenHeight)=>
+  unsetWideScreen:(wideScreenHeight)=>

           @$(".formline-codeshare").css "margin-left":"168px"
           @$(".formline-codeshare").css "margin-right":"0px"
@@ -558,7 +558,7 @@
           @isWideScreen = no
           @wideScreenBtn.setTitle "Increase Editor Size"

-    unsetWideScreen:(wideScreenHeight)=>
+  setWideScreen:(wideScreenHeight)=>
           @$(".formline-codeshare").css "margin-left":"10px"
           @$(".formline-codeshare").css "margin-right":"10px"
           @$(".code-snip-container").css "max-width":"100%"
@@ -637,6 +637,7 @@
       @codeShareCloseButton?.hide()
       @codeShareResultButton?.setTitle "Run"
     @tagController.reset()
+    @unsetWideScreen(undefined)

   switchToEditView:(activity)->
     @submitBtn.setTitle "Edit your Code Share"


machinelist: when error occured, set loadExpandedMachinestore default

--- client/home/lib/virtualmachines/components/machineslist/machinedetails.coffee
+++ client/home/lib/virtualmachines/components/machineslist/machinedetails.coffee
@@ -272,6 +272,7 @@
         kd.singletons.router.handleRoute "/Home/stacks/virtual-machines/#{@state.machineLabel}"
       .catch (err) =>
         @setState { machineLabel: @props.machine.get 'label' }
+        EnvironmentFlux.actions.loadExpandedMachineLabel @props.machine.get 'label'
         new kd.NotificationView { title: 'Something went wrong', duration: 2000 }


new: Data.check added

new: Data.check added

--- src/prim-data.coffee
+++ src/prim-data.coffee
@@ -3,7 +3,7 @@
 {match, show, samples, sample} = require './typespec'

 class Data
-	constructor: ({spec, samples, name, discription}) ->
+	constructor: ({spec, check, name, samples, discription}) ->
 		if not spec?
 			throw ReferenceError 'spec must be specified for a Data declaration'
 		if samples? and not all(match(spec))(take(100) samples)
@@ -15,14 +15,15 @@
 		return {
 			constructor: Data
 			spec
+			check
+			name
 			samples
-			name
 			discription
 		}

 instance('TypeSpec')(Data).where
-	match: ({spec}) -> (v) ->
-		match(spec) v
+	match: ({spec, check}) -> (v) ->
+		match(spec)(v) and (if check? then check(v) else true)
 	show: ({name, spec}) ->
 		name or (show spec)
 	samples: ({spec, samples: ls}) ->


Domain selector in query

Domain selector in query

--- src/quo.query.coffee
+++ src/quo.query.coffee
@@ -9,7 +9,7 @@
     PARENT_NODE = "parentNode"

     $$.query = (domain, selector) ->
-        dom_elements = document.querySelectorAll(selector)
+        dom_elements = domain.querySelectorAll(selector)
         dom_elements = Array::slice.call(dom_elements)
         dom_elements


Only update containingTags when moving to next token

--- src/token-iterator.coffee
+++ src/token-iterator.coffee
@@ -51,6 +51,13 @@
     @containingTags

   moveToSuccessor: ->
+    if @closeTags?
+      for tag in @closeTags
+        @containingTags.splice(@containingTags.lastIndexOf(tag), 1)
+
+    if @openTags?
+      @containingTags.push(@openTags...)
+
     @startScreenPosition = @endScreenPosition
     @startBufferPosition = @endBufferPosition
     @closeTags = null
@@ -116,12 +123,8 @@
         @endScreenPosition = @patchIterator.translateInputPosition(@endBufferPosition)

     if @decorationIterator?
-      if @closeTags?
-        for tag in @closeTags
-          @containingTags.splice(@containingTags.lastIndexOf(tag), 1)
-
       if @isFold()
-        @closeTags = (@closeTags ? []).concat(@containingTags)
+        @closeTags = @containingTags.slice()
         @containingTags.length = 0
         @openTags = null
         @tagsToReopen = @decorationIterator.seek(@getEndBufferPosition())
@@ -136,6 +139,3 @@
           @endBufferPosition = decorationIteratorPosition
           @endScreenPosition = @patchIterator.translateInputPosition(@endBufferPosition)
           @text = @text.substring(0, @endScreenPosition.column - @startScreenPosition.column)
-
-      if @openTags?
-        @containingTags.push(@openTags...)


Rewrite selectors

Rewrite selectors

--- menus/tree-view.cson
+++ menus/tree-view.cson
@@ -94,6 +94,10 @@
     {'label': 'Paste', 'command': 'tree-view:paste'}
   ]

-  'atom-pane[data-active-path]': [
+  'atom-pane[data-active-path] .item-views': [
     {'label': 'Reveal in Tree View', 'command': 'tree-view:reveal-active-file'}
   ]
+
+  'atom-pane[data-active-path] .tab.active': [
+    {'label': 'Reveal in Tree View', 'command': 'tree-view:reveal-active-file'}
+  ]


clean email on vote record

clean email on vote record

--- models/models.coffee
+++ models/models.coffee
@@ -234,7 +234,7 @@
     @completeness = parseInt options?.completeness

     @comment = options?.comment
-    @email = options?.email
+    @email = options?.email?.trim()?.toLowerCase()

     @ipAddress = options?.ipAddress
     @userAgent = options?.userAgent


can't create a new contestant unless invited. #142

--- models/person.coffee
+++ models/person.coffee
@@ -39,10 +39,16 @@
             foundUser.updateWithGithub ghUser, (err, updatedUser) ->
               return promise.fail err if err
               promise.fulfill updatedUser
+          else if sess.invite
+            Team = mongoose.model 'Team'
+            Team.findOne 'invites.code': sess.invite, (err, team) ->
+              return promise.fail err if err
+              return promise.fulfill(id: null) unless team
+              Person.createWithGithub ghUser, accessTok, (err, createdUser) ->
+                return promise.fail err if err
+                promise.fulfill createdUser
           else
-            Person.createWithGithub ghUser, accessTok, (err, createdUser) ->
-              return promise.fail err if err
-              promise.fulfill createdUser
+            promise.fulfill id: null
         promise
   twitter:
     everyauth:


Fix for @elements and @element, add jQuery method

--- js/u.coffee
+++ js/u.coffee
@@ -56,25 +56,25 @@
         # setter
         if( n and v )
           return @each (i,el) -> el.style[n] = v
-        else if typeof n is "object" and @element
+        else if typeof n is "object" and @el
           return @each (i,el) ->
             for k,val in n
               el.style[k] = val
         # getter
-        if typeof n is "string" and @element
-          return @element.style[n]
+        if typeof n is "string" and @el
+          return @el.style[n]

       attr: (n,v) ->
         # setter
         if( n and v )
           return @each (i,el) -> el.setAttribute(n,v)
-        else if typeof n is "object" and @element
+        else if typeof n is "object" and @el
           return @each (i,el) ->
             for k,val in n
               el.setAttribute(k,val)
         # getter
-        if typeof n is "string" and @element
-          return @element.getAttribute(n)
+        if typeof n is "string" and @el
+          return @el.getAttribute(n)

       each: (cb) ->
         if @els
@@ -96,25 +96,29 @@
       # Returns style or computed style
       ###
       style: (computed) ->
-        return unless @element
-        return window.getComputedStyle(@element) if computed
-        @element.style
+        return unless @el
+        return window.getComputedStyle(@el) if computed
+        @el.style

       height: (a) ->
         if a
           return @each (i,e) -> e.style.height = parseInt(a) + 'px'
         else
-          return unless @element
-          return parseInt(@element.style.height) if @element.style.height
+          return unless @el
+          return parseInt(@el.style.height) if @el.style.height
           parseInt(@style(1).height)

       width: (a) ->
         if a
           @each (i,e) -> e.style.width = parseInt(a) + 'px'
         else
-          return unless @element
-          return parseInt(@element.style.width) if @element.style.width
+          return unless @el
+          return parseInt(@el.style.width) if @el.style.width
           parseInt(@style(1).width)
+
+      # convert element or element collection to jQuery object.
+      jQuery: () -> $( @els or @el )
+
     window.u = u
   )()
   ###


Cleaned up imports

Cleaned up imports

--- spec/spark-ide-spec.coffee
+++ spec/spark-ide-spec.coffee
@@ -1,10 +1,12 @@
 {WorkspaceView} = require 'atom'
 _s = require 'underscore.string'
+spark = require 'spark'
+fs = require 'fs-plus'
 SettingsHelper = require '../lib/utils/settings-helper'
 SerialHelper = require '../lib/utils/serial-helper'
 SpecHelper = require '../lib/utils/spec-helper'
+utilities = require '../lib/vendor/utilities'
 SparkStub = require './stubs/spark'
-spark = require 'spark'

 describe 'Main Tests', ->
   activationPromise = null
@@ -246,8 +248,6 @@
         atom.project.setPath oldPath

         # Remove firmware files
-        utilities = require '../lib/vendor/utilities'
-        fs = require 'fs-plus'
         for file in fs.listSync(__dirname + '/data/sampleproject')
           if utilities.getFilenameExt(file).toLowerCase() == '.bin'
             fs.unlinkSync file
@@ -338,3 +338,10 @@
       jasmine.unspy atom.workspaceView, 'trigger'
       SettingsHelper.clearCurrentCore()
       SettingsHelper.clearCredentials()
+
+    it 'tests one firmware file', ->
+      SettingsHelper.setCredentials 'foo@bar.baz', '0123456789abcdef0123456789abcdef'
+      SettingsHelper.setCurrentCore '0123456789abcdef0123456789abcdef', 'Foo'
+
+      SettingsHelper.clearCurrentCore()
+      SettingsHelper.clearCredentials()


Revert "removing the tree view core:close test, because cmd-w no longer closes the sidebar."

This reverts commit ab144a6d312765a37aba0bc4bd9dd9d6bc3839f1.

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -193,6 +193,19 @@
       expect(treeView).toBeVisible()
       expect(treeView).not.toMatchSelector(':focus')
       expect(rootView.getActiveEditor().isFocused).toBeTruthy()
+
+  describe "when core:close is triggered on the tree view", ->
+    it "detaches the TreeView, focuses the RootView and does not bubble the core:close event", ->
+      treeView.attach()
+      treeView.focus()
+      rootViewCloseHandler = jasmine.createSpy('rootViewCloseHandler')
+      rootView.on 'core:close', rootViewCloseHandler
+      spyOn(rootView, 'focus')
+
+      treeView.trigger('core:close')
+      expect(rootView.focus).toHaveBeenCalled()
+      expect(rootViewCloseHandler).not.toHaveBeenCalled()
+      expect(treeView.hasParent()).toBeFalsy()

   describe "when a directory's disclosure arrow is clicked", ->
     it "expands / collapses the associated directory", ->


In copy spec, wait for the new path to exist

Previously we were asserting the existence of the new path directly
after the tree view updated. Since mkdirp is async, it's possible to
create the initial directory that updates the tree view without creating
the entire path. This seems to happen occasionally on CI.

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -1482,8 +1482,9 @@
               waitsFor "tree view to update", ->
                 treeView.root.find('> .entries > .directory:contains(new)').length > 0

+              waitsFor "new path to exist", -> fs.existsSync(newPath)
+
               runs ->
-                expect(fs.existsSync(newPath)).toBeTruthy()
                 expect(fs.existsSync(filePath)).toBeTruthy()

           describe "when a file or directory already exists at the target path", ->


Fix GIF

Fix GIF

--- modules/giphy.coffee
+++ modules/giphy.coffee
@@ -8,7 +8,7 @@
   init: =>
     @chance = new Chance()

-    @registerCommand 'giphy', { aliases: ['gif'], allowDM: true }, (msg, q)=>
+    @registerCommand 'giphy', { aliases: ['gif'], allowDM: true }, (msg, q, d)=>
       return unless d.data.allowImages
       try
         { data } = await Giphy.get('search', {


fix a typo in the Function#bind shim; add console.time and console.timeEnd to our console shim.

--- client/Framework/core/KD.coffee
+++ client/Framework/core/KD.coffee
@@ -5,7 +5,7 @@
     if 1 < arguments.length
       args = slice.call arguments, 1
       return -> func.apply context, if arguments.length then args.concat slice.call arguments else args
-    -> if arguments.z then func.apply context, arguments else func.call context
+    -> if arguments.length then func.apply context, arguments else func.call context

 Function::swiss = (parent, names...)->
   for name in names
@@ -48,10 +48,10 @@
     @remote.api.JAccount.impersonate username, (err)->
       if err then new KDNotificationView title: err.message
       else location.reload()
-#
-#  testKDML:->
-#    {KDMLParser} = Bongo.KDML
-#    kdml = new KDMLParser @classes
+
+  # testKDML:->
+  #   {KDMLParser} = Bongo.KDML
+  #   kdml = new KDMLParser @classes

   debugStates     : {}
   instances       : {}
@@ -193,7 +193,7 @@
   enableLogs:do->
     oldConsole = window.console
     window.console = {}
-    console[method] = noop  for method in ['log','warn','error','trace']
+    console[method] = noop  for method in ['log','warn','error','trace','time','timeEnd']

     enableLogs =->
       window.console = oldConsole


FasterKoding: removed ready notion from router no more necessary

--- client/Main/kodingrouter.coffee
+++ client/Main/kodingrouter.coffee
@@ -15,9 +15,6 @@
     @openRoutes     = {}
     @openRoutesById = {}
     KD.singleton('display').on 'DisplayIsDestroyed', @bound 'cleanupRoute'
-
-    KD.getSingleton('mainController').once 'AccountChanged', =>
-      @utils.defer => @emit 'ready'

     super()

@@ -81,16 +78,14 @@

   openSection:(app, group, query)->

-    @ready =>
-      {appManager, groupsController} = KD.singletons
-      groupsController.ready =>
-        @setPageTitle nicenames[app] ? app
-        handleQuery = appManager.tell.bind appManager, app, "handleQuery", query
-
-        appManager.once "AppCreated", handleQuery  unless appWasOpen = appManager.get app
-        appManager.open app
-
-        handleQuery()  if appWasOpen
+    {appManager} = KD.singletons
+    @setPageTitle nicenames[app] ? app
+    handleQuery = appManager.tell.bind appManager, app, "handleQuery", query
+
+    appManager.once "AppCreated", handleQuery  unless appWasOpen = appManager.get app
+    appManager.open app
+
+    handleQuery()  if appWasOpen

   handleNotFound:(route)->


LocalStorage: add method to get the storage keys

--- client/Main/localstorage.coffee
+++ client/Main/localstorage.coffee
@@ -54,6 +54,9 @@
   getSignature:(key)->
     "koding-#{@_applicationID}-#{@_applicationVersion}-#{key}"

+  getLocalStorageKeys:->
+    return Object.keys storage
+
 class LocalStorageController extends KDController

   constructor:->
@@ -65,4 +68,4 @@
     return @localStorages[key] or= new LocalStorage appName, version

 # Let people can use AppStorage
-KD.classes.LocalStorage = LocalStorage+KD.classes.LocalStorage = LocalStorage


locale fixes

locale fixes

--- client/app/views/account.coffee
+++ client/app/views/account.coffee
@@ -77,10 +77,10 @@
             , 10000

         if form.password1.length < 5
-            showError 'account password change short'
+            showError 'account change password short'

         else if form.password1 isnt form.password2
-            showError 'account password change difference'
+            showError 'account change password difference'

         else

@@ -107,7 +107,7 @@
                             @infoAlert.hide()
                         , 10000
                     else
-                        showError 'account password change error'
+                        showError 'account change password error'

     # Display errors that occured through the error alert.


Show checker output in admin result cells (fix #192)

--- client/components/Submit.coffee
+++ client/components/Submit.coffee
@@ -114,11 +114,14 @@
     render: () ->
         canToggle = "input" of @props.result
         res = []
-        res.push

+        status = @props.result.string_status
+        if @props.result.checker_output
+            status = @props.result.checker_output.substr(0, 70)
+        res.push

             {@props.index}{" "}
                 {@props.copyTest && <FontAwesome name="chevron-circle-down"/>}

-            {@props.result.string_status}
+            {status}
             {@props.result.time/1000}
             {@props.result.max_memory_used}


Add tests for end_of_line

Add tests for end_of_line

--- test/brave-mouse/validate.coffee
+++ test/brave-mouse/validate.coffee
@@ -193,3 +193,67 @@
 						results.should.be.true

 						done()
+
+			describe 'end_of_line', ->
+				after ->
+					mockFs.restore()
+
+				it 'should validate if file uses LF but CRLF is expected', (done) ->
+					prepareFs 'end_of_line', 'crlf', 'end_of_line/lf.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.end_of_line.should.deep.equal
+							expected: 'crlf'
+							is: 'lf'
+
+						done()
+
+				it 'should validate if file uses LF and LF is expected', (done) ->
+					prepareFs 'end_of_line', 'lf', 'end_of_line/lf.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.should.be.true
+
+						done()
+
+				it 'should validate if file uses CRLF but LF is expected', (done) ->
+					prepareFs 'end_of_line', 'lf', 'end_of_line/crlf.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.end_of_line.should.deep.equal
+							expected: 'lf'
+							is: 'crlf'
+
+						done()
+
+				it 'should validate if file uses CRLF and CRLF is expected', (done) ->
+					prepareFs 'end_of_line', 'crlf', 'end_of_line/crlf.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.should.be.true
+
+						done()
+
+				it 'should validate if file uses no newline character and LF is expected', (done) ->
+					prepareFs 'end_of_line', 'lf', 'end_of_line/none.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.should.be.true
+
+						done()
+
+				it 'should validate if file uses no newline character and CRLF is expected', (done) ->
+					prepareFs 'end_of_line', 'crlf', 'end_of_line/none.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.should.be.true
+
+						done()
+
+				it 'should not validate if nothing is expected', (done) ->
+					prepareFs null, null, 'end_of_line/none.txt'
+
+					bmValidate 'file.txt', (err, results) ->
+						results.should.be.true
+
+						done()


Add onboarding modal to appcontroller

Add onboarding modal to appcontroller

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -220,19 +220,13 @@
           machineLabel = machine.label
           splashs      = IDE.splashMarkups

-          tabView      = @activeTabView
-          terminalView = new KDCustomHTMLView partial: splashs.getTerminal nickname
-          terminalPane = tabView.parent.createPane_ terminalView, { name: 'Terminal' }
-
-          finderView   = new KDCustomHTMLView partial: splashs.getFileTree nickname, machineLabel
-          @finderPane.addSubView finderView, '.nfinder .jtreeview-wrapper'
-
-          KD.utils.defer =>
-            @machineStateModal.once 'IDEBecameReady', =>
-              finderView.destroy()
-              tabView.removePane_ terminalPane
-              @createNewTerminal @machineStateModal.getData()
-              @setActiveTabView @ideViews.first.tabView
+          @fakeTabView      = @activeTabView
+          @fakeTerminalView = new KDCustomHTMLView partial: splashs.getTerminal nickname
+          @fakeTerminalPane = @fakeTabView.parent.createPane_ @fakeTerminalView, { name: 'Terminal' }
+
+          @fakeFinderView   = new KDCustomHTMLView partial: splashs.getFileTree nickname, machineLabel
+          @finderPane.addSubView @fakeFinderView, '.nfinder .jtreeview-wrapper'
+
         else
           @createNewTerminal machine
           @setActiveTabView @ideViews.first.tabView
@@ -255,32 +249,59 @@
     computeController.fetchMachines (err, machines) =>
       return KD.showError 'Something went wrong. Try again.'  if err

-      for machine in machines when machine.uid is machineUId
-        machineItem = machine
-
-      if machineItem
-        {state} = machineItem.status
-        machineId = machineItem._id
-
-        if state is 'Running'
-          @mountMachine machineItem
+
+      callback = (machineUId) =>
+        for machine in machines when machine.uid is machineUId
+          machineItem = machine
+
+        if machineItem
+          {state} = machineItem.status
+          machineId = machineItem._id
+
+          if state is 'Running'
+            @mountMachine machineItem
+          else
+            @createMachineStateModal state, container, machineItem
+
+          stateHandler = (event, states) =>
+            if event.status in states
+              unless @machineStateModal
+                @createMachineStateModal state, container, machineItem
+
+          computeController.on "stop-#{machineId}", (event) ->    stateHandler event, [ 'Stopping', 'Stopped' ]
+          computeController.on "destroy-#{machineId}", (event) -> stateHandler event, [ 'Terminated' ]
         else
-          @createMachineStateModal state, container, machineItem
-
-        stateHandler = (event, states) =>
-          if event.status in states
-            unless @machineStateModal
-              @createMachineStateModal state, container, machineItem
-
-        computeController.on "stop-#{machineId}", (event) ->    stateHandler event, [ 'Stopping', 'Stopped' ]
-        computeController.on "destroy-#{machineId}", (event) -> stateHandler event, [ 'Terminated' ]
-      else
-        @machineStateModal = new IDE.MachineStateModal { state: 'NotFound', container } , undefined
+          @machineStateModal = new IDE.MachineStateModal { state: 'NotFound', container } , undefined
+
+
+      { appStorageController } = KD.singletons
+
+      appStorage = appStorageController.storage 'IDE', '1.0.0'
+
+      appStorage.fetchStorage =>
+        hideOnboardingView = appStorage.getValue 'hideOnboardingModal'
+
+        console.log { hideOnboardingView }
+
+        unless hideOnboardingView
+          @onboardingModal = new IDE.OnboardingModal { container }
+          @onboardingModal.once 'OnboardingModalDismissed', -> callback machineUId
+        else
+          callback machineUId
+

   createMachineStateModal: (state, container, machineItem) ->
     @machineStateModal = new IDE.MachineStateModal { state, container }, machineItem
-    @machineStateModal.once 'IDEBecameReady', => @finderPane.finderController.reset()
     @machineStateModal.once 'KDObjectWillBeDestroyed', => @machineStateModal = null
+    @machineStateModal.once 'IDEBecameReady', =>
+      @finderPane.finderController.reset()
+
+      unless @fakesDestroyed
+        @fakeFinderView.destroy()
+        @fakeTabView.removePane_ @fakeTerminalPane
+        @createNewTerminal @machineStateModal.getData()
+        @setActiveTabView @ideViews.first.tabView
+        @fakesDestroyed = yes

   collapseSidebar: ->
     panel        = @workspace.getView()


prevent duplicate onboarding modals

prevent duplicate onboarding modals

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -288,6 +288,9 @@
       appStorage = appStorageController.storage 'IDE', '1.0.0'

       appStorage.fetchStorage =>
+
+        return  if @onboardingModal?
+
         hideOnboardingView = appStorage.getValue 'hideOnboardingModal'

         unless hideOnboardingView


Introduced tab navigation with numbers.

Introduced tab navigation with numbers.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -18,6 +18,7 @@
       'expand sidebar'     : 'expandSidebar'
       'go to left tab'     : 'goToLeftTab'
       'go to right tab'    : 'goToRightTab'
+      'go to tab number'   : 'goToTabNumber'
     keyBindings: [
       { command: 'split vertically',   binding: 'ctrl+alt+v', global: yes }
       { command: 'split horizontally', binding: 'ctrl+alt+h', global: yes }
@@ -27,6 +28,15 @@
       { command: 'expand sidebar',     binding: 'ctrl+alt+e', global: yes }
       { command: 'go to left tab',     binding: 'ctrl+alt+[', global: yes }
       { command: 'go to right tab',    binding: 'ctrl+alt+]', global: yes }
+      { command: 'go to tab number',   binding: 'ctrl+alt+1', global: yes }
+      { command: 'go to tab number',   binding: 'ctrl+alt+2', global: yes }
+      { command: 'go to tab number',   binding: 'ctrl+alt+3', global: yes }
+      { command: 'go to tab number',   binding: 'ctrl+alt+4', global: yes }
+      { command: 'go to tab number',   binding: 'ctrl+alt+5', global: yes }
+      { command: 'go to tab number',   binding: 'ctrl+alt+6', global: yes }
+      { command: 'go to tab number',   binding: 'ctrl+alt+7', global: yes }
+      { command: 'go to tab number',   binding: 'ctrl+alt+8', global: yes }
+      { command: 'go to tab number',   binding: 'ctrl+alt+9', global: yes }
     ]

   constructor: (options = {}, data) ->
@@ -223,6 +233,16 @@

     @activeTabView.showPaneByIndex index + 1

+  goToTabNumber: (keyEvent) ->
+    keyEvent.preventDefault()
+    keyEvent.stopPropagation()
+
+    keyCodeMap    = [ 49, 50, 51, 52, 53, 54, 55, 56, 57 ]
+    requiredIndex = keyCodeMap.indexOf keyEvent.keyCode
+    tabView       = @activeTabView.getPaneByIndex requiredIndex
+
+    @activeTabView.showPaneByIndex requiredIndex
+
   forEachSubViewInIDEViews_: (callback = noop) ->
     for ideView in @ideViews
       for pane in ideView.tabView.panes


Tests: IDE: File: Use deleteFile helper in deleteFolder test.

--- client/test/lib/ide/file.coffee
+++ client/test/lib/ide/file.coffee
@@ -102,7 +102,7 @@
     webPath    = '/home/' + user.username
     selector   = "span[title='" + webPath + '/' + folderData.name + "']"

-    helpers.deleteFolder(browser, folderData.selector)
+    helpers.deleteFile(browser, folderData.selector)
     browser.end()


removes old input tag

removes old input tag

--- app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.coffee
+++ app/assets/javascripts/oxalis/view/action-bar/merge_modal_view.coffee
@@ -64,7 +64,6 @@

-                          <input type="file" name="nmlFile" accept=".nml">

                           Upload NML


Update pagination links to match prototype


Did not not convert the logic to having content

--- app/assets/javascripts/components/inline_edit_text.js.coffee
+++ app/assets/javascripts/components/inline_edit_text.js.coffee
@@ -2,8 +2,12 @@
   editing: false
   isNew: false

+  hasContent: (->
+    !Em.isEmpty(@get('bodyPart.value'))
+  ).property('bodyPart.value')
+
   hasNoContent: (->
-    Em.isEmpty(@get('bodyPart.value'))
+    !@get('hasContent')
   ).property('bodyPart.value')

   focusOnEdit: (->
@@ -21,7 +25,7 @@
       @toggleProperty 'editing'

     save: ->
-      unless @get('hasNoContent')
+      if @get('hasContent')
         if @get('isNew')
           @get('model.body').pushObject(@get('bodyPart'))
           @set('bodyPart', null)


Make each username in InlineUsers link to the userpage

--- app/assets/javascripts/components/overview/inline_users.cjsx
+++ app/assets/javascripts/components/overview/inline_users.cjsx
@@ -6,14 +6,21 @@
   render: ->
     key = @props.title + '_' + @props.role
     user_list = @props.users.map (user) ->
+      link = "https://en.wikipedia.org/wiki/User:#{user.wiki_id}"
       if user.real_name?
-        "#{user.real_name} (#{user.wiki_id}#{if user.email? then " / " + user.email else ""})"
+        displayName = "#{user.real_name} (#{user.wiki_id}#{if user.email? then " / " + user.email else ""})"
       else
-        user.wiki_id
+        displayName = user.wiki_id
+      {displayName}

-    user_list = user_list.join(', ')
-    user_list = if user_list.length > 0 then user_list else 'None'
-    inline_list = {@props.title}: {user_list} if @props.users.length > 0 || @props.editable
+    # This is feels really hacky, but it works.
+    last_user_index = user_list.length - 1
+    inline_user_list = ([user, ', '] for user, index in user_list when index != last_user_index)
+    inline_user_list.push user_list[last_user_index]
+
+    inline_user_list = if user_list.length > 0 then inline_user_list else 'None'
+    if @props.users.length > 0 || @props.editable
+      inline_list = {@props.title}: {inline_user_list}
     allowed = @props.role != 4 || (@props.current_user.role == 4 || @props.current_user.admin)
     button = <EnrollButton {...@props} users={@props.users} role={@props.role} key={key} inline=true allowed={allowed} show={@props.editable && allowed} />


do not add a value in both $set and $setOnInsert

--- packages/rocketchat-lib/server/functions/settings.coffee
+++ packages/rocketchat-lib/server/functions/settings.coffee
@@ -70,8 +70,10 @@
 	updateOperations =
 		$set: options
 		$setOnInsert:
-			value: value
 			createdAt: new Date
+
+	if not options.value?
+		updateOperations.$setOnInsert.value = value

 	if not options.section?
 		updateOperations.$unset = { section: 1 }


Test snippet externals

Test snippet externals

--- test/snippet/snippet.spec.coffee
+++ test/snippet/snippet.spec.coffee
@@ -6,10 +6,13 @@

   beforeEach () ->
     code = """
-    uniform float uni;
-    attribute vec3 att;
-    varying vec4 var;
-    void testSnippet() { }
+    uniform float uni1;
+    uniform vec3 uni2;
+    attribute vec3 att1;
+    attribute vec4 att2;
+    void callback1(in vec3 color);
+    void callback2(out vec3 color);
+    void testSnippet() { };
     """

     snippet = Snippet.load 'test', code
@@ -25,12 +28,22 @@
     expect(snippet.main).toBeTruthy()

     # rename uniforms
-    expect(snippet.uniforms['_sgtest_uni'].name).toBe('uni')
-    expect(snippet.uniforms['_sgtest_uni'].type).toBe('f')
+    expect(snippet.uniforms['_sgtest_uni1'].name).toBe('uni1')
+    expect(snippet.uniforms['_sgtest_uni1'].type).toBe('f')
+    expect(snippet.uniforms['_sgtest_uni2'].name).toBe('uni2')
+    expect(snippet.uniforms['_sgtest_uni2'].type).toBe('v3')
+
+    # rename externals
+    expect(snippet.externals['_sgtest_callback1'].name).toBe('callback1')
+    expect(snippet.externals['_sgtest_callback1'].type).toBe('(v3)()')
+    expect(snippet.externals['_sgtest_callback2'].name).toBe('callback2')
+    expect(snippet.externals['_sgtest_callback2'].type).toBe('()(v3)')

     # don't rename attributes
-    expect(snippet.attributes['att'].name).toBe('att')
-    expect(snippet.attributes['att'].type).toBe('v3')
+    expect(snippet.attributes['att1'].name).toBe('att1')
+    expect(snippet.attributes['att1'].type).toBe('v3')
+    expect(snippet.attributes['att2'].name).toBe('att2')
+    expect(snippet.attributes['att2'].type).toBe('v4')

   it 'binds uniforms', () ->
     uniforms =


Make redeye terminate the dispatcher on a timeout

--- test/support/redeye_suite.coffee
+++ test/support/redeye_suite.coffee
@@ -70,7 +70,12 @@
       @dispatcher = dispatcher.run @opts
       @queue.run => @expect.apply this
       setTimeout (=> @setup.apply this), 100
-      @timeout = setTimeout (=> @finish()), 5000
+      @timeout = setTimeout (=> @die()), 5000
+
+  # Forcefully quit the test
+  die: ->
+    @dispatcher.quit()
+    @finish()

   # Terminate the last redis connection, ending the test
   finish: ->


Adding opera to browser matrix

Adding opera to browser matrix

--- test/tests/utils/browsers.coffee
+++ test/tests/utils/browsers.coffee
@@ -24,6 +24,10 @@
     # safari5_snow
     safari6_mountain

+    # -- opera
+    opera_linux
+    # opera_win7
+
     # -- ie
     ie8_win7
     ie9_win7
@@ -114,6 +118,25 @@

+# opera
+# ----------------------------------------------------------------------------
+
+opera_linux =
+  version: '12'
+  browserName: 'opera'
+  platform: 'Linux'
+  tags: ['ways-browser', 'opera', 'linux']
+  'record-video': true
+
+opera_win7 =
+  version: '12'
+  browserName: 'opera'
+  platform: 'Windows 7'
+  tags: ['ways-browser', 'opera', 'win7']
+  'record-video': true
+
+
+
 # firefox
 # ----------------------------------------------------------------------------
 firefox_winxp =


Test enabled event

Test enabled event

--- test/unit/test_task_lists.coffee
+++ test/unit/test_task_lists.coffee
@@ -25,6 +25,9 @@
     $('#qunit-fixture').append(@container).pageUpdate()

   teardown: ->
+    $(document).off 'tasklist:enabled'
+    $(document).off 'tasklist:disabled'
+    $(document).off 'tasklist:change'

 asyncTest "triggers a tasklist:change event on task list item changes", ->
   expect 1
@@ -37,3 +40,27 @@
   , 20

   @checkbox.click()
+
+asyncTest "enables task list items when a .js-task-list-field is present", ->
+  expect 1
+
+  $(document).on 'tasklist:enabled', (event) ->
+    ok true
+
+  @container.pageUpdate()
+  setTimeout ->
+    start()
+  , 20
+
+asyncTest "doesn't enable task list items when a .js-task-list-field is absent", ->
+  expect 0
+
+  $(document).on 'tasklist:enabled', (event) ->
+    ok true
+
+  @field.remove()
+
+  @container.pageUpdate()
+  setTimeout ->
+    start()
+  , 20


Simplified coverage test

Simplified coverage test

--- test/dummy-project/test/coverage.coffee
+++ test/dummy-project/test/coverage.coffee
@@ -1,6 +1,6 @@
-all = require('../../../src')
-
-# relative to package root
-all.config.globals.dummyProject = './dist/dummy-project.coverage.node.js'
-
-all.test.coverage({ base: __dirname })+require('../../../src').test.coverage({
+  base: __dirname
+  globals: {
+    dummyProject: './dist/dummy-project.coverage.node.js'
+  }
+})


add extra light

add extra light

--- model-viewer/app.coffee
+++ model-viewer/app.coffee
@@ -48,12 +48,43 @@
   $scope.nameFromPath = (path) ->
     path.split('/').last().split('.json').first()

+THREEx = THREEx or {}
+
+###*
+# build a classic 3 points lighting
+# @return {THREE.Object3D} container for the 3 lights
+###
+
+THREEx.ThreePointsLighting = ->
+  container = new (THREE.Object3D)
+  object3d = new (THREE.AmbientLight)('#111111')
+  object3d.name = 'Ambient light'
+  container.add object3d
+  object3d = new (THREE.DirectionalLight)('white', 0.225)
+  object3d.position.set 2.6, 1, 3
+  object3d.name = 'Back light'
+  container.add object3d
+  object3d = new (THREE.DirectionalLight)('white', 0.375)
+  object3d.position.set -2, -1, 0
+  object3d.name = 'Key light'
+  container.add object3d
+  object3d = new (THREE.DirectionalLight)('white', 0.75)
+  object3d.position.set 3, 3, 2
+  object3d.name = 'Fill light'
+  container.add object3d
+  container
+
 class ModelViwerScene extends BaseScene
   constructor: ->
     super()

-    @scene.add Helper.light()
-    @scene.add Helper.ambientLight()
+    light = new THREEx.ThreePointsLighting()
+    light.position.set 0, 0, 5
+    @scene.add light
+
+    light = new THREEx.ThreePointsLighting()
+    light.position.set 0, 0, -5
+    @scene.add light

     @controls = new (THREE.OrbitControls)(engine.camera)
     @controls.damping = 0.2


number_extensions

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -313,18 +313,16 @@
 Returns a number that is maxDelta closer to target.

-   255.approach(0, 5)
-=> 250
-
-   5.approach(0, 10)
-=> 0
+255.approach(0, 5)
+# => 250
+
+5.approach(0, 10)
+# => 0

 @name approach
 @methodOf Number#
-
-@type Number
-@returns A number maxDelta toward target
+@returns {Number} A number maxDelta toward target
 ###
 Number::approach = (target, maxDelta) ->
   (target - this).clamp(-maxDelta, maxDelta) + this


Deprecate editor:will-be-removed

Deprecate editor:will-be-removed

--- src/workspace-view.coffee
+++ src/workspace-view.coffee
@@ -176,6 +176,8 @@
       switch eventName
         when 'cursor:moved'
           deprecate('Use Editor::onDidChangeCursorPosition instead')
+        when 'editor:will-be-removed'
+          deprecate('Use Editor::onDidDestroy instead')
         when 'pane:active-item-changed'
           deprecate('Use Pane::onDidChangeActiveItem instead')
         when 'pane:active-item-modified-status-changed'
@@ -210,6 +212,8 @@
       switch eventName
         when 'cursor:moved'
           deprecate('Use Editor::onDidChangeCursorPosition instead')
+        when 'editor:will-be-removed'
+          deprecate('Use Editor::onDidDestroy instead')
         when 'selection:changed'
           deprecate('Use Editor::onDidChangeSelectionRange instead')
       originalEditorViewOn.apply(this, arguments)
@@ -219,6 +223,8 @@
       switch eventName
         when 'cursor:moved'
           deprecate('Use Editor::onDidChangeCursorPosition instead')
+        when 'editor:will-be-removed'
+          deprecate('Use Editor::onDidDestroy instead')
         when 'pane:active-item-changed'
           deprecate('Use Pane::onDidChangeActiveItem instead')
         when 'pane:active-item-modified-status-changed'


specs for checkCacheForData that validate the presence of optional fields when there is no only query

--- spec/loaders/abstract-loader-shared-behavior.coffee
+++ spec/loaders/abstract-loader-shared-behavior.coffee
@@ -353,7 +353,21 @@
               loader.setup(opts)
               notFound(loader, opts)

-          # above contexts for optional fields
+          context 'all of the cached models have their optional fields loaded', ->
+            beforeEach ->
+              taskOne.set('test_field', 'test value')
+
+            it 'calls #_onLoadSuccess with the models from the cache', ->
+              opts.optionalFields = ['test_field']
+              loader.setup(opts)
+              loader._checkCacheForData()
+              expect(loader._onLoadSuccess).toHaveBeenCalledWith([taskOne])
+
+          context 'all of the cached models do not have their optional fields loaded', ->
+            it 'returns false and does not call #_onLoadSuccess', ->
+              opts.optionalFields = ['test_field']
+              loader.setup(opts)
+              expect(loader._onLoadSuccess).not.toHaveBeenCalled()

         context 'cache is invalid', ->
           beforeEach ->


Add a test showing that destructors are called when going back in history

--- spec_app/spec/javascripts/up/history_spec.js.coffee
+++ spec_app/spec/javascripts/up/history_spec.js.coffee
@@ -37,6 +37,43 @@
         expect(up.history.isUrl('/host/path/')).toBe(true)

   describe 'unobtrusive behavior', ->
+
+    describe 'back button', ->
+
+      it 'calls destructor functions when destroying compiled elements (bugfix)', (done) ->
+        waitForBrowser = 70
+
+        # By default, up.history will replace the  tag when
+        # the user presses the back-button. We reconfigure this
+        # so we don't lose the Jasmine runner interface.
+        up.history.config.popTargets = ['.container']
+
+        constructorSpy = jasmine.createSpy('constructor')
+        destructorSpy = jasmine.createSpy('destructor')
+
+        up.compiler '.example', ($example) ->
+          constructorSpy()
+          return destructorSpy
+
+        up.history.push('/one')
+        up.history.push('/two')
+
+        $container = affix('.container')
+        $example = $container.affix('.example')
+        up.hello($example)
+
+        expect(constructorSpy).toHaveBeenCalled()
+
+        history.back()
+        u.setTimer waitForBrowser, =>
+          expect(location.pathname).toEqual('/one')
+
+          @respondWith "
restored container text
"
+
+          u.nextFrame ->
+            expect(destructorSpy).toHaveBeenCalled()
+            done()
+

     describe '[up-back]', ->


Fix whitespace

Fix whitespace

--- lib/notification-element.coffee
+++ lib/notification-element.coffee
@@ -255,7 +255,7 @@

   removeNotificationAfterTimeout: ->
     atom.workspace.getActivePane().activate() if this is document.activeElement
-
+
     setTimeout =>
       @remove()
     , @animationDuration # keep in sync with CSS animation


dispose previous tooltips when re-create provider-info

--- lib/provider-information.coffee
+++ lib/provider-information.coffee
@@ -7,8 +7,6 @@
 class ProviderInformation
   constructor: (@ui) ->
     {@editor, @provider} = @ui
-    @disposables = new CompositeDisposable
-
     @container = document.createElement('div')
     @container.className = 'narrow-provider-information'
     @container.innerHTML = """
@@ -44,7 +42,7 @@
     @wholeWordButton.addEventListener('click', @toggleSearchWholeWord)

   destroy: ->
-    @disposables.destroy()
+    @toolTipDisposables?.dispose()
     @marker?.destroy()

   # Can be called multiple times
@@ -64,12 +62,15 @@
   toggleSearchWholeWord: => @ui.toggleSearchWholeWord()

   activateToolTips: ->
-    @disposables.add atom.tooltips.add @wholeWordButton,
+    @toolTipDisposables?.dispose()
+    @toolTipDisposables = disposables = new CompositeDisposable
+
+    disposables.add atom.tooltips.add @wholeWordButton,
       title: "wholeWord"
       keyBindingCommand: 'narrow-ui:toggle-search-whole-word'
       keyBindingTarget: @ui.editorElement

-    @disposables.add atom.tooltips.add @ignoreCaseButton,
+    disposables.add atom.tooltips.add @ignoreCaseButton,
       title: "ignoreCase"
       keyBindingCommand: 'narrow-ui:toggle-search-ignore-case'
       keyBindingTarget: @ui.editorElement


Guard clause move in run

Guard clause move in run

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -8,10 +8,7 @@
     @subscriptions.add atom.commands.add 'atom-workspace',
       'rubocop-auto-correct:current-file': =>
         if editor = atom.workspace.getActiveTextEditor()
-          if editor.getGrammar().scopeName == "source.ruby"
-            @run(editor)
-          else
-            atom.notifications.addError("Only use source.ruby")
+          @run(editor)

   destroy: ->
     @subscriptions.dispose()
@@ -29,6 +26,8 @@
     process

   run: (editor) ->
+    if editor.getGrammar().scopeName != "source.ruby"
+      return atom.notifications.addError("Only use source.ruby")
     if editor.isModified()
       editor.save()
     @autoCorrect(editor.getPath())


intersect removed from underscore, use intersection instead

--- static/coffee/tools/selecttool.coffee
+++ static/coffee/tools/selecttool.coffee
@@ -84,7 +84,7 @@
       datasource_selections[datasource_id].push(selected)

     for own k,v of datasource_selections
-      selected = _.intersect.apply(_, v)
+      selected = _.intersection.apply(_, v)
       datasources[k].set('selected', selected)
       datasources[k].save()
     return null


Fix text matching/exclusion; repair tests (again).

We should also take a look at these tests and the assumptions they're
making.  They're difficult to follow as is.

--- tests/dom_tests/dom_tests.coffee
+++ tests/dom_tests/dom_tests.coffee
@@ -156,6 +156,9 @@
     assert.equal "", hintMarkers[0].style.display

 context "Filtered link hints",
+  # Note.  In all of these tests, the order of the elements returned by getHintMarkers() may be different from
+  # the order they are listed in the test HTML content.  This is because LinkHints.activateMode() sorts the
+  # elements.

   setup ->
     stub settings.values, "filterLinkHints", true
@@ -205,8 +208,8 @@
     should "label the images", ->
       hintMarkers = getHintMarkers()
       assert.equal "1: alt text", hintMarkers[0].textContent.toLowerCase()
-      assert.equal "2: alt text", hintMarkers[1].textContent.toLowerCase()
-      assert.equal "3: some title", hintMarkers[2].textContent.toLowerCase()
+      assert.equal "2: some title", hintMarkers[1].textContent.toLowerCase()
+      assert.equal "3: alt text", hintMarkers[2].textContent.toLowerCase()
       assert.equal "4", hintMarkers[3].textContent.toLowerCase()

   context "Input hints",


chore(quick-start): make build name generic

--- src/cli/quick-start.coffee
+++ src/cli/quick-start.coffee
@@ -26,7 +26,7 @@
 	# ==========
 	# root files
 	try	fse.copySync "#{config.build.cli.templates.root}/gitignore.sh", "#{appPath}/.gitignore", opts
-	for file, i in ['rapid-build.cson']
+	for file, i in ["#{config.build.pkg.name}.cson"]
 		try	fse.copySync "#{config.build.cli.templates.root}/#{file}", "#{appPath}/#{file}", opts

 	if mkClient
@@ -39,4 +39,4 @@

 	# message
 	# =======
-	console.log 'Quick start complete! Now run: rapid-build dev'.attn
+	console.log "Quick start complete! Now run: #{config.build.pkg.name} dev".attn


Fix split chord transposing badness

Fix split chord transposing badness

--- static/coffee/song.coffee
+++ static/coffee/song.coffee
@@ -146,7 +146,6 @@
     splitted = chord.split '/'
     if splitted.length > 1
         return $.map splitted, (chordPart) -> transposeChord chordPart, increment
-            .get()
             .join '/'

     flatScale  = scale = 'C Db D Eb E F Gb G Ab A Bb B'.split ' '


Fix who's on call output

Fix who's on call output

Found that running `who's on call` will give some garbage output:

```
hubot> hubot who's on call
4
6
5
3
1
2
7
8
* Foo Bar is on call for Example - Engineering - https://foo.pagerduty.com/schedules#PSH77FE
* Baz Biz is on call for Example - DEV - https://foo.pagerduty.com/schedules#chedules#PQ4T39J
```

Traced this back to ef1d84bd where the output was changed to return in a
single message rather several. Both the results of `messages.push()`
which is the length of the array and the actual `messages` array itself
was being sent back.

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -573,10 +573,8 @@
     messages = []
     renderSchedule = (s, cb) ->
       withCurrentOncall msg, s, (username, schedule) ->
-        cb null, messages.push("* #{username} is on call for #{schedule.name} - https://#{pagerduty.subdomain}.pagerduty.com/schedules##{schedule.id}")
-    setTimeout ( ->
-      msg.send messages.join("\n")
-    ), 1500
+        messages.push("* #{username} is on call for #{schedule.name} - https://#{pagerduty.subdomain}.pagerduty.com/schedules##{schedule.id}")
+        cb null

     if scheduleName?
       withScheduleMatching msg, scheduleName, (s) ->
@@ -591,11 +589,11 @@
           robot.emit 'error', err, msg
           return
         if schedules.length > 0
-          async.map schedules, renderSchedule, (err, results) ->
+          async.map schedules, renderSchedule, (err) ->
             if err?
               robot.emit 'error', err, msg
               return
-            msg.send results.join("\n")
+            msg.send messages.join("\n")
         else
           msg.send 'No schedules found!'


Added identifier tokenisation

Added identifier tokenisation

--- src/coffee/services.coffee
+++ src/coffee/services.coffee
@@ -16,6 +16,51 @@
       @$get = -> stepConfig

   Services.provider 'stepConfig', StepConfig
+
+  # Little pull parsing state machine for tokenising identifiers.
+  Services.factory 'tokenise', -> (string) ->
+    charIndex = 0
+    escaping = false
+    inQuotes = false
+    current = []
+    tokens = []
+
+    endCurrent = ->
+      if current.length
+        tokens.push current.join ''
+        current = []
+
+    isDelimiter = (c) -> /\s/.test(char) or ',' is char
+
+    if string
+      while char = string.charAt(charIndex++)
+        if inQuotes and !escaping and char is "\\"
+          escaping = true
+        else if inQuotes and escaping and char is '"'
+          current.push(char)
+          escaping = false
+        else if inQuotes and escaping and char is 'n'
+          current.push("\n")
+          escaping = false
+        else if inQuotes and escaping
+          current.push("\\")
+          current.push(char)
+          escaping = false
+        else if inQuotes and !escaping and char is '"'
+          inQuotes = false
+          endCurrent()
+        else if inQuotes
+          current.push(char)
+        else if !inQuotes and !escaping and char is '"'
+          inQuotes = true
+        else if !inQuotes and !escaping and isDelimiter(char)
+          endCurrent()
+        else
+          current.push(char)
+
+      endCurrent()
+
+    return tokens

   Services.factory 'ClassUtils', Array '$q', '$timeout', (Q, timeout) ->
     setClasses = (scope, grouper, propName = 'outputType') -> (model) -> timeout ->


Refine nylas-connection error handling

Refine nylas-connection error handling

--- nylas-connection.coffee
+++ nylas-connection.coffee
@@ -69,8 +69,13 @@
           error = e unless error

         if error or response.statusCode > 299
-          error ?= new Error(body.message)
+          unless error
+            if _.isString(body.message)
+              error = new Error(body.message)
+            else
+              error = new Error(JSON.stringify(body.message))
           error.code = body.type if body.type
+          error.server_error = body.server_error if body.server_error
           logOnError(error, response, body)
           reject(error)
         else


fix spec

fix spec

--- test/address_resolver.coffee
+++ test/address_resolver.coffee
@@ -8,14 +8,14 @@
         @resolver.find('0010933').then (result) ->
           assert.ok result.prefecture == '北海道'

-    context 'when the postal code is short', ->
+    context 'when the zip code is short', ->
       beforeEach ->
         @resolver = new AddressResolver
       it 'returns null value', ->
         @resolver.find('00').then (result) ->
           assert.ok result == null

-    context 'when the postal code not found', ->
+    context 'when the zip code not found', ->
       beforeEach ->
         @resolver = new AddressResolver
       it 'returns null value', ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -69,6 +69,8 @@
   target = target[item] for item in this.split '.'

   return target
+
+return this.match(/\.([^\.]*)$/, '').last() || '';

 ###*
 Returns a new string that is a more human readable version.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -137,7 +137,7 @@
 @name startsWith
 @methodOf String#

-@returns {Boolean}
+@returns {Boolean} True if
 ###
 String::startsWith = (str) ->
   @lastIndexOf(str, 0) is 0


add test for options file with debug and transforms

--- test/browserify-tests.coffee
+++ test/browserify-tests.coffee
@@ -60,6 +60,85 @@
   test.equal compileStep.js.data, 'test data'

+# test options file with debug=true and a transform
+Tinytest.add 'test options file', (test) ->
+
+  clearPreviousResults()
+
+  options = defaultOptions()
+
+  # add exposify transform to options
+  options.transforms.exposify =
+    global: true
+    expose:
+      react: "React"
+
+  # add imaginary transform to options so there's more in there
+  options.transforms.imaginary =
+    some:'value'
+    and:'another'
+
+  # supply an options file. make our options JSON content
+  Result.optionsFile = JSON.stringify options
+
+  # get the function registered with Plugin.registerSourceHandler
+  processFile = Plugin.fn
+
+  # create our mock CompileStep
+  compileStep = Plugin.compileStep()
+
+  # call our plugin function with our mock CompileStep
+  processFile compileStep
+
+  # test results
+  test.equal compileStep?.readBuffer?, true, 'CompileStep.read() should be called'
+  test.equal Result.passThrough.run, true, 'should create a new PassThrough'
+  test.equal Result.passThrough.buffer, compileStep.readBuffer
+
+  # test fs checked for options file and returned it
+  test.equal Result?.existsSync, '/full/path/to/app/packages/file.browserify.options.json'
+  test.equal Result?.readFileSync?.name, '/full/path/to/app/packages/file.browserify.options.json'
+  test.equal Result?.readFileSync?.encoding, 'utf8'
+
+  # test building browserify
+  test.equal Result?.browserify?, true, 'must run browserify function'
+  test.equal Result.browserify.array?, true, 'must have an array to browserify'
+  test.equal Result.browserify.array?.length, 1, 'browserify gets [readable], length should be 1'
+  test.equal Result.browserify.array[0]?.isTestPassThrough, true, 'should receive our test PassThrough'
+  test.equal Result.browserify?.options, options
+
+  # test envify transform
+  test.equal Result?.transforms?.envify?, true, 'transform should be called with envify'
+  test.equal Result.transforms.envify?.NODE_ENV, 'development' # testing dev mode only so far
+  test.equal Result.transforms.envify?._, 'purge'
+
+  # test exposify transform
+  test.equal Result?.transforms?.exposify?, true, 'transform should be called with exposify'
+  test.equal Result.transforms.exposify.global, true
+  test.equal Result.transforms.exposify?.expose?.react, 'React'
+
+  # test imaginary transform
+  test.equal Result?.transforms?.imaginary?, true, 'transform should be called with imaginary'
+  test.equal Result.transforms.imaginary?.some, 'value'
+  test.equal Result.transforms.imaginary?.and, 'another'
+
+  # test bundling with browserify and reading its result
+  test.equal Result.browserify?.bundle, true, 'must run browserify.bundle()'
+  test.equal Result.browserify?.encoding, 'utf8'
+  test.equal Result.browserify?.on?.event, 'data', 'must call bundle.on \'data\''
+  test.equal Result.browserify?.once?.end?, true, 'must call bundle.once \'end\''
+  test.equal Result.browserify?.once?.error?, true, 'must call bundle.once \'error\''
+
+  # test error was *not* received by CompileStep#error()
+  test.equal Result?.errorReceived?, false, 'CompileStep should *not* receive an error'
+  test.equal Result?.errorReceived?.message, undefined
+
+  # test what's given to CompileStep.addJavaScript
+  test.equal compileStep?.js?, true, 'CompileStep should contain addJavaScript info'
+  test.equal compileStep.js.path, 'file.browserify.js'
+  test.equal compileStep.js.sourcePath, 'file.browserify.js'
+  test.equal compileStep.js.data, 'test data'
+

 # test an error in browserify
 Tinytest.add 'test with browserify error', (test) ->


Add tests for new get/format data methods

--- test/models/indicator.coffee
+++ test/models/indicator.coffee
@@ -34,3 +34,28 @@
       readFileStub.restore()
   ).fail(done)
 )
+
+test(".query loads and formats the data based on its source", ->
+  indicator = new Indicator(
+    source: "gdoc"
+  )
+
+  gotData = {some: 'data'}
+  getDataStub = sinon.stub(indicator, 'getDataFrom', ->
+    Q.fcall(-> gotData)
+  )
+
+  formatDataFrom = sinon.stub(indicator, 'formatDataFrom', ->
+    Q.fcall(-> {})
+  )
+
+  assert.isTrue(
+    getDataStub.calledWith(indicator.source),
+    "Expected getDataFrom to be called with the indicator source"
+  )
+
+  assert.isTrue(
+    formatDataFrom.calledWith(indicator.source, gotData),
+    "Expected formatDataFrom to be called with the indicator source and fetched data"
+  )
+)


Introduce even more complex materialized views because why not

--- storage/lib/db.coffee
+++ storage/lib/db.coffee
@@ -26,10 +26,10 @@
             CREATE VIEW logsRel AS
                 SELECT id
                      , updated
-                     , data->>\'CacheCode\' as cachecode
-                     , data->\'Finder\'->>\'UserName\' as username
-                     , data->\'LogType\'->>\'WptLogTypeId\' as logtype
-                     , (date \'epoch\' + (substring(data->>\'UTCCreateDate\' from 7 for 10)::numeric * interval \'1 second\')) as createdate
+                     , data->>'CacheCode' as cachecode
+                     , data->'Finder'->>'UserName' as username
+                     , data->'LogType'->>'WptLogTypeId' as logtype
+                     , (date 'epoch' + (substring(data->>'UTCCreateDate' from 7 for 10)::numeric * interval '1 second')) as createdate
                 FROM logs
             """
         ]
@@ -42,3 +42,24 @@
                 FROM geocaches c
             """
         ]
+        yield migrate.up [
+            """
+            CREATE MATERIALIZED VIEW geocachesRel AS
+                SELECT
+                    c.id,
+                    c.updated as updated,
+                    (c.data->>'Name') as Name,
+                    (c.data->>'Latitude')::numeric as Latitude,
+                    (c.data->>'Longitude')::numeric as Longitude,
+                    (c.data->'CacheType'->>'GeocacheTypeId')::numeric as GeocacheTypeId,
+                    c.data->'ContainerType'->>'ContainerTypeName' as ContainerTypeName,
+                    (c.data->>'Difficulty')::numeric as Difficulty,
+                    (c.data->>'Terrain')::numeric as Terrain,
+                    c.data->>'EncodedHints' as EncodedHints,
+                    (c.data->>'Archived')::bool as Archived,
+                    (c.data->>'Available')::bool as Available,
+                    (date 'epoch' + (substring(c.data->>'UTCPlaceDate' from 7 for 10)::numeric - substring(c.data->>'UTCPlaceDate' from 21 for 2)::numeric * 3600) * interval '1 second') as UTCPlaceDate
+                FROM geocaches c
+            """
+        ]
+


Client: Activity: Updated renderChildren method to delete unnecessary lines

--- client/activity/lib/components/sidebarmodallist/index.coffee
+++ client/activity/lib/components/sidebarmodallist/index.coffee
@@ -56,13 +56,12 @@

     { itemComponent: Component, threads } = @props

-    channelItems = threads.map (thread, i) ->
+    @state.threads.toList().map (thread, i) ->
       itemProps =
         key     : thread.get 'channelId'
         channel : thread.get 'channel'
       <Component {...itemProps} />

-    return channelItems.toList()

   renderNoResultText: ->


avatar flashing fix

avatar flashing fix

--- client/landing/app/CommonViews/avatarviews/avatarview.coffee
+++ client/landing/app/CommonViews/avatarviews/avatarview.coffee
@@ -12,6 +12,8 @@
     options.cssClass = "avatarview #{options.cssClass}"

     super options,data
+
+    @bgImg = null

   click:(event)->

@@ -28,8 +30,13 @@
     options = @getOptions()
     host = "#{location.protocol}//#{location.host}/"
     @$().attr "title", options.title or "#{Encoder.htmlDecode profile.firstName}'s avatar"
-    fallbackUrl = "url(#{location.protocol}//gravatar.com/avatar/#{profile.hash}?size=#{options.size.width}&d=#{encodeURIComponent(host + 'images/defaultavatar/default.avatar.' + options.size.width + '.png')})"
-    @$().css "background-image", fallbackUrl
+
+    # this is a temp fix to avoid avatar flashing on each account change - Sinan 08/2012
+    bgImg = "url(#{location.protocol}//gravatar.com/avatar/#{profile.hash}?size=#{options.size.width}&d=#{encodeURIComponent(host + 'images/defaultavatar/default.avatar.' + options.size.width + '.png')})"
+    if @bgImg isnt bgImg
+      @$().css "background-image", bgImg
+      @bgImg = bgImg
+
     flags = account.globalFlags?.join(" ") ? ""
     @$('cite').addClass flags


Don't show topic popover

Don't show topic popover

--- core/app/backbone/views/activities/activity_item_view.coffee
+++ core/app/backbone/views/activities/activity_item_view.coffee
@@ -59,14 +59,14 @@
     topic: @topic().toJSON()

   onRender: ->
-    Backbone.Factlink.makeTooltipForView @,
-      positioning:
-        align: 'left'
-        side: 'bottom'
-      selector: '.js-link'
-      $offsetParent: @options.$offsetParent
-      tooltipViewFactory: =>
-        new TopicPopoverContentView model: @topic()
+    # Backbone.Factlink.makeTooltipForView @,
+    #   positioning:
+    #     align: 'left'
+    #     side: 'bottom'
+    #   selector: '.js-link'
+    #   $offsetParent: @options.$offsetParent
+    #   tooltipViewFactory: =>
+    #     new TopicPopoverContentView model: @topic()

   topic: -> @_topic ?= new Topic(@model.get('activity').topic)


allow DDP connections

allow DDP connections

--- tap_i18n_db-client.coffee
+++ tap_i18n_db-client.coffee
@@ -60,12 +60,21 @@

   return obj

-TAPi18n.subscribe = (name) ->
+TAPi18n.subscribe = ->
   local_session = new ReactiveDict
   local_session.set("ready", false)
+  # connection ?= Meteor
+
+  if typeof arguments[0] is 'string'
+    name = arguments[0]
+    shift = 1
+  else
+    connection = arguments[0] ? Meteor
+    name = arguments[1]
+    shift = 2

   # parse arguments
-  params = Array.prototype.slice.call(arguments, 1)
+  params = Array.prototype.slice.call(arguments, shift)
   callbacks = {}
   if params.length
     lastParam = params[params.length - 1]
@@ -102,9 +111,9 @@
       lang_tag = TAPi18n.getLanguage()

       subscription =
-        Meteor.subscribe.apply @, removeTrailingUndefs [].concat(name, params, lang_tag, callbacks)
+        connection.subscribe.apply connection, removeTrailingUndefs [].concat(name, params, lang_tag, callbacks)

-      # if the subscription is already ready:
+      # if the subscription is already ready:
       local_session.set("ready", subscription.ready())

   # If TAPi18n is called in a computation, to maintain Meteor.subscribe
@@ -115,7 +124,7 @@
   if currentComputation?
     # If TAPi18n.subscribe was called in a computation, call subscribe in a
     # non-reactive context, but make sure that if the computation is getting
-    # invalidated also the subscription computation
+    # invalidated also the subscription computation
     # (invalidations are allowed up->bottom but not bottom->up)
     Deps.onInvalidate ->
       subscription_computation.invalidate()


use parentElement and unique to select nodes

--- src/helpers/mathjax.coffee
+++ src/helpers/mathjax.coffee
@@ -10,7 +10,7 @@

 # Search document for math and [data-math] elements and then typeset them
 typesetDocument = ->
-  allNodes = []
+  latexNodes = []

   for node in document.querySelectorAll(MATH_DATA_SELECTOR)
     formula = node.getAttribute('data-math')
@@ -19,19 +19,21 @@
       node.textContent = "#{MATH_MARKER_BLOCK}#{formula}#{MATH_MARKER_BLOCK}"
     else
       node.textContent = "#{MATH_MARKER_INLINE}#{formula}#{MATH_MARKER_INLINE}"
-    allNodes.push(node)
+    latexNodes.push(node)
+
+  unless _.isEmpty(latexNodes)
+    window.MathJax.Hub.Typeset(latexNodes)

   mathMLNodes = _.toArray(document.querySelectorAll(MATH_ML_SELECTOR))
-  hasMathML = not _.isEmpty(mathMLNodes)
-  allNodes = allNodes.concat(mathMLNodes)
+  unless _.isEmpty(mathMLNodes)
+    # ugg - mathjax seems to need at least a bit of other content around the element to style
+    # If it just styles a parent element that has no other text then it doesn't know what size the text should be
+    parents = _.unique( _.map( mathMLNodes, (el) -> el.parentElement.parentElement ) )
+    window.MathJax.Hub.Typeset( parents )

-  # MathJax sometimes fails to set the height/width properly when processing individual mathml elements
-  # If we have any of those nodes then mark the entire document for processing
-  window.MathJax.Hub.Typeset( if hasMathML then document.body else allNodes )
   window.MathJax.Hub.Queue ->
-    # Queue a call to mark the found nodes as rendered so
-    # they can be ignored if typesetting is called repeatedly
-    for node in allNodes
+    # Queue a call to mark the found nodes as rendered so are ignored if typesetting is called repeatedly
+    for node in latexNodes.concat(mathMLNodes)
       node.classList.add(MATH_RENDERED_CLASS)

 # Install a debounce around typesetting function so that it will only run once


Make a single mathjax pass on entire document

On the Teacher and QA views of exercises, mathjax was being invoked
several hundred times (twice per question).  This modifies it so it will
only perform once pass on the entire document

--- src/helpers/mathjax.coffee
+++ src/helpers/mathjax.coffee
@@ -2,6 +2,8 @@

 MATH_MARKER_BLOCK  = '\u200c\u200c\u200c' # zero-width non-joiner
 MATH_MARKER_INLINE = '\u200b\u200b\u200b' # zero-width space
+
+MATH_SELECTOR = '[data-math]:not(.math-rendered)'

 cleanMathArtifacts = ->
   # Once MathJax finishes processing, cleanup the MathJax message nodes to prevent
@@ -19,13 +21,12 @@
   ])

-typesetMath = (root) ->
-  nodes = root.querySelectorAll('[data-math]:not(.math-rendered)') or []
-  hasMath = root.querySelector('math')

-  # Return immediatly if no [data-math] or <math> elements are present
-  # TODO: If the MathJax Queue is not available then MathJax has not loaded yet. Add a load callback to enqueue.
-  return unless window.MathJax?.Hub?.Queue? and (_.any(nodes) or hasMath)
+# Typesets the entire document
+TypesetDocument = ->
+
+  nodes = document.querySelectorAll(MATH_SELECTOR)
+  return if _.isEmpty(nodes)

   for node in nodes
     formula = node.getAttribute('data-math')
@@ -40,10 +41,26 @@
   # submit all nodes at once for optimal rendering performance
   window.MathJax.Hub.Queue(['Typeset', MathJax.Hub, _.toArray(nodes)])

+  cleanMathArtifacts()
+
+# Install a debounce around typesetting function so that it will only run once
+# every 10ms even if called multiple calls times in that period
+TypesetDocument = _.debounce( TypesetDocument, 10)
+
+
+typesetMath = (root) ->
+  nodes = root.querySelectorAll(MATH_SELECTOR) or []
+  hasMath = root.querySelector('math')
+
+  # Return immediatly if no [data-math] or <math> elements are present
+  # TODO: If the MathJax Queue is not available then MathJax has not loaded yet. Add a load callback to enqueue.
+  return unless window.MathJax?.Hub?.Queue? and (_.any(nodes) or hasMath)
+
   # render MathML with MathJax
   window.MathJax.Hub.Queue(['Typeset', MathJax.Hub, root]) if hasMath

-  cleanMathArtifacts()
+  TypesetDocument()
+

 # The following should be called once and configures MathJax.


kontrol: change auth defaults

kontrol: change auth defaults

--- src/kontrol/kontrol.coffee
+++ src/kontrol/kontrol.coffee
@@ -7,8 +7,6 @@
   @Kite = require '../kite/kite.coffee'

   constructor: (options) ->
-    throw new Error "Missing option: auth"  unless options.auth
-
     return new Kontrol options  unless this instanceof Kontrol

     @options = options
@@ -20,14 +18,12 @@
     @kite.on 'error', @emit.bind this, 'error'  # forward kite error events

   authenticate: ->
-    { url, auth: { type, key }} = @options
-
-    type ?= 'sessionID'
+    { url, auth } = @options

     @kite = new @constructor.Kite
       name  : 'kontrol'
       url   : url
-      auth  : { type, key }
+      auth  : auth

   createKite: ({ kite: { name }, token, url }) ->
     new @constructor.Kite


add a test

add a test

--- test/app/lifecycle.coffee
+++ test/app/lifecycle.coffee
@@ -71,6 +71,11 @@

     it 'sends 500s as json for xhr requests'

+    it 'passes the recaptcha_token through signup', ->
+      @req.body.recaptcha_token = 'recaptcha_token'
+      lifecycle.onLocalSignup @req, @res, @next
+      @request.send.args[0][0].recaptcha_token.should.equal 'recaptcha_token'
+
     it 'passes the user agent through signup', ->
       @req.get.returns 'foo-agent'
       lifecycle.onLocalSignup @req, @res, @next


add default param

add default param

--- test/color/matcher.coffee
+++ test/color/matcher.coffee
@@ -1,7 +1,7 @@
 module.exports =

   # checks if actual object contains all expected properties and that the number values are similar
-  toApprox: (expected, maxEps) ->
+  toApprox: (expected, maxEps = 0) ->
     for key, numb of expected
       expect(@actual[key]).toBeDefined()
       if (Math.abs @actual[key] - numb) > maxEps


Cleanup test for document error handling

--- test/document_test.coffee
+++ test/document_test.coffee
@@ -18,28 +18,36 @@
     document = new treeSitter.Document()
     document.setParser(parser)

-  describe "setParser", ->
-    describe "when the supplied object is not a parser", ->
-      it "throws an exception", ->
-        assert.throws((->
-          document.setParser({})
-        ), /Invalid parser/)
+  describe "error handling", ->
+    describe "setParser", ->
+      describe "when the supplied object is not a parser", ->
+        it "throws an exception", ->
+          assert.throws((->
+            document.setParser({})
+          ), /Invalid parser/)

-  describe "setInput", ->
-    describe "when the supplied object does not implement .seek(n)", ->
-      it "throws an exception", ->
-        assert.throws((->
-          document.setInput({
-            seek: (n) -> 0
-          })
-        ), /Input.*implement.*read/)
+          assert.throws((->
+            document.setParser(undefined)
+          ), /Invalid parser/)

-        assert.throws((->
-          document.setInput({
-            read: -> ""
-          })
-        ), /Input.*implement.*seek/)
+    describe "setInput", ->
+      describe "when the supplied object does not implement .seek(n)", ->
+        it "throws an exception", ->
+          assert.throws((->
+            document.setInput({
+              read: -> ""
+            })
+          ), /Input.*implement.*seek/)

+      describe "when the supplied object does not implement .read()", ->
+        it "throws an exception", ->
+          assert.throws((->
+            document.setInput({
+              seek: (n) -> 0
+            })
+          ), /Input.*implement.*read/)
+
+      it "succeeds otherwise", ->
         document.setInput({
           read: -> ""
           seek: (n) -> 0


fix https://github.com/sagemathinc/cocalc/issues/2766 --- project startup not robust against SQLite DB issues

--- src/smc-project/jupyter/jupyter-blobs-sqlite.coffee
+++ src/smc-project/jupyter/jupyter-blobs-sqlite.coffee
@@ -3,6 +3,8 @@
 ###

 fs = require('fs')
+
+winston = require('winston')

 misc      = require('smc-util/misc')
 misc_node = require('smc-util-node/misc_node')
@@ -15,6 +17,25 @@

 class BlobStore
     constructor: ->
+        winston.debug("jupyter BlobStore: constructor")
+        try
+            @_init()
+            winston.debug("jupyter BlobStore: #{DB_FILE} opened fine")
+        catch err
+            winston.debug("jupyter BlobStore: #{DB_FILE} open error - #{err}")
+            # File may be corrupt/broken/etc. -- in this case, remove and try again.
+            # This database is only an image *cache*, so this is fine.
+            # See https://github.com/sagemathinc/cocalc/issues/2766
+            # Using sync is also fine, since this only happens once
+            # during initialization.
+            winston.debug("jupyter BlobStore: resetting database cache")
+            try
+                fs.unlinkSync(DB_FILE)
+            catch err
+                winston.debug("Error trying to delete #{DB_FILE}... ignoring: ", err)
+            @_init()
+
+    _init: =>
         @_db = new Database(DB_FILE)
         @_db.prepare('CREATE TABLE IF NOT EXISTS blobs (sha1 TEXT, data BLOB, type TEXT, ipynb TEXT, time INTEGER)').run()
         @_clean()  # do this once on start


Check if name matches before resolving path

Path resolution hits the filesystem so check for a name
match first.

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -161,6 +161,8 @@
     @emit 'loaded'

   loadPackage: (nameOrPath) ->
+    return pack if pack = @getLoadedPackage(nameOrPath)
+
     if packagePath = @resolvePackagePath(nameOrPath)
       name = path.basename(nameOrPath)
       return pack if pack = @getLoadedPackage(name)


cleaning up tests

cleaning up tests

--- tests/test_builder.coffee
+++ tests/test_builder.coffee
@@ -1386,7 +1386,7 @@
                         "ac/b": "ac_b"
                     type: -> @that.equals("cjs")

-describe.only 'Building various AMD formats', ->
+describe 'Building various AMD formats', ->
     beforeEach ->
         mock(yaml.safeLoad("""
             testimonial:


Tweaked physics update loops so they create plain JavaScript loops after the conversion.

--- src/physics/physics.coffee
+++ src/physics/physics.coffee
@@ -58,12 +58,16 @@
       p = particles[i]
       p.update()
       dead.push p if p.state is stateDead
+      undefined

     # remove dead particles
     i = dead.length
     while i--
       p = dead[i]
       util.removeElement p, particles
+      undefined
+
+    undefined

   # --- utilities ---
@@ -71,20 +75,18 @@
   # applies all effectors to the given particle list when their states match
   applyEffectors: (effectors, particles) ->
     state = effectors.length
-    while state--
-      stateEffectors = effectors[state]
+    for state in effectors
+      # apply all behaviours for this state if particle is in this state
+      for effector in effectors[state]
+        effector.prepare this

-      # apply all behaviours for this state if particle is in this state
-      j = stateEffectors.length
-      while j--
-        e = stateEffectors[j]
-        e.prepare this
+        for particle in particles
+          effector.apply particle if particle.state is state
+          undefined

-        i = particles.length
-        while i--
-          particle = particles[i]
-          e.apply particle if particle.state is state
-
+        undefined
+      undefined
+    undefined

   # returns the number of particles
   size: -> @particles.length


Add Workspace::onDidDestroyPaneItem for symmetry with ::onDidDestroyPane

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -296,6 +296,18 @@
   # Returns a {Disposable} on which `.dispose` can be called to unsubscribe.
   onWillDestroyPaneItem: (callback) -> @paneContainer.onWillDestroyPaneItem(callback)

+  # Extended: Invoke the given callback when a pane item is destroyed.
+  #
+  # * `callback` {Function} to be called when pane items are destroyed.
+  #   * `event` {Object} with the following keys:
+  #     * `item` The destroyed item.
+  #     * `pane` {Pane} containing the destroyed item.
+  #     * `index` {Number} indicating the index of the destroyed item in its
+  #       pane.
+  #
+  # Returns a {Disposable} on which `.dispose` can be called to unsubscribe.
+  onDidDestroyPaneItem: (callback) -> @paneContainer.onDidDestroyPaneItem(callback)
+
   # Extended: Invoke the given callback when a text editor is added to the
   # workspace.
   #


fixed tokenizedLines tab length problem

fixed tokenizedLines tab length problem

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -621,8 +621,12 @@
     text = text.replace(/[\t]{1}/gi,
       '')
     line.innerHTML = text
-    marginLeft = tokenizedLine.indentLevel *
-      tokenizedLine.tabLength * attrs.charWidth
+    if tokenizedLine.indentLevel?
+      marginLeft = tokenizedLine.indentLevel *
+        tokenizedLine.tabLength * attrs.charWidth
+    else
+      marginLeft = attrs.tokenizedBuffer.indentLevelForLine(tokenizedLine.text) *
+        attrs.tokenizedBuffer.getTabLength() * attrs.charWidth
     marginLeft -= attrs.scrollLeft
     line.style.cssText = "
       margin-left:#{marginLeft}px;
@@ -721,6 +725,8 @@
             attrsForward = {
               charWidth:charWidth
               scrollLeft:attrs.scrollLeft
+              tokenizedLines:attrs.tokenizedLines
+              tokenizedBuffer:attrs.tokenizedBuffer
             }
             for line in attrs.screenLines
               @drawLine line,attrsForward
@@ -781,6 +787,8 @@
                     scrollTop:scrollTop
                     scrollLeft:scrollLeft
                     visibleBuffer: actualLines
+                    tokenizedLines:tokenizedLines
+                    tokenizedBuffer:displayBuffer.tokenizedBuffer
                   }
                 @drawLines attrs


change selector for clear button

change selector for clear button

--- MediaAdminBundle/Resources/public/coffee/mediatheque/orchestraMediaType.coffee
+++ MediaAdminBundle/Resources/public/coffee/mediatheque/orchestraMediaType.coffee
@@ -8,7 +8,7 @@
     $(inputId).val ''
     $(previewId).removeAttr 'src'
 widgetChannel.bind 'loaded', (view) ->
-  if $('.clear-media', view.$el).length > 0
+  if $("[data-prototype*='clear-media']", view.$el).length > 0
     $.extend true, view, extendView['orchestraMediaType']
     return view.delegateEvents()
   return


Set window title to project title

Set window title to project title

--- framer/Components/ShareComponent.coffee
+++ framer/Components/ShareComponent.coffee
@@ -89,6 +89,9 @@
 		# Get the project id from the url
 		projectId = window.location.pathname.replace(/\//g, "")

+		# Set the window title
+		document.title = @shareInfo.title
+
 		# When fixed is set to true, the sheet won't hide on resize.
 		# This is triggered by a manual open / close action.
 		@options =


Update ShareComponent to new animation API

Fixes https://github.com/motif/company/issues/2852

--- framer/Components/ShareComponent.coffee
+++ framer/Components/ShareComponent.coffee
@@ -67,13 +67,10 @@

 		@onMouseOver ->
 			@style.cursor = "pointer"
-			@states.switch("hover")
-			@animate
-				properties:
-					opacity: .8
+			@animate("hover")

 		@onMouseOut ->
-			@states.switch("full")
+			@animate("full")

 		@onTap ->
 			if options.shareButton


Add 'Drop' to Git Stash Delete in command menu

--- lib/git-plus-commands.coffee
+++ lib/git-plus-commands.coffee
@@ -61,7 +61,7 @@
     commands.push ['git-plus:stash-save-changes', 'Stash: Save Changes', -> GitStashSave()]
     commands.push ['git-plus:stash-pop', 'Stash: Apply (Pop)', -> GitStashPop()]
     commands.push ['git-plus:stash-apply', 'Stash: Apply (Keep)', -> GitStashApply()]
-    commands.push ['git-plus:stash-delete', 'Stash: Delete', -> GitStashDrop()]
+    commands.push ['git-plus:stash-delete', 'Stash: Delete (Drop)', -> GitStashDrop()]
     commands.push ['git-plus:status', 'Status', -> GitStatus()]
     commands.push ['git-plus:tags', 'Tags', -> GitTags()]
     commands.push ['git-plus:unstage-files', 'Unstage Files', -> GitUnstageFiles()]


use the same approach for selections as find-and-replace:show

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -115,12 +115,9 @@
   attach: ->
     atom.workspaceView.prependToBottom(this) unless @hasParent()

-    previousQuery = @findEditor.getText()
-
-    @setSelectionAsFindPattern()
-
-    if not @findEditor.getText()
-      @findEditor.setText(previousQuery)
+    selectedText = atom.workspace.getActiveEditor()?.getSelectedText?()
+    if selectedText and selectedText.indexOf('\n') < 0
+      @findEditor.setText(selectedText)

     @findEditor.focus()
     @findEditor.getEditor().selectAll()


Fix typo

Fix typo

--- lib/provider-metadata.coffee
+++ lib/provider-metadata.coffee
@@ -26,10 +26,10 @@
     if providerBlacklist = @provider.providerblacklist?['autocomplete-plus-fuzzyprovider']
       @disableDefaultProviderSelectors = Selector.create(providerBlacklist)

-    @enableCustorTextEditorSelector = semver.satisfies(@provider[API_VERSION], '>=2.1.0')
+    @enableCustomTextEditorSelector = semver.satisfies(@provider[API_VERSION], '>=2.1.0')

   matchesEditor: (editor) ->
-    if @enableCustorTextEditorSelector and @provider.getTextEditorSelector?
+    if @enableCustomTextEditorSelector and @provider.getTextEditorSelector?
       atom.views.getView(editor).matches(@provider.getTextEditorSelector())
     else
       # Backwards compatibility.


add test for indonesian translation

add test for indonesian translation

--- tests/locales.test.coffee
+++ tests/locales.test.coffee
@@ -116,6 +116,19 @@
     it "shows the correct CONFIRM translation", ->
       expect(@labels.confirm).to.equal "Применить"

+  describe "Indonesian", ->
+    beforeEach ->
+      @setLocale "id"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "OK"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "Batal"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "OK"
+
   describe "Italian", ->
     beforeEach ->
       @setLocale "it"


Fix id generation within the resource combiner

--- lib/resource-combiner.coffee
+++ lib/resource-combiner.coffee
@@ -5,7 +5,7 @@

 # TODO : Implement cache invalidation on a periodic basis here
 exports.combine = ({resources, req, separator, contentType, prefix}) ->
-  id = resources.length + (resources.href for resource in resources).join ':'
+  id = resources.length + (resource.href for resource in resources).join ':'

   if not resource = resources[id]
     # Create a new resource and load async


Reset ending flag in dequeue

Reset ending flag in dequeue

--- src/redis-transport.coffee
+++ src/redis-transport.coffee
@@ -53,6 +53,7 @@

   dequeue: (name) ->
+    @ending = false
     @events.source (events) =>
       do _dequeue = =>
         try


refactor code

refactor code

--- lib/markdown-preview-enhanced-view.coffee
+++ lib/markdown-preview-enhanced-view.coffee
@@ -8,7 +8,7 @@
 katex = require 'katex'
 matter = require('gray-matter')
 {allowUnsafeEval, allowUnsafeNewFunction} = require 'loophole'
-cheerio = require 'cheerio'
+cheerio = null

 {getMarkdownPreviewCSS} = require './style'
 plantumlAPI = require './puml'
@@ -868,8 +868,8 @@
       if options.matplotlib or options.mpl
         scriptElements = outputDiv.getElementsByTagName('script')
         if scriptElements.length
-          window.d3 = require('../dependencies/mpld3/d3.v3.min.js')
-          window.mpld3 = require('../dependencies/mpld3/mpld3.v0.3.min.js')
+          window.d3 ?= require('../dependencies/mpld3/d3.v3.min.js')
+          window.mpld3 ?= require('../dependencies/mpld3/mpld3.v0.3.min.js')
           for scriptElement in scriptElements
             code = scriptElement.innerHTML
             allowUnsafeNewFunction -> allowUnsafeEval ->
@@ -1108,14 +1108,13 @@
   ## {Function} callback (htmlContent)
   insertCodeChunksResult: (htmlContent)->
     # insert outputDiv and outputElement accordingly
+    cheerio ?= require 'cheerio'
     $ = cheerio.load(htmlContent)
     codeChunks = $('.code-chunk')
     jsCode = ''
     requireCache = {} # key is path
     scriptsStr = ""
-    stylesStr = ""
-
-    # codeChunksArr = []
+
     for codeChunk in codeChunks
       $codeChunk = $(codeChunk)
       dataArgs = $codeChunk.attr('data-args').unescape()
@@ -1136,32 +1135,11 @@
       outputDiv = @codeChunksData[id]?.outputDiv
       outputElement = @codeChunksData[id]?.outputElement

-      # codeChunksArr.push {id, cmd, options, code} # save to arr
-
-      ###
-      # check continue
-      # actually, no need to check continue for getHTMLContent
-      if options.continue
-        last = null
-        if options.continue == true
-          last = codeChunksArr[codeChunksArr.length - 1]
-        else
-          for c in codeChunksArr
-            if c.id == options.continue
-              last = c
-              break
-
-        if last
-          code = last.code + '\n' + code
-        else
-          atom.notifications.addError('Invalid continue for code chunk ' + (options.id or ''), detail: options.continue.toString())
-          return $.html()
-      ###
-
       if outputDiv # append outputDiv result
         $codeChunk.append("
#{outputDiv.innerHTML}
")
         if options.matplotlib or options.mpl
           # remove innerHTML of

+          # this is for fixing mpld3 exporting issue.
           gs = $('.output-div > div', $codeChunk)
           if gs
             for g in gs
@@ -1171,14 +1149,6 @@

           ss = $('.output-div > script', $codeChunk)
           if ss
-            # load d3.min.js and mpld3.min.js
-            ###
-            requires = options.require or []
-            requires.push(path.resolve(__dirname, '../dependencies/mpld3/d3.v3.min.js'))
-            requires.push(path.resolve(__dirname, '../dependencies/mpld3/mpld3.v0.3.min.js'))
-            options.require = requires
-            ###
-
             for s in ss
               $s = $(s)
               c = $s.html()
@@ -1207,10 +1177,6 @@

         jsCode += (requiresStr + code + '\n')

-    # console.log jsCode
-
-    # return callback($.html()) if !jsCode
-    return $.html() if !jsCode
     return $.html() + "#{scriptsStr}\n"

   ##


Update node version to 0.21.0

Update node version to 0.21.0

--- src/apm.coffee
+++ src/apm.coffee
@@ -53,7 +53,7 @@
     process.env.ATOM_API_URL ? 'https://atom.io/api'

   getNodeVersion: ->
-    process.env.ATOM_NODE_VERSION ? '0.20.0'
+    process.env.ATOM_NODE_VERSION ? '0.21.0'

   getNodeArch: ->
     switch process.platform


fix broken pagination for symfiles page

fix broken pagination for symfiles page

--- src/app.coffee
+++ src/app.coffee
@@ -238,7 +238,6 @@
           page: page
           pageCount: pageCount

-  breakpad.use paginate.middleware(10, 50)
   breakpad.get '/symfiles', (req, res, next) ->
     limit = req.query.limit
     offset = req.offset