don't disable the check for updated backup, instead disable the startup check

--- lib/sync-settings.coffee
+++ lib/sync-settings.coffee
@@ -28,35 +28,34 @@
       atom.commands.add 'atom-workspace', "sync-settings:view-backup", => @viewBackup()
       atom.commands.add 'atom-workspace', "sync-settings:check-backup", => @checkForUpdate()

-      @checkForUpdate()
+      @checkForUpdate() if atom.config.get('sync-settings.checkForUpdatedBackup')

   deactivate: ->

   serialize: ->

   checkForUpdate: (cb=null) ->
-    if atom.config.get('sync-settings.gistId') and atom.config.get('sync-settings.checkForUpdatedBackup')
-      setImmediate =>
-        console.debug('checking latest backup...')
-        @createClient().gists.get
-          id: atom.config.get 'sync-settings.gistId'
-        , (err, res) =>
-          console.debug(err, res)
-          if err
-            console.error "error while retrieving the gist. does it exists?", err
-            try
-              message = JSON.parse(err.message).message
-              message = 'Gist ID Not Found' if message is 'Not Found'
-            catch SyntaxError
-              message = err.message
-            atom.notifications.addError "sync-settings: Error retrieving your settings. ("+message+")"
-            return cb?()
-
-          console.debug("latest backup version #{res.history[0].version}")
-          if res.history[0].version isnt atom.config.get('sync-settings._lastBackupHash')
-            @notifyNewerBackup()
-
-          cb?()
+    if atom.config.get('sync-settings.gistId')
+      console.debug('checking latest backup...')
+      @createClient().gists.get
+        id: atom.config.get 'sync-settings.gistId'
+      , (err, res) =>
+        console.debug(err, res)
+        if err
+          console.error "error while retrieving the gist. does it exists?", err
+          try
+            message = JSON.parse(err.message).message
+            message = 'Gist ID Not Found' if message is 'Not Found'
+          catch SyntaxError
+            message = err.message
+          atom.notifications.addError "sync-settings: Error retrieving your settings. ("+message+")"
+          return cb?()
+
+        console.debug("latest backup version #{res.history[0].version}")
+        if res.history[0].version isnt atom.config.get('sync-settings._lastBackupHash')
+          @notifyNewerBackup()
+
+        cb?()
     else
       @notifyMissingGistId()


:lipstick:

:lipstick:

--- lib/tag-generator.coffee
+++ lib/tag-generator.coffee
@@ -16,7 +16,7 @@

   generate: ->
     command = "#{require.resolve('ctags')}"
-    args = ["--fields=+KS", "-nf", "-", "#{@path}"]
+    args = ['--fields=+KS', '-nf', '-', @path]
     ctags = ChildProcess.spawn(command, args)
     deferred = $.Deferred()
     output = ''


:minibus: El -> el

:minibus: El -> el

--- lib/views/message.coffee
+++ lib/views/message.coffee
@@ -13,34 +13,34 @@
     atom.project.getPaths().forEach (path) ->
       return if message.filePath.indexOf(path) isnt 0 or displayFile isnt message.filePath # Avoid double replacing
       displayFile = message.filePath.substr( path.length + 1 ) # Remove the trailing slash as well
-    El = document.createElement 'a'
-    El.addEventListener 'click', ->
+    el = document.createElement 'a'
+    el.addEventListener 'click', ->
       Message.onClick message.filePath, message.range
     if message.range
-      El.textContent = "at line #{message.range.start.row + 1} col #{message.range.start.column + 1} "
+      el.textContent = "at line #{message.range.start.row + 1} col #{message.range.start.column + 1} "
     if addPath
-      El.textContent += "in #{displayFile}"
-    El
+      el.textContent += "in #{displayFile}"
+    el

   @renderRibbon: (type) ->
-    El = document.createElement 'span'
-    El.classList.add 'badge'
-    El.classList.add 'badge-flexible'
-    El.classList.add "linter-highlight"
-    El.classList.add type.toLowerCase()
-    El.textContent = type
-    El
+    el = document.createElement 'span'
+    el.classList.add 'badge'
+    el.classList.add 'badge-flexible'
+    el.classList.add "linter-highlight"
+    el.classList.add type.toLowerCase()
+    el.textContent = type
+    el

   @renderMessage: (message) ->
-    El = document.createElement 'span'
+    el = document.createElement 'span'
     if message.html
       if typeof message.html is 'string'
-        El.innerHTML = message.html
+        el.innerHTML = message.html
       else
-        El.appendChild message.html
+        el.appendChild message.html
     else
-      El.textContent = message.text
-    El
+      el.textContent = message.text
+    el

   @onClick: (file, range) ->
     atom.workspace.open(file).then ->


Trim leading and trailing whitespace before format

--- lib/xml-formatter.coffee
+++ lib/xml-formatter.coffee
@@ -46,6 +46,7 @@
     editor = atom.workspace.getActiveTextEditor()
     if editor
       allText = editor.getText()
+      allText = allText.replace /^\s+|\s+$/g, ""
       formatted = ''
       if opts.xml_utf8_header
         regXML = /^<\?xml.+\?>/


CLI/docs making the command to loop through all the source directory files

--- cli/commands/docs.coffee
+++ cli/commands/docs.coffee
@@ -1,12 +1,61 @@
 #
 # Automatic api-documentation generator command
 #
-# Copyright (C) 2011 Nikolay Nemshilov
+# Copyright (C) 2011-2012 Nikolay Nemshilov
 #

+fs        = require('fs')
+path      = require('path')
+cwd       = process.cwd()
+docs      = 'docs/'
+overwrite = false
+
+
+#
+# Generates docs from a coffee-script file
+#
+# @param {String} filename
+#
+generate_docs_for = (filename)->
+  print "   ■ ".yellow + filename
+
+
+#
+# Recoursively loops through the source directory
+#
+# @param {String} dirname
+#
+loop_directory_recursively = (dirname)->
+  for filename in fs.readdirSync("#{cwd}/#{dirname}")
+    if fs.statSync("#{cwd}/#{dirname}/#{filename}").isDirectory()
+      loop_directory_recursively("#{cwd}/#{dirname}/#{filename}")
+    else
+      generate_docs_for("#{dirname}/#{filename}")
+
+
+#
+# Kicks in the documentation generator
+#
 exports.init = (args) ->
-  print "TODO: make me happen!"
+  docs_dir = "#{cwd}/#{docs}"

+  print "Generating the API docs for the project\n".magenta
+
+  sout  (" • ".grey + "Creating the ")+ (docs.yellow) + (" directory ".ljust(80, '.'.grey))
+
+  if path.existsSync(docs_dir)
+    sout " Already exists\n".yellow
+  else
+    fs.mkdirSync(docs_dir, 0o0755); sout  " Ok\n".green
+
+  print " • ".grey + "Generating documentation"
+
+  generate_docs_for('main.coffee') if path.existsSync("#{cwd}/main.coffee")
+  loop_directory_recursively('src')
+
+#
+# Prints out the help
+#
 exports.help = (args) ->
   """
   Automatic API-documentation generator


Add even more fixtures up to Shau Kei Wan

--- server/fixtures.coffee
+++ server/fixtures.coffee
@@ -153,6 +153,56 @@
     image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/ring3.jpg'
     vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/ring+logo.jpg'

+  Presents.insert
+    location: 'Chai Wan'
+    title: 'Apple Gift Cards'
+    description: 'For Apple Products'
+    price: 'HKD 1400'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/present008.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/ring+logo.jpg'
+
+  Presents.insert
+    location: 'North Point'
+    title: 'Windows Gift Cards'
+    description: 'For the love of blue screeens'
+    price: 'HKD 500'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/present009.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/chocologo.png'
+
+  Presents.insert
+    location: 'Quarry Bay'
+    title: 'Disneyland Tickets'
+    description: 'To the happiest place on Earth'
+    price: 'HKD 1400'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/present010.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/ring+logo.jpg'
+
+  Presents.insert
+    location: 'Sai Wan Ho'
+    title: 'Ocean Park Tickets'
+    description: 'To the wettest place on Earth'
+    price: 'HKD 1300'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/present011.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/ring+logo.jpg'
+
+  Presents.insert
+    location: 'Shau Kei Wan'
+    title: 'LV Bag'
+    description: 'Prestigious Bag'
+    price: 'HKD 16300'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/present012.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/ring+logo.jpg'
+
 if Dinners.find().count() is 0

   Dinners.insert
@@ -225,6 +275,56 @@
     image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner3.jpg'
     vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner3+logo.png'

+  Dinners.insert
+    location: 'Chai Wan'
+    title: 'Wooden Dinner'
+    description: 'Dinner in a forest'
+    price: 'HKD 1300'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner8.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner3+logo.png'
+
+  Dinners.insert
+    location: 'North Point'
+    title: 'Timber Dinner'
+    description: 'Timber beavers'
+    price: 'HKD 800'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner9.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner3+logo.png'
+
+  Dinners.insert
+    location: 'Quarry Bay'
+    title: 'The View'
+    description: 'View Eating for two'
+    price: 'HKD 1250'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner010.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner3+logo.png'
+
+  Dinners.insert
+    location: 'Sai Wan Ho'
+    title: 'Fancy Restaurant'
+    description: 'Restaurant for madames'
+    price: 'HKD 2100'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner011.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner1+logo.png'
+
+  Dinners.insert
+    location: 'Shau Kei Wan'
+    title: 'English Restaurant'
+    description: 'Restaurant for madames'
+    price: 'HKD 3100'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner012.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/dinner1+logo.png'
+
 if Anythings.find().count() is 0

   Anythings.insert
@@ -296,3 +396,53 @@
     phonenumber: '1278 7897'
     image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/flower3.jpg'
     vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/flower3+logo.png'
+
+  Anythings.insert
+    location: 'Chai Wan'
+    title: 'Lipstick'
+    description: 'Luscious Red'
+    price: 'HKD 200'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/anything008.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/flower3+logo.png'
+
+  Anythings.insert
+    location: 'North Point'
+    title: 'Pink Heels'
+    description: 'So cute! Waaa~'
+    price: 'HKD 200'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/anything009.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/flower3+logo.png'
+
+  Anythings.insert
+    location: 'Quarry Bay'
+    title: 'Pink Ribbon'
+    description: 'For little girls'
+    price: 'HKD 100'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/anything010.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/flower1+logo.png'
+
+  Anythings.insert
+    location: 'Sai Wan Ho'
+    title: 'Macbook Pro'
+    description: 'Code.'
+    price: 'HKD 9300'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/anything011.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/flower1+logo.png'
+
+  Anythings.insert
+    location: 'Shau Kei Wan'
+    title: 'Running Shoes'
+    description: 'Good Luck Girl'
+    price: 'HKD 1100'
+    vendor: 'Vendor'
+    phonenumber: '1278 7897'
+    image: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/anything012.jpg'
+    vendorimage: 'https://s3-ap-southeast-1.amazonaws.com/pdf-giftimage/flower1+logo.png'


Added language highlighter

Added language highlighter

--- PythonRequestsCodeGenerator.coffee
+++ PythonRequestsCodeGenerator.coffee
@@ -120,5 +120,6 @@
 PythonRequestsCodeGenerator.title =
     "Python (Requests)"
 PythonRequestsCodeGenerator.fileExtension = "py"
+PythonRequestsCodeGenerator.languageHighlighter = "python"

 registerCodeGenerator PythonRequestsCodeGenerator


internal

internal

--- assets/javascripts/models/overview.js.coffee
+++ assets/javascripts/models/overview.js.coffee
@@ -9,17 +9,13 @@
     clientConnections = _(collection.pluck("client_connections")).sum()

     serverConnections = 0
-    nodes = []
-
     for nodesCollection in collection.pluck("nodes")
       serverConnections += nodesCollection.serverConnections()
-      nodes = _(nodes).union(nodesCollection.pluck("serverUrl"))

-    @set "clusters", collection
+    @set "clusters",          collection
     @set "serverConnections", serverConnections
     @set "clientConnections", clientConnections
-    @set "nodes", nodes

   nodes: =>
     _(@get("clusters").pluck("nodes")).chain()


Add some logger notes

Add some logger notes

--- src/nack/logger.coffee
+++ src/nack/logger.coffee
@@ -2,9 +2,13 @@

 {BufferedLineStream} = require './buffered'

+# Eat new lines, nom nom nom!
 chomp = (str) ->
   str.replace /(\n|\r)+$/, ''

+# **Logger** wraps a readable stream and logs it to stdout.
+#
+# Not sure if this belongs in nack, may eventually go away.
 exports.Logger = class Logger
   constructor: (stream, log) ->
     stream = new BufferedLineStream stream
@@ -13,5 +17,6 @@
     stream.on 'data', (line) ->
       log chomp(line)

+# Expose `Logger` helper method
 exports.logStream = (stream) ->
   new Logger stream


Support webpack.*.conf.js files (#576)

Support webpack.*.conf.js files (#576)

--- config.cson
+++ config.cson
@@ -4116,7 +4116,7 @@
 		icon: "webpack"
 		priority: 2
 		match: [
-			[/webpack\.conf(ig)?\./i, "medium-blue"]
+			[/webpack(\.\w+)*\.conf(ig)?\./i, "medium-blue"]
 			[/^webpackfile\.js$/i, "medium-blue"]
 		]


Changed Pan tool fixes #88

Changed Pan tool fixes #88

--- coffee/core/tools.coffee
+++ coffee/core/tools.coffee
@@ -81,6 +81,9 @@
     lc.pan @start.x - x, @start.y - y
     lc.repaint()

+  end: (x, y, lc) ->
+    lc.repaint()
+

 class LC.EyeDropper extends LC.Tool


Update value displays * allow parameter to set how many decimals to be displayed * default to 2 decimal place * hide if .00

--- app/assets/javascripts/income_calculator.coffee
+++ app/assets/javascripts/income_calculator.coffee
@@ -40,8 +40,9 @@
       return
     $('label.error:visible').length == 0

-  formatCurrency: (val) ->
-    return '£' + parseFloat(val).toFixed(2)
+  formatCurrency: (val, dec = 2) ->
+    result = parseFloat(val).toFixed(dec).replace(/\.0{2}/,'');
+    return '£' + result

   showResult = (data) ->
     add_class = 'callout-' + data.type


units: scale MT properly. Closes #1022

units: scale MT properly. Closes #1022

--- app/assets/javascripts/lib/models/metric.coffee
+++ app/assets/javascripts/lib/models/metric.coffee
@@ -18,14 +18,15 @@
       when "MW"
         u = "watt"
         power += 2
-      when "MT" then u = "ton"
+      when "MT"
+        u = "ton"
+        power += 2
       else u = unit
     unit_label = @scaling_in_words(power, u)
     return unit_label

   # scale is the power of thousand
   scale_value: (value, scale) -> value / Math.pow(1000, scale)
-

   calculate_performance : (now, fut) ->
     return null if now == null || fut == null || fut == 0


add a default for cnxId

add a default for cnxId

When viewing as teacher it's null

--- tutor/src/components/book-content-mixin.cjsx
+++ tutor/src/components/book-content-mixin.cjsx
@@ -170,7 +170,7 @@
     @updateCanonicalLink()

   updateCanonicalLink: ->
-    cnxId = @props.cnxId or @getCnxId?()
+    cnxId = @props.cnxId or @getCnxId?() or ''
     # leave versioning out of canonical link
     canonicalCNXId = _.first(cnxId.split('@'))
     {courseId} = Router.currentParams()


participant: Support MSGFLO_DISCOVERY_PERIOD envvar

--- src/participant.coffee
+++ src/participant.coffee
@@ -49,6 +49,9 @@

   return def

+defaultDiscoveryPeriod = 60
+defaultDiscoveryPeriod = parseInt process.env.MSGFLO_DISCOVERY_PERIOD if process.env.MSGFLO_DISCOVERY_PERIOD
+
 class Participant extends EventEmitter
   # @func gets called with inport, , and should return outport, outdata
   constructor: (client, def, @func, role, options={}) ->
@@ -60,7 +63,7 @@
     newrelic = require './newrelic'
     @_transactions = new newrelic.Transactions @definition
     @options = options
-    @options.discoveryPeriod = 60 if not @options.discoveryPeriod # seconds
+    @options.discoveryPeriod = defaultDiscoveryPeriod if not @options.discoveryPeriod # seconds

   start: (callback) ->
     @messaging.connect (err) =>


Fixed call to handle_collisions (again)

Fixed call to handle_collisions (again)

--- game.coffee
+++ game.coffee
@@ -78,7 +78,7 @@
         else if lastpos.direction == Game.NORTH
           player.addToLine(new Position([lastpos.x, lastpos.y - (Game.VELOCITY * elapsed_time)], Game.NORTH, new_time))

-      @handle_collisions(player) for player in @players if Game.use_collisions
+      @handle_collisions(player) if Game.use_collisions for player in @players

       @old_time = new_time


Don't abuse the window.alert()

Don't abuse the window.alert()

--- lib/guard/jasmine/phantomjs/run-jasmine.coffee
+++ lib/guard/jasmine/phantomjs/run-jasmine.coffee
@@ -43,18 +43,17 @@
 # from the specs into a JSON message.
 #
 page.onConsoleMessage = (msg, line, source) ->
-  console.log JSON.stringify({ log: msg, line: line, source: source })
-
-# The console reporter sends its response as alert.
-#
-page.onAlert = (msg) -> console.log msg
+  if /^ConsoleReporter: ([\s\S]*)$/.test(msg)
+    console.log RegExp.$1
+  else
+    console.log JSON.stringify({ log: msg, line: line, source: source })

 # Initialize the page before the JavaScript is run.
 #
 page.onInitialized = ->
   page.evaluate ->

-    # Jasmine Reporter that logs results through an alert.
+    # Jasmine Reporter that logs reporter steps.
     #
     class ConsoleReporter

@@ -72,7 +71,7 @@
       log: (str) ->

       report: (response) ->
-        alert JSON.stringify(response)
+        console.log "ConsoleReporter: #{ JSON.stringify(response) }"

     # Attach the console reporter when the document is ready.
     #


remove basic fields js thingy

remove basic fields js thingy

--- src/app/schemas/edit/edit_schema.js.coffee
+++ src/app/schemas/edit/edit_schema.js.coffee
@@ -1,6 +1,5 @@
 angular.module("case-ui.schemas.edit",[
   "case-ui.schemas.edit.field_set"
-  "case-ui.basic-fields"
   'ui.bootstrap'
   "ui.router"
   "ui.sortable"


specs: Removed console output

specs: Removed console output

--- spec/build-spec.coffee
+++ spec/build-spec.coffee
@@ -26,7 +26,6 @@
     atom.workspace = atom.workspaceView.model
     directory = temp.mkdirSync({ prefix: 'atom-build-spec-' }) + '/';
     atom.project.setPath(directory);
-    console.log directory

     atom.config.set('build.arguments', '')
     atom.config.set('build.environment', '')
@@ -304,6 +303,5 @@

         runs ->
           expect(atom.workspaceView.find('.build')).toExist()
-          console.log atom.workspaceView.find('.build .output').text()
           expect(atom.workspaceView.find('.build .output').text()).not.toMatch /PROJECT_PATH=\{PROJECT_PATH\}/
           expect(atom.workspaceView.find('.build .output').text()).toMatch /FILE_ACTIVE=.*\.atom-build\.json/


syntax fix

syntax fix

--- src/caffeine_script/caffeine_script.coffee
+++ src/caffeine_script/caffeine_script.coffee
@@ -26,7 +26,7 @@
   compile: (code, options = {})->
     Neptune.CaffeineScript.metaCompiler = @
     Neptune.CaffeineScript.compiler = new WrappedCoffeeScript
-    .compi
+
     if [_, metaCode, rest] = code.match CaffeineScript.metaCompiledSectionRegExp
       {compiled:{js}} = Neptune.CaffeineScript.compiler.compile metaCode
       eval js


--- spec/spec-suite.coffee
+++ spec/spec-suite.coffee
@@ -5,6 +5,7 @@
   fsUtils = require 'fs-utils'
   path = require 'path'
   _ = require 'underscore'
+  Git = require 'git'
   require 'spec-helper'

   requireSpecs = (directoryPath, specType) ->
@@ -16,8 +17,10 @@
       spec.specType = specType

   runAllSpecs = ->
-    requireSpecs(window.resourcePath)
-    setSpecType('core')
+    # Only run core specs when resource path is the Atom repository
+    if Git.exists(window.resourcePath)
+      requireSpecs(window.resourcePath)
+      setSpecType('core')

     fixturesPackagesPath = fsUtils.resolveOnLoadPath('fixtures/packages')
     packagePaths = atom.getAvailablePackageNames().map (packageName) -> atom.resolvePackagePath(packageName)


Testing fix for weird _.pluck issue

Testing fix for weird _.pluck issue

--- server/queues/scoring/scoringUtils.coffee
+++ server/queues/scoring/scoringUtils.coffee
@@ -41,7 +41,7 @@
   shouldUpdateLastOpponentSubmitDateForLeague: session.shouldUpdateLastOpponentSubmitDateForLeague

 module.exports.calculateSessionScores = (callback) ->
-  sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID'
+  sessionIDs = _.map @clientResponseObject.sessions, 'sessionID'
   async.map sessionIDs, retrieveOldSessionData.bind(@), (err, oldScores) =>
     if err? then return callback err, {error: 'There was an error retrieving the old scores'}
     try
@@ -151,7 +151,7 @@
   #log.info "Match object computed, result: #{JSON.stringify(matchObject, null, 2)}"
   #log.info 'Writing match object to database...'
   #use bind with async to do the writes
-  sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID'
+  sessionIDs = _.map @clientResponseObject.sessions, 'sessionID'
   async.each sessionIDs, updateMatchesInSession.bind(@, matchObject), (err) ->
     callback err


Make things a bit more coffeescripty

Make things a bit more coffeescripty

--- sdk.coffee
+++ sdk.coffee
@@ -9,26 +9,25 @@
   allowErrors: true

 app.set 'view engine', 'html'
-app.set 'view options', {layout: false}
+app.set 'view options', layout: false
 app.register '.html', swig

 # Hook up static file serving for JS and CSS bits.
 app.use express.static __dirname + '/public'

 # Enable parsing of post body data.
-app.use(express.bodyParser());
+app.use express.bodyParser()

 # Proxy method
 # acts as the makeRequest proxy server.
 # does not work yet.
 proxy = (method, proxyUrl, headers, contentType, postData, callback) ->
   parsedUrl = url.parse proxyUrl
-  requestOptions = {
-    host: parsedUrl.host,
-    port: 80,
-    method: method,
+  requestOptions =
+    host: parsedUrl.host
+    port: 80
+    method: method
     path: parsedUrl.path
-  }

   req = http.request requestOptions, (res) ->
     # TODO: Implement this
@@ -49,7 +48,8 @@
   method = q.httpMethod

   proxy method, proxyUrl, headers, contentType, {}, (proxyResponse) ->
-    res.writeHead 200 { 'content-type': contentType }
+    res.writeHead 200,
+      'content-type': contentType
     res.end proxyResponse

 #app.post "/gadgets/makeRequest", (req, res) ->
@@ -61,5 +61,5 @@
 app.get "/", (req, res) ->
   res.render 'index'

-app.listen(8000)
-console.log("Server started at localhost:8000")
+app.listen 8000
+console.log "Server started at localhost:8000"


Teamwork: Added indexKey variable to track file indexes when a tab changed.

--- client/Teamwork/Views/teamworktabview.coffee
+++ client/Teamwork/Views/teamworktabview.coffee
@@ -92,7 +92,10 @@
   createDashboard: ->
     return @tabView.showPane @dashboard  if @dashboard

-    @dashboard = new KDTabPaneView title: "Dashboard"
+    @dashboard = new KDTabPaneView
+      title    : "Dashboard"
+      indexKey : "dashboard"
+
     dashboard  = new TeamworkDashboard
       delegate : @workspace.getDelegate()

@@ -101,7 +104,11 @@
     @dashboard.once "KDObjectWillBeDestroyed", =>
       @dashboard = null

-    @keysRef.push type: "dashboard"  if @amIHost
+    if @amIHost
+      @keysRef.push
+        type     : "dashboard"
+        indexKey : "dashboard"
+

   openFile: (file, content) ->
     @createEditor file, content
@@ -120,10 +127,11 @@
         indexKey   : indexKey

-  createEditor: (file, content = "", sessionKey) ->
+  createEditor: (file, content = "", sessionKey, indexKey) ->
     isLocal  = not file
     file     = file or FSHelper.createFileFromPath "localfile:/untitled.txt"
-    pane     = new KDTabPaneView title: file.name
+    indexKey = indexKey or @createSessionKey()
+    pane     = new KDTabPaneView { title: file.name, indexKey }
     delegate = @getDelegate()
     editor   = new CollaborativeEditorPane { delegate, sessionKey, file, content }

@@ -133,14 +141,16 @@
         type      : "editor"
         sessionKey: editor.sessionKey
         filePath  : file.path
+        indexKey  : indexKey

     @workspace.addToHistory "$0 opened a new editor"  if isLocal

-  createTerminal: (sessionKey) ->
-    pane         = new KDTabPaneView title: "Terminal"
-    klass        = if @isJoinedASession then SharableClientTerminalPane else SharableTerminalPane
-    delegate     = @getDelegate()
-    terminal     = new klass { delegate, sessionKey }
+  createTerminal: (sessionKey, indexKey) ->
+    indexKey = indexKey or @createSessionKey()
+    pane     = new KDTabPaneView { title: "Terminal", indexKey }
+    klass    = if @isJoinedASession then SharableClientTerminalPane else SharableTerminalPane
+    delegate = @getDelegate()
+    terminal = new klass { delegate, sessionKey }

     @appendPane_ pane, terminal

@@ -148,6 +158,7 @@
       terminal.on "WebtermCreated", =>
         @keysRef.push
           type       : "terminal"
+          indexKey   : indexKey
           sessionKey :
             key      : terminal.remote.session
             host     : KD.nick()
@@ -155,8 +166,9 @@

     @workspace.addToHistory "$0 opened a new terminal"

-  createPreview: (sessionKey) ->
-    pane     = new KDTabPaneView title: "Browser"
+  createPreview: (sessionKey, indexKey) ->
+    indexKey = indexKey or @createSessionKey()
+    pane     = new KDTabPaneView { title: "Browser", indexKey }
     delegate = @getDelegate()
     preview  = new CollaborativePreviewPane { delegate, sessionKey }

@@ -166,6 +178,7 @@
       @keysRef.push
         type      : "preview"
         sessionKey: preview.sessionKey
+        indexKey  : indexKey

     @workspace.addToHistory "$0 opened a new browser"


Added SaveRequested event to EditorPane.

--- client/ide/workspace/panes/editorpane.coffee
+++ client/ide/workspace/panes/editorpane.coffee
@@ -8,6 +8,8 @@
     options.paneType = 'editor'

     super options, data
+
+    @on 'SaveRequested', @bound 'save'

     @createEditor()


Add scoring time to comparison table. PP-135

--- client/src/scripts/scoring-sheet-view.coffee
+++ client/src/scripts/scoring-sheet-view.coffee
@@ -180,14 +180,23 @@

 metricVariables.push
   id: uniqueId()
-  name: 'trainingDuration'
-  caption: 'Time (ms)'
+  name: 'trainingTime'
+  caption: 'Training Time (ms)'
   type: 'integer'
   read: (metric) -> metric.model.training_duration_in_ms
   format: formatInteger
   domain: [ 0, 1 ]
   extent: []

+metricVariables.push
+  id: uniqueId()
+  name: 'scoringTime'
+  caption: 'Scoring Time (ms)'
+  type: 'integer'
+  read: (metric) -> metric.data.duration_in_ms
+  format: formatInteger
+  domain: [ 0, 1 ]
+  extent: []

 metricVariables.push
   id: uniqueId()
@@ -887,7 +896,7 @@
       if _sortAscending then a > b else b > a

     columnVariables = clone _filteredMetricVariables
-    columnVariables.splice.apply columnVariables, flatten [ 7, 0, _filteredInputVariables ]
+    columnVariables.splice.apply columnVariables, flatten [ 8, 0, _filteredInputVariables ]

     headers = map columnVariables, (variable) ->
       tag = if variable isnt _sortByVariable then th else if _sortAscending then thAsc else thDesc


reverted extensionsbox

reverted extensionsbox

--- client/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
+++ client/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
@@ -69,7 +69,7 @@
               defaultValue            : "subdomain"
               radios                  : domainOptions
               change                  : =>
-                {DomainOption, domainName, domains, regYears, extensionSelect} = @forms["Domain Address"].inputs
+                {DomainOption, domainName, domains, regYears} = @forms["Domain Address"].inputs
                 {checkButton, registerButton} = @forms["Domain Address"].buttons
                 actionState = DomainOption.getValue()
                 domainName.setValue ''
@@ -78,7 +78,6 @@
                     @suggestionBox?.show()
                     # domainName.setValidation domainNameValidation
                     domains.hide()
-                    extensionSelect.show()
                     regYears.show()
                     createButton.hide()
                     checkButton.show()
@@ -88,7 +87,6 @@
                     @suggestionBox?.hide()
                     # domainName.setValidation domainNameValidation
                     domains.hide()
-                    extensionSelect.hide()
                     regYears.hide()
                     checkButton.hide()
                     registerButton.hide()
@@ -97,7 +95,6 @@
                   when "subdomain"
                     # domainName.unsetValidation()
                     @suggestionBox?.hide()
-                    extensionSelect.hide()
                     domains.show()
                     regYears.hide()
                     checkButton.hide()
@@ -134,14 +131,6 @@
                       required        : yes
                     messages          :
                       required        : "Please select a parent domain."
-                extensionSelect       :
-                  cssClass            : "domains hidden"
-                  itemClass           : KDSelectBox
-                  validate            :
-                    rules             :
-                      required        : yes
-                    messages          :
-                      required        : "Please select an extension"
             suggestionBox             :
               type                    : "hidden"
     form = @forms["Domain Address"]
@@ -157,22 +146,17 @@
       for domain in userDomains
         if not domain.regYears > 0
           domainList.push {title:".#{domain.domain}", value:domain.domain}
-      {domains, extensionSelect} = @forms["Domain Address"].inputs
+      {domains} = @forms["Domain Address"].inputs
       domains.setSelectOptions domainList
-      availableTldList = ["com", "net", "org"]
-      extensionsList = ({title:'.'+ext, value:ext} for ext in availableTldList)
-      extensionSelect.setSelectOptions extensionsList
-      extensionSelect.setDefaultValue availableTldList[0]

   checkAvailability: ->
     form = @forms["Domain Address"]
-    domain            = form.inputs.domainName.getValue()
-    tld               = form.inputs.extensionSelect.getValue()
+    domainInput       = domainName
+    domainName        = form.inputs.domainName.getValue()
+    splittedDomain    = domainName.match(/([\w\-]+)\.(.*)/)
+    domain            = splittedDomain[1]
+    tld               = splittedDomain[2]
     {createButton, checkButton, registerButton} = form.buttons
-    unless domain.match(/^\w+$/)
-      notifyUser "Please select the extension from the select box"
-      checkButton.hideLoader()
-      return
     @clearSuggestions()
     KD.remote.api.JDomain.getTldPrice tld, (tldPrice) =>
       KD.remote.api.JDomain.isDomainAvailable domain, tld, (avErr, status, price, suggestions) =>
@@ -205,47 +189,46 @@
     {createButton, registerButton} = form.buttons
     @clearSuggestions()

-    {DomainOption, domainName, regYears, domains, extensionSelect} = form.inputs
+    {DomainOption, domainName, regYears, domains} = form.inputs
     domainInput       = domainName
-    domain            = domainName.getValue()
-    tld               = extensionSelect.getValue()
-    unless domain.match(/^\w+$/)
-      notifyUser "Please select the extension from the select box"
-      registerButton.hideLoader()
-      return
+    domainName        = form.inputs.domainName.getValue()
+
     domainOptionValue = DomainOption.getValue()

     if domainOptionValue is 'new'
-        form = @forms["Domain Address"]
-        {createButton, registerButton} = form.buttons
-        paymentController = KD.getSingleton('paymentController')
-        group             = KD.getSingleton("groupsController").getCurrentGroup()
-        registerTheDomain = ->
-            KD.remote.api.JDomain.registerDomain
-              domainName : domainInput.getValue()
-              years      : regYears.getValue()
-            , (err, domain) =>
-              if err
-                warn err
-                console.log err
-                notifyUser "An error occured. Please try again later."
-              else
-                @showSuccess domain
-                domain.setDomainCNameToProxyDomain()
-              registerButton.hideLoader()
-
-        paymentController.getBillingInfo 'user', group, (err, account)->
-          need = err or not account or not account.cardNumber
-          if need
-            paymentController.setBillingInfo 'user', group, (success)->
-              if success
-                registerTheDomain()
-          else
-            registerTheDomain()
+      splittedDomain    = domainName.match(/([\w\-]+)\.(.*)/)
+      domain            = splittedDomain[1]
+      tld               = splittedDomain[2]
+      form = @forms["Domain Address"]
+      {createButton, registerButton} = form.buttons
+      paymentController = KD.getSingleton('paymentController')
+      group             = KD.getSingleton("groupsController").getCurrentGroup()
+      registerTheDomain = ->
+          KD.remote.api.JDomain.registerDomain
+            domainName : domainInput.getValue()
+            years      : regYears.getValue()
+          , (err, domain) =>
+            if err
+              warn err
+              console.log err
+              notifyUser "An error occured. Please try again later."
+            else
+              @showSuccess domain
+              domain.setDomainCNameToProxyDomain()
+            registerButton.hideLoader()
+
+      paymentController.getBillingInfo 'user', group, (err, account)->
+        need = err or not account or not account.cardNumber
+        if need
+          paymentController.setBillingInfo 'user', group, (success)->
+            if success
+              registerTheDomain()
+        else
+          registerTheDomain()

     else if domainOptionValue is 'existing'
-      @createDomain {domain, regYears:0, domainType:'existing'}, (err, domain)=>
+      @createDomain {domainName, regYears:0, domainType:'existing'}, (err, domain)=>
         createButton.hideLoader()
         if err
           warn err
@@ -258,12 +241,12 @@

     else # create a subdomain
       subDomainPattern = /^([a-z0-9]([_\-](?![_\-])|[a-z0-9]){0,60}[a-z0-9]|[a-z0-9])$/
-      unless subDomainPattern.test domain
+      unless subDomainPattern.test domainName
         createButton.hideLoader()
         return notifyUser "#{domainName} is an invalid subdomain."
-      domain = "#{domainName}.#{domains.getValue()}"
-
-      @createDomain {domain, regYears:0, domainType:'subdomain'}, (err, domain)=>
+      domainName = "#{domainName}.#{domains.getValue()}"
+
+      @createDomain {domainName, regYears:0, domainType:'subdomain'}, (err, domain)=>
         createButton.hideLoader()
         if err
           warn err
@@ -314,7 +297,7 @@
   showSuccess:(domain) ->
     @clearSuggestions()
     form            = @forms["Domain Address"]
-    {domainName, extensionSelect}    = form.inputs
+    {domainName}    = form.inputs
     {suggestionBox} = form.fields
     {close, createButton, cancel, another} = form.buttons

@@ -333,7 +316,7 @@
       # partial : "Thank you!
Your domain #{domainName.getValue()} has been added to our database. Please go to your provider's website and add a CNAME record mapping to kontrol.in.koding.com."

       # change this part when registering is there.
-      partial : "Thank you!
Your subdomain #{domainName.getValue()}.#{extensionSelect.getValue()} has been added to our database. You can dismiss this panel and point your new domain to one of your VMs on the settings screen."
+      partial : "Thank you!
Your subdomain #{domainName.getValue()} has been added to our database. You can dismiss this panel and point your new domain to one of your VMs on the settings screen."
       click   : => @reset()


Start server if dcd-client can't connect

--- lib/dcd-provider.coffee
+++ lib/dcd-provider.coffee
@@ -7,13 +7,12 @@
   disableForSelector: ".source.d .comment, .source.d .string"
   inclusionPriority: 1
   excludeLowerPriority: true
-  server: null

   startServer: ->
-    @server = childProcess.spawn("dcd-server", stdio: ["ignore", "ignore", "ignore"])
+    childProcess.spawn("dcd-server", stdio: ["ignore", "ignore", "ignore"])

   stopServer: ->
-    @server.kill()
+    childProcess.spawn("dcd-client", ["--shutdown"])

   getPosition: (request) ->
     ed = request.editor
@@ -74,6 +73,11 @@
           completionType = line
     )

+    client.on("exit", (code) =>
+      if code
+        @startServer()
+    )
+
     client.stdin.setEncoding("utf-8")
     client.stdin.write(text)
     client.stdin.end()


better loging control

better loging control

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -21,8 +21,11 @@

 DEBUG = DEBUG2 = false

-if process.env.DEVEL and not process.env.SMC_TEST
-    DEBUG = true
+if not process.env.SMC_TEST
+    if process.env.SMC_DEBUG or process.env.SMC_DEBUG2 or process.env.DEVEL
+        DEBUG = true
+    if process.env.SMC_DEBUG2
+        DEBUG2 = true

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


hub: last email invite time check with explicit type conversion (to be sure) and a typo

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -2911,7 +2911,7 @@
                                 cb         : (err, when_sent) =>
                                     if err
                                         cb(err)
-                                    else if when_sent - 0 >= misc.days_ago(7) # successfully sent < one week ago -- don't again
+                                    else if when_sent - 0 >= misc.days_ago(7) - 0 # successfully sent < one week ago -- don't again
                                         done = true
                                         cb()
                                     else
@@ -2937,7 +2937,7 @@
                             if mesg.link2proj? # make sure invitees know where to go
                                 base_url = mesg.link2proj.split("/")
                                 base_url = "#{base_url[0]}//#{base_url[2]}"
-                                direct_link = "Then goto project '#{mesg.title}'."
+                                direct_link = "Then go to project '#{mesg.title}'."
                             else # fallback for outdated clients
                                 base_url = 'https://cloud.sagemath.com/'
                                 direct_link = ''


wait 6 minutes before sending version update messages

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -31,6 +31,9 @@

 # How frequently to check if the smc-util/smc-version.js file has changed.
 SMC_VERSION_CHECK_INTERVAL_S = 15
+
+# Don't tell users to upgrade until this many minutes after version file updated.
+SMC_VERSION_CHECK_AGE_M = 6

 # How long to cache a positive authentication for using a project.
 CACHE_PROJECT_AUTH_MS = 1000*60*15    # 15 minutes
@@ -92,6 +95,12 @@
             j = s.indexOf('\n')
             if i != -1 and j != -1
                 ver = parseInt(s.slice(i+1,j))
+            ver_age_s = (new Date() - ver*1000)/1000
+            winston.debug("ver_age_s=#{ver_age_s}, SMC_VERSION_CHECK_AGE_M*60=#{SMC_VERSION_CHECK_AGE_M*60}")
+            if ver_age_s <= SMC_VERSION_CHECK_AGE_M * 60
+                # do nothing - we wait until the version in the file is at least SMC_VERSION_CHECK_AGE_M old
+                return
+
             if not SMC_VERSION?  # initialization on startup
                 SMC_VERSION = ver
             else if ver != SMC_VERSION


:memo: :fire: broken TextEditorElement link

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -19,9 +19,7 @@

 # Essential: This class represents all essential editing state for a single
 # {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
-# If you're manipulating the state of an editor, use this class. If you're
-# interested in the visual appearance of editors, use {TextEditorElement}
-# instead.
+# If you're manipulating the state of an editor, use this class.
 #
 # A single {TextBuffer} can belong to multiple editors. For example, if the
 # same file is open in two different panes, Atom creates a separate editor for


Inline the no-op assert.

Inline the no-op assert.

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -101,7 +101,7 @@
     throw new Error("Must pass a clipboard parameter when constructing TextEditors") unless @clipboard?
     throw new Error("Must pass a grammarRegistry parameter when constructing TextEditors") unless @grammarRegistry?

-    @assert ?= @defaultAssert.bind(this)
+    @assert ?= (condition) -> condition
     @firstVisibleScreenRow ?= 0
     @firstVisibleScreenColumn ?= 0
     @emitter = new Emitter
@@ -3364,9 +3364,6 @@
     @emitter.emit 'will-insert-text', willInsertEvent
     result

-  defaultAssert: (condition, message, callback) ->
-    condition
-
   ###
   Section: Language Mode Delegated Methods
   ###


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_effect.coffee
+++ src/text_effect.coffee
@@ -1,9 +1,5 @@
 ###*
 The Text Effect class provides a method to display moving text onscreen, fading out the text over the effect duration.
-
-By default, images are loaded asynchronously. A proxy object is
-returned immediately. Even though it has a draw method it will not
-draw anything to the screen until the image has been loaded.

 @name TextEffect
 @constructor
@@ -11,8 +7,7 @@

 ###*
 Updates the position of the text based on the effect velocity. Updates the
-alpha based on the elapsed time since the effect creation. If rotationalVelocity
-is provided then the text rotation is updated as well.
+alpha based on the elapsed time since the effect creation.

 @name update
 @methodOf TextEffect#
@@ -20,7 +15,7 @@
 ###

 ###*
-Draws the textShadow text and the text text.
+Draws text from `I.textShadow` `I.text`.

 @name draw
 @methodOf TextEffect#
@@ -36,16 +31,15 @@
     textShadow: Color('black')
     alpha: 1
     rotation: 0
-    rotationalVelocity: 0
     velocity: Point(0, 0)

   self = GameObject(I)

   self.bind "update", ->
     I.alpha = 1 - (I.age / I.duration)
-
+
   self.unbind "draw"
-  self.bind "draw", (canvas) ->
+  self.bind "draw", (canvas) ->
     unless I.color.channels
       I.color = Color(I.color)

@@ -54,17 +48,19 @@

     I.color.a = I.alpha
     I.textShadow.a = I.alpha
+
+    width = canvas.measureText()

     canvas.font I.font
     canvas.drawText
       color: I.textShadow
-      x: 1
+      x: 1 - width / 2
       y: 1
       text: I.text

     canvas.drawText
       color: I.color
-      x: 0
+      x: 0 - width / 2
       y: 0
       text: I.text


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -1,5 +1,6 @@
 GameOver = (I={}) ->
   Object.reverseMerge I,
+
     font: '24px Helvetica'
     fontColor: 'white'


add extends method

add extends method

--- src/utils/Utils.coffee
+++ src/utils/Utils.coffee
@@ -72,3 +72,20 @@
     # @return {String} Browser app name
     @getBrowserName: ->
         navigator.appName
+
+
+    # Extends any class properties or methods simply
+    #
+    # @param {Object} child The one that receives everything
+    # @param {Object} child The one that gives everything
+    # @return {Object} The child, extended
+    @extends: (child, parent) ->
+        for key in parent
+            if parent.hasOwnProperty key
+                child[key] = parent[key]
+        ctor = ->
+            @constructor = child
+
+        ctor.prototype = parent.prototype
+        child.prototype = new ctor()
+        child.__super__ = parent.prototype


EDSC-122: Fix merge problem

EDSC-122: Fix merge problem

--- app/assets/javascripts/models/data/datasets.js.coffee
+++ app/assets/javascripts/models/data/datasets.js.coffee
@@ -10,7 +10,7 @@
                   QueryModel = ns.Query
                   toParam=jQuery.param
                   extend=jQuery.extend
-
+                  ) ->
   class Dataset
     constructor: (jsonData, @query) ->
       @_loadJson(jsonData)


Add title and template attrs on Ember side

--- app/assets/javascripts/models/task_template.js.coffee
+++ app/assets/javascripts/models/task_template.js.coffee
@@ -2,4 +2,5 @@
 ETahi.TaskTemplate = DS.Model.extend
   phaseTemplate: DS.belongsTo('phaseTemplate')
   journalTaskType: DS.belongsTo('journalTaskType')
-  title: Em.computed.oneWay('journalTaskType.title')
+  title: a('string')
+  template: a()


Changed flexi_variants key for Cart.current to flexi_variant_message

--- app/assets/javascripts/sprangular/models/order.coffee
+++ app/assets/javascripts/sprangular/models/order.coffee
@@ -58,7 +58,7 @@
         variant = product.findVariant(item.variant_id)
         break if variant

-      @items.push(variant: variant, flexi_variants: item.flexi_variants, quantity: item.quantity, price: item.price)
+      @items.push(variant: variant, flexi_variant_message: item.flexi_variant_message, quantity: item.quantity, price: item.price)

     @


Update sv.js.coffee

Update sv.js.coffee

Corrected grammatical error

--- app/assets/javascripts/uploadcare/locale/sv.js.coffee
+++ app/assets/javascripts/uploadcare/locale/sv.js.coffee
@@ -45,7 +45,7 @@
           url: 'Direkta länkar'
           camera: 'Kamera'
         file:
-          drag: 'Släpp en fil hit'
+          drag: 'Släpp filen här'
           nodrop: 'Ladda upp filer från din dator'
           cloudsTip: 'Cloud storages<br>och sociala nätverk'
           or: 'eller'


Better name for callback execution function

--- jquery.keystroke.coffee
+++ jquery.keystroke.coffee
@@ -22,14 +22,14 @@
 			activeKeys = activeKeys.filter ( item, index, array ) ->
 				index == $.inArray item, array

-			execute event if checkKeystroke event
+			executeCallback event if checkKeystroke event

 		$document.keyup ( event ) ->
 			# Remove this key from activeKeys if it's in there
 			index = $.inArray event.keyCode, activeKeys
 			activeKeys.splice index, 1 if index > -1

-		execute = ( event ) ->
+		executeCallback = ( event ) ->
 			event.preventDefault() if options.preventDefault
 			callback.apply( options.context, [ event ].concat options.args )


Let moment.js convert multiple dates per page.

--- app/assets/javascripts/articles.js.coffee
+++ app/assets/javascripts/articles.js.coffee
@@ -15,10 +15,11 @@
     $('.search-bar form').submit()

   localize_datetimes = ->
-    date = $(".articles time")
-    old_time = $(date).attr('datetime')
-    new_time = moment(old_time).format('MMM Do YYYY')
-    $(date).html(new_time)
+    dates = $(".articles time")
+    for date in dates
+      old_time = $(date).attr('datetime')
+      new_time = moment(old_time).format('MMM Do YYYY')
+      $(date).html(new_time)

   timeout = null

@@ -31,7 +32,7 @@
       $('textarea#article_content').insertAtCaret('  ')
       evt.preventDefault()

-  $('#article_tag_tokens').tokenInput '/tags.json'
+  $('#article_tag_tokens').tokenInput '/tags.json',
     theme: "facebook"
     prePopulate: $('#article_tag_tokens').data('load')


remove default table sorting

remove default table sorting

--- app/assets/javascripts/data_tables.coffee
+++ app/assets/javascripts/data_tables.coffee
@@ -3,6 +3,7 @@
     paging: false
     searching: false
     bInfo: false
+    order: []

 $(document).on 'page:load', ready
 $(document).ready ready


remove whitespace

remove whitespace

--- server/lib/server/services-presence.coffee
+++ server/lib/server/services-presence.coffee
@@ -62,8 +62,3 @@
     i = genericServices.seq++ % services.length
     res.set "Content-Type", "text/json"
     res.send "\"#{ protocol }//#{ services[i] }\""
-
-
-
-
-


Add support for passing custom headers through Turbolinks.

--- lib/assets/javascripts/turbograft/turbolinks.coffee
+++ lib/assets/javascripts/turbograft/turbolinks.coffee
@@ -94,6 +94,9 @@
     xhr.open 'GET', url.withoutHashForIE10compatibility(), true
     xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml'
     xhr.setRequestHeader 'X-XHR-Referer', referer
+
+    options.headers ?= []
+    xhr.setRequestHeader options.headers[header].key, options.headers[header].value for header of options.headers

     xhr.onload = ->
       if xhr.status >= 500


Add a 10min timeout to Bob Ross

Add a 10min timeout to Bob Ross

--- scripts/nostalgia.coffee
+++ scripts/nostalgia.coffee
@@ -179,6 +179,19 @@
     guessWhoQuote = ''
     res.send "Guess Who game over"

+
+bobRossOnVacation = false
+returnFromVacation = () ->
+    bobRossOnVacation = false
+
+bobRossRespond = (res) ->
+    if !(bobRossOnVacation)
+        res.send "There are no bugs, just happy little accidents!"
+        res.send "http://a2.files.biography.com/image/upload/c_fit,cs_srgb,dpr_1.0,q_80,w_620/MTI1NDg4NTg2MDAxODA1Mjgy.jpg"
+
+        bobRossOnVacation = true
+        setTimeout(returnFromVacation, 600000) # 10min
+
 module.exports = (robot) ->
     robot.respond /Remember that (.*) said "(.*)"/i, rememberPerson

@@ -205,9 +218,7 @@
     robot.respond /guess (.*)/i, guessRespond
     robot.respond /end guess who/i, endGuessWhoRespond

-    robot.hear /.*a bug.*/i, (res) ->
-        res.send "There are no bugs, just happy little accidents!"
-        res.send "http://a2.files.biography.com/image/upload/c_fit,cs_srgb,dpr_1.0,q_80,w_620/MTI1NDg4NTg2MDAxODA1Mjgy.jpg"
+    robot.hear /.*a bug.*/i, bobRossRespond

 `// All code below from http://www.atrixnet.com/bs-generator.html, I take no credit for it
 function randomarray(a) {


add japan to hidemontlysub (#6843)

add japan to hidemontlysub (#6843)

Co-authored-by: smallst_wsl <6c42689ff77371401bf43b7d56b93ac1ca3e92d3@163.com>

--- app/views/core/SubscribeModal.coco.coffee
+++ app/views/core/SubscribeModal.coco.coffee
@@ -30,7 +30,7 @@

     super(options)
     # Path check due to modal refresh when user isn't signed in.
-    @hideMonthlySub = options?.hideMonthlySub or window.location.pathname.startsWith('/parents') or null
+    @hideMonthlySub = options?.hideMonthlySub or window.location.pathname.startsWith('/parents') or me.get('country') is 'japan' or null
     @state = 'standby'
     @couponID = utils.getQueryVariable('coupon')
     @subModalContinue = options.subModalContinue


Added watch for enter pressed in file dialog

--- src/code/views/file-dialog-tab-view.coffee
+++ src/code/views/file-dialog-tab-view.coffee
@@ -106,12 +106,19 @@
         return metadata
     null

+  watchForEnter: (e) ->
+    if e.keyCode is 13 and not @confirmDisabled()
+      @confirm()
+
+  confirmDisabled: ->
+    (@state.filename.length is 0) or (@isOpen and not @state.metadata)
+
   renderWhenAuthorized: ->
-    confirmDisabled = (@state.filename.length is 0) or (@isOpen and not @state.metadata)
+    confirmDisabled = @confirmDisabled()
     removeDisabled = (@state.metadata is null) or (@state.metadata.type is CloudMetadata.Folder)

     (div {className: 'dialogTab'},
-      (input {type: 'text', value: @state.filename, placeholder: (tr "~FILE_DIALOG.FILENAME"), onChange: @filenameChanged})
+      (input {type: 'text', value: @state.filename, placeholder: (tr "~FILE_DIALOG.FILENAME"), onChange: @filenameChanged, onKeyDown: @watchForEnter})
       (FileList {provider: @props.provider, folder: @state.folder, selectedFile: @state.metadata, fileSelected: @fileSelected, list: @state.list, listLoaded: @listLoaded})
       (div {className: 'buttons'},
         (button {onClick: @confirm, disabled: confirmDisabled, className: if confirmDisabled then 'disabled' else ''}, if @isOpen then (tr "~FILE_DIALOG.OPEN") else (tr "~FILE_DIALOG.SAVE"))


lg updated resource path

lg updated resource path

--- src/components/learning-guide-chart.coffee
+++ src/components/learning-guide-chart.coffee
@@ -202,7 +202,7 @@
       .attr('y', point.y - 3)
       .attr('width', 8)
       .attr('height', 6)
-      .attr('xlink:href', PLANE_PATH)
+      .attr('xlink:href', AppConfigStore.urlForResource(PLANE_PATH))
       .attr('transform', "rotate(#{lineAngle}, #{point.x}, #{point.y})")


better descriptions

better descriptions

--- lib/julia-client.coffee
+++ lib/julia-client.coffee
@@ -8,7 +8,10 @@
 semver = require 'semver'

 # TODO: Update me when tagging a new relase:
-INK_VERSION_COMPAT = "^0.11"
+INK_VERSION_COMPAT  = "^0.11"
+
+INK_LINK            = '[`ink`](https://github.com/JunoLab/atom-ink)'
+LANGUAGE_JULIA_LINK = '[`language-julia`](https://github.com/JuliaEditorSupport/atom-language-julia)'

 module.exports = JuliaClient =
   misc:       require './misc'
@@ -43,8 +46,17 @@
         .catch (err) ->
           console.error err
           atom.notifications.addError 'Installing Juno\'s dependencies failed.',
-            detail: 'Juno requires the packages `ink` and `language-julia` to run.
-                     Please install them manually from the settings view.'
+            description:
+              """
+              Juno requires the packages #{INK_LINK} and #{LANGUAGE_JULIA_LINK} to run.
+              Please install them manually via `File -> Settings -> Packages`,
+              or open a terminal and run
+
+                  apm install ink
+                  apm install language-julia
+
+              and then restart Atom.
+              """
             dismissable: true

   enableDeps: (fn) ->
@@ -60,8 +72,8 @@
         atom.notifications.addSuccess "Automatically enabled Juno's dependencies.",
           description:
             """
-            Juno requires the `ink` and `language-julia` packages. We've automatically enabled them
-            for you.
+            Juno requires the #{INK_LINK} and #{LANGUAGE_JULIA_LINK} packages.
+            We've automatically enabled them for you.
             """
           dismissable: true

@@ -70,11 +82,15 @@
           atom.notifications.addWarning "Potentially incompatible `ink` version detected.",
             description:
               """
-              Please make sure to upgrade `ink` to a version compatible with `#{INK_VERSION_COMPAT}`.
+              Please make sure to upgrade #{INK_LINK} to a version compatible with `#{INK_VERSION_COMPAT}`.
               The currently installed version is `#{inkVersion}`.

-              If you cannot install an appropriate version through the `Packages` menu, open a terminal
-              and type in `apm install ink@x.y.z`, where `x.y.z` is satisfies `#{INK_VERSION_COMPAT}`.
+              If you cannot install an appropriate version via via `File -> Settings -> Packages`,
+              open a terminal and run
+
+                  apm install ink@x.y.z
+
+              where `x.y.z` is satisfies `#{INK_VERSION_COMPAT}`.
               """
             dismissable: true

@@ -83,8 +99,14 @@
         atom.notifications.addError "Failed to enable Juno's dependencies.",
           description:
             """
-            Juno requires the `ink` and `language-julia` packages. Please install and enable them
-            and restart Atom.
+            Juno requires the #{INK_LINK} and #{LANGUAGE_JULIA_LINK} packages.
+            Please install them manually via `File -> Settings -> Packages`,
+            or open a terminal and run
+
+                apm install ink
+                apm install language-julia
+
+            and then restart Atom.
             """
           dismissable: true


Strip trailing/leading whitespace from any queried identifier (fixes #21)

--- js/book-aligner.coffee
+++ js/book-aligner.coffee
@@ -353,7 +353,7 @@
     else $('#results').prepend($('<p/>').text('Unsupported identifier string.'))

 find_matches = ->
-  process_identifier($('#identifier_input').val())
+  process_identifier($('#identifier_input').val().trim())
   return false

 $(document).ready ->


Use IA "date" if year is not present

Use IA "date" if year is not present

--- js/book-aligner.coffee
+++ js/book-aligner.coffee
@@ -116,7 +116,7 @@
             '',
             "#{ia_id}",
             data.metadata.title,
-            data.metadata.year || '',
+            data.metadata.year || data.metadata.date || '',
             data.metadata.volume || '',
             data.metadata.imagecount || '',
             oclc_href(data.metadata['oclc-id']) || '',


Fix parsing of Lint error message on windows (#34)

* Fix parsing of Lint error message on windows

On windows the filePath includes `C:` which breaks the current `split(":")` logic which depends on the error message being in the second element of `splittedline`.  This simply removes the filename from `line` before the call to `split`, and then assumes that the error message is in the first element of `splittedline`.  This seems to work on windows.  I have not tested on mac or linux.

* should have being in previous commit

--- lib/linter-julia.coffee
+++ lib/linter-julia.coffee
@@ -23,9 +23,9 @@
   lines = data.split("\n")
   for line in lines
     try
-      splittedline = line.split(":")
-      numbers = splittedline[1].split(" ")
-      if numbers[1] in ignore
+      splittedline = line.replace(filePath+":","").split(":")
+      numbers = splittedline[0].split(" ")
+      if numbers.length < 2 || numbers[1] in ignore
         continue
       row_number = parseInt(numbers[0],10) - 1
       severity = (numbers[1])[0]


:fire: Remove some invalid comments

:fire: Remove some invalid comments

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -8,8 +8,8 @@
   constructor: (@linter) ->
     @messages = []

-    @_bottomTabFile = new BottomTabFile() # consumed in views/bottom-tab-project
-    @_bottomTabProject = new BottomTabProject() # consumed in views/bottom-tab-file
+    @_bottomTabFile = new BottomTabFile()
+    @_bottomTabProject = new BottomTabProject()
     @_panel = new Panel
     @_bottomStatus = new BottomStatus()


Add punctuation to messages of HLint

Add punctuation to messages of HLint

--- lib/linter/hlint.coffee
+++ lib/linter/hlint.coffee
@@ -72,7 +72,7 @@
     matches = @message.match(HLintViolation.MESSAGE_PATTTERN)
     return null unless matches?
     [match, message, foundCode, alternativeCode] = matches
-    HTML = _.escape(message)
+    HTML = _.escape(@punctuate(message))
     HTML += '
'
     HTML += '
Found:
'
     HTML += @formatSnippet(foundCode)
@@ -81,6 +81,12 @@
     HTML += '
'
     HTML

+  punctuate: (string) ->
+    if string.match(/[\.,:;]$/)
+      string
+    else
+      string + '.'
+
   formatSnippet: (snippet) ->
     lines = snippet.split('\n')
     unindentedLines = for line in lines


Different behaviour for minimap click and scroller drag

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -54,9 +54,8 @@

   initialize: ->
     @on 'mousewheel', @onMouseWheel
-    @on 'mousedown', (e) =>
-      @onMouseDown(e)
-      @onDragStart(e)
+    @on 'mousedown', @onMouseDown
+    @miniVisibleArea.on 'mousedown', @onDragStart

     @subscribe @paneView.model.$activeItem, @onActiveItemChanged
     # Fix items movin to another pane.
@@ -264,13 +263,28 @@
   onDragStart: (e) =>
     # Handle left-click only
     return if e.which isnt 1
+    @isClicked = true
+    e.preventDefault()
+    e.stopPropagation()
     @on 'mousemove.visible-area', @onMove

   onMove: (e) =>
     if e.which is 1
-      @onMouseDown e
-    else
+      @onDrag e
+    else
+      @isClicked = false
       @off '.visible-area'
+
+  onDrag: (e) =>
+    # The logic for dragging the scroller is a bit different
+    # than for a single click.
+    # Here we have to compensate for the minimap scroll
+
+    # TODO: need to center the scroller where the mouse has grabbed it
+    y = e.pageY - @offsetTop
+    top = y / @indicator.wrapper.height * @indicator.scroller.height/ @scaleY
+    @editorView.scrollTop(top)
+

   # OTHER PRIVATE METHODS


Do nothing when restored Cursor is out side of changed range

--- lib/misc-command.coffee
+++ lib/misc-command.coffee
@@ -48,8 +48,6 @@
       else
         newRanges.push(newRange)

-    # cursorPositionBeforeMutate = @editor.getCursorBufferPosition()
-
     fn()

     disposable.dispose()
@@ -64,7 +62,8 @@
       @vimState.mark.setRange('[', ']', changedRange)
       if settings.get('setCursorToStartOfChangeOnUndoRedo')
         if isLinewiseRange(changedRange)
-          setBufferRow(@editor.getLastCursor(), changedRange.start.row)
+          if changedRange.containsPoint(cursorPositionAfterMutate)
+            setBufferRow(@editor.getLastCursor(), changedRange.start.row)
         else
           @editor.setCursorBufferPosition(changedRange.start)


ActivitySidebar: Currently comment out.

ActivitySidebar: Currently comment out.

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -49,8 +49,8 @@

     # @appsList = new DockController

-    router
-      .on "RouteInfoHandled",          @bound 'deselectAllItems'
+    # router
+    #   .on "RouteInfoHandled",          @bound 'deselectAllItems'

     notificationController
       .on 'AddedToChannel',            @bound 'accountAddedToChannel'
@@ -399,19 +399,19 @@

   deselectAllItems: ->

-    @selectedItem = null
-
-    @machineTree.deselectAllNodes()
-
-    for own name, {listController} of @sections
-      listController.deselectAllItems()
+    # @selectedItem = null
+
+    # @machineTree.deselectAllNodes()
+
+    # for own name, {listController} of @sections
+    #   listController.deselectAllItems()

   viewAppended: ->

     super

-    @addVMTree()
+    @addMachineList()
     @addFollowedTopics()
     @addConversations()

@@ -558,11 +558,11 @@
             id           : workspace._id
             parentId     : machine.getId()

-    for data in treeData
-
-      node = @machineTree.addNode data
-
-      @mapWorkspaceWithChannel data, node  if data.type is 'workspace'
+    # for data in treeData
+
+    #   node = @machineTree.addNode data
+
+    #   @mapWorkspaceWithChannel data, node  if data.type is 'workspace'

     @emit 'MachinesListed'

@@ -602,43 +602,43 @@

   selectWorkspace: (data) ->

-    data = @latestWorkspaceData or {}  unless data
-    { workspace, machine } = data
-
-    return if not machine or not workspace
-
-    tree = @machineTree
-
-    for key, node of tree.nodes
-      nodeData         = node.getData()
-      isSameMachine    = nodeData.uid is machine.uid
-      isMachineRunning = machine.status.state is Machine.State.Running
-
-      if node.type is 'machine'
-        if isSameMachine
-          if isMachineRunning
-            tree.expand node
-          else
-            tree.selectNode node
-            @watchMachineState workspace, machine
-        else
-          tree.collapse node
-
-      else if node.type is 'workspace'
-        if isMachineRunning and nodeData.machineLabel is (machine.slug or machine.label)
-          slug = nodeData.data?.slug or KD.utils.slugify nodeData.title
-          tree.selectNode node  if slug is workspace.slug
-
-    @latestWorkspaceData = data
-
-    localStorage = KD.getSingleton("localStorageController").storage "IDE"
-
-    minimumDataToStore =
-      machineLabel     : machine.slug or machine.label
-      workspaceSlug    : workspace.slug
-      channelId        : data.channelId
-
-    localStorage.setValue 'LatestWorkspace', minimumDataToStore
+    # data = @latestWorkspaceData or {}  unless data
+    # { workspace, machine } = data
+
+    # return if not machine or not workspace
+
+    # tree = @machineTree
+
+    # for key, node of tree.nodes
+    #   nodeData         = node.getData()
+    #   isSameMachine    = nodeData.uid is machine.uid
+    #   isMachineRunning = machine.status.state is Machine.State.Running
+
+    #   if node.type is 'machine'
+    #     if isSameMachine
+    #       if isMachineRunning
+    #         tree.expand node
+    #       else
+    #         tree.selectNode node
+    #         @watchMachineState workspace, machine
+    #     else
+    #       tree.collapse node
+
+    #   else if node.type is 'workspace'
+    #     if isMachineRunning and nodeData.machineLabel is (machine.slug or machine.label)
+    #       slug = nodeData.data?.slug or KD.utils.slugify nodeData.title
+    #       tree.selectNode node  if slug is workspace.slug
+
+    # @latestWorkspaceData = data
+
+    # localStorage = KD.getSingleton("localStorageController").storage "IDE"
+
+    # minimumDataToStore =
+    #   machineLabel     : machine.slug or machine.label
+    #   workspaceSlug    : workspace.slug
+    #   channelId        : data.channelId
+
+    # localStorage.setValue 'LatestWorkspace', minimumDataToStore

   watchMachineState: (workspace, machine) ->
@@ -674,44 +674,49 @@
       callback machines

-  addVMTree: ->
-
-    @addSubView section = new KDCustomHTMLView
-      tagName  : 'section'
-      cssClass : 'vms'
-
-    @machineTree = new JTreeViewController
-      type                : 'main-nav'
-      treeItemClass       : NavigationItem
-      addListsCollapsed   : yes
-
-    @machineTree.getView().unsetClass 'kdscrollview'
-
-    # This is temporary, we will create a separate TreeViewController
-    # for this and put this logic into there ~ FIXME ~ GG
-    @machineTree.dblClick = (nodeView, event)->
-      machine = nodeView.getData()
-      if machine.status?.state is Machine.State.Running
-        @toggle nodeView
-
-    section.addSubView header = new KDCustomHTMLView
-      tagName  : 'h3'
-      cssClass : 'sidebar-title'
-      partial  : 'VMs'
-      click    : @bound 'handleMoreVMsClick'
-
-    header.addSubView new CustomLinkView
-      cssClass : 'add-icon buy-vm'
-      title    : ' '
-
-    section.addSubView @machineTree.getView()
-
-    @machineTree.on 'NodeWasAdded', (machineItem) =>
-      machineItem.on 'click', @lazyBound 'handleMachineItemClick', machineItem
-
-    if KD.userMachines.length
-    then @listMachines (new Machine machine: (KD.remote.revive machine) for machine in KD.userMachines)
-    else @fetchMachines @bound 'listMachines'
+  # addVMTree: ->
+
+  #   @addSubView section = new KDCustomHTMLView
+  #     tagName  : 'section'
+  #     cssClass : 'vms'
+
+  #   @machineTree = new JTreeViewController
+  #     type                : 'main-nav'
+  #     treeItemClass       : NavigationItem
+  #     addListsCollapsed   : yes
+
+  #   @machineTree.getView().unsetClass 'kdscrollview'
+
+  #   # This is temporary, we will create a separate TreeViewController
+  #   # for this and put this logic into there ~ FIXME ~ GG
+  #   @machineTree.dblClick = (nodeView, event)->
+  #     machine = nodeView.getData()
+  #     if machine.status?.state is Machine.State.Running
+  #       @toggle nodeView
+
+  #   section.addSubView header = new KDCustomHTMLView
+  #     tagName  : 'h3'
+  #     cssClass : 'sidebar-title'
+  #     partial  : 'VMs'
+  #     click    : @bound 'handleMoreVMsClick'
+
+  #   header.addSubView new CustomLinkView
+  #     cssClass : 'add-icon buy-vm'
+  #     title    : ' '
+
+  #   section.addSubView @machineTree.getView()
+
+  #   @machineTree.on 'NodeWasAdded', (machineItem) =>
+  #     machineItem.on 'click', @lazyBound 'handleMachineItemClick', machineItem
+
+  #   if KD.userMachines.length
+  #   then @listMachines (new Machine machine: (KD.remote.revive machine) for machine in KD.userMachines)
+  #   else @fetchMachines @bound 'listMachines'
+
+
+   addMachineList: ->
+
+    @addSubView new SidebarOwnMachinesList

   handleMachineItemClick: (machineItem, event) ->
@@ -734,13 +739,13 @@
       return

-  handleMoreVMsClick: (ev) ->
-
-    KD.utils.stopDOMEvent ev
-
-    if 'add-icon' in ev.target.classList
-    then ComputeHelpers.handleNewMachineRequest()
-    else new MoreVMsModal {}, KD.userMachines
+  # handleMoreVMsClick: (ev) ->
+
+  #   KD.utils.stopDOMEvent ev
+
+  #   if 'add-icon' in ev.target.classList
+  #   then ComputeHelpers.handleNewMachineRequest()
+  #   else new MoreVMsModal {}, KD.userMachines

   handleMoreWorkspacesClick: (data) ->
@@ -951,10 +956,10 @@

   renderMachines: (machines, callback = noop)->

-    @machineTree.removeAllNodes()
-    @listMachines machines
-
-    @selectWorkspace()
+    # @machineTree.removeAllNodes()
+    # @listMachines machines
+
+    # @selectWorkspace()
     callback()

@@ -973,8 +978,8 @@

   removeMachineNode: (machine) ->
-    {nodes}    = @machineTree
-    {jMachine} = machine
-
-    for nodeId, node of nodes when node.data?.jMachine is jMachine
-      @machineTree.removeNode nodeId
+    # {nodes}    = @machineTree
+    # {jMachine} = machine
+
+    # for nodeId, node of nodes when node.data?.jMachine is jMachine
+    #   @machineTree.removeNode nodeId


teams: groupsController.currentGroupIsNew util added

--- client/app/lib/maincontroller/groupscontroller.coffee
+++ client/app/lib/maincontroller/groupscontroller.coffee
@@ -41,6 +41,9 @@
     {stackTemplates} = @getCurrentGroup()

     return stackTemplates?.length > 0
+
+
+  currentGroupIsNew: -> not @getCurrentGroup().stackTemplates

   filterXssAndForwardEvents: (target, events) ->


Repositioned add new VM modal

Repositioned add new VM modal

--- client/app/lib/providers/computeplansmodalpaid.coffee
+++ client/app/lib/providers/computeplansmodalpaid.coffee
@@ -20,6 +20,8 @@
   constructor:(options = {}, data)->

     options.cssClass = 'paid-plan'
+    options.position =
+        top : '78'

     super options, data


fix express routing (nb. express routes aren't defined using regular expressions, even if they look like it)

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -201,7 +201,7 @@
 app.get '/subscribe/?*', renderClientApp
 app.get '/pricing/?*', renderClientApp
 app.get '/signup/?*', renderClientApp
-app.get '/(docs|help)/?*', renderClientApp
+app.get '/help/?*', renderClientApp
 app.get '/', renderClientApp

 # Switch is protected by a specific function.


landing: welcome tab step ignored, directly jumps to join

--- client/landing/site.landing/coffee/core/routes.coffee
+++ client/landing/site.landing/coffee/core/routes.coffee
@@ -34,7 +34,25 @@
     utils.routeIfInvitationTokenIsValid token,
       success   : ({email}) ->
         utils.storeNewTeamData 'invitation', { token, email }
-        kd.singletons.router.handleRoute '/Welcome'
+        teamData = utils.getTeamData()
+
+        go = ->
+          utils.storeNewTeamData 'signup', formData
+          utils.storeNewTeamData 'welcome', yes
+          kd.singletons.router.handleRoute '/Join'
+
+        formData          = {}
+        formData.join     = yes
+        formData.username = email
+        formData.slug     = kd.config.group.slug
+
+        utils.validateEmail { email },
+          success : ->
+            formData.alreadyMember = no
+            go()
+          error   : ->
+            formData.alreadyMember = yes
+            go()
       error     : ({responseText}) ->
         new kd.NotificationView title : responseText
         kd.singletons.router.handleRoute '/'
@@ -81,7 +99,7 @@
     # e.g. team.koding.com/Invitation
     '/Invitation/:token'   : handleInvitation
     '/Account/Oauth'       : handleOauth
-    '/Welcome'             : handleTeamOnboardingRoute.bind this, 'welcome'
+    # '/Welcome'             : handleTeamOnboardingRoute.bind this, 'welcome'
     '/Join'                : handleTeamOnboardingRoute.bind this, 'join'
     '/Authenticate/:step?' : handleTeamOnboardingRoute.bind this, 'stacks'
     '/Congratz'            : handleTeamOnboardingRoute.bind this, 'congratz'


Attach pinchStart event, fix scaleStart reset value to null

--- framer/LayerPinchable.coffee
+++ framer/LayerPinchable.coffee
@@ -5,8 +5,8 @@
 {Gestures} = require "./Gestures"

 Events.PinchStart = "pinchstart"
+Events.Pinch = "pinch"
 Events.PinchEnd = "pinchend"
-Events.Pinch = "pinch"
 Events.RotateStart = "rotatestart"
 Events.Rotate = "rotate"
 Events.RotateEnd = "rotateend"
@@ -38,7 +38,7 @@
 		@_attach()

 	_attach: ->
-		@layer.on(Gestures.PinchStart, @_pinch)
+		@layer.on(Gestures.PinchStart, @_pinchStart)
 		@layer.on(Gestures.Pinch, @_pinch)
 		@layer.on(Gestures.PinchEnd, @_pinchEnd)

@@ -101,8 +101,8 @@
 		super eventName, event, @

 	onPinchStart: (cb) -> @on(Events.PinchStart, cb)
+	onPinch: (cb) -> @on(Events.Pinch, cb)
 	onPinchEnd: (cb) -> @on(Events.PinchEnd, cb)
-	onPinch: (cb) -> @on(Events.Pinch, cb)
 	onRotateStart: (cb) -> @on(Events.RotateStart, cb)
 	onRotate: (cb) -> @on(Events.Rotate, cb)
 	onRotateEnd: (cb) -> @on(Events.RotateEnd, cb)


add bindId completion for ItemTemplate

add bindId completion for ItemTemplate

--- lib/viewProvider.coffee
+++ lib/viewProvider.coffee
@@ -155,7 +155,7 @@
   getAttributeNameCompletions: ({editor, bufferPosition, prefix}) ->
     completions = []
     tag = @getPreviousTag(editor, bufferPosition)
-    tagAttributes = @getTagAttributes(tag).concat(['id','class','platform'])
+    tagAttributes = @getTagAttributes(tag).concat(['id','class','platform','bindId'])

     # tag attributes
     for attribute in tagAttributes when not prefix or firstCharsEqual(attribute, prefix)


Update welcome verbiage

Update welcome verbiage

--- lib/welcome-view.coffee
+++ lib/welcome-view.coffee
@@ -31,12 +31,17 @@
         @section class:'welcome-panel', =>
           @p 'For help, please visit:'
           @ul =>
-            @li => @raw 'The Atom docs contain Guides and the API reference.'
-            @li => @raw 'Discuss Atom at discuss.atom.io.'
+            @li => @raw 'The Atom docs for Guides and the API reference.'
+            @li => @raw 'The Atom forum at discuss.atom.io.'
             @li => @raw 'The Atom Org. This is where all GitHub created Atom packages can be found.'
           @p class: 'welcome-note welcome-metrics', =>
-             @raw 'Note: To help us improve Atom, we anonymously track usage metrics, such as launch time, screen size, used version etc. See the
-             atom/metrics package for details and for instructions on how to disable it.'
+             @raw '''
+                Note: To help us improve Atom, we anonymously
+                track usage metrics, such as launch time, screen size, and current
+                version. See the
+                atom/metrics
+                package for details and instructions to disable it.
+              '''

         @footer class:'welcome-footer', =>
           @raw 'atom.io × '


Basic cache for mustache files

Basic cache for mustache files

--- lib/zen.response.coffee
+++ lib/zen.response.coffee
@@ -7,7 +7,9 @@

 CONST    = require "./zen.constants"

-module.exports =
+response =
+  # -- Context variables -------------------------------------------------------
+  mustaches: {}

   # -- Common responses ---------------------------------------------------------
   run: (body, code = 200, type = "application/json", headers = {}) ->
@@ -58,13 +60,15 @@
     else
       @page "404"

+module.exports = response

-__mustache = (file) ->
+__mustache = (name) ->
   dir = "#{__dirname}/../../../www/mustache/"
-  try
-    fs.readFileSync "#{dir}#{file}.mustache", "utf8"
-  catch error
-    try
-      fs.readFileSync "#{dir}404.mustache", "utf8"
-    catch error
-      "
 404 - Not found
"
+  if response.mustaches[name]
+    response.mustaches[name]
+  else if fs.existsSync file = "#{dir}#{name}.mustache"
+    response.mustaches[name] = fs.readFileSync file, "utf8"
+  else if fs.existsSync file = "#{dir}404.mustache"
+    response.mustaches[name] = fs.readFileSync file, "utf8"
+  else
+    response.mustaches[name] = "
 404 - Not found
"


Only submit in interactive wheel view when the fact_id is known.

--- core/app/backbone/views/interactive_wheel_view.coffee
+++ core/app/backbone/views/interactive_wheel_view.coffee
@@ -6,30 +6,33 @@
       @options.fact.id

   clickOpinionType: (opinionType, e) ->
-    @toggleActiveOpinionType opinionType
-    if opinionType.is_user_opinion
-      $.ajax
-        url: "/facts/" + @getFactId() + "/opinion/" + opinionType.type + "s.json"
-        type: "POST"
-        success: (data) =>
-          @updateTo data.authority, data.opinion_types
-          mp_track "Factlink: Opinionate",
-            factlink: @options.fact.id
-            opinion: opinionType.type
+    fact_id = @getFactId()

-        error: =>
-          @toggleActiveOpinionType opinionType
-          alert "Something went wrong while setting your opinion on the Factlink, please try again"
+    if fact_id?
+      @toggleActiveOpinionType opinionType
+      if opinionType.is_user_opinion
+        $.ajax
+          url: "/facts/" + fact_id + "/opinion/" + opinionType.type + "s.json"
+          type: "POST"
+          success: (data) =>
+            @updateTo data.authority, data.opinion_types
+            mp_track "Factlink: Opinionate",
+              factlink: @options.fact.id
+              opinion: opinionType.type

-    else
-      $.ajax
-        type: "DELETE"
-        url: "/facts/" + @getFactId() + "/opinion.json"
-        success: (data) =>
-          @updateTo data.authority, data.opinion_types
-          mp_track "Factlink: De-opinionate",
-            factlink: @options.fact.id
+          error: =>
+            @toggleActiveOpinionType opinionType
+            alert "Something went wrong while setting your opinion on the Factlink, please try again"

-        error: =>
-          @toggleActiveOpinionType opinionType
-          alert "Something went wrong while removing your opinion on the Factlink, please try again"
+      else
+        $.ajax
+          type: "DELETE"
+          url: "/facts/" + fact_id + "/opinion.json"
+          success: (data) =>
+            @updateTo data.authority, data.opinion_types
+            mp_track "Factlink: De-opinionate",
+              factlink: @options.fact.id
+
+          error: =>
+            @toggleActiveOpinionType opinionType
+            alert "Something went wrong while removing your opinion on the Factlink, please try again"


Ensure branches get correct parent

Ensure branches get correct parent

--- lib/BehaviorTree.coffee
+++ lib/BehaviorTree.coffee
@@ -95,14 +95,14 @@
     callback t, tree if callback
     t

-  onBranch: (orig, branch, callback) =>
+  onBranch: (orig, branch, callback, silent) =>
     log.tree "#{@name or @id} Branch #{branch} from #{orig}"
     if orig.id is 'root'
       throw new Error 'Cannot branch the root node'
     originalNode = @nodes[orig.id]
     unless originalNode
       throw new Error "Source node #{orig.id} not found"
-    id = @registerNode originalNode.promiseSource, branch.name, originalNode.type, callback, false
+    id = @registerNode originalNode.promiseSource, branch.name, originalNode.type, callback, false, branch.source.id
     sourcePath = if orig.source then orig.source.toString() else ''
     branch.treeId = @id
     @nodes[id].choices[sourcePath] = branch
@@ -135,7 +135,7 @@
       seqId = "#{id}_#{seq}"
     seqId

-  registerNode: (source, name, type, callback, autoresolve = true) ->
+  registerNode: (source, name, type, callback, autoresolve = true, onlySource = null) ->
     unless callback
       type = name
       callback = type
@@ -160,7 +160,11 @@
       destinations: []
       branches: []

-    @findSources @nodes[id], autoresolve
+    unless onlySource
+      @findSources @nodes[id], autoresolve
+    else
+      @nodes[id].sources.push @nodes[onlySource]
+      @nodes[onlySource].destinations.push @nodes[id]

     id

@@ -229,11 +233,11 @@
             return
           if state.countFulfilled() > 1
             f = state.getBranches()[0]
-            f.forEach (f, i) =>
+            f.forEach (fulfilled, i) =>
               choice.branch "#{choice.id}_#{i}", (bnode) =>
-                log.values "#{@name or @id} #{choice} resulted via branch #{bnode} in #{typeof f.value} %s", f.value
-                bnode.registerSubleaf f.choice, true, true
-                f.value
+                log.values "#{@name or @id} #{choice} resulted via branch #{bnode} in #{typeof fulfilled.value} %s", fulfilled.value
+                bnode.registerSubleaf fulfilled.choice, true, true
+                fulfilled.value
             return
           if state.countRejected()
             [rejected] = state.getBranches state.rejected


respond to font size changes

respond to font size changes

--- lib/console/view.coffee
+++ lib/console/view.coffee
@@ -9,8 +9,10 @@
     @items.classList.add 'items'
     @appendChild @items

-    @style.fontSize = atom.config.get('editor.fontSize') + 'px'
-    @style.fontFamily = atom.config.get('editor.fontFamily')
+    atom.config.observe 'editor.fontSize', (v) =>
+      @style.fontSize = v + 'px'
+    atom.config.observe 'editor.fontFamily', (v) =>
+      @style.fontFamily = v

     @views = {}
     for view in ['input', 'stdout', 'stderr', 'info', 'result']


Added Windows support for `--flash=@chrome`

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -52,8 +52,18 @@
         chromeFlashPath
       catch
         null
-    when 'linux' then null  # TODO
-    when 'win32' then null  # TODO
+    when 'win32'
+      try
+        chromeVersionsDir = join app.getPath('appData'), 'Local', 'Google', 'Chrome', 'Application'
+        chromeVersions = fs.readdirSync chromeVersionsDir
+        return null if chromeVersions.length is 0 # Broken Chrome...
+        chromeVer = chromeVersions.reduce getNewerVersion
+        chromeFlashPath = join chromeVersionsDir, chromeVer, 'PepperFlash', FILENAME
+        fs.accessSync chromeFlashPath
+        chromeFlashPath
+      catch
+        null
+    else null

 findSystemFlashPath = ->
   # Download/install from: https://get.adobe.com/flashplayer/otherversions/
@@ -65,8 +75,7 @@
         systemFlashPath
       catch
         null
-    when 'linux' then null  # TODO
-    when 'win32' then null  # TODO
+    else null

 getFlashVersion = (path) ->
   dbg.ex.flashLoader?.assert path, 'No flash player path passed in!'
@@ -78,8 +87,7 @@
       plistContent = fs.readFileSync(plistPath, 'utf8')
       match = /<key>CFBundleVersion<\/key>\s*<string>(\d+(?:\.\d+)*)<\/string>/.exec plistContent
       if match and match.length > 1 then match[1] else ''
-    when 'linux' then ''  # TODO
-    when 'win32' then ''  # TODO
+    else ''

 class FlashPlayerVersions
   constructor: (builtInFlashPath, chromeFlashPath, systemFlashPath) ->


:art: introduce onDidActivateMode subscription

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -15,6 +15,15 @@
     {@editor, @editorElement} = @vimState
     @emitter = new Emitter

+    @onDidActivateMode ({mode, submode}) =>
+      @vimState.showCursors()
+      # Update selecter class
+      for mode in supportedModes
+        toggleClassByCondition(@editorElement, "#{mode}-mode", mode is @mode)
+      for submode in supportedSubModes
+        toggleClassByCondition(@editorElement, submode, submode is @submode)
+      @vimState.statusBarManager.update(mode, submode)
+
   eachSelection: (fn) ->
     eachSelection(@editor, fn)

@@ -27,6 +36,9 @@

   onWillDeactivateMode: (fn) ->
     @emitter.on 'will-deactivate-mode', fn
+
+  onDidActivateMode: (fn) ->
+    @emitter.on 'did-activate-mode', fn

   # activate: Public
   #  Use this method to change mode, DONT use other direct method.
@@ -43,7 +55,7 @@

     # Deactivate old mode
     if (mode isnt @mode)
-      @emitter.emit('will-deactivate-mode', {@mode, @submode})
+      @emitter.emit 'will-deactivate-mode', {@mode, @submode}
       @deactivator?.dispose()

     # Activate
@@ -55,15 +67,7 @@

     # Now update mode variables and update CSS selectors.
     [@mode, @submode] = [mode, submode]
-    @vimState.showCursors()
-    @updateModeSelector()
-    @vimState.statusBarManager.update(mode, submode)
-
-  updateModeSelector: ->
-    for mode in supportedModes
-      toggleClassByCondition(@editorElement, "#{mode}-mode", mode is @mode)
-    for submode in supportedSubModes
-      toggleClassByCondition(@editorElement, submode, submode is @submode)
+    @emitter.emit 'did-activate-mode', {@mode, @submode}

   # Normal
   # -------------------------


Don't run Isort if atom.workspace.getActiveEditor() is null

This should fix #2

--- lib/python-isort.coffee
+++ lib/python-isort.coffee
@@ -7,6 +7,8 @@

   checkForPythonContext: ->
     editor = atom.workspace.getActiveEditor()
+    if not editor?
+      return false
     return editor.getGrammar().name == 'Python'

   removeStatusbarItem: ->


fix dependancy

fix dependancy

--- lib/toolbar-view.coffee
+++ lib/toolbar-view.coffee
@@ -1,7 +1,7 @@

 # lib/toolbar-view

-{$, View}   = require 'atom'
+{$, View}   = require 'atom-space-pen-views'
 OmniboxView = require './omnibox-view'
 SubAtom     = require 'sub-atom'


DP loves their tiff files

DP loves their tiff files

--- client/events.coffee
+++ client/events.coffee
@@ -206,7 +206,7 @@
     on_error = (error) -> console.log error
     services = ["COMPUTER", "DROPBOX", "FACEBOOK", "FLICKR", "GOOGLE_DRIVE", "SKYDRIVE", "IMAGE_SEARCH", "INSTAGRAM", "URL", "WEBCAM"]
     filepicker.setKey("AA_3IkmAOQX2Drld5QS9qz")
-    filepicker.pickAndStore {mimetype: 'image/*', services: services},
+    filepicker.pickAndStore {mimetype: 'image/*', services: services, extensions: [".png", ".jpg", ".jpeg"]},
       {location: 'S3'}, on_success, on_error

 Template.event_form.helpers


tweak to autosizebox

tweak to autosizebox

--- client/shared.coffee
+++ client/shared.coffee
@@ -205,18 +205,17 @@

 dom.AUTOSIZEBOX = ->
-  @props.style.resize = if @props.style.width then 'none' else 'horizontal'
+  @props.style.resize = if @props.style.width or @props.cols then 'none' else 'horizontal'
   TEXTAREA
     ref: 'textbox'
     rows: 1
+    cols: @props.cols
     placeholder: @props.placeholder
     onKeyDown: (e) =>@props.onKeyDown?(e)
     onChange: (e) => @props.onChange?(e)
     className: @props.className
     style: @props.style
     value: @props.value
-    resize: false  # infinite loop can be triggered if you resize manually,
-                   # and then trigger auto resize by typing

 resizebox = (target) ->
   target.style.height = null


Set time ago indicators like @markijbema proposed

--- core/app/backbone/components/time_ago.coffee
+++ core/app/backbone/components/time_ago.coffee
@@ -28,15 +28,15 @@
     else if time < 1 * minutes
       "now"
     else if time < 1*hours
-      @showNr(time / minutes, 'min')
+      @showNr(time / minutes, 'm')
     else if time < 1*days
       @showNr(time / hours, 'h')
     else if time < 1*months
       @showNr(time / days , 'd')
     else if time < 1*years
-      @showNr(time / months, 'm')
+      @showNr(time / months, 'mon')
     else
-      @showNr(time / years, 'y')
+      @showNr(time / years, 'yr')

   render: ->
     span = _span {}, @displayTime(@seconds_lapsed())


Moved public method up

Moved public method up

--- core/app/backbone/factlink/tooltip_jq.coffee
+++ core/app/backbone/factlink/tooltip_jq.coffee
@@ -42,6 +42,9 @@
       over: @_hoverTarget true
       out: @_hoverTarget false

+  close: ->
+    ttActions.remove() for id, ttActions of @_instances
+
   _openInstance: ($target) ->
     instId = 'tt' + @_instCounter++
     inTooltip = false
@@ -73,6 +76,3 @@
         ttActions.setTargetHover inTarget
       else if inTarget #target has no tooltip but is hovered
         @_openInstance $target
-
-  close: ->
-    ttActions.remove() for id, ttActions of @_instances


Added an "Open in Browser" button

Added an "Open in Browser" button

--- views/navigation-bar.cjsx
+++ views/navigation-bar.cjsx
@@ -1,6 +1,5 @@
 {$, $$, _, React, ReactBootstrap, FontAwesome, ROOT} = window
 {Grid, Col, Row, Button, ButtonGroup} = ReactBootstrap
-remote = require 'remote'
 webview = $('inner-page webview')

 psLog = (msg) ->
@@ -84,8 +83,8 @@
     currWin.setFullScreen(!currWin.isFullScreen())
   hideWindow: ->
     remote.getCurrentWindow().hide()
-  copyToClipboard: ->
-    webview.copy()
+  openExternal: ->
+    require('electron').shell.openExternal webview.getURL()
   render: ->
     <Grid fluid=true>
       <Row>
@@ -101,6 +100,8 @@
             <Button bsSize='small' bsStyle='primary' onClick={@maximiseWindow} onContextMenu={@resetWindow} title='Zoom/Reset Window'><FontAwesome name='arrows' /></Button>
             <Button bsSize='small' bsStyle='primary' onClick={@toggleFullscreen} title='Toggle Fullscreen'><FontAwesome name='arrows-alt' /></Button>
           </ButtonGroup>
+          <span>　</span>
+          <Button bsSize='small' onClick={@openExternal} title='Open in Browser'><FontAwesome name='external-link' /></Button>
         </Col>
         <Col lg={1} md={1} xs={1}>
           <Button bsSize='small' onClick={@hideWindow} title='Hide Window'><FontAwesome name='times' /></Button>


Dont output invalid JS when source is malformed

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2034,7 +2034,7 @@
 malformed = (expression, message) ->
   # TODO support multiple malformations
   expression.malformed = message
-  (jsMalformed message)
+  jsNoop()

 isWellformed = (expression) ->
   if expression.malformed


Make function substitution even worse

Make function substitution even worse

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -5614,7 +5614,7 @@
       if s.TypeApp
         opName = actualOpName s.op
         if opName is 'Fn'
-          6
+          20
         else
           3
       else


Fix flow manager column sinkage.

Fix flow manager column sinkage.

--- app/assets/javascripts/flow_manager.js.coffee
+++ app/assets/javascripts/flow_manager.js.coffee
@@ -71,8 +71,9 @@
       closeButton = (div {className: 'remove-column glyphicon glyphicon-remove', onClick: @remove})

       (li {className: 'column'},
-        (h2 {}, @props.title),
-        closeButton,
+        (div {className: "column-title"},
+          (h2 {}, @props.title),
+          closeButton),
         (div {className: 'column-content'},
           if @props.paperProfiles.length
             (ul {className: 'cards'},


Unload litePaper from store when Paper is destroyed

--- app/assets/javascripts/models/paper.js.coffee
+++ app/assets/javascripts/models/paper.js.coffee
@@ -37,3 +37,8 @@
   ).property('tasks.content.@each.isMetadataTask')

   allMetadataTasksCompleted: ETahi.computed.all('allMetadataTasks', 'completed', true)
+
+  unloadRecord: ->
+    litePaper = @store.getById('litePaper', @get('id'))
+    @store.unloadRecord(litePaper) if litePaper
+    @_super()


added filters for data-table

added filters for data-table

--- app/assets/javascripts/monster/filters.coffee
+++ app/assets/javascripts/monster/filters.coffee
@@ -9,3 +9,50 @@
         text.replace new RegExp(search, "gi"), "$&"
     else
       text
+
+# delimiter - defaults to $
+# percision - true | false | clean (dont should decimal if .00)
+# seperator - will add seperate for thousands, true == ',' or specific your own.
+app.filter 'data_table_currency', ->
+  (input,opts={})->
+    return unless input
+    input = "#{input}"
+
+    opts.seperator ||= ','
+    opts.delimiter ||= '$'
+    opts.seperator   = ',' if _.isBoolean opts.seperator && opts.seperator is true
+    opts.delimiter   = '$' if _.isBoolean opts.delimiter && opts.delimiter is true
+
+    if opts.percision
+      if opts.percision is 'clean'
+        input = String Math.round(Number(input) * 100) / 100
+        input = input.replace /\.00$/, '' if input.match /\.00$/
+      else if opts.percision is true
+        input = String Math.round(Number(input) * 100) / 100
+
+    input = input.replace /\B(?=(\d{3})+(?!\d))/g, opts.seperator if opts.seperator
+
+    "#{opts.delimiter}#{input}"
+
+
+app.filter 'data_table_date', ->
+  (input,opts={})->
+    return unless input
+    if moment(input).isSame(new Date,'year')
+      moment(input).format 'MMM D'
+    else
+      moment(input).format 'MMM D YYYY'
+
+# 24 hous time
+# kind - smart date (exclude date if implied)
+# format
+app.filter 'data_table_datetime', ->
+  (input,opts={})->
+    return unless input
+    if moment(input).isSame(new Date,'day')       then moment(input).format 'h:mma'
+    else if moment(input).isSame(new Date,'year') then moment(input).format 'MMM-DD h:mma'
+    else
+      moment(input).format('YYYY MMM-DD h:mma')
+
+
+


sv-rollout output, round 2

sv-rollout output, round 2

--- app/assets/javascripts/plugins/borg.js.coffee
+++ app/assets/javascripts/plugins/borg.js.coffee
@@ -157,7 +157,7 @@
         @getTask(log.host).update
           numPending: match[1]
           numLights: match[2]
-      else if match = log.output.match(/\[(\d+)\/(\d+)\].* successfully restarted/i)
+      else if match = log.output.match(/\[(\d+)\/(\d+)\].* (successfully restarted|was not required to restart in time)/i)
         task = @getTask(log.host)
         task.addStatus("up", match[1])
       else if match = log.output.match(/\[(\d+)\/(\d+)\].* did not restart in time/i)


Show all assignment groups in the gradebook

fixes CNVS-23943

Quick patch to handle pagination in the gradebook page of the
assignment groups endpoint, introduced as a performance optimization.

Future commits should extract pagination logic since it's shared with
students (duplicated) in this file.

also make sure we clear the rows array before building
it always, and that we make data loading students
order dependent on already having assignments loaded

TEST PLAN:
1) have more than 10 assignment groups
2) go to the grade book
3) see all your assignment groups

Change-Id: I9b9491b7ae81d01bda2b1f33d9eb98fdb762d655
Reviewed-on: https://gerrit.instructure.com/64963
Reviewed-by: Keith T. Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>
Reviewed-by: Matthew Wheeler <ce71fbafda91d6c8f12582e1837687d5fe592dd9@instructure.com>
Product-Review: Ethan Vizitei <73eeb0b2f65d05a906adf3b21ee1f9f5a2aa3c1c@instructure.com>
QA-Review: Pedro Fajardo <121b528f0c2f2e923ce3103793aca31b9a662ac4@instructure.com>
Tested-by: Ethan Vizitei <73eeb0b2f65d05a906adf3b21ee1f9f5a2aa3c1c@instructure.com>

--- app/coffeescripts/gradebook2/Gradebook.coffee
+++ app/coffeescripts/gradebook2/Gradebook.coffee
@@ -102,15 +102,18 @@
       else
         'students_url'

-      gotAllStudents = $.Deferred().done => @gotAllStudents()
-
-      assignmentGroupsParams = {exclude_descriptions: true}
+      @gotAllAssignmentGroupsPromise = $.Deferred().done => @gotAllAssignmentGroups()
+      gotAllStudents = $.Deferred().done(()=>
+        @gotAllAssignmentGroupsPromise.then(()=> @gotAllStudents())
+      )
+
+      @assignmentGroupsParams = {exclude_descriptions: true}
       if @mgpEnabled && @gradingPeriodToShow && @gradingPeriodToShow != '0' && @gradingPeriodToShow != ''
-        $.extend(assignmentGroupsParams, {grading_period_id: @gradingPeriodToShow})
+        $.extend(@assignmentGroupsParams, {grading_period_id: @gradingPeriodToShow})

       ajax_calls = [
         $.ajaxJSON(@options[enrollmentsUrl], "GET")
-      , $.ajaxJSON(@options.assignment_groups_url, "GET", assignmentGroupsParams, @gotAssignmentGroups)
+      , $.ajaxJSON(@options.assignment_groups_url, "GET", @assignmentGroupsParams, @gotAssignmentGroups)
       , $.ajaxJSON( @options.sections_url, "GET", {}, @gotSections)
       ]

@@ -152,7 +155,7 @@
           gotAllStudents.resolve()

       gotCustomColumns = @getCustomColumns()
-      @gotAllData = $.when(gotCustomColumns, gotAllStudents)
+      @gotAllData = $.when(gotCustomColumns, gotAllStudents, @gotAllAssignmentGroupsPromise)

       @allSubmissionsLoaded.done =>
         for c in @customColumns
@@ -268,7 +271,8 @@
       @getSubmissionsChunks()
       @initHeader()

-    gotAssignmentGroups: (assignmentGroups) =>
+    gotAllAssignmentGroups: () =>
+      assignmentGroups = @allAssignmentGroups;
       @assignmentGroups = {}
       @assignments      = {}

@@ -286,6 +290,39 @@
           @assignments[assignment.id] = assignment
       @postGradesStore.setGradeBookAssignments @assignments

+
+    gotAssignmentGroups: (assignmentGroups, xhr) =>
+      @allAssignmentGroups = [];
+
+      gotAssignmentGroupsChunk = (groups) =>
+        for group in groups
+          @allAssignmentGroups.push(group)
+
+      gotAssignmentGroupsChunk(assignmentGroups)
+
+
+      paginationLinks = xhr.getResponseHeader('Link')
+      lastLink = paginationLinks.match(/<[^>]+>; *rel="last"/)
+      unless lastLink?
+        @gotAllAssignmentGroupsPromise.resolve()
+        return
+      lastPage = lastLink[0].match(/page=(\d+)/)[1]
+      lastPage = parseInt lastPage, 10
+      if lastPage == 1
+        @gotAllAssignmentGroupsPromise.resolve()
+        return
+      agParams = @assignmentGroupsParams
+      fetchAssignmentGroupsChunk = (page) =>
+        $.ajaxJSON @options.assignment_groups_url, "GET", $.extend(agParams, {page})
+      dfds = (fetchAssignmentGroupsChunk(page) for page in [2..lastPage])
+      $.when(dfds...).then (responses...) =>
+        if dfds.length == 1
+          gotAssignmentGroupsChunk(responses[0])
+        else
+          gotAssignmentGroupsChunk(groups) for [groups, x, y] in responses
+        @gotAllAssignmentGroupsPromise.resolve()
+
+
     gotCourse: (course) =>
       @course = course

@@ -317,6 +354,8 @@
         if @mgpEnabled
           gradingPeriods = @indexedGradingPeriods()
           overrides = @indexedOverrides()
+        #empty the array so this is idempotent, you can end up with too many rows
+        @rows.length = 0
         for student_id, student of students
           student.computed_current_score ||= 0
           student.computed_final_score ||= 0