Update some css to work better with inline elements and block elements

--- vendor/assets/javascripts/mercury/regions/simple.js.coffee
+++ vendor/assets/javascripts/mercury/regions/simple.js.coffee
@@ -15,8 +15,12 @@

   build: ->
-    width = '100%'
-    height = @element.height()
+    if @element.css('display') == 'block'
+      width = '100%'
+      height = @element.height()
+    else
+      width = @element.width()
+      height = @element.height() # 'auto'

     value = @element.text()
     @element.removeClass(Mercury.config.regions.className)
@@ -25,7 +29,6 @@
     @textarea.css
       border: 0
       background: 'transparent'
-      display: 'block'
       'overflow-y': 'hidden'
       width: width
       height: height
@@ -34,6 +37,12 @@
       fontWeight: 'inherit'
       fontStyle: 'inherit'
       color: 'inherit'
+      'min-height': 0
+      padding: '0'
+      margin: 0
+      'border-radius': 0
+      display: 'inherit'
+      lineHeight: 'inherit'
     @element.addClass(Mercury.config.regions.className)
     @element.empty().append(@textarea)


Fixed snippets

Fixed snippets

--- snippets/language-sqr.cson
+++ snippets/language-sqr.cson
@@ -1,49 +1,19 @@
-".source.sqr":
-  heading:
-    prefix: "head"
-    body: '''
-      begin-heading $1
-      	$2
-      end-heading
-
-    '''
-  procedure:
-    prefix: "proc"
-    body: '''
-      begin-procedure $1 ($2)
-      	$3
-      end-procedure
-
-    '''
-  report:
-    prefix: "rep"
-    body: '''
-      begin-report
-      	$1
-      end-report
-
-    '''
-  select:
-    prefix: "sel"
-    body: '''
-      begin-select
-      	$1
-      end-select
-
-    '''
-  sql:
-    prefix: "sql"
-    body: '''
-      begin-sql $1
-      	$2
-      end-sql
-
-    '''
-  if:
-    prefix: "if"
-    body: '''
-      if $1
-      	$2
-      end-if
-
-    '''
+'.source.sqr':
+  'begin-heading ... end-heading':
+    'prefix': 'head'
+    'body': 'begin-heading $1\n\t$0\nend-heading'
+  'begin-procedure ... end-procedure':
+    'prefix': 'proc'
+    'body': 'begin-procedure $1\n\t$0\nend-procedure'
+  'begin-report ... end-report':
+    'prefix': 'rep'
+    'body': 'begin-report\n\t$0\nend-report'
+  'begin-select ... end-select':
+    'prefix': 'sel'
+    'body': 'begin-select\n$0\nend-select'
+  'begin-sql ... end-sql':
+    'prefix': 'sql'
+    'body': 'begin-sql $1\n\t$0\nend-sql'
+  'if ... end-if':
+    'prefix': 'if'
+    'body': 'if ${1:condition}\n\t$0\nend-if'


Mark continuation

Mark continuation

--- test/28.coffee
+++ test/28.coffee
@@ -23,4 +23,4 @@
   return unless 0<(x=array.length)<7
   if 1==x
     x = array[0]
-    return [x >> 6 | 0xC0, x & 0x3F]
+    return [x >> 6 | 0xC0, x & 0x3F | 0x80]


use session storage

use session storage

--- src/plugins/scaleApp.strophe.coffee
+++ src/plugins/scaleApp.strophe.coffee
@@ -90,14 +90,14 @@
 key2cache   = (k) -> "#{CACHE_PREFIX}#{k}"

 saveData = ->
-  if localStorage?
+  if sessionStorage?
     for k in ["jid", "sid", "rid"] when connection[k]?
-      localStorage[key2cache k] = connection[k]
+      sessionStorage[key2cache k] = connection[k]

     for k in ["host", "port", "path"] when connection_options[k]?
-      localStorage[key2cache k] = connection_options[k]
-
-clearData = -> localStorage?.clear()
+      sessionStorage[key2cache k] = connection_options[k]
+
+clearData = -> sessionStorage?.clear()

 onConnected = ->

@@ -195,7 +195,7 @@
 attach_connection = (opt) ->

   connection = create_connection_obj()
-  connection_options.jid = Strophe.getBareJidFromJid connection_options.jid
+  #connection_options.jid = Strophe.getBareJidFromJid connection_options.jid
   connection.attach connection_options.jid
     , connection_options.sid
     , connection_options.rid
@@ -243,10 +243,10 @@

 # Loads the connection data from local storage.
 restoreData = ->
-  if localStorage?
+  if sessionStorage?
     for k in [ "jid", "sid", "rid", "host", "port", "path" ]
       j = key2cache k
-      connection_options[k] = localStorage[j]  if localStorage[j]
+      connection_options[k] = sessionStorage[j]  if sessionStorage[j]

 hasConnectionData = ->
   opt = connection_options


fixed jenkins-notifier script

fixed jenkins-notifier script

--- src/scripts/jenkins-notifier.coffee
+++ src/scripts/jenkins-notifier.coffee
@@ -63,8 +63,7 @@
     envelope.user.type = query.type if query.type
     envelope.notstrat = query.notstrat if query.notstrat
     try
-      for key of req.body
-        data = JSON.parse key
+      data = req.body

       if data.build.phase == 'FINISHED'
         robot.send envelope, data.name


fixed bad module exports problem

fixed bad module exports problem

--- src/scripts/opendns-umbrella.coffee
+++ src/scripts/opendns-umbrella.coffee
@@ -16,24 +16,6 @@
 #   Scott J Roberts - @sroberts

 OPENDNS_KEY = process.env.OPENDNS_KEY
-
-# module.exports = (robot) ->
-#   robot.respond /opendns (.*)/i, (msg) ->
-#
-#     if OPENDNS_KEY?
-#       artifact = msg.match[1].toLowerCase()
-#
-#       msg.http("https://investigate.api.opendns.com/domains/categorization/#{artifact}")
-#         .headers('Authorization': 'Bearer ' + OPENDNS_KEY)
-#         .get() (err, res, body) ->
-#           if res.statusCode is 200
-#
-#             opendns_json = JSON.parse body
-#
-#           else
-#             msg.send "Doh! #{res.statusCode}: Which means that didn't work."
-#     else
-#       msg.send "OpenDNS API key not configured."

 module.exports = (robot) ->
   robot.respond /whats up with (.*)/i, (msg) ->
@@ -63,7 +45,6 @@
     else
       msg.send "OpenDNS API key not configured."

-module.exports = (robot) ->
   robot.respond /opendns rr (.*)/i, (msg) ->

     if OPENDNS_KEY?
@@ -90,7 +71,6 @@
     else
       msg.send "OpenDNS API key not configured."

-module.exports = (robot) ->
   robot.respond /opendns secinfo (.*)/i, (msg) ->

     if OPENDNS_KEY?
@@ -118,7 +98,7 @@
               msg.send response
             else
               msg.send "OpenDNS doesn't know about #{artifact}."
-        else
-          msg.send "Doh! #{res.statusCode}: Which means that didn't work."
+          else
+            msg.send "Doh! #{res.statusCode}: Which means that didn't work."
     else
       msg.send "OpenDNS API key not configured."


Login route needs req.login() call

Login route needs req.login() call

--- src/server/routes/post/login.coffee
+++ src/server/routes/post/login.coffee
@@ -15,8 +15,9 @@
 					reason:
 						info.message
 				return res.status(401).json json
-			else
-				return res.status(200).send()
+			else req.logIn user, (err) ->
+					if err then return next err
+					else return res.status(200).send()
 		return auth req, res, next

 module.exports = route


Update editIncidentReport method to accept 0 values

--- methods/incidentReports.coffee
+++ methods/incidentReports.coffee
@@ -27,9 +27,9 @@

     # Remove existing type props if user changes incident type and merge incident
     # from client with existing incident
-    if incident.cases
+    if incident.cases >= 0
       fieldsToRemove = deaths: true, specify: true
-    else if incident.deaths
+    else if incident.deaths >= 0
       fieldsToRemove = cases: true, specify: true
     else if incident.specify
       fieldsToRemove = cases: true, deaths: true


Reload file view if user type changes

Reload file view if user type changes

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -303,7 +303,6 @@
                             return

                         is_public = group == 'public'
-
                         ext = misc.filename_extension_notilde(opts.path).toLowerCase()

                         if not is_public and (ext == "sws" or ext.slice(0,4) == "sws~")
@@ -348,7 +347,8 @@
                         open_files = store.open_files

                         # Only generate the editor component if we don't have it already
-                        if not open_files.has(opts.path)
+                        # Also regenerate if view type (public/not-public) changes
+                        if not open_files.has(opts.path) or open_files.getIn([opts.path, 'component']).is_public != is_public
                             open_files_order = store.open_files_order

                             # Initialize the file's store and actions
@@ -368,9 +368,12 @@
                             open_files = open_files.setIn([opts.path, 'component'], info)
                             open_files = open_files.setIn([opts.path, 'is_chat_open'], opts.chat)
                             open_files = open_files.setIn([opts.path, 'chat_width'], opts.chat_width)
+                            index = open_files_order.indexOf(opts.path)
+                            if index == -1
+                                index = open_files_order.size
                             @setState
                                 open_files       : open_files
-                                open_files_order : open_files_order.push(opts.path)
+                                open_files_order : open_files_order.set(index, opts.path)

                         if opts.foreground
                             @foreground_project()


[modules] remove not needed newline before class declaration

--- modules/gettext.coffee
+++ modules/gettext.coffee
@@ -1,150 +1,149 @@
 path        = require('path')
 NodeGettext = require('node-gettext')

-module.exports = (grunt) ->
-  return class Gettext
-    constructor: ({ @locales, @cwd, @src, @defaultDomain = 'messages' }) ->
-      @gt = new NodeGettext()
-
-      ###*
-       * Load l10n files
-       * @todo Since node-gettext doesn't have method for switching between languages AND domains,
-       *       use `dgettext('{{locale}}:{{domain'), 'String')` to switch between locales and domains
-       *       `/locale/en/{defaultLocale}.po` will result in `en` domain.
-       *       `/locale/en/nav/bar.po` will result in `en:nav:bar` domain.
-       *       Related Github issues:
-       *       * https://github.com/andris9/node-gettext/issues/22
-       *       * https://github.com/LotusTM/Kotsu/issues/45
-      ###
-      @load = ({
-        localeDir
-        cwd = path.join(@cwd, '/')
-        src = @src
-        domain = false
-        defaultDomain = @defaultDomain
-      }) ->
-        cwd = path.join(cwd, localeDir)
-
-        grunt.file.expand({ cwd: cwd, filter: 'isFile' }, src).forEach (filepath) =>
-          resolvedDomain = if domain then domain else filepath.replace('LC_MESSAGES/', '').replace('/', ':').replace(path.extname(filepath), '')
-          resolvedDomain = if resolvedDomain == defaultDomain then localeDir else localeDir + ':' + resolvedDomain
-          messages = grunt.file.read(path.join(cwd, filepath), { encoding: null })
-
-          @gt.addTextdomain(resolvedDomain, messages)
-
-      @locales.forEach (locale) =>
-        @load({ localeDir: locale })
-
-    resolveDomain: (domain) ->
-      if domain.charAt(0) == ':' then @gt.textdomain() + domain else domain
-
-    textdomain: (domain) ->
-      @gt.textdomain(@resolveDomain(domain))
+module.exports = (grunt) -> class Gettext
+  constructor: ({ @locales, @cwd, @src, @defaultDomain = 'messages' }) ->
+    @gt = new NodeGettext()

     ###*
-    * Load string from current locale
-    * @param {string} string String, which should be loaded
-    * @return {string} Translated string into current locale
+     * Load l10n files
+     * @todo Since node-gettext doesn't have method for switching between languages AND domains,
+     *       use `dgettext('{{locale}}:{{domain'), 'String')` to switch between locales and domains
+     *       `/locale/en/{defaultLocale}.po` will result in `en` domain.
+     *       `/locale/en/nav/bar.po` will result in `en:nav:bar` domain.
+     *       Related Github issues:
+     *       * https://github.com/andris9/node-gettext/issues/22
+     *       * https://github.com/LotusTM/Kotsu/issues/45
     ###
-    gettext: (string) ->
-      @gt.gettext(arguments...)
+    @load = ({
+      localeDir
+      cwd = path.join(@cwd, '/')
+      src = @src
+      domain = false
+      defaultDomain = @defaultDomain
+    }) ->
+      cwd = path.join(cwd, localeDir)

-    ###*
-     * Load string from specified domain
-     * @param {string} domain Domain or locale, from which string should be loaded
-     *                        `en-US:other:inner` will load from `en-US/other/inner.po`
-     *                        `:other` will load from `{{currentLocale}}/other.po`
-     * @param {string} string String, which should be loaded
-     * @return {string} Translated string into specified locale
-    ###
-    dgettext: (domain, string) ->
-      [domain, args...] = arguments
-      @gt.dgettext(@resolveDomain(domain), args...)
+      grunt.file.expand({ cwd: cwd, filter: 'isFile' }, src).forEach (filepath) =>
+        resolvedDomain = if domain then domain else filepath.replace('LC_MESSAGES/', '').replace('/', ':').replace(path.extname(filepath), '')
+        resolvedDomain = if resolvedDomain == defaultDomain then localeDir else localeDir + ':' + resolvedDomain
+        messages = grunt.file.read(path.join(cwd, filepath), { encoding: null })

-    ###*
-    * Load plural string from current locale
-    * @param {string} string       String, which should be loaded
-    * @param {string} pluralString Plural form of string
-    * @param {number} count        Count for detecting correct plural form
-    * @return {string} Pluralized and translated into current locale string
-    ###
-    ngettext: (string, pluralString, count) ->
-      @gt.ngettext(arguments...)
+        @gt.addTextdomain(resolvedDomain, messages)

-    ###*
-     * Load plural string from specified domain
-     * @param {string} domain       Domain or locale, from which string should be loaded
-     *                              `en-US:other:inner` will load from `en-US/other/inner.po`
-     *                              `:other` will load from `{{currentLocale}}/other.po`
-     * @param {string} string       String, which should be loaded
-     * @param {string} pluralString Plural form of string
-     * @param {number} count        Count for detecting correct plural form
-     * @return {string} Pluralized and translated into specified locale string
-    ###
-    dngettext: (domain, string, pluralString, count) ->
-      [domain, args...] = arguments
-      @gt.dngettext(@resolveDomain(domain), args...)
+    @locales.forEach (locale) =>
+      @load({ localeDir: locale })

-    ###*
-    * Load string of specific context from current locale
-    * @param {string} context Context of curret string
-    * @param {string} string  String, which should be loaded
-    * @return {string} Translated string into current locale
-    ###
-    pgettext: (context, string) ->
-      @gt.pgettext(arguments...)
+  resolveDomain: (domain) ->
+    if domain.charAt(0) == ':' then @gt.textdomain() + domain else domain

-    ###*
-     * Load string of specific context from specified domain
-     * @param {string} domain  Domain or locale, from which string should be loaded
-     *                         `en-US:other:inner` will load from `en-US/other/inner.po`
-     *                         `:other` will load from `{{currentLocale}}/other.po`
-     * @param {string} context Context of curret string
-     * @param {string} string  String, which should be loaded
-     * @return {string} Translated string into specified locale
-    ###
-    dpgettext: (domain, context, string) ->
-      [domain, args...] = arguments
-      @gt.dpgettext(@resolveDomain(domain), args...)
+  textdomain: (domain) ->
+    @gt.textdomain(@resolveDomain(domain))

-    ###*
-    * Load plural string of specific context from current locale
-    * @param {string} context         Context of curret string
-    * @param {string} string          String, which should be loaded
-    * @param {string} pluralString    Plural form of string
-    * @param {number} count           Count for detecting correct plural form
-    * @return {string} Pluralized and translated into current locale string
-    ###
-    npgettext: (context, string, pluralString, count) ->
-      @gt.npgettext(arguments...)
+  ###*
+  * Load string from current locale
+  * @param {string} string String, which should be loaded
+  * @return {string} Translated string into current locale
+  ###
+  gettext: (string) ->
+    @gt.gettext(arguments...)

-    ###*
-     * Load plural string of specific context from specified domain
-     * @param {string} domain       Domain or locale, from which string should be loaded
-     *                              `en-US:other:inner` will load from `en-US/other/inner.po`
-     *                              `:other` will load from `{{currentLocale}}/other.po`
-     * @param {string} context      Context of curret string
-     * @param {string} string       String, which should be loaded
-     * @param {string} pluralString Plural form of string
-     * @param {number} count        Count for detecting correct plural form
-     * @return {string} Pluralized and translated into specified locale string
-    ###
-    dnpgettext: (domain, context, string, pluralString, count) ->
-      [domain, args...] = arguments
-      @gt.dnpgettext(@resolveDomain(domain), args...)
+  ###*
+   * Load string from specified domain
+   * @param {string} domain Domain or locale, from which string should be loaded
+   *                        `en-US:other:inner` will load from `en-US/other/inner.po`
+   *                        `:other` will load from `{{currentLocale}}/other.po`
+   * @param {string} string String, which should be loaded
+   * @return {string} Translated string into specified locale
+  ###
+  dgettext: (domain, string) ->
+    [domain, args...] = arguments
+    @gt.dgettext(@resolveDomain(domain), args...)

-    nunjucksExtensions: (env, currentLocale) ->
-      @textdomain(currentLocale)
+  ###*
+  * Load plural string from current locale
+  * @param {string} string       String, which should be loaded
+  * @param {string} pluralString Plural form of string
+  * @param {number} count        Count for detecting correct plural form
+  * @return {string} Pluralized and translated into current locale string
+  ###
+  ngettext: (string, pluralString, count) ->
+    @gt.ngettext(arguments...)

-      env.addGlobal 'textdomain', (domain) => @textdomain(arguments...)
+  ###*
+   * Load plural string from specified domain
+   * @param {string} domain       Domain or locale, from which string should be loaded
+   *                              `en-US:other:inner` will load from `en-US/other/inner.po`
+   *                              `:other` will load from `{{currentLocale}}/other.po`
+   * @param {string} string       String, which should be loaded
+   * @param {string} pluralString Plural form of string
+   * @param {number} count        Count for detecting correct plural form
+   * @return {string} Pluralized and translated into specified locale string
+  ###
+  dngettext: (domain, string, pluralString, count) ->
+    [domain, args...] = arguments
+    @gt.dngettext(@resolveDomain(domain), args...)

-      env.addGlobal 'gettext', () => @gettext(arguments...)
-      env.addGlobal 'dgettext', () => @dgettext(arguments...)
-      env.addGlobal 'ngettext', () => @ngettext(arguments...)
-      env.addGlobal 'dngettext', () => @dngettext(arguments...)
-      env.addGlobal 'pgettext', () => @pgettext(arguments...)
-      env.addGlobal 'dpgettext', () => @dpgettext(arguments...)
-      env.addGlobal 'npgettext', () => @npgettext(arguments...)
-      env.addGlobal 'dnpgettext', () => @dnpgettext(arguments...)
+  ###*
+  * Load string of specific context from current locale
+  * @param {string} context Context of curret string
+  * @param {string} string  String, which should be loaded
+  * @return {string} Translated string into current locale
+  ###
+  pgettext: (context, string) ->
+    @gt.pgettext(arguments...)

-      return+  ###*
+   * Load string of specific context from specified domain
+   * @param {string} domain  Domain or locale, from which string should be loaded
+   *                         `en-US:other:inner` will load from `en-US/other/inner.po`
+   *                         `:other` will load from `{{currentLocale}}/other.po`
+   * @param {string} context Context of curret string
+   * @param {string} string  String, which should be loaded
+   * @return {string} Translated string into specified locale
+  ###
+  dpgettext: (domain, context, string) ->
+    [domain, args...] = arguments
+    @gt.dpgettext(@resolveDomain(domain), args...)
+
+  ###*
+  * Load plural string of specific context from current locale
+  * @param {string} context         Context of curret string
+  * @param {string} string          String, which should be loaded
+  * @param {string} pluralString    Plural form of string
+  * @param {number} count           Count for detecting correct plural form
+  * @return {string} Pluralized and translated into current locale string
+  ###
+  npgettext: (context, string, pluralString, count) ->
+    @gt.npgettext(arguments...)
+
+  ###*
+   * Load plural string of specific context from specified domain
+   * @param {string} domain       Domain or locale, from which string should be loaded
+   *                              `en-US:other:inner` will load from `en-US/other/inner.po`
+   *                              `:other` will load from `{{currentLocale}}/other.po`
+   * @param {string} context      Context of curret string
+   * @param {string} string       String, which should be loaded
+   * @param {string} pluralString Plural form of string
+   * @param {number} count        Count for detecting correct plural form
+   * @return {string} Pluralized and translated into specified locale string
+  ###
+  dnpgettext: (domain, context, string, pluralString, count) ->
+    [domain, args...] = arguments
+    @gt.dnpgettext(@resolveDomain(domain), args...)
+
+  nunjucksExtensions: (env, currentLocale) ->
+    @textdomain(currentLocale)
+
+    env.addGlobal 'textdomain', (domain) => @textdomain(arguments...)
+
+    env.addGlobal 'gettext', () => @gettext(arguments...)
+    env.addGlobal 'dgettext', () => @dgettext(arguments...)
+    env.addGlobal 'ngettext', () => @ngettext(arguments...)
+    env.addGlobal 'dngettext', () => @dngettext(arguments...)
+    env.addGlobal 'pgettext', () => @pgettext(arguments...)
+    env.addGlobal 'dpgettext', () => @dpgettext(arguments...)
+    env.addGlobal 'npgettext', () => @npgettext(arguments...)
+    env.addGlobal 'dnpgettext', () => @dnpgettext(arguments...)
+
+    return


Slightly more sorted props

Slightly more sorted props

--- resources/assets/coffee/react/contest-voting/art-entry-list.coffee
+++ resources/assets/coffee/react/contest-voting/art-entry-list.coffee
@@ -24,14 +24,14 @@
     entries = entries.map (entry, index) =>
       el ArtEntry,
         key: index,
+        contest: @state.contest,
         displayIndex: index,
         entry: entry,
-        waitingForResponse: @state.waitingForResponse,
+        isSelected: selected.has(entry.id)
         options: @state.options,
-        contest: @state.contest,
-        isSelected: selected.has(entry.id)
         selected: @state.selected,
         totalVotes: if @state.contest.show_votes then totalVotes
+        waitingForResponse: @state.waitingForResponse,

     if @state.contest.show_votes
       partitions = _.partition entries, (i) ->


move v_s test in a describe block

move v_s test in a describe block

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -89,28 +89,26 @@
     beforeEach ->
       editor.setText('012345')
       editor.setCursorScreenPosition([0, 1])
+
+    it "deletes the character to the right and enters insert mode", ->
       keydown('s')
-
-    it "deletes the character to the right and enters insert mode", ->
       expect(editorView).toHaveClass 'insert-mode'
       expect(editor.getText()).toBe '02345'
       expect(editor.getCursorScreenPosition()).toEqual [0, 1]
       expect(vimState.getRegister('"').text).toBe '1'

-  describe "the v_s keybinding", ->
-    beforeEach ->
-      editor.setText('012345')
-      editor.setCursorScreenPosition([0, 1])
-      keydown('v')
-      editor.selectRight()
-      editor.selectRight()
-      keydown('s')
-
-    it "deletes the selected characters and enters insert mode", ->
-      expect(editorView).toHaveClass 'insert-mode'
-      expect(editor.getText()).toBe '0345'
-      expect(editor.getCursorScreenPosition()).toEqual [0, 1]
-      expect(vimState.getRegister('"').text).toBe '12'
+    describe "in visual mode", ->
+      beforeEach ->
+        keydown('v')
+        editor.selectRight()
+        editor.selectRight()
+        keydown('s')
+
+      it "deletes the selected characters and enters insert mode", ->
+        expect(editorView).toHaveClass 'insert-mode'
+        expect(editor.getText()).toBe '0345'
+        expect(editor.getCursorScreenPosition()).toEqual [0, 1]
+        expect(vimState.getRegister('"').text).toBe '12'

   describe "the S keybinding", ->
     beforeEach ->


Fix command name in test description

Fix command name in test description

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -327,7 +327,7 @@
             expect(treeView).toBeVisible()
             expect(treeView.list).not.toMatchSelector(':focus')

-  describe "when tree-view:reveal-current-file is triggered on the root view", ->
+  describe "when tree-view:reveal-active-file is triggered on the root view", ->
     beforeEach ->
       treeView.detach()
       spyOn(treeView, 'focus')


Use platform separator in paths

Use platform separator in paths

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -567,7 +567,7 @@

       it "selects the path's parent dir if its entry is not visible", ->
         waitsForPromise ->
-          atom.workspace.open('dir1/sub-dir1/sub-file1')
+          atom.workspace.open(path.join('dir1', 'sub-dir1', 'sub-file1'))

         runs ->
           dirView = root.find('.directory:contains(dir1)')
@@ -1248,8 +1248,8 @@
         it "opens an add dialog with the file's current directory path populated", ->
           expect(addDialog).toExist()
           expect(addDialog.promptText.text()).toBeTruthy()
-          expect(atom.project.relativize(dirPath)).toMatch(/[^\/]$/)
-          expect(addDialog.miniEditor.getText()).toBe(atom.project.relativize(dirPath) + "/")
+          expect(atom.project.relativize(dirPath)).toMatch(/[^\\\/]$/)
+          expect(addDialog.miniEditor.getText()).toBe(atom.project.relativize(dirPath) + path.sep)
           expect(addDialog.miniEditor.getEditor().getCursorBufferPosition().column).toBe addDialog.miniEditor.getText().length
           expect(addDialog.miniEditor.isFocused).toBeTruthy()

@@ -1262,7 +1262,7 @@
             expect(directoryModifiedHandler).toHaveBeenCalled()
             expect(treeView.find('.selected').text()).toBe path.basename(filePath)

-        describe "when the path without a trailing '/' is changed and confirmed", ->
+        describe "when the path without a trailing '#{path.sep}' is changed and confirmed", ->
           describe "when no file exists at that location", ->
             it "add a file, closes the dialog and selects the file in the tree-view", ->
               newPath = path.join(dirPath, "new-test-file.txt")
@@ -1293,9 +1293,9 @@
               expect(addDialog).toHaveClass('error')
               expect(addDialog.hasParent()).toBeTruthy()

-        describe "when the path with a trailing '/' is changed and confirmed", ->
+        describe "when the path with a trailing '#{path.sep}' is changed and confirmed", ->
           it "shows an error message and does not close the dialog", ->
-            addDialog.miniEditor.insertText("new-test-file/")
+            addDialog.miniEditor.insertText("new-test-file" + path.sep)
             addDialog.trigger 'core:confirm'

             expect(addDialog.errorMessage.text()).toContain 'names must not end with'
@@ -1337,8 +1337,8 @@

           expect(addDialog).toExist()
           expect(addDialog.promptText.text()).toBeTruthy()
-          expect(atom.project.relativize(dirPath)).toMatch(/[^\/]$/)
-          expect(addDialog.miniEditor.getText()).toBe(atom.project.relativize(dirPath) + "/")
+          expect(atom.project.relativize(dirPath)).toMatch(/[^\\\/]$/)
+          expect(addDialog.miniEditor.getText()).toBe(atom.project.relativize(dirPath) + path.sep)
           expect(addDialog.miniEditor.getEditor().getCursorBufferPosition().column).toBe addDialog.miniEditor.getText().length
           expect(addDialog.miniEditor.isFocused).toBeTruthy()

@@ -1379,17 +1379,17 @@
         it "opens an add dialog with the file's current directory path populated", ->
           expect(addDialog).toExist()
           expect(addDialog.promptText.text()).toBeTruthy()
-          expect(atom.project.relativize(dirPath)).toMatch(/[^\/]$/)
-          expect(addDialog.miniEditor.getText()).toBe(atom.project.relativize(dirPath) + "/")
+          expect(atom.project.relativize(dirPath)).toMatch(/[^\\\/]$/)
+          expect(addDialog.miniEditor.getText()).toBe(atom.project.relativize(dirPath) + path.sep)
           expect(addDialog.miniEditor.getEditor().getCursorBufferPosition().column).toBe addDialog.miniEditor.getText().length
           expect(addDialog.miniEditor.isFocused).toBeTruthy()

-        describe "when the path without a trailing '/' is changed and confirmed", ->
+        describe "when the path without a trailing '#{path.sep}' is changed and confirmed", ->
           describe "when no directory exists at the given path", ->
             it "adds a directory and closes the dialog", ->
               treeView.attachToDom()
-              newPath = path.join(dirPath, "new/dir")
-              addDialog.miniEditor.insertText("new/dir")
+              newPath = path.join(dirPath, 'new', 'dir')
+              addDialog.miniEditor.insertText("new#{path.sep}dir")
               addDialog.trigger 'core:confirm'
               expect(fs.isDirectorySync(newPath)).toBeTruthy()
               expect(addDialog.parent()).not.toExist()
@@ -1398,12 +1398,12 @@
               expect(atom.workspaceView.getActiveView().isFocused).toBeFalsy()
               expect(dirView.find('.directory.selected:contains(new)').length).toBe 1

-        describe "when the path with a trailing '/' is changed and confirmed", ->
+        describe "when the path with a trailing '#{path.sep}' is changed and confirmed", ->
           describe "when no directory exists at the given path", ->
             it "adds a directory and closes the dialog", ->
               treeView.attachToDom()
-              newPath = path.join(dirPath, "new/dir")
-              addDialog.miniEditor.insertText("new/dir/")
+              newPath = path.join(dirPath, 'new', 'dir')
+              addDialog.miniEditor.insertText("new#{path.sep}dir#{path.sep}")
               addDialog.trigger 'core:confirm'
               expect(fs.isDirectorySync(newPath)).toBeTruthy()
               expect(addDialog.parent()).not.toExist()
@@ -1421,8 +1421,8 @@
               expandedView.expand()

               treeView.attachToDom()
-              newPath = path.join(dirPath, "new2/")
-              addDialog.miniEditor.insertText("new2/")
+              newPath = path.join(dirPath, "new2") + path.sep
+              addDialog.miniEditor.insertText("new2#{path.sep}")
               addDialog.trigger 'core:confirm'
               expect(fs.isDirectorySync(newPath)).toBeTruthy()
               expect(addDialog.parent()).not.toExist()
@@ -1436,7 +1436,7 @@
             it "shows an error message and does not close the dialog", ->
               newPath = path.join(dirPath, "new-dir")
               fs.makeTreeSync(newPath)
-              addDialog.miniEditor.insertText("new-dir/")
+              addDialog.miniEditor.insertText("new-dir#{path.sep}")
               addDialog.trigger 'core:confirm'

               expect(addDialog.errorMessage.text()).toContain 'already exists'
@@ -1491,7 +1491,7 @@

           describe "when the directories along the new path don't exist", ->
             it "creates the target directory before moving the file", ->
-              newPath = path.join(rootDirPath, 'new/directory', 'renamed-test-file.txt')
+              newPath = path.join(rootDirPath, 'new', 'directory', 'renamed-test-file.txt')
               moveDialog.miniEditor.setText(newPath)

               moveDialog.trigger 'core:confirm'
@@ -1608,7 +1608,7 @@

           describe "when the directories along the new path don't exist", ->
             it "duplicates the tree and opens the new file", ->
-              newPath = path.join(rootDirPath, 'new/directory', 'duplicated-test-file.txt')
+              newPath = path.join(rootDirPath, 'new', 'directory', 'duplicated-test-file.txt')
               copyDialog.miniEditor.setText(newPath)

               waitsForFileToOpen ->


workaround for and closes #415

workaround for and closes #415

--- src/modules/toolbar.coffee
+++ src/modules/toolbar.coffee
@@ -56,6 +56,7 @@
       @quill.focus()
       range = @quill.getSelection()
       callback(range, value) if range?
+      @quill.editor.selection.scrollIntoView() if dom.isIE(11)
       @preventUpdate = false
       return true
     )


tooltip: Call event.preventDefault() when handling enter/escape in the tooltip, prevents form submit

--- src/modules/tooltip.coffee
+++ src/modules/tooltip.coffee
@@ -23,11 +23,16 @@
     )

   initTextbox: (textbox, enterCallback, escapeCallback) ->
-    dom(textbox).on('keyup', (event) =>
+    dom(textbox).on('keydown', (event) =>
       switch event.which
-        when dom.KEYS.ENTER  then enterCallback.call(this)
-        when dom.KEYS.ESCAPE then escapeCallback.call(this)
-        else return true
+        when dom.KEYS.ENTER
+          event.preventDefault()
+          enterCallback.call(this)
+        when dom.KEYS.ESCAPE
+          event.preventDefault()
+          escapeCallback.call(this)
+        else
+          return true
     )

   hide: ->


:lipstick:

:lipstick:

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -7,7 +7,7 @@
     atom.project.setPath(atom.project.resolve('dir'))
     workspace = new Workspace

-  describe "::openSync(filePath, options)", ->
+  describe "::openSync(uri, options)", ->
     [activePane, initialItemCount] = []

     beforeEach ->
@@ -15,7 +15,7 @@
       spyOn(activePane, 'activate')
       initialItemCount = activePane.items.length

-    describe "when called with no path", ->
+    describe "when called without a uri", ->
       it "adds and activates an empty editor on the active pane", ->
         editor = workspace.openSync()
         expect(activePane.items.length).toBe initialItemCount + 1
@@ -23,8 +23,8 @@
         expect(editor.getPath()).toBeUndefined()
         expect(activePane.activate).toHaveBeenCalled()

-    describe "when called with a path", ->
-      describe "when the active pane already has an editor for the given path", ->
+    describe "when called with a uri", ->
+      describe "when the active pane already has an editor for the given uri", ->
         it "activates the existing editor on the active pane", ->
           editor1 = workspace.openSync('a')
           editor2 = workspace.openSync('b')
@@ -37,7 +37,7 @@
           expect(activePane.activate).toHaveBeenCalled()
           expect(activePane.items.length).toBe 2

-      describe "when the active pane does not have an editor for the path being opened", ->
+      describe "when the active pane does not have an editor for the given uri", ->
         it "adds and activates a new editor for the given path on the active pane", ->
           editor = workspace.openSync('a')
           expect(activePane.items.length).toBe 1


Made correct/incorrect determination in coffeescript a little more sensitive.  Still very hacky.


Remove comment

Remove comment

--- src/views/connection.cjsx
+++ src/views/connection.cjsx
@@ -88,19 +88,4 @@
       database: @refs.databaseInput.value
     )

-    ###
-    .then(=>
-      @setState loading: false
-
-      _.defer(-> ApplicationActions.loadView('main'))
-    ).catch((err) =>
-      @setState loading: false
-
-      console.log err
-
-      # Play 'beep' sound
-      _.defer(ApplicationDelegate.beep)
-    )
-    ###
-
 module.exports = ConnectionView


:racehorse: Use DOM APIs to find editor views

--- src/workspace-view.coffee
+++ src/workspace-view.coffee
@@ -240,7 +240,8 @@
   #
   # Returns an {Array} of {EditorView}s.
   getEditorViews: ->
-    @panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray()
+    for editorElement in @panes.element.querySelectorAll('.pane > .item-views > .editor')
+      $(editorElement).view()

   # Public: Prepend an element or view to the panels at the top of the
   # workspace.


reinitialized accordions

reinitialized accordions

--- app/coffee/drm-accordion/drm-accordion.coffee
+++ app/coffee/drm-accordion/drm-accordion.coffee
@@ -86,5 +86,5 @@
         @showDefaultContent()
         @addEvents()

-# new DrmAccordionContent()
-# new DrmAccordionNav()+new DrmAccordionContent()
+new DrmAccordionNav()


don't update cursor if elem is not focused

If the focus is not on the textarea moving the cursor moves the focus
to the textarea which is very annoying if the user is already typing in
another form input.

--- src/client/textarea.coffee
+++ src/client/textarea.coffee
@@ -30,7 +30,7 @@
     scrollTop = elem.scrollTop
     elem.value = newText
     elem.scrollTop = scrollTop if elem.scrollTop != scrollTop
-    [elem.selectionStart, elem.selectionEnd] = newSelection
+    [elem.selectionStart, elem.selectionEnd] = newSelection if window.document.activeElement === elem

   @on 'insert', insert_listener = (pos, text) ->
     transformCursor = (cursor) ->


Make the retry-on-fail behaviour optional

--- services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee
+++ services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee
@@ -14,7 +14,7 @@
 		_go = () ->
 			request opts, (err, response, data) ->
 				if err?
-					if iteration == retryTimings.length
+					if iteration == retryTimings.length or !opts.retryOnFail
 						logger.err {err, url: opts.url},
 							"Error in analytics request, retries failed"
 						return callback(err)
@@ -51,6 +51,7 @@
 			method:"POST"
 			timeout:1000
 			url: "/user/#{user_id}/event"
+			retryOnFail: true
 		if settings.overleaf?
 			opts.qs = {fromV2: 1}
 		makeRequest opts, callback
@@ -68,6 +69,7 @@
 			qs:
 				userId: userId
 				projectId: projectId
+			retryOnFail: true
 		if settings.overleaf?
 			opts.qs.fromV2 = 1
 		makeRequest opts, callback


Rename activePane to paneView

Rename activePane to paneView

--- lib/markdown-preview.coffee
+++ lib/markdown-preview.coffee
@@ -13,8 +13,8 @@
       new MarkdownPreviewView(pathname)

   show: ->
-    activePane = atom.workspaceView.getActivePane()
-    editor = activePane.activeItem
+    paneView = atom.workspaceView.getActivePaneView()
+    editor = paneView.activeItem

     unless editor.getGrammar?().scopeName is "source.gfm"
       console.warn("Can not render markdown for '#{editor.getUri() ? 'untitled'}'")
@@ -23,4 +23,4 @@
     uri = "markdown-preview://#{editor.getPath()}"
     atom.workspace.open(uri, split: 'right').done (markdownPreviewView) ->
       markdownPreviewView.renderMarkdown()
-      activePane.focus()
+      paneView.focus()


tableName added to sync

tableName added to sync

--- lib/model_extensions.coffee
+++ lib/model_extensions.coffee
@@ -156,8 +156,8 @@
   ###################################

   model_type::cache = -> model_type.cache
+  model_type::schema = model_type.schema = -> model_type::sync('schema')
   model_type::tableName = model_type.tableName = -> model_type::sync('tableName')
-  model_type::schema = model_type.schema = -> model_type::sync('schema')
   model_type::relation = model_type.relation = (key) -> if schema = model_type::sync('schema') then schema.relation(key) else return undefined
   model_type::relationIsEmbedded = model_type.relationIsEmbedded = (key) -> return if relation = model_type.relation(key) then !!relation.embed else false
   model_type::reverseRelation = model_type.reverseRelation = (key) -> if schema = model_type::sync('schema') then schema.reverseRelation(key) else return undefined


Changed exit code message, longer var name

--- lib/spec-runner-view.coffee
+++ lib/spec-runner-view.coffee
@@ -32,5 +32,5 @@
       cwd: atom.project.getPath()
     stdout = (output) => @addLine(output)
     stderr = (output) => @addLine(output)
-    exit = (code) -> console.log("pwd exited with #{code}")
+    exit = (return_code) -> console.log("Exited with #{return_code}")
     process = new BufferedProcess({command, args, options, stdout, stderr, exit})


:art: cleanup

:art: cleanup

--- lib/visual-blockwise.coffee
+++ lib/visual-blockwise.coffee
@@ -96,8 +96,7 @@
     which = if @after then 'end' else 'start'
     @eachSelection (s) ->
       s.cursor.setBufferPosition s.getBufferRange()[which]
-    @vimState.activate('normal')
-    @vimState.activate('insert')
+    @new('Insert').execute()

 class BlockwiseInsertAfterEndOfLine extends BlockwiseInsertAtBeginningOfLine
   @extend()
@@ -106,21 +105,24 @@
 class BlockwiseSelect extends VisualBlockwise
   @extend()
   execute: ->
-    s = @editor.getLastSelection()
-    tail = s.getTailBufferPosition()
-    head = s.getHeadBufferPosition()
-    {start, end} = s.getBufferRange()
-    [action, step] = if s.isReversed() then ['Up', -1] else ['Down', +1]
+    selection = @editor.getLastSelection()
+    wasReversed = reversed = selection.isReversed()
+    [startRow, endRow] = selection.getBufferRowRange()
+    {start: {column: startColumn}, end: {column: endColumn}} = selection.getBufferRange()

-    range = new Range(tail, [tail.row, head.column])
-    range = range.translate([0, -1], [0, +1]) if start.column >= end.column
+    if startColumn >= endColumn
+      reversed = not reversed
+      startColumn += 1
+      endColumn -= 1

-    s.setBufferRange(range, reversed: head.column < tail.column)
-    # NOTE: Need to skip the amount of rows where no selectable chars exist.
-    _.times (end.row - start.row), =>
-      range = range.translate([step, 0], [step, 0])
-      if @editor.getTextInBufferRange(range)
-        @new("BlockwiseMove#{action}").execute()
+    ranges = ([[row, startColumn], [row, endColumn]] for row in [startRow..endRow])
+    @editor.setSelectedBufferRanges(ranges, {reversed})
+    if wasReversed
+      @updateProperties {head: @getTop(), tail: @getBottom()}
+    else
+      @updateProperties {head: @getBottom(), tail: @getTop()}
+    @eachSelection (s) ->
+      s.destroy() if s.isEmpty()

 class BlockwiseRestoreCharacterwise extends VisualBlockwise
   @extend()


pefully fix https://github.com/sagemathinc/cocalc/issues/2069 --- "render: toISOString Invalid time"

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -530,9 +530,11 @@

     render: ->
         d = if misc.is_date(@props.date) then @props.date else new Date(@props.date)
-        if isNaN(d)  # http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
-            # This can and does happen!  Passing this to the third party TimeAgo component
-            # goes ballistic and crashes react (lame, but whatever).
+        try
+            d.toISOString()
+        catch
+            # NOTE: Using isNaN might not work on all browsers, so we use try/except
+            # See https://github.com/sagemathinc/cocalc/issues/2069
             return
Invalid Date

         if @props.popover
             s = d.toLocaleString()


Strip $ from output in Markdown

Strip $ from output in Markdown

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -711,6 +711,10 @@
     shouldComponentUpdate: (newProps) ->
         return @props.value != newProps.value or not underscore.isEqual(@props.style, newProps.style)

+    update_escaped_chars: ->
+        node = $(ReactDOM.findDOMNode(@))
+        node.text(node[0].innerText.replace(/\\\$/g, '$'))
+
     update_mathjax: ->
         if @_x?.has_mathjax?
             $(ReactDOM.findDOMNode(@)).mathjax()
@@ -721,10 +725,12 @@
     componentDidUpdate: ->
         @update_links()
         @update_mathjax()
+        @update_escaped_chars()

     componentDidMount: ->
         @update_links()
         @update_mathjax()
+        @update_escaped_chars()

     to_html: ->
         if @props.value


Separate commit since Git assumed this was renamed to different file

--- src/synapse/connect.coffee
+++ src/synapse/connect.coffee
@@ -1,155 +0,0 @@
-((root, factory) ->
-    if typeof exports isnt 'undefined'
-        # Node/CommonJS
-        factory(root, exports, require('synapse/core'))
-    else if typeof define is 'function' and define.amd
-        # AMD
-        define 'synapse/connect', ['synapse/core', 'exports'], (core, exports) ->
-            factory(root, exports, core)
-    else
-        # Browser globals
-        root.SynapseConnect = factory(root, {}, root.SynapseCore)
-) @, (root, connect, core) ->
-
-    # Detects an appropriate event to attach an event handler to. This
-    # applies only to subjects.
-    detectEvent = (object, args...) ->
-        if (value = object.hook.detectEvent object.raw, args...)
-            return value
-        throw new Error "#{object.hook.typeName} types do not support events"
-
-    # Attaches an event handler. This applies only to subjects.
-    onEvent = (object, args...) ->
-        if (value = object.hook.onEventHandler? object.raw, args...)
-            return object
-        throw new Error "#{object.hook.typeName} types do not support events"
-
-    # Detaches an event handler. This applies only to subjects.
-    offEvent = (object, args...) ->
-        if (value = object.hook.offEventHandler? object.raw, args...)
-            return object
-        throw new Error "#{object.hook.typeName} types do not support events"
-
-    # Triggers an event handler. This applies only to subjects.
-    triggerEvent = (object, args...) ->
-        if (value = object.hook.triggerEventHandler? object.raw, args...)
-            return object
-        throw new Error "#{object.hook.typeName} types do not support events"
-
-    # Detects an appropriate interface (property or method) to use as a
-    # data source for a given communication channel.
-    detectInterface = (object) ->
-        object.hook.detectInterface? object.raw
-
-    # Detects an interface for the other end of the channel.
-    detectOtherInterface = (object) ->
-        object.hook.detectOtherInterface? object.raw
-
-
-    # ## Create a Connection
-    #
-    # - ``event`` - the event that will trigger the ``subjectInterface`` to be
-    # invoked
-    # - ``subjectInterface`` - the interface for getting a value from the subject
-    # - ``observerInterface`` - the interface for setting a value on the observer
-    # - ``converter`` - an intermediate function that transforms the value received
-    # before passing it to the ``observerInterface``
-    # - ``triggerOnBind`` - defines when or not to trigger the event once bound
-    # acting as the _initial_ invocation.
-    defaultConnectOptions =
-        event: null
-        subjectInterface: null
-        observerInterface: null
-        converter: null
-        triggerOnBind: true
-
-    # The workhorse which sets up a single connection between the ``subject``
-    # and ``observer``. Note, the ``subject`` and ``observer``
-    connectOne = (subject, observer, options) ->
-
-        for key, value of defaultConnectOptions
-            if not options[key]
-                options[key] = value
-
-        # A converter may be defined as a string which is assumed to be a
-        # method name on the original object
-        if (converter = options.converter) and not core.isFunction(converter)
-            converter = observer.object[converter]
-
-        # Detect the interface for the subject if not defined
-        if not (subjectInterface = options.subjectInterface)
-            if not (subjectInterface = detectInterface(subject) or detectOtherInterface(observer)) and not converter
-                throw new Error "An interface for #{subject.hook.typeName} objects could not be detected"
-
-        # Detect the interface for the observer if not defined
-        if not (observerInterface = options.observerInterface)
-            if not (observerInterface = detectInterface(observer) or detectOtherInterface(subject))
-                throw new Error "An interface for #{observer.hook.typeName} objects could not be detected"
-
-        # Get the events that trigger the subject's change in state
-        if not (events = options.event)
-            events = detectEvent(subject, subjectInterface)
-        if not core.isArray(events) then events = [events]
-
-        triggerOnBind = options.triggerOnBind
-
-        for event in events
-            # Create a unique channel relative to the subject's interface
-            if subjectInterface
-                channel = "#{subject.guid}:#{subjectInterface}"
-            else
-                channel = "#{subject.guid}:#{event}"
-
-            observer.channels.push channel
-
-            core.subscribe channel, (value) ->
-                if converter then value = converter(value)
-                observer.set observerInterface, value
-
-            handler = (event) ->
-                value = subject.get subjectInterface
-                # If subjectInterface is not undefined, cache the value
-                core.publish channel, value
-
-            onEvent(subject, event, handler)
-            if triggerOnBind then handler()
-
-        return
-
-
-    # Defines multiple connections between two objects via the
-    # interfaces specified. A few shorthand arguments that can be passed
-    # include:
-    #
-    # - ``subject``, ``observer``, ``converter`` - interfaces are detected
-    # with a converter. Useful for simple data transformations e.g. raw data →
-    # HTMLified
-    # - ``subject``, ``observer``, ``subjectInterface``, ``observerInterface`` -
-    # one or both interfaces are explicitly defined. The direction is always
-    # ``subject`` → ``observer``, thus the subject interface is specified
-    # first.
-    # - ``subject``, ``observer``, ``options`` - an ``options`` object can be
-    # passed specifying one or all of the connection options, including the
-    # event that will trigger the pipeline.
-    # - ``subject``, ``observer``, ...
-    connect = (subject, observer, args...) ->
-        options = args
-        arg0 = args[0]
-        arg1 = args[1]
-
-        if core.isFunction(arg0)
-            options = converter: arg0
-        else if core.isArray(arg0) or not core.isObject(arg0)
-            options =
-                subjectInterface: arg0
-                observerInterface: arg1
-
-        # the configuration is already defined as an object
-        if not core.isArray(options)
-            options = [options]
-
-        for opt in options
-            connectOne(subject, observer, opt)
-        return
-
-    return connect


fix dataTable

fix dataTable

--- BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
+++ BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
@@ -60,8 +60,16 @@
     if @options.page?
       page = parseInt(@options.page) - 1
       displayStart = pageLength * page
+
+    dom = "<'dt-toolbar'"
+    dom += "<'col-xs-12 col-sm-6'f>" if @options.displayGlobalSearch
+    numberColum = if @options.displayGlobalSearch then 5 else 11
+    dom += "<'col-sm-"+numberColum+" col-xs-6 hidden-xs'C><'col-xs-12 col-sm-1 hidden-xs'l>>"
+    dom += "t"
+    dom += "<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-sm-6 col-xs-12'p>>"
+
     @options.table = $('#tableviewCollectionTable').dataTable(
-      searching: @options.displayGlobalSearch
+      searching: true
       ordering: true
       serverSide: true
       displayStart: displayStart
@@ -70,9 +78,7 @@
       processing: true
       autowidth: false
       pagingType: "input_full"
-      dom: "<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-5 col-xs-6 hidden-xs'C><'col-xs-12 col-sm-1 hidden-xs'l>>"+
-                    "t"+
-                    "<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-sm-6 col-xs-12'p>>"
+      dom: dom
       language: {
         url: appRouter.generateUrl('loadTranslationDatatable')
       }


msg -> m

msg -> m

--- modules/player/commands.coffee
+++ modules/player/commands.coffee
@@ -15,7 +15,7 @@
         return m.reply 'You must be in a voice channel to request songs.'
       # Check voice channel name
       if d.data.voiceChannel isnt '*' and d.data.voiceChannel isnt vc.name and
-         not @permissions.isAdmin(msg.member)
+         not @permissions.isAdmin(m.member)
         return m.reply "You're not allowed to use the bot on the current voice channel."
       # Check item limit
       if @util.checkItemCountLimit(player, m.member)


ttest_power: a bit more precision in non-central t dist'n

--- ttest_power/distributions.coffee
+++ ttest_power/distributions.coffee
@@ -210,7 +210,7 @@
     quant

 # CDF of non-central t distribution
-pnct = (x, df, ncp, tol=1e-5) ->
+pnct = (x, df, ncp, tol=1e-14) ->
     x = parseFloat(x)
     df = parseFloat(df)
     ncp = parseFloat(ncp)
@@ -240,7 +240,7 @@
     prob

 # density of non-central t distribution
-dnct = (x, df, ncp, tol=1e-5) ->
+dnct = (x, df, ncp, tol=1e-14) ->
     x = parseFloat(x)
     df = parseFloat(df)
     ncp = parseFloat(ncp)


Sprite paths

Sprite paths

--- src/games/test-game.coffee
+++ src/games/test-game.coffee
@@ -19,19 +19,21 @@
 sentry = yu ['forward', 'forward', 'reverse', 'reverse']

 exit = (n) -> {
-  kind: 'gate'
-  name: 'yu'
+  kind: 'exit'
+  name: 'exit'
   p: [6,n]
   fill: "maroon"
-  programs: {turtle: ['victory']}
 }

 exports.game = {
   name: 'Test Game'
   kinds: {
-    # Assume drawin in a 10x10 box
-    turtle: {path: ""}
-    gate: {path: ""}
+    # paths center on 0, x,y = +-10
+    turtle: {paths: []}
+    exit: {
+      paths: ["M-10,0 l10,10 l10,-10 l-10,-10 Z"]
+      programs: {turtle: ['victory']}
+    }
   }
   levels: [
     {name: 'End of the Line', sprites: [me, exit(1)]}


this whitespace is unattractive, sir!

this whitespace is unattractive, sir!

--- src/jasmine-fixture.coffee
+++ src/jasmine-fixture.coffee
@@ -101,9 +101,6 @@

     $(($) -> init())
     afterEach -> tidyUp()
-
-
-

     $.jasmine


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -14,7 +14,9 @@

   obj.include ActiveBounds

-  obj.bind 'dest'
+  de
+
+  obj.bind 'destroy'

 test "should remove objects outside of activeBounds", 2, ->
   obj = GameObject


Supervisord: add numprocs_start default value

--- deployment/supervisord.coffee
+++ deployment/supervisord.coffee
@@ -28,6 +28,7 @@
     stderr_logfile          : "/var/log/koding/#{app}.log"
     stdout_logfile          : "/var/log/koding/#{app}.log"
     numprocs                : options.instances or 1
+    numprocs_start          : 0
     directory               : "/opt/koding"
     autostart               : yes
     autorestart             : yes


Test comments

Test comments

--- test/core/test-amd.coffee
+++ test/core/test-amd.coffee
@@ -1,7 +1,7 @@
 $(document).ready( ->
   module("knockback-defaults-amd.js")

-  # Knockback and depdenencies
+  # library and dependencies
   require(['underscore', 'backbone', 'knockout', 'knockback', 'knockback-inspector'], (_, Backbone, ko, kb, kbi) ->
     _ or= @_
     Backbone or= @Backbone


Started on UTILITY tests.

Started on UTILITY tests.

--- test/lib/utilities.coffee
+++ test/lib/utilities.coffee
@@ -1,5 +1,5 @@

-casper.test.begin "Utility Methods Testing", 1, (test) ->
+casper.test.begin "Utility Methods Testing", 4, (test) ->

 	casper.start()

@@ -11,8 +11,66 @@

 		# DEBOUNCE ------------------------------------------------------------------
 		# FLATTEN -------------------------------------------------------------------
+
+		# The FLATTEN function takes any array and returns a new array with all the
+		# elements flattened into one dimension.
+
+		.then ->
+
+			testArray = @.evaluate ->
+
+				junction("body").append("

")
+				junction("#flattenTest").append("

")
+				junction("#flattenTest").append("

")
+
+				testThing = junction("#flattenTest").children()
+				arr = junction.flatten(testThing)
+				return arr
+
+			test.assertEquals testArray.length, 2, ["FLATTEN is successful."]
+
+			return
+
+
 		# FLATTENOBJECT -------------------------------------------------------------
+
+		# The FLATTENOBJECT function works the same as FLATTEN except it accepts
+		# and object and turns it into a flattened array.
+
+		.then ->
+
+			testArray = @.evaluate ->
+
+				junction("body").append("

")
+				junction("#flattenObjectTest").append("

")
+				junction("#flattenObjectTest").append("

")
+
+				testObject = []
+				testThing = junction("#flattenObjectTest").children()
+				testThing.each ->
+					element = {}
+					element.id = @.id
+					testObject.push element
+
+				arr = junction.flattenObject(testObject)
+				return arr
+
+			test.assertEquals testArray.length, 2, ["FLATTENOBJECT is successful."]
+
+			return
+
+
 		# GETKEYS -------------------------------------------------------------------
+
+		# The GETKEYS function returns an array of keys that match a certain value.
+
+		.then ->
+
+			test.assertEquals 1, 1, ["Working on GETKEYS."]
+
+			return
+
+
 		# GETQUERYVARIABLE ----------------------------------------------------------
 		# ISELEMENT -----------------------------------------------------------------
 		# ISELEMENTINVIEW -----------------------------------------------------------


kontrol: auto-connect should be optional

--- src/kontrol/kontrol.coffee
+++ src/kontrol/kontrol.coffee
@@ -11,9 +11,10 @@

     @options = options

-    @options.autoReconnect ?= yes
+    @options.autoConnect    ?= yes
+    @options.autoReconnect  ?= yes

-    @authenticate()
+    @authenticate()  if @autoConnect

     @kite.on 'error', @emit.bind this, 'error'  # forward kite error events


allow leaf-list to auto-convert a single string set by using comma-delim

--- src/lang/extensions.coffee
+++ src/lang/extensions.coffee
@@ -590,6 +590,7 @@
         data = []
         data = expr.eval data, ctx for expr in @exprs
         return undefined
+      data = data.split(',') if typeof data is 'string'
       data = [ data ] unless data instanceof Array
       data = data.filter((x) -> x != undefined && x != null)
       data = Array.from(new Set(data))


made background music loop

made background music loop

--- src/lib/States/Play.coffee
+++ src/lib/States/Play.coffee
@@ -21,7 +21,7 @@
         @aliens = game.add.group()
         @shadows = game.add.group()
         @hud = game.add.group()
-        @music = game.add.audio("music")
+        @music = game.add.audio "music", 1, true
         @music.play()
         game.physics.startSystem Phaser.Physics.ARCADE
         @backdropPlayer.create 0, 0, "background"


Switch character measurement to TokenIterator

Instead of using TokenizedLine::tokens shim

--- src/lines-component.coffee
+++ src/lines-component.coffee
@@ -354,19 +354,22 @@
     iterator = null
     charIndex = 0

-    for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens
+    tokenIterator = TokenIterator.instance.reset(tokenizedLine)
+    while tokenIterator.next()
+      scopes = tokenIterator.getScopes()
+      text = tokenIterator.getText()
       charWidths = @presenter.getScopedCharacterWidths(scopes)

-      valueIndex = 0
-      while valueIndex < value.length
-        if hasPairedCharacter
-          char = value.substr(valueIndex, 2)
+      textIndex = 0
+      while textIndex < text.length
+        if tokenIterator.isPairedCharacter()
+          char = text
           charLength = 2
-          valueIndex += 2
+          textIndex += 2
         else
-          char = value[valueIndex]
+          char = text[textIndex]
           charLength = 1
-          valueIndex++
+          textIndex++

         continue if char is '\0'


Remove old call to Q("")

Remove old call to Q("")

--- src/liquid/template.coffee
+++ src/liquid/template.coffee
@@ -35,7 +35,7 @@
   #    liquid more with its host application
   #
   _render: (args...) ->
-    return Q("") unless @root?
+    return Promise.cast("") unless @root?

     context = if args[0] instanceof Liquid.Context
       args.shift()


fixing test_literals

fixing test_literals

--- test/test_literals.coffee
+++ test/test_literals.coffee
@@ -159,6 +159,8 @@

 ok obj.one is 'one'
 ok obj.two is 'three'
+
+a = b = undefined

 result = func
     b:1


~ fixed failing test

~ fixed failing test

--- test/unit/property.coffee
+++ test/unit/property.coffee
@@ -78,7 +78,7 @@

   propTypeErrorTests = [
     [ null, Type.STRING, 'propertyName must be a string; got null' ]
-    [ 'length', 0, 'propertyType must be one of [\'boolean\', \'number\', \'string\', \'object\', \'function\', \'undefined\']; got 0' ]
+    [ 'length', 0, 'propertyType must be a valid type; got 0' ]
     [ 'length', Type.STRING, 'arg.length must be a string; got 3' ]
     [ 'length', Type.UNDEFINED, 'arg.length must be undefined; got 3' ]
     [ 'length', Type.BOOLEAN, 'arg.length must be a boolean; got 3' ]


Added checkToken

Added checkToken

First attempt.

--- src/octonode/client.coffee
+++ src/octonode/client.coffee
@@ -191,6 +191,14 @@
       return callback(err) if err
       if s isnt 200 then callback(new HttpError('Client rate_limit error', s)) else callback null, b.rate.remaining, b.rate.limit

+  checkToken: (id, callback) ->
+    if typeof @token == 'string'
+      @get "/applications/#{id}tokens/#{@token}", (err, s, b) ->
+        return callback(err) if err
+        if s isnt 200 then callback(new HttpError("Client token checking error", s)) else callback null, true
+
+    else typeof @token == 'object' and @token.id
+      throw new Error("Cannot check token without providing it first!")
 # Export modules
 module.exports = (token, credentials...) ->
   new Client(token, credentials...)


Enforce lowercase in placeholders.

Enforce lowercase in placeholders.

--- src/project_creator.coffee
+++ src/project_creator.coffee
@@ -97,7 +97,7 @@
   collectVars = (path, next) ->
     fs.readFile path, (err, contents) ->
       return next err if err
-      if matches = contents.toString().match  /---([A-z]+(-[A-z]+)*?)---/g
+      if matches = contents.toString().match  /---([a-z]+(-[a-z]+)*)---/g
         for match in matches
           varName = match.substring 3,match.length-3
           varNames.push varName if varNames.indexOf varName is -1


[backport] fix issue #4585 - fix search in discover home page

--- app/modules/discover/components/discover-search-bar/discover-search-bar.directive.coffee
+++ app/modules/discover/components/discover-search-bar/discover-search-bar.directive.coffee
@@ -26,10 +26,13 @@
         templateUrl: 'discover/components/discover-search-bar/discover-search-bar.html',
         bindToController: true,
         scope: {
-            q: "="
+            q: "=",
             filter: "=",
             onChange: "&"
         },
+        compile: (element, attrs) ->
+            if !attrs.q
+                attrs.q = ''
         link: link
     }


Fix "don't dismiss pairing progress dialog when displaying success"

--- app/src/controllers/wallet/pairing/wallet_pairing_progress_dialog_view_controller.coffee
+++ app/src/controllers/wallet/pairing/wallet_pairing_progress_dialog_view_controller.coffee
@@ -9,11 +9,12 @@
     @_request = @params.request
     @_request?.onComplete (screen, error) =>
       @_request = null
-      if screen?
-        dialog = new CommonDialogsMessageDialogViewController(kind: "success", title: t("wallet.pairing.errors.pairing_succeeded"), subtitle: _.str.sprintf(t("wallet.pairing.errors.dongle_is_now_paired"), screen.name))
-      else
-        dialog = new CommonDialogsMessageDialogViewController(kind: "error", title: t("wallet.pairing.errors.pairing_failed"), subtitle: t("wallet.pairing.errors." + error))
-      dialog.show()
+      @dismiss () =>
+        if screen?
+          dialog = new CommonDialogsMessageDialogViewController(kind: "success", title: t("wallet.pairing.errors.pairing_succeeded"), subtitle: _.str.sprintf(t("wallet.pairing.errors.dongle_is_now_paired"), screen.name))
+        else
+          dialog = new CommonDialogsMessageDialogViewController(kind: "error", title: t("wallet.pairing.errors.pairing_failed"), subtitle: t("wallet.pairing.errors." + error))
+        dialog.show()
     @_request?.on 'finalizing', @_onFinalizing
     # show spinner
     @view.spinner = ledger.spinners.createLargeSpinner(@view.contentContainer[0])


Refactored: tests to compound

Refactored: tests to compound

--- test/template_tests.coffee
+++ test/template_tests.coffee
@@ -1,9 +1,10 @@
-should = require('should')
-Client = require('request-json').Client
-app = require('../server')
+should = require('chai').Should()
+Client = require('request-json').JsonClient

 client = new Client "http://localhost:8888/"

+instantiateApp = require '../server'
+app = instantiateApp()

 describe "Test section", ->

@@ -12,7 +13,7 @@
         done()

     after (done) ->
-        app.close()
+        app.compound.server.close()
         done()

     it "Then it succeeds", ->


fix icon name change

fix icon name change

--- js/inner/build.coffee
+++ js/inner/build.coffee
@@ -147,7 +147,7 @@
       "fa-times": @komp => @important_style() || @warning_style() || @canceled()
       "fa-clock-o": @komp => @queued()
       "fa-refresh": @komp => @info_style()
-      "fa-calendar-empty": @komp => @scheduled()
+      "fa-calendar-o": @komp => @scheduled()

     @status_words = @komp => switch @status()
       when "infrastructure_fail"


Track build retries in Mixpanel

Track build retries in Mixpanel

--- js/inner/build.coffee
+++ js/inner/build.coffee
@@ -306,14 +306,26 @@
     existing = (message.message for message in @messages())
     (@messages.push(msg) if msg.message not in existing) for msg in json

+  trackRetryBuild: (build, clearCache, SSH) =>
+    mixpanel.track("Trigger Build",
+      "vcs-url": build.project_name()
+      "build-num": build.build_num
+      "retry?": true
+      "clear-cache?": clearCache
+      "ssh?": SSH)
+
   # TODO: CSRF protection
-  retry_build: (data, event) =>
+  retry_build: (data, event, clearCache) =>
     $.ajax
       url: "/api/v1/project/#{@project_name()}/#{@build_num}/retry"
       type: "POST"
       event: event
       success: (data) =>
-        (new CI.inner.Build(data)).visit()
+        console.log("retry build data", data)
+        console.log("retry event", event)
+        build = new CI.inner.Build(data)
+        build.visit()
+        @trackRetryBuild build, clearCache, false
     false

   clear_cache_and_retry_build: (data, event) =>
@@ -322,7 +334,7 @@
       type: "DELETE"
       event: event
       success: (data) =>
-        @retry_build data, event
+        @retry_build data, event, true
     false

   ssh_build: (data, event) =>
@@ -331,7 +343,9 @@
       type: "POST"
       event: event
       success: (data) =>
-        (new CI.inner.Build(data)).visit()
+        build = new CI.inner.Build(data)
+        build.visit()
+        @trackRetryBuild build, false, true
     false

   cancel_build: (data, event) =>


Make error page follow a similar style to other pages

--- js/outer/error.coffee
+++ js/outer/error.coffee
@@ -19,7 +19,9 @@
     document.title = "Circle - " + title

     # Display page
-    $("body").attr("id","error").html HAML['header'](renderContext)
-    $("body").append HAML['error'](title: title, error: renderContext.status, message: message)
-    $('body > div').wrapAll('
');
-    $("body").append HAML['footer'](renderContext)
+    $('#IntercomTab').text ""
+    $("body").attr("id","error")
+    $('#main').html HAML['header'](renderContext)
+    $("#main").append HAML['error'](title: title, error: renderContext.status, message: message)
+    $('#main > div').wrapAll "
" # this is the only page on which we want a sticky footer
+    $("#main").append HAML['footer'](renderContext)


Fixes channel message rendering

Fixes channel message rendering

Signed-off-by: Akash Manohar J <e4cf979cf717fd295f4e4edc427ee1aee78fa7dc@akash.im>

--- app/assets/javascripts/backbone/views/show_activity.js.coffee
+++ app/assets/javascripts/backbone/views/show_activity.js.coffee
@@ -4,7 +4,7 @@
   className: 'activity'

   render: ()->
-    if @options.activity.get('message')
+    if @options.activity.get('action')=="message"
       @template = JST['activity']
     else
       @template = JST['user_notification']


Configure $http service for Rails

Configure $http service for Rails

--- app/assets/javascripts/hyper_admin/angularjs/config.js.coffee
+++ app/assets/javascripts/hyper_admin/angularjs/config.js.coffee
@@ -1 +1,6 @@
 angular.module("hyperadmin", [ "ui.router" ])
+  .config ($httpProvider) ->
+    authToken = $("meta[name=\"csrf-token\"]").attr("content")
+    $httpProvider.defaults.headers.common["X-CSRF-TOKEN"] = authToken
+    $httpProvider.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest"
+    $httpProvider.defaults.headers.common["Accept"] = "text/html,application/json"


append locale to attachment upload request

--- app/assets/javascripts/rubber_ring/attachment_uploader.coffee
+++ app/assets/javascripts/rubber_ring/attachment_uploader.coffee
@@ -14,6 +14,7 @@
       form_data.append(csrf_param, csrf_token)
       form_data.append('page_controller', App.controller)
       form_data.append('page_action', App.action)
+      form_data.append('locale', App.locale)

     dz.on "success", (file, response) ->
       if response.type == "image"


Fix bug when trying to edit a conference

Fixes: CORE-3035

This code was written poorly. It had a typo where it meant to use a
fat arrow but used a skinny arrow so on this line it thought it was
doing “this.$” where it thought “this” was the backbone view instance
but because they used a skinny arrow this.$ was actually globalThis.$,
which happened to work pre “strict mode” because this in a global
context would be window, and there happens to be a global variable “$”
available at window.$. but it breaks in strict mode, which is inferred
whenever a file is being interpreted as an ES module.

In strict mode if you do this.whatever in a global context, “this” will
be undefined.

Test plan:
* Create a conference.
* Attempt to edit the conference and notice that it doesn’t throw an
  error.

Change-Id: Ia04500c17d517718272d39984793bac7bc210567
Reviewed-on: https://gerrit.instructure.com/196487
Tested-by: Jenkins
Reviewed-by: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
QA-Review: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/views/conferences/EditConferenceView.coffee
+++ app/coffeescripts/views/conferences/EditConferenceView.coffee
@@ -204,7 +204,7 @@

   markInvitedUsers: ->
     _.each(@model.get('user_ids'), (id) ->
-      el = @$("#members_list .member.user_" + id).find(":checkbox")
+      el = $("#members_list .member.user_" + id).find(":checkbox")
       el.attr('checked', true)
       el.attr('disabled', true)
     )


Add relations labels

Add relations labels

--- app/frontend/javascripts/views/visualization-canvas.js.coffee
+++ app/frontend/javascripts/views/visualization-canvas.js.coffee
@@ -125,9 +125,12 @@
     @context.clearRect 0, 0, @viewport.width, @viewport.height

     @drawRelations()
-
+
+    if @node_active or @node_hovered
+      @drawRelationsLabels()
+
     @drawNodes()
-
+
     if @parameters.showNodesLabel or @node_hovered
       @drawNodesLabels()

@@ -159,17 +162,17 @@

   drawNodesLabels: ->
     # set styles
-    @context.lineWidth = 1
-    @context.strokeStyle = 'rgba(255,255,255,0.85)'
-    @context.textAlign = 'center'
+    @context.lineWidth    = 1
+    @context.strokeStyle  = 'rgba(255,255,255,0.85)'
+    @context.textAlign    = 'center'
     @context.textBaseline = 'top'
-    @data_nodes.forEach (d) =>
-      # don't draw labels of weaken nodes
-      if d.state > -1
-        @context.fillStyle = d.fontColor
-        @context.font      = '300 '+d.fontSize+'px Montserrat'
-        @context.strokeText  d.name, d.x, d.y+d.size+1
-        @context.fillText  d.name, d.x, d.y+d.size
+    @data_nodes
+      .filter (d) -> d.state != -1 # don't draw labels of weaken nodes
+      .forEach (d) =>
+        @context.fillStyle  = d.fontColor
+        @context.font       = '300 '+d.fontSize+'px Montserrat'
+        @context.strokeText d.name, d.x, d.y+d.size+1
+        @context.fillText   d.name, d.x, d.y+d.size

   drawRelations: ->
     # make stroke color dynamic based on nodes state !!!
@@ -182,11 +185,22 @@
       @context.stroke()
       @context.closePath()

-  updateNodesColorValue: =>
-    super()
-    @data_nodes.forEach (d) =>
-      @setNodeFill d
-      @setNodeStroke d
+  drawRelationsLabels: ->
+    @context.textAlign    = 'center'
+    @context.textBaseline = 'bottom'
+    @context.fillStyle    = '#aaaaaa'
+    @context.font         = '300 9px Montserrat'
+    @data_relations_visibles
+      .filter (d) -> d.state == 1 and d.relation_type # only draw labels of highlighted relations
+      .forEach (d) =>
+        angle = Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x)
+        if angle > Math.PI/2 or angle < -Math.PI/2
+          angle += Math.PI
+        @context.save()
+        @context.translate (d.source.x+d.target.x)*0.5, (d.source.y+d.target.y)*0.5
+        @context.rotate angle
+        @context.fillText d.relation_type, 0, 0
+        @context.restore()

   # Canvas Mouse Events
@@ -225,6 +239,8 @@
     @data_relations_visibles.forEach (d) =>
       @setRelationState d
       @setRelationColor d
+    # sort relations to put highlighted on top
+    # that's make some weird effects in force layout during animation
     @data_relations_visibles.sort @sortRelations
     # update canvas if force layout stoped
     if @force.alpha() < @force.alphaMin()


Add more cores

Add more cores

--- app/assets/javascripts/angular/controllers/configuration_controller.js.coffee
+++ app/assets/javascripts/angular/controllers/configuration_controller.js.coffee
@@ -21,7 +21,7 @@
       { box: 'ubuntu/trusty32', name: 'Ubuntu Trusty 14.04 LTS 32' },
       { box: 'ubuntu/trusty64', name: 'Ubuntu Trusty 14.04 LTS 64' }
     ]
-    @$.coresList = [ '1', '2', '3', '4' ]
+    @$.coresList = [ '1', '2', '3', '4', '5', '6', '7', '8' ]
     @$.rubyInstalls =
       rvm:
         label: 'RVM'


Clean up

Clean up

--- app/assets/javascripts/components/moving_records/moving_records_app.js.coffee
+++ app/assets/javascripts/components/moving_records/moving_records_app.js.coffee
@@ -1,18 +1,12 @@
-# required props
-# @props.id
-# @props.data
-
 # Canvases for charts
 PieChartCanvas = React.createClass
   render: ->
     React.DOM.canvas
-      id:    @props.id
       style: { height: 200, width: 200 }

 BarChartCanvas = React.createClass
   render: ->
     React.DOM.canvas
-      id:    @props.id
       style: { height: 200, width: 400 }

 # Remember Chart.js instances so we can delete them later.
@@ -28,6 +22,92 @@

   getDefaultProps: ->
     records: []
+
+  addRecord: (record) ->
+    records = React.addons.update(@state.records, { $unshift: [record] })
+    @setState records: records
+
+  deleteRecord: (record) ->
+    index = @state.records.indexOf record
+    records = React.addons.update(@state.records, { $splice: [[index, 1]] })
+    @replaceState records: records
+
+  # Relpace a record with the new one.
+  updateRecord: (record, newRecord) ->
+    index = @state.records.indexOf record
+    records = React.addons.update(@state.records, { $splice: [[index, 1, newRecord]] })
+    @replaceState records: records
+
+  noticeProcessingAjax: ->
+    R.div
+      className: "alert alert-warning"
+      R.i
+        className: "fa fa-cog fa-spin fa-3x"
+      R.div null,
+        "Processing... If this is taking long, please make sure you are online."
+
+  chartsPanel: ->
+    R.div
+      className: "panel panel-blue"
+      R.div
+        className: 'panel-heading'
+        R.div
+          className: "row"
+          R.div
+            className: "col-xs-3"
+            R.div
+              className: "fa fa-home fa-5x"
+          R.div
+            className: "col-xs-9 text-right"
+            R.div
+              className: 'huge'
+              "Total: #{@totalVolume()}"
+            R.div null,
+              "cubic feet"
+      R.div
+        className: 'panel-body'
+        R.div
+          className: 'row text-center'
+          R.div
+            className: 'col-sm-6'
+            React.createElement BarChartCanvas,
+              ref: "bar"
+          R.div
+            className: 'col-sm-6'
+            React.createElement PieChartCanvas,
+              ref: "chart"
+
+  totalVolume: ->
+    sum = 0
+    for obj in @state.records
+      sum += obj.volume
+    sum
+
+  render: ->
+    R.div
+      className: "app_wrapper"
+      @noticeProcessingAjax() if @state.ajax
+
+      R.h2 null, "Moving volume overview"
+      R.div
+        className: 'row'
+        R.div
+          className: 'col-sm-12'
+          @chartsPanel()
+
+      R.hr null
+
+      R.h2 null, "Add a new item"
+      React.createElement NewMovingRecordForm,
+        handleNewRecord: @addRecord
+        roomSuggestions: @props.roomSuggestions
+        categorySuggestions: @props.categorySuggestions
+      R.hr null
+
+      React.createElement Records,
+        records: @state.records,
+        handleDeleteRecord: @deleteRecord,
+        handleUpdateRecord: @updateRecord

   # Draw the charts after the component has been rendered.
   componentDidMount: ->
@@ -51,21 +131,6 @@
     canvas = React.findDOMNode(@refs.chart)
     ctx    = canvas.getContext("2d")
     chartInstances["pieChart"] = new Chart( ctx ).Pie(@dataForPieChart())
-
-  addRecord: (record) ->
-    records = React.addons.update(@state.records, { $unshift: [record] })
-    @setState records: records
-
-  deleteRecord: (record) ->
-    index = @state.records.indexOf record
-    records = React.addons.update(@state.records, { $splice: [[index, 1]] })
-    @replaceState records: records
-
-  # Relpace a record with the new one.
-  updateRecord: (record, newRecord) ->
-    index = @state.records.indexOf record
-    records = React.addons.update(@state.records, { $splice: [[index, 1, newRecord]] })
-    @replaceState records: records

   shuffleArray: (o) ->
     i = o.length
@@ -122,12 +187,6 @@
         return 1
       0

-  totalVolume: ->
-    sum = 0
-    for obj in @state.records
-      sum += obj.volume
-    sum
-
   # prop: "room" or "category"
   volumeSortedBy: (prop) ->
     # 1. Filtering
@@ -147,68 +206,3 @@
       ary.push data
     # 3. Sorting
     ary.sort( @predicateBy("volume") )
-
-  noticeProcessingAjax: ->
-    R.div
-      className: "alert alert-warning"
-      R.i
-        className: "fa fa-cog fa-spin fa-3x"
-      R.div null,
-        "Processing... If this is taking long, please make sure you are online."
-
-  chartsPanel: ->
-    R.div
-      className: "panel panel-blue"
-      R.div
-        className: 'panel-heading'
-        R.div
-          className: "row"
-          R.div
-            className: "col-xs-3"
-            R.div
-              className: "fa fa-home fa-5x"
-          R.div
-            className: "col-xs-9 text-right"
-            R.div
-              className: 'huge'
-              "Total: #{@totalVolume()}"
-            R.div null,
-              "cubic feet"
-      R.div
-        className: 'panel-body'
-        R.div
-          className: 'row text-center'
-          R.div
-            className: 'col-sm-6'
-            React.createElement BarChartCanvas,
-              ref: "bar"
-          R.div
-            className: 'col-sm-6'
-            React.createElement PieChartCanvas,
-              ref: "chart"
-
-  render: ->
-    R.div
-      className: "app_wrapper"
-      @noticeProcessingAjax() if @state.ajax
-
-      R.h2 null, "Moving volume overview"
-      R.div
-        className: 'row'
-        R.div
-          className: 'col-sm-12'
-          @chartsPanel()
-
-      R.hr null
-
-      R.h2 null, "Add a new item"
-      React.createElement NewMovingRecordForm,
-        handleNewRecord: @addRecord
-        roomSuggestions: @props.roomSuggestions
-        categorySuggestions: @props.categorySuggestions
-      R.hr null
-
-      React.createElement Records,
-        records: @state.records,
-        handleDeleteRecord: @deleteRecord,
-        handleUpdateRecord: @updateRecord


[fix] Inserted bands in a consistent order (2m)

--- app/assets/javascripts/houston/roadmaps/views/_gantt_chart.coffee
+++ app/assets/javascripts/houston/roadmaps/views/_gantt_chart.coffee
@@ -92,7 +92,7 @@
       .data(@groupMilestonesIntoBands(), (band)-> band.key)

     bands.enter()
-      .append('div')
+      .insert('div')
         .attr('class', (band)-> "roadmap-band")
         .attr('style', (date)=> "height: #{@bandHeight}px; margin: #{@bandMargin}px 0;")
         .attr('data-band', (band)-> band.number)


use gravatar tooltip

use gravatar tooltip

--- client/landing/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
+++ client/landing/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
@@ -97,15 +97,15 @@
       {callback, inputValue, blacklist} = event
       @fetchAutoCompleteDataForTags inputValue, blacklist, callback

-    @avatar        = new AvatarStaticView
+    avatarOptions  =
       size         :
         width      : 90
         height     : 90
       click        : =>
-        pos =
+        pos        =
           top      : @avatar.getBounds().y - 8
           left     : @avatar.getBounds().x - 8
-        modal = new KDModalView
+        modal      = new KDModalView
           width    : 400
           fx       : yes
           overlay  : yes
@@ -116,7 +116,13 @@
             width  : 400
             height : 400
         , @memberData
-    , @memberData
+
+    if KD.whoami().getId() is @memberData.getId()
+      avatarOptions.tooltip =
+        title    : "
please use gravatar.com
to set your avatar
"
+        placement: "below"
+
+    @avatar = new AvatarStaticView avatarOptions, @memberData

     userDomain = @memberData.profile.nickname + "." + KD.config.userSitesDomain
     @userHomeLink = new KDCustomHTMLView


Deprecated BST.

Deprecated BST.

--- BST.coffee
+++ BST.coffee
@@ -1,146 +0,0 @@
-# TODO: consider if remove and peek should return empty node instead of undefined.
-
-# Binary search tree.
-Node = require './Node'
-
-NODE_FOUND = 0
-NODE_TOO_BIG = -1
-NODE_TOO_SMALL = 1
-
-class treeNode extends Node
-	constructor: (value) ->
-		super(value)
-		@parent
-		@leftChild
-		@rightChild
-
-class BST
-	constructor: ->
-		@root
-
-	add: (value) ->
-		nodeToInsert = new treeNode(value)
-		if not @root
-			@root = nodeToInsert
-		else
-			_findNode @root, (node) ->
-				if value is node.value then NODE_FOUND
-				else
-					if value < node.value
-						if node.leftChild then NODE_TOO_BIG
-						else
-							nodeToInsert.parent = node
-							node.leftChild = nodeToInsert
-							NODE_FOUND
-					# Inserting "undefined" will go to right child. Important to keep
-					# this conditional in sync with find().
-					else
-						if node.rightChild then NODE_TOO_SMALL
-						else
-							nodeToInsert.parent = node
-							node.rightChild = nodeToInsert
-							NODE_FOUND
-		return value
-
-	find: (value) ->
-		foundNode = _findNode @root, (node) ->
-			if value is node.value then NODE_FOUND
-			# Keep the conditional this way; node.value > value wouldn't work.
-			# The insertion uses the same comparison to add "undefined" (to the right child).
-			else if value < node.value then NODE_TOO_BIG
-			else NODE_TOO_SMALL
-
-	peekMinimum: ->
-		_peekMinimumNode(@root)?.value
-
-	peekMaximum: ->
-		_peekMaximumNode(@root)?.value
-
-	remove: (value) ->
-		nodeToRemove = @find(value)
-		if not nodeToRemove then return
-		@_removeNode(@root, nodeToRemove)
-		return value
-
-	removeMinimum: ->
-		nodeToRemove = _peekMinimumNode(@root)
-		if not nodeToRemove then return
-		# Store in. Might destroy the node during removal in the future, so can't just return nodeToRemove.value.
-		valueToReturn = nodeToRemove.value
-		@_removeNode(@root, nodeToRemove)
-		return valueToReturn
-
-	removeMaximum: ->
-		nodeToRemove = _peekMaximumNode(@root)
-		if not nodeToRemove then return
-		# Store in. Might destroy the node during removal in the future, so can't just return nodeToRemove.value.
-		valueToReturn = nodeToRemove.value
-		@_removeNode(@root, nodeToRemove)
-		return valueToReturn
-
-	_removeNode: (root, node) ->
-		# Leaf.
-		if not node.leftChild and not node.rightChild
-			if node is @root then @root = undefined
-			else
-				node.parent[_leftOrRightChild(node)] = undefined
-				node.parent = undefined
-		# Internal node with two children.
-		else if node.leftChild and node.rightChild
-			# In-order successor. Left-most child of right subtree.
-			# Or in-order precessor. Right-most child of left subtree.
-			successor = _peekMinimumNode(node.rightChild) or _peekMaximumNode(node.leftChild)
-			# Not really swapping, but same result.
-			node.value = successor.value
-			# Remove successor and join its parent and its child. Only one or zero.
-			successor.leftChild?.parent = successor.parent
-			successor.rightChild?.parent = successor.parent
-			successor.parent?[_leftOrRightChild(successor)] = successor.leftChild or successor.rightChild
-			# successor is now detached
-		# Internal node with one child.
-		else
-			successor = node.leftChild or node.rightChild
-			# Not really swapping, but same result.
-			node.value = successor.value
-			# Remove node and join its parent and its children.
-			successor.leftChild?.parent = node
-			successor.rightChild?.parent = node
-
-			node.leftChild = successor.leftChild
-			node.rightChild = successor.rightChild
-			# successor is now detached
-
-			# Alt version:
-			# successor = node.leftChild or node.rightChild
-			# if node is @root then @root = successor
-			# successor.parent = node.parent
-			# node.parent?[_leftOrRightChild node] = successor
-
-_leftOrRightChild = (node) ->
-	# No need to check if parent exist. It's never used this way.
-	if node is node.parent.leftChild then "leftChild" else "rightChild"
-
-_findNode = (startingNode, comparator) ->
-	currentNode = startingNode
-	foundNode = undefined
-	while currentNode
-		comparisonResult = comparator(currentNode)
-		if comparisonResult is NODE_FOUND
-			foundNode = currentNode
-			break
-		if comparisonResult is NODE_TOO_BIG
-			currentNode = currentNode.leftChild
-		else
-			currentNode = currentNode.rightChild
-	return foundNode
-
-_peekMinimumNode = (startingNode) ->
-	_findNode startingNode, (node) ->
-		if node.leftChild then NODE_TOO_BIG else NODE_FOUND
-
-_peekMaximumNode = (startingNode) ->
-	_findNode startingNode, (node) ->
-		if node.rightChild then NODE_TOO_SMALL else NODE_FOUND
-
-module.exports = BST
-


add map_limit option to copy_all_blobs_to_gcloud

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -2693,7 +2693,8 @@
     copy_all_blobs_to_gcloud: (opts) =>
         opts = defaults opts,
             bucket    : BLOB_GCLOUD_BUCKET # name of bucket
-            limit     : undefined          # only copy this many to gcloud
+            limit     : 1000               # copy this many in each batch
+            map_limit : 1                  # copy this many at once.
             repeat_until_done_s : 0        # if nonzero, waits this many seconds, then recalls this function until nothing gets uploaded.
             errors    : {}                 # used to accumulate errors
             remove    : false
@@ -2729,7 +2730,7 @@
                             if err
                                 opts.errors[x.id] = err
                             cb()
-                async.mapSeries v, f, () =>
+                async.mapLimit v, opts.map_limit, f, () =>
                     dbg("finished this round")
                     if opts.repeat_until_done_s and v.length > 0
                         dbg("repeat_until_done triggering another round")


backend part of https://github.com/sagemathinc/cocalc/issues/2426  (session + support)

--- src/smc-hub/support.coffee
+++ src/smc-hub/support.coffee
@@ -33,8 +33,8 @@
     return (process.env.SMC_ROOT ? '.') + '/data/secrets/zendesk'

 fixSessions = (body) ->
-    # takes the body of the ticket, searches for http[s]://<theme.DNS>/ URLs and either replaces ?session=* by ?session=support or adds it
-    body = body.replace(/\?session=([^\s]*)/g, '?session=support')
+    # takes the body of the ticket, searches for http[s]://<theme.DNS>/ URLs and either replaces ?session=* by ?session= or adds it
+    body = body.replace(/\?session=([^\s]*)/g, '?session=')

     urlPattern = new RegExp("(http[s]?://[^\\s]*#{theme.DNS}[^\\s]+)", "g")
     reSession = /session=([^\s]*)/g
@@ -56,9 +56,9 @@
             url = url[0...q]
         q = url.indexOf('?')
         if q >= 0
-            url += '&session=support'
+            url += '&session='
         else
-            url += '?session=support'
+            url += '?session='
         ret += url
         offset = j
     ret += body[offset...body.length]
@@ -252,12 +252,12 @@
         # TODO fix hardcoded URL
         if opts.location?
             url  = "https://" + path.join(theme.DNS, opts.location)
-            body = body + "\n\n#{url}?session=support"
+            body = body + "\n\n#{url}?session="
         else
             body = body + "\n\nNo location provided."

         if misc.is_valid_uuid_string(opts.info.course)
-            body += "\n\nCourse: #{theme.DOMAIN_NAME}/projects/#{opts.info.course}?session=support"
+            body += "\n\nCourse: #{theme.DOMAIN_NAME}/projects/#{opts.info.course}?session="

         # https://developer.zendesk.com/rest_api/docs/core/tickets#request-parameters
         ticket =


Use '>> 0' to make integers

Use '>> 0' to make integers

--- app/assets/javascripts/app/app.js.coffee
+++ app/assets/javascripts/app/app.js.coffee
@@ -48,7 +48,7 @@
       minutes = Math.abs(seconds) / 60
       hours = (minutes / 60) >> 0
       minutes %= 60
-      minutes = Math.round(minutes);
+      minutes >>= 0;
       if seconds < 0 then "-#{hours}:#{minutes}" else "#{hours}:#{minutes}"
     else
       "—"


Double bang

Double bang

--- app/assets/javascripts/buckets.js.coffee
+++ app/assets/javascripts/buckets.js.coffee
@@ -110,7 +110,7 @@
   else if pusher_config.length > 0
     window.channels[channel_name].bind "update", (data) ->
       refreshOnUpdateByOtherUsers(data)
-  else if window.channels[channel_name]
+  else if !!window.channels[channel_name]
     window.channels[channel_name].unbind()

 refreshOnUpdateByOtherUsers = (data) ->


Add selection code to schemes.js.coffee

Add selection code to schemes.js.coffee

--- app/assets/javascripts/schemes.js.coffee
+++ app/assets/javascripts/schemes.js.coffee
@@ -1,3 +1,32 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
+SECTIONS =
+  '#code': 'Background'
+  '.c':  'Comment'
+  '.cl': 'Cursor line'
+  '.co': 'Constant'
+  '.cu': 'Cursor'
+  '.id': 'Identifier'
+  '.ln': 'Line number'
+  '.pp': 'PreProc'
+  '.ra': 'rubyArrayDelimiter'
+  '.rb': 'rubyBlock'
+  '.rd': 'rubyDefine'
+  '.rm': 'rubyMethodBlock'
+  '.sp': 'Special'
+  '.st': 'Statement'
+  '.t':  'Type'
+
+jQuery ->
+
+  for klass, section of SECTIONS
+    do (klass, section) ->
+      $(klass).click (event) ->
+        event.stopPropagation()
+        $('#section_name').text(section)
+
+Raphael ->
+
+  cp = $('#colorpicker')
+  offset = cp.offset()
+
+  Raphael.colorwheel(offset.left, offset.top, 320, '#ccc', cp.get(0))
+


Validation for required text inputs doesn't allow only spaces or returns value

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -61,7 +61,7 @@
       return true
     else
       if @isTextishQuestion(question)
-        return question.find("input[type='text'], input[type='number'], input[type='password'], input[type='email'], input[type='url'], textarea").val().toString().length
+        return question.find("input[type='text'], input[type='number'], input[type='password'], input[type='email'], input[type='url'], textarea").val().toString().trim().length

       if @isSelectQuestion(question)
         return question.find("select").val()


added support for ice

added support for ice

--- app/assets/javascripts/mixins/order_items_mixin.js.coffee
+++ app/assets/javascripts/mixins/order_items_mixin.js.coffee
@@ -51,3 +51,13 @@
   totalPrice: (->
     @get("orderItems.content").reduce $.proxy(@totalPriceReduceCallback, @), 0
   ).property("orderItems.@each.item.base", "orderItems.@each.cantidad")
+
+  iceReduceCallback: (prev, orderItem) ->
+    ice = if @get("isVenta")
+      orderItem.get("item.iceVenta")
+    else
+      orderItem.get("item.iceCompra")
+    prev + orderItem.get("cantidad") * ice
+  ice: (->
+    @get("orderItems.content").reduce $.proxy(@iceReduceCallback, @), 0
+  ).property("orderItems.@each.item.base", "orderItems.@each.cantidad")


display in a flash how many kudos you gave

--- app/assets/javascripts/routes/application_route.js.coffee
+++ app/assets/javascripts/routes/application_route.js.coffee
@@ -28,7 +28,7 @@
           currentUserCon.decrementKudos kudoNum
           newKudo = Sks.KudoReceived.createRecord value: kudoNum, comment: kudoComment
           user.get('kudoReceiveds').pushObject(newKudo)
-          showFlash 'alert-success', 'You\'ve added a kudo!'
+          showFlash 'alert-success', "You've added #{kudoNum} kudo(s)!"
         else
           showFlash 'alert-error', 'Oops! An error occured!'
       )


add album

add album

--- app/frontend/scripts/templates.js.coffee
+++ app/frontend/scripts/templates.js.coffee
@@ -65,6 +65,10 @@
     remove from queue
   {{/queued}}

+  {{#queued}}
+    remove from queue
+  {{/queued}}
+
   {{^queued}}
     add to queue
   {{/queued}}


Added a small comment

Added a small comment

--- test/browserchannel.coffee
+++ test/browserchannel.coffee
@@ -427,9 +427,10 @@
     socket.send {auth:{a:1234}}

   'Socket timeout if no auth message is sent': (test) ->
+    # This test should be rewritten to use timerstub.
     socket = new BCSocket "http://localhost:#{@server.address().port}/channel"
     socket.onmessage = (data) ->
       test.strictEqual data.auth, null
       test.strictEqual data.error, 'Timeout waiting for client auth message'
     socket.onclose = () ->
-      test.done()+      test.done()