More work on watching / joining ... Fixes #1941, fixes #365. Watched files may now be added and removed, including with --join

--- command.coffee
+++ command.coffee
@@ -50,6 +50,7 @@
 opts         = {}
 sources      = []
 sourceCode   = []
+notSources   = {}
 optionParser = null

 # Run `coffee` by parsing passed options and determining what action to take.
@@ -72,47 +73,40 @@
   process.argv = process.argv.slice(0, 2).concat opts.literals
   process.argv[0] = 'coffee'
   process.execPath = require.main.filename
-  compileScripts()
-
-# Asynchronously read in each CoffeeScript in a list of source files and
-# compile them. If a directory is passed, recursively compile all
-# '.coffee' extension source files in it and all subdirectories.
-compileScripts = ->
-
-  for source, index in sources
-    sourceCode[index] = null
+  for source in sources
+    compilePath source, yes, path.join source

-    base = path.join source
-
-    compile = (source, topLevel) ->
-      path.exists source, (exists) ->
-        if topLevel and not exists and source[-7..] isnt '.coffee'
-          source = sources[sources.indexOf(source)] = "#{source}.coffee"
-          return compile source, topLevel
-        if topLevel and not exists
-          console.error "File not found: #{source}"
-          process.exit 1
-        fs.stat source, (err, stats) ->
+# Compile a path, which could be a script or a directory. If a directory
+# is passed, recursively compile all '.coffee' extension source files in it
+# and all subdirectories.
+compilePath = (source, topLevel, base) ->
+  path.exists source, (exists) ->
+    if topLevel and not exists and source[-7..] isnt '.coffee'
+      source = sources[sources.indexOf(source)] = "#{source}.coffee"
+      return compilePath source, topLevel, base
+    if topLevel and not exists
+      console.error "File not found: #{source}"
+      process.exit 1
+    fs.stat source, (err, stats) ->
+      throw err if err
+      if stats.isDirectory()
+        watchDir source, base if opts.watch
+        fs.readdir source, (err, files) ->
           throw err if err
-          if stats.isDirectory()
-            fs.readdir source, (err, files) ->
-              throw err if err
-              files = files.map (file) -> path.join source, file
-              index = sources.indexOf source
-              sources[index..index] = files
-              sourceCode[index..index] = files.map -> null
-              compile file for file in files
-          else if topLevel or path.extname(source) is '.coffee'
-            fs.readFile source, (err, code) ->
-              throw err if err
-              compileScript(source, code.toString(), base)
-            watch source, base if opts.watch
-          else
-            index = sources.indexOf source
-            sources.splice index, 1
-            sourceCode.splice index, 1
+          files = files.map (file) -> path.join source, file
+          index = sources.indexOf source
+          sources[index..index] = files
+          sourceCode[index..index] = files.map -> null
+          compilePath file, no, base for file in files
+      else if topLevel or path.extname(source) is '.coffee'
+        watch source, base if opts.watch
+        fs.readFile source, (err, code) ->
+          throw err if err
+          compileScript(source, code.toString(), base)
+      else
+        notSources[source] = yes
+        removeSource source

-    compile source, true

 # Compile a single source script, containing the given code, according to the
 # requested options. If evaluating the script directly sets `__filename`,
@@ -126,8 +120,9 @@
     if      o.tokens      then printTokens CoffeeScript.tokens t.input
     else if o.nodes       then printLine CoffeeScript.nodes(t.input).toString().trim()
     else if o.run         then CoffeeScript.run t.input, t.options
-    else if o.join and file isnt o.join
-      compileJoin t.file, t.input
+    else if o.join and t.file isnt o.join
+      sourceCode[sources.indexOf(t.file)] = t.input
+      compileJoin()
     else
       t.output = CoffeeScript.compile t.input, t.options
       CoffeeScript.emit 'success', task
@@ -153,8 +148,7 @@

 # If all of the source files are done being read, concatenate and compile
 # them together.
-compileJoin = (file, code) ->
-  sourceCode[sources.indexOf(file)] = code
+compileJoin = ->
   unless sourceCode.some((code) -> code is null)
     compileScript opts.join, sourceCode.join('\n'), opts.join

@@ -188,28 +182,64 @@
     else if event is 'rename'
       watcher.close()
       setTimeout ->
-        compile()
-        watcher = fs.watch source, callback
+        path.exists source, (exists) ->
+          if exists
+            compile()
+            watcher = fs.watch source, callback
+          else
+            removeSource source
+            if opts.join
+              compileJoin()
+            else
+              fs.unlink outputPath(source, base), (err) ->
+                throw err if err
+            timeLog "removed #{source}"
       , 250
+
+# Watch a directory of files for new additions.
+watchDir = (source, base) ->
+  watcher = fs.watch source, ->
+    fs.readdir source, (err, files) ->
+      throw err if err
+      files = files.map (file) -> path.join source, file
+      for file in files when not notSources[file] and sources.indexOf(file) < 0
+        sources.push file
+        sourceCode.push null
+        compilePath file, no, base
+
+# Remove a file from our source list, and source code cache.
+removeSource = (source) ->
+  index = sources.indexOf source
+  sources.splice index, 1
+  sourceCode.splice index, 1
+
+# Get the corresponding output JavaScript path for a source file.
+outputPath = (source, base) ->
+  filename  = path.basename(source, path.extname(source)) + '.js'
+  srcDir    = path.dirname source
+  baseDir   = if base is '.' then srcDir else srcDir.substring base.length
+  dir       = if opts.output then path.join opts.output, baseDir else srcDir
+  path.join dir, filename

 # Write out a JavaScript source file with the compiled code. By default, files
 # are written out in `cwd` as `.js` files with the same name, but the output
 # directory can be customized with `--output`.
 writeJs = (source, js, base) ->
-  filename  = path.basename(source, path.extname(source)) + '.js'
-  srcDir    = path.dirname source
-  baseDir   = if base is '.' then srcDir else srcDir.substring base.length
-  dir       = if opts.output then path.join opts.output, baseDir else srcDir
-  jsPath    = path.join dir, filename
-  compile   = ->
+  jsPath = outputPath source, base
+  jsDir  = path.dirname jsPath
+  compile = ->
     js = ' ' if js.length <= 0
     fs.writeFile jsPath, js, (err) ->
       if err
         printLine err.message
       else if opts.compile and opts.watch
-        console.log "#{(new Date).toLocaleTimeString()} - compiled #{source}"
-  path.exists dir, (exists) ->
-    if exists then compile() else exec "mkdir -p #{dir}", compile
+        timeLog "compiled #{source}"
+  path.exists jsDir, (exists) ->
+    if exists then compile() else exec "mkdir -p #{jsDir}", compile
+
+# When watching scripts, it's useful to log changes with the timestamp.
+timeLog = (message) ->
+  console.log "#{(new Date).toLocaleTimeString()} - #{message}"

 # Pipe compiled JS through JSLint (requires a working `jsl` command), printing
 # any errors or warnings that arise.
@@ -238,6 +268,8 @@
   o.run         = not (o.compile or o.print or o.lint)
   o.print       = !!  (o.print or (o.eval or o.stdio and o.compile))
   sources       = o.arguments
+  sourceCode[i] = null for source, i in sources
+  return

 # The compile-time options to pass to the CoffeeScript compiler.
 compileOptions = (filename) -> {filename, bare: opts.bare}


Log upgrade failure

Log upgrade failure

--- src/gui/tabs/info/upgrade.coffee
+++ src/gui/tabs/info/upgrade.coffee
@@ -11,6 +11,7 @@

     require('self/upgrade/upgrade') (success)->
       unless success
+        echo "Failed to upgrade"
         return
       dom.body.innerHTML = do require './upgrade.html'
       for z in $ 'a'


Don't shout at our users.

Don't shout at our users.

--- src/mailButler.appsscript.coffee
+++ src/mailButler.appsscript.coffee
@@ -27,7 +27,7 @@
       setupComplete:              'Setup complete!'
       statusOK:                   'Service status: OK'
       scheduleSuccessCloseWindow: 'Your email has been scheduled, you can close this window now!'
-      successCloseWindow:         'You can close this window now!'
+      successCloseWindow:         'You can close this window now.'
       butSomethingWentWrong:      'But something went wrong: $status$'

       # The following keys must not be changed - they are "error" + ErrorCodes.KEY


array_extensions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -8,8 +8,7 @@

 @name compact
 @methodOf Array#
-@type Array
-@returns A new array that contains only the non-null values.
+@returns {Array} A new array that contains only the non-null values.
 ###
 Array::compact = ->
   this.select (element) ->
@@ -87,11 +86,11 @@
 and return a new array containing the results of the invocation.

-   [1.1, 2.2, 3.3, 4.4].invoke("floor")
-=> [1, 2, 3, 4]
-
-   ['hello', 'world', 'cool!'].invoke('substring', 0, 3)
-=> ['hel', 'wor', 'coo']
+[1.1, 2.2, 3.3, 4.4].invoke("floor")
+# => [1, 2, 3, 4]
+
+['hello', 'world', 'cool!'].invoke('substring', 0, 3)
+# => ['hel', 'wor', 'coo']

 @param {String} method The name of the method to invoke.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -2,7 +2,7 @@
 Calculate the average value of an array.

-[null, undefined, 3, 3, undefined, 5].compact()
+[null, undefined, 3, 3, undefined, 5].average()
 # => [3, 3, 5]


Bigger timeout for router tests

Bigger timeout for router tests

--- test/specs/lib/routes.coffee
+++ test/specs/lib/routes.coffee
@@ -1,4 +1,4 @@
-'use strict';
+'use strict'
 sinon = require 'sinon'

 server = null
@@ -10,6 +10,8 @@
 isTrusted         = sinon.spy require '../../example/api/policies/is-trusted'

 describe 'Route provider', ->
+
+  @timeout 5000

   before ->
     mockery.enable


fixed coffeelint errors on workers/social/lib/social/models/payment/validators.coffee

--- workers/social/lib/social/models/payment/validators.coffee
+++ workers/social/lib/social/models/payment/validators.coffee
@@ -3,4 +3,6 @@
   fee: [
     'invalid fee amount'
     (value) -> not value? or value >= 0
-  ]+  ]
+
+


Remove API deprecations

Remove API deprecations

--- lib/snippets-provider.coffee
+++ lib/snippets-provider.coffee
@@ -16,8 +16,8 @@
     for __, snippet of snippets when snippet.prefix.lastIndexOf(prefix, 0) isnt -1
       {
         snippet: snippet
-        word: snippet.prefix
-        prefix: prefix
-        label: snippet.name
         isSnippet: true
+        text: snippet.prefix
+        replacementPrefix: prefix
+        rightLabel: snippet.name
       }


FIX: Limit task feedback to 5 items

FIX: Limit task feedback to 5 items

Current task feedback page showed 10 items on the task page.
This made it hard to find/view the pdf, comments, status
areas.

--- src/app/tasks/task-feedback-assessor-list/task-feedback-assessor-list.coffee
+++ src/app/tasks/task-feedback-assessor-list/task-feedback-assessor-list.coffee
@@ -23,7 +23,7 @@
     else
       $scope.title = "Selected Task"
       $scope.statusFilter = null
-      $scope.taskPageSize = 10
+      $scope.taskPageSize = 5
       category = 'Teacher View - Tasks Tab'

     $scope.search = ""


update MinimapView function name

update MinimapView function name

--- lib/minimap.coffee
+++ lib/minimap.coffee
@@ -20,4 +20,4 @@
   open: ->
     unless @minimapView
       @minimapView = new MinimapView()
-    @minimapView.update()
+    @minimapView.updateActiveStatus()


Layer

Layer

- Position layer using max-width, max-height instead of width / height,
  and in some cases "bottom" instead of "top" and "right" instead of "left".
  This way the Layer can react automatically to content width / height
  changes. ("ng" only). There is now solution for "center" hor/ver yet, this
  is still positioned using "top/left".

--- src/base/Layer/Layer.coffee
+++ src/base/Layer/Layer.coffee
@@ -392,8 +392,8 @@

 		# reset previously set layer dimensions
 		CUI.DOM.setStyle @__layer.DOM,
-			top: 0
-			left: 0
+			top: ""
+			left: ""
 			width: ""
 			height: ""
 			margin: ""
@@ -452,6 +452,7 @@
 					vp.bottom = vp.window_bottom
 					vp.align_vertical = "center"
 					vp.align_horizontal = "center"
+
 				when "n"
 					vp.top = dim_layer.marginTop
 					vp.left = dim_layer.marginLeft
@@ -589,7 +590,6 @@
 				when "center"
 					layer_pos.top = dim_element.viewportCenterTop - layer_pos.height / 2

-
 			if vp.dim_pointer
 				# now align the pointer within the available viewport
 				switch vp.pointer_align_horizontal
@@ -611,7 +611,6 @@
 				pointer_pos.width = vp.dim_pointer.borderBoxWidth
 				pointer_pos.height = vp.dim_pointer.borderBoxHeight
 				pointer_pos.direction = vp.dim_pointer.direction
-

 			# move layer into viewport in case we overlap
 			if layer_pos.top < vp.top
@@ -869,16 +868,50 @@
 			# in "ng" we position this by pure CSS
 			return @

-		# set layer
-		CUI.DOM.setStyle @__layer.DOM,
-			top: vp.layer_pos.top
-			left: vp.layer_pos.left
-			width: Math.ceil(vp.layer_pos.width)
-			height: Math.ceil(vp.layer_pos.height)
-			margin: 0
-			minWidth: minWidth
-			# maxWidth: vp.width + vp.overlap_width
-			# maxHeight: vp.height + vp.overlap_height
+		# console.debug "viewport", vp, left, right, top, bottom
+
+		if CUI.__ng__
+
+			switch vp.align_horizontal
+				when "left"
+					left = vp.layer_pos.left
+				when "right"
+					right = dim_window.width - (vp.layer_pos.left + vp.layer_pos.width)
+				when "center"
+					left = vp.layer_pos.left
+
+			switch vp.align_vertical
+				when "top"
+					top = vp.top
+				when "bottom"
+					bottom = dim_window.height - (vp.layer_pos.top + vp.layer_pos.height)
+				when "center"
+					top = vp.layer_pos.top
+
+			# set layer
+			CUI.DOM.setStyle @__layer.DOM,
+				top: top
+				bottom: bottom
+				left: left
+				right: right
+
+				# width: Math.ceil(vp.layer_pos.width)
+				# height: Math.ceil(vp.layer_pos.height)
+				margin: 0
+				minWidth: minWidth
+				maxWidth: Math.floor(vp.width + vp.overlap_width)
+				maxHeight: Math.floor(vp.height + vp.overlap_height)
+		else
+			# set layer
+			CUI.DOM.setStyle @__layer.DOM,
+				top: vp.layer_pos.top
+				left: vp.layer_pos.left
+				width: Math.ceil(vp.layer_pos.width)
+				height: Math.ceil(vp.layer_pos.height)
+				margin: 0
+				minWidth: minWidth
+				# maxWidth: Math.floor(vp.width + vp.overlap_width)
+				# maxHeight: Math.floor(vp.height + vp.overlap_height)

 		if @__pointer
 			# set pointer


Fix promise error handling at the _applyDomainEventsFromStoreToProjection function

--- src/projection/projection.coffee
+++ src/projection/projection.coffee
@@ -106,7 +106,7 @@

   _parseEventNamesFromProjection: (projection) ->
-    new Promise (resolve, reject) =>
+    new Promise (resolve, reject) ->
       eventNames = []
       for key, value of projection
         if (key.indexOf 'handle') is 0 and (typeof value is 'function')
@@ -116,31 +116,28 @@

   _applyDomainEventsFromStoreToProjection: (projectionId, projection, eventNames, aggregateId) ->
-    new Promise (resolve, reject) =>
-      @_domainEventsApplied[projectionId] = {}
-
-      if aggregateId
-        findEvents = @_context.findDomainEventsByNameAndAggregateId eventNames, aggregateId
-      else
-        findEvents = @_context.findDomainEventsByName eventNames
-
-      findEvents
-      .then (domainEvents) =>
-        if not domainEvents or domainEvents.length is 0
-          return resolve eventNames
-
-        @_eventric.eachSeries domainEvents, (domainEvent, next) =>
+    @_domainEventsApplied[projectionId] = {}
+
+    if aggregateId
+      findEvents = @_context.findDomainEventsByNameAndAggregateId eventNames, aggregateId
+    else
+      findEvents = @_context.findDomainEventsByName eventNames
+
+    findEvents
+    .then (domainEvents) =>
+      if not domainEvents or domainEvents.length is 0
+        return eventNames
+
+      promise = new Promise (resolve) -> resolve()
+      domainEvents.forEach (domainEvent) =>
+        promise = promise.then =>
           @_applyDomainEventToProjection domainEvent, projection
           .then =>
             @_domainEventsApplied[projectionId][domainEvent.id] = true
-            next()
-
-        , (err) ->
-          return reject err if err
-          resolve eventNames
-
-      .catch (err) ->
-        reject err
+      promise.then ->
+        return eventNames
+
+      return promise

   _subscribeProjectionToDomainEvents: (projectionId, projectionName, projection, eventNames, aggregateId) ->
@@ -209,4 +206,4 @@
     Promise.all unsubscribePromises

-module.exports = Projection+module.exports = Projection


Fix JS errors around excerpt updating with new files

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -252,6 +252,7 @@
         @segment = new Segment(asset.segment)
         select(this) if `asset.id == target.asset`
         $scope.asset = asset if $scope.current == this
+        @updateExcerpt()
         return

       Object.defineProperty @prototype, 'id',
@@ -338,6 +339,7 @@

       updateExcerpt: () ->
         @excerpt = undefined
+        return unless @asset && @excerpts
         seg = if @segment.full then @segment else getSelection()
         return if !@asset || !seg || !@segment.overlaps(seg)
         excerpt = @excerpts.find((e) -> seg.overlaps(e.segment))


Fix errors

Fix errors

--- lib/project.coffee
+++ lib/project.coffee
@@ -8,11 +8,12 @@
   db: null
   projectSettings: null

-  constructor: (props={}) ->
+  constructor: (@props={}) ->
+    @db = new DB()
     @propsToSave = []
-    for key, value of props
+    for key, value of @props
       @propsToSave.push(key) unless key in @propsToSave
-    @props = _.deepExtend @getDefaultProps(), props
+    @props = _.deepExtend @getDefaultProps(), @props

   getDefaultProps: ->
     props =
@@ -30,8 +31,10 @@
     @save()

   unset: (key) ->
-    unset(@props[key])
-    unset(@propsToSave[key])
+    defaults = @getDefaultProps()
+    delete(@props[key])
+    @propsToSave = _.without @propsToSave, key
+    @props[key] = defaults[key] if defaults[key]?
     @save()

   isCurrent: =>
@@ -66,8 +69,11 @@
       if @props._id
         @db.update(props)
       else
-        @db.add props, (newProject) =>
-          @props._id = newProject._id
+        @db.add props, (id) =>
+          @props._id = id
+      return true
+    else
+      return false

   remove: ->
     @db ?= new DB()


Add a blank value to an empty array to make sure the param is submitted

This is what Rails does -- submit a blank value to indicate that no
values were submitted to the controller.

--- vendor/assets/javascripts/editable/rails/editable_form.js.coffee
+++ vendor/assets/javascripts/editable/rails/editable_form.js.coffee
@@ -1,40 +1,55 @@
 jQuery ($) ->
   EditableForm = $.fn.editableform.Constructor
   EditableForm.prototype.saveWithUrlHook = (value) ->
-    originalUrl = @options.url
-    model = @options.model
-    nestedName = @options.nested
-    nestedId =  @options.nid
-    nestedLocale = @options.locale
+    originalUrl   = @options.url
+    model         = @options.model
+    nestedName    = @options.nested
+    nestedId      = @options.nid
+    nestedLocale  = @options.locale
+
     @options.url = (params) =>
       if typeof originalUrl == 'function'
         originalUrl.call(@options.scope, params)
       else if originalUrl? && @options.send != 'never'
         myName = params.name
+
         if typeof params.value == 'string'
           myValue = params.value.replace(/(\r\n|\n|\r)/gm,"<br/>")
         else
           myValue = params.value
+
+        # if there are no values in a list, add a blank value so Rails knows all values were removed
+        if Object.prototype.toString.call(params.value) == '[object Array]' && params.value.length == 0
+          params.value.push("")
+
         obj = {}
+
         if nestedName
-          nested = {}
-          nested[myName] = myValue
-          nested['id'] = nestedId
+          nested          = {}
+          nested[myName]  = myValue
+          nested['id']    = nestedId
+
           if nestedLocale
             nested['locale'] = nestedLocale
+
           obj[nestedName + '_attributes'] = nested
         else
           obj[myName] = myValue
+
         params[model] = obj
+
         delete params.name
         delete params.value
         delete params.pk
+
         $.ajax($.extend({
-          url     : originalUrl
-          data    : params
-          type    : 'PUT'
+          url:      originalUrl
+          data:     params
+          type:     'PUT'
           dataType: 'json'
         }, @options.ajaxOptions))
+
     @saveWithoutUrlHook(value)
+
   EditableForm.prototype.saveWithoutUrlHook = EditableForm.prototype.save
   EditableForm.prototype.save = EditableForm.prototype.saveWithUrlHook


Auto detect bootstrap version

Auto detect bootstrap version

--- vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
+++ vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
@@ -15,7 +15,7 @@

           <button type="button" class="close" data-dismiss="modal">×</button>
-
...

+
...

@@ -41,7 +41,7 @@

   $dialog
     .find(".modal-header")
-      .find("h3")
+      .find(".modal-title")
         .html(element.data("confirm-title") || $.fn.twitter_bootstrap_confirmbox.defaults.title || window.top.location.origin)
       .end()
     .end()


Add the handleDidAddTextEditor method and move deserialization of serialized level code editors to this method

--- lib/workspace-manager.coffee
+++ lib/workspace-manager.coffee
@@ -1,10 +1,12 @@
 {CompositeDisposable} = require('atom')

+executionManager      = require('./execution-manager').getInstance()
 languageInstaller     = require('./language-installer').getInstance()
 languageRegistry      = require('./language-registry').getInstance()
 workspace             = require('./workspace').getInstance()

-# languageUtils         = require('./language-utils')
+notificationUtils     = require('./notification-utils')
+workspaceUtils        = require('./workspace-utils')

 LevelCodeEditor       = require('./level-code-editor')
 LevelStatusView       = require('./level-status-view')
@@ -19,27 +21,7 @@

   ## Set-up and clean-up operations --------------------------------------------

-  setUpWorkspace: (state) ->
-    # restore/initialitze the Levels workspace
-    for textEditor in atom.workspace.getTextEditors()
-    #   if (result = languageUtils.)?
-    #     levelCodeEditor = new LevelCodeEditor
-    #       textEditor: textEditor
-    #       language: result.language
-    #       level: result.level
-    #     continue
-
-      if (data = state?.serializedLevelCodeEditorsById[textEditor.id])?
-        if (language = languageRegistry.getLanguageForName(data.language))?
-          level = language.getLevelForName(data.level)
-          terminal = new Terminal(data.serializedTerminal)
-          levelCodeEditor = new LevelCodeEditor
-            textEditor: textEditor
-            language: language
-            level: level
-            terminal: terminal
-          workspace.addLevelCodeEditor(levelCodeEditor)
-
+  setUpWorkspace: (@state) ->
     # add view providers
     @viewProviders = new CompositeDisposable
     @viewProviders.add atom.views.addViewProvider Terminal, (terminal) ->
@@ -49,12 +31,6 @@
     @levelStatusView = new LevelStatusView
     @levelSelectView = new LevelSelectView
     @languageConfigView = new LanguageConfigView
-
-    # initialize active level code editor
-    if (textEditor = atom.workspace.getActiveTextEditor())
-      if workspace.isLevelCodeEditor(textEditor)
-        levelCodeEditor = workspace.getLevelCodeEditorForTextEditor(textEditor)
-        workspace.setActiveLevelCodeEditor(levelCodeEditor)

   cleanUpWorkspace: ->
     # remove view providers
@@ -91,17 +67,12 @@
   ## Atom workspace subscriptions ----------------------------------------------

   subscribeToAtomWorkspace: ->
-    # general Atom workspace subscriptions
+    @textEditorSubscrsById = {}
     @atomWorkspaceSubscrs = new CompositeDisposable
-    @atomWorkspaceSubscrs.add atom.workspace.onDidChangeActivePaneItem (item) =>
+    @atomWorkspaceSubscrs.add atom.workspace.observeTextEditors (textEditor) =>
+      @handleDidAddTextEditor(textEditor)
+    @atomWorkspaceSubscrs.add atom.workspace.observeActivePaneItem (item) =>
       @handleDidChangeActivePaneItem(item)
-    @atomWorkspaceSubscrs.add atom.workspace.onDidAddTextEditor (event) =>
-      @handleDidAddTextEditor(event)
-
-    # text editor subscriptions
-    @textEditorSubscrsById = {}
-    for textEditor in atom.workspace.getTextEditors()
-      @subscribeToTextEditor(textEditor)

   unsubscribeFromAtomWorkspace: ->
     # dispose Atom workspace event handlers
@@ -119,9 +90,27 @@
     else
       workspace.unsetActiveLevelCodeEditor()

-  handleDidAddTextEditor: ({textEditor}) ->
+  handleDidAddTextEditor: (textEditor) ->
+    result = workspaceUtils.readLanguageInformationFromFileHeader(textEditor)
+    language = result?.language
+    level = result?.level
+
+    if (data = @state?.serializedLevelCodeEditorsById[textEditor.id])?
+      languageName = data.language
+      levelName = data.level
+      unless language? and language.getName() isnt languageName
+        language ?= languageRegistry.getLanguageForName(languageName)
+        level ?= language.getLevelForName(levelName) if language?
+      terminal = new Terminal(data.serializedTerminal)
+
+    language ?= workspaceUtils.readLanguageFromFileExtension(textEditor)
+    language ?= languageRegistry.getLanguageForGrammar(textEditor.getGrammar())
+
+    if language?
+      params = {textEditor,language,level,terminal}
+      levelCodeEditor = new LevelCodeEditor(params)
+      workspace.addLevelCodeEditor(levelCodeEditor)
     @subscribeToTextEditor(textEditor)
-    # ...

   ## Text editor subscriptions -------------------------------------------------

@@ -202,7 +191,14 @@
     event.abortKeyBinding()

   doStartExecution: (event) ->
-    event.abortKeyBinding()
+    if (activeLevelCodeEditor = workspace.getActiveLevelCodeEditor())?
+      if activeLevelCodeEditor.getTextEditor().getPath()?
+        executionManager.startExecution(activeLevelCodeEditor)
+      else
+        notificationUtils.addError notificationUtils.executionNotPossible,
+          important: true
+    else
+      event.abortKeyBinding()

   doStopExecution: (event) ->
     event.abortKeyBinding()


further result refactoring

further result refactoring

--- lib/results.coffee
+++ lib/results.coffee
@@ -26,14 +26,14 @@
     body.innerText = opts.body
     view

-  result: (ed, opts) ->
+  result: (ed, content) ->
     view = document.createElement 'div'
     view.classList.add 'ink', 'inline', 'result'
     view.style.position = 'relative'
     view.style.top = -ed.getLineHeightInPixels() + 'px'
     view.style.left = '10px'
     view.style.pointerEvents = 'auto'
-    view.appendChild @collapsible opts
+    view.appendChild content
     view: view

   methods: (r) ->
@@ -41,13 +41,11 @@
     r.invalidate = => @invalidate r
     r.validate = => @validate r

-  show: (ed, mark, {watch, header, body}={}) ->
+  show: (ed, mark, {watch, content}={}) ->
     mark.getBufferRange().isReversed and throw "Cannot add result to reversed marker"
     flag = @removeLines ed, mark.getHeadBufferPosition().row,
                             mark.getTailBufferPosition().row
-    result = @result ed,
-      header: header
-      body: body
+    result = @result ed, content
     mark.result = result
     result.editor = ed
     result.marker = mark


Fix Device.ID param missing on first contact

--- lib/session.coffee
+++ lib/session.coffee
@@ -150,6 +150,9 @@
     return callback(err) if err

     if sessionContext.new
+      params.push([['DeviceID', 'ID'], timestamp,
+        {object: [timestamp, 0], writable: [timestamp, 0], value: [timestamp, [sessionContext.deviceId, 'xsd:string']]}])
+
       params.push([['Events', 'Registered'], timestamp,
         {object: [timestamp, 0], writable: [timestamp, 0], value: [timestamp, [timestamp, 'xsd:dateTime']]}])


Fix ff bug

Fix ff bug

--- odometer.coffee
+++ odometer.coffee
@@ -208,6 +208,8 @@
     @startWatchingMutations()

     setTimeout =>
+      @el.offsetHeight
+
       @el.className += ' odometer-animating'
     , 0


Added Sprngular.queryString.generate() function

--- app/assets/javascripts/sprangular/utilities/queryString.coffee
+++ app/assets/javascripts/sprangular/utilities/queryString.coffee
@@ -1,22 +1,39 @@
-Sprangular.parseQueryString = (uri) ->
-  queryString = {}
+Sprangular.queryString =
+  parse: (uri) ->
+    queryString = {}

-  addArrayValue = (key, value) ->
-    (queryString[key] ||= []).push(value)
+    addArrayValue = (key, value) ->
+      (queryString[key] ||= []).push(value)

-  addDictValue = (name, key, value) ->
-    (queryString[name] ||= {})[key] = value
+    addDictValue = (name, key, value) ->
+      (queryString[name] ||= {})[key] = value

-  addValue = (key, value) ->
-    match = key.match(/^([^\[]+)(\[(.*)\])$/i)
+    addValue = (key, value) ->
+      match = key.match(/^([^\[]+)(\[(.*)\])$/i)

-    if match
-      if match[3]
-        addDictValue(match[1], match[3], value)
+      if match
+        if match[3]
+          addDictValue(match[1], match[3], value)
+        else
+          addArrayValue(match[1], value)
       else
-        addArrayValue(match[1], value)
-    else
-      queryString[key] = value
+        queryString[key] = value

-  uri.replace new RegExp("([^?=&]+)(=([^&]*))?", "g"), (_, key, _, value) -> addValue(key, value)
-  queryString
+    uri.replace new RegExp("([^?=&]+)(=([^&]*))?", "g"), (_, key, _, value) -> addValue(key, value)
+    queryString
+
+  generate: (obj) ->
+    parts = []
+
+    for key, value of obj
+      if Array.isArray(value)
+        parts.push("#{key}[]=#{v}") for v in value
+
+      else if typeof(value) == 'object'
+        for k, v of value
+          parts.push("#{key}[#{k}]=#{v}")
+
+      else
+        parts.push("#{key}=#{value}")
+
+    parts.join("&")


make readExif separate function

make readExif separate function

--- app/assets/javascripts/uploadcare/utils/image-processor.coffee
+++ app/assets/javascripts/uploadcare/utils/image-processor.coffee
@@ -33,16 +33,9 @@
         img.onerror = null  # do not fire when set to blank
         df.notify(.10)

-        exif = null
-        op = ns.readJpegChunks(file)
-        op.progress (pos, length, marker, view) ->
-          if not exif and marker == 0xe1
-            if view.byteLength >= 14
-              if view.getUint32(0) == 0x45786966 and view.getUint16(4) == 0
-                exif = view.buffer
-        op.always ->
+        ns.readExif(file).always (exif) ->
           df.notify(.2)
-          isJPEG = op.state() is 'resolved'
+          isJPEG = @state() is 'resolved'

           op = ns.shrinkImage(img, settings)
           op.progress (progress) ->
@@ -212,6 +205,19 @@
     df.promise()

+  ns.readExif = (file) ->
+    exif = null
+    op = ns.readJpegChunks(file)
+    op.progress (pos, l, marker, view) ->
+      if not exif and marker == 0xe1
+        if view.byteLength >= 14
+          if view.getUint32(0) == 0x45786966 and view.getUint16(4) == 0
+            exif = view.buffer
+    return op.then ->
+      return exif
+    , (reason) ->
+      return $.Deferred().reject(exif, reason)
+
   ns.hasTransparency = (img) ->
     pcsn = 50


Fix stream callbacks in renderer

Fix stream callbacks in renderer

--- src/core/renderer.coffee
+++ src/core/renderer.coffee
@@ -8,15 +8,25 @@

 {ContentTree} = require './content'

+pump = (source, destination, callback) ->
+  source.pipe destination
+  source.on 'error', (error) ->
+    callback? error
+    callback = null
+  source.on 'close', ->
+    callback?()
+    callback = null
+
 renderView = (env, content, locals, contents, templates, callback) ->
-  view = content.view
-  if typeof view is 'string'
-    name = view
-    view = env.views[view]
-    if not view?
-      callback new Error "content '#{ content.filename }' specifies unknown view '#{ name }'"
-      return
-  view.call content, env, locals, contents, templates, callback
+  setImmediate ->
+    view = content.view
+    if typeof view is 'string'
+      name = view
+      view = env.views[view]
+      if not view?
+        callback new Error "content '#{ content.filename }' specifies unknown view '#{ name }'"
+        return
+    view.call content, env, locals, contents, templates, callback

 render = (env, outputDir, contents, templates, locals, callback) ->
   ### Render *contents* and *templates* using environment *env* to *outputDir*.
@@ -27,16 +37,16 @@

   renderPlugin = (content, callback) ->
     ### render *content* plugin, calls *callback* with true if a file is written; otherwise false. ###
-    destination = path.join outputDir, content.filename
-    env.logger.verbose "writing content #{ content.url } to #{ destination }"
     renderView env, content, locals, contents, templates, (error, result) ->
       if error
         callback error
       else if result instanceof fs.ReadStream or result instanceof Buffer
+        destination = path.join outputDir, content.filename
+        env.logger.verbose "writing content #{ content.url } to #{ destination }"
         mkdirp.sync path.dirname destination
         writeStream = fs.createWriteStream destination
         if result instanceof fs.ReadStream
-          result.pipe writeStream, callback
+          pump result, writeStream, callback
         else
           writeStream.write result
           writeStream.end()
@@ -46,7 +56,7 @@
         callback()

   items = ContentTree.flatten contents
-  async.forEachLimit items, env.config.fileLimit, renderPlugin, callback
+  async.forEachLimit items, env.config._fileLimit, renderPlugin, callback

 ### Exports ###


Fixing tree placement of the tooltip elements

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -26,17 +26,17 @@
     #  * TESTING

     constructor: (@element, @options = {}) ->
-        @brushTooltip = false
+        @brushTooltip = @options.brushTooltip
         @brushTooltipOffset = [30, 20]

-        @tooltip = d3.select("body").append("div")
+        @tooltip = d3.select(@element).append("div")
             .attr("class", "tooltip")
             .style("opacity", 0)

-        @tooltipBrushMin = d3.select("body").append("div")
+        @tooltipBrushMin = d3.select(@element).append("div")
             .attr("class", "tooltip")
             .style("opacity", 0)
-        @tooltipBrushMax = d3.select("body").append("div")
+        @tooltipBrushMax = d3.select(@element).append("div")
             .attr("class", "tooltip")
             .style("opacity", 0)


Home: taken into main app

Home: taken into main app

--- projects.coffee
+++ projects.coffee
@@ -159,6 +159,7 @@
     projects      : [
                      'KDBackend'
                      'Core'
+                     'Home'
                      'KDMainApp'
                      'Activity'
                      'PostOperations'


Smooth shading routine in shader.

Smooth shading routine in shader.

--- packages/landsofillusions/engine/materials/rampmaterial.coffee
+++ packages/landsofillusions/engine/materials/rampmaterial.coffee
@@ -72,8 +72,15 @@
     }
   }

+  vec3 destinationColor = bestColor;
+
+  /* Smooth shading routine
+  float blendFactor = bestColorDistance / (bestColorDistance + secondBestColorDistance);
+  vec3 destinationColor = mix(bestColor, secondBestColor, blendFactor);
+  */
+
   // Color the pixel with the best match from the palette.
-  gl_FragColor = vec4(bestColor, 1);
+  gl_FragColor = vec4(destinationColor, 1);
 }
 """


Don't remember window position. #technicaldebt

--- src/atom/window.coffee
+++ src/atom/window.coffee
@@ -12,23 +12,10 @@
     @path = $atomController.path.toString()
     @setTitle (_.last @path.split '/') or 'Untitled Document'

-    # Remember sizing!
-    defaultFrame = x: 0, y: 0, width: 600, height: 800
-    frame = atom.storage.get "window.frame.#{@path}", defaultFrame
-    rect = OSX.CGRectMake(frame.x, frame.y, frame.width, frame.height)
-    $atomController.window.setFrame_display rect, true
-
     $atomController.window.makeKeyWindow
     atom.trigger 'window:load'

   shutdown: ->
-    frame = $atomController.window.frame
-    x = frame.origin.x
-    y = frame.origin.y
-    width = frame.size.width
-    height = frame.size.height
-
-    atom.storage.set "window.frame.#{@path}", {x:x, y:y, width:width, height:height}

   showConsole: ->
     $atomController.webView.inspector.showConsole true


Add ! keyboard shortcut

Add ! keyboard shortcut

--- app/js/views/shortcuts.coffee
+++ app/js/views/shortcuts.coffee
@@ -33,6 +33,10 @@
     'Previous Repository':
       key: 'K'
       action: 'prevRepo'
+
+    'Send Feedback':
+      key: '!'
+      action: -> Backbone.history.navigate 'feedback', trigger: true

   # Put undocumented shortcuts here
   keyboardEvents:


more explicit modal when host is removed from pool

--- app/modules/host/index.coffee
+++ app/modules/host/index.coffee
@@ -159,7 +159,7 @@
     $scope.pool_removeHost = (id) ->
       modal.confirm({
         title: 'Remove host from pool'
-        message: 'Are you sure you want to detach this host from its pool? It will be automatically rebooted'
+        message: 'Are you sure you want to detach this host from its pool? It will be automatically rebooted AND LOCAL STORAGE WILL BE ERASED.'
       }).then ->
         xo.host.detach id
     $scope.rebootHost = (id) ->


4564: Fixed report with partial data

4564: Fixed report with partial data

--- app/assets/javascripts/backbone/views/meals/report_charts_view.coffee
+++ app/assets/javascripts/backbone/views/meals/report_charts_view.coffee
@@ -39,14 +39,14 @@
   addDinersByWeekday: (num) ->
     data = [{values: @data.diners_by_weekday[0]}]
     chart = @barChart().forceY([0,40])
-    chart.xAxis.axisLabel('Weekday').tickFormat (d) -> data[0].values[d].l
+    chart.xAxis.axisLabel('Weekday').tickFormat (d) => @tickFormat(data, d)
     chart.yAxis.axisLabel('Avg. Diners per Meal').tickValues([10,20,30,40]).tickFormat(d3.format(',.1f'))
     @addChart(num, chart, data, "Avg. Diners per #{@cmty}Meal by Weekday")

   addCostByWeekday: (num) ->
     data = [{values: @data.cost_by_weekday[0]}]
     chart = @barChart().forceY([0,8])
-    chart.xAxis.axisLabel('Weekday').tickFormat (d) -> data[0].values[d].l
+    chart.xAxis.axisLabel('Weekday').tickFormat (d) => @tickFormat(data, d)
     chart.yAxis.axisLabel('Avg. Adult Cost per Meal').tickValues([2,4,6,8]).tickFormat(d3.format('$,.2f'))
     @addChart(num, chart, data, "Avg. Adult Cost per #{@cmty}Meal by Weekday")

@@ -88,5 +88,11 @@
   setMonthXAxis: (chart, data) ->
     chart.xAxis
       .axisLabel('Month')
-      .tickFormat((d) -> data[0].values[d].l) # l = Label
+      .tickFormat((d) => @tickFormat(data, d))
       .tickValues([0,3,6,9])
+
+  tickFormat: (data, d) ->
+    if d >= 0 && d < data[0].values.length
+      data[0].values[d].l  # l = Label
+    else
+      ''


SliderComponent Improvements: _touchDown() now outputs the proper value and position, regardless of screen/canvas scale & resolution. Accounting for screenScaledFrame() and canvasScaleX.

--- framer/Components/SliderComponent.coffee
+++ framer/Components/SliderComponent.coffee
@@ -71,7 +71,7 @@
 	_touchDown: (event) =>
 		event.preventDefault()
 		event.stopPropagation()
-		@value = @valueForPoint(event.x - @canvasFrame.x)
+		@value = @valueForPoint(event.x - @screenScaledFrame().x) / @canvasScaleX()
 		@knob.draggable._touchStart(event)

 	_updateFill: =>


users list

users list

--- client/app/scripts/users.coffee
+++ client/app/scripts/users.coffee
@@ -1,19 +1,19 @@
 {div, span, header, i} = React.DOM
 {form, input, label} = React.DOM
+{ul, li} = React.DOM

-roomForm = React.createClass
-  submit: ->
-    {room} = @refs
-    roomNode = room.getDOMNode()
-    if roomNode.value
-      @props.joinMessage {room: room.state.value, nick: 'Peppe'}
-    false
+user = React.createClass
+  render: ->
+    li className: 'user',
+      div className: 'user-name',
+        @props.nick

+usersList = React.createClass
   render: ->
-    form className: "credentials-form", onSubmit: @submit,
-      label {}, "Huone",
-        input className: "credentials-room", type: "text", ref: "room"
-      input className: "credentials-send", type: "submit", value: "Send",
+    users = @props.users.map (u) ->
+      console.log u
+      user(u)
+    ul className: "users-list", users

 usersHeader = React.createClass
   handleRoomContentEditable: () ->
@@ -38,7 +38,6 @@
   render: ->
     div id: "users-container",
       usersHeader {room: @props.room}
-      # roomForm {joinMessage: @joinMessage}
+      usersList {users: @props.users}

 module.exports.usersContainer = usersContainer
-


fixed loggly bug

fixed loggly bug

--- log/Logger._coffee
+++ log/Logger._coffee
@@ -8,8 +8,7 @@
     @source = source
     # TODO Remove magic number
     @currentStage = 3
-    conf = LogglyConf.getLogglyConfig()
-    console.log("test", conf)
+    conf = LogglyConf.getLogglyConf()
     @logglyLogger = Loggly.createClient conf

   log: (level, content) ->


Make some HTML generated in JS more readable by making it a multiline String

--- source/javascripts/index/holidayTableFactory.coffee
+++ source/javascripts/index/holidayTableFactory.coffee
@@ -49,15 +49,20 @@
   generateTableSection: (iterator) ->
     rowData = @generateRows(iterator)
     rows = rowData.rows
+    return if 0 == rows.length
     tableSection = []
-    return if 0 == rows.length
     for row in rows
       if row.time?
         tableSection.push("#{row.description}	#{row.time}	")
       else
         tableSection.push("#{row.description}	")
-    dateCells = "#{rowData.dayOfWeek}	#{rowData.gregorianDate}	"
-    "
#{dateCells}#{tableSection.join("

")}
"
+    """
+

+        #{rowData.dayOfWeek}
+        #{rowData.gregorianDate}
+        #{tableSection.join("

")}
+

+    """
   generateRows: (iterator) ->
     [gregorianDate, hebrewDate, zmanim, shaharit, mincha] = [@gregorianWeek[iterator], @hebrewWeek[iterator], @zmanimWeek[iterator], @shaharitWeek[iterator], @minchaWeek[iterator]]
     rows = []


Another fix for FF

Another fix for FF

--- app/assets/javascripts/activeadmin-wysihtml5/base.js.coffee
+++ app/assets/javascripts/activeadmin-wysihtml5/base.js.coffee
@@ -155,10 +155,10 @@
           true

   $ ->
-    $('.activeadmin-wysihtml5').activeAdminWysihtml5()
+    $('.activeadmin-wysihtml5:visible').activeAdminWysihtml5()
     $("a").bind "click", ->
       setTimeout(
-        -> $('.activeadmin-wysihtml5').activeAdminWysihtml5()
+        -> $('.activeadmin-wysihtml5:visible').activeAdminWysihtml5()
         50
       )


Workspace: Fixed client terminal height on resized.

--- client/app/CommonViews/workspace/panes/collaborativeclientterminalpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativeclientterminalpane.coffee
@@ -24,6 +24,8 @@

     @createElements()
     @blinkCursor()
+
+    @on "PaneResized", => @setHeight @parent.getHeight() - 37

   createElements: ->
     @container  = new KDView


Collaboration: `IDEAppController::listChatParticipants`

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1172,6 +1172,19 @@
       callback @socialChannel

+  listChatParticipants: (callback) ->
+
+    channelId = @socialChannel.getId()
+
+    {socialapi} = KD.singletons
+    socialapi.channel.listParticipants {channelId}, (err, participants) ->
+
+      socialApiIds = participants.map (participant) -> participant.accountId
+
+      KD.remote.api.JAccount.some {socialApiId: socialApiIds}
+        .then callback
+
+
   startCollaborationSession: (callback) ->

     return callback msg : 'no social channel'  unless @socialChannel


fix font paths

fix font paths

--- app/templates/gulpfile.coffee
+++ app/templates/gulpfile.coffee
@@ -219,7 +219,7 @@
 #*------------------------------------*/
 gulp.task "font", () ->
   files = ['eot', 'woff', 'ttf', 'svg'].map (curr) ->
-    "#{conf.path.dev.fnt}/**/*#{curr}"
+    "#{conf.path.dev.fnt}/**/*.#{curr}"

   gulp.src(files)
     .pipe cache(rev())


IDE: Listen router to deal with sidebar states.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -115,12 +115,25 @@

         {@finderPane} = @workspace.panel.getPaneByName 'filesPane'

+        @bindRouteHandler()
+
     KD.singletons.appManager.on 'AppIsBeingShown', (app) =>
       # Temporary fix for IDE is not shown after
       # opening pages which uses old SplitView.
       # TODO: This needs to be fixed. ~Umut
       if app instanceof IDEAppController
         KD.singletons.windowController.notifyWindowResizeListeners()
+
+  bindRouteHandler: ->
+    {router, mainView} = KD.singletons
+
+    router.on 'RouteInfoHandled', (routeInfo) =>
+      if routeInfo.path.indexOf('/IDE') is -1
+        if mainView.isSidebarCollapsed
+          mainView.toggleSidebar()
+      else
+        if @isKodingSidebarCollapsed and not mainView.isSidebarCollapsed
+          mainView.toggleSidebar()

   setActiveTabView: (tabView) ->
     @activeTabView = tabView


create method to read hash parameters

create method to read hash parameters

--- app/assets/javascripts/components/image-subject-viewer.cjsx
+++ app/assets/javascripts/components/image-subject-viewer.cjsx
@@ -13,6 +13,7 @@
 PointTool                     = require './point'
 Classification                = require '../models/classification'

+
 ImageSubjectViewer = React.createClass # rename to Classifier
   displayName: 'ImageSubjectViewer'

@@ -27,6 +28,22 @@

   resizing: false

+  getParameterByName: (name) ->
+    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]")
+    regex = new RegExp("[\\?&]" + name + "=([^&#]*)")
+    results = regex.exec(location.search)
+    (if not results? then "" else decodeURIComponent(results[1].replace(/\+/g, " ")))
+
+  checkForTranscribeSubject: ->
+    subject_url = @getParameterByName("subject_url")
+    y_upper     = @getParameterByName("y_yupper")
+    y_lower     = @getParameterByName("y_lower")
+
+    if subject_url isnt "" and y_upper isnt "" and y_lower isnt ""
+      console.log 'FOUND SUBJECT URL'
+    else
+      console.log 'Nothing to see here. Move along.'
+
   getInitialState: ->
     subjects: example_subjects # TODO: need to remove this

@@ -52,6 +69,8 @@

   componentDidMount: ->
     @setView 0, 0, @state.imageWidth, @state.imageHeight
+    # console.log 'FOO: ', @getParameterByName("foo")
+    @checkForTranscribeSubject()
     @fetchSubjects()

     window.addEventListener "resize", this.updateDimensions


Add usage message for rebuild-module-cache command

--- src/rebuild-module-cache.coffee
+++ src/rebuild-module-cache.coffee
@@ -1,5 +1,6 @@
 path = require 'path'
 async = require 'async'
+optimist = require 'optimist'
 Command = require './command'
 config = require './config'
 fs = require './fs'
@@ -10,6 +11,22 @@

   constructor: ->
     @atomPackagesDirectory = path.join(config.getAtomDirectory(), 'packages')
+
+  parseOptions: (argv) ->
+    options = optimist(argv)
+    options.usage """
+
+      Usage: apm rebuild-module-cache
+
+      Rebuild the module cache for all the packages installed to
+      ~/.atom/packages
+
+      You can see the state of the module cache for a package by looking
+      at the _atomModuleCache property in the package's package.json file.
+
+      This command skips all linked packages.
+    """
+    options.alias('h', 'help').describe('help', 'Print this usage message')

   getResourcePath: (callback) ->
     if @resourcePath


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -19,7 +19,7 @@

   fadeOutDefaults =
     color: 'transparent'
-
+    dur

   configureFade = (duration, color, alpha) ->
     I.flashDuration = duration


Fixed javascript test

Fixed javascript test

--- core/spec/javascripts/views/conversations/start_conversation_view_spec.coffee
+++ core/spec/javascripts/views/conversations/start_conversation_view_spec.coffee
@@ -10,11 +10,11 @@
       content = view.$('.js-message-textarea').val()
       expect(content).to.equal "henk"

-    it 'contains the text "Check out this Factlink!"" in the textarea when no default message is given', ->
+    it 'contains the text "Check out this #{Factlink.Global.t.factlink}!"" in the textarea when no default message is given', ->
       view = new StartConversationView
       view.render()
       content = view.$('.js-message-textarea').val()
-      expect(content).to.equal "Check out this Factlink!"
+      expect(content).to.equal "Check out this #{Factlink.Global.t.factlink}!"

   describe "clicking on the textarea", ->


Add convenient accessors for Window API.

--- browser/api/lib/window.coffee
+++ browser/api/lib/window.coffee
@@ -1,3 +1,20 @@
 Window = process.atom_binding('window').Window

+# Convient accessors.
+setupGetterAndSetter = (constructor, name, getter, setter) ->
+  constructor.prototype.__defineGetter__ name, ->
+    this[getter].apply(this, arguments)
+  constructor.prototype.__defineSetter__ name, ->
+    this[setter].apply(this, arguments)
+
+setupGetterAndSetter Window, 'fullscreen', 'isFullscreen', 'setFullscreen'
+setupGetterAndSetter Window, 'size', 'getSize', 'setSize'
+setupGetterAndSetter Window, 'maximumSize', 'getMaximumSize', 'setMaximumSize'
+setupGetterAndSetter Window, 'minimumSize', 'getMinimumSize', 'setMinimumSize'
+setupGetterAndSetter Window, 'resizable', 'isResizable', 'setResizable'
+setupGetterAndSetter Window, 'alwaysOnTop', 'isAlwaysOnTop', 'setAlwaysOnTop'
+setupGetterAndSetter Window, 'position', 'getPosition', 'setPosition'
+setupGetterAndSetter Window, 'title', 'getTitle', 'setTitle'
+setupGetterAndSetter Window, 'kiosk', 'isKiosk', 'setKiosk'
+
 module.exports = Window


Config: decrease rateLimits for user sessions to 60 per 60 sec.

--- config/generateKonfig.coffee
+++ config/generateKonfig.coffee
@@ -259,8 +259,8 @@
     recaptcha                     : recaptcha
     uri                           : { address: options.customDomain.public }
     # TODO: average request count per hour for a user should be measured and a reasonable limit should be set
-    nodejsRateLimiter             : { enabled: no, guestRules: [{ interval: 3600, limit: 5000 }], userRules: [{ interval: 3600, limit: 10000 }] } # limit: request limit per rate limit window, interval: rate limit window duration in seconds
-    nodejsRateLimiterForApi       : { enabled: yes, guestRules: [{ interval: 60,   limit: 10 }],   userRules: [{ interval: 60,   limit: 300 }] }   # limit: request limit per rate limit window, interval: rate limit window duration in seconds
+    nodejsRateLimiter             : { enabled: no,  guestRules: [{ interval: 3600, limit: 5000 }], userRules: [{ interval: 3600, limit: 10000 }] } # limit: request limit per rate limit window, interval: rate limit window duration in seconds
+    nodejsRateLimiterForApi       : { enabled: yes, guestRules: [{ interval: 60,   limit: 5 }],    userRules: [{ interval: 60,   limit: 60 }] }    # limit: request limit per rate limit window, interval: rate limit window duration in seconds
     webserver                     : { port: 8080 }
     social                        : { port: 3030, login: "#{credentials.rabbitmq.login}", queueName: options.socialQueueName, kitePort: 8760, kiteKey: "#{credentials.kiteHome}/kite.key" }
     boxproxy                      : { port: parseInt(options.publicPort, 10) }


remove unnecessary console logging

remove unnecessary console logging

--- app/views/media/search.coffee
+++ app/views/media/search.coffee
@@ -38,7 +38,6 @@
     type: 'GET'
     dataType: 'json'
     success: (result) ->
-      console.log result
       for option in result
         new_option = document.createElement('option')
         new_option.value = option.value


correct color of 'show by' radio toggle buttons

fixes OUT-1500

test plan:
- go the the assignment index page as a student
- the 'show by' buttons should be gray instead of neon green

Change-Id: I82504d5a1384815e6ff8d7bbb59460cd94661a7d
Reviewed-on: https://gerrit.instructure.com/125209
Tested-by: Jenkins
Reviewed-by: Augusto Callejas <7d9b9fd126d23d111f693e51511b33ea0e7192f6@instructure.com>
QA-Review: Andrew Porter <747194079243b0beca9081f8cd559c020b06a314@instructure.com>
Product-Review: Sidharth Oberoi <b9947257dd0ff1b6a5d899864f5ca846b60f0be3@instructure.com>

--- app/coffeescripts/views/assignments/ToggleShowByView.coffee
+++ app/coffeescripts/views/assignments/ToggleShowByView.coffee
@@ -111,8 +111,8 @@
           defaultValue: defaultValue,
           onChange: @toggleShowBy
         },
-          createElement(RadioInput, {id: 'show_by_date', label: I18n.t("Show by Date"), value: "date"})
-          createElement(RadioInput, {id: 'show_by_type', label: I18n.t("Show by Type"), value: "type"})
+          createElement(RadioInput, {id: 'show_by_date', label: I18n.t("Show by Date"), value: "date", context: "off"})
+          createElement(RadioInput, {id: 'show_by_type', label: I18n.t("Show by Type"), value: "type", context: "off"})
         ), @el
       )


fixed share scale buffer

fixed share scale buffer

--- src/viz/types/helpers/graph/includes/plot.coffee
+++ src/viz/types/helpers/graph/includes/plot.coffee
@@ -105,9 +105,12 @@
     "font-weight": vars.y.ticks.font.weight
   if vars.y.scale.value is "log"
     yText = vars.y.ticks.visible.map (d) -> formatPower d
+  else if vars.y.scale.value is "share"
+    yText = vars.y.ticks.visible.map (d) -> d * 100 + "%"
   else
     yText = vars.y.ticks.visible.map (d) ->
       vars.format.value(d, vars.y.value, vars)
+
   yAxisWidth             = d3.max fontSizes(yText,yAttrs), (d) -> d.width
   yAxisWidth             = Math.round yAxisWidth + vars.labels.padding
   vars.axes.margin.left += yAxisWidth
@@ -129,11 +132,12 @@
     "font-size":   vars.x.ticks.font.size+"px"
     "font-family": vars.x.ticks.font.family.value
     "font-weight": vars.x.ticks.font.weight
+  xValues = vars.x.ticks.visible
   if vars.x.scale.value is "log"
-    xValues = vars.x.ticks.visible
     xText = xValues.map (d) -> formatPower d
-  else
-    xValues = vars.x.ticks.visible
+  else if vars.x.scale.value is "share"
+    xText = vars.x.ticks.visible.map (d) -> d * 100 + "%"
+  else
     if typeof xValues[0] is "string"
       xValues = vars.x.scale.viz.domain().filter (d) ->
         d.indexOf("d3plus_buffer_") != 0


Remove test for nonexistent file

Remove test for nonexistent file

--- spec/javascripts/app/store_spec.js.coffee
+++ spec/javascripts/app/store_spec.js.coffee
@@ -1,9 +0,0 @@
-#= require spec_helper
-
-describe "Dashboard.ApplicationStore", ->
-  beforeEach( ->
-    Test.store = TestUtil.lookupStore()
-  )
-
-  it "works with latest Ember-Data revision", ->
-    assert.equal Test.store.get('revision'), 12


project: clean env for spawning

project: clean env for spawning

--- src/smc-project/local_hub.coffee
+++ src/smc-project/local_hub.coffee
@@ -359,6 +359,11 @@
 if program.kucalc
     winston.debug("running in kucalc")
     kucalc.IN_KUCALC = true
+    # clean environment to get rid of nvm and other variables
+    process.env.PATH = process.env.PATH.split(':').filter(((x) -> not x.startsWith('/cocalc/nvm'))).join(':')
+    for name in ['NODE_PATH', 'NODE_ENV', 'NODE_VERSION', 'NVM_CD_FLAGS', 'NVM_DIR', 'NVM_BIN']
+        delete process.env[name]
+
     if program.test_firewall
         kucalc.init_gce_firewall_test(winston)
 else


project page: add line below smc-file-tabs to help with visual orientation

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -555,7 +555,7 @@
     render_file_tabs: (is_public) ->
         shrink_fixed_tabs = $(window).width() < (376 + (@props.open_files_order.size + @props.num_ghost_file_tabs) * 250)

-

+

                 {<Nav
                     bsStyle   = "pills"


remove TODO

remove TODO

--- src/client/auth.coffee
+++ src/client/auth.coffee
@@ -8,7 +8,6 @@
 try
   h = document.location.hash.replace('#', '')
   if !!h
-    # TODO use Base64 module...
     hash = JSON.parse(Base64.decode(h))
     if window?.opener?.Hull? and window?.opener?.__hull_login_status__ and  !!hash
       window.opener.__hull_login_status__(hash)


Code cleanup

Code cleanup

--- src/closer-core.coffee
+++ src/closer-core.coffee
@@ -701,23 +701,14 @@
      )

   'not_$_every_$QMARK_' : (pred,coll) ->
-    assertions.arity 2, arguments.length
-    assertions.function pred
-    assertions.collection coll
-    !m.every? pred,coll
+    !core.every_$QMARK_.apply @,arguments

   'not_$_any_$QMARK_' : (pred,coll) ->
-    assertions.arity 2, arguments.length
-    assertions.function pred
-    assertions.collection coll
-    !m.some pred, coll
+    !core.some.apply @,arguments

   'distinct_$QMARK_' : (args...) ->
     assertions.arity 1, Infinity, arguments.length
-    if arguments.length > m.count(m.set args)
-      false
-    else
-      true
+    return arguments.length is m.count(m.set args)

 bind = (that, args) ->


Write to selection clipboard from setTimeout

This is so that in progress transactions aren't written to the
selection clipboard.

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -435,12 +435,16 @@
   # Listen for selection changes and store the currently selected text
   # in the selection clipboard. This is only applicable on Linux.
   trackSelectionClipboard: ->
-    @subscribe @props.editor.onDidChangeSelectionRange =>
+    timeoutId = null
+    writeSelectedTextToSelectionClipboard = =>
       if selectedText = @props.editor.getSelectedText()
         # This uses ipc.send instead of clipboard.writeText because
         # clipboard.writeText is a sync ipc call on Linux and that
         # will slow down selections.
         ipc.send('write-text-to-selection-clipboard', selectedText)
+    @subscribe @props.editor.onDidChangeSelectionRange =>
+      clearTimeout(timeoutId)
+      timeoutId = setTimeout(writeSelectedTextToSelectionClipboard)

   observeConfig: ->
     @subscribe atom.config.observe 'editor.useHardwareAcceleration', @setUseHardwareAcceleration


[Gutter] Don't bother updating custom gutter decorations if editor is mini

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -428,6 +428,8 @@
       return unless @startRow? and @endRow? and @lineHeight?

       @state.gutters.customDecorations = {}
+      return if @model.isMini()
+
       for gutter in @model.getGutters()
         gutterName = gutter.name
         @state.gutters.customDecorations[gutterName] = {}


Preserve line number for mouseWheelScreenRow in a sane way

What we were doing before made no sense.

Signed-off-by: Max Brunsfeld <78036c9b69b887700d5846f26a788d53b201ffbb@gmail.com>

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -267,11 +267,11 @@
       visibleLineNumberIds[id] = true

     if @mouseWheelScreenRow?
-      screenRow = @mouseWheelScreenRow
-      top = screenRow * @lineHeight
-      bufferRow = @model.bufferRowForScreenRow(screenRow)
-      @state.gutter.lineNumbers[id] = {screenRow, bufferRow, top}
-      visibleLineNumberIds[bufferRow] = true
+      bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow)
+      wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow)
+      id = bufferRow
+      id += '-' + wrapCount if wrapCount > 0
+      visibleLineNumberIds[id] = true

     for id of @state.gutter.lineNumbers
       delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id]


Add temporary to-do and not-found routes

--- app/main.cjsx
+++ app/main.cjsx
@@ -1,6 +1,6 @@
 React = require 'react'
 window.React = React
-Router = {RouteHandler, DefaultRoute, Route} = require 'react-router'
+Router = {RouteHandler, DefaultRoute, Route, NotFoundRoute} = require 'react-router'
 MainHeader = require './partials/main-header'
 MainFooter = require './partials/main-footer'

@@ -43,6 +43,9 @@
     <Route name="new-project-review" path="review" handler={require './pages/new-project/review'} />
   </Route>
   <Route name="edit-project" path="edit-project/:id" handler={require './pages/edit-project'} />
+
+  <Route path="todo/?*" handler={React.createClass render: ->
 TODO
} />
+  <NotFoundRoute handler={React.createClass render: ->
 Not found
} />
 </Route>

 mainContainer = document.createElement 'div'


Change to CustomEvent for IE 11

Change to CustomEvent for IE 11

--- app/main.cjsx
+++ app/main.cjsx
@@ -11,7 +11,7 @@
   location.pathname = location.hash.slice(1)

 router.run (Handler, handlerProps) ->
-  window.dispatchEvent new Event 'locationchange'
+  window.dispatchEvent new CustomEvent 'locationchange'
   React.render(<Handler {...handlerProps} />, mainContainer);

 logDeployedCommit = require './lib/log-deployed-commit'


Style 'get started' button

Style 'get started' button

--- app/main.cjsx
+++ app/main.cjsx
@@ -66,7 +66,7 @@

 {@state.project?.description}

 {@state.project?.introduction}

-      {
  Get started!
 if @state.project?}
+      {
  Get started!
 if @state.project?}

   setBackground: (project) ->


SVG: don't return rgba colors if the path is fully opaque.

--- CGDraw.coffee
+++ CGDraw.coffee
@@ -33,8 +33,11 @@
 				value: "#{r}, #{g}, #{b}, "
 			}

-		RGBColorSVG: ( color ) ->
-			"rgba(" + "#{Math.round color.red},#{Math.round color.green},#{Math.round color.blue},".toUpperCase( )
+		RGBColorSVG: ( color, opacity ) ->
+			if opacity is 1
+				"rgb(#{Math.round color.red},#{Math.round color.green},#{Math.round color.blue})"
+			else
+				"rgba(#{Math.round color.red},#{Math.round color.green},#{Math.round color.blue}, #{round2 opacity})"

 		GrayColor: ( color ) ->
 			# CGContextSetGrayFillColor
@@ -44,9 +47,12 @@
 				value: "#{pct}, #{pct}, #{pct}, "
 			}

-		GrayColorSVG: ( color ) ->
+		GrayColorSVG: ( color, opacity ) ->
 			value = Math.round (100 - color.gray)*255/100
-			"rgba(" + "#{value},#{value},#{value},".toUpperCase( )
+			if opacity is 1
+				"rgb(#{value},#{value},#{value})"
+			else
+				"rgba(#{value},#{value},#{value},#{round2 opacity})"

 		# The CMYK colors that CG displays do not seem to be quite the same
 		# as the ones displayed in Illustrator. I'm not sure if this is due
@@ -281,12 +287,10 @@
 			stroke = undefined

 			if manageColor[fillColor.typename + 'SVG']
-				fill = manageColor[fillColor.typename + 'SVG']( fillColor )
-				fill += path.opacity/100 + ')'
+				fill = manageColor[fillColor.typename + 'SVG'] fillColor, path.opacity/100

 			if manageColor[strokeColor.typename + 'SVG']
-				stroke = manageColor[strokeColor.typename + 'SVG']( strokeColor )
-				stroke += path.opacity/100 + ')'
+				stroke = manageColor[strokeColor.typename + 'SVG'] strokeColor, path.opacity/100

 			[fill, stroke]


config: use a blank DB for new.koding.com

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -24,7 +24,7 @@
   sourceServer  :
     enabled     : yes
     port        : 1337
-  mongo         : 'dev:k9lc4G1k32nyD72@kmongodb1.in.koding.com:27017/koding_dev2_copy'
+  mongo         : 'dev:k9lc4G1k32nyD72@kmongodb1.in.koding.com:27017/koding2'
   runGoBroker   : no
   runKontrol    : yes
   runRerouting  : no


Use direct connection to broker

Use direct connection to broker

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -108,7 +108,7 @@
       version   : version
       mainUri   : "http://koding.com"
       broker    :
-        sockJS   : "https://broker-#{version}.x.koding.com/subscribe"
+        sockJS   : "https://broker-#{version}.koding.com/subscribe"
       apiUri    : 'https://www.koding.com'
       # Is this correct?
       appsUri   : 'https://koding-apps.s3.amazonaws.com'


Client: Activity: Overrided DropboxInputMixin.onEnter method in channelParticipantAvaters

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -10,6 +10,8 @@
 ProfileLinkContainer        = require 'app/components/profile/profilelinkcontainer'
 ChannelParticipantsDropdown = require 'activity/components/channelparticipantsdropdown'
 DropboxInputMixin           = require 'activity/components/dropbox/dropboxinputmixin'
+getGroup                    = require 'app/util/getGroup'
+validator                   = require 'validator'

 module.exports = class ChannelParticipantAvatars extends React.Component

@@ -208,7 +210,9 @@
     accountId    = whoami()._id
     groupAdminId = @props.channelThread.getIn ['channel','accountOldId']

-    if accountId is groupAdminId then return yes else return no
+    return accountId is groupAdminId
+
+
   isGroupChannel: ->

     channelId = @props.channelThread.getIn ['channel','id']
@@ -216,6 +220,21 @@
     return channelId is getGroup().socialApiDefaultChannelId

+  onEnter: (event) ->
+
+    DropboxInputMixin.onEnter.call this, event
+
+    if @isGroupAdmin() and @isGroupChannel()
+
+      value        = event.target.value.trim()
+      isValidEmail = validator.isEmail value
+
+      if isValidEmail
+
+        { channel, user } = ActivityFlux.actions
+
+        channel.inviteMember [{email: value}], ->
+          user.unsetChannelParticipantsInputQuery()

   getPlaceHolder: ->
@@ -235,7 +254,7 @@
         onKeyDown   = { @bound 'onKeyDown' }
         onChange    = { @bound 'onChange' }
         placeholder = { @getPlaceHolder() }
-        value       = { @state.value }
+        value       = { @state.query }
         ref         = 'textInput'
       />
     </div>


ActivityAppView: Cleared activity counter when scrolled to top.

--- client/landing/app/Applications/Activity.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppView.coffee
@@ -40,6 +40,7 @@
         else
           @activityHeader.unsetClass "scrolling-up-outset"
           @activityHeader.liveUpdateButton.setValue on
+          KD.getSingleton("activityController").clearNewItemsCount()

     @decorate()


fixxing bug in min value expression

fixxing bug in min value expression

--- Src/coffeescript/cql-execution/src/elm/arithmetic.coffee
+++ Src/coffeescript/cql-execution/src/elm/arithmetic.coffee
@@ -11,6 +11,9 @@
 MIN_FLOAT_PRECISION_VALUE =  Math.pow(10,-8)
 MIN_DATE = DateTime.parse("1900-01-01T00:00:00.000")
 MAX_DATE = DateTime.parse("9999-12-31T23:59:59.999")
+
+
+
 module.exports.OverFlowException = class OverFlowException extends Exception

@@ -270,30 +273,22 @@

 module.exports.MinValue = class MinValue extends Expression
+  MIN_VALUES:  "Integer" : MIN_INT_VALUE,"Real" : MIN_FLOAT_VALUE,"DateTime" : MIN_DATE
   constructor: (json) ->
     super

   exec: (ctx) ->
     val = @execArgs(ctx)
-    if val == "Integer"
-      MIN_INT_VALUE
-    else if val = "Real"
-      MIN_FLOAT_VALUE
-    else if val == "DateTime"
-      MIN_DATE
+    MIN_VALUES[val]

 module.exports.MaxValue = class MaxValue extends Expression
+   MAX_VALUES: "Integer" : MAX_INT_VALUE, "Real" : MAX_FLOAT_VALUE, "DateTime" : MAX_DATE
   constructor: (json) ->
     super

   exec: (ctx) ->
     val = @execArgs(ctx)
-    if val == "Integer"
-      MAX_INT_VALUE
-    else if val = "Real"
-      MAX_FLOAT_VALUE
-    else if val == "DateTime"
-      MAX_DATE
+    MAX_VALUES[val]

 module.exports.Successor = class Successor extends Expression
   constructor: (json) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- source/javascripts/_test/string_extensions.coffee
+++ source/javascripts/_test/string_extensions.coffee
@@ -49,8 +49,7 @@
 test "#toInt", ->
   equals "31.3".toInt(), 31
   equals "31.".toInt(), 31
-
-
+
   equals "009".toInt(), 9


Fixed pointer events on scaled items by parents

--- src/renderer/impl/base/level0/item/pointer.coffee
+++ src/renderer/impl/base/level0/item/pointer.coffee
@@ -39,6 +39,7 @@
             h = item._height * parentScale
             scale = item._scale
             rotation = item._rotation
+            fullScale = scale * parentScale

             # add scale
             if scale isnt 1
@@ -68,7 +69,7 @@
                 if item._children
                     child = item._children.topChild
                     while child
-                        result = checkItem(child, ex, ey, onItem, x, y, scale)
+                        result = checkItem(child, ex, ey, onItem, x, y, fullScale)
                         if result & STOP_PROPAGATION
                             return result
                         if result & STOP_ASIDE_PROPAGATION


Fixed sitemapgenerator for crawlable activity feed and topics

--- workers/sitemapgenerator/lib/sitemapgenerator/sitemapgeneratorworker.coffee
+++ workers/sitemapgenerator/lib/sitemapgenerator/sitemapgeneratorworker.coffee
@@ -5,7 +5,6 @@
 {CronJob} = require 'cron'

 NAMEPERPAGE = 50000
-GROUPPERPAGE = 5

 module.exports = class SitemapGeneratorWorker extends EventEmitter
   constructor: (@bongo, @options = {}) ->
@@ -49,6 +48,10 @@

     {JName, JSitemap} = @bongo.models

+    feedLinksAdded = no
+    activityFeedURL = "Activity"
+    topicsFeedURL = "Topics"
+
     generateSitemapIndex = (sitemapNames)=>
       name = "sitemap.xml"
       content = @generateSitemapIndexString sitemapNames
@@ -77,6 +80,12 @@
         JName.some selector, option, (err, names)=>
           if names
             urls = (name.name for name in names)
+
+            unless feedLinksAdded
+              urls.push activityFeedURL
+              urls.push topicsFeedURL
+              feedLinksAdded = yes
+
             sitemapName =  @generateSitemapName skip
             content = @generateSitemapString urls
             @saveSitemap sitemapName, content


use expectAccessDenied helper function

use expectAccessDenied helper function

--- workers/social/lib/social/models/computeproviders/stacktemplate.test.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.test.coffee
@@ -4,9 +4,12 @@
   expect
   withDummyClient
   withConvertedUser
+  expectAccessDenied
   generateRandomString
   checkBongoConnectivity }    = require '../../../../testhelper'
-{ generateStackTemplateData } = require '../../../../testhelper/models/computeproviders/stacktemplatehelper'
+{ generateStackTemplateData
+  withConvertedUserAndStackTemplate } = require \
+  '../../../../testhelper/models/computeproviders/stacktemplatehelper'

 # this function will be called once before running any test
@@ -24,11 +27,7 @@

       it 'should fail to create stack template', (done) ->

-        withDummyClient ({ client }) ->
-
-          StackTemplate.create client, {}, (err, template) ->
-            expect(err?.message).to.be.equal 'Access denied'
-            done()
+        expectAccessDenied StackTemplate, 'create', {}, done

     describe 'when user has the permission', ->
@@ -58,11 +57,7 @@

       it 'should fail to fetch stack templates', (done) ->

-        withDummyClient ({ client }) ->
-
-          StackTemplate.some$ client, {}, (err, template) ->
-            expect(err?.message).to.be.equal 'Access denied'
-            done()
+        expectAccessDenied StackTemplate, 'some$', {}, done

     describe 'when user has the permission', ->
@@ -103,11 +98,7 @@

       it 'should fail to fetch stack templates', (done) ->

-        withDummyClient ({ client }) ->
-
-          StackTemplate.one$ client, {}, {}, (err, template) ->
-            expect(err?.message).to.be.equal 'Access denied'
-            done()
+        expectAccessDenied StackTemplate, 'one$', {}, {}, done

     describe 'when user has the permission', ->
@@ -149,36 +140,59 @@

       it 'should fail to delete the stack template', (done) ->

-        stackTemplate = null
-
-        queue = [
-
-          ->
-            withConvertedUser ({ client }) ->
-
+        withConvertedUserAndStackTemplate ({ stackTemplate }) ->
+          expectAccessDenied stackTemplate, 'delete', done
+
+
+    describe 'when user has the permission', ->
+
+      it 'should be able to delete the stack template', (done) ->
+
+        withConvertedUser ({ client }) ->
+
+          stackTemplate     = null
+          stackTemplateData = null
+
+          queue = [
+
+            ->
               stackTemplateData = generateStackTemplateData client
               StackTemplate.create client, stackTemplateData, (err, template_) ->
                 expect(err).to.not.exist
                 stackTemplate = template_
                 queue.next()

-          ->
-            withDummyClient ({ client }) ->
-
+            ->
               stackTemplate.delete client, (err) ->
-                expect(err?.message).to.be.equal 'Access denied'
-                queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
-
-
-    describe 'when user has the permission', ->
-
-      it 'should be able to delete the stack template', (done) ->
+                expect(err).to.not.exist
+                queue.next()
+
+            ->
+              StackTemplate.one { _id : stackTemplate._id }, (err, stackTemplate_) ->
+                expect(err).to.not.exist
+                expect(stackTemplate_).to.not.exist
+                queue.next()
+
+            -> done()
+
+          ]
+
+          daisy queue
+
+
+  describe 'setAccess()', ->
+
+    describe 'when user doesnt have the permission', ->
+
+      it 'should fail to set access level of the template', (done) ->
+
+        withConvertedUserAndStackTemplate ({ stackTemplate }) ->
+          expectAccessDenied stackTemplate, 'setAccess', 'public', done
+
+
+    describe 'when user has the permission', ->
+
+      it 'should be able to set access level of stack template', (done) ->

         withConvertedUser ({ client }) ->

@@ -195,75 +209,6 @@
                 queue.next()

             ->
-              stackTemplate.delete client, (err) ->
-                expect(err).to.not.exist
-                queue.next()
-
-            ->
-              StackTemplate.one { _id : stackTemplate._id }, (err, stackTemplate_) ->
-                expect(err).to.not.exist
-                expect(stackTemplate_).to.not.exist
-                queue.next()
-
-            -> done()
-
-          ]
-
-          daisy queue
-
-
-  describe 'setAccess()', ->
-
-    describe 'when user doesnt have the permission', ->
-
-      it 'should fail to set access level of the template', (done) ->
-
-        stackTemplate = null
-
-        queue = [
-
-          ->
-            withConvertedUser ({ client }) ->
-
-              stackTemplateData = generateStackTemplateData client
-              StackTemplate.create client, stackTemplateData, (err, template_) ->
-                expect(err).to.not.exist
-                stackTemplate = template_
-                queue.next()
-
-          ->
-            withDummyClient ({ client }) ->
-
-              stackTemplate.setAccess client, 'public', (err) ->
-                expect(err?.message).to.be.equal 'Access denied'
-                queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
-
-
-    describe 'when user has the permission', ->
-
-      it 'should be able to set access level of stack template', (done) ->
-
-        withConvertedUser ({ client }) ->
-
-          stackTemplate     = null
-          stackTemplateData = null
-
-          queue = [
-
-            ->
-              stackTemplateData = generateStackTemplateData client
-              StackTemplate.create client, stackTemplateData, (err, template_) ->
-                expect(err).to.not.exist
-                stackTemplate = template_
-                queue.next()
-
-            ->
               stackTemplate.setAccess client, 'someInvalidAccessLevel', (err) ->
                 expect(err?.message).to.be.equal 'Wrong level specified!'
                 queue.next()
@@ -293,31 +238,8 @@

       it 'should fail to update the stack template', (done) ->

-        stackTemplate = null
-
-        queue = [
-
-          ->
-            withConvertedUser ({ client }) ->
-
-              stackTemplateData = generateStackTemplateData client
-              StackTemplate.create client, stackTemplateData, (err, template_) ->
-                expect(err).to.not.exist
-                stackTemplate = template_
-                queue.next()
-
-          ->
-            withDummyClient ({ client }) ->
-
-              stackTemplate.update$ client, {}, (err) ->
-                expect(err?.message).to.be.equal 'Access denied'
-                queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+        withConvertedUserAndStackTemplate ({ stackTemplate }) ->
+          expectAccessDenied stackTemplate, 'update$', {}, done

     describe 'when user has the permission', ->


WebtermView: do not trigger resize for hidden screens [fixes #87833330]

This is not a perfect solution for this, but we need to re-write this triggering
flow from scratch, it's now in a not fixable state. Too many component depended
on this flow and fixing one breaking other, so instead I put a filter for
requesting to fitWindow on hidden terminals which has sizeX and sizeY as 1

--- client/app/lib/terminal/webtermview.coffee
+++ client/app/lib/terminal/webtermview.coffee
@@ -285,6 +285,7 @@
   triggerFitToWindow: ->

     return  unless @terminal?.server?
+    return  if @terminal.sizeX + @terminal.sizeY <= 2

     @terminal.server.controlSequence String.fromCharCode 2
     @terminal.server.input 'F'


Tests: Activity: EditDelete: Removed unused line.

--- client/test/lib/activity/editdelete.coffee
+++ client/test/lib/activity/editdelete.coffee
@@ -143,7 +143,6 @@
     post = helpers.postActivity(browser)

     postSelector            = "#{activitySelector} .activity-content-wrapper"
-    editWidgetSelector      = "#{activitySelector} .activity-input-widget.edit-widget"
     settingsWrapperSelector = "#{activitySelector} .settings-menu-wrapper"

     browser


Tests: Activity: likeunlike: Fixed beginTest.

--- client/test/lib/activity/likeunlike.coffee
+++ client/test/lib/activity/likeunlike.coffee
@@ -9,7 +9,7 @@

   likePost: (browser) ->

-    user = @beginTest(browser)
+    user = helpers.beginTest(browser)
     helpers.likePost(browser, user)
     browser.end()


Make sure saying required = false works when an email is configured, but no users are found

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -440,8 +440,12 @@

     pagerDutyGet msg, "/users", {query: email}, (json) ->
       if json.users.length isnt 1
-        msg.send "Sorry, I expected to get 1 user back for #{email}, but got #{json.users.length} :sweat:. Can you make sure that is actually a real user on PagerDuty?"
-        return
+        if json.users.length is 0 and not required
+          cb null
+          return
+        else
+          msg.send "Sorry, I expected to get 1 user back for #{email}, but got #{json.users.length} :sweat:. Can you make sure that is actually a real user on PagerDuty?"
+          return

       cb(json.users[0])


added more faces

added more faces

--- src/scripts/trollicon.coffee
+++ src/scripts/trollicon.coffee
@@ -10,7 +10,12 @@
   'kidding' : 'http://i.imgur.com/0uCcv.png',
   'megusta' : 'http://i.imgur.com/QfeUB.png',
   'isee': 'http://i.imgur.com/M4bcv.png',
-  'fuckyeah' : 'http://i.imgur.com/m7mEZ.png'
+  'fuckyeah' : 'http://i.imgur.com/m7mEZ.png',
+  'problem' : 'http://i.imgur.com/oLlJm.png',
+  'dissapoint' : 'http://i.imgur.com/EwBi7.png',
+  'nothing' : 'http://i.imgur.com/Nwos9.png',
+  'pokerface' : 'http://i.imgur.com/dDjvG.png',
+  'ok' : 'http://i.imgur.com/QRCoI.png'
 }

 module.exports = (robot)->


remove scrolling flag

remove scrolling flag

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -173,7 +173,7 @@
 							pending = []
 							eof = false
 							bof = false
-							adjustBuffer(ridActual, false)
+							adjustBuffer(ridActual)

 						bottomVisiblePos = ->
 							viewport.scrollTop() + viewport.outerHeight()
@@ -234,12 +234,12 @@
 								first += overage
 								log "clipped off top #{overage} top padding #{adapter.topPadding()}"

-						enqueueFetch = (rid, direction, scrolling)->
+						enqueueFetch = (rid, direction)->
 							if (!isLoading)
 								isLoading = true
 								loading(true)
 							if pending.push(direction) == 1
-								fetch(rid, scrolling)
+								fetch(rid)

 						hideElementBeforeAppend = (element) ->
 							element.displayTemp = element.css('display')
@@ -289,12 +289,12 @@
 									# if not, increment scrollTop
 									viewport.scrollTop(viewport.scrollTop() + wrapper.element.outerHeight(true))

-						doAdjustment = (rid, scrolling, finalize)->
+						doAdjustment = (rid, finalize)->
 							log "top {actual=#{adapter.topDataPos()} visible from=#{topVisiblePos()} bottom {visible through=#{bottomVisiblePos()} actual=#{adapter.bottomDataPos()}}"
 							if shouldLoadBottom()
-								enqueueFetch(rid, true, scrolling)
-							else
-								enqueueFetch(rid, false, scrolling) if shouldLoadTop()
+								enqueueFetch(rid, true)
+							else
+								enqueueFetch(rid, false) if shouldLoadTop()
 							finalize(rid) if finalize
 							if pending.length == 0
 								topHeight = 0
@@ -309,7 +309,7 @@
 										topVisible(item) if newRow
 										break

-						adjustBuffer = (rid, scrolling, newItems, finalize)->
+						adjustBuffer = (rid, newItems, finalize)->
 							if newItems and newItems.length
 								$timeout ->
 									rows = []
@@ -322,25 +322,25 @@
 											rowTop = itemTop
 									for row in rows
 										adjustRowHeight(row.appended, row.wrapper)
-									doAdjustment(rid, scrolling, finalize)
-							else
-								doAdjustment(rid, scrolling, finalize)
-
-						finalize = (rid, scrolling, newItems)->
-							adjustBuffer rid, scrolling, newItems, ->
+									doAdjustment(rid, finalize)
+							else
+								doAdjustment(rid, finalize)
+
+						finalize = (rid, newItems)->
+							adjustBuffer rid, newItems, ->
 								pending.shift()
 								if pending.length == 0
 									isLoading = false
 									loading(false)
 								else
-									fetch(rid, scrolling)
-
-						fetch = (rid, scrolling) ->
+									fetch(rid)
+
+						fetch = (rid) ->
 							direction = pending[0]
 							#log "Running fetch... #{{true:'bottom', false: 'top'}[direction]} pending #{pending.length}"
 							if direction
 								if buffer.length && !shouldLoadBottom()
-									finalize(rid, scrolling)
+									finalize(rid)
 								else
 									#log "appending... requested #{bufferSize} records starting from #{next}"
 									datasource.get next, bufferSize,
@@ -356,10 +356,10 @@
 											for item in result
 												newItems.push (insert ++next, item)
 											#log "appended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
-										finalize(rid, scrolling, newItems)
+										finalize(rid, newItems)
 							else
 								if buffer.length && !shouldLoadTop()
-									finalize(rid, scrolling)
+									finalize(rid)
 								else
 									#log "prepending... requested #{size} records starting from #{start}"
 									datasource.get first-bufferSize, bufferSize,
@@ -375,21 +375,15 @@
 											for i in [result.length-1..0]
 												newItems.unshift (insert --first, result[i])
 											#log "prepended: requested #{bufferSize} received #{result.length} buffer size #{buffer.length} first #{first} next #{next}"
-										finalize(rid, scrolling, newItems)
-
-						resizeHandler = ->
+										finalize(rid, newItems)
+
+						resizeAndResizeHandler = ->
 							if !$rootScope.$$phase && !isLoading
-								adjustBuffer(null, false) #todo dhilt : is null rid passing safe for resize, scroll, delete and insert cases?
+								adjustBuffer()
 								$scope.$apply()

-						viewport.bind 'resize', resizeHandler
-
-						scrollHandler = ->
-							if !$rootScope.$$phase && !isLoading
-								adjustBuffer(null, true)
-								$scope.$apply()
-
-						viewport.bind 'scroll', scrollHandler
+						viewport.bind 'resize', resizeAndResizeHandler
+						viewport.bind 'scroll', resizeAndResizeHandler

 						wheelHandler = (event) ->
 							scrollTop = viewport[0].scrollTop
@@ -440,7 +434,7 @@
 									next--

 							item.scope.$index = first + i for item,i in buffer
-							adjustBuffer(null, false)
+							adjustBuffer()

 						eventListener.$on "insert.item", (event, locator, item)->
 							inserted = []
@@ -464,7 +458,7 @@
 									next++

 							item.scope.$index = first + i for item,i in buffer
-							adjustBuffer(null, false, inserted)
+							adjustBuffer(null, inserted)

 		])


fix bug comment

fix bug comment

--- app/assets/javascripts/web/views/comparison/comparison_proposition.js.coffee
+++ app/assets/javascripts/web/views/comparison/comparison_proposition.js.coffee
@@ -18,11 +18,7 @@
       @showComments = true
       view = new CommentsView(model: @model)
       if @model.commentsCount() == 0
-        if app.models.user.loggedIn()
-          view.render()
-        else
-          sessionView = new SessionView(model: app.models.user)
-          sessionView.render()
+        view.render()
       else
         # indicator
         @.$('.comments-count a').addClass "indicator"


regarding to http://git.sj.koding.com/koding/koding/merge_requests/853/diffs#note_2844

--- client/app/Applications/Account.kdapplication/views/deleteaccountview.coffee
+++ client/app/Applications/Account.kdapplication/views/deleteaccountview.coffee
@@ -13,19 +13,17 @@
       mouseenter : do ->
         times = 0
         ->
-          if times is 0
-            @setTitle "Are you sure?!"
-          if times is 1
-            @setTitle "OK, go ahead :)"
-          if times > 1
-            KD.utils.wait 5000, =>
-              times = 0
-              @setTitle "Delete Account"
-            return
+          switch times
+            when 0 then @setTitle "Are you sure?!"
+            when 1 then @setTitle "OK, go ahead :)"
+            else
+              KD.utils.wait 5000, =>
+                times = 0
+                @setTitle "Delete Account"
+              return
           @toggleClass 'escape'
           times++
-      callback   : ->
-        new DeleteModalView
+      callback   : -> new DeleteModalView

   pistachio:->
     """


link embed box links now target _blank

link embed box links now target _blank

--- client/landing/app/Applications/Activity.kdapplication/views/embedbox.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/embedbox.coffee
@@ -97,10 +97,10 @@
         html = """

-
+

-
+

#{data.title or "untitled"}

Provided by #{data.provider_name or "the internet"}#{if data.provider_url then " at "+prettyLink(data.provider_url)+"" else ""}

#{data.description or ""}


s/ruby/js/

s/ruby/js/

--- specs/unit/type_factory/factory/sub_spec.coffee
+++ specs/unit/type_factory/factory/sub_spec.coffee
@@ -15,7 +15,7 @@

     it 'shoudl have the BuiltinType(Number) super type', ->
       subject.superType.should.be.an.instanceof(BuiltinType)
-      subject.superType.ruby_type.should.be.an.instanceof(Number)
+      subject.superType.jsType.should.be.an.instanceof(Number)

     it 'should have the correct constraint', ->
       subject.fromQ(10).should.equal(10)


Maintenance: Fix js error in kb agent controller when many login and logouts happen in tests.

--- app/assets/javascripts/app/lib/app_post/task_manager.coffee
+++ app/assets/javascripts/app/lib/app_post/task_manager.coffee
@@ -509,6 +509,8 @@

   # reset while tasks
   reset: =>
+    # clear loading tasks
+    App.Delay.clearLevel('task')

     # release touch tasks
     for key, task of @allTasksByKey


added reverce function

added reverce function

--- app/assets/javascripts/todo/controllers/task_ctrl.js.coffee
+++ app/assets/javascripts/todo/controllers/task_ctrl.js.coffee
@@ -52,12 +52,19 @@
     )

   _reverse_order: (old_index, new_index) ->
-    console.log 'reversed'
+    console.log "old_index: #{old_index}"
+    console.log "new_index: #{new_index}"
+    list = @$scope.tasks_list
+    list[new_index].position = old_index
+
+    list.sort (a, b) ->
+      return -1  if a.position < b.position
+      return 1 if a.position > b.position
+      return 0

   _reorder_list: (old_index, new_index) ->
     for index in [old_index..new_index]
       @$scope.tasks_list[index].position = index
-    console.log 'reordered'

   updateTask: (task, field, callback = {}) =>
     params = this._updateParams(task, field)


Adds "locked explanation" preview for locked files

fixes CNVS-17407

Test Plan:
* Enable "Better File Browsing" feature flag
1. Have a file that is restricted with the "UNTIL" date chosen to yesterday
2. Open the file as a teacher, copy the actual link to the file
3. As a student, try to access the file with the link
4. A preview should appear with a message stating that the file is locked

Change-Id: Ied97387c1d8a1d2c93f6c0cad5447d80523f4a24
Reviewed-on: https://gerrit.instructure.com/45826
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Dan Minkevitch <2591e5f46f28d303f9dc027d475a5c60d8dea17a@instructure.com>

--- app/coffeescripts/react_files/components/FilePreview.coffee
+++ app/coffeescripts/react_files/components/FilePreview.coffee
@@ -63,12 +63,12 @@

       visibleFile = props.query.preview and _.findWhere(files, {id: props.query.preview})

-      if !visibleFile and files.length
+      if !visibleFile
         new File({id: props.query.preview}, {preflightUrl: 'no/url/needed'}).fetch(data: $.param({"include":"usage_rights"}) if props.usageRightsRequiredForContext).success (file) ->
           initialItem = new FilesystemObject(file)
           cb?({initialItem, otherItems})
       else
-        initialItem = visibleFile or files[0]
+        initialItem = visibleFile or (files[0] if files.length)
         cb?({initialItem, otherItems})

     stateProperties: (items, props) ->


Clear all the jobs link.

Clear all the jobs link.

--- app/assets/javascripts/jobs_list/views/jobs_list_view.js.coffee
+++ app/assets/javascripts/jobs_list/views/jobs_list_view.js.coffee
@@ -1,7 +1,11 @@
 window.JobsListView = class JobsListView extends Backbone.View
+
+  events:
+    'click p.clear-all a': 'clearAll'

   initialize: ->
     @model.bind 'add', => this.render()
+    @model.bind 'remove', => this.render()

     $(@el).append('
Jobs List
')

@@ -9,11 +13,19 @@

     $(@el).append('
Save jobs here by clicking "Add to jobs list" on a job.')

+    $(@el).append('
Clear
')
+
     $(window).bind 'resize', => this.setHeight()

   setHeight: ->
     height = $(window).height() - 350;
     $(@list).css(maxHeight: height)
+
+  clearAll: ->
+    if confirm("Are you sure you want to remove all the saved jobs in your jobs list?")
+      _.each(jobsList.models.slice(0), (job) -> job.destroy())
+
+    return false

   render: ->
     @list.empty()
@@ -23,6 +35,11 @@
       @list.append(jobView.render().el)
     , this)

+    if jobsList.models.length > 0
+      $(@el).find('p.clear-all').show()
+    else
+      $(@el).find('p.clear-all').hide()
+
     this.setHeight()

     return this


Prevent double submit editable_bool

Prevent double submit editable_bool

--- app/assets/javascripts/ab_admin/core/ui_utils.js.coffee
+++ app/assets/javascripts/ab_admin/core/ui_utils.js.coffee
@@ -68,10 +68,9 @@

 window.initEditableBool = ->
   $(document).on 'click', '.js-auto-submit-checkbox', (e) ->
-    target = $(e.target).closest('.auto-submit-checkbox-wrap')
-    target.removeClass('success', 'error')
-    console.log target
     $el = $(this)
+    $el.attr('disabled', true)
+    $wrap = $el.closest('.auto-submit-checkbox-wrap').removeClass('success', 'error')
     params = {}
     params[$el.attr('name')] = if $el.prop('checked') then 1 else 0
     params['_method'] = $el.data('method') || 'PATCH'
@@ -79,8 +78,7 @@
       data: params
       method: 'POST'
       error: ->
-        console.log target
-        target.addClass('error')
+        $wrap.addClass('error')
       success: ->
-        console.log target
-        target.addClass('success')
+        $wrap.addClass('success')
+        $el.attr('disabled', null)


Do not overwrite already existing window.onerror if exists.

--- app/assets/javascripts/app/lib/app_init/track.js.coffee
+++ app/assets/javascripts/app/lib/app_init/track.js.coffee
@@ -29,10 +29,10 @@

     @log( 'start', 'notice', {} )

-    # start initial submit 10 sec. later to avoid ie10 cookie issues
+    # start initial submit 30 sec. later to avoid ie10 cookie issues
     delay = =>
-      App.Interval.set @send, 60000
-    App.Delay.set delay, 10000
+      App.Interval.set @send, 80000
+    App.Delay.set delay, 30000

     # log clicks
     $(document).bind(
@@ -139,9 +139,15 @@
     @data

 `
-window.onerror = function(errorMsg, url, lineNumber) {
-  console.error(errorMsg + " - in " + url + ", line " + lineNumber);
-};
+(function() {
+  window.onerrorOld = window.onerror
+  window.onerror = function(errorMsg, url, lineNumber) {
+    console.error(errorMsg + " - in " + url + ", line " + lineNumber);
+    if (window.onerrorOld) {
+      window.onerrorOld(errorMsg, url, lineNumber);
+    }
+  }
+}).call(this);

 (function() {
   var console = window.console


add key to training modules in timeline

add key to training modules in timeline

--- app/assets/javascripts/components/training_modules.cjsx
+++ app/assets/javascripts/components/training_modules.cjsx
@@ -49,7 +49,7 @@
           deadlineStatus = "(#{module.deadline_status}, due on #{module.due_date})"

         (
-

+

             {module.name}

               {module.module_progress}


Remove title tag from preview elements

Remove title tag from preview elements

There is no real UX from this title tag, but it leads
to I18n issues and JS errors.

Closes #1670

--- app/assets/javascripts/alchemy/alchemy.preview.js.coffee
+++ app/assets/javascripts/alchemy/alchemy.preview.js.coffee
@@ -1,5 +1,3 @@
-#= require alchemy/alchemy.i18n
-
 window.Alchemy = Alchemy || {}

 Alchemy.initAlchemyPreviewMode = ->
@@ -30,12 +28,10 @@
         @elements = document.querySelectorAll("[data-alchemy-element]")
         @elements.forEach (element) =>
           element.addEventListener 'mouseover', =>
-            element.setAttribute('title', Alchemy.t('click_to_edit'))
             unless element.classList.contains('selected')
               Object.assign element.style, @getStyle('hover')
             return
           element.addEventListener 'mouseout', =>
-            element.removeAttribute('title')
             unless element.classList.contains('selected')
               Object.assign element.style, @getStyle('reset')
             return


Url not URL because i goofed the init

Url not URL because i goofed the init

--- app/assets/javascripts/components/article_form.js.coffee
+++ app/assets/javascripts/components/article_form.js.coffee
@@ -1,13 +1,13 @@
 @ArticleForm = React.createClass
 	getInitialState: ->
-		articleURL: ''
+		articleUrl: ''
 	handleValueChange: (e) ->
 		valueName = e.target.name
 		@setState
 			"#{ valueName }": e.target.value
 			"status": "process"
 	valid: ->
-		@state.articleURL
+		@state.articleUrl
 	handleSubmit: (e) ->
 		e.preventDefault()
 		$.post '', { article: @state }, (data) =>
@@ -24,8 +24,8 @@
 					type: 'text'
 					className: 'form-control'
 					placeholder: 'Enter an articles URL to get some mood music!'
-					name: 'articleURL'
-					value: @state.articleURL
+					name: 'articleUrl'
+					value: @state.articleUrl
 					onChange: @handleValueChange
 				React.DOM.span
 					className: 'input-group-button'


Fix js chat events path

Fix js chat events path

--- app/assets/javascripts/interaction_web_tools/chat.coffee
+++ app/assets/javascripts/interaction_web_tools/chat.coffee
@@ -1,6 +1,5 @@
 window.Chat = class Chat
-  #@EVENTS_PATH: 'interaction_web_tools/events'
-  @EVENTS_PATH: 'http://private-cf542-ininmessages.apiary-mock.com/events'
+  @EVENTS_PATH: 'interaction_web_tools/events'
   @MESSAGES_DIV: '.chat-messages'

   constructor: ->


Added Status.removeBodyClass function

Added Status.removeBodyClass function

--- app/assets/javascripts/sprangular/services/status.coffee
+++ app/assets/javascripts/sprangular/services/status.coffee
@@ -23,6 +23,9 @@
     addBodyClass: (klass) ->
       @bodyClasses[klass] = true

+    removeBodyClass: (klass) ->
+      @bodyClasses[klass] = false
+
     toggleBodyClass: (klass) ->
       @bodyClasses[klass] = !@bodyClasses[klass]


CONFIG: Update contributors list

CONFIG: Update contributors list

--- src/app/config/contributors/contributors.coffee
+++ src/app/config/contributors/contributors.coffee
@@ -4,13 +4,15 @@
 #
 .constant('DoubtfireContributors', [
   'macite'              # Andrew Cain
+  'alexcu'              # Alex Cummaudo
   'apj'                 # Allan Jones
-  'alexcu'              # Alex Cummaudo
+  'jakerenzella'        # Jake Renzella
+  'reubsinit'           # Reuben Wilson
+  'hellola'             # Evo Kellerman
+  'AvDongle'            # Cliff Warren
   'joostfunkekupper'    # Joost Funke Kupper
   'rohanliston'         # Rohan Liston
   'lukehorvat'          # Luke Horvat
-  'hellola'             # Evo Kellerman
-  'AvDongle'            # Cliff Warren

   #
   # TODO: Find out account names for...


[MOD] Changes roster component to new select table style

--- src/thinkspace/client/thinkspace-team-builder/addon/components/roster.coffee
+++ src/thinkspace/client/thinkspace-team-builder/addon/components/roster.coffee
@@ -21,7 +21,7 @@

   columns: ember.computed 'manager', 'model', ->
     [
-      column.create({display: 'Select', component: '__table/cells/selectable', data: {calling: @}}),
+      column.create({display: 'Select', component: '__table/cells/selectable', data: {calling: {component: @}}}),
       column.create({display: 'Last Name',  property: 'last_name'})
       column.create({display: 'First Name', property: 'first_name'}),
       column.create({display: 'Team',       property: 'computed_title'}),