Restructure compilation to compile loader only once, then copy that directly to dist.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -27,7 +27,7 @@
           'build/js/jail_iframe/initializers/*.js'
           'build/js/jail_iframe/wrap/last.js'
         ]
-        dest: 'build/jail_iframe.js'
+        dest: 'build/js/jail_iframe.js'
     sass:
       build:
         options:
@@ -53,33 +53,32 @@
         preserveComments: (node, comment) -> !comment.value.lastIndexOf('@license', 0)
       jail_iframe:
         files:
-          'build/jail_iframe.min.js':                        ['build/jail_iframe.js']
+          'build/js/jail_iframe.min.js':                        ['build/js/jail_iframe.js']
       factlink_loader:
         files:
           'build/js/loader/loader_common.min.js':       ['build/js/loader/loader_common.js']
     shell:
       gzip_js_files:
-        command: ' find build/ -iname \'*.js\'  -maxdepth 1  -exec bash -c \' gzip -9 -f < "{}" > "{}.gz" \' \\; '
+        command: ' find build/js/loader/ -iname \'*.js\'  -maxdepth 1  -exec bash -c \' gzip -9 -f < "{}" > "{}.gz" \' \\; '

     copy:
-      loader_aliases:
+      dist_loader_aliases:
         files: [
-          { src: 'js/loader/loader_common.js', cwd: 'build/', dest: 'factlink_loader_basic.js' }
-          { src: 'js/loader/loader_common.js', cwd: 'build/', dest: 'factlink_loader_publishers.js' }
-          { src: 'js/loader/loader_common.js', cwd: 'build/', dest: 'factlink_loader_bookmarklet.js' }
+          { src: 'build/js/loader/loader_common.js', dest: 'output/dist/factlink_loader_basic.js' }
+          { src: 'build/js/loader/loader_common.js', dest: 'output/dist/factlink_loader_publishers.js' }
+          { src: 'build/js/loader/loader_common.js', dest: 'output/dist/factlink_loader_bookmarklet.js' }

-          { src: 'js/loader/loader_common.min.js', cwd: 'build/', dest: 'factlink_loader_basic.min.js' }
-          { src: 'js/loader/loader_common.min.js', cwd: 'build/', dest: 'factlink_loader_publishers.min.js' }
-          { src: 'js/loader/loader_common.min.js', cwd: 'build/', dest: 'factlink_loader_bookmarklet.min.js' }
+          { src: 'build/js/loader/loader_common.min.js', dest: 'output/dist/factlink_loader_basic.min.js' }
+          { src: 'build/js/loader/loader_common.min.js', dest: 'output/dist/factlink_loader_publishers.min.js' }
+          { src: 'build/js/loader/loader_common.min.js', dest: 'output/dist/factlink_loader_bookmarklet.min.js' }

-          { src: 'js/loader/loader_common.js.gz', cwd: 'build/', dest: 'factlink_loader_basic.js.gz' }
-          { src: 'js/loader/loader_common.js.gz', cwd: 'build/', dest: 'factlink_loader_publishers.js.gz' }
-          { src: 'js/loader/loader_common.js.gz', cwd: 'build/', dest: 'factlink_loader_bookmarklet.js.gz' }
+          { src: 'build/js/loader/loader_common.js.gz', dest: 'output/dist/factlink_loader_basic.js.gz' }
+          { src: 'build/js/loader/loader_common.js.gz', dest: 'output/dist/factlink_loader_publishers.js.gz' }
+          { src: 'build/js/loader/loader_common.js.gz', dest: 'output/dist/factlink_loader_bookmarklet.js.gz' }

-          { src: 'js/loader/loader_common.min.js.gz', cwd: 'build/', dest: 'factlink_loader_basic.min.js.gz' }
-          { src: 'js/loader/loader_common.min.js.gz', cwd: 'build/', dest: 'factlink_loader_publishers.min.js.gz' }
-          { src: 'js/loader/loader_common.min.js.gz', cwd: 'build/', dest: 'factlink_loader_bookmarklet.min.js.gz' }
-
+          { src: 'build/js/loader/loader_common.min.js.gz', dest: 'output/dist/factlink_loader_basic.min.js.gz' }
+          { src: 'build/js/loader/loader_common.min.js.gz', dest: 'output/dist/factlink_loader_publishers.min.js.gz' }
+          { src: 'build/js/loader/loader_common.min.js.gz', dest: 'output/dist/factlink_loader_bookmarklet.min.js.gz' }
         ]

       config_development:
@@ -99,13 +98,9 @@
         files: [
           { src: ['**/*.js', '**/*.png', '**/*.gif', '**/*.woff', 'robots.txt'], cwd: 'app', dest: 'build', expand: true }
         ]
-      extension_events:
+      dist_static_content:
         files: [
-          { src: ['factlink.*.js'], cwd: 'build/js/extension_events', dest: 'build', expand: true }
-        ]
-      dist:
-        files: [
-          { src: ['*.js', '*.js.gz', 'robots.txt', 'images/**/*'], cwd: 'build', dest: 'output/dist', expand: true }
+          { src: ['robots.txt', 'images/**/*'], cwd: 'build', dest: 'output/dist', expand: true }
         ]
     watch:
       files: ['app/**/*', 'Gruntfile.coffee']
@@ -131,30 +126,26 @@
       }
       {
         placeholder: '__INLINE_JS_PLACEHOLDER__'
-        content_file: 'build/jail_iframe.js'
+        content_file: 'build/js/jail_iframe.js'
       }
     ]
-    targets = [
-      'build/factlink_loader_basic.js'
-      'build/factlink_loader_bookmarklet.js'
-      'build/factlink_loader_publishers.js'
-    ]
     file_variant_funcs.forEach (file_variant_func) ->
-        replacements.forEach (replacement) ->
-          input_filename = file_variant_func(replacement.content_file)
-          input_content = grunt.file.read(input_filename, 'utf8')
-          input_content_stringified = JSON.stringify(input_content)
-          targets.map(file_variant_func).forEach (target_filename) ->
-            grunt.log.writeln "Inlining '#{input_filename}' into '#{target_filename}' where  '#{replacement.placeholder}'."
-            target_content = grunt.file.read target_filename, 'utf8'
-            target_with_inlined_content = target_content.replace replacement.placeholder, input_content_stringified
-            grunt.file.write(target_filename, target_with_inlined_content)
+      replacements.forEach (replacement) ->
+        input_filename = file_variant_func(replacement.content_file)
+        input_content = grunt.file.read(input_filename, 'utf8')
+        input_content_stringified = JSON.stringify(input_content)
+        target_filename = file_variant_func 'build/js/loader/loader_common.js'

-  grunt.registerTask 'preprocessor',  [
-    'clean', 'copy:build', 'copy:extension_events', 'coffee', 'sass', 'cssUrlEmbed', 'cssmin', ]
+        grunt.log.writeln "Inlining '#{input_filename}' into '#{target_filename}' where  '#{replacement.placeholder}'."
+        target_content = grunt.file.read target_filename, 'utf8'
+        target_with_inlined_content = target_content.replace replacement.placeholder, input_content_stringified
+        grunt.file.write(target_filename, target_with_inlined_content)
+
+  grunt.registerTask 'preprocessor', [
+    'clean', 'copy:build', 'coffee', 'sass', 'cssUrlEmbed', 'cssmin', ]

   grunt.registerTask 'postprocessor', [
-    'concat', 'mocha', 'uglify', 'code_inliner', 'shell:gzip_js_files', 'copy:dist' ]
+    'concat', 'mocha', 'uglify', 'code_inliner', 'shell:gzip_js_files', 'copy:dist_loader_aliases', 'copy:dist_static_content' ]

   grunt.registerTask 'compile_develop',   [ 'preprocessor', 'copy:config_development', 'postprocessor' ]
   grunt.registerTask 'compile_staging',   [ 'preprocessor', 'copy:config_staging',     'postprocessor' ]


fixed typo

fixed typo

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -78,7 +78,7 @@
         ]
         tasks: [ 'exec:jekyll' ]
     connect: server: options:
-      port: 4080
+      port: 4040
       base: '_site'
       livereload: false
   grunt.registerTask 'build', [


Added single run tests to build

Added single run tests to build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -97,7 +97,11 @@
 		'karma:unit'
 	]

-	grunt.registerTask 'build', ['karma:travis', 'coffee:build', 'concat', 'jshint']
+	grunt.registerTask 'build', [
+		'karma:travis',
+		'coffee:build',
+		'concat',
+		'jshint']

 	grunt.registerTask 'travis', [
 		'karma:travis'


Force english language during qunit tests

Signed-off-by: Maxime Gaillard <1acc295174379ec718e1123290d06dcd8d68feb6@naeka.fr>

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -286,6 +286,11 @@
       all:
         options:
           urls: ['http://localhost:8001/spec/']
+          page: {
+            customHeaders: {
+              'Accept-Language': 'en'
+            }
+          },

   #
   # Events


Fix glob patterns in watch tasks.

Fix glob patterns in watch tasks.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -30,10 +30,10 @@
     # Automated recompilation and testing when developing
     watch:
       build:
-        files: ['spec/*.coffee', 'grammar/*.peg']
+        files: ['spec/**/*.coffee', 'src/**/*.{coffee,peg}']
         tasks: ['build']
       test:
-        files: ['spec/*.coffee', 'grammar/*.peg']
+        files: ['spec/**/*.coffee', 'src/**/*.{coffee,peg}']
         tasks: ['test']

     # BDD tests on Node.js


Run entire lint task as part of default task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -32,6 +32,6 @@

   grunt.registerTask 'clean', -> require('rimraf').sync('lib')
   grunt.registerTask('lint', ['coffeelint:src', 'coffeelint:test'])
-  grunt.registerTask('default', ['coffeelint:src', 'coffee'])
+  grunt.registerTask('default', ['lint', 'coffee'])
   grunt.registerTask('test', ['lint', 'coffee', 'shell:test'])
   grunt.registerTask('publish', ['clean', 'default'])


fixed test task settings - don't really like the way tests are handled

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -79,6 +79,11 @@
 					cwd: './src/'
 					src: 'scripts/**/*.js'
 					dest: './.temp/'
+					expand: true
+				,
+					cwd: './src/'
+					src: 'scripts/**/*.js'
+					dest: './dist_test/'
 					expand: true
 				]
 			# Copies select files from the temp directory to the dist directory.


Add an event to the script reload.

Add an event to the script reload.

This allows scripts being reloaded to clean up after themselves.

--- src/scripts/reload.coffee
+++ src/scripts/reload.coffee
@@ -49,6 +49,7 @@

 reloadAllScripts = (msg, success, error) ->
   robot = msg.robot
+  robot.emit('reload_scripts')
   scriptsPath = Path.resolve ".", "scripts"
   robot.load scriptsPath


Fetch koding group content to render main homepage aka koding.com

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -148,11 +148,20 @@
   if frag = req.query._escaped_fragment_?
     res.send 'this is crawlable content'
   else
+    {JGroup} = koding.models
+    groupName = 'koding'
+    JGroup.one { slug: groupName }, (err, group)->
+      if err or !group? then console.error err
+      else
+        group.fetchHomepageView (err, view)->
+          if err then console.error err
+          else res.send view
+
     # log.info "serving index.html"
-    res.header 'Content-type', 'text/html'
-    fs.readFile "#{projectRoot}/website/index.html", (err, data) ->
-      throw err if err
-      res.send data
+    # res.header 'Content-type', 'text/html'
+    # fs.readFile "#{projectRoot}/website/index.html", (err, data) ->
+    #   throw err if err
+    #   res.send data

 app.get "/-/presence/:service", (req, res) ->
   {service} = req.params


Fixed: errors during import were not raised

--- server/models/konnector.coffee
+++ server/models/konnector.coffee
@@ -60,7 +60,9 @@

                 if err?
                     data = isImporting: false
-                    @updateAttributes data, callback
+                    @updateAttributes data, ->
+                        # raise the error from the import, not the update
+                        callback err

                 else
                     data =


Emergency fix to return query

Emergency fix to return query

--- server/models/recipient.coffee
+++ server/models/recipient.coffee
@@ -40,6 +40,7 @@
       .from('recipient')
       .field('email')
       .outer_join(squel.select().from('packages').where('released = \'\''), null, 'recipient.id = packages.recipient')
+    return db.query(query)

   #Create recipient
   self.create = (id, params) ->


Create zips in order and delete vscripts before zipping client

--- server/modfetch/fetcher.coffee
+++ server/modfetch/fetcher.coffee
@@ -103,18 +103,10 @@
     servPath = stage+servname
     fs.unlinkSync servPath if fs.existsSync servPath
     servzip = archiver 'zip'
-    servFinished = false
-    clientFinished = false
     stream = fs.createWriteStream servPath
     servzip.pipe stream
     servzip.bulk [{expand: true, cwd: stage, src: fetch.info.name+'/**'}]
-    log.info "Creating client bundle #{clientname}..."
-    clizip = archiver 'zip'
-    stream = fs.createWriteStream clientPath
-    clizip.pipe stream
-    clizip.bulk [{expand: true, cwd: stagem, src: '**'}]
     finished = ->
-      return if !servFinished || !clientFinished
       log.info "Bundles complete, uploading to AWS..."
       res = upload servPath, servname
       console.log res
@@ -128,13 +120,16 @@
         done(null, null)
       ).run()
     servzip.on 'finish', ->
-      servFinished = true
-      new Fiber(finished).run()
+      log.info "Creating client bundle #{clientname}..."
+      clizip = archiver 'zip'
+      stream = fs.createWriteStream clientPath
+      rmdir stagem+"scripts/vscripts/"
+      clizip.pipe stream
+      clizip.bulk [{expand: true, cwd: stagem, src: '**'}]
+      clizip.finalize()
     clizip.on 'finish', ->
-      clientFinished = true
       new Fiber(finished).run()
     servzip.finalize()
-    clizip.finalize()

 @clearExistingRepo = (id)->
   rmdir rootDir+id+"/"


Test Theme#getIndicatorsByTheme

Test Theme#getIndicatorsByTheme

--- server/test/units/theme.coffee
+++ server/test/units/theme.coffee
@@ -54,6 +54,37 @@
   )
 )

+test('.getIndicatorsByTheme returns all Indicators for given Theme', (done) ->
+  themeAttributes = [{
+    title: 'Theme 1'
+    externalId: 1
+  }]
+
+  helpers.createThemesFromAttributes(themeAttributes, (err, themes) ->
+    if err
+      console.error err
+      throw new Error(err)
+
+    indicatorAttributes = [{
+      title: "I'm an indicator of theme 1"
+      theme: themes[0].externalId
+    }]
+
+    helpers.createIndicatorModels(indicatorAttributes).success((subIndicators)->
+
+      Theme.getIndicatorsByTheme(themes[0].externalId, (err, returnedIndicators) ->
+        if err
+          console.error(err)
+          throw new Error(err)
+        assert.lengthOf returnedIndicators, 1
+
+        assert.strictEqual returnedIndicators[0].title, indicatorAttributes[0].title
+        done()
+      )
+    )
+  )
+)
+
 test('.getIndicators returns all Indicators for given Theme', (done) ->
   themeAttributes = [{
     title: 'Theme 1'


WorkspaceView::openSync -> Workspace::open

--- spec/vim-mode-spec.coffee
+++ spec/vim-mode-spec.coffee
@@ -5,8 +5,12 @@

   beforeEach ->
     atom.workspaceView = new WorkspaceView
-    atom.workspaceView.openSync()
-    atom.workspaceView.simulateDomAttachment()
+
+    waitsForPromise ->
+      atom.workspace.open()
+
+    runs ->
+      atom.workspaceView.simulateDomAttachment()

     waitsForPromise ->
       atom.packages.activatePackage('vim-mode')


Add correct function call on load

Add correct function call on load

--- app/assets/javascripts/select2-rails.coffee
+++ app/assets/javascripts/select2-rails.coffee
@@ -3,5 +3,5 @@
     selector = if typeof selector == 'undefined' then '.select2-rails' else selector
     $(selector).select2()

-$(document).ready window.Binco.Select2
-$(document).on 'page:load', window.Binco.Select2
+$(document).ready window.Binco.Select2.load
+$(document).on 'page:load', window.Binco.Select2.load


Removed some default preferences

Removed some default preferences

--- settings/language-latex2e.cson
+++ settings/language-latex2e.cson
@@ -5,8 +5,6 @@
     'decreaseIndentPattern': '^\\s*(\\\\)end\\{\\w*\\}'
     'tabLength': 4
     'softWrap': true
-    'preferredLineLength': 90
-    'scrollPastEnd': true

 '.text.tex.latex.tikz':
   'editor':


log user_id when clearing project

log user_id when clearing project

--- app/coffee/ProjectPersistenceManager.coffee
+++ app/coffee/ProjectPersistenceManager.coffee
@@ -37,7 +37,7 @@
 					callback() # ignore any errors from deleting directories

 	clearProject: (project_id, user_id, callback = (error) ->) ->
-		logger.log project_id: project_id, "clearing project for user"
+		logger.log project_id: project_id, user_id:user_id, "clearing project for user"
 		CompileManager.clearProject project_id, user_id, (error) ->
 			return callback(error) if error?
 			ProjectPersistenceManager.clearProjectFromCache project_id, (error) ->


more source

more source

--- shared/code/scraperwiki.coffee
+++ shared/code/scraperwiki.coffee
@@ -35,7 +35,7 @@

 scraperwiki.sql = (sql, success, error) ->
   settings = scraperwiki.readSettings()
-  token = settings.token
+  token = settings.source.token
   options =
     url: "#{window.location.protocol}//#{window.location.host}/#{scraperwiki.boxName}/#{token}/sqlite"
     type: "GET"


Fixing Yes/No translations in admin memberships

--- app/coffee/modules/admin/memberships.coffee
+++ app/coffee/modules/admin/memberships.coffee
@@ -235,7 +235,7 @@
 ## Member IsAdminCheckbox Directive
 #############################################################################

-MembershipsRowAdminCheckboxDirective = ($log, $repo, $confirm, $template) ->
+MembershipsRowAdminCheckboxDirective = ($log, $repo, $confirm, $template, $compile) ->
     template = $template.get("admin/admin-memberships-row-checkbox.html", true)

     link = ($scope, $el, $attrs) ->
@@ -243,6 +243,8 @@
             ctx = {inputId: "is-admin-#{member.id}"}

             html = template(ctx)
+            html = $compile(html)($scope)
+
             $el.html(html)

         if not $attrs.tgMembershipsRowAdminCheckbox?
@@ -273,8 +275,8 @@
     return {link: link}

-module.directive("tgMembershipsRowAdminCheckbox", ["$log", "$tgRepo", "$tgConfirm", "$tgTemplate",
-                                                   MembershipsRowAdminCheckboxDirective])
+module.directive("tgMembershipsRowAdminCheckbox", ["$log", "$tgRepo", "$tgConfirm",
+    "$tgTemplate", "$compile", MembershipsRowAdminCheckboxDirective])

 #############################################################################


fix escaping of truncate handlebars helpers

fixes CNVS-16644, CNVS-16678

test plan:
 * navigate to the calendar
 * switch to agenda view
 * add a new calendar event (plus button)
   - title: "><script>alert('NO!')</script>
 * refresh the page

 - at no point should an alert saying "NO!" appear
 - all views should show the text the same way it was input

Change-Id: Ia7d07d64a5ee2288f8f45c81b00d72d3c10a5fea
Reviewed-on: https://gerrit.instructure.com/44940
Reviewed-by: Andrew Butterfield <c9c7a32d9a1e9779cfad12bed963960e7af36eac@instructure.com>
QA-Review: Steven Shepherd <e794458448443812fb2685675aab3bbd1a3891bc@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Alex Boyd <6bb5a335b0b6c251e818c0de653d342158f949f9@instructure.com>
Reviewed-by: Brad Horrocks <7b5c5d1f2eba6c540b8e53f17d87a9bd7dbf604b@instructure.com>
Reviewed-by: Mark Severson <cdf1f90aee9d0d073a2a24141ebc71887f3ba533@instructure.com>
Product-Review: Cosme Salazar <d37f5e06cd46b8fd23ce5553334e09ede5394232@instructure.com>

--- app/coffeescripts/handlebars_helpers.coffee
+++ app/coffeescripts/handlebars_helpers.coffee
@@ -389,11 +389,11 @@
         'disabled'
       else
         ''
-    truncate_left: ( string, max) ->
-       return textHelper.truncateText( string.split("").reverse().join(""), {max: max}).split("").reverse().join("")
-
-    truncate: ( string, max) ->
-      return textHelper.truncateText( string, {max: max})
+    truncate_left: ( string, max ) ->
+       return Handlebars.Utils.escapeExpression(textHelper.truncateText(string.split("").reverse().join(""), {max: max}).split("").reverse().join(""))
+
+    truncate: ( string, max ) ->
+      return Handlebars.Utils.escapeExpression(textHelper.truncateText(string, {max: max}))

     escape_html: (string) ->
       htmlEscape string


Workspace: Fixed "Exit App" button on disconnected modal.

--- client/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -124,7 +124,7 @@
         Exit       :
           title    : "Exit App"
           cssClass : "modal-cancel"
-          callback : ->
+          callback : =>
             @disconnectedModal.destroy()
             delete @disconnectedModal
             appManager = KD.getSingleton("appManager")


NEW: Add lcfirst (lowercase first) filter to match ucfirst

--- src/app/common/filters/filters.coffee
+++ src/app/common/filters/filters.coffee
@@ -230,3 +230,8 @@
     input = $filter('limitTo')(input, pagination.pageSize)
     input
 )
+
+.filter('lcfirst', ->
+  (input) ->
+    input[0].toLowerCase() + input.substring(1)
+)


coffee:dist Compile all coffee-script for distribution (no source maps)

--- src/tintan/compile.coffee
+++ src/tintan/compile.coffee
@@ -53,6 +53,13 @@
     Tintan.$.onTaskNamespace options.name + ':force', ->
       desc "Compile all coffee-script (regardless of mod time) into #{options.target}"
       task 'force', ->
+        for source, task of map then do (task) ->
+          touch source, {mtime: true}, -> invoke task
+
+    Tintan.$.onTaskNamespace options.name + ':dist', ->
+      desc "Compile all coffee-script for distribution (no source maps) into #{options.target}"
+      task 'dist', ->
+        jake.program.envVars['source_maps'] = false
         for source, task of map then do (task) ->
           touch source, {mtime: true}, -> invoke task


:art:

:art:

--- src/token-iterator.coffee
+++ src/token-iterator.coffee
@@ -31,7 +31,7 @@
     @openTags = EMPTY_ARRAY
     @closeTags = EMPTY_ARRAY
     @containingTags = null
-    @tagsToReopen = null
+    @tagsToReopenAfterFold = null

   seekToScreenRow: (screenRow) ->
     @startScreenPosition = Point(screenRow, 0)
@@ -83,12 +83,12 @@
       if isEqualPoint(@startScreenPosition, @patchIterator.getOutputEnd())
         @patchIterator.moveToSuccessor()

-    if @tagsToReopen?
+    if @tagsToReopenAfterFold?
       for tag in @closeTags
-        @tagsToReopen.splice(@tagsToReopen.lastIndexOf(tag), 1)
+        @tagsToReopenAfterFold.splice(@tagsToReopenAfterFold.lastIndexOf(tag), 1)
       @closeTags = EMPTY_ARRAY
-      @openTags = @tagsToReopen.concat(@openTags)
-      @tagsToReopen = null
+      @openTags = @tagsToReopenAfterFold.concat(@openTags)
+      @tagsToReopenAfterFold = null

     @assignEndPositionsAndText()
     true
@@ -123,7 +123,7 @@
         @closeTags = @containingTags.slice()
         @containingTags.length = 0
         @openTags = EMPTY_ARRAY
-        @tagsToReopen = @decorationIterator.seek(@getEndBufferPosition())
+        @tagsToReopenAfterFold = @decorationIterator.seek(@getEndBufferPosition())
       else
         decorationIteratorPosition = @decorationIterator.getPosition()


Fix indentation

Fix indentation

--- src/trix/text_view.coffee
+++ src/trix/text_view.coffee
@@ -40,8 +40,8 @@

     # Add an extra newline if the text ends with one. Otherwise, the cursor won't move down.
     if @text.endsWith("\n")
-     node = createNodesForString("\n", @text.getLength())[0]
-     elements.push(node)
+      node = createNodesForString("\n", @text.getLength())[0]
+      elements.push(node)

     elements


gcloud: ability to list all snapshots; create disk

--- salvus/smc_gcloud.coffee
+++ salvus/smc_gcloud.coffee
@@ -228,12 +228,14 @@
                         id = data?.id; cb(err)
             (cb) =>
                 dbg("get all snapshots with given id as source")
-                @gcloud._gce.getSnapshots {filter:"sourceDiskId eq #{id}", maxResults:500}, (err, snapshots) =>
-                    if err
-                        cb(err)
-                    else
-                        s = (@gcloud.snapshot(name:x.name) for x in snapshots)
-                        cb()
+                @gcloud.get_snapshots
+                    filter : "sourceDiskId eq #{id}"
+                    cb     : (err, snapshots) =>
+                        if err
+                            cb(err)
+                        else
+                            s = (@gcloud.snapshot(name:x.name) for x in snapshots)
+                            cb()
         ], (err) =>
             opts.cb(err, s)
         )
@@ -325,6 +327,24 @@
                         opts.cb(err)
                     else
                         opts.cb(undefined, data.storageBytes / 1000 / 1000 / 1000)
+
+    # create disk based on this snapshot
+    create_disk: (opts) =>
+        opts = defaults opts,
+            name    : required
+            size_GB : undefined
+            type    : 'pd-standard'   # 'pd-standard' or 'pd-ssd'
+            zone    : DEFAULT_ZONE
+            cb      : undefined
+        dbg = @dbg("create_disk(#{misc.to_json(opts)})")
+        dbg("starting...")
+        config =
+            sourceSnapshot : "global/snapshots/#{@name}"
+        config.sizeGb = opts.size_GB if opts.size_GB?
+        config.type = opts.type if opts.type?
+        @gcloud._gce.zone(@zone).createDisk opts.name, config, (err, disk, operation, apiResponse) =>
+            handle_operation(err, operation, (->dbg('done')), (err) => opts.cb?(err))
+

 class GoogleCloud
     constructor: (opts={}) ->
@@ -408,11 +428,37 @@
         @_snapshot_cache ?= {}
         return (@_snapshot_cache[key] ?= new Snapshot(@, opts.name))

+    # return list of names of all snapshots
+    get_snapshots: (opts) =>
+        opts = defaults opts,
+            filter : undefined
+            cb     : required
+        options = {maxResults:500}   # deal with pagers next year
+        options.filter = opts.filter if opts.filter?
+        dbg = @dbg("get_snapshots")
+        dbg("options=#{misc.to_json(options)}")
+        @_gce.getSnapshots options, (err, snapshots) =>
+            dbg("done")
+            if err
+                opts.cb(err)
+            else
+                s = []
+                for x in snapshots
+                    i = x.metadata.sourceDisk.indexOf('/zones/')
+                    s.push
+                        name      : x.name
+                        timestamp : new Date(x.metadata.creationTimestamp)
+                        size_GB   : x.metadata.storageBytes / 1000 / 1000 / 1000
+                        source    : x.metadata.sourceDisk.slice(i+7)
+                opts.cb(undefined, s)
+
     get_vms: (opts) =>
         opts = defaults opts,
             cb  : required
-        @dbg("get_vms")()
+        dbg = @dbg("get_vms")
+        dbg('starting...')
         @_gce.getVMs (err, vms) =>
+            dbg('done')
             if err
                 opts.cb(err)
             else


bug -- ipython notebook server; I evidently accidently set the startup timeout to 1 seconds instead of 10 seconds.

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -2843,7 +2843,7 @@

     remove: () =>
         @element.salvus_console(false)
-        @element.remove()
+        @element.remove()

     show: () =>
         @element.show()
@@ -3079,7 +3079,7 @@
             command    : "ipython-notebook"
             args       : ['start']
             bash       : false
-            timeout    : 1
+            timeout    : 10
             err_on_exit: false
             cb         : (err, output) =>
                 if err
@@ -3205,6 +3205,7 @@
                     path       : @path
                     command    : "ls"
                     args       : ['-lt', "--time-style=+%s", @file, @syncdoc_filename]
+                    timeout    : 10
                     err_on_exit: false
                     cb         : (err, output) =>
                         if err?


only set nomorehistory if length is 0

only set nomorehistory if length is 0

--- src/ui/models/conv.coffee
+++ src/ui/models/conv.coffee
@@ -232,7 +232,7 @@
         c.requestinghistory = false
         event = state?.event
         c.event = (event ? []).concat (c.event ? [])
-        c.nomorehistory = true if (event?.length ? 0) < HISTORY_AMOUNT
+        c.nomorehistory = true if event?.length == 0

         # first signal is to give views a change to record the
         # current view position before injecting new DOM


refactor activate and deactivate player info triggers

--- app/scripts/components/PlayerInfo.js.coffee
+++ app/scripts/components/PlayerInfo.js.coffee
@@ -30,12 +30,10 @@

     @listenTo player, 'UpdateLives', @updatePlayerInfo
     @listenTo player, 'UpdatePoints', @updatePlayerInfo
-    @listenTo player, 'NewComponent', (cl) ->
-      for n in cl when n is 'ControlScheme'
-        @updatePlayerInfo()
-    @listenTo player, 'RemoveComponent', (n) ->
-      if n is 'ControlScheme'
-        @updatePlayerInfo()
+    @listenTo player, 'Activated', =>
+      @updatePlayerInfo()
+    @listenTo player, 'Deactivated', =>
+      @updatePlayerInfo()
     this

   updatePlayerInfo: ->


Adapta inicialização do Sku Seletor para o padrão dos Portal Plugins

#5671804704367/8074187421565

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -351,20 +351,16 @@

 # PLUGIN ENTRY POINT
 $.fn.skuSelector = (productData, jsOptions = {}) ->
-	if this.length > 1
-		throw new Error('Sku Selector should be activated on only one element! To activate many sku selectors, activate it for each element.')
-	else if this.length == 0
-		throw new Error('Sku Selector was activated on 0 elements')
-
-	this.addClass('sku-selector-loading')
-
-	domOptions = this.data()
-	defaultOptions = $.extend({}, $.fn.skuSelector.defaults)
-	options = $.extend(true, defaultOptions, domOptions, jsOptions)
-
-	this.data('skuSelector', new SkuSelector(this, productData, options))
-
-	this.removeClass('sku-selector-loading')
+	defaultOptions = $.extend true, {}, $.fn.skuSelector.defaults
+	for element in this
+		$element = $(element)
+		$element.addClass('sku-selector-loading')
+		domOptions = $element.data()
+		options = $.extend(true, defaultOptions, domOptions, jsOptions)
+		unless $element.data('skuSelector')
+			$element.data('skuSelector', new SkuSelector($element, productData, options))
+
+		$element.removeClass('sku-selector-loading')

 	return this


fixed .size( ) overriding non-discrete axis in line charts

--- src/viz/types/line.coffee
+++ src/viz/types/line.coffee
@@ -36,11 +36,14 @@
     axis = if vars.time.value is vars.y.value then "y" else "x"
     vars.self[axis] scale: "discrete"

-  y    = vars[vars.axes.opposite].value
+  y    = vars[vars.axes.opposite]
   size = vars.size
-  if (not y.value and size.value) or (size.changed and size.previous is y.value)
+
+  if (not y.value and size.value) or
+     (size.changed and size.previous is y.value)
     vars.self[vars.axes.opposite] size.value
-  else if (not size.value and y.value) or (y.changed and y.previous is size.value)
+  else if (not size.value and y.value) or
+          (y.changed and y.previous is size.value)
     vars.self.size y.value
   return
 line.shapes  = ["line"]


change debug log in pluginsEngine for loading.

--- src/core/pluginsEngine/plugins.coffee
+++ src/core/pluginsEngine/plugins.coffee
@@ -50,7 +50,7 @@
 			env.config.plugins.push plugin_name
 			pluginsEngine.plugin[plugin_name] = plugin
 		catch e
-			env.debug "Error requiring plugin \'" + plugin_name + "\' entry point. err:" + e
+			env.debug "Error requiring plugin \'" + plugin_name + "\' entry point."
 		return

 	pluginsEngine.init = (cwd, callback) ->


DateTime: Added "bc_appendix" to support B.C. dates.

- Fix for setting short format dates when picking from calendar.

--- src/elements/DateTime/DateTime.coffee
+++ src/elements/DateTime/DateTime.coffee
@@ -17,6 +17,7 @@

 	@defaults:
 		button_tooltip: "Open calendar"
+		bc_appendix: ["B.C.","BC"]

 	initOpts: ->
 		super()
@@ -83,7 +84,7 @@
 		@__input_format = @initFormat(@__default_format)
 		return

-	setInputFormat: (use_clock) ->
+	setInputFormat: (use_clock = (@__input_format_no_time == @__input_format)) ->
 		if use_clock == true
 			@__input_format = @initFormat(@__input_formats[0])
 		else
@@ -318,7 +319,10 @@

 		# console.debug "display format", s, output_type, CUI.util.dump(output_format), output_format[type], type

-		mom.format(output_format[type])
+		v = mom.format(output_format[type])
+		if mom.bc
+			v = v + " " + CUI.DateTime.defaults.bc_appendix[0]
+		v

 	regexpMatcher: ->
 		YYYY:
@@ -383,20 +387,26 @@

 		mom = @parse(value)

-		if mom.isValid()
-			mom.format(@getCurrentFormatDisplay())
-		else
-			value
+		if not mom.isValid()
+			return value
+
+		v = mom.format(@getCurrentFormatDisplay())
+		if mom.bc
+			v = v + " " + CUI.DateTime.defaults.bc_appendix[0]
+		return v

 	getValueForInput: (v = @getValue()) ->
 		if CUI.util.isEmpty(v?.trim())
 			return ""

 		mom = @parse(v)
-		if mom.isValid()
-			return mom.format(@__input_format.input)
-		else
+		if not mom.isValid()
 			return v
+
+		v = mom.format(@__input_format.input)
+		if mom.bc
+			v = v + " " + CUI.DateTime.defaults.bc_appendix[0]
+		return v

 	__checkInput: (value) ->
 		if not CUI.util.isEmpty(value?.trim())
@@ -408,7 +418,7 @@
 		return true

 	__getInputBlocks: (v) ->
-		console.debug "getInputBlocks", v, @__input_format.regexp
+		# console.debug "getInputBlocks", v, @__input_format.regexp
 		match = v.match(@__input_format.regexp)
 		if not match
 			return false
@@ -439,6 +449,9 @@
 		mom = @parse(value)
 		if mom.isValid()
 			value = mom.format(@__input_format.store)
+			if mom.bc
+				value = "-"+value
+
 		else if @_store_invalid and value.trim().length > 0
 			value = 'invalid'
 		else
@@ -609,6 +622,19 @@
 		if not (s?.trim?().length > 0)
 			return moment.invalid()

+		bc = false
+		if s.startsWith("-")
+			bc = true
+			s = s.substring(1)
+		else
+			us = s.toLocaleUpperCase()
+			for appendix in CUI.DateTime.defaults.bc_appendix
+				ua = appendix.toLocaleUpperCase()
+				if us.endsWith(" "+ua)
+					s = s.substring(0, s.length - ua.length).trim()
+					bc = true
+					break
+
 		for format in formats
 			mom = @__parseFormat(format, s)
 			if mom
@@ -617,6 +643,8 @@
 				else
 					@__input_format = @initFormat(@__default_format)
 				mom.locale(moment.locale())
+				mom.bc = bc
+
 				# console.debug "parsing ok", mom, f, moment.locale()
 				return mom

@@ -632,10 +660,14 @@
 		if not output_format
 			return mom

-		if mom.isValid()
-			mom.format(@__input_format[output_format])
+		if not mom.isValid()
+			return null
+
+		v = mom.format(@__input_format[output_format])
+		if mom.bc
+			return "-"+v
 		else
-			null
+			return v

 	__parseFormat: (f, s) ->
 		for k in CUI.DateTime.formatTypes
@@ -678,6 +710,7 @@
 	drawDate: (_mom) ->
 		if not _mom
 			mom = @__current_moment.clone()
+			mom.bc = @__current_moment.bc
 		else
 			mom = _mom

@@ -692,6 +725,8 @@

 		if update_current_moment
 			@__current_moment = mom.clone()
+			@__current_moment.bc = mom.bc
+			@setInputFormat()
 			@setInputFromMoment()

 		console.info("CUI.DateTime.updateCalendar:", @__current_moment.format(@__input_format.input))
@@ -925,8 +960,6 @@
 					class: "cui-date-time-header-month"
 					buttons:
 						[
-							appearance: if CUI.__ng__ then undefined else "flat"
-							size: if CUI.__ng__ then undefined else "mini"
 							icon: "left"
 							onClick: (ev) =>
 								if mom.clone().subtract(1, "months").year() < @_min_year
@@ -954,8 +987,6 @@

 							# ).start()
 						,
-							appearance: if CUI.__ng__ then undefined else "flat"
-							size: if CUI.__ng__ then undefined else "mini"
 							icon: "right"
 							onClick: (ev) =>
 								if mom.clone().add(1, "months").year() > @_max_year
@@ -968,10 +999,8 @@
 				content: new CUI.Buttonbar
 					class: "cui-date-time-header-year"
 					buttons: [
-						appearance: if CUI.__ng__ then undefined else "flat"
-						size: if CUI.__ng__ then undefined else "mini"
 						icon: "left"
-						group: if CUI.__ng__ then "year" else null
+						group: "year"
 						onClick: (ev) =>
 							if data.year-1 < @_min_year
 								return
@@ -986,7 +1015,7 @@
 							placeholder: ""+now_year
 							data: data
 							name: "year"
-							group: if CUI.__ng__ then "year" else null
+							group: "year"
 							onDataChanged: (data) =>
 								if CUI.util.isEmpty(data.year)
 									year = now_year
@@ -995,10 +1024,8 @@
 								CUI.scheduleCallback(ms: 500, call: updateCalendar)
 						).start()
 					,
-						appearance: if CUI.__ng__ then undefined else "flat"
-						size: if CUI.__ng__ then undefined else "mini"
 						icon: "right"
-						group: if CUI.__ng__ then "year" else null
+						group: "year"
 						onClick: (ev) =>
 							if data.year+1 > @_max_year
 								return
@@ -1318,7 +1345,10 @@
 		if not mom.isValid()
 			return null

-		return mom.format(dt.getCurrentFormatDisplay())
+		v = mom.format(dt.getCurrentFormatDisplay())
+		if mom.bc
+			v = v + " " + CUI.DateTime.defaults.bc_appendix[0]
+		v

 	@toMoment: (datestr) ->
 		if CUI.util.isEmpty(datestr)


ListView - fix calculation typo bug

ListView - fix calculation typo bug

width -dim.paddingHorizontal + dim.borderHorizontal

was interpreted as

width( -dim.paddingHorizontal + dim.borderHorizontal)

adding a space before dim.paddingHorizontal fixed the calculation

--- src/elements/ListView/ListView.coffee
+++ src/elements/ListView/ListView.coffee
@@ -950,7 +950,7 @@
 				if dim.computedStyle.boxSizing == "border-box"
 					cell.style.setProperty("width", width+"px", "important")
 				else
-					cell.style.setProperty("width", (width -dim.paddingHorizontal + dim.borderHorizontal)+"px", "important")
+					cell.style.setProperty("width", (width - dim.paddingHorizontal + dim.borderHorizontal)+"px", "important")

 		# CUI.info("ListView#"+@listViewCounter, "Set colspan cell styles.")


provide element for digest exception

provide element for digest exception

--- src/changeDetector.coffee
+++ src/changeDetector.coffee
@@ -385,6 +385,7 @@
         total += cd.watchList.length
         for w in cd.watchList.slice()
             result.src = w.src
+            result.element = w.el
             last = w.value
             value = w.exp scope
             if last isnt value
@@ -439,7 +440,10 @@
     result.total = total
     result.changes = changes
     result.extraLoop = extraLoop
+
     result.scope = null
+    result.src = ''
+    result.element = null
     return

@@ -458,6 +462,7 @@
             extraLoop: false
             src: ''
             scope: null
+            element: null

         while mainLoop
             mainLoop--
@@ -482,6 +487,7 @@
         alight.exceptionHandler e, '$scan, error in expression: ' + result.src,
             src: result.src
             scope: result.scope
+            element: result.element
             result: result
     result.mainLoop = mainLoop
     result.totalChanges = totalChanges


Give hull-remote a little more time to start

--- src/client/channel.coffee
+++ src/client/channel.coffee
@@ -54,7 +54,7 @@
     domready(@onDomReady)

   onDomReady : ()=>
-    @timeout = setTimeout(@loadingFailed, 30000);
+    @timeout = setTimeout(@loadingFailed, 60000);
     @rpc = new xdm.Rpc
       remote    : @currentConfig.getRemoteUrl()
       container : document.body


respect the status code on the error if it exists

--- services/clsi/app.coffee
+++ services/clsi/app.coffee
@@ -62,7 +62,7 @@

 app.use (error, req, res, next) ->
 	logger.error err: error, "server error"
-	res.send 500
+	res.send err?.statusCode || 500

 app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) ->
 	logger.log "CLSI listening on #{host}:#{port}"


Fix CutstomJS' tests

Fix CutstomJS' tests

--- bokehjs/test/models/callbacks/customjs.coffee
+++ bokehjs/test/models/callbacks/customjs.coffee
@@ -30,7 +30,7 @@
     r = new CustomJS({args: {foo: rng }})

     it "should contain the args values", ->
-      expect(r.get('values')).to.be.deep.equal [rng]
+      expect(r.values).to.be.deep.equal [rng]

     it "should round-trip through document serialization", ->
       d = new Document()
@@ -41,30 +41,30 @@
       copy = Document.from_json_string(JSON.stringify(parsed))
       r_copy = copy.get_model_by_id(r.id)
       rng_copy = copy.get_model_by_id(rng.id)
-      expect(r.get('values')).to.be.deep.equal [rng]
-      expect(r_copy.get('values')).to.be.deep.equal [rng_copy]
+      expect(r.values).to.be.deep.equal [rng]
+      expect(r_copy.values).to.be.deep.equal [rng_copy]

     it "should update when args changes", ->
       rng2 = new Range1d()
       r.set('args', {foo: rng2 })
-      expect(r.get('values')).to.be.deep.equal [rng2]
+      expect(r.values).to.be.deep.equal [rng2]

   describe "func property", ->

     it "should return a Function", ->
       r = new CustomJS()
-      expect(r.get('func')).to.be.an.instanceof Function
+      expect(r.func).to.be.an.instanceof Function

     it "should have code property as function body", ->
       r = new CustomJS({code: "return 10"})
       f = new Function("cb_obj", "cb_data", "require", "return 10")
-      expect(r.get('func').toString()).to.be.equal f.toString()
+      expect(r.func.toString()).to.be.equal f.toString()

     it "should have values as function args", ->
       rng = new Range1d()
       r = new CustomJS({args: {foo: rng.ref()}, code: "return 10"})
       f = new Function("foo", "cb_obj", "cb_data", "require", "return 10")
-      expect(r.get('func').toString()).to.be.equal f.toString()
+      expect(r.func.toString()).to.be.equal f.toString()

   describe "execute method", ->


kill the beta message

kill the beta message

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -23,7 +23,6 @@
   # syncAuth()
   # setupFastClick()
   initShortCuts()
-  showBetaMessage()

 setMobileClass = ->
   if /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)


left justifying the zoom button text

left justifying the zoom button text

--- app/assets/javascripts/inspector_address.coffee
+++ app/assets/javascripts/inspector_address.coffee
@@ -409,7 +409,7 @@
       {
         element: ".leaflet-control-zoom.leaflet-bar.leaflet-control"
         intro: "This can get tricky when dealing with funky boundary lines. When you run into this, look for one or two that seem to be primary dividers of multiple properties and use that to decide what belongs to whom. Zooming out a bit might make it easier to determine. "
-        position: "right"
+        position: "left"
         polygon_index: 14
       }
       {
@@ -499,7 +499,7 @@
       {
           element: ".leaflet-control-zoom.leaflet-bar.leaflet-control"
         intro: "This structure was actually built to house the world's longest hot dog. No probably not, but it's very long. Zoom out to see who it belongs to. "
-        position: "right"
+        position: "left"
         polygon_index: 26
       }
       {


bigfix

bigfix

--- app/assets/javascripts/projects/tasks.js.coffee
+++ app/assets/javascripts/projects/tasks.js.coffee
@@ -689,6 +689,12 @@
       remaining += parseInt(item.remaining_estimate)
       delta     += parseInt(item.delta)

+    $('#tasks_delta').removeClass('bg-red bg-green')
+    if delta < 0
+      $('#tasks_delta').addClass('bg-red')
+    else if delta > 0
+      $('#tasks_delta').addClass('bg-green')
+
     estimate  = Duration.stringify(estimate, {format: 'micro'}) if estimate != 0
     remaining = Duration.stringify(remaining, {format: 'micro'}) if remaining != 0
     logged    = Duration.stringify(logged, {format: 'micro'}) if logged != 0
@@ -704,8 +710,4 @@
     $("#tasks_remaining").html(remaining)
     $("#tasks_delta").html(delta)

-    $('#tasks_delta').removeClass('bg-red bg-green')
-    if delta < 0
-      $('#tasks_delta').addClass('bg-red')
-    else if delta > 0
-      $('#tasks_delta').addClass('bg-green')
+


Fix bug #1154: Nav-Menu broken affter submit any of the two project profile forms

--- app/coffee/modules/admin/project-profile.coffee
+++ app/coffee/modules/admin/project-profile.coffee
@@ -96,7 +96,7 @@
 ## Project Profile Directive
 #############################################################################

-ProjectProfileDirective = ($rootscope, $log, $repo, $confirm, $loading) ->
+ProjectProfileDirective = ($repo, $confirm, $loading) ->
     link = ($scope, $el, $attrs) ->
         form = $el.find("form").checksley({"onlyOneErrorElement": true})
         submit = (target) =>
@@ -108,7 +108,7 @@
             promise.then ->
                 $loading.finish(target)
                 $confirm.notify("success")
-                $rootscope.$broadcast("project:loaded", $scope.project)
+                $scope.$emit("project:loaded", $scope.project)

             promise.then null, (data) ->
                 $loading.finish(target)
@@ -132,14 +132,14 @@

     return {link:link}

-module.directive("tgProjectProfile", ["$rootScope", "$log", "$tgRepo", "$tgConfirm", "$tgLoading", ProjectProfileDirective])
+module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", ProjectProfileDirective])

 #############################################################################
 ## Project Modules Directive
 #############################################################################

-ProjectModulesDirective = ($rootscope, $log, $repo, $confirm, $loading) ->
+ProjectModulesDirective = ($repo, $confirm, $loading) ->
     link = ($scope, $el, $attrs) ->
         form = $el.find("form").checksley()
         submit = =>
@@ -181,4 +181,4 @@

     return {link:link}

-module.directive("tgProjectModules", ["$rootScope", "$log", "$tgRepo", "$tgConfirm", "$tgLoading", ProjectModulesDirective])
+module.directive("tgProjectModules", ["$tgRepo", "$tgConfirm", "$tgLoading", ProjectModulesDirective])


Print banner after booting app

Print banner after booting app

Instead of listening for when the framework has booted, listen for the event
that is fired when the application is booted and the DOM is ready.

At this stage, any changes to up.log.config would be completed, allowing the
banner to be disabled if desired.

Closes #157

--- lib/assets/javascripts/unpoly/log.coffee
+++ lib/assets/javascripts/unpoly/log.coffee
@@ -115,7 +115,7 @@
       console.log(logo + text)

-  up.on 'up:framework:boot', printBanner
+  up.on 'up:app:boot', printBanner

   up.on 'up:framework:reset', reset


Don't need to explicitly skip Mobile Artillery's next level now.

--- app/views/play/level/modal/victory_modal.coffee
+++ app/views/play/level/modal/victory_modal.coffee
@@ -78,7 +78,7 @@
     c = super()
     c.body = @body
     c.me = me
-    c.hasNextLevel = _.isObject(@level.get('nextLevel')) and (@level.get('name') isnt "Mobile Artillery")
+    c.hasNextLevel = _.isObject(@level.get('nextLevel'))
     c.levelName = utils.i18n @level.attributes, 'name'
     c.level = @level
     if c.level.get('type') is 'ladder'


Add optional gcs config to override gcs settings (for fake gcs server)

--- config/settings.defaults.coffee
+++ config/settings.defaults.coffee
@@ -33,6 +33,12 @@
 		# fs - local filesystem
 		# gcs - Google Cloud Storage
 		backend: process.env['BACKEND']
+
+		gcs:
+			if process.env['GCS_API_ENDPOINT']
+				apiEndpoint: process.env['GCS_API_ENDPOINT']
+				apiScheme: process.env['GCS_API_SCHEME']
+				projectId: process.env['GCS_PROJECT_ID']

 		s3:
 			if process.env['AWS_ACCESS_KEY_ID']? or process.env['S3_BUCKET_CREDENTIALS']?
@@ -71,7 +77,7 @@

 	sentry:
 		dsn: process.env.SENTRY_DSN
-
+
 # Filestore health check
 # ----------------------
 # Project and file details to check in persistor when calling /health_check


Populate S3 settings from environment variable

--- config/settings.defaults.coffee
+++ config/settings.defaults.coffee
@@ -23,7 +23,8 @@
 			user_files: Path.resolve(__dirname + "/../user_files")
 			public_files: Path.resolve(__dirname + "/../public_files")
 			template_files: Path.resolve(__dirname + "/../template_files")
-		# if you are using S3, then fill in your S3 details below
+		# if you are using S3, then fill in your S3 details below,
+		# or use env var with the same structure.
 		# s3:
 		# 	key: ""     # default
 		# 	secret: ""  # default
@@ -31,6 +32,7 @@
 		#     auth_key: ""
 		#     auth_secret: ""
 		#  bucketname2: # secrets for bucketname2...
+		s3: JSON.parse process.env['S3_CREDENTIALS'] if process.env['S3_CREDENTIALS']

 	path:
 		uploadFolder: Path.resolve(__dirname + "/../uploads")


added more tests. type class matching comes next.

--- test/compoundTypes.coffee
+++ test/compoundTypes.coffee
@@ -21,3 +21,49 @@
       recognize('IntField', {aField: 1}).should.eql true
       recognize('IntField', {aField: 1.5}).should.eql false
       recognize('IntField', {foo: 1}).should.eql false
+
+    it 'should recognize an object with multiple fields', ->
+      system = typeSystem.init()
+
+      type = newtype:
+        name: 'ThreeFields'
+        fields: [
+          intField: 'Int'
+          stringField: 'String'
+          objectField: 'Object'
+        ]
+
+      system.register type
+
+      recognize = recognizer.init system
+
+      recognize('ThreeFields', {intField: 0, stringField: '', objectField: {}}).should.eql true
+      recognize('ThreeFields', {intField: 1, stringField: '1', objectField: {foo: 2}}).should.eql true
+      recognize('ThreeFields', {intField: '1', stringField: '1', objectField: {foo: 2}}).should.eql false
+      recognize('ThreeFields', {stringField: '1', objectField: {foo: 2}}).should.eql false
+
+    it 'should recognize an object with another object as a field', ->
+      system = typeSystem.init()
+
+      outerType = newtype:
+        name: 'Outer'
+        fields: [
+          innerField: 'Inner'
+        ]
+
+      innerType = newtype:
+        name: 'Inner'
+        fields: [
+          intField: 'Int'
+        ]
+
+      system.register outerType
+      system.register innerType
+
+      recognize = recognizer.init system
+
+      recognize('Outer', {innerField: {intField: 1} }).should.eql true
+      recognize('Outer', {innerField: {intField: 0} }).should.eql true
+      recognize('Inner', {intField: 1}).should.eql true
+      recognize('Outer', {innerField: {notIntField: 1} }).should.eql false
+      recognize('Outer', {innerField: {intField: 'foo'} }).should.eql false


Replace anchor w/ marker in selection destruction spec

--- spec/app/selection-spec.coffee
+++ spec/app/selection-spec.coffee
@@ -68,10 +68,7 @@
       expect(changeScreenRangeHandler).toHaveBeenCalled()

   describe "when the selection is destroyed", ->
-    it "destroys its cursor and its anchor's cursor", ->
+    it "destroys its marker", ->
       selection.setBufferRange([[2, 0], [2, 10]])
-
       selection.destroy()
-
-      expect(editSession.getAnchors().indexOf(selection.anchor)).toBe -1
-      expect(editSession.getAnchors().indexOf(selection.cursor.anchor)).toBe -1
+      expect(editSession.getMarkerBufferRange(selection.marker)).toBeUndefined()


Add failing spec to demonstate the problem

--- spec/notifications-spec.coffee
+++ spec/notifications-spec.coffee
@@ -485,6 +485,25 @@
             generateFakeAjaxResponses
               packageResponse:
                 repository: url: 'https://github.com/someguy/notifications'
+                releases: latest: '0.10.0'
+            generateException()
+            fatalError = notificationContainer.querySelector('atom-notification.fatal')
+            waitsForPromise ->
+              fatalError.getRenderPromise().then -> issueBody = fatalError.issue.issueBody
+
+          it "asks the user to update their packages", ->
+            fatalNotification = fatalError.querySelector('.fatal-notification')
+            button = fatalError.querySelector('.btn')
+
+            expect(button.textContent).toContain 'Check for package updates'
+            expect(fatalNotification.textContent).toContain 'Upgrading to the latest'
+            expect(button.getAttribute('href')).toBe '#'
+
+        describe "when the package is an atom-owned non-core package", ->
+          beforeEach ->
+            generateFakeAjaxResponses
+              packageResponse:
+                repository: url: 'https://github.com/atom/sort-lines'
                 releases: latest: '0.10.0'
             generateException()
             fatalError = notificationContainer.querySelector('atom-notification.fatal')


Buma field input name

Buma field input name

--- snippets/bulma_form.cson
+++ snippets/bulma_form.cson
@@ -4,9 +4,9 @@
     'prefix': 'bulma:field'
     'body': """

-        Label
+        Label

-          <input class="input" type="text" placeholder="Text input">
+          <input class="input" id="${1:name}" name="${1:name}" type="text" placeholder="Text input">

This is a help text


Added snippet methods for formfield

Added snippet methods for formfield

--- snippets/php_fields.cson
+++ snippets/php_fields.cson
@@ -1,5 +1,35 @@
 '.text.html.php':
   #  Fields
+  'setAttribute':
+    'prefix': 'setattribute'
+    'body': '->setAttribute(\'${1}\', \'${2}\')${0}'
+  'setValue':
+    'prefix': 'setvalue'
+    'body': '->setValue(${1})${0}'
+  'setName':
+    'prefix': 'setname'
+    'body': '->setName(\'${1}\')${0}'
+  'setTitle':
+    'prefix': 'settitle'
+    'body': '->setTitle(\'${1}\')${0}'
+  'setRightTitle':
+    'prefix': 'setrighttitle'
+    'body': '->setRightTitle(\'${1}\')${0}'
+  'setLeftTitle':
+    'prefix': 'setlefttitle'
+    'body': '->setLeftTitle(\'${1}\')${0}'
+  'setDescription':
+    'prefix': 'setdescription'
+    'body': '->setDescription(\'${1}\')${0}'
+  'addExtraClass':
+    'prefix': 'addextraclass'
+    'body': '->addExtraClass(\'${1}\')${0}'
+  'removeExtraClass':
+    'prefix': 'removeextraclass'
+    'body': '->removeExtraClass(\'${1}\')${0}'
+  'Required':
+    'prefix': 'required'
+    'body': '->Required()${0}'
   'HTMLEditorField':
     'prefix': 'htmlfield'
     'body': """


Add spec for a fold causing a line to unwrap

--- spec/atom/renderer-spec.coffee
+++ spec/atom/renderer-spec.coffee
@@ -275,6 +275,13 @@
           fold.destroy()
           expect(renderer.lineForRow(0).text).toBe 'var quicksort = function () {'

+      describe "when a fold causes a wrapped line to become shorter than the max line length", ->
+        fit "unwraps the line", ->
+          renderer.setMaxLineLength(50)
+          renderer.createFold([[3, 0], [3, 15]])
+          expect(renderer.lineForRow(3).text).toBe '... items.shift(), current, left = [], right = [];'
+          expect(renderer.lineForRow(4).text).toBe '    while(items.length > 0) {'
+
     describe "when the buffer changes", ->
       [fold1, fold2] = []
       beforeEach ->


We are traversing list and not object.

We are traversing list and not object.

--- server/publication.coffee
+++ server/publication.coffee
@@ -333,7 +333,7 @@
   handlePublications = null

   removePublications = (ids) =>
-    for id of ids when currentPublications[id]
+    for id in ids when currentPublications[id]
       delete currentPublications[id]
       @removed 'Publications', id


Add tests for parse_query_string.

Add tests for parse_query_string.

--HG--
branch : qunit

--- src/js/collection-editor-tests.coffee
+++ src/js/collection-editor-tests.coffee
@@ -1,7 +1,7 @@
 $(document).ready ->
   mock_access_token_params =
     access_token: 'nonsense'
-    expires_in: 3600
+    expires_in: '3600'
     token_type: 'Bearer'

   test "hello test", ->
@@ -123,17 +123,18 @@
     teardown: ->
       history.replaceState(null,'',window.location.href.replace("#{location.hash}",''))

-  test "filter_url_params should filter off access_token, expires_in, and token_type by default", ->
+  test "filter_url_params should filter off access_token, expires_in, and token_type by default, parse_query_string should parse hash parameters", ->
     clean_url = window.location.href.replace("#{location.hash}",'')
     equal( window.location.href, clean_url, "url is clean at test start" )
     history.replaceState(null,'',"#{window.location.href}##{$.param(mock_access_token_params)}")
     equal( window.location.href, "#{clean_url}##{$.param(mock_access_token_params)}", "url gets expected parameters at test start" )
     original_params = parse_query_string()
+    deepEqual( original_params, mock_access_token_params, "parse_query_string parses hash parameters as expected" )
     filtered_params = filter_url_params(original_params)
-    equal( filtered_params, original_params, "filter_url_params returns original params" )
+    deepEqual( filtered_params, original_params, "filter_url_params returns original params" )
     equal( window.location.href, clean_url, "filter_url_params strips off expected params" )

-  test "filter_url_params should filter off passed in parameters", ->
+  test "filter_url_params should filter off passed in parameters, parse_query_string should parse hash parameters", ->
     clean_url = window.location.href.replace("#{location.hash}",'')
     equal( window.location.href, clean_url, "url is clean at test start" )
     collection_param =
@@ -141,6 +142,7 @@
     history.replaceState(null,'',"#{window.location.href}##{$.param(collection_param)}")
     equal( window.location.href, "#{clean_url}##{$.param(collection_param)}", "url gets expected parameters at test start" )
     original_params = parse_query_string()
+    deepEqual( original_params, collection_param, "parse_query_string parses hash parameters as expected" )
     filtered_params = filter_url_params(original_params,['collection'])
-    equal( filtered_params, original_params, "filter_url_params returns original params" )
+    deepEqual( filtered_params, original_params, "filter_url_params returns original params" )
     equal( window.location.href, clean_url, "filter_url_params strips off expected params" )


:bug: fix trivial mistake

:bug: fix trivial mistake

--- spec/motion-search-spec.coffee
+++ spec/motion-search-spec.coffee
@@ -310,7 +310,7 @@
           """

       it "Do nothing when search history is empty", ->
-        set cursor [2, 1]
+        set cursor: [2, 1]
         ensure ['/', search: ''], cursor: [2, 1]
         ensure ['?', search: ''], cursor: [2, 1]


update specs

update specs

--- spec/sync-settings-spec.coffee
+++ spec/sync-settings-spec.coffee
@@ -114,16 +114,16 @@
             expect(atom.config.get "some-dummy").toBeTruthy()

       it "overrides keymap.cson", ->
-        original = SyncSettings.fileContent atom.keymap.getUserKeymapPath()
+        original = SyncSettings.fileContent atom.keymaps.getUserKeymapPath()
         run (cb) ->
           SyncSettings.upload cb
         , ->
-          fs.writeFileSync atom.keymap.getUserKeymapPath(), "#{original}\n# modified by sync setting spec"
+          fs.writeFileSync atom.keymaps.getUserKeymapPath(), "#{original}\n# modified by sync setting spec"
           run (cb) ->
             SyncSettings.download cb
           , ->
-            expect(SyncSettings.fileContent(atom.keymap.getUserKeymapPath())).toEqual original
-            fs.writeFileSync atom.keymap.getUserKeymapPath(), original
+            expect(SyncSettings.fileContent(atom.keymaps.getUserKeymapPath())).toEqual original
+            fs.writeFileSync atom.keymaps.getUserKeymapPath(), original

       it "downloads all other files in the gist as well", ->
         atom.config.set 'sync-settings.extraFiles', ['test.tmp', 'test2.tmp']
@@ -135,4 +135,4 @@
           , ->
             for file in atom.config.get 'sync-settings.extraFiles'
               expect(fs.existsSync("#{atom.config.configDirPath}/#{file}")).toBe(true)
-              fs.unlink "#{atom.config.configDirPath}/#{file}"+              fs.unlink "#{atom.config.configDirPath}/#{file}"


service: split activation between install & start #78

--- test.sample.coffee
+++ test.sample.coffee
@@ -7,7 +7,8 @@
   disable_ldap_acl: true
   disable_ldap_index: true
   disable_ldap_user: true
-  disable_service: true
+  disable_service_install: true
+  disable_service_start: true
   disable_db: true
   disable_system_tmpfs: true
   docker: # eg `docker-machine create --driver virtualbox nikita`


Player model is required in the round controller

--- app/index.coffee
+++ app/index.coffee
@@ -26,7 +26,7 @@
     @start()

   start: =>
-    @round = new Round(odds:ODDS, pot_size:POT_SEED, max_bet:MAX_BET, blinds:BLINDS)
+    @round = new Round(odds:ODDS, pot_size:POT_SEED, max_bet:MAX_BET, blinds:BLINDS, player:@player)
     @table.html @round.el

   restart: =>


Cleaned up.

Cleaned up.

--- app/index.coffee
+++ app/index.coffee
@@ -7,7 +7,14 @@
   init_xml_doc "library"
   set_xml_doc "app", generate_xml_from_schema "app"
   get_records ["clients", "groups", "app", "library"]
-  set_mixins [{field: null}, VIEWS_RECORD, VIEWS_RECORD_DISPLAY, VIEWS_STRUCTURAL, VIEWS_TABLE, VIEWS_XREF]
+  set_mixins [
+    field: null
+    VIEWS_RECORD
+    VIEWS_RECORD_DISPLAY
+    VIEWS_STRUCTURAL
+    VIEWS_TABLE
+    VIEWS_XREF
+  ]
   init_menu()
   init_clipboard()
   init_library()


Clarify default placeholder

Clarify default placeholder

--- framer/InputLayer.coffee
+++ framer/InputLayer.coffee
@@ -60,7 +60,7 @@
 				@_setTextProperties()
 				@_setPlaceholder()

-		# If text has been defined, use that, otherwise default to placeholder
+		# Set default placeholder text
 		@_defaultText = @text
 		@_setPlaceholder()


Added theme and preset support

Added theme and preset support

--- src/jquery.rondell.coffee
+++ src/jquery.rondell.coffee
@@ -90,8 +90,11 @@
       @maxItems = numItems

       # Update rondell properties with new options
-      $.extend(true, @, $.rondell.defaults, options or {})
-
+      if options?.preset of $.rondell.presets
+        $.extend(true, @, $.rondell.defaults, $.rondell.presets[options.preset], options or {})
+      else
+        $.extend(true, @, $.rondell.defaults, options or {})
+
       @itemProperties.sizeFocused =
         width: @itemProperties.sizeFocused.width or @itemProperties.size.width * @scaling
         height: @itemProperties.sizeFocused.height or @itemProperties.size.height * @scaling
@@ -407,14 +410,14 @@
         unless item.small
           item.small = true
           if item.icon and not item.resizeable
-            margin = (itemHeight - item.icon.height()) / 2
+            margin = (@itemProperties.size.height - item.icon.height()) / 2
             item.icon.stop(true).animate(
                 marginTop: margin
                 marginBottom: margin
               , fadeTime
             )
       else if item.hidden
-        # Update position even if out of view to
+        # Update position even if out of view to fix animation when reappearing
         item.object.css(
           left: newX
           top: newY
@@ -492,6 +495,8 @@

     # Set container size
     rondell.container = @parent().css(rondell.size)
+
+    rondell.container.addClass("rondellTheme_#{rondell.theme}")

     # Setup each item
     @each ->
@@ -520,5 +525,57 @@

     # Return rondell instance
     rondell
+
+  # Option presets
+  $.rondell.presets =
+    carousel:
+      autoRotation:
+        enabled: true
+        direction: 1
+        once: false
+        delay: 5000
+      funcSize: (layerDiff, rondell) ->
+        (rondell.maxItems / Math.abs(layerDiff)) / rondell.maxItems
+
+    products:
+      repeating: false
+      alwaysShowCaption: true
+      visibleItems: 4
+      itemProperties:
+        delay: 0
+      center:
+        left: 400
+        top: 100
+      funcTop: (layerDiff, rondell) ->
+        0
+      funcDiff: (layerDiff, rondell) ->
+        Math.abs(layerDiff) + 1
+      funcLeft: (layerDiff, rondell) ->
+        rondell.center.left + (layerDiff - 0.5) * rondell.itemProperties.size.width
+      funcOpacity: (layerDist, rondell) ->
+        0.8
+
+    pages:
+      radius:
+        x: 0
+        y: 0
+      scaling: 1
+      visibleItems: 1
+      controls:
+        enabled: false
+      funcTop: (layerDiff, rondell) ->
+          rondell.center.top - rondell.itemProperties.size.height / 2
+      funcLeft: (layerDiff, rondell) ->
+          rondell.center.left + layerDiff * rondell.itemProperties.size.width
+      funcDiff: (layerDiff, rondell) ->
+          Math.abs(layerDiff) + 0.5
+
+    cubic:
+      funcTop: (layerDiff, rondell) ->
+          rondell.center.top - rondell.itemProperties.size.height / 2 + Math.pow(layerDiff / 2, 3) * rondell.radius.x
+      funcLeft: (layerDiff, rondell) ->
+          rondell.center.left - rondell.itemProperties.size.width / 2 + Math.sin(layerDiff) * rondell.radius.x
+      funcSize: (layerDiff, rondell) ->
+          Math.pow((Math.PI - Math.abs(layerDiff)) / Math.PI, 3)

 )(jQuery)


StatisPage: regress getSingleActivityPage (getSingleActivityContent is used instead)

--- server/lib/crawler/staticpages/activity.coffee
+++ server/lib/crawler/staticpages/activity.coffee
@@ -38,87 +38,6 @@

   """

-getSingleActivityPage = ({activityContent, account, models})->
-  {Relationship} = require 'jraphical'
-  getStyles      = require './styleblock'
-  getGraphMeta   = require './graphmeta'
-  analytics      = require './analytics'
-  model          = models.first if models and Array.isArray models
-
-  title  = activityContent?.title
-  graphMeta = getGraphMeta
-    title    : "Post on koding.com by #{activityContent.fullName}"
-    body     : title
-    shareUrl : "#{uri.address}/Activity/#{activityContent.slug}"
-
-  """
-
-  <!DOCTYPE html>
-
-
-    <title>#{title} - Koding</title>
-    #{graphMeta}
-
-
-
-

-      #{getDock()}
-

-
-

-

-
-
-
-

-
-                    Sinan Yasar
-
-                  <time class="kdview">4 days ago</time>
-                  San Francisco
-

-

-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
-                  sem orci, porttitor ut mollis non, vehicula eu purus. Pellentesque massa
-                  odio, tempus cursus eros nec, lacinia aliquam risus. Mauris dignissim,
-                  metus quis aliquam pretium, lectus libero consequat quam, sed congue nulla
-                  arcu ac leo.

-

-

-                  Sinan Yasar
-                   liked this.
-

-

-

-

-
-
-
-

-
-
-
-

-                      Sinan Yasar
-

-
HULAHOP

-

-

-

-
-
-
-

-

-

-
-

-

-    #{analytics()}
-
-
-
-  """

 createCommentNode = (comment)->


[neo4j] added decorator types

[neo4j] added decorator types

--- server/lib/server/graph/decorators/base.coffee
+++ server/lib/server/graph/decorators/base.coffee
@@ -29,7 +29,14 @@
     maps =
       'JStatusUpdate' : 'CStatusActivity'
       'JCodeSnip'     : 'CCodeSnipActivity'
+      #"hede1"         : 'CFollowerBucketActivity'
+      #"hede2"         : 'CNewMemberBucketActivity'
       'JDiscussion'   : 'CDiscussionActivity'
+      #"hede2"         : 'CInstallerBucketActivity'
+      'JTutorial'     : 'CTutorialActivity'
+      'JBlogPost'     : 'CBlogPostActivity'
+
+    return maps

     return maps


add command  #2   * theta --list   * theta --id (object_id) --save out.jpg

--- bin/theta.coffee
+++ bin/theta.coffee
@@ -13,6 +13,9 @@
 parser = new optparse.OptionParser [
   ['-h', '--help', 'show help']
   ['--capture [FILENAME]', 'take a picture']
+  ['--list', 'list pictures']
+  ['--id [Object ID]', 'specify picture by ID']
+  ['--save [FILENAME]', 'save picture']
 ]

 parser.on 'help', ->
@@ -23,10 +26,21 @@
   Usage:
     % theta --capture
     % theta --capture out.jpg
-    % DEBUG=* theta capture
+    % theta --list
+    % theta --id [object_id] --save out.jpg
+    % DEBUG=* theta --capture
   """
   console.log parser.toString()
   process.exit 0
+
+savePicture = (object_id, filename) ->
+  theta.getPicture object_id, (err, picture) ->
+    if err
+      console.error err
+      return process.exit 1
+    fs.writeFile filename, picture, (err) ->
+      console.log "picture (ID:#{object_id}) saved => #{filename}"
+      theta.disconnect()

 parser.on 'capture', (opt, filename) ->
   theta.connect()
@@ -40,12 +54,22 @@
         return theta.disconnect()

   theta.once 'objectAdded', (object_id) ->
-    theta.getPicture object_id, (err, picture) ->
-      if err
-        console.error err
-        return process.exit 1
-      fs.writeFile filename, picture, (err) ->
-        console.log "picture saved => #{filename}"
-        theta.disconnect()
+    savePicture object_id, filename
+
+parser.on 'list', ->
+  theta.connect()
+  theta.once 'connect', ->
+    theta.listPictures (err, object_ids) ->
+      console.log "Object IDs: #{JSON.stringify(object_ids)}"
+      console.log "#{object_ids.length} pictures"
+      theta.disconnect()
+
+parser.on 'id', (opt, object_id) ->
+  config.object_id = object_id
+
+parser.on 'save', (opt, filename) ->
+  theta.connect()
+  theta.once 'connect', ->
+    savePicture config.object_id, filename

 parser.parse process.argv


Simplifying ternary per @arthurschrieber's suggestion

--- src/login7-payload.coffee
+++ src/login7-payload.coffee
@@ -249,7 +249,7 @@

   createNTLMRequest: (options) ->
     domain = escape(options.domain.toUpperCase())
-    workstation = escape( if options.workstation then options.workstation.toUpperCase() else '')
+    workstation = if options.workstation then escape( options.workstation.toUpperCase() ) else ''
     protocol = 'NTLMSSP\u0000'
     BODY_LENGTH = 40
     type1flags = @getNTLMFlags()


ControllerRunner timeout increased (test)

--- server/test/Controller/ControllerRunner.coffee
+++ server/test/Controller/ControllerRunner.coffee
@@ -19,7 +19,7 @@
                 method: 'get'
                 before: ->
                     done()
-            runner.run instance, 1000, ->
+            runner.run instance, 10000, ->
                 expect.fail()

         it 'should call the after() callback if it is defined', (done) ->
@@ -30,7 +30,7 @@
                     callback {}
                 after: ->
                     done()
-            runner.run instance, 1000, ->
+            runner.run instance, 10000, ->
                 expect.fail()

         it 'should call the get() method if the instance has the method property defined as "get"', (done) ->
@@ -39,7 +39,7 @@
                 method: 'get'
                 get: ->
                     done()
-            runner.run instance, 1000, ->
+            runner.run instance, 10000, ->
                 expect.fail()

         it 'should call the callback passing a Timeout exception if the timeout has occured', (done) ->


fix whitespace

fix whitespace

--- services/clsi/app/coffee/ResourceWriter.coffee
+++ services/clsi/app/coffee/ResourceWriter.coffee
@@ -53,7 +53,7 @@

 	storeSyncState: (state, basePath, callback) ->
 		stateFile = Path.join(basePath, @SYNC_STATE_FILE)
-		if not state? # remove the  file if no state passed in
+		if not state? # remove the file if no state passed in
 			logger.log state:state, basePath:basePath, "clearing sync state"
 			fs.unlink stateFile, (err) ->
 				if err? and err.code isnt 'ENOENT'


don't crash the game view if the subscription isn't ready

--- routes.coffee
+++ routes.coffee
@@ -23,7 +23,8 @@

     @route 'game/:_id',
         data: ->
-            CBGA.findGame @params._id
+            if CBGA.Games.find().count() # if games subscription ready
+                CBGA.findGame @params._id

         waitOn: ->
             [


Fix crash on konnector page load, introduced with #560

--- server/controllers/konnectors.coffee
+++ server/controllers/konnectors.coffee
@@ -1,3 +1,4 @@
+path = require 'path'
 Konnector = require '../models/konnector'
 konnectorHash = require '../lib/konnector_hash'
 handleNotification = require '../lib/notification_handler'


fix output, optimise button margin

fix output, optimise button margin

--- lib/build/build-state-view.coffee
+++ lib/build/build-state-view.coffee
@@ -42,11 +42,10 @@
       @div class: 'actions', =>
         @div class: 'pull-left',outlet:"closebutton", =>
           @button 'Close', click: 'destroy', class: 'inline-block-tight btn'
-
+
         @div class: 'pull-right',outlet:"refreshbutton", =>
+          @button 'Cancel', click: 'cancel', class: 'inline-block-tight btn'
           @button 'Refresh', click: 'refreshTaskState', class: 'inline-block-tight btn'
-        @div class: 'pull-right',outlet:"cancelbutton", =>
-          @button 'Cancel', click: 'cancel', class: 'inline-block-tight btn'

   initialize: ->

@@ -88,9 +87,9 @@
       #   @find('.task-state').text job.state
       #   @showState(job)

-    @socket.on 'stdout', (out) => @console.append(out)
+    @socket.on 'stdout', (out) => @console.append(out.content)

-    @socket.on 'stderr', (out) => @console.append(out, 'text-error')
+    @socket.on 'stderr', (out) => @console.append(out.content, 'text-error')

   destroy: ->
     @remove()
@@ -158,4 +157,3 @@
         console.log "task #{@task.id} deleted."
       .catch ->
         console.error "failed to delete task #{@task.id}"
-


Add Spanish

Add Spanish

--- app/index.coffee
+++ app/index.coffee
@@ -46,6 +46,7 @@
     en: label: 'English', strings: enUs
     zh_cn: label: '简体中文', strings: './translations/zh-cn.json'
     zh_tw: label: '繁體中文', strings: './translations/zh-tw.json'
+    es: label: 'Español', strings: './translations/es.json'
     ru: label: 'русский', strings: './translations/ru.json'
     ja: label: '日本語', strings: './translations/ja.json'
     de: label: 'Deutsch', strings: './translations/de.json'


sort global deps

sort global deps

--- src/npm-up.coffee
+++ src/npm-up.coffee
@@ -193,7 +193,7 @@
             obj[pack.name] = pack.version
             obj
         , {}
-        console.log chalk.cyan Object.keys(globalDep).join ' '
+        console.log chalk.cyan Object.keys(globalDep).sort().join ' '

         deps = _.map globalDep, (val, key) ->
             parsePackage key, val, 'g'


tweak some magic numbers

tweak some magic numbers

--- src/player.coffee
+++ src/player.coffee
@@ -13,8 +13,12 @@
     if gameState.testBet
       return next null, gameState.testBet

-    commitment = gameState.players[gameState.in_action]?.bet
+    me = gameState.players[gameState.in_action]
+    commitment = me.bet
     minBet = getMinBet gameState
+    minBet = minBet + Math.round Math.random() * 2
+    maxBet = me.stack * 0.8
+    maxBet = minBet unless maxBet < minBet
     seriousness = minBet / (gameState.small_blind * 2)

     myCards = gameState.players[gameState.in_action].hole_cards
@@ -32,21 +36,25 @@

     if communityCards.length == 0
       if value >= 0.5
-        if gameState.players[0]?.status == 'active'
-          return next null, 1000
+        bet = if gameState.players[0]?.status == 'active'
+          700
         else
-          return next null, 200
+          200
+        bet = maxBet unless bet < maxBet
+        bet = minBet unless bet > minBet
+        return next null, maxBet
       unless value < 0.1
         return next null, minBet

     if value > 0
       bet = (gameState.small_blind * 2) * 2
-      bet = minBet if bet < minBet
+      bet = minBet unless bet > minBet
       if value < 0.5 and seriousness > 2
-        return next null, 0
+        unless minBet < me.bet * 1.2
+          return next null, 0
       return next null, bet

-    if seriousness > value * 5
+    if seriousness > value * 5 and minBet > me.bet * 1.2
       return next null, 0

     next null, minBet


change return value of invalidate

change return value of invalidate

--- src/plugin.coffee
+++ src/plugin.coffee
@@ -209,6 +209,7 @@
   plugin.invalidate = ->
     plugin.emptyTable()
     plugin.populateTable()
+    true

   init options
   plugin


remove rx klass

remove rx klass

--- src/render.coffee
+++ src/render.coffee
@@ -24,7 +24,7 @@
   labels = world.labels()
   label = labels[0]
   level_label = "#{labels.length}_#{label}"
-  klass = world.rx().array [label, level_label]
+  klass = [label, level_label]
   klass.push world.get_local('class') ? ''
   klass.push world.get_local(my.key.kind) ? ''
   klass.push 'selected' if world.get 'selected'
@@ -32,10 +32,9 @@
   klass

 create_attrs = (world, style) ->
-  klass = class_attrs(world)
   attrs = {
     id: "#{world.label()}_#{world.uid}"
-    class: world.bind() -> klass.all()
+    class: world.bind() -> class_attrs(world)
     style: world.bind() -> style
     click: clicker(world)
     # TODO: add touch events that do not mess up mutation


Move the formatters object outside of Rivets.config and expose it on the rivets module.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -34,8 +34,8 @@
       id = args.shift()
       value = if @model[id] instanceof Function
         @model[id] value, args...
-      else if Rivets.config.formatters[id]
-        Rivets.config.formatters[id] value, args...
+      else if Rivets.formatters[id]
+        Rivets.formatters[id] value, args...

     value

@@ -215,12 +215,17 @@
 # Default configuration.
 Rivets.config =
   preloadData: true
-  formatters: {}
+
+# Default formatters. There aren't any.
+Rivets.formatters = {}

 # The rivets module. This is the public interface that gets exported.
 rivets =
   # Exposes the core binding routines that can be extended or stripped down.
   routines: Rivets.routines
+
+  # Exposes the formatters object to be extended.
+  formatters: Rivets.formatters

   # Exposes the rivets configuration options. These can be set manually or from
   # rivets.configure with an object literal.


Rejoin the keypath using '.' instead of ','. [Closes #63]

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -152,7 +152,7 @@
             options.formatters = pipes
             model = @models[splitPath.shift()]
             options.bypass = path.indexOf(":") != -1
-            keypath = splitPath.join()
+            keypath = splitPath.join '.'

             if model
               if dependencies = context.shift()


Add the textNode internal binder for fast lookups when building out textNode bindings. [#181]

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -24,7 +24,7 @@
   # containing view, the DOM node, the type of binding, the model object and the
   # keypath at which to listen for changes.
   constructor: (@view, @el, @type, @key, @keypath, @options = {}) ->
-    unless @binder = @view.binders[type]
+    unless @binder = Rivets.internalBinders[@type] or @view.binders[type]
       for identifier, value of @view.binders
         if identifier isnt '*' and identifier.indexOf('*') isnt -1
           regexp = new RegExp "^#{identifier.replace('*', '.+')}$"
@@ -537,6 +537,15 @@
     else
       el.removeAttribute @type

+# Rivets.internalBinders
+# ----------------------
+
+# Contextually sensitive binders that are used outside of the standard attribute
+# bindings. Put here for fast lookups and to prevent them from being overridden.
+Rivets.internalBinders =
+  textNode: (node, value) ->
+    node.data = value ? ''
+
 # Rivets.config
 # -------------


Rewrite the DOM template parser in Rivets.View#build to include text template parsing. [#181]

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -173,54 +173,71 @@
     skipNodes = []
     bindingRegExp = @bindingRegExp()

+    buildBinding = (node, type, declaration) =>
+      options = {}
+
+      pipes = (pipe.trim() for pipe in declaration.split '|')
+      context = (ctx.trim() for ctx in pipes.shift().split '<')
+      path = context.shift()
+      splitPath = path.split /\.|:/
+      options.formatters = pipes
+      options.bypass = path.indexOf(':') != -1
+
+      if splitPath[0]
+        key = splitPath.shift()
+      else
+        key = null
+        splitPath.shift()
+
+      keypath = splitPath.join '.'
+
+      if dependencies = context.shift()
+        options.dependencies = dependencies.split /\s+/
+
+      @bindings.push new Rivets.Binding @, node, type, key, keypath, options
+
     parse = (node) =>
       unless node in skipNodes
-        for attribute in node.attributes
-          if bindingRegExp.test attribute.name
-            type = attribute.name.replace bindingRegExp, ''
-            unless binder = @binders[type]
-              for identifier, value of @binders
-                if identifier isnt '*' and identifier.indexOf('*') isnt -1
-                  regexp = new RegExp "^#{identifier.replace('*', '.+')}$"
-                  if regexp.test type
-                    binder = value
-
-            binder or= @binders['*']
-
-            if binder.block
-              skipNodes.push n for n in node.getElementsByTagName '*'
-              attributes = [attribute]
-
-        for attribute in attributes or node.attributes
-          if bindingRegExp.test attribute.name
-            options = {}
-            type = attribute.name.replace bindingRegExp, ''
-            pipes = (pipe.trim() for pipe in attribute.value.split '|')
-            context = (ctx.trim() for ctx in pipes.shift().split '<')
-            path = context.shift()
-            splitPath = path.split /\.|:/
-            options.formatters = pipes
-            options.bypass = path.indexOf(':') != -1
-            if splitPath[0]
-              key = splitPath.shift()
-            else
-              key = null
-              splitPath.shift()
-            keypath = splitPath.join '.'
-
-            if not key or @models[key]?
-              if dependencies = context.shift()
-                options.dependencies = dependencies.split /\s+/
-
-              @bindings.push new Rivets.Binding @, node, type, key, keypath, options
-
-        attributes = null if attributes
-
-      return
-
-    for el in @els
-      parse el
-      parse node for node in el.getElementsByTagName '*' when node.attributes?
+        if node.nodeType is Node.TEXT_NODE
+          if (tokens = Rivets.TextTemplateParser.parse node.data).length
+            unless tokens.length is 1 and tokens[0].type is 0
+              [startToken, restTokens...] = tokens
+
+              node.data = startToken.value
+
+              switch startToken.type
+                when 0 then node.data = startToken.value
+                when 1 then buildBinding node, 'textNode', startToken.value
+
+              for token in restTokens
+                node.parentNode.appendChild (text = document.createTextNode token.value)
+                buildBinding text, 'textNode', token.value if token.type is 1
+
+        else if node.attributes?
+          for attribute in node.attributes
+            if bindingRegExp.test attribute.name
+              type = attribute.name.replace bindingRegExp, ''
+              unless binder = @binders[type]
+                for identifier, value of @binders
+                  if identifier isnt '*' and identifier.indexOf('*') isnt -1
+                    regexp = new RegExp "^#{identifier.replace('*', '.+')}$"
+                    if regexp.test type
+                      binder = value
+
+              binder or= @binders['*']
+
+              if binder.block
+                skipNodes.push n for n in node.childNodes
+                attributes = [attribute]
+
+          for attribute in attributes or node.attributes
+            if bindingRegExp.test attribute.name
+              type = attribute.name.replace bindingRegExp, ''
+              buildBinding node, type, attribute.value
+
+        parse childNode for childNode in node.childNodes
+
+    parse el for el in @els

     return


Decide Thread based on msgId/parentMsgId

--- src/server.coffee
+++ src/server.coffee
@@ -46,6 +46,51 @@
         console.log labels
         Thread.find { 'labels': { $in: labels } }, (err, docs) ->
             console.log docs
+
+        # Message ID (Generate one if missing)
+        if message.header['message-id']?
+            msgId = message.header['message-id']['value']
+        else
+            d = new Date()
+            msgId = 'generated-id@example.com'
+
+        # Parent Message ID (could be undefined)
+        parentMsgId = message.header['in-reply-to']['value'] if message.header['in-reply-to']?
+        console.log msgId
+        console.log parentMsgId
+
+        if parentMsgId?
+            #
+            # Child message
+            # 1. Look for parentMessageId in Thread.messageIds
+            # 2. Insert messageId into Thread.messageIds
+            # 3. Append message to Thread.messages
+            # 4. save Thread
+            #
+            Thread.find { 'messageIds': parentMsgId }, (err, docs) ->
+                console.log "Unable to find Thread with containing #{parentMessageId}:#{err}" if err?
+                for doc in docs
+                    do (doc) ->
+                        doc.messageIds.push msgId
+                        doc.messages.push { subject: message.header.subject
+                                           ,sender:  message.header.from.value
+                                           ,body:    message.body }
+                        doc.save()
+        else
+            #
+            # Root message
+            # 1. Create new Thread with labels
+            # 2. Set messages and messageIds
+            # 3. save Thread
+            #
+            thread = new Thread
+                labels: labels
+                messages: [ { subject: message.header.subject
+                             ,sender:  message.header.from.value
+                             ,body:    message.body } ]
+                messageIds: [ msgId ]
+            thread.save()
+
 	    #Thread.where('labels').in(labels).run (err, docs) ->
         #    console.log docs
         #for listener in @listeners
@@ -70,4 +115,4 @@
 class Server.Message
     constructor: (@header, @body) ->

-module.exports = Server+module.exports = Server


removed slash prefix from the included script path which prevented it from loading on vm8

--- app/views.coffee
+++ app/views.coffee
@@ -45,7 +45,7 @@
     script src: "coffee/charts/time-series.js"
     script src: "coffee/charts/time-slider.js"
     script src: 'coffee/utils-purpose.js'
-    script src: "/bubbles.js"
+    script src: "bubbles.js"

@@ -288,7 +288,7 @@
     script src: 'coffee/utils-aiddata.js'
     script src: 'coffee/utils-purpose.js'
     script src: "coffee/charts/bar-hierarchy.js"
-    script src: "/purpose-bars.js"
+    script src: "purpose-bars.js"

   @coffee '/purpose-bars.js': ->


fix issue with ctx.setLineDash that was breaking compat and calendar issues

--- bokehjs/src/coffee/common/properties.coffee
+++ bokehjs/src/coffee/common/properties.coffee
@@ -251,7 +251,7 @@

     @cache_select("dash_offset", i)
     if ctx.getLineDashOffset() != @cache.dash_offset
-      ctx.setLineDash(@cache.dash_offset)
+      ctx.setLineDash([@cache.dash_offset])

 class Fill extends ContextProperties


Move litenTo to the rest of the bind_events

--- bokehjs/src/coffee/models/plots/plot.coffee
+++ bokehjs/src/coffee/models/plots/plot.coffee
@@ -159,8 +159,6 @@
       @levels[level] = {}
     @build_levels()
     @bind_bokeh_events()
-
-    @listenTo(@model.document.solver(), 'layout_update', @request_render)

     toolbar_location = @mget('toolbar_location')
     if toolbar_location?
@@ -405,6 +403,7 @@
     @listenTo(@model, 'change:tools', @build_levels)
     @listenTo(@model, 'change', @request_render)
     @listenTo(@model, 'destroy', () => @remove())
+    @listenTo(@model.document.solver(), 'layout_update', @request_render)

   set_initial_range : () ->
     # check for good values for ranges before setting initial range


comment about performance of the haml binding [ci skip]

--- js/libs/custom-bindings.coffee
+++ js/libs/custom-bindings.coffee
@@ -121,6 +121,7 @@
 # Example: %div{data-bind: "haml: {template: myObservable, args: {}}"}
 #   Renders the HAML.myObservable() template and will re-render when the
 #   observable changes.
+# Careful with performance: don't iterate in a haml binding in place of a knockout foreach loop if the array you're iterating over is an observableArray that is modified with knockout's array manipulation functions
 ko.bindingHandlers.haml =
   init: () =>
     {controlsDescendantBindings: true}


Hide changelog/readme buttons if they don't exist

Instead of having them do nothing when clicked

--- lib/installed-package-view.coffee
+++ lib/installed-package-view.coffee
@@ -48,6 +48,7 @@
     @populate()
     @handleButtonEvents()
     @updateEnablement()
+    @updateFileButtons()
     @checkForUpdate()

   populate: ->
@@ -102,26 +103,30 @@
       false

     @readmeButton.on 'click', =>
-      for child in fs.listSync(@pack.path)
-        extension = path.extname(child)
-        name = path.basename(child, extension)
-        if name.toLowerCase() is 'readme'
-          atom.workspaceView.open(child)
-          break
+      atom.workspaceView.open(@readmePath) if @readmePath
       false

     @changelogButton.on 'click', =>
-      for child in fs.listSync(@pack.path)
-        extension = path.extname(child)
-        name = path.basename(child, extension)
-        if name.toLowerCase() in ['changelog', 'history']
-          atom.workspaceView.open(child)
-          break
+      atom.workspaceView.open(@changelogPath) if @changelogPath
       false

     @openButton.on 'click', =>
       atom.open(pathsToOpen: [@pack.path]) if fs.existsSync(@pack.path)
       false
+
+  updateFileButtons: ->
+    @changelogPath = null
+    @readmePath = null
+
+    for child in fs.listSync(@pack.path)
+      switch path.basename(child, path.extname(child)).toLowerCase()
+        when 'changelog', 'history' then @changelogPath = child
+        when 'readme' then @readmePath = child
+
+      break if @readmePath and @changelogPath
+
+    if @changelogPath then @changelogButton.show() else @changelogButton.hide()
+    if @readmePath then @readmeButton.show() else @readmeButton.hide()

   updateEnablement: ->
     if atom.packages.isPackageDisabled(@pack.name)
@@ -158,6 +163,7 @@
         @updateLink.text('Install')
         @errors.append(new ErrorView(error))
       else
+        @updateFileButtons()
         @updateArea.hide()
         if updatedPackage = atom.packages.getLoadedPackage(@pack.name)
           @pack = updatedPackage


spec: Delay timeout of window.opener test

--- spec/chromium-spec.coffee
+++ spec/chromium-spec.coffee
@@ -51,6 +51,8 @@
       b = window.open "file://#{fixtures}/pages/window-opener-node.html", '', 'node-integration=no,show=no'

   describe 'window.opener', ->
+    @timeout 10000
+
     ipc = remote.require 'ipc'
     url = "file://#{fixtures}/pages/window-opener.html"
     w = null
@@ -61,16 +63,17 @@

     it 'is null for main window', (done) ->
       ipc.on 'opener', (event, opener) ->
-        done(if opener is null then undefined else opener)
+        assert.equal opener, null
+        done()
       BrowserWindow = remote.require 'browser-window'
       w = new BrowserWindow(show: false)
       w.loadUrl url

     it 'is not null for window opened by window.open', (done) ->
-      b = window.open url, '', 'show=no'
       ipc.on 'opener', (event, opener) ->
         b.close()
         done(if opener isnt null then undefined else opener)
+      b = window.open url, '', 'show=no'

   describe 'window.opener.postMessage', ->
     it 'sets source and origin correctly', (done) ->


Fixed a dumb mistake.

Fixed a dumb mistake.

--- lib/langs/uncrustify/index.coffee
+++ lib/langs/uncrustify/index.coffee
@@ -9,7 +9,7 @@
     # console.log(configPath);
     if pathToCommand

-      # Use absolute path
+      # Use path given by user
       cmd = pathToCommand + " -c \"" + configPath + "\" -f \"" + inputPath + "\" -o \"" + outputPath + "\" -l \"" + lang + "\""
     else


Fix incorrect escaping.

Fix incorrect escaping.


Change type -> typedef

Change type -> typedef

--- grammars/protosnirk.cson
+++ grammars/protosnirk.cson
@@ -33,7 +33,7 @@
     }
     'keyword_variable': {
         'name': 'keyword.control.protosnirk'
-        'match': '\\b(let|mut|const|static|provided|get|set|owned|shared|ref|self|type)\\b'
+        'match': '\\b(let|mut|const|static|provided|get|set|owned|shared|ref|self|typedef)\\b'
     }
     'keyword_control': {
         'name': 'keyword.control.protosnirk'


:bug: #137 delay ui instantiation until get ready ( really again )

--- lib/provider/provider-base.coffee
+++ lib/provider/provider-base.coffee
@@ -51,7 +51,7 @@
     # to override

   checkReady: ->
-    Promise.resolve(true)
+    Promise.resolve()

   bindEditor: (editor) ->
     @editorSubscriptions?.dispose()
@@ -80,13 +80,11 @@
     @subscriptions = new CompositeDisposable
     @restoreEditorState = saveEditorState(editor)
     @bindEditor(editor)
-    {query, activate, pending} = @options
-    @ui = new UI(this, {query, activate, pending})
-
-    @checkReady().then (ready) =>
-      if ready
-        @initialize()
-        @ui.start()
+    @checkReady().then =>
+      {query, activate, pending} = @options
+      @ui = new UI(this, {query, activate, pending})
+      @initialize()
+      @ui.start()

   subscribeEditor: (args...) ->
     @editorSubscriptions.add(args...)


minor cleanup

minor cleanup

--- lib/provider/provider-base.coffee
+++ lib/provider/provider-base.coffee
@@ -23,8 +23,8 @@
   reopened: false

   @reopen: ->
-    if state = @destroyedProviderStates.shift()
-      {name, options, state} = state
+    if stateAtDestroyed = @destroyedProviderStates.shift()
+      {name, options, state} = stateAtDestroyed
       @start(name, options, state)

   @start: (name, options, state) ->
@@ -223,12 +223,13 @@
     if itemToOpen?
       pane.activate() if activatePane
       pane.activateItem(itemToOpen)
-      # console.log 'early return'
       return Promise.resolve(itemToOpen)

     openOptions = {pending: true, activatePane: activatePane, activateItem: true}
     if WorkspaceOpenAcceptPaneOption
       openOptions.pane = pane
+      atom.workspace.open(filePath, openOptions)
+
     else
       # NOTE: See #107
       # In Atom v1.16.0 or older, `workspace.open` doesn't allow to specify target pane to open file.
@@ -236,15 +237,14 @@
       # Otherwise, when original pane have item for same path(URI), it opens on CURRENT pane.
       originalActivePane = atom.workspace.getActivePane() unless activatePane
       pane.activate()
-
-    atom.workspace.open(filePath, openOptions).then (editor) ->
-      originalActivePane?.activate()
-      return editor
+      atom.workspace.open(filePath, openOptions).then (editor) ->
+        originalActivePane?.activate()
+        return editor

   confirmed: (item) ->
     @needRestoreEditorState = false
-    {point} = item
     @openFileForItem(item, activatePane: true).then (editor) ->
+      {point} = item
       editor.setCursorBufferPosition(point, autoscroll: false)
       editor.scrollToBufferPosition(point, center: true)
       return editor
@@ -255,10 +255,7 @@
     if item.header?
       item.header
     else
-      if item._lineHeader?
-        item._lineHeader + item.text
-      else
-        item.text
+      (item._lineHeader ? '') + item.text

   # Direct Edit
   # -------------------------


change 'connection' to 'serverClient'

change 'connection' to 'serverClient'

--- lib/authenticate-client.coffee
+++ lib/authenticate-client.coffee
@@ -13,7 +13,7 @@
   unless isClientAllowed clientName, peer

     # if they specified a function to 'reject' the client, use it
-    if @rejectClient? then @rejectClient @connection, clientName, peer
+    if @rejectClient? then @rejectClient @serverClient, clientName, peer

     # otherwise emit an error event on the socket
     else


AccountCredentialList: show overlays under secondary modals

--- client/account/lib/accountcredentiallist.coffee
+++ client/account/lib/accountcredentiallist.coffee
@@ -1,6 +1,7 @@
 kd                        = require 'kd'
 KDListView                = kd.ListView
 KDModalView               = kd.ModalView
+KDOverlayView             = kd.OverlayView
 KDNotificationView        = kd.NotificationView

 showError                 = require 'app/util/showError'
@@ -21,14 +22,24 @@

     credential = item.getData()

-    modal = KDModalView.confirm
-      title       : "Remove credential"
-      description : "Do you want to remove ?"
+    # Since KDModalView.confirm not passing overlay options
+    # to the base class (KDModalView) I had to do this hack
+    # Remove this when issue fixed in Framework ~ GG
+    overlay = new KDOverlayView cssClass: 'second-overlay'
+
+    modal   = KDModalView.confirm
+      title       : 'Remove credential'
+      description : 'Do you want to remove ?'
       ok          :
-        title     : "Yes"
-        callback  : => credential.delete (err) =>
+        title     : 'Yes'
+        callback  :  => credential.delete (err) =>
           modal.destroy()
           @emit 'ItemDeleted', item  unless showError err
+
+    modal.once   'KDObjectWillBeDestroyed', overlay.bound 'destroy'
+    overlay.once 'click',                   modal.bound   'destroy'
+
+    return modal

   shareItem: (item) ->
@@ -67,9 +78,12 @@
             title: "An error occurred"

         new KDModalView
-          title    : credential.title
-          subtitle : credential.provider
-          content  : "
#{cred}
"
+          title          : credential.title
+          overlay        : yes
+          overlayOptions :
+            cssClass     : 'second-overlay'
+          subtitle       : credential.provider
+          content        : "
#{cred}
"

   checkIsBootstrapped: (item) ->


Teams: TMS2277: The message when team owner is trying to leave the team is incorrect

--- client/account/lib/views/leavegroupmodal.coffee
+++ client/account/lib/views/leavegroupmodal.coffee
@@ -30,7 +30,7 @@

     kd.singletons.groupsController.getCurrentGroup().leave (err) =>
       if err
-        new kd.NotificationView title : 'There was a problem, please try again!'
+        new kd.NotificationView title : 'You need to transfer ownership of team before leaving team'
         return @modalTabs.forms.dangerForm.buttons.confirmButton.hideLoader()

       kookies.expire 'clientId'


extra save button at bottom screen + loading message

--- imports/ui/apps/AppEditor.cjsx
+++ imports/ui/apps/AppEditor.cjsx
@@ -54,7 +54,12 @@

Bigboat Compose

         <ComposeAceEditor name='bigboatCompose' compose={@props.bigboatCompose} onChange={@onBigboatComposeChange} />
-
+

+

+          <button onClick={@save} id="submitButtonBottom" type="button" style={marginTop:15} className="btn btn-primary #{@saveButtonDisabledClass()} pull-right">Save</button>
+

     else
-      loading...
+

+        Please, select an application definition from the list on the left.
+