an attempt to fix the security problems discovered last night by gokmen and i

--- kites/sharedHosting/api.coffee
+++ kites/sharedHosting/api.coffee
@@ -13,6 +13,7 @@
 Kite      = require 'kite-amqp'
 mkdirp    = require 'mkdirp'
 coffee    = require 'coffee-script'
+bash      = require 'koding-bash-user-glue'

 createTmpDir = require './createtmpdir'

@@ -32,7 +33,8 @@
                                                                  .replace(/\s/g, '\\ ')

 makedirp = (path, user, cb)->
-  exec "mkdir -p #{path} && chown #{user}: #{path}", cb
+  userInputs = [path, user, path]
+  exec bash("mkdir -p %s && chown %s: %s", userInputs), cb

 createAppsDir = (user, cb)->
   path = escapePath "/Users/#{user}/Applications"
@@ -246,17 +248,17 @@
       makedirp appRootPath, username, (err)->
         if err then cb err
         else
-          exec "test -d #{versionedPath}", (err, stdout, stderr)->
+          exec bash("test -d %s", [versionedPath]), (err, stdout, stderr)->
             unless err or stderr.length then cb "[ERROR] Version is already published, change version and try again!"
             else
-              exec "cp -r #{userAppPath} #{versionedPath}", (err, stdout, stderr)->
+              exec bash("cp -r %s %s", [userAppPath, versionedPath]), (err, stdout, stderr)->
                 if err or stderr then cb err
                 else
                   manifestPath = "#{versionedPath}/.manifest"
-                  exec "cat #{manifestPath}", (err, stdout, stderr)->
+                  exec bash("cat %s", [manifestPath]), (err, stdout, stderr)->
                     if err or stderr then cb err
                     else
-                      exec "rm -f #{manifestPath}", ->
+                      exec bash("rm -f %s", [manifestPath]), ->
                         manifest = JSON.parse stdout
                         manifest.author = "#{profile.firstName} #{profile.lastName}"
                         manifest.authorNick = username
@@ -281,11 +283,13 @@
       callback? new AuthorizationError username
       return false

-    exec "test -d #{kpmAppPath}", (err, stdout, stderr)->
+    exec bash("test -d %s", [kpmAppPath]), (err, stdout, stderr)->
       if err or stderr.length
         cb "[ERROR] App files not found! Download cancelled."
       else
-        exec "mv #{userAppPath} #{backupPath} && cp -r #{kpmAppPath}/ #{userAppPath} && chown -R #{username}: #{userAppPath}", (err, stdout, stderr)->
+        userInputs = [userAppPath, backupPath, kpmAppPath, userAppPath, userAppPath]
+        cmd = bash("mv %s %s && cp -r %s/ %s && chown -R #{username}: %s", userInputs)
+        exec cmd, (err, stdout, stderr)->
           if err or stderr then cb err
           else
             cb null
@@ -310,11 +314,14 @@
       makedirp appPath, username, (err)->
         if err then cb err
         else
-          exec "cp #{kpmAppPath}/index.js #{appPath} && cp #{kpmAppPath}/.manifest #{appPath}", (err, stdout, stderr)->
+          userInputs = [kpmAppPath, appPath, kpmAppPath, appPath]
+          cmd1 = bash "cp %s/index.js %s && cp %s/.manifest %s", userInputs
+          exec cmd1, (err, stdout, stderr)->
             if err or stderr.length
               cb err or "[ERROR] #{stderr}"
             else
-              exec "chown -R #{username}: #{appPath}", (err, stdout, stderr)->
+              cmd2 = bash "chown -R #{username}: %s", [appPath]
+              exec cmd2, (err, stdout, stderr)->
                 if err or stderr.length
                   cb err or "[ERROR] #{stderr}"
                 else
@@ -336,11 +343,13 @@
       console.error err if err
       callback? err, null

-    exec "test -d #{versionedPath}", (err, stdout, stderr)->
+    exec bash("test -d %s", [versionedPath]), (err, stdout, stderr)->
       if err or stderr.length
         cb "[ERROR] Version is not exists!", version
       else
-        exec "rm -f #{latestPath} && ln -s #{versionedPath} #{latestPath}", (err, stdout, stderr)->
+        userInputs = [latestPath, versionedPath, latestPath]
+        cmd = bash "rm -f %s && ln -s %s %s", userInputs
+        exec cmd, (err, stdout, stderr)->
           if err or stderr then cb err
           else cb null

@@ -450,11 +459,13 @@

     domainName ?= "#{username}.#{config.defaultDomain}"
     targetPath = "/Users/#{username}/Sites/#{domainName}"
-    cmd        = "mkdir -p #{targetPath} && cp -r #{config.defaultVhostFiles}/website #{targetPath} && chown #{uid}:#{uid} -R #{targetPath}/*"
-    cmd       += " && echo 'curl https://koding.com/koding-announcement.txt' > /Users/#{username}/.bashrc && chown #{uid}:#{uid} /Users/#{username}/.bashrc"
+
+    userInputs = [targetPath, targetPath, uid, uid, targetPath, uid, uid]
+    cmd        = "mkdir -p %s && cp -r #{config.defaultVhostFiles}/website %s && chown %s:%s -R %s/*"
+    cmd       += " && echo 'curl https://koding.com/koding-announcement.txt' > /Users/#{username}/.bashrc && chown %s:%s /Users/#{username}/.bashrc"
     log.debug "executing CreateVhost:",cmd

-    exec cmd,(err,stdout,stderr)->
+    exec bash(cmd, userInputs), (err,stdout,stderr)->
       unless err
         callback null, "vhost created with default files:",domainName
       else
@@ -472,8 +483,9 @@
     #

     {username,newPassword} = options
-
-    chpw = exec "echo '#{newPassword}' | /usr/bin/passwd --stdin #{username}", (err,stdout,stderr)=>
+    userInputs = [newPassword]
+    cmd = bash "echo %s | /usr/bin/passwd --stdin #{username}", userInputs
+    chpw = exec cmd, (err,stdout,stderr)=>
       if err?
         log.error "[ERROR] can't set password for #{username}: #{stderr}"
         callback? "[ERROR] can't set password for #{username}: #{stderr}"
@@ -492,39 +504,50 @@
     #     * move compressed archive to config.suspendDir
     #
     # options =
-    #   username    : String #username of the unix user
-    #
-
-    {username} = options
-    homeDir = nodePath.join config.usersPath,username
-
-    killProc = exec "/usr/bin/pkill -u #{username} -s9", (err,stdout,stderr)->
-      if stderr # cant use err there becasue pkill return 1 if no processes was running under #{username}
+    #   userToSuspend    : String #userToSuspend of the unix user
+    #
+
+    {username, userToSuspend} = options
+    homeDir = nodePath.join config.usersPath,userToSuspend
+
+    # did i really have to write a line like this? C.T.
+    return unless username in ['chris', 'devrim', 'gokmen']
+
+    userInputs = [userToSuspend]
+    cmd1 = bash "/usr/bin/pkill -u %s -s9", userInputs
+    killProc = exec cmd1, (err,stdout,stderr)->
+      if stderr # cant use err there becasue pkill return 1 if no processes was running under #{userToSuspend}
         # this should never happens...
-        log.error "[ERROR] can't kill processes for  #{username}: #{stderr}"
-        callback? "[ERROR] can't kill processes for  #{username}: #{stderr}"
-      else
-        log.debug "[OK] func:suspendUser: /usr/bin/pkill -u #{username} -s9"
-        compress = exec "tar -v -C #{config.usersPath} -czf #{config.suspendDir}/#{username}.tar.gz #{username}",(err,stdout,stderr) ->
+        log.error "[ERROR] can't kill processes for  #{userToSuspend}: #{stderr}"
+        callback? "[ERROR] can't kill processes for  #{userToSuspend}: #{stderr}"
+      else
+        log.debug "[OK] func:suspendUser: /usr/bin/pkill -u #{userToSuspend} -s9"
+        userInputs = [userToSuspend, userToSuspend]
+        cmd2 = bash "tar -v -C #{config.usersPath} -czf #{config.suspendDir}/%s.tar.gz %s", userInputs
+        compress = exec cmd2,(err,stdout,stderr) ->
           if err?
-            log.error "[ERROR] can't creaate archive #{config.suspendDir}/#{username}.tar.gz: #{stderr}"
-            callback? "[ERROR] can't creaate archive #{config.suspendDir}/#{username}.tar.gz: #{stderr}"
+            log.error "[ERROR] can't creaate archive #{config.suspendDir}/#{userToSuspend}.tar.gz: #{stderr}"
+            callback? "[ERROR] can't creaate archive #{config.suspendDir}/#{userToSuspend}.tar.gz: #{stderr}"
           else
-            log.debug "[OK] func:suspendUser: tar -v -C #{config.usersPath} -czf #{config.suspendDir}/#{username}.tar.gz #{username}"
-            lock = exec "/usr/sbin/usermod -L #{username}", (err,stdout,stderr) ->
+            log.debug "[OK] func:suspendUser: tar -v -C #{config.usersPath} -czf #{config.suspendDir}/#{userToSuspend}.tar.gz #{userToSuspend}"
+            userInputs = [userToSuspend]
+            cmd3 = bash "/usr/sbin/usermod -L %s", userInputs
+            lock = exec cmd3, (err,stdout,stderr) ->
               if err?
-                log.error "[ERROR] can't lock user #{username}: #{stderr}"
-                callback? "[ERROR] can't lock user #{username}: #{stderr}"
+                log.error "[ERROR] can't lock user #{userToSuspend}: #{stderr}"
+                callback? "[ERROR] can't lock user #{userToSuspend}: #{stderr}"
               else
                 # Measure thrice and cut once
                 if homeDir is config.usersPath
                   log.error "[ERROR] can't remove this dir #{homeDir}"
                   callback? "[ERROR] can't remove this dir #{homeDir}"
                 else
-                  rmHome = exec "/bin/rm -r #{homeDir}",(err,stdout,stderr)->
+                  userInputs = [homeDir]
+                  cmd4 = bash "/bin/rm -r %s", userInputs
+                  rmHome = exec cmd4,(err,stdout,stderr)->
                     if err?
-                      log.error "[ERROR] cant remove homedir #{homeDir} for user #{username}"
-                      callback? "[ERROR] cant remove homedir #{homeDir} for user #{username}"
+                      log.error "[ERROR] cant remove homedir #{homeDir} for user #{userToSuspend}"
+                      callback? "[ERROR] cant remove homedir #{homeDir} for user #{userToSuspend}"
                     else
                       log.debug "[OK] func:suspendUser: /bin/rm -r #{homeDir}"
                       log.info "[OK] user was sucsessfully suspended"
@@ -539,31 +562,42 @@
     #     * remove archive from config.suspendDir
     #
     # options =
-    #   username    : String #username of the unix user
-    #
-    {username} = options
-    homeDir = nodePath.join config.usersPath,username
-    unlock = exec "/usr/sbin/usermod -U #{username}", (err,stdout,stderr)->
+    #   userToSuspend    : String #userToSuspend of the unix user
+    #
+    {userToSuspend, username} = options
+
+    return unless username in ['chris', 'devrim', 'gokmen']
+
+    homeDir = nodePath.join config.usersPath,userToSuspend
+
+    cmd1 = bash "/usr/sbin/usermod -U %s", [userToSuspend]
+    unlock = exec cmd1, (err,stdout,stderr)->
       if err?
-        callback? "[ERROR] can't unlock user #{username}: #{stderr}"
-      else
-        log.debug "[OK] func:unSuspendUser: /usr/sbin/usermod -U #{username}"
-        uncompress = exec "tar -C #{config.usersPath} -xzf #{config.suspendDir}/#{username}.tar.gz",(err,stdout,stderr)->
+        callback? "[ERROR] can't unlock user #{userToSuspend}: #{stderr}"
+      else
+        log.debug "[OK] func:unSuspendUser: /usr/sbin/usermod -U #{userToSuspend}"
+        userInputs = [userToSuspend]
+        cmd2 = bash "tar -C #{config.usersPath} -xzf #{config.suspendDir}/%s.tar.gz", userInputs
+        uncompress = exec cmd2,(err,stdout,stderr)->
           if err?
-            callback? "[ERROR] can't uncompress user's homedir #{config.suspendDir}/#{username}.tar.gz: #{stderr}"
+            callback? "[ERROR] can't uncompress user's homedir #{config.suspendDir}/#{userToSuspend}.tar.gz: #{stderr}"
           else
-            log.debug "[OK] func:unSuspendUser: tar -C #{config.usersPath} -xzf #{config.suspendDir}/#{username}.tar.gz"
-            rmarchive = exec "rm #{config.suspendDir}/#{username}.tar.gz",(err,stdout,stderr)->
-            if err?
-              e = "[ERROR] can't remove archive #{config.suspendDir}/#{username}.tar.gz: #{stderr}"
-              log.error e; callback? e
-            else
-              log.debug "[OK] func:unSuspendUser: rm #{config.suspendDir}/#{username}.tar.gz"
-              remount = exec "/usr/sbin/cagefsctl -w #{username}",(err,stdout,stderr)->
-                if err?
-                  e = "[ERROR] can't remount user #{username}: #{stderr}"
-                  log.error e; callback? e
-                else
-                  log.debug "[OK] func:unSuspendUser: /usr/sbin/cagefsctl -w #{username}"
-                  res = "[OK] user #{username} was successfully unsuspended"
-                  log.info res; callback? null, res
+            log.debug "[OK] func:unSuspendUser: tar -C #{config.usersPath} -xzf #{config.suspendDir}/#{userToSuspend}.tar.gz"
+            userInputs = [userToSuspend]
+            cmd3 = bash "rm #{config.suspendDir}/%s.tar.gz", userInputs
+            rmarchive = exec cmd3, (err,stdout,stderr)->
+              if err?
+                e = "[ERROR] can't remove archive #{config.suspendDir}/#{userToSuspend}.tar.gz: #{stderr}"
+                log.error e; callback? e
+              else
+                log.debug "[OK] func:unSuspendUser: rm #{config.suspendDir}/#{userToSuspend}.tar.gz"
+                userInputs = [userToSuspend]
+                cmd4 = bash "/usr/sbin/cagefsctl -w %s", userInputs
+                remount = exec cmd4,(err,stdout,stderr)->
+                  if err?
+                    e = "[ERROR] can't remount user #{userToSuspend}: #{stderr}"
+                    log.error e; callback? e
+                  else
+                    log.debug "[OK] func:unSuspendUser: /usr/sbin/cagefsctl -w #{userToSuspend}"
+                    res = "[OK] user #{userToSuspend} was successfully unsuspended"
+                    log.info res; callback? null, res


fixed bug with Generator.generate - if one directory had no files, the remaining directories wouuldn't be processed

--- src/Generation/Generator.coffee
+++ src/Generation/Generator.coffee
@@ -33,6 +33,8 @@
             generator.generate()
             .then ->
               Generator.watch root, merge options, lastGenerator: generator if options.watch
+            .catch (error) ->
+              log "Error: ".red, foo

       promiseSequence filePromiseGenerators

@@ -146,6 +148,5 @@
       if files.length == 0
         error = "no .coffee files found"
         @log error.yellow.bold
-        throw new Error error
       else
         @generateFromFiles files


Eliminate benchmark for outdated `OnigRegExp.getCaptureTree` code

--- benchmark/benchmark-suite.coffee
+++ benchmark/benchmark-suite.coffee
@@ -92,14 +92,3 @@

     benchmark "construction", 20, ->
       new TokenizedBuffer(buffer, { languageMode, tabLength: 2})
-
-describe "OnigRegExp.", ->
-  [regexes, line] = []
-
-  beforeEach ->
-    line = "  l.comment_matcher = new RegExp('^\\s*' + l.symbol + '\\s?')"
-    regexes = _.pluck(TextMateBundle.grammarForFileName('medium.coffee').initialRule.getIncludedPatterns(), "regex")
-
-  benchmark ".getCaptureTree", 10000, ->
-    OnigRegExp.captureIndices(line, 22, regexes)
-


Stricter checking for local server URLs.

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -369,7 +369,7 @@
 	not /^([a-zA-Z]{1,8}:\/\/).*$/.test(url)

 Utils.isLocalServerUrl = (url) ->
-	return url.indexOf("127.0.0.1") isnt -1 or url.indexOf("localhost")  isnt -1
+	return /[a-zA-Z]{1,8}:\/\/127\.0\.0\.1/.test(url) or /[a-zA-Z]{1,8}:\/\/localhost/.test(url)

 Utils.isLocalUrl = (url) ->
 	return true if Utils.isFileUrl(url)


grenade for disabling right click on artwork images

--- lib/global_client_setup.coffee
+++ lib/global_client_setup.coffee
@@ -28,6 +28,7 @@
   listenForBounce()
   confirmation.check()
   setupAnalytics()
+  disableRightClick()

 ensureFreshUser = (data) ->
   return unless sd.CURRENT_USER
@@ -85,3 +86,16 @@
     'X-XAPP-TOKEN': sd.ARTSY_XAPP_TOKEN
     'X-ACCESS-TOKEN': sd.CURRENT_USER?.accessToken
   window[key] = helper for key, helper of templateModules
+
+# We tell our partners, importantly institutions like Museums, that we'll
+# disable right click to "protect" their images. Yes, it's just smoke and
+# mirrors, but it makes them _feel_ better so please be mindful of that.
+disableRightClick = ->
+  # Allow it for admins (e.g. it's super annoying to the design team)
+  return if sd.CURRENT_USER?.type is 'Admin'
+  $(document).on 'contextmenu', (e) ->
+    # Grenade for selecting any images with the name 'artwork' in their,
+    # or their parent's, class
+    disable = $(e.target).is('[class*=artwork]') or
+              $(e.target).parent().is('[class*=artwork]')
+    return false if disable


Groups: Invitation code max usage validations are added

--- client/Dashboard/views/groupsinvitationview.coffee
+++ client/Dashboard/views/groupsinvitationview.coffee
@@ -83,6 +83,13 @@
             itemClass     : KDInputView
             name          : "maxUses"
             placeholder   : "How many times can this code be redeemed?"
+            validate      :
+              rules       :
+                required  : yes
+                regExp    : /\d+/i
+              messages    :
+                required  : 'Max usage is required'
+                regExp    : 'numbers only please'
           memo            :
             label         : "Memo"
             itemClass     : KDInputView


Make sure line is deleted

Make sure line is deleted

--- lib/highlight-line-view.coffee
+++ lib/highlight-line-view.coffee
@@ -32,6 +32,7 @@
   deactivate: ->
     for line in lines
       line.destroy()
+      line = null
     lines = []
     atom.workspaceView.off 'highlight-line:toggle-background'
     atom.workspaceView.off 'highlight-line:toggle-underline'


virtualization controller: close the modal on cancel

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -371,6 +371,7 @@
       overlay : yes

     workflow.on 'DataCollected', -> debugger
+    workflow.on 'Cancel', -> modal.destroy()

     workflow.enter()


HelpSupportModal: put uploaded log link instead of content [completes #89350576]

--- client/app/lib/commonviews/helpsupportmodal.coffee
+++ client/app/lib/commonviews/helpsupportmodal.coffee
@@ -1,5 +1,5 @@
 $ = require 'jquery'
-parseLogs = require '../util/parseLogs'
+uploadLogs = require '../util/uploadLogs'
 kd = require 'kd'
 KDModalViewWithForms = kd.ModalViewWithForms
 KDNotificationView = kd.NotificationView
@@ -53,7 +53,8 @@
       {submit} = @modalTabs.forms.Main.buttons
       {subject, message} = form

-      message += "\n\n --- LOGS FROM parseLogs() --- \n #{parseLogs()}"
+      if @_logUrl?
+        message += "\n\n --- LOGS: #{@_logUrl} --- \n"

       request = $.ajax "#{global.location.origin}/-/support/new",
         type        : "POST"
@@ -72,6 +73,9 @@
         new KDNotificationView title: "Sorry, could not process your request, please try again."
         submit.hideLoader()
         @ticketRequested = no
+
+    uploadLogs (err, logUrl)=>
+      @_logUrl = logUrl  if not err and logUrl?

   @getTopics = ->


Fix typo

Fix typo

--- client/ide/lib/videocollaborationcontroller.coffee
+++ client/ide/lib/videocollaborationcontroller.coffee
@@ -30,7 +30,7 @@
     if /^((?!chrome|android).)*safari/i.test userAgent
       showError """
         The video component is not supported for Safari.<br />
-        To enabled this  functionality, please use Google Chrome or Firefox.
+        To enable this functionality, please use Google Chrome or Firefox.
       """
       return


prevent downcasing of fair booth sections

--- lib/middleware/downcase.coffee
+++ lib/middleware/downcase.coffee
@@ -1,7 +1,9 @@
 module.exports = (req, res, next) ->
   url = req._parsedUrl

-  if /[A-Z]/.test url.pathname
+  if url.pathname.includes '/browse/booths/section/'
+    next()
+  else if /[A-Z]/.test url.pathname
     res.redirect 301, url.pathname.toLowerCase() + (url.search or '')
   else
     next()


Try http url for travis/public friendliness

--- lib/new/clone-blueprint.coffee
+++ lib/new/clone-blueprint.coffee
@@ -2,7 +2,7 @@
 exec = require("child_process").execFile

 module.exports = (name, cb) ->
-  cloneGitRepo("git@github.com:cabbagejs/blueprint-simple.git", name, cb)
+  cloneGitRepo("https://github.com/cabbagejs/blueprint-simple.git", name, cb)

 cloneGitRepo = (gitRepo, name, cb) ->
   exec(which.sync('git'), ["clone", gitRepo, name], {cwd: process.cwd()}, cb)


TinyMCE RTL in course bulk email

TinyMCE RTL in course bulk email

--- common/lib/xmodule/xmodule/js/src/html/edit.coffee
+++ common/lib/xmodule/xmodule/js/src/html/edit.coffee
@@ -66,6 +66,8 @@
         schema: "html5",
         # Necessary to preserve relative URLs to our images.
         convert_urls : false,
+        # Sniff UI direction from `.wrapper-view` in studio or `.window-wrap` in LMS
+        directionality: $(".wrapper-view, .window-wrap").prop('dir'),
         content_css : tiny_mce_css_links.join(", "),
         formats : {
           # tinyMCE does block level for code by default
@@ -91,7 +93,7 @@
         height: '400px',
         menubar: false,
         statusbar: false,
-
+
         # Necessary to avoid stripping of style tags.
         valid_children : "+body[style]",

@@ -99,7 +101,7 @@
         valid_elements: "*[*]",
         extended_valid_elements: "*[*]",
         invalid_elements: "",
-
+
         setup: @setupTinyMCE,
         # Cannot get access to tinyMCE Editor instance (for focusing) until after it is rendered.
         # The tinyMCE callback passes in the editor as a parameter.


Add copy icon to keymaps in package view

Closes #166

--- lib/package-keymap-view.coffee
+++ lib/package-keymap-view.coffee
@@ -1,5 +1,6 @@
+path = require 'path'
 _ = require 'underscore-plus'
-{$$$, View} = require 'atom'
+{$, $$$, View} = require 'atom'

 # Displays the keybindings for a package namespace
 module.exports =
@@ -18,14 +19,40 @@
   initialize: (namespace) ->
     otherPlatformPattern = new RegExp("\\.platform-(?!#{_.escapeRegExp(process.platform)}\\b)")

-    for {command, keystrokes, selector} in atom.keymap.getKeyBindings()
+    for keyBinding in atom.keymap.getKeyBindings()
+      {command, keystrokes, selector} = keyBinding
       continue unless command?.indexOf?("#{namespace}:") is 0
       continue if otherPlatformPattern.test(selector)

-      @keybindingItems.append $$$ ->
+      keyBindingView = $$$ ->
         @tr =>
-          @td keystrokes
+          @td =>
+            @span class: 'icon icon-clippy copy-icon'
+            @span keystrokes
           @td command
           @td selector
+      keyBindingView = $(keyBindingView)
+      keyBindingView.data('keyBinding', keyBinding)
+
+      @keybindingItems.append(keyBindingView)

     @hide() unless @keybindingItems.children().length > 0
+
+    @on 'click', '.copy-icon', ({target}) =>
+      keyBinding = $(target).closest('tr').data('keyBinding')
+      @writeKeyBindingToClipboard(keyBinding)
+
+  writeKeyBindingToClipboard: ({selector, keystrokes, command}) ->
+    keymapExtension = path.extname(atom.keymap.getUserKeymapPath())
+    if keymapExtension is '.cson'
+      content = """
+        '#{selector}':
+          '#{keystrokes}': '#{command}'
+      """
+    else
+      content = """
+        "#{selector}": {
+          "#{keystrokes}": "#{command}"
+        }
+      """
+    atom.clipboard.write(content)


Fix tooltip.coffee some bugs

Fix tooltip.coffee some bugs

--- vendor/assets/javascripts/mice/tooltip.coffee
+++ vendor/assets/javascripts/mice/tooltip.coffee
@@ -43,7 +43,7 @@
         actualHeight = $tip[0].offsetHeight
         gravity = maybeCall @.options.gravity, @.$element[0]

-        switch gravity.chartAt(0)
+        switch gravity.charAt(0)
           when 'n'
             tp = {top: pos.top + pos.height + @.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
           when 's'
@@ -54,10 +54,13 @@
             tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + @.options.offset};

         if gravity.length == 2
-          if gravity.chartAt(1) == 'w' then (tp.left = pos.left + pos.width / 2 - 15) else (tp.left = pos.left + pos.width / 2 - actualWidth + 15)
+          if gravity.charAt(1) == 'w'
+            tp.left = pos.left + pos.width / 2 - 15
+          else
+            tp.left = pos.left + pos.width / 2 - actualWidth + 15

         $tip.css(tp).addClass('tipsy-' + gravity)
-        $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.chartAt(0)
+        $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0)
         if @.options.className then $tip.addClass(maybeCall @.options.className, @.$element[0])

         if @.options.fade
@@ -113,13 +116,13 @@
       if tipsy then tipsy[options]()
       return @

-    options = $.extend({}, $.fn.tipsy.default, options)
+    options = $.extend({}, $.fn.tipsy.defaults, options)

     get = (element) ->
       tipsy = $.data(element, 'tipsy')
       if !tipsy
         tipsy = new Tipsy(element, $.fn.tipsy.elementOptions(element, options))
-        $.data(element, tipsy, tipsy)
+        $.data(element, 'tipsy', tipsy)
       return tipsy

     enter = ->
@@ -139,7 +142,11 @@
       else
         setTimeout((-> tipsy.hide() if tipsy.hoverState == 'out' ), options.delayOut)

-    @.each( -> get @ ) if !options.live
+    if !options.live
+      @.each( ->
+        get(@)
+        return
+      )

     if options.trigger != 'manual'
       binder  = if options.live then 'live' else 'bind'


Make it easier to override the serialize and deserialize methods

--- lib/components/uploader/DropzoneComponent.coffee
+++ lib/components/uploader/DropzoneComponent.coffee
@@ -121,7 +121,15 @@
     # update the `multiple` flag
     if settings.maxFiles > 1 then @multiple = true

-    settings
+    # the settings object contains mostly DropzoneJS params,
+    # but also allows to override the serialize and deserialize methods
+    if settings.serialize and _.isFunction(settings.serialize)
+      @serialize = settings.serialize
+
+    if settings.deserialize and _.isFunction(settings.deserialize)
+      @deserialize = settings.deserialize
+
+    _.omit settings, 'serialize', 'deserialize'

   ###


fix docblockr tag inside star comment

fix docblockr tag inside star comment

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -729,13 +729,13 @@
   'docblockr-comment':
     'patterns': [
       {
-        'match': '[\\s*]*(@(error|ERROR))'
+        'match': '(@(error|ERROR))\\b'
         'captures':
           '1':
             'name': 'invalid.illegal.name.stata'
       }
       {
-        'match': '^[\\s*]*(@[A-za-z0-9]+)'
+        'match': '(@[A-za-z0-9]+)\\b'
         'captures':
           '1':
             'name': 'keyword.docblockr.stata'


Fixes api.model

Fixes api.model

--- src/client/component/api.coffee
+++ src/client/component/api.coffee
@@ -22,7 +22,8 @@
         hullApi = core.data.hullApi

         core.data.api = (args...)->
-          hullApi.api( args...).then (res, headers={})->
+          dfd = hullApi.api( args...)
+          dfd.then (res, headers={})->
             if headers?
               hullTrack = headers['Hull-Track']
               if hullTrack
@@ -33,7 +34,7 @@
                   console.warn 'Error', error
               if headers['Hull-Auth-Scope']
                 authScope = headers['Hull-Auth-Scope'].split(':')[0]
-
+          dfd
         #
         #
         # Models/Collection related
@@ -231,6 +232,7 @@
           core.data.api("me/identities/#{provider}", 'delete').then ->
             app.sandbox.data.api.model('me').fetch().then callback

+        # FIXME Does it double with l.247-249?
         for m in ['me', 'app', 'org', 'entity']
           attrs = data[m]
           if attrs
@@ -247,8 +249,8 @@
         rawFetch('app', true);
         rawFetch('org', true);

-        # core.mediator.on    'hull.login',       clearModelsCache
-        # core.mediator.on    'hull.logout',      clearModelsCache
+        core.mediator.on    'hull.login',       clearModelsCache
+        core.mediator.on    'hull.logout',      clearModelsCache

         # core.mediator.on    'hull.login',       ->
         #   core.data.hullApi.track 'hull.auth.login',  core.data.api.model('me').toJSON()


Home: removed home appearance on other routes

--- client/landing/Intro/home.coffee
+++ client/landing/Intro/home.coffee
@@ -35,6 +35,8 @@
     @markers = new MarkerController

   show:->
+
+    @appendToDomBody()  unless document.getElementById 'home-page'

     @unsetClass 'out'
     document.body.classList.add 'intro'
@@ -208,4 +210,6 @@
     """

 KD.introView = new HomePage
-KD.introView.appendToDomBody()
+
+if location.hash in ['#!/Home', '/', '']
+  KD.introView.show()


newfiles: alert screenreader if upload fails

fixes: CNVS-17035

test plan:
Turn on New files feature
1. Navigate to Course --> Files
2. Upload a file so that it exceeds file size for a course
3. JAWS should say "file size exceeds quota"

Change-Id: Iaa19d65a2043788533addfb27a9eb7f6988b78d7
Reviewed-on: https://gerrit.instructure.com/44771
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Cosme Salazar <d37f5e06cd46b8fd23ce5553334e09ede5394232@instructure.com>

--- app/coffeescripts/react_files/modules/BaseUploader.coffee
+++ app/coffeescripts/react_files/modules/BaseUploader.coffee
@@ -45,6 +45,8 @@
       @deferred = $.Deferred()
       @deferred.fail (failReason) =>
         @error = failReason
+        $.screenReaderFlashError(@error.message) if @error?.message
+
       $.ajaxJSON(@getPreflightUrl(), 'POST', @createPreFlightParams(), @onPreflightComplete, @deferred.reject)
       @deferred.promise()


fix black/white toggling; add instructions

--- src/coffee/client/puzzle.coffee
+++ src/coffee/client/puzzle.coffee
@@ -181,9 +181,11 @@

     toggleOpenness: () ->
         if @state.grid_focus != null
+            grid_focus = Utils.clone @state.grid_focus
             grid_focus.field_open = "none"
-            row = @state.grid_focus.row
-            col = @state.grid_focus.col
+            @setState { grid_focus: grid_focus }
+            row = grid_focus.row
+            col = grid_focus.col
             newvalue = not @state.puzzle.grid[row][col].open
             op = Ot.opEditCellValue row, col, "open", newvalue
             if @state.maintainRotationalSymmetry
@@ -328,6 +330,14 @@

         else

+

+                  Commands:

+                  Ctrl+b to toggle a cell between black/white

+                  Type any letter to enter into the selected cell

+                  Hit backspace to empty a cell

+                  Ctrl+u to enter arbitrary text into a cell (not restricted to a single letter)

+                  Ctlr+i to edit the number of the cell (but the easiest way to set the numbers is the button at the bottom)
+

                     <PuzzleGrid


Finish to document BaseService

Finish to document BaseService

--- src/coffee/services/base.coffee
+++ src/coffee/services/base.coffee
@@ -349,15 +349,25 @@
           .done()
       _processPage(@_params.query.page or 1, @_params.query.perPage or 20)

-  ###*
-   * Save a new resource by sending a payload to the _currentEndpoint, describing
-   * the new resource model.
-   * If the `id` was provided, the API expects the request to be an update by
-   * by providing a payload of {UpdateAction}.
-   * @param {Object} body The payload as JSON object
-   * @throws {Error} If body is not given
-   * @return {Promise} A promise, fulfilled with an {Object} or rejected with a {SphereError}
-  ###
+  # Public: Save a new resource defined by the `Service` by passing the payload {Object}.
+  #
+  # body - {Object} The payload described by the related API resource as JSON
+  #
+  # Throws an {Error} if `body` is missing
+  #
+  # Returns a {Promise}, fulfilled with an {Object} or rejected with an instance of an {Error}
+  #
+  # Examples
+  #
+  #   service = client.products()
+  #   service.save
+  #     name:
+  #       en: 'Foo'
+  #     slug:
+  #       en: 'foo'
+  #     productType:
+  #       id: '123'
+  #       typeId: 'product-type'
   save: (body) ->
     unless body
       throw new Error "Body payload is required for creating a resource (endpoint: #{@constructor.baseResourceEndpoint})"
@@ -365,17 +375,32 @@
     endpoint = @constructor.baseResourceEndpoint
     @_save(endpoint, body)

-  ###*
-   * Alias for {@link save}.
-  ###
+  # Public: Alias of {::save}
   create: -> @save.apply(@, arguments)

-  ###*
-   * Alias for {@link save}, as it's the same type of HTTP request.
-   * Updating a resource is done by sending a list of {UpdateAction}.
-   * (more intuitive way of describing an update, given that an [id] is provided)
-   * @example `{service}.byId({id}).update({actions})`
-  ###
+  # Public: Update a resource defined by the `Service` by passing the payload {Object}
+  # with [UpdateAction](http://dev.sphere.io/http-api.html#partial-updates).
+  # The `id` of the resource must be provided with {::byId}.
+  #
+  # body - {Object} The payload described by the related API resource as JSON
+  #
+  # Throws an {Error} if resource `id` is missing
+  # Throws an {Error} if `body` is missing
+  #
+  # Returns a {Promise}, fulfilled with an {Object} or rejected with an instance of an {Error}
+  #
+  # Examples
+  #
+  #   service = client.products()
+  #   service.byId('123').update
+  #     version: 2
+  #     actions: [
+  #       {
+  #         action: 'changeName'
+  #         name:
+  #           en: Bar
+  #       }
+  #     ]
   update: (body) ->
     throw new Error "Missing resource id. You can set it by chaining '.byId(ID)'" unless @_params.id
     unless body
@@ -384,13 +409,18 @@
     endpoint = @_currentEndpoint
     @_save(endpoint, body)

-  ###*
-   * Delete an existing resource of the _currentEndpoint
-   * If the `id` was provided, the API expects this to be a resource update with given {UpdateAction}
-   * @param {Number} version The current version of the resource
-   * @throws {Error} If version is not given
-   * @return {Promise} A promise, fulfilled with an {Object} or rejected with a {SphereError}
-  ###
+  # Public: Delete an existing resource defined by the `Service`
+  #
+  # version - {Number} The current version of the resource to delete
+  #
+  # Throws an {Error} if `version` is missing
+  #
+  # Returns a {Promise}, fulfilled with an {Object} or rejected with an instance of an {Error}
+  #
+  # Examples
+  #
+  #   service = client.products()
+  #   service.byId('123').delete(2)
   delete: (version) ->
     # TODO: automatically fetch the resource if no version is given?
     # TODO: describe which endpoints support this?
@@ -400,11 +430,11 @@
     endpoint = "#{@_currentEndpoint}?version=#{version}"
     @_delete(endpoint)

-  ###*
-   * Return a {Promise} for a GET call. It can be overridden for custom logic.
-   * @param {String} endpoint The resource endpoint
-   * @return {Promise} A promise, fulfilled with an {Object} or rejected with a {SphereError}
-  ###
+  # Private: Return a {Promise} for a GET call. It can be overridden for custom logic.
+  #
+  # endpoint - {String} The resource endpoint
+  #
+  # Returns a {Promise}, fulfilled with an {Object} or rejected with an instance of an {Error}
   _get: (endpoint) ->
     @_setDefaults()
     @_task.addTask =>
@@ -414,11 +444,11 @@
         @_rest.GET endpoint, =>
           @_wrapResponse.apply(@, [resolve, reject, originalRequest].concat(_.toArray(arguments)))

-  ###*
-   * Return a {Promise} for a PAGED call. It can be overridden for custom logic.
-   * @param {String} endpoint The resource endpoint
-   * @return {Promise} A promise, fulfilled with an {Object} or rejected with a {SphereError}
-  ###
+  # Private: Return a {Promise} for a PAGED call. It can be overridden for custom logic.
+  #
+  # endpoint - {String} The resource endpoint
+  #
+  # Returns a {Promise}, fulfilled with an {Object} or rejected with an instance of an {Error}
   _paged: (endpoint) ->
     @_setDefaults()
     @_task.addTask =>
@@ -429,12 +459,12 @@
         @_rest.PAGED endpoint, =>
           @_wrapResponse.apply(@, [resolve, reject, originalRequest].concat(_.toArray(arguments)))

-  ###*
-   * Return a {Promise} for a POST call. It can be overridden for custom logic.
-   * @param {String} endpoint The resource endpoint
-   * @param {String} payload The body payload as a String
-   * @return {Promise} A promise, fulfilled with an {Object} or rejected with a {SphereError}
-  ###
+  # Private: Return a {Promise} for a POST call. It can be overridden for custom logic.
+  #
+  # endpoint - {String} The resource endpoint
+  # payload - {Object} The body payload as JSON
+  #
+  # Returns a {Promise}, fulfilled with an {Object} or rejected with an instance of an {Error}
   _save: (endpoint, payload) ->
     @_setDefaults()
     @_task.addTask =>
@@ -445,11 +475,11 @@
         @_rest.POST endpoint, payload, =>
           @_wrapResponse.apply(@, [resolve, reject, originalRequest].concat(_.toArray(arguments)))

-  ###*
-   * Return a {Promise} for a DELETE call. It can be overridden for custom logic.
-   * @param {String} endpoint The resource endpoint
-   * @return {Promise} A promise, fulfilled with an {Object} or rejected with a {SphereError}
-  ###
+  # Private: Return a {Promise} for a DELETE call. It can be overridden for custom logic.
+  #
+  # endpoint - {String} The resource endpoint
+  #
+  # Returns a {Promise}, fulfilled with an {Object} or rejected with an instance of an {Error}
   _delete: (endpoint) ->
     @_setDefaults()
     @_task.addTask =>
@@ -459,15 +489,14 @@
         @_rest.DELETE endpoint, =>
           @_wrapResponse.apply(@, [resolve, reject, originalRequest].concat(_.toArray(arguments)))

-  ###*
-   * @private
-   * Wrap responses and decide whether to reject or resolve the promise
-   * @param {Function} resolve The function called to resolve the promise
-   * @param {Function} reject The function called to reject the promise
-   * @param {Object} error An error object when applicable (usually from `http.ClientRequest` object) otherwise `null`
-   * @param {Object} response An `http.IncomingMessage` object containing all kind of information about the request / response
-   * @param {Object} body A JSON object containing the HTTP API resource or error messages
-  ###
+  # Private: Wrap the HTTP response and decide whether to reject or resolve the promise
+  #
+  # resolve - {Function} The function called to `resolve` the {Promise}
+  # reject - {Function} The function called to `reject` the {Promise}
+  # originalRequest - {Object} The object containing information about the request, used when the request fails
+  # error - {Object} An error object when applicable (usually from `http.ClientRequest` object) otherwise `null`
+  # response - {Object} An `http.IncomingMessage` object containing all kind of information about the request / response
+  # body - {Object} A JSON object containing the HTTP API resource or error messages
   _wrapResponse: (resolve, reject, originalRequest, error, response, body) ->
     responseJson =
       if @_stats.includeHeaders


state hooks refactoring

state hooks refactoring

--- src/coffee_state_machine.coffee
+++ src/coffee_state_machine.coffee
@@ -49,13 +49,13 @@

     obj.is_valid_state = (state) -> all_states[state]? and all_states[state].state is state

-    state_builder.enter = (onEnterState..., options) ->
-        fn = if typeof options is 'function' then options else options?.do
-        add_state_hooks "enter", onEnterState, fn
+    create_state_hook = (hook) ->
+        (state..., options) ->
+            fn = if typeof options is 'function' then options else options?.do
+            add_state_hooks hook, state, fn

-    state_builder.exit = (onExitState..., options) ->
-        fn = if typeof options is 'function' then options else options?.do
-        add_state_hooks "exit", onExitState, fn
+    state_builder.enter = create_state_hook "enter"
+    state_builder.exit = create_state_hook "exit"

     origProperties = {}

@@ -67,7 +67,7 @@
         for own k, v of all_states[nextState].properties
             [origProperties[k], obj[k]] = [obj[k], v]
         # invoke state hooks
-        # XXX parents?
+        # TODO don't forget the parents!
         if oldState? and nextState isnt oldState
             on_exit = state_hooks[oldState]?.exit or []
             fn.call obj for fn in on_exit


let users forward their existing domains

--- client/landing/app/Applications/Environments.kdapplication/views/DomainRegisterModalFormView.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/DomainRegisterModalFormView.coffee
@@ -58,70 +58,43 @@

     super options, data

-    KD.remote.api.JVM.fetchVms (err, vms)=>
-      selects = []
-      for i, vmName of vms
-        selects.push {
-          title :"#{vmName}.kd.io"
-          value:"#{vmName}.kd.io"
-          }
-
-      @emit "UserVMMenuReady", selects
-
-
     @on "DomainRegistered", (orderInfo) =>
-      @modalTabs.forms["Connect"].buttons.Next.hideLoader()
-
       @modalTabs.forms["Done"].addSubView (
         new DomainRegistrationCompleteView {orderInfo : orderInfo}
         )

-      @modalTabs.showPaneByIndex "3"
+      @modalTabs.showPaneByIndex "2"

     @on "DomainForwarded", (orderInfo) =>
-      @modalTabs.forms["Connect"].buttons.Next.hideLoader()
-
       @modalTabs.forms["Done"].addSubView (
         new DomainForwardingCompleteView {orderInfo : orderInfo}
         )

-      @modalTabs.showPaneByIndex "3"
+      @modalTabs.showPaneByIndex "2"

   loadSearchDomainPane:->
     @modalTabs.showPaneByIndex 1
     @setData {}
-
+
+    actionState = @modalTabs.forms["Domain Address"].inputs.DomainOption.getValue()

     if actionState is "new"
-
       @modalTabs.forms["Find Your Domain"].addSubView (
         new DomainSearchForm {
           modalTabs  : @modalTabs
           parentData : @getData()
           }
         )
+
     else
-
       @modalTabs.forms["Find Your Domain"].addSubView (
         new DomainForwardForm {
           modalTabs  : @modalTabs
           parentData : @getData()
           }
         )
-
-  saveDomainInformation:->
-    {selectedDomain, newDomain} = @getData()
-
-    if newDomain
-      VMToBind = @modalTabs.forms["Connect"].inputs.SelectVM.getValue()
-      KD.remote.api.JDomain.registerNewDomain {domainAddress:selectedDomain, selectedVM : VMToBind}, (err, orderInfo)=>
-        @emit "DomainRegistered", orderInfo
-    else
-      VMToBind = @modalTabs.forms["Connect"].inputs.SelectVM.getValue()
-      KD.remote.api.JDomain.addNewDNSRecord {domainAddress:selectedDomain, selectedVM : VMToBind}, (err, orderInfo)=>
-        @emit "DomainForwarded", orderInfo

 class DomainSearchForm extends KDScrollView
@@ -151,7 +124,7 @@
         domainName      :
           label         : "Domain Name:"
           name          : "domainName"
-          placeholder   : "Domain (e.g. www.example.com)"
+          placeholder   : "Domain (e.g. example.com)"
           validate      :
             rules       :
               required  : yes
@@ -210,14 +183,14 @@
     {{> @header}}
     {{> @searchForm}}
     {{> @domainSearchResultView}}
-
     """

   viewAppended: JView::viewAppended

 class DomainForwardForm extends KDView
-  constructor:(options = {}, data)->
+
+  constructor:(options={}, data)->
     super options, data

     @header = new KDHeaderView
@@ -225,12 +198,7 @@
       title : "Forward My Domain"

     @forwardForm = new KDFormViewWithFields
-      callback          : =>
-        {modalTabs,parentData} = @getOptions()
-        parentData["selectedDomain"] = @forwardForm.inputs.domainAddress.getValue()
-        parentData["newDomain"] = no
-        modalTabs.showPaneByIndex 2
-
+      callback          : @bound "saveDomain"
       buttons           :
         Forward         :
           type          : "submit"
@@ -239,15 +207,30 @@
             diameter    : 12

       fields            :
-          domainAddress :
-            label       : "Enter your domain address"
-            placeholder : "mydomain.com"
+          domainName    :
+            label       : "Enter your domain name"
+            placeholder : "Domain (e.g. example.com)"
             validate    :
               rules     :
                 required: yes
               messages  :
-                requires: "write domain name"
+                requires: "Enter your domain name"

+  saveDomain:->
+    modalTabs = @getOptions().modalTabs
+    domainName = @forwardForm.inputs.domainName.getValue()
+
+    KD.remote.api.JDomain.createDomain
+      domain         : domainName
+      regYears       : 0
+      hostnameAlias  : []
+      loadBalancer   :
+          mode       : "roundrobin"
+    , (err, domain)=>
+      unless err
+        modalTabs.parent.emit "DomainForwarded", {}
+      console.log err
+

   pistachio:->
     """
@@ -302,8 +285,8 @@
     @content = new KDView
       partial : """

-        Your #{orderInfo.domain} domain address is linked to -> #{orderInfo.linkedVM}

-        You should add CNAME record from your domain name provider.

+        Thank you! Your domain #{domainName} has been added to our database.
+        Please go to your provider's website and add a CNAME record mapping to kontrol.in.koding.com.

       """


Extend specs for product service

Extend specs for product service

--- src/spec/services/products.spec.coffee
+++ src/spec/services/products.spec.coffee
@@ -2,5 +2,17 @@

 describe 'ProductService', ->

-  it 'should be defined', ->
-    expect(ProductService).toBeDefined()
+  beforeEach ->
+    @restMock =
+      config: {}
+      GET: ->
+      POST: ->
+      PUT: ->
+      DELETE: ->
+      _preRequest: ->
+      _doRequest: ->
+
+  it 'should initialize with Rest client', ->
+    products = new ProductService @restMock
+    expect(products).toBeDefined()
+    expect(products._projectEndpoint).toBe '/products'


Remove redundant replaceState on cards/append

--- app/assets/javascripts/lib/extends/controller.js.coffee
+++ app/assets/javascripts/lib/extends/controller.js.coffee
@@ -46,7 +46,6 @@
     append: (data, analytics) =>
       @_updateOffset(data.pagination) if data.pagination and data.pagination.page_offsets
       @_removePageParam() # All other requests display the first page
-      @_replaceUrl(@_createUrl())
       @trigger(':cards/append/received', [data, @state, analytics])

     newPage: (data, analytics) =>


Removed underscore from method name

Removed underscore from method name

--- app/assets/javascripts/sprangular/resources/cart.coffee
+++ app/assets/javascripts/sprangular/resources/cart.coffee
@@ -64,7 +64,7 @@
     removeItem: (item) ->
       i = @items.indexOf item
       @items.splice(i, 1) unless i is -1
-      @_updateItemQuantity item.variant.id, 0
+      @updateItemQuantity item.variant.id, 0

     changeItemQuantity: (item, delta) ->
       oldQuantity = item.quantity
@@ -72,9 +72,9 @@
       item.quantity = 0 if item.quantity < 0

       if item.quantity != oldQuantity
-        @_updateItemQuantity item.variant.id, item.quantity
+        @updateItemQuantity item.variant.id, item.quantity

-    _updateItemQuantity: (id, quantity) ->
+    updateItemQuantity: (id, quantity) ->
       params = $.param variant_id: id, quantity: quantity
       $http.put '/api/cart/update_variant', params
         .success (data) ->


module consolidator DI fix

module consolidator DI fix

--- app/scripts/common/factories/module-consolidator.coffee
+++ app/scripts/common/factories/module-consolidator.coffee
@@ -8,11 +8,14 @@
 			injector = angular.injector [
 				'ng'
 				'KalturaUsageDashboard'
-				($provide) ->
-					$provide.value '$rootElement', angular.element window.document
-					undefined
+				[
+					'$provide'
+					($provide) ->
+						$provide.value '$rootElement', angular.element window.document
+						undefined
+				]
 			]
 			for f in module._invokeQueue
 				name = f[2][0]
 				data[name.replace trim, ''] = injector.get name unless name is serviceName
-			data
+			data


Fix sync check for specs

Fix sync check for specs

--- components/CheckSyncState.coffee
+++ components/CheckSyncState.coffee
@@ -280,7 +280,7 @@

       localOnly = data.project.specs.filter (localSpec) ->
         notPushed = true
-        for remoteSpec in objects.components
+        for remoteSpec in objects.specs
           notPushed = false if localSpec.sha is remoteSpec.sha
           notPushed = false if normalizeName(localSpec.name) is remoteSpec.name
         return notPushed


Do capability subscription earlier

Do capability subscription earlier

--- components/ConnectRuntime.coffee
+++ components/ConnectRuntime.coffee
@@ -95,6 +95,7 @@

     rt = new Runtime definition
     rt.setParentElement @element if @element
+    rt.once 'capabilities', onCapabilities
     rt.once 'error', onError
     timeout = setTimeout onTimeout, @timeout unless definition.protocol is 'iframe'
     @outPorts.runtime.beginGroup definition.id
@@ -102,7 +103,6 @@
     @outPorts.runtime.endGroup()
     @outPorts.runtime.disconnect()
     rt.on 'connected', ->
-      rt.once 'capabilities', onCapabilities
       setTimeout ->
         rt.sendRuntime 'getruntime', {}
       , 100


Fixed word-case bug

Fixed word-case bug

--- app/assets/javascripts/oxalis/model/binary/cube.coffee
+++ app/assets/javascripts/oxalis/model/binary/cube.coffee
@@ -1,5 +1,5 @@
 Backbone = require("backbone")
-ErrorHandling = require("libs/Error_handling")
+ErrorHandling = require("libs/error_handling")

 class Cube


Make it simple.

Make it simple.

--- client/landing/app/MainApp/maincontroller/mainview.coffee
+++ client/landing/app/MainApp/maincontroller/mainview.coffee
@@ -214,7 +214,6 @@
   switchProfileState:(isLoggedIn)->
     $('body').addClass "login"
     @addProfileViews()
-    # @getSingleton('router').handleRoute "/Activity"

   switchGroupState:(isLoggedIn)->

@@ -367,36 +366,29 @@

   decorateLoginState:(isLoggedIn = no)->

+    if @userEnteredFromGroup()
+      @switchGroupState isLoggedIn
+    else if @userEnteredFromProfile()
+      @switchProfileState isLoggedIn
+
     if isLoggedIn
-      if @userEnteredFromGroup()
-        @switchGroupState yes
-      else if @userEnteredFromProfile()
-        @switchProfileState yes
-      else
-        $('body').addClass "loggedIn"
-        @mainTabView.showHandleContainer()
-
+      $('body').addClass "loggedIn"
       logoutLinkView = new LandingPageNavLink
         title : 'Logout'

+      # Workarounf for Develop Tab
+      if "Develop" isnt @getSingleton("router")?.getCurrentPath()
+        @contentPanel.setClass "social"
+
       @mainTabView.showHandleContainer()
-      @contentPanel.setClass "social"  if "Develop" isnt @getSingleton("router")?.getCurrentPath()
-      # @buttonHolder.hide()

     else
-      if @userEnteredFromGroup()
-        @switchGroupState no
-      else if @userEnteredFromProfile()
-        @switchProfileState no
-      else
-        $('body').removeClass "loggedIn"
-
+      $('body').removeClass "loggedIn"
       loginLinkView = new LandingPageNavLink
         title : 'Login'

       @contentPanel.unsetClass "social"
       @mainTabView.hideHandleContainer()
-      # @buttonHolder.show()

     @changeHomeLayout isLoggedIn
     @utils.wait 300, => @notifyResizeListeners()


test(core): simplify test

test(core): simplify test

--- packages/core/test/actions/execute/assert.coffee
+++ packages/core/test/actions/execute/assert.coffee
@@ -12,7 +12,7 @@
       nikita.execute.assert
         command: 'exit 1'
         code: 1
-        $handler: ({config}) ->
+        ({config}) ->
           config.code.should.eql [1]

   describe 'exit code', ->


added ember specs

added ember specs

--- test/javascripts/integration/admin_journal_user_roles_test.js.coffee
+++ test/javascripts/integration/admin_journal_user_roles_test.js.coffee
@@ -114,12 +114,21 @@
       200, "Content-Type": "application/json", JSON.stringify adminJournalUsersResults
     ]

-test 'user search results field should have add role button', ->
+test 'admin adds a role for user', ->
   visit "/admin/journals/#{TahiTest.journalId}"
   fillIn '.admin-search-input', TahiTest.query
   click '.admin-search-button'
   click '.assign-role-button'
-  .then -> $('#add-role-input').typeahead('val', 'Edit')
+  .then -> $('.add-role-input').typeahead('val', 'Edit')
   .then -> click '.tt-suggestion'
-  andThen ->
-    equal $.trim(find('.assigned-role').text()), "Editor"
+  andThen -> equal $.trim(find('.assigned-role').text()), "Editor"
+
+test 'admin removes a role for user', ->
+  visit "/admin/journals/#{TahiTest.journalId}"
+  fillIn '.admin-search-input', TahiTest.query
+  click '.admin-search-button'
+  click '.assign-role-button'
+  .then -> $('.add-role-input').typeahead('val', 'Edit')
+  .then -> click '.tt-suggestion'
+  click '.remove-button'
+  andThen -> ok !exists '.assigned-role.token'


remove unused methods

remove unused methods

--- bokehjs/src/coffee/renderer/glyph/glyph_renderer.coffee
+++ bokehjs/src/coffee/renderer/glyph/glyph_renderer.coffee
@@ -123,12 +123,6 @@
       @have_new_data = false
       ctx.restore()

-    xrange: () ->
-      return @plot_view.x_range
-
-    yrange: () ->
-      return @plot_view.y_range
-
     map_to_screen: (x, y) ->
       @plot_view.map_to_screen(x, y, @mget("x_range_name"), @mget("y_range_name"))


add AsyncTestStorageAdapter

add AsyncTestStorageAdapter

--- tests/batman/model/model_helper.coffee
+++ tests/batman/model/model_helper.coffee
@@ -58,7 +58,16 @@
     else
       callback(new Error("Can't delete record without an primary key!"), record)

+class AsyncTestStorageAdapter extends TestStorageAdapter
+  for k in ['update', 'create', 'read', 'readAll', 'destroy']
+    do (k) =>
+      AsyncTestStorageAdapter[k] = ->
+        setTimeout (=> TestStorageAdapter[k].apply(@, arguments)), ASYNC_TEST_DELAY
+
 if typeof exports is 'undefined'
   window.TestStorageAdapter = TestStorageAdapter
+  window.AsyncTestStorageAdapter = AsyncTestStorageAdapter
 else
   exports.TestStorageAdapter = TestStorageAdapter
+  exports.AsyncTestStorageAdapter = AsyncTestStorageAdapter
+


Refactor integration test

Refactor integration test

--- spec/dash-autocomplete/integration/render_search_results.coffee
+++ spec/dash-autocomplete/integration/render_search_results.coffee
@@ -10,35 +10,54 @@
       collection = new Backbone.Collection(data)
       collection.each((model) => @$el.append(model.get('task_name')))

-  it 'renders search results, and toggles spinner before request completes', ->
+  beforeEach ->
     jasmine.clock().install()
     setFixtures('

')
-
-    sandboxUrl = 'https://sandbox.smchcn.net/smi/api/TaskItems/search'
-    server = sinon.fakeServer.create()
-    taskJson = [{"id": "1", "task_name": "First Task"}, {"id": "2", "task_name": "Second Task"}]
-    server.respondWith("POST", sandboxUrl, [200, { "Content-Type": "application/json" }, JSON.stringify(taskJson)])
+    @sandboxUrl = 'https://sandbox.smchcn.net/smi/api/TaskItems/search'
+    @server = sinon.fakeServer.create()

     configs = new DashAutocomplete.ContainerConfiguration
       token: 'asdfdsf'
       el: $('[data-id=test]')
-      url: sandboxUrl
+      url: @sandboxUrl
       resultsView: new CollectionView()
     .configuration()

-    containerView = new DashAutocomplete.ContainerView(configs).render()
+    @containerView = new DashAutocomplete.ContainerView(configs).render()

-    expect(containerView.$el).toContainText('Collection View Template')
+  stubSearchResults = (server, response) ->
+    server.respondWith(
+      'POST',
+      'https://sandbox.smchcn.net/smi/api/TaskItems/search',
+      [200, { 'Content-Type': 'application/json' }, JSON.stringify(response)]
+    )
+
+  afterEach ->
+    jasmine.clock().uninstall()
+
+  it 'renders the container view', ->
+    expect(@containerView.$el).toContainText('Collection View Template')
+
+  it 'renders search results', ->
+    stubSearchResults(@server, [{
+      'id': '1',
+      'task_name': 'First Task'
+    }, {
+      'id': '2',
+      'task_name': 'Second Task'
+    }])

     $('[data-id=text-input]').val('test').keyup()
-    jasmine.clock().tick(111111000000) #this is ridiculous, but I can't get the debounce to time properly
+    @server.respond()

-    expect(containerView.$('[data-id=spinner-container]')).not.toBeEmpty()
-    server.respond()
-    expect(containerView.$('[data-id=spinner-container]')).toBeEmpty()
+    expect(@containerView.$el).toContainText('First Task')
+    expect(@containerView.$el).toContainText('Second Task')

-    expect(containerView.$el).toContainText('First Task')
-    expect(containerView.$el).toContainText('Second Task')
+  it 'renders search results, and toggles spinner before request completes', ->
+    stubSearchResults(@server, [])

-    jasmine.clock().uninstall()
+    $('[data-id=text-input]').val('test').keyup()

+    expect(@containerView.$('[data-id=spinner-container]')).not.toBeEmpty()
+    @server.respond()
+    expect(@containerView.$('[data-id=spinner-container]')).toBeEmpty()


Update LabelSet visual properties on model and model.source change  (#5003)

* Reset visuals on model and source change

* Use model attr syntax

* Use model attr syntax

--- bokehjs/src/coffee/models/annotations/label_set.coffee
+++ bokehjs/src/coffee/models/annotations/label_set.coffee
@@ -9,42 +9,45 @@
   initialize: (options) ->
     super(options)

-    @xmapper = @plot_view.frame.get('x_mappers')[@mget("x_range_name")]
-    @ymapper = @plot_view.frame.get('y_mappers')[@mget("y_range_name")]
+    @xmapper = @plot_view.frame.get('x_mappers')[@model.x_range_name]
+    @ymapper = @plot_view.frame.get('y_mappers')[@model.y_range_name]

-    @set_data(@mget('source'))
-    @set_visuals(@mget('source'))
+    @set_data()

-    if @mget('render_mode') == 'css'
+    if @model.render_mode == 'css'
       for i in [0...@_text.length]
         @title_div = $("<div>").addClass('bk-annotation-child').hide()
         @title_div.appendTo(@$el)

   bind_bokeh_events: () ->
-    if @mget('render_mode') == 'css'
+    if @model.render_mode == 'css'
       # dispatch CSS update immediately
       @listenTo(@model, 'change', () ->
-        @set_data(@mget('source'))
+        @set_data()
         @render())
-      @listenTo(@mget('source'), 'change', () ->
-        @set_data(@mget('source'))
+      @listenTo(@model.source, 'change', () ->
+        @set_data()
         @render())
     else
       @listenTo(@model, 'change', () ->
-        @set_data(@mget('source'))
+        @set_data()
         @plot_view.request_render())
-      @listenTo(@mget('source'), 'change', () ->
-        @set_data(@mget('source'))
+      @listenTo(@model.source, 'change', () ->
+        @set_data()
         @plot_view.request_render())

+  set_data: () ->
+    super(@model.source)
+    @set_visuals(@model.source)
+
   _map_data: () ->
-    if @mget('x_units') == "data"
+    if @model.x_units == "data"
       vx = @xmapper.v_map_to_target(@_x)
     else
       vx = @_x.slice(0) # make deep copy to not mutate
     sx = @canvas.v_vx_to_sx(vx)

-    if @mget('y_units') == "data"
+    if @model.y_units == "data"
       vy = @ymapper.v_map_to_target(@_y)
     else
       vy = @_y.slice(0) # make deep copy to not mutate
@@ -57,7 +60,7 @@

     [sx, sy] = @_map_data()

-    if @mget('render_mode') == 'canvas'
+    if @model.render_mode == 'canvas'
       for i in [0...@_text.length]
         @_v_canvas_text(ctx, i, @_text[i], sx[i] + @_x_offset[i], sy[i] - @_y_offset[i], @_angle[i])
     else


Removed debug statements [MARKS-88]

Removed debug statements [MARKS-88]

--- src/core/javascripts/controllers/multi_service_select.js.coffee
+++ src/core/javascripts/controllers/multi_service_select.js.coffee
@@ -9,6 +9,7 @@
 ($scope, $rootScope, $q, $attrs, BBModel, AlertService, CategoryService, FormDataStoreService, $modal) ->

   FormDataStoreService.init 'MultiServiceSelect', $scope, [
+    'selected_category_name'
   ]

   $scope.options                    = $scope.$eval($attrs.bbMultiServiceSelect) or {}
@@ -17,7 +18,6 @@
   $scope.options.services           = $scope.options.services or 'items'

   $rootScope.connection_started.then ->
-    debugger
     if $scope.bb.company.$has('parent') && !$scope.bb.company.$has('company_questions')
       $scope.bb.company.getParentPromise().then (parent) ->
         $scope.company = parent
@@ -33,8 +33,6 @@

   initialise = () ->
-
-    debugger

     return if !$scope.items or !$scope.company

@@ -151,8 +149,6 @@
         $scope.selected_category = $scope.categories[$scope.categories.length - 1]
         $scope.selected_category_name = $scope.selected_category.name

-    debugger
-

   $scope.changeCategory = (category_name, services) ->


Client:Ticker: remove empty line

Client:Ticker: remove empty line

--- client/Social/Activity/views/activitytickeritem.coffee
+++ client/Social/Activity/views/activitytickeritem.coffee
@@ -112,7 +112,6 @@

   pistachio: ->
     {target} = @getData()
-
     if actor.getId is KD.whoami().getId()
       return "{{> @avatar}} You installed {{> @object}}"


AdminModal: Disable default channels for now.

--- client/admin/lib/views/groupgeneralsettingsview.coffee
+++ client/admin/lib/views/groupgeneralsettingsview.coffee
@@ -59,16 +59,16 @@
       defaultValue : Encoder.htmlDecode group.slug ? ''
       placeholder  : 'Please enter a title here'

-    @addInput form,
-      label        : 'Default Channels'
-      description  : 'Your new members will automatically join to #general channel. Here you can specify more channels for new team members to join automatically.'
-      name         : 'channels'
-      placeholder  : 'product, design, ux, random etc'
-      defaultValue : Encoder.htmlDecode group.defaultChannels?.join(', ') ? ''
-      nextElement  : new KDCustomHTMLView
-        cssClass   : 'warning-text'
-        tagName    : 'span'
-        partial    : 'Please add channel names separated by commas.'
+    # @addInput form,
+    #   label        : 'Default Channels'
+    #   description  : 'Your new members will automatically join to #general channel. Here you can specify more channels for new team members to join automatically.'
+    #   name         : 'channels'
+    #   placeholder  : 'product, design, ux, random etc'
+    #   defaultValue : Encoder.htmlDecode group.defaultChannels?.join(', ') ? ''
+    #   nextElement  : new KDCustomHTMLView
+    #     cssClass   : 'warning-text'
+    #     tagName    : 'span'
+    #     partial    : 'Please add channel names separated by commas.'

     @addInput form,
       label        : 'Allowed Domains'
@@ -197,19 +197,20 @@

   update: ->

-    { channels, domains } = @generalSettingsForm.getFormData()
+    # { channels, domains } = @generalSettingsForm.getFormData()
+    { domains } = @generalSettingsForm.getFormData()

     formData     = @generalSettingsForm.getFormData()
     jGroup       = @getData()
-    newChannels  = @separateCommas channels
+    # newChannels  = @separateCommas channels
     newDomains   = @separateCommas domains
     dataToUpdate = {}

     unless formData.title is jGroup.title
       dataToUpdate.title = formData.title

-    unless _.isEqual newChannels, jGroup.defaultChannels
-      dataToUpdate.defaultChannels = newChannels
+    # unless _.isEqual newChannels, jGroup.defaultChannels
+    #   dataToUpdate.defaultChannels = newChannels

     unless _.isEqual newDomains, jGroup.allowedDomains
       for domain in newDomains when not validator.isURL domain


dashboard : leaf channels is added to hidden view

--- client/admin/lib/views/moderation/topicitemview.coffee
+++ client/admin/lib/views/moderation/topicitemview.coffee
@@ -85,6 +85,9 @@

     @createSimilarChannelsListController()
     @settings.addSubView @similarChannelsListController.getView()
+
+    @createLeafChannelsListController()
+    @settings.addSubView @leafChannelsListController.getView()

   searchSimilarChannels: ->


client: add logout action to disabled member modals

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/components/disabledmodals/member.coffee
+++ client/app/lib/components/disabledmodals/member.coffee
@@ -23,6 +23,10 @@

     status or= getGroupStatus groupsController.getCurrentGroup()

+    onLogoutClick = =>
+      @destroy()
+      router.handleRoute '/Logout'
+
     switch status

       when Status.EXPIRED
@@ -31,7 +35,8 @@
           router.handleRoute '/Disabled/Member/notify-success'
         <TrialEndedMemberModal
           isOpen={yes}
-          onButtonClick={onClick} />
+          onButtonClick={onClick}
+          onSecondaryButtonClick={onLogoutClick} />

       when Status.PAST_DUE, Status.CANCELED
         onClick = =>
@@ -39,7 +44,8 @@
           router.handleRoute '/Disabled/Member/suspended-notify-success'
         <SuspendedMemberModal
           isOpen={yes}
-          onButtonClick={onClick} />
+          onButtonClick={onClick}
+          onSecondaryButtonClick={onLogoutClick} />

       when Status.NEEDS_UPGRADE
         onClick = =>
@@ -47,7 +53,8 @@
           router.handleRoute '/Disabled/Member/upgrade-notify-success'
         <UpgradeNeededMemberModal
           isOpen={yes}
-          onButtonClick={onClick} />
+          onButtonClick={onClick}
+          onSecondaryButtonClick={onLogoutClick} />

       when 'suspended-notify-success'
         onClick = -> console.log 'support link clicked'


Allow modal to hide itself when close btn is clicked

--- client/lanes/components/record-finder/RecordFinder.cjsx
+++ client/lanes/components/record-finder/RecordFinder.cjsx
@@ -23,9 +23,10 @@
         @context.viewport.displayModal(
             title: "Find #{@props.query.title}"
             buttons: [{title: 'Cancel'}]
+            autoHide: true
             body: =>
                 <LC.RecordFinder.Dialog query={@props.query} onRecordSelect={@setModel} />
-        ).then(Lanes.emptyFn, Lanes.emptyFn)
+        )

     setModel: (model) ->
         if @props.parentModel


fix(thread-list): Cache `MailLabel` component instances to prevent needless rendering

--- internal_packages/thread-list/lib/thread-list.cjsx
+++ internal_packages/thread-list/lib/thread-list.cjsx
@@ -79,6 +79,8 @@
         else
           <ThreadListParticipants thread={thread} />

+    c3LabelComponentCache = {}
+
     c3 = new ListTabular.Column
       name: "Message"
       flex: 4
@@ -92,9 +94,12 @@
         currentCategoryId = FocusedCategoryStore.categoryId()
         allCategoryId = CategoryStore.getStandardCategory('all')?.id
         ignoredIds = [currentCategoryId, allCategoryId]
+
         for label in (thread.sortedLabels() ? [])
           continue if label.id in ignoredIds
-          labels.push <MailLabel label={label} key={label.id} />
+          if not c3LabelComponentCache[label.id]
+            c3LabelComponentCache[label.id] = <MailLabel label={label} key={label.id} />
+          labels.push c3LabelComponentCache[label.id]

         <span className="details">
           {labels}


Add bulk rejection support.

Add bulk rejection support.

--- services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee
@@ -31,6 +31,12 @@

 			@$scope.$on "change:reject", (e, change_id) =>
 				@rejectChangeId(change_id)
+
+			@$scope.$on "change:bulk-accept", (e, change_ids) =>
+				@bulkAcceptChangeIds(change_ids)
+
+			@$scope.$on "change:bulk-reject", (e, change_ids) =>
+				@bulkRejectChangeIds(change_ids)

 			@$scope.$on "comment:remove", (e, comment_id) =>
 				@removeCommentId(comment_id)
@@ -233,6 +239,13 @@
 			else
 				throw new Error("unknown change: #{JSON.stringify(change)}")

+		bulkAcceptChangeIds: (change_ids) ->
+			console.log change_id for change_id in change_ids
+
+		bulkRejectChangeIds: (change_ids) ->
+			for change_id in change_ids
+				@rejectChangeId change_id
+
 		removeCommentId: (comment_id) ->
 			@rangesTracker.removeCommentId(comment_id)
 			@updateAnnotations()


sw -> sp

sw -> sp

--- modules/player/commands.coffee
+++ modules/player/commands.coffee
@@ -181,7 +181,7 @@

     # Swap
     @m.registerCommand 'swap', {
-      aliases: ['sw'], djOnly: true, argSeparator: ' '
+      aliases: ['sp'], djOnly: true, argSeparator: ' '
     }, (msg, args, d)=>
       queue = await @q.getForGuild msg.guild
       return msg.channel.sendMessage 'Invalid arguments provided.' unless args.length is 2


Extracted _setStylesFromElement

Extracted _setStylesFromElement

--- js-library/app/js/jail_iframe/classes/icon_buttons.coffee
+++ js-library/app/js/jail_iframe/classes/icon_buttons.coffee
@@ -1,7 +1,5 @@
 class IconButton
   constructor: (options) ->
-    $targetElement = $(options.targetElement)
-
     @$el = $ """
       <factlink-icon-button>
         <factlink-icon-button-bubble>
@@ -12,13 +10,7 @@
     """
     FactlinkJailRoot.$factlinkCoreContainer.append(@$el)

-    @_tether = new Tether
-      element: @$el[0]
-      target: $targetElement[0]
-      attachment: 'top left'
-      targetAttachment: 'top right'
-      classPrefix: 'factlink-tether'
-      targetOffset: options.targetOffset || '0 0'
+    @_setStylesFromElement(options.targetElement)

     @_robustHover = new FactlinkJailRoot.RobustHover
       $el: @$el
@@ -26,23 +18,13 @@
       mouseleave: options.onmouseleave
     @$el.on 'click', options.onclick

-    targetColor = $targetElement.css('color')
-
-    # See https://gamedev.stackexchange.com/questions/38536/given-a-rgb-color-x-how-to-find-the-most-contrasting-color-y/38561#38561
-    targetRGB = targetColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)
-    r = targetRGB[1]/255; g = targetRGB[2]/255; b = targetRGB[3]/255;
-    targetBrightness = 0.2126*r*r + 0.7152*g*g + 0.0722*b*b
-
-    @$el.css
-      'line-height': $targetElement.css('line-height')
-      'font-size': Math.max 15, Math.min 20, parseInt $targetElement.css('font-size')
-      'color': if targetBrightness > 0.5 then 'black' else 'white'
-
-    @$el.find('factlink-icon-button-bubble').css
-      'background-color': targetColor
-
-    @$el.find('factlink-icon-button-bubble-triangle').css
-      'border-top-color': targetColor
+    @_tether = new Tether
+      element: @$el[0]
+      target: options.targetElement
+      attachment: 'top left'
+      targetAttachment: 'top right'
+      classPrefix: 'factlink-tether'
+      targetOffset: options.targetOffset || '0 0'

   destroy: ->
     @_tether.destroy()
@@ -54,6 +36,27 @@

   fadeOut: ->
     @$el.removeClass 'factlink-control-visible'
+
+  _setStylesFromElement: (element) ->
+    $element = $(element)
+    targetColor = $element.css('color')
+
+    # See https://gamedev.stackexchange.com/questions/38536/given-a-rgb-color-x-how-to-find-the-most-contrasting-color-y/38561#38561
+    targetRGB = targetColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)
+    r = targetRGB[1]/255; g = targetRGB[2]/255; b = targetRGB[3]/255;
+    targetBrightness = 0.2126*r*r + 0.7152*g*g + 0.0722*b*b
+
+    @$el.css
+      'line-height': $element.css('line-height')
+      'font-size': Math.max 15, Math.min 20, parseInt $element.css('font-size')
+      'color': if targetBrightness > 0.5 then 'black' else 'white'
+
+    @$el.find('factlink-icon-button-bubble').css
+      'background-color': targetColor
+
+    @$el.find('factlink-icon-button-bubble-triangle').css
+      'border-top-color': targetColor
+

 class FactlinkJailRoot.ShowButton
   constructor: (highlightElements, factId) ->


ported over check for at least one transport

--- lib/assets/javascripts/firehose/consumer.js.coffee
+++ lib/assets/javascripts/firehose/consumer.js.coffee
@@ -5,6 +5,8 @@
   constructor: (config = {}) ->
     # List of transport stragies we have to use.
     config.transports   ||= Firehose.Consumer.transports
+    unless config.transports.length > 0
+      throw 'You must provide at least one tranport for Firehose.Consumer'
     # Empty handler for messages.
     config.message      ||= ->
     # Empty handler for error handling.


payment: simplify makeTag helper

payment: simplify makeTag helper

--- node_modules_koding/koding-payment/lib/main.coffee
+++ node_modules_koding/koding-payment/lib/main.coffee
@@ -156,8 +156,8 @@
         then new models.PaymentMethod paymentMethod
         else {}

-  makeTag = (data, field, tagName = field) ->
-    if data[field] then "<#{tagName}>#{data[field]}</#{tagName}>" else ''
+  makeTag = (field, tagName = field) ->
+    if @[field]? then "<#{ tagName }>#{ @[field] }</#{ tagName }>" else ''

   @setPaymentMethodById = (paymentMethodId, data, callback)->
     # Remove credit card number if it's not updated
@@ -165,7 +165,7 @@
       delete data.cardNumber
       delete data.cardCV

-    tag = makeTag.bind null, data
+    tag = makeTag.bind data

     # See https://docs.recurly.com/api/account for XML format
     xml =


solving bug 1451

solving bug 1451

Signed-off-by: Arpit Goyal <72df09b02047b5b6857897f0b5d0495073ea24a3@gmail.com>

--- plugins/080-settings/app/assets/javascripts/settings.js.coffee
+++ plugins/080-settings/app/assets/javascripts/settings.js.coffee
@@ -23,7 +23,8 @@

 $(document).on "click", ".remote-check", (event)->
   checkbox = $(this)
-  false
+  checkbox.prop("checked",!checkbox.prop("checked"))
+  true

 $(document).on "ajax:complete",".remote-check", ->
       $(this).prop("checked",!$(this).prop("checked"))


Client: Activity: Added create private channel success event listener

--- client/activity/lib/flux/stores/followedprivatechannelidsstore.coffee
+++ client/activity/lib/flux/stores/followedprivatechannelidsstore.coffee
@@ -1,7 +1,8 @@
-actions         = require '../actions/actiontypes'
-immutable       = require 'immutable'
-toImmutable     = require 'app/util/toImmutable'
-KodingFluxStore = require 'app/flux/store'
+actions              = require '../actions/actiontypes'
+immutable            = require 'immutable'
+toImmutable          = require 'app/util/toImmutable'
+KodingFluxStore      = require 'app/flux/store'
+createChannelActions = require 'activity/flux/createchannel/actions/actiontypes'

 module.exports = class FollowedPrivateChannelIdsStore extends KodingFluxStore

@@ -13,6 +14,7 @@

     @on actions.LOAD_FOLLOWED_PRIVATE_CHANNEL_SUCCESS, @handleLoadChannelSuccess
     @on actions.DELETE_PRIVATE_CHANNEL_SUCCESS, @handleDeletePrivateChannelSuccess
+    @on createChannelActions.CREATE_PRIVATE_CHANNEL_SUCCESS, @handleLoadChannelSuccess

   ###*
    * Adds given channel to privateMessageIds container.


encapsulate xsolla script load

encapsulate xsolla script load

--- resources/assets/coffee/_classes/store-checkout.coffee
+++ resources/assets/coffee/_classes/store-checkout.coffee
@@ -21,32 +21,40 @@
     return unless document.querySelector('#js-xsolla-pay')
     document.querySelector('#js-xsolla-pay').disabled = true
     # load script
-    deferredScript = $.Deferred()
+    deferredScript = @loadScript()
+    deferredToken = $.Deferred()
+
+    # get token
+    @getXsollaToken()
+    .done (data) ->
+      deferredToken.resolve(data)
+      console.debug('token get')
+    .fail (xhr, error) ->
+      console.error xhr
+      deferredToken.reject(xhr)
+
+    $.when(deferredScript)
+    .done (_, token) =>
+      console.debug 'when done'
+      options = @optionsWithToken(token)
+      XPayStationWidget.init(options)
+      document.querySelector('#js-xsolla-pay').disabled = false
+    .fail (error) ->
+      console.error error
+
+  @loadScript: ->
+    deferred = $.Deferred()
     script = document.createElement('script')
     script.type = "text/javascript"
     script.async = true
     script.src = "https://static.xsolla.com/embed/paystation/1.0.7/widget.min.js"
     script.addEventListener 'load', ->
-      deferredScript.resolve()
+      console.debug('script loaded')
+      deferred.resolve()
     , false
     document.head.appendChild(script)

-    deferredToken = $.Deferred()
-    # get token
-    @getXsollaToken()
-    .done (data) ->
-      deferredToken.resolve(data)
-    .fail (xhr, error) ->
-      console.error xhr
-      deferredToken.reject(xhr)
-
-    $.when(deferredScript, deferredToken)
-    .done (_, token) =>
-      options = @optionsWithToken(token)
-      XPayStationWidget.init(options)
-      document.querySelector('#js-xsolla-pay').disabled = false
-    .fail (error) ->
-      console.error error
+    deferred

   @optionsWithToken: (token) ->
     options =


Fix CoffeeScript syntax (#3711)

Fix CoffeeScript syntax (#3711)

--- packages/rocketchat-lib/server/methods/sendMessage.coffee
+++ packages/rocketchat-lib/server/methods/sendMessage.coffee
@@ -18,7 +18,7 @@
 				_id: Random.id()
 				rid: room._id
 				ts: new Date
-				msg: TAPi18n.__('You_have_been_muted', {}, user.language);
+				msg: TAPi18n.__('You_have_been_muted', {}, user.language)
 			}
 			return false


sender => origin ; message => subject

sender => origin ; message => subject

--- server/app/bongo/models/messages/privatemessage.coffee
+++ server/app/bongo/models/messages/privatemessage.coffee
@@ -37,8 +37,8 @@
             fin err
           else
             recipient.sendNotification 'NewPrivateMessageHasArrived',
-              sender  : ObjectRef(sender).data
-              message : ObjectRef(pm).data
+              origin  : ObjectRef(sender).data
+              subject : ObjectRef(pm).data
             fin()
             # jraphical.Channel.fetchPrivateChannelById recipient.getId(), (err, channel)->
             #   channel.publish sender, pm


feature(macTableSelectable): Differentiate between click and text selection

--- src/directives/table/table_section_selected_models.coffee
+++ src/directives/table/table_section_selected_models.coffee
@@ -171,6 +171,16 @@
       controllers[2].parentController = controllers[1]

       $element.on "click", (event) ->
+        # Cancel select if select evaluates to false
         return unless $scope.$eval $attrs.macTableSelectable
+
+        # Cancel select if the user is selecting text on this element
+        if $window.getSelection
+          selection = $window.getSelection()
+        else if $document.selection
+          selection = $document.selection.createRange()
+
+        return if selection.toString()
+
         controllers[2].selectRow($scope.row)
 ]


ConfirmationChoice

ConfirmationChoice

- Fixed passing empty choice in choices Array.

--- src/elements/ConfirmationChoice/ConfirmationChoice.coffee
+++ src/elements/ConfirmationChoice/ConfirmationChoice.coffee
@@ -27,7 +27,7 @@
 						return false

 					for choice in v
-						if not v
+						if not choice
 							continue
 						CUI.Element.readOpts(choice, "new ConfirmationChoice", @choiceOpts)
 					return true


fix for nl_NL

fix for nl_NL

--- vue/src/shared/services/session.coffee
+++ vue/src/shared/services/session.coffee
@@ -22,6 +22,7 @@
   locale.replace('-', '_')

 dateFnsLocale = (locale) ->
+  return 'nl' if locale == "nl-NL"
   locale.replace('_','-')

 loadLocale = (locale) ->


Behaviors support for Yandex.Maps

Behaviors support for Yandex.Maps

--- vendor/assets/javascripts/gmaps4rails/yandex/objects/map.coffee
+++ vendor/assets/javascripts/gmaps4rails/yandex/objects/map.coffee
@@ -21,6 +21,7 @@
     yandexOptions =
       center: @createLatLng(@options.center_latitude, @options.center_longitude)
       zoom:   @options.zoom
+      behaviors: @options.behaviors

     mergedYandexOptions = @mergeObjects map_options.raw, yandexOptions


Notify user when dragging file over upload target

--- app/assets/javascripts/backbone/plugins/attachments.js.coffee
+++ app/assets/javascripts/backbone/plugins/attachments.js.coffee
@@ -30,7 +30,7 @@

     fileItemTemplate: _.template '''

-        <a href="<%= url %>">
+        <a target="_blank" href="<%= url %>">
           <img src="<%= iconUrl %>"/>
           <%= fileName %>

@@ -59,7 +59,7 @@

-  # TODO this part is very bad for APIs! shoudnt be exposing a backbone collection in a plugin.
+  # TODO this part is very bad for APIs! Shouldn't be exposing a backbone collection in a plugin.
   @render: ($widgetEl)->
     $uploadForm = @templates.dropzone({
       channelId:    Kandan.Data.Channels.activeChannelId(),
@@ -121,6 +121,11 @@

       dragOver: ->
         console.log "reached dropzone!"
+        $(".dropzone").text("UPLOAD!")
+
+      dragLeave: ->
+        console.log "left dropzone!"
+        $(".dropzone").text("Drop file here to upload")
     })


Renamed editPassword in-line with others (_ instead of tCC)

--- app/assets/javascripts/controllers/users/edit_password.coffee
+++ app/assets/javascripts/controllers/users/edit_password.coffee
@@ -6,27 +6,33 @@
       type: 'object'
       title: 'User'
       properties:
-        password:
-          title: 'Password'
+        current_password:
+          title: 'Current Password'
           type: 'string'
           minLength: 6
           required: true
-        confirmPassword:
-          title: 'Repeat Password'
+        password:
+          title: 'New Password'
+          type: 'string'
+          minLength: 6
+          required: true
+        confirm_password:
+          title: 'Repeat New Password'
           type: 'string'
           minLength: 6
           required: true

     $scope.form = [
+      { key: 'current_password', type: 'password' }
       { key: 'password', type: 'password' }
-      { key: 'confirmPassword', type: 'password' }
+      { key: 'confirm_password', type: 'password' }
       { type: 'submit', style: 'btn btn-primary', title: 'Update Password' }]

-    $scope.$watch("user.confirmPassword", (value) ->
+    $scope.$watch("user.confirm_password", (value) ->
       if (value != $scope.user.password)
-        $scope.$broadcast('schemaForm.error.confirmPassword', 'matchPassword', "Passwords must match")
+        $scope.$broadcast('schemaForm.error.confirm_password', 'matchPassword', "Passwords must match")
       else
-        $scope.$broadcast('schemaForm.error.confirmPassword', 'matchPassword', true)
+        $scope.$broadcast('schemaForm.error.confirm_password', 'matchPassword', true)
     )
     $scope.submitForm = (form) ->
       $scope.$broadcast("schemaFormValidate")


Fixed last commit

Fixed last commit

--- spec/javascripts/ember/views/cart_items/mini_cart_item_spec.js.coffee
+++ spec/javascripts/ember/views/cart_items/mini_cart_item_spec.js.coffee
@@ -55,12 +55,12 @@

       miniCartItem.$('dt').length.should.equal(1)

-      miniCartItem.$('dt li.ec-name').text().should.
+      miniCartItem.$('dt li.ec-cart-item-name').text().should.
         equal(cartItem.get('name'))
-      miniCartItem.$('dt div.ec-price').text().trim().should.
+      miniCartItem.$('dt div.ec-cart-item-price').text().trim().should.
         equal(
           Ember.I18n.t('currency.unit') +
-          cartItem.get('price') + 'x' +
+          cartItem.get('formattedPrice') + 'x' +
           cartItem.get('quantity')
         )
       miniCartItem.$('dt div.ec-delete').text().trim().should.
@@ -89,8 +89,8 @@
     it 'toggles the children when clicking on the name of the parent', ->
       Ember.run -> miniCartItem.appendTo('#konacha')

-      miniCartItem.$('dt li.ec-name').click()
+      miniCartItem.$('dt li.ec-cart-item-name').click()
       miniCartItem.get('isChildrenHidden').should.be.false

-      miniCartItem.$('dt li.ec-name').click()
+      miniCartItem.$('dt li.ec-cart-item-name').click()
       miniCartItem.get('isChildrenHidden').should.be.true


fix JS scrolling bug on certain pages

fix JS scrolling bug on certain pages

--- app/assets/javascripts/application/home.coffee
+++ app/assets/javascripts/application/home.coffee
@@ -11,8 +11,10 @@
         scrollTop: $(href).offset().top
       }, 500)

-  $(window).scroll () ->
-    rect = $('.main-logo').get(0).getBoundingClientRect();
+  $(window).scroll ->
+    main_logo = $('.main-logo').get(0)
+    return unless main_logo
+    rect = main_logo.getBoundingClientRect();
     isBigLogoStillVisible = (
       rect.top >= 0 &&
       rect.left >= 0 &&


redactor setting to allow div

redactor setting to allow div

--- app/assets/javascripts/gnoibox/admin.js.coffee
+++ app/assets/javascripts/gnoibox/admin.js.coffee
@@ -3,3 +3,4 @@
     s3: '/gnoibox/s3/put_url'
     imageGetJson: '/gnoibox/s3'
     minHeight: 200
+    convertDivs: false


Added transferables support, Dropped MozWebSocket support

--- app/assets/javascripts/model/pullworker.coffee
+++ app/assets/javascripts/model/pullworker.coffee
@@ -2,14 +2,14 @@

 initialize = (url) ->

-  socket = new WebSocketImpl(url)
+  socket = new WebSocket(url)
   socket.binaryType = 'arraybuffer'

   socket.onopen = ->
     postMessage({ message: 'open' })

   socket.onerror = (err) ->
-    postMessage({ message: 'error', error: err })
+    postMessage({ message: 'error', error: err.toString() })

   socket.addEventListener(
     "close"
@@ -23,7 +23,7 @@
   socket.addEventListener(
     "message"
     (event) =>
-      webkitPostMessage({ message: 'data', buffer: event.data })
+      webkitPostMessage({ message: 'data', buffer: event.data }, [ event.data ])
   )

@@ -37,9 +37,6 @@
       @socket.close()
       @socket = null

-
-WebSocketImpl = if self.MozWebSocket then self.MozWebSocket else self.WebSocket
-
 self.onmessage = (message) ->

   switch message.data.message


support both

support both

--- app/assets/javascripts/monster/scaffold.coffee
+++ app/assets/javascripts/monster/scaffold.coffee
@@ -39,7 +39,10 @@
   args.unshift '$scope': '$'
   args.push 'Event'
   args.push 'Api'
-  if $('body').hasClass('home_pages') || $('body').hasClass('app_pages')
+  if $('body').hasClass('home_pages') ||
+     $('body').hasClass('app_pages')  ||
+     $('body').hasClass('pages_app')  ||
+     $('body').hasClass('pages_home')
     args.push '$state'
     args.push '$stateParams'
   names = name.split '/'


allow top-level attachments

allow top-level attachments

test plan:
1. go to a discussion as a teacher
2. confirm you can attach a file with a new top-level reply
3. go to the discussion as a student who can't attach files
4. confirm that you can't attach a file to a new top-level reply

Change-Id: I005581e2a704d162ad69b9adc5706f60e66cee4d
Reviewed-on: https://gerrit.instructure.com/9620
Tested-by: Zach Wily <9de8c4480303b5335cd2a33eefe814615ba3612a@instructure.com>
Reviewed-by: Zach Wily <9de8c4480303b5335cd2a33eefe814615ba3612a@instructure.com>

--- app/coffeescripts/discussions/TopicView.coffee
+++ app/coffeescripts/discussions/TopicView.coffee
@@ -37,6 +37,9 @@

       # overwrite cid so Reply::getModelAttributes gets the right "go to parent" link
       @model.cid = 'main'
+
+      @model.set 'canAttach', ENV.DISCUSSION.PERMISSIONS.CAN_ATTACH
+
       @render()
       @initEntries() unless ENV.DISCUSSION.INITIAL_POST_REQUIRED


Fix postmerge issue

Fix postmerge issue

--- services/filestore/app/coffee/FileHandler.coffee
+++ services/filestore/app/coffee/FileHandler.coffee
@@ -60,19 +60,12 @@
 		], (err)->
 			if err?
 				return callback(err)
-<<<<<<< HEAD
 			PersistorManager.getFileStream bucket, convertedKey, callback

 	_convertFile: (bucket, originalKey, opts, callback)->
 		@_writeS3FileToDisk bucket, originalKey, (err, originalFsPath)->
-=======
-			PersistorManager.getFileStream bucket, convetedKey, opts, callback
-
-	_convertFile: (bucket, origonalKey, opts, callback)->
-		@_writeS3FileToDisk bucket, origonalKey, opts, (err, origonalFsPath)->
 			if err?
 				return callback(err)
->>>>>>> sharelatex/master
 			done = (err, destPath)->
 				if err?
 					logger.err err:err, bucket:bucket, originalKey:originalKey, opts:opts, "error converting file"


Use getBoundingClientRect

Use getBoundingClientRect

--- app/js/jail_iframe/classes/icon_buttons.coffee
+++ app/js/jail_iframe/classes/icon_buttons.coffee
@@ -16,8 +16,9 @@

     # TODO: really do grouping, so we don't have to do hacks like this!
     textContainer = @_textContainer(@$highlightElements[0])
-    verticalOffset = @$highlightElements.offset().top - $(textContainer).offset().top
-    verticalOffsetPercentage = verticalOffset*100 / $(textContainer).height()
+    textContainerBoundingRect = textContainer.getBoundingClientRect()
+    verticalOffset = @$highlightElements[0].getBoundingClientRect().top - textContainerBoundingRect.top
+    verticalOffsetPercentage = verticalOffset*100 / textContainerBoundingRect.height

     @_tether = new Tether
       element: @$el[0]


update attribution for parseJSON helper

update attribution for parseJSON helper

--- src/helpers/helpers-data.coffee
+++ src/helpers/helpers-data.coffee
@@ -25,7 +25,10 @@
     file = Utils.readJSON(file)
     Utils.safeString(JSON.stringify(file, null, 2))

+  ###
+  # From: Keegan Street (@keeganstreet)
   # https://github.com/assemble/assemble/issues/228#issuecomment-20855238
+  ###
   parseJSON: parseJSON = (data, options) ->
     options.fn JSON.parse(data)


Implement the different modes of the StepInterpolator

--- bokehjs/src/coffee/models/transforms/step_interpolator.coffee
+++ bokehjs/src/coffee/models/transforms/step_interpolator.coffee
@@ -18,11 +18,29 @@
     # Apply the transform to a single value
     @sort(descending = false)

-    ind = _.findLastIndex(@_x_sorted, (num) ->
+    ind = -1
+    if @get('mode') == "after"
+      ind = _.findLastIndex(@_x_sorted, (num) ->
         return x >= num
-    )
+      )

-    ret = @_y_sorted[ind]
+    if @get('mode') == "before"
+      ind = _.findIndex(@_x_sorted, (num) ->
+        return x <= num
+      )
+
+    if @get('mode') == "center"
+      diffs = (Math.abs(tx - x) for tx in @_x_sorted)
+      mdiff = _.min(diffs)
+      ind = _.findIndex(diffs, (num) ->
+        return mdiff == num
+      )
+
+    if ind != -1
+      ret = @_y_sorted[ind]
+    else
+      ret = NULL
+
     return(ret)

   v_compute: (xs) ->


Fix for absolute URL on the client.

Fix for absolute URL on the client.

--- packages/artificialengines/base/router/router-client.coffee
+++ packages/artificialengines/base/router/router-client.coffee
@@ -114,6 +114,9 @@
     # HACK: Override absolute URL function to use the browser origin as the root url.
     _absoluteUrl = Meteor.absoluteUrl
     Meteor.absoluteUrl = (path, options) ->
+      # Absolute URL doesn't remove the leading slash, so we do it to allow both relative and server-relative URLs.
+      path = path.substring 1 if path?[0] is '/'
+
       # Just in case, we only want to replace the origin if options didn't do any changes to it.
       rootUrl = _absoluteUrl()
       url = _absoluteUrl path, options


Fix ActivateLicensesModal scrolling on checkbox toggle

--- app/views/courses/ActivateLicensesModal.coffee
+++ app/views/courses/ActivateLicensesModal.coffee
@@ -45,10 +45,11 @@
           @supermodel.trackRequests(jqxhrs)
       })

-    @listenTo @state, 'change', @render
+    @listenTo @state, 'change', ->
+      @renderSelectors('#submit-form-area')
     @listenTo @state.get('selectedUsers'), 'change add remove reset', ->
       @updateVisibleSelectedUsers()
-      @render()
+      @renderSelectors('#submit-form-area')
     @listenTo @users, 'change add remove reset', ->
       @updateVisibleSelectedUsers()
       @render()
@@ -68,8 +69,8 @@
   updateSelectedStudents: (e) ->
     userID = $(e.currentTarget).data('user-id')
     user = @users.get(userID)
-    if @state.get('selectedUsers').contains(user)
-      @state.get('selectedUsers').remove(user)
+    if @state.get('selectedUsers').findWhere({ _id: user.id })
+      @state.get('selectedUsers').remove(user.id)
     else
       @state.get('selectedUsers').add(user)
     # @render() # TODO: Have @state automatically listen to children's change events?


Fixed error message.

Fixed error message.

--- lib/adaptors.coffee
+++ lib/adaptors.coffee
@@ -69,5 +69,5 @@
 exports.set_nat = (vm, nic, callback) ->
 	command.exec 'modifyvm', vm, "--nic#{nic}", 'nat', (err, code, output) ->
 		return callback err if err
-		return callback new Error "cannot set nat netname for nic #{nic} on #{vm}" if code > 0
+		return callback new Error "cannot set nat network for nic #{nic} on #{vm}" if code > 0
 		return do callback if callback


Fix no remove button for items added by javascript

--- lib/assets/javascripts/jquery.handle-nesting.js.coffee
+++ lib/assets/javascripts/jquery.handle-nesting.js.coffee
@@ -48,6 +48,8 @@
       added.find("input, select, textarea").each (i, el) ->
         $(el).val ""
         el.name = el.name.replace(match, replace)
+
+      base.addRemoveButton added

       base.$el.append added
       base.options.afterAdd added  if base.options.afterAdd?


kodingenUsername check err fixed

kodingenUsername check err fixed

--- server/app/bongo/models/user.coffee
+++ server/app/bongo/models/user.coffee
@@ -251,11 +251,18 @@
      firstName, lastName, agree, inviteCode, kodingenUser} = userFormData
     @usernameAvailable username, (err, r)=>
       isAvailable = yes
-      isAvailable = no for key, value of r when value is yes
+
+      # r =
+      #   forbidden    : yes/no
+      #   kodingenUser : yes/no
+      #   kodingUser   : yes/no
+
       if err
         callback err
-      else unless isAvailable
-        callback new KodingError 'That username is not available!'
+      else if r.forbidden
+        callback new KodingError 'That username is forbidden!'
+      else if r.kodingUser
+        callback new KodingError 'That username is taken!'
       else
         @verifyEnrollmentEligibility {email, inviteCode}, (err, isEligible, invite)=>
           if err


Move hover elements to inline-block and fixed width

--- annotran/static/scripts/annotator/plugin/substitution.coffee
+++ annotran/static/scripts/annotator/plugin/substitution.coffee
@@ -63,10 +63,21 @@

       this.singleSubstitution(packager, data.text)

-  createSubstitutionElement: (originalText, substituteText) ->
+  createSubstitutionElement: (originalText, substituteText, ele) ->
     newEle = $('')
     newEle.text(substituteText)
-    newEle.css({"background-color" : "#F5F5F5"})
+
+    newEle.css({
+      "background-color" : "#F5F5F5",
+      "display": "inline-block"
+    })
+
+    ele.before(newEle)
+
+    width = newEle.width() + "px"
+    newEle.css({
+        "width": width
+    })

     newEle.mouseover (event) ->
       newEle.text(originalText)
@@ -87,8 +98,7 @@

     removeArray = []

-    newEle = this.createSubstitutionElement(start.textContent, substituteText)
-    $(start).before(newEle)
+    newEle = this.createSubstitutionElement(start.textContent, substituteText, $(start))

     start.textContent = ""


don't log errors when files have disappeared from build directory

--- services/clsi/app/coffee/OutputCacheManager.coffee
+++ services/clsi/app/coffee/OutputCacheManager.coffee
@@ -125,8 +125,11 @@
 	_checkFileIsSafe: (src, callback = (error, isSafe) ->) ->
 		# check if we have a valid file to copy into the cache
 		fs.stat src, (err, stats) ->
-			if err?
-				# some problem reading the file
+			if err?.code is 'ENOENT'
+				logger.warn err: err, file: src, "file has disappeared before copying to build cache"
+				callback(err, false)
+			else if err?
+				# some other problem reading the file
 				logger.error err: err, file: src, "stat error for file in cache"
 				callback(err, false)
 			else if not stats.isFile()
@@ -140,7 +143,10 @@
 	_copyFile: (src, dst, callback) ->
 		# copy output file into the cache
 		fse.copy src, dst, (err) ->
-			if err?
+			if err?.code is 'ENOENT'
+				logger.warn err: err, file: src, "file has disappeared when copying to build cache"
+				callback(err, false)
+			else if err?
 				logger.error err: err, src: src, dst: dst, "copy error for file in cache"
 				callback(err)
 			else
@@ -155,4 +161,4 @@
 			return callback(null, true)
 		if Settings.clsi?.archive_logs and Path.basename(src) in ["output.log", "output.blg"]
 			return callback(null, true)
-		return callback(null, false)+		return callback(null, false)


Pass twitter key and secret as params

Pass twitter key and secret as params

--- lib/commands.coffee
+++ lib/commands.coffee
@@ -1,38 +1,60 @@
-minimist = require('minimist')
-sinos    = require('../index.js')
-
-argv = minimist process.argv.slice(2),
+argv = require('minimist') process.argv.slice(2),
   alias:
     search: ['s', 'q', 'query']
-    stream: ['w', 'wait', 'l', 'listen']
+    key:    ['twitter-key', 'twitter-consumer-key']
+    secret: ['twitter-secret', 'twitter-secconsumer-ret']
+    help:   ['h']
+
+sinos = require('../index')
+
+###
+Prints help information
+###
+print_help = ->
+  console.log """
+  usage: sinos-river-scraper-twitter [OPTIONS]
+
+  OPTIONS:
+    -q, --query,
+    -s, --search   Search old tweets
+
+    --twitter-key
+    --key          Twitter consumer key
+
+    --twitter-secret
+    --secret       Twitter consumer secret
+
+    --help, -h     This help
+  """
+  process.exit 0
+
+###
+Check for required params
+###
+check_params = ->
+  unless argv.key? and argv.secret?
+    console.warn "Twitter consumer key and secret must be provided.\n"
+    print_help()

 switch
+  # help
+  when argv.help?
+    print_help()
+
   # search
-  when argv.search
-    sinos.search.on 'error', (err) ->
-      console.error err
+  when argv.search?
+    check_params()
+    {key, secret, query} = argv
+    sinos.search {query, key, secret}, (tweets) ->
+      tweets.on 'error', (err) ->
+        console.error err

-    sinos.search.on 'data', (data) ->
-      console.log 'Measure in %s, at %sh: %sm',
-        data.tweet.created_at,
-        data.measure.time,
-        data.measure.meters
-
-    query = if argv.search is true then sinos.DEFAULT_QUERY else argv.search
-    sinos.search.write query
-
-  # stream
-  when argv.stream
+      tweets.on 'data', (data) ->
+        console.log 'Measure in %s, at %sh: %sm',
+          data.tweet.created_at,
+          data.measure.time,
+          data.measure.meters

   # print help
   else
-    console.log """
-    usage: sinos-river-scraper-twitter [OPTIONS]
-
-    OPTIONS:
-      -q, --query,
-      -s, --search   Search old tweets
-      -w, --wait,
-      -l, --listen,
-      --stream       Wait for new tweets stream
-    """
+    print_help()